Найти в Дзене
Один Rust не п...Rust

Как использовать &self, &mut self, self и mut self — в ML

Повысить производительность в машинном обучении: &self и &mut self позволяют работать с одними и теми же данными в памяти (например, в GPU через CUDA), просто передавая ссылки на них.
Повысить безопасность: &mut self — гарантирует, что в
любой момент времени только один поток может изменять данные, что
полностью исключает ошибки многопоточности. Так же &mut self предотвращает одновременные мутации при многопоточном обучении.
1. &self: Неизменяемое заимствование
"Посмотреть, но не трогать" (Неизменяемое заимствование) use ndarray::{array, Axis}; let array = array![[1., 2.], [3., 4.], [5., 6.]]; assert_eq!(array.nrows(), 3); // Uses &self internally В тензоре tch: метод numel(&self) подсчитывает общее количество элементов в тензоре, что необходимо для проверки входных данных в нейронных сетях. Фрагмент кода: use tch::Tensor; let tensor = Tensor::zeros(&[2, 3], (tch::Kind::Float, tch::Device::Cpu)); assert_eq!(tensor.numel(), 6); // Read-only access via &self Общая схема в машинно

Для чего нужна данная статья?

Повысить производительность в машинном обучении: &self и &mut self позволяют работать с одними и теми же данными в памяти (например, в GPU через CUDA), просто передавая ссылки на них.

Повысить безопасность: &mut self — гарантирует, что в
любой момент времени только один поток может изменять данные, что
полностью исключает ошибки многопоточности. Так же &mut self предотвращает одновременные мутации при многопоточном обучении.

1. &self: Неизменяемое заимствование

"Посмотреть, но не трогать" (Неизменяемое заимствование)

  • Аналогия: Дать кому-то прочитать страницу книги, но не разрешать делать пометки.
  • Что делает: Даёт доступ к чтению данных, но не позволяет их изменять.
  • Зачем в ML:
    Производительность:

    Можно одновременно "читать" данные из многих мест (например, в
    нескольких потоках), не создавая их копий. Это "представления с нулевым копированием".
    Безопасность: Полная гарантия, что исходные данные не изменятся случайно.
  • Примеры:
    tensor.numel() — узнать количество элементов в тензоре.
    model.predict(input) — получить прогноз от модели, не меняя её весов.

    Применение: Для запросов, вычислений или представлений, которые считывают данные, но не изменяют их. Идеально подходит для таких задач в машинном обучении, как вычисление статистики (например, среднего значения набора данных) или генерация прогнозов без изменения модели.

    Плюсы: Безопасен для параллельного доступа; нет риска сделать исходный экземпляр недействительным.

    Минусы: Невозможно изменить поля; попытки сделать это приведут к ошибке компиляции.

    Примеры машинного обучения:

    В ArrayBase класса ndarray: Метод nrows(&self) возвращает количество строк в двумерном массиве, что полезно для проверки размерности набора данных перед обучением. Фрагмент кода:

use ndarray::{array, Axis};

let array = array![[1., 2.], [3., 4.], [5., 6.]];

assert_eq!(array.nrows(), 3); // Uses &self internally

В тензоре tch: метод numel(&self) подсчитывает общее количество элементов в тензоре, что необходимо для проверки входных данных в нейронных сетях. Фрагмент кода:

use tch::Tensor;

let tensor = Tensor::zeros(&[2, 3], (tch::Kind::Float, tch::Device::Cpu));

assert_eq!(tensor.numel(), 6); // Read-only access via &self

Общая схема в машинном обучении: используется на этапах оценки модели, где вы можете вызвать predict(&self, input: &Tensor) для обученной модели, не изменяя ее весов.

2. &mut self: изменяемое заимствование

"Дать на редактирование с exclusivным доступом" (Изменяемое заимствование)

  • Аналогия: Дать кому-то страницу книги и красную ручку, но при этом запереть комнату. Пока он работает, никто другой не может даже заглянуть в эту страницу.
  • Что делает: Даёт эксклюзивный доступ для изменения данных.
  • Зачем в ML:
    Безопасность:
    Это главный механизм для безопасного многопоточного обучения. &mut self
    предотвращает ситуацию, когда один поток пытается читать веса модели, а другой в это же время обновляет их (состояние гонки). Компилятор Rust просто не скомпилирует такой код.
    Производительность: Позволяет проводить эффективные операции "на месте" (in-place), без создания лишних копий больших тензоров.
  • Примеры:
    tensor.add_(other) — прибавить другой тензор к текущему, изменив его.
    model.update_weights(gradients) — применить градиенты для обновления параметров модели.

    Применение: Когда методу необходимо изменить состояние экземпляра, но сохранить его для дальнейшего использования. Часто используется в циклах оптимизации (например, градиентный спуск), где тензоры обновляются итеративно.

    Плюсы: Обеспечивает эффективные операции на месте; отсутствует полная передача прав собственности.

    Минусы: Исключительный заём означает, что никакие другие ссылки не могут сосуществовать, что обеспечивает соблюдение правил изменяемости Rust.

    Примеры машинного обучения:
    В ArrayBase ndarray: Метод insert_axis_inplace(&mut self, axis: Axis) добавляет новую ось длиной 1, напрямую изменяя форму и шаги массива — полезно для подготовки данных к пакетной обработке в машинном обучении. Фрагмент кода:

