Для чего нужна данная статья?
Повысить производительность в машинном обучении: &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>) для добавления и мутации перед финализацией.