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

Rust в Python

Оглавление

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

Создать простую функцию на 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.
  • Скомпилируйте и запустите проект.