Introduction
burnはRust用Deep Learningフレームワークです。
現在アクティブに開発が進められているようで、
今後が期待できるプロダクトです。
公開されているMNISTデモはこちら。
今回はこのburnを用いて、ONNX形式の既存モデルを
burn用モデルに変換して使ってみます。
Burn?
burnは2021年にリリースされた新しめの深層学習フレームワークです。
少し使ってみた感じだと、PyTorchに近い感じです。
burnの特徴は、以下のとおりです。
Tensor
Tensor(テンソル)は、深層学習フレームワークを使う際の
基本的なデータ構造であり、
多次元の数値データを表現するために使用します。
burnでも例によってTensor構造体を使います。
このあたりも既存のフレームワークを使い慣れている人なら
馴染みやすいかと思います。
バックエンド
burnではだいたいの実装がBackendトレイトに基づいており、
バックエンドを切り替えることによりいろいろな実装でTensorの演算を使用できます。
現在は下記のようにTorch、ndarray、wgpuの3種類のバックエンドに対応。
- Torch : CPU・GPUともにサポート
- Ndarray : CPUのみ。no_stdもサポート
- WebGPU : GPU専用。WASMも?
Ndarrayはno_std対応しているので、使用環境の幅が広がりますね。
サンプル付属
ここに、いろいろなサンプルがありますので、すぐにビルドして動作確認できます。
Datasetやimport
burnは機械学習データパイプラインの作成プロセスを
効率化するためのいろいろなデータセット実装や変換ロジック、
データソースを提供します。
また、burn-importは、ONNX形式のモデルを変換し、Burn用にRustコードを生成します。
(今回試すやつ)
その他、burnの情報についてはこことかを参考にしてください。
Environment
- MacBook Pro (13-inch, M1, 2020)
- OS : MacOS 13.5.2
- Python : 3.8.8
- Rust : 1.74.0
Try
では、PyTorchで適当なモデルを作成してONNXでexportし、
そのモデルをburnで変換して使ってみます。
PyTorchで適当なモデルを作成
まずはベースとなるモデルをPyTorchで作成。
↓みたいな適当なCSV(train_data)を学習させ、
Weightを渡したらHeightを推論するモデルを作成します。
Weight,Height
60.3,164.1
59.0,168.5
44.7,172.1
47.9,166.4
57.1,164.7
38.9,163.5
・
・
・
必要なライブラリをインストール。
pip3 install torch numpy pandas
PyTorchで学習&推論するサンプルを作成します。
import torch
import torch.nn as nn
import pandas as pd
import torch.onnx
# データセットの読み込み
df = pd.read_csv('train_data.csv')
# 入力と出力を分割
weights = df['Weight'].values
heights = df['Height'].values
# データの前処理
weights = (weights - weights.mean()) / weights.std() # 標準化
# PyTorchのテンソルに変換
weights = torch.tensor(weights, dtype=torch.float).view(-1, 1)
heights = torch.tensor(heights, dtype=torch.float).view(-1, 1)
# モデルの定義
class RegressionModel(nn.Module):
def __init__(self):
super(RegressionModel, self).__init__()
self.linear = nn.Linear(1, 1)
def forward(self, x):
return self.linear(x)
model = RegressionModel()
# 損失関数と最適化関数の定義
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
# 学習
num_epochs = 1000
for epoch in range(num_epochs):
# forward
outputs = model(weights)
loss = criterion(outputs, heights)
# backwardとパラメータ更新
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 途中結果の表示
if (epoch+1) % 100 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
# モデルの推論
weights = [60.,70.,80.]
test_weights = torch.tensor(weights, dtype=torch.float32).view(-1, 1)
predicted_height = model(test_weights)
for i in range(len(test_weights)):
print(f'Test Sample {i + 1}: Weights = {weights[i]} , Predicted Height={predicted_height[i][0]}')
実行してみます。
結果はともかく作成したモデルで推論が動いてます。
% python3 main.py
Epoch [100/1000], Loss: 526.2801
Epoch [200/1000], Loss: 32.2470
Epoch [300/1000], Loss: 23.5581
Epoch [400/1000], Loss: 23.4052
Epoch [500/1000], Loss: 23.4026
Epoch [600/1000], Loss: 23.4025
Epoch [700/1000], Loss: 23.4025
Epoch [800/1000], Loss: 23.4025
Epoch [900/1000], Loss: 23.4025
Epoch [1000/1000], Loss: 23.4025
Test Sample 1: Weights = 60.0 , Predicted Height=185.85348510742188
Test Sample 2: Weights = 70.0 , Predicted Height=189.27638244628906
Test Sample 3: Weights = 80.0 , Predicted Height=192.69927978515625
ONNXで出力
ONNXはOpen Neural Network eXchangeの略で、
機械学習モデルを表現するためのフォーマットです。
TensorflowやPyTorchなど主要なフレームワークはONNX変換できるので、
burnでそのままimportできます。
さきほどのPyTorchのプログラムを↓のようにしてONNXで出力します。
# ダミーのinputデータを生成
dummy_input = torch.zeros(1, 1) # ダミーのテンソル
# モデルをONNX形式にエクスポート
torch.onnx.export(model, # モデル
dummy_input, # ダミー入力データ
'model.onnx', # 出力ファイル名
export_params=True, # パラメータを含めるかどうか
opset_version=9, # ONNXのバージョン
do_constant_folding=True, # 定数折りたたみを行うかどうか
input_names=['input'], # 入力の名前
output_names=['output']) # 出力の名前
Rust
Cargoでプロジェクトを作成し、さきほどのonnxファイルをコピーしておきます。
% cargo new burn_example && cd burn_example
% mkdir ./src/ptmodel
% cp /<先程のonnxファイルパス>/model.onnx ./src/ptmodel/
Cargo.tomlに依存ライブラリを記述。
build-dependenciesにburn-importを追加するのを忘れずに。
[dependencies]
burn = { version = "0.9.0", features = ["ndarray", "std", "wgpu", "tch", "train"] }
serde = "1.0.189"
[build-dependencies]
burn-import = "0.9.0"
ビルド時にonnxからrsファイルを生成するため、
burn_exampleディレクトリにbuild.rsを作成します。
use burn_import::onnx::{ModelGen, ONNXGraph};
fn main() {
ModelGen::new()
.input("src/ptmodel/model.onnx")
.out_dir("ptmodel/")
.run_from_script();
}
ModelGenを使うことで、指定したパスにある
onnxファイルを任意のディレクトリに出力できます。
上記のbuild.rsで実際にビルドすると、
burn_example/target/debug/build/burn_example-xxx/out/ptmodel
に出力されます。
ptmodel/mod.rsを作成し、
ModelGenで出力したmodel.rsファイルをincludeマクロで読み込みます。
pub mod model {
include!(concat!(env!("OUT_DIR"), "/ptmodel/model.rs"));
}
main.rsではbuild.rsで生成されるrsファイルを使ってコードを記述します。
mod ptmodel;
use ptmodel::model::*;
use burn::tensor::Tensor;
use burn::backend::NdArrayBackend;
type Backend = NdArrayBackend;
fn main() {
// Create Model
let model: Model<Backend> = Model::default();
// Create a new input tensor
let input = Tensor::<NdArrayBackend<f32>, 2>::from_data([[60.],[70.],[80.]]);
// Run the model
let output = model.forward(input);
// Print the output
println!("{:?}", output);
}
実行してみます。推論できてますね。
% cargo run
・
・
・
Tensor { primitive: NdArrayTensor {
array: [[185.85349],[189.27638], [192.69928]],
shape=[3, 1], strides=[1, 1], layout=CFcf (0xf), dynamic ndim=2 } }
ちなみに、main.rsで↓のようにすれば生成されたファイルの中身がみれます。
println!("{}",include_str!(concat!(env!("OUT_DIR"), "/ptmodel/model.rs")));
Summary
今回はburnを使ってonnx形式のモデルを変換して使ってみました。
簡単にモデル変換と推論ができました。
なお、ここにburnのドキュメントがあるのですが、
目次に「8.3. WebAssembly」「8.4. No-Std」などおもしろそうなのがあるので、
(追加されたら)確認してみようかと思います。