m2cgen: 機械学習モデルの多言語トランスパイル

近年のAI(人工知能)の発展は目覚ましく、様々な分野でその技術が活用されています。機械学習モデルの構築は、その中心的な役割を担っており、Pythonはその開発言語として広く利用されています。scikit-learnなどのライブラリを用いることで、比較的容易に学習済みモデルを作成することが可能です。
しかし、学習済みモデルを実際に組み込みシステムやWebフロントエンド環境、あるいはPython以外のバックエンド言語(RubyやGo言語など)でデプロイしようとすると、様々な課題に直面します。
例えば、組み込みシステムでは、計算資源やメモリが限られているため、Pythonで学習させたモデルをそのまま動作させることは困難です。また、Webフロントエンドでは、JavaScriptでモデルを実行する必要があるため、Pythonで学習させたモデルをJavaScriptに変換する必要があります。さらに、Python以外のバックエンド言語でモデルをデプロイする場合、言語間の互換性の問題や、機械学習モデルのアルゴリズムに関する深い理解が必要となる場合があります。
これらの課題を解決するために、機械学習モデルを様々な言語に変換するツールが開発されています。本記事では、その中でも「m2cgen」というツールについて解説します。m2cgenを利用することで、Pythonで学習させたモデルを様々な言語のコードに変換することができます。
m2cgen とは?
m2cgenは、学習済みの機械学習モデルを様々なプログラミング言語のコードに変換するためのツールです。このパッケージを利用することで、Pythonで学習させたモデルをC言語、Java、JavaScript、Goなど、様々な言語で実装し直す手間を省くことができます。
主な特徴としては下記が挙げられます。
- 多様な言語への変換: Pythonで学習させたモデルを、C、Java、JavaScript、Go、C#、R、PHP、Dart、Haskell、Ruby、F#、Rustなど、様々なプログラミング言語のコードに変換できます。
- 軽量: 外部の依存関係が少ないため、軽量に動作します。
- 使いやすさ: シンプルなAPIで、簡単にモデルをコードに変換できます。
m2cgenは、以下のような場合に特に役立ちます。
- 組み込みシステムへの実装: 組み込みシステムでは、メモリや計算資源が限られているため、軽量なコードでモデルを実行する必要があります。m2cgenを使えば、Pythonで学習させたモデルをC言語などの軽量なコードに変換し、組み込みシステムに実装することができます。
- Webアプリケーションへの統合: Webアプリケーションでは、JavaScriptでモデルを実行する必要がある場合があります。m2cgenを使えば、Pythonで学習させたモデルをJavaScriptのコードに変換し、Webアプリケーションに統合することができます。
- パフォーマンスの向上: 特定の言語で実装することで、Pythonで直接モデルを実行するよりもパフォーマンスが向上する場合があります。
m2cgenがサポートするプログラミング言語は下記のものになります。
- C言語
- C#
- Dart
- F#
- Go
- Haskell
- Java
- JavaScript
- PHP
- PowerShell
- Python
- R
- Ruby
- Rust
- Visual Basic (VBA-compatible)
- Elixir
また、m2cgenは以下のモデルをサポートしています。
分類モデル
| タイプ | ライブラリ | モデル |
|---|---|---|
| 線形 | scikit-learn | LogisticRegression |
| LogisticRegressionCV | ||
| PassiveAggressiveClassifier | ||
| Perceptron | ||
| RidgeClassifier | ||
| RidgeClassifierCV | ||
| SGDClassifier | ||
| sklearn-contrib-lightning | AdaGradClassifier | |
| CDClassifier | ||
| FistaClassifier | ||
| SAGAClassifier | ||
| SAGClassifier | ||
| SDCAClassifier | ||
| SGDClassifier | ||
| SVM | scikit-learn | LinearSVC |
| NuSVC | ||
| OneClassSVM | ||
| SVC | ||
| sklearn-contrib-lightning | KernelSVC | |
| LinearSVC | ||
| Tree | scikit-learn | DecisionTreeClassifier |
| ExtraTreeClassifier | ||
| Random Forest | scikit-learn | ExtraTreesClassifier |
| RandomForestClassifier | ||
| lightgbm | LGBMClassifier(rf booster only) | |
| xgboost | XGBRFClassifier | |
| Boosting | lightgbm | LGBMClassifier(gbdt/dart/goss booster only) |
| xgboost | XGBClassifier(gbtree(including boosted forests)/gblinear booster only) |
回帰モデル
| タイプ | ライブラリ | モデル |
|---|---|---|
| 線形 | scikit-learn | ARDRegression |
| BayesianRidge | ||
| ElasticNet | ||
| ElasticNetCV | ||
| GammaRegressor | ||
| HuberRegressor | ||
| Lars | ||
| LarsCV | ||
| Lasso | ||
| LassoCV | ||
| LassoLars | ||
| LassoLarsCV | ||
| LassoLarsIC | ||
| LinearRegression | ||
| OrthogonalMatchingPursuit | ||
| OrthogonalMatchingPursuitCV | ||
| PassiveAggressiveRegressor | ||
| PoissonRegressor | ||
| RANSACRegressor(only supported regression estimators can be used as a base estimator) | ||
| Ridge | ||
| RidgeCV | ||
| SGDRegressor | ||
| TheilSenRegressor | ||
| TweedieRegressor | ||
| StatsModels | Generalized Least Squares (GLS) | |
| Generalized Least Squares with AR Errors (GLSAR) | ||
| Generalized Linear Models (GLM) | ||
| Ordinary Least Squares (OLS) | ||
| [Gaussian] Process Regression Using Maximum Likelihood-based Estimation (ProcessMLE) | ||
| Quantile Regression (QuantReg) | ||
| Weighted Least Squares (WLS) | ||
| sklearn-contrib-lightning | AdaGradRegressor | |
| CDRegressor | ||
| FistaRegressor | ||
| SAGARegressor | ||
| SAGRegressor | ||
| SDCARegressor | ||
| SGDRegressor | ||
| SVM | scikit-learn | LinearSVR |
| NuSVR | ||
| SVR | ||
| sklearn-contrib-lightning | LinearSVR | |
| Tree | scikit-learn | DecisionTreeRegressor |
| ExtraTreeRegressor | ||
| Random Forest | scikit-learn | ExtraTreesRegressor |
| RandomForestRegressor | ||
| lightgbm | LGBMRegressor(rf booster only) | |
| xgboost | XGBRFRegressor | |
| Boosting | lightgbm | LGBMRegressor(gbdt/dart/goss booster only) |
| xgboost | XGBRegressor(gbtree(including boosted forests)/gblinear booster only) |
m2cgen の使い方
では、ここからは m2cgen を実際に使用して、機械学習モデルをトランスパイルしてみましょう。初めに、必要なパッケージをインストールします。
# m2cgen をインストール
$ pip install m2cgen
# 今回は scikit-learn のモデルをトランスパイル対象にします
$ pip install scikit-learn
この文章の執筆時点では、m2cgenの更新は数年間行われていません。もし最新版のscikit-learnでエラーが発生する場合は、scikit-learnのバージョンを下げるなどの対応が必要となります。
今回は scikit-learn に同梱されている Irisデータセット を使用して、DecisionTreeClassifierを学習させます。まずは、使用するパッケージをインポートしておきます。
import m2cgen as m2c from sklearn.datasets import load_iris from sklearn.tree import DecisionTreeClassifier
次に、データセットを読み込んで中身を確認しましょう。以下のように、4つの説明変数から構成されています。
datasets = load_iris(as_frame=True) print(datasets["data"])
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
0 5.1 3.5 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 1.3 0.2
3 4.6 3.1 1.5 0.2
4 5.0 3.6 1.4 0.2
.. ... ... ... ...
145 6.7 3.0 5.2 2.3
146 6.3 2.5 5.0 1.9
147 6.5 3.0 5.2 2.0
148 6.2 3.4 5.4 2.3
149 5.9 3.0 5.1 1.8
[150 rows x 4 columns]
合わせて、目的変数も見ておきましょう。次のように、3クラスのラベルが付与されていることが確認できます。
print(datasets["target"])
0 0
1 0
2 0
3 0
4 0
..
145 2
146 2
147 2
148 2
149 2
Name: target, Length: 150, dtype: int64
通常は、訓練データとテストデータに分割して機械学習モデルを構築しますが、今回はトランスパイルの手順を解説するだけなので、データセットのすべてを訓練データに使用します。
モデルのハイパーパラメータについても、通常は任意の探索アルゴリズムを使用して決定しますが。、ここでは適当に設定します。
tree = DecisionTreeClassifier(
criterion="gini",
splitter="best",
max_depth=None,
min_samples_split=2,
max_features="sqrt",
)
# 訓練
tree.fit(datasets["data"], datasets["target"])
# 一応、精度も確認
print(f"Accuracy: {tree.score(datasets['data'], datasets['target'])}")
Accuracy: 1.0
以上でトランスパイル対象の機械学習モデルを準備できたので、ここから様々なプログラミング言語に変換していきます。
まずは、Java のコードを見て見ましょう。
java_code = m2c.export_to_java(
model=tree,
package_name=None,
class_name="Model",
indent=4,
function_name="score",
)
print(java_code)
出力されたJavaコード (クリックして展開)
public class Model {
public static double[] score(double[] input) {
double[] var0;
if (input[3] <= 0.800000011920929) {
var0 = new double[] {1.0, 0.0, 0.0};
} else {
if (input[0] <= 6.1499998569488525) {
if (input[3] <= 1.6500000357627869) {
if (input[0] <= 5.950000047683716) {
var0 = new double[] {0.0, 1.0, 0.0};
} else {
if (input[1] <= 2.649999976158142) {
if (input[3] <= 1.199999988079071) {
var0 = new double[] {0.0, 1.0, 0.0};
} else {
var0 = new double[] {0.0, 0.0, 1.0};
}
} else {
var0 = new double[] {0.0, 1.0, 0.0};
}
}
} else {
if (input[2] <= 4.8500001430511475) {
if (input[1] <= 3.100000023841858) {
var0 = new double[] {0.0, 0.0, 1.0};
} else {
var0 = new double[] {0.0, 1.0, 0.0};
}
} else {
var0 = new double[] {0.0, 0.0, 1.0};
}
}
} else {
if (input[3] <= 1.75) {
if (input[2] <= 5.049999952316284) {
var0 = new double[] {0.0, 1.0, 0.0};
} else {
var0 = new double[] {0.0, 0.0, 1.0};
}
} else {
var0 = new double[] {0.0, 0.0, 1.0};
}
}
}
return var0;
}
}
続けて、Rubyのコードも見てみましょう。
ruby_code = m2c.export_to_ruby(
model=tree,
indent=2,
function_name="score"
)
print(ruby_code)
出力されたRubyコード (クリックして展開)
def score(input)
if input[3] <= 0.800000011920929
var0 = [1.0, 0.0, 0.0]
else
if input[0] <= 6.1499998569488525
if input[3] <= 1.6500000357627869
if input[0] <= 5.950000047683716
var0 = [0.0, 1.0, 0.0]
else
if input[1] <= 2.649999976158142
if input[3] <= 1.199999988079071
var0 = [0.0, 1.0, 0.0]
else
var0 = [0.0, 0.0, 1.0]
end
else
var0 = [0.0, 1.0, 0.0]
end
end
else
if input[2] <= 4.8500001430511475
if input[1] <= 3.100000023841858
var0 = [0.0, 0.0, 1.0]
else
var0 = [0.0, 1.0, 0.0]
end
else
var0 = [0.0, 0.0, 1.0]
end
end
else
if input[3] <= 1.75
if input[2] <= 5.049999952316284
var0 = [0.0, 1.0, 0.0]
else
var0 = [0.0, 0.0, 1.0]
end
else
var0 = [0.0, 0.0, 1.0]
end
end
end
var0
end
最後はC言語にトランスパイルして、実際に生成されたコードを使ってみましょう。
c_code = m2c.export_to_c(
model=tree,
indent=4,
function_name="score",
)
print(c_code)
# コードをファイルに出力
with open("iris.c", "w") as f:
f.write(c_code)
出力されたC言語のコード (クリックして展開)
#include <string.h>
void score(double * input, double * output) {
double var0[3];
if (input[3] <= 0.800000011920929) {
memcpy(var0, (double[]){1.0, 0.0, 0.0}, 3 * sizeof(double));
} else {
if (input[0] <= 6.1499998569488525) {
if (input[3] <= 1.6500000357627869) {
if (input[0] <= 5.950000047683716) {
memcpy(var0, (double[]){0.0, 1.0, 0.0}, 3 * sizeof(double));
} else {
if (input[1] <= 2.649999976158142) {
if (input[3] <= 1.199999988079071) {
memcpy(var0, (double[]){0.0, 1.0, 0.0}, 3 * sizeof(double));
} else {
memcpy(var0, (double[]){0.0, 0.0, 1.0}, 3 * sizeof(double));
}
} else {
memcpy(var0, (double[]){0.0, 1.0, 0.0}, 3 * sizeof(double));
}
}
} else {
if (input[2] <= 4.8500001430511475) {
if (input[1] <= 3.100000023841858) {
memcpy(var0, (double[]){0.0, 0.0, 1.0}, 3 * sizeof(double));
} else {
memcpy(var0, (double[]){0.0, 1.0, 0.0}, 3 * sizeof(double));
}
} else {
memcpy(var0, (double[]){0.0, 0.0, 1.0}, 3 * sizeof(double));
}
}
} else {
if (input[3] <= 1.75) {
if (input[2] <= 5.049999952316284) {
memcpy(var0, (double[]){0.0, 1.0, 0.0}, 3 * sizeof(double));
} else {
memcpy(var0, (double[]){0.0, 0.0, 1.0}, 3 * sizeof(double));
}
} else {
memcpy(var0, (double[]){0.0, 0.0, 1.0}, 3 * sizeof(double));
}
}
}
memcpy(output, var0, 3 * sizeof(double));
}
次に、生成したコードを呼び出すコードを準備します。
/*
* main.c
*/
#include <stdio.h>
#include <stdlib.h>
#define FEATURE_COUNT (4) // 特徴量の個数
#define CLASS_COUNT (3) // 分類クラスの個数
// 生成されたコードをプロトタイプ宣言しておく
extern void score(double * input, double * output);
static int max_index_of(const double output[]) {
int index = 0;
for (int i = 0; i < CLASS_COUNT; i++) {
if (output[index] < output[i]) {
index = i;
}
}
return index;
}
int main(int argc, char** argv) {
double input[FEATURE_COUNT] = {6.2, 3.4, 5.4, 2.3};
double output[CLASS_COUNT] = {0.0, 0.0, 0.0};
// 生成されたコードを実行
score(&input[0], &output[0]);
// 出力は、それぞれのクラスである確率の推定値となる
printf("Score: [%f, %f, %f]\n", output[0], output[1], output[2]);
// もっとも大きいインデックスが推定されたクラスに対応する
printf("Class: %i\n", max_index_of((const double *)&output[0]));
return EXIT_SUCCESS;
}
次は、これをコンパイルして実行してみましょう。
# GCCでコンパイル
$ gcc -Wall -std=c99 -o main main.c iris.c
# プログラムの実行
$ ./main
Score: [0.0, 0.0, 1.0]
Class: 2
以上のように、学習済みの機械学習モデルがあれば様々なプログラミング言語にトランスパイルできることが確認できました。今回紹介した言語以外に変換したい場合は、GitHub上のコード(m2cgen/exporters.py)を見て使い方を確認して下さい。
なお、トランスパイルの実行で RecursionError が発生する場合は、次のように再帰回数の上限を変更してください。
import sys # 上限回数は適宜変更のこと sys.setrecursionlimit(5000)
おわりに
今回は、機械学習モデルを様々なプログラミング言語にトランスパイルできる m2cgen というパッケージを紹介しました。
このパッケージは多くの機械学習モデルをサポートしているものの、近年トレンドとなっている深層学習には対応していません。もし深層学習のモデルをトランスパイルしたい場合、C言語であれば emlearn と呼ばれるパッケージが利用できるので、興味があればご確認ください。なお、emlearn が対応している深層学習モデルは、Kerasの全結合層のみで構築されたものに限定されます。