Для чего нужна данная статья? :
Создать простую функцию на Rust, которая будет доступна в Python.
Обучить модель ML для предсказаний параметров.
Зачем Вам это уметь? :
Найти альтернативы между разными подходами:
PyO3 для создания Python-модулей на Rust
- Описание: pyo3 позволяет писать нативные модули на Rust, которые могут быть импортированы и использованы в Python.
- Пример:Написание функций и структур на Rust.
Экспортирование их как модуля, доступного для использования в Python. - Применение: Высокопроизводительные вычисления, расширение функциональности Python, выполнение кода на Rust внутри Python.
PyO3 с maturin
- Описание: maturin — это инструмент, который упрощает процесс упаковки и распространения Python-модулей на основе Rust.
- Пример:Создание и сборка Python-модуля на Rust.
Упаковка модуля для публикации в PyPI или локальной установки. - Применение: Легкость упаковки и распространения Python-модулей, написанных на Rust.
setuptools-rust
- Описание: Расширение setuptools, которое позволяет интегрировать сборку модулей на Rust непосредственно в процесс сборки Python-пакетов.
- Пример:Написание Python-модуля с нативными расширениями на Rust.
Интеграция сборки через setup.py. - Применение: Упрощение сборки Rust-модулей в процессе сборки и установки Python-пакетов.
rust-cpython
- Описание: Библиотека, предоставляющая альтернативный интерфейс для взаимодействия с Python из Rust.
- Пример:Написание Python-расширения на Rust с использованием rust-cpython.
Экспортирование функций и типов для использования в Python. - Применение: Аналогично pyo3, но с немного другой парадигмой и API.
FFI для взаимодействия с Rust
- Описание: Использование Foreign Function Interface (FFI) для вызова функций, написанных на Rust, из Python через библиотеки, такие как ctypes или cffi.
- Пример:Написание библиотеки на Rust, компиляция её в динамическую библиотеку.
Импортирование и использование библиотеки в Python через ctypes или cffi. - Применение: Вызов отдельных функций на Rust без создания полного Python-модуля.
PyO3 совместно с PyOxidizer
- Описание: PyOxidizer — инструмент, который позволяет собирать standalone-приложения на Python, включая модули на Rust.
- Пример:Написание приложения на Python с нативными расширениями на Rust.
Сборка standalone-исполняемого файла с использованием PyOxidizer. - Применение: Создание самодостаточных бинарных файлов с вшитым Python-интерпретатором и Rust-модулями.
PyScript для интеграции Python в Rust веб-приложениях
- Описание: PyScript — это инструмент для запуска Python-кода прямо в браузере, который может взаимодействовать с Rust через WebAssembly.
- Пример:Создание фронтенд-приложения на Rust (через WebAssembly) и Python (через PyScript).
Взаимодействие между ними на клиентской стороне. - Применение: Создание гибридных веб-приложений с использованием Rust и Python.
Пример
Создайте новый проект с поддержкой pyo3.
cargo new --lib rust_py_extension
cd rust_py_extension
Добавьте зависимости в Cargo.toml:
[dependencies]
pyo3 = { version = "0.18", features = ["extension-module"] }
Напишите код в src/lib.rs:
use pyo3::prelude::*;
// Добавляем функцию `sum_as_string`, которая складывает два числа и возвращает результат как строку
#[pyfunction]
fn sum_as_string(a: i32, b: i32) -> PyResult<String> {
Ok((a + b).to_string())
}
// Модуль Python с функцией `sum_as_string`
#[pymodule]
fn rust_py_extension(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
Ok(())
}
Установите maturin — инструмент, который упрощает сборку и упаковку расширений на Rust для Python.
pip install maturin
Соберите и установите расширение:
maturin develop
Теперь, когда расширение собрано и установлено, его можно использовать в Python. Создайте Python-скрипт test.py:
import rust_py_extension
result = rust_py_extension.sum_as_string(5, 10)
print(f"Result of 5 + 10: {result}")
Запустите test.py:
python test.py
Обучение модели машинного обучения в Python с использованием библиотеки scikit-learn, с загрузкой в Rust с помощью библиотеки tract для выполнения предсказаний.
Установите необходимые библиотеки:
pip install scikit-learn skl2onnx pandas numpy
Создайте новый проект Rust и добавьте зависимости в Cargo.toml:
cargo new rust_ml_example
cd rust_ml_example
Обновите Cargo.toml:
[dependencies]
tract-onnx = "0.15.0"
csv = "1.1"
tract-ndarray = "0.15.0"
log = "0.4"
env_logger = "0.9"
rayon = "1.5"
Этот код обучает нейронную сеть, выполняет нормализацию данных, конвертирует модель в ONNX и сохраняет тестовые данные и параметры нормализации.
Сохраните следующий код в файл, например, train_mlp.py:
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import StandardScaler
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType
import pandas as pd
import pickle
# Загрузка датасета Iris
iris = load_iris()
X, y = iris.data, iris.target
# Разделение на тренировочные и тестовые данные
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Нормализация данных
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# Обучение нейронной сети
model = MLPClassifier(hidden_layer_sizes=(10, 10), max_iter=1000, random_state=42)
model.fit(X_train_scaled, y_train)
# Конвертация модели в ONNX
initial_type = [('float_input', FloatTensorType([None, 4]))]
onnx_model = convert_sklearn(model, initial_types=initial_type)
# Сохранение модели в файл
with open("mlp.onnx", "wb") as f:
f.write(onnx_model.SerializeToString())
# Сохранение тестовых данных в CSV
test_data = pd.DataFrame(X_test, columns=iris.feature_names)
test_data['target'] = y_test
test_data.to_csv('test_data.csv', index=False)
# Сохранение параметров нормализации
with open('scaler_params.pkl', 'wb') as f:
pickle.dump((scaler.mean_, scaler.scale_), f)
print("Модель обучена, конвертирована в ONNX и сохранена. Тестовые данные и параметры нормализации сохранены.")
Этот код загружает модель ONNX, читает тестовые данные и параметры нормализации, выполняет нормализацию на Rust, реализует многопоточный инференс, добавляет логирование и обработку ошибок.
Сохраните следующий код в src/main.rs:
use std::error::Error;
use std::fs::File;
use std::io::Read;
use tract_onnx::prelude::*;
use csv::Reader;
use tract_ndarray::Array;
use log::{info, error};
use env_logger;
use rayon::prelude::*;
use std::sync::Arc;
fn main() -> Result<(), Box<dyn Error>> {
// Инициализация логирования
env_logger::init();
info!("Запуск программы");
// Загрузка параметров нормализации
let mut file = File::open("scaler_params.pkl")?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
let (mean, scale): (Vec<f64>, Vec<f64>) = bincode::deserialize(&buffer).unwrap();
let mean: Vec<f32> = mean.into_iter().map(|x| x as f32).collect();
let scale: Vec<f32> = scale.into_iter().map(|x| x as f32).collect();
info!("Параметры нормализации загружены: mean={:?}, scale={:?}", mean, scale);
// Загрузка модели ONNX
let model = tract_onnx::onnx()
.model_for_path("mlp.onnx")?
.with_input_fact(0, InferenceFact::dt_shape(f32::datum_type(), tvec!(1, 4)))?
.into_optimized()?
.into_runnable()?;
let model = Arc::new(model);
info!("Модель ONNX загружена и оптимизирована");
// Чтение тестовых данных из CSV
let mut rdr = Reader::from_path("test_data.csv")?;
let records: Vec<_> = rdr.records().collect::<Result<_, _>>()?;
info!("Тестовые данные загружены: {} записей", records.len());
// Многопоточный инференс
let predictions: Vec<_> = records.par_iter().map(|record| {
// Извлечение признаков
let features: Vec<f32> = record.iter()
.take(4)
.map(|s| s.parse::<f32>().unwrap())
.collect();
// Нормализация на Rust
let normalized: Vec<f32> = features.iter()
.zip(&mean)
.zip(&scale)
.map(|((x, m), s)| (x - m) / s)
.collect();
// Преобразование в тензор
let input = Array::from_shape_vec((1, 4), normalized).unwrap().into_tensor();
// Выполнение предсказания
let output = model.run(tvec!(input)).unwrap();
let prediction = output[0].to_array_view::<f32>().unwrap().iter().map(|x| x.round() as i32).collect::<Vec<_>>();
let predicted_class = prediction.iter().enumerate().max_by_key(|&(_, &val)| val).unwrap().0 as i32;
// Извлечение истинной метки
let target: i32 = record[4].parse::<i32>().unwrap();
(predicted_class, target)
}).collect();
// Вычисление точности
let correct = predictions.iter().filter(|&&(pred, target)| pred == target).count();
let total = predictions.len();
let accuracy = correct as f32 / total as f32;
info!("Точность модели: {:.2}%", accuracy * 100.0);
// Вывод предсказаний
for (i, (pred, target)) in predictions.iter().enumerate() {
info!("Образец {}: Предсказание = {}, Истинная метка = {}", i + 1, pred, target);
}
Ok(())
}
- Сохраните Python-код в файл, например, train_model.py.
- Выполните:
python train_model.py
Это создаст файлы mlp.onnx, test_data.csv и scaler_params.pkl.
- Убедитесь, что файлы mlp.onnx, test_data.csv и scaler_params.pkl находятся в корневой директории проекта Rust.
- Скомпилируйте и запустите проект.