use ndarray::{Axis, arr2};

let mut a = arr2(&[[1, 2, 3], [4, 5, 6]]).into_dyn();

a.insert_axis_inplace(Axis(1)); // Mutates via &mut self

assert_eq!(a.shape(), &[2, 1, 3]);

В тензоре tch: метод internal_amp_non_finite_check_and_unscale(&mut self, found_inf: &mut Tensor, inv_scale: &Tensor) демасштабирует тензор при проверке на бесконечность, что критически важно при обучении со смешанной точностью для предотвращения распространения NaN. (Примечание: это специфично для CUDA, но иллюстрирует операции с изменяемыми тензорами в DL.)
Общая схема в машинном обучении: в циклах обучения такие методы, как update_weights(&mut self, gradients: &Tensor), используют это для применения градиентов без использования модели.

3. self: Владение (неизменяемо по умолчанию)

"Передать право собственности" (Владение)

  • Аналогия: Отдать кому-то книгу навсегда. У вас её больше нет, и вы не можете её изменить.
  • Что делает: Метод "забирает" объект себе. После вызова метода исходный объект становится недоступен.
  • Зачем в ML:
    Для преобразований, когда исходный объект больше не нужен. Это
    позволяет эффективно менять структуру данных без лишнего копирования.
  • Пример:
    array.into_dyn()
    — преобразовать массив фиксированной размерности в массив с
    динамической размерностью. Исходный массив "поглощается" и перестаёт существовать.
    Применение: Для преобразований или операций, когда экземпляр впоследствии не нужен, например, для перемещения данных в новую структуру. В машинном обучении это позволяет избежать клонирования больших массивов.

    Плюсы: Сигнализирует об окончании жизненного цикла экземпляра; эффективно для односторонних преобразований.

    Минусы: Вызывающий объект теряет доступ к исходному объекту; мутации внутри него не допускаются, если только не используются совместно с mut.

    Примеры машинного обучения:

    В ArrayBase ndarray: Метод into_dyn(self) преобразует массив фиксированной размерности в динамический, поглощая исходный объект — удобно для гибких конвейеров машинного обучения, где формы могут меняться. Фрагмент кода:

use ndarray::{arr2, ArrayD};

let array: ArrayD<i32> = arr2(&[[1, 2], [3, 4]]).into_dyn(); // Consumes via self

В tch: Менее распространено для основных операций, но аналогично into_kind(self, kind: Kind) для преобразования типов при предварительной обработке тензоров.
Общий шаблон в машинном обучении: Используется в загрузчиках данных, например, into_dataset(self) для передачи необработанных данных обучающему итератору.

4. mut self: Изменяемое владение

"Передать право собственности с правом редактирования"

  • Аналогия: Отдать книгу навсегда и дать разрешение переписать в ней несколько глав перед тем, как передать её дальше.
  • Что делает: То же, что и self, но позволяет методу изменять объект перед тем, как он будет "потреблён" или возвращён.
  • Зачем в ML:
    Часто используется в шаблоне "Строитель" (Builder Pattern) для
    конфигурации сложных объектов (например, модели) цепочкой вызовов.
  • Пример:
    model_builder.with_layers(layers).with_learning_rate(0.01).build() — здесь методы .with_...() могут использовать mut self, чтобы изменять конфигурацию строителя, и затем возвращать его для следующего вызова в цепочке.

    Когда использовать: Когда владение передано, но метод должен сначала изменить значение (например, для финальной корректировки перед уничтожением). В машинном обучении это встречается в таких шаблонах, как финализация конфигурации модели.

    Плюсы: Сочетает потребление с изменяемостью; полезно для цепочечных API.

    Минусы: Всё ещё потребляет экземпляр; встречается реже, чем ссылки в критичном к производительности коде машинного обучения.

    Примеры машинного обучения:

    В шаблонах сборщиков (распространено в конфигурациях машинного обучения): Как в идиоме сборщика Rust, где промежуточные методы используют mut self для разрешения изменений перед финальной сборкой (self). Хотя это и не относится напрямую к библиотекам машинного обучения, это применимо к таким контейнерам, как linfa для алгоритма params.users.rust-lang.org.
    В ndarray: неявно в некоторых операциях-потребителях, где происходит мутация, но явные примеры редки; аналогично mapv_into(mut self, f: F), которая мутирует элементы перед возвратом. Фрагмент кода:

use ndarray::{Array, Dim};

let mut arr = Array::from_vec(vec![1, 2, 3]).into_shape(Dim([3])).unwrap();

// Hypothetical: a method like fn transform(mut self) -> Self { self.mapv_inplace(|x| x * 2); self }

Общая схема в ML: в построителях моделей, например, with_layers(mut self, layers: Vec<Layer>) для добавления и мутации перед финализацией.