burn для Rust: полное руководство с примерами
1. Что это и зачем нужна
Burn — это современный фреймворк для глубокого обучения, написанный на Rust. Он предоставляет полный набор инструментов для создания, обучения и развертывания нейронных сетей с акцентом на производительность, безопасность памяти и гибкость. В отличие от традиционных фреймворков, таких как PyTorch или TensorFlow, Burn использует все преимущества Rust: нулевая стоимость абстракций, гарантии безопасности на этапе компиляции и отсутствие сборщика мусора. Это делает его идеальным выбором для систем, где критичны предсказуемая производительность и низкое потребление ресурсов — например, в робототехнике, встраиваемых системах, высоконагруженных серверах и edge-устройствах.
Burn предлагает уникальную архитектуру, основанную на концепции "backend-нейтральности". Это означает, что один и тот же код может выполняться на CPU (через собственный тензорный движок), GPU (через CUDA/WGPU), а в будущем — на специализированных ускорителях. Фреймворк поддерживает автоматическое дифференцирование, оптимизаторы (Adam, SGD, AdamW), функции потерь, слои (Linear, Conv2d, BatchNorm, Dropout и др.), а также встроенные датасеты и DataLoader. Ключевая особенность — возможность компилировать модели в статические библиотеки для развертывания без зависимостей от Python или CUDA runtime.
Зачем использовать Burn? Во-первых, для проектов, где требуется максимальная производительность и контроль над памятью. Во-вторых, для создания кроссплатформенных решений, работающих на CPU, GPU и даже WebAssembly. В-третьих, для безопасного развертывания моделей в production, где ошибки времени выполнения (например, null pointer или type mismatch) недопустимы. Burn активно развивается сообществом и уже используется в таких областях, как компьютерное зрение, NLP, рекомендательные системы и обучение с подкреплением.
2. Установка
Для установки Burn требуется Rust (рекомендуется версия 1.75 или новее). Если Rust еще не установлен, воспользуйтесь rustup.
Создайте новый проект и добавьте зависимости в Cargo.toml:
cargo new my_burn_project
cd my_burn_project
Добавьте в Cargo.toml:
[dependencies]
burn = { version = "0.15", features = ["ndarray"] } # для CPU
# или для GPU (CUDA):
# burn = { version = "0.15", features = ["cuda"] }
# или для WGPU (кроссплатформенный GPU):
# burn = { version = "0.15", features = ["wgpu"] }
Затем выполните:
cargo build
Для работы с CUDA потребуется установленный CUDA Toolkit 11.x или 12.x. Для WGPU — Vulkan, Metal или DirectX 12 (в зависимости от ОС).
3. Быстрый старт — минимальный рабочий пример
Создадим простую нейронную сеть для задачи регрессии на синтетических данных. Этот пример демонстрирует основные шаги: определение модели, создание данных, обучение и инференс.
use burn::{
module::Module,
nn::{Linear, Relu},
tensor::{backend::Backend, Tensor},
train::{Adam, Learner},
data::dataloader::DataLoaderBuilder,
};
// Определяем структуру модели
#[derive(Module, Debug)]
struct MyModel {
linear1: Linear,
linear2: Linear,
activation: Relu,
}
impl MyModel {
fn new() -> Self {
Self {
linear1: Linear::new(1, 16),
linear2: Linear::new(16, 1),
activation: Relu::new(),
}
}
fn forward(&self, input: Tensor) -> Tensor {
let x = self.linear1.forward(input);
let x = self.activation.forward(x);
self.linear2.forward(x)
}
}
fn main() {
// Используем backend на CPU
type B = burn::backend::NdArray;
// Создаем модель
let model = MyModel::::new();
// Генерируем синтетические данные: y = 2*x + 1 + шум
let x = Tensor::::from_data([[1.0], [2.0], [3.0], [4.0], [5.0]]);
let y = Tensor::::from_data([[3.0], [5.0], [7.0], [9.0], [11.0]]);
// Создаем оптимизатор Adam
let mut optimizer = Adam::new(0.01);
// Обучаем 100 эпох
for epoch in 0..100 {
let output = model.forward(x.clone());
let loss = (output.clone() - y.clone()).powf(2.0).mean();
// Обратное распространение
let grads = loss.backward();
optimizer.step(&model, &grads);
if epoch % 10 == 0 {
println!("Epoch {}: Loss = {:.4}", epoch, loss.into_scalar());
}
}
// Инференс
let test_input = Tensor::::from_data([[6.0]]);
let prediction = model.forward(test_input);
println!("Prediction for x=6: {:.2}", prediction.into_data().to_vec::()[0]);
}
Этот пример показывает, как легко создать и обучить модель в Burn. Обратите внимание на использование дженериков B: Backend — это позволяет переключаться между CPU и GPU без изменения кода модели.
4. Основные методы/функции/классы
4.1. Module — derive-макрос для нейросетевых модулей
Сигнатура: #[derive(Module)]
Что делает: Автоматически реализует трейт Module для структуры, позволяя управлять параметрами, сериализацией и перемещением между устройствами.
#[derive(Module, Debug)]
struct MyModule {
linear: Linear,
dropout: Dropout,
}
4.2. Tensor::from_data — создание тензора из данных
Сигнатура: pub fn from_data(data: impl Into>) -> Tensor
Что делает: Создает тензор заданной размерности из предоставленных данных. Данные могут быть массивом, вектором или срезом.
let t = Tensor::::from_data([[1.0, 2.0], [3.0, 4.0]]);
4.3. Linear::new — полносвязный слой
Сигнатура: pub fn new(d_input: usize, d_output: usize) -> Self
Что делает: Создает линейный (полносвязный) слой с заданным количеством входных и выходных нейронов. Инициализирует веса и смещения.
let layer = Linear::::new(64, 128);
4.4. Conv2d::new — двумерный сверточный слой
Сигнатура: pub fn new(in_channels: usize, out_channels: usize, kernel_size: usize) -> Self
Что делает: Создает 2D сверточный слой для обработки изображений. Можно задать размер ядра, шаг и паддинг.
let conv = Conv2d::::new(3, 32, 3); // 3 входных канала, 32 выходных, ядро 3x3
4.5. Adam::new — оптимизатор Adam
Сигнатура: pub fn new(learning_rate: f64) -> Self
Что делает: Создает оптимизатор Adam с заданной скоростью обучения. Можно дополнительно настроить параметры beta1, beta2 и epsilon.
let optimizer = Adam::new(0.001);
4.6. Tensor::backward — обратное распространение
Сигнатура: pub fn backward(&self) -> Gradients
Что делает: Вычисляет градиенты для всех параметров, от которых зависит данный тензор. Возвращает структуру Gradients, содержащую градиенты.
let loss = (output - target).powf(2.0).mean();
let grads = loss.backward();
4.7. DataLoaderBuilder — построитель загрузчика данных
Сигнатура: pub fn new(dataset: D) -> DataLoaderBuilder
Что делает: Создает загрузчик данных с возможностью настройки размера батча, перемешивания и многопоточной загрузки.
let dataloader = DataLoaderBuilder::new(dataset)
.batch_size(32)
.shuffle(true)
.num_workers(4)
.build();
4.8. CrossEntropyLoss — функция потерь кросс-энтропии
Сигнатура: pub fn new() -> Self
Что делает: Создает функцию потерь для задач многоклассовой классификации. Применяет softmax к выходным логитам и вычисляет кросс-энтропию.
let loss_fn = CrossEntropyLoss::::new();
let loss = loss_fn.forward(output, targets);
4.9. Tensor::matmul — матричное умножение
Сигнатура: pub fn matmul(&self, other: &Tensor) -> Tensor
Что делает: Выполняет матричное умножение двух двумерных тензоров. Аналог torch.mm или np.dot.
let a = Tensor::::from_data([[1.0, 2.0], [3.0, 4.0]]);
let b = Tensor::::from_data([[5.0], [6.0]]);
let c = a.matmul(&b);
4.10. Module::save / Module::load — сериализация модели
Сигнатура: pub fn save
pub fn load
Что делает: Сохраняет и загружает параметры модели в/из файла. Использует формат, совместимый с serde.
// Сохранение
let mut file = File::create("model.bin").unwrap();
model.save(&mut file).unwrap();
// Загрузка
let mut file = File::open("model.bin").unwrap();
let loaded_model = MyModel::::load(&mut file).unwrap();
5. Реальный практический пример
Разработаем полную систему для классификации рукописных цифр (MNIST) с использованием Burn. Пример включает: загрузку данных, определение CNN, обучение, валидацию и сохранение модели.
use burn::{
module::Module,
nn::{Conv2d, Linear, Relu, MaxPool2d, Dropout},
tensor::{backend::Backend, Tensor},
train::{Adam, Learner},
data::{dataset::Dataset, dataloader::DataLoaderBuilder},
record::{FullPrecisionSettings, NamedMpkFileRecorder},
};
// Определяем модель CNN
#[derive(Module, Debug)]
struct MnistModel {
conv1: Conv2d,
conv2: Conv2d,
pool: MaxPool2d,
fc1: Linear,
fc2: Linear,
dropout: Dropout,
activation: Relu,
}
impl MnistModel {
fn new() -> Self {
Self {
conv1: Conv2d::new(1, 32, 3), // 1 канал -> 32, ядро 3x3
conv2: Conv2d::new(32, 64, 3), // 32 -> 64, ядро 3x3
pool: MaxPool2d::new(2), // пулинг 2x2
fc1: Linear::new(64 * 5 * 5, 128), // после пулинга: 64*5*5
fc2: Linear::new(128, 10), // 10 классов
dropout: Dropout::new(0.5),
activation: Relu::new(),
}
}
fn forward(&self, input: Tensor) -> Tensor {
let batch_size = input.dims()[0];
let x = input.reshape([batch_size, 1, 28, 28]); // [N, 1, 28, 28]
let x = self.conv1.forward(x);
let x = self.activation.forward(x);
let x = self.pool.forward(x);
let x = self.conv2.forward(x);
let x = self.activation.forward(x);
let x = self.p