💨
Elixir microsoft/LightGBM クラス分類を試す
この記事はElixir Advent Calendar 2022の1つです。カレンダーも是非ご覧ください!
Elixirでmicrosoft/LightGBMを使って、クラス分類を行う流れの紹介です。
下記2つのライブラリを使います。
-
https://github.com/tato-gh/lgbm_ex_cli
- LightGBM CLIを内部で利用しています
- モデル構築と大規模データの予測に利用します
- CLI単体で使う流れはこちらの記事をご覧ください
-
https://github.com/tato-gh/lgbm_ex_capi
- LightGBM C-APIを内部で利用しています
- データ予測に利用します
- 大規模データになるとCLIの方が早いです(おそらく)
- どちらもNot stableですので、もしお手元で試される場合はご注意ください
クラス分類の対象データとして、Iris Data Setを取り上げます(ElixirのScidataパッケージで簡単に取得できます)。
- ここではクラス分類を取り上げていますが、回帰も同様の流れで可能です。
以下、Livebookを想定して進めていきます
インストール
新しいノートブックを開いたら、Notebook dependencies and setup 欄でインストールします。
lightgbm_cmd = File.cwd!() <> "/LightGBM/lightgbm"
if System.find_executable(lightgbm_cmd) do
"already existing"
else
System.shell("git clone --recursive https://github.com/microsoft/LightGBM")
System.shell("cd LightGBM && mkdir build && cd build && cmake .. && make -j4")
end
Mix.install(
[
{:scidata, "~> 0.1.9"},
{:lgbm_ex_cli, "0.1.0", git: "https://github.com/tato-gh/lgbm_ex_cli"},
{:lgbm_ex_capi, "0.1.0", git: "https://github.com/tato-gh/lgbm_ex_capi"},
],
config: [
lgbm_ex_cli: [lightgbm_cmd: lightgbm_cmd]
],
system_env: %{
"LIGHTGBM_DIR" => Path.join(File.cwd!(), "LightGBM")
}
)
# 見た目のためにalias
alias LGBMExCli, as: LightGBM_CLI
alias LGBMExCapi, as: LightGBM_API
:ok
- lgbm_ex_cliアプリケーションにlightgbmコマンドのパスを設定しています
- lgbm_ex_capiライブラリのインストール時にmakeが必要になるため、LIGHTGBM_DIRを環境変数に設定しています
(1)モデル作成
モデルはCLIで作成します。モデルや訓練データはファイル保存されるため、適当な格納先フォルダを作っています。
{features, labels} = Scidata.Iris.download()
workdir = Path.join(File.cwd!(), "iris")
File.mkdir_p(workdir)
{:ok, model_file} =
LightGBM_CLI.fit(workdir, features, labels, [
objective: "multiclass",
metric: "multi_logloss",
num_class: 3,
num_iterations: 10,
num_leaves: 5,
min_data_in_leaf: 1
])
(2-A)データ予測 CLI編
CLIでデータ予測を行う場合は、モデル作成時に取得したモデルファイルのパスと、予測対象データを渡します。
results = LightGBM_CLI.predict(model_file, features)
-
model_file
があるフォルダには、結果が格納されたTSVファイルも作成されます -
predict
が返すresultsはそれを読んだものです
(2-B)データ予測 C-API編
C-APIでデータ予測を行う場合は、モデル作成時に取得したモデルファイルからモデルをロードした後で、予測対象データを渡します。
{:ok, nif_reference} = LightGBM_API.load(model_file)
results = LightGBM_API.predict(nif_reference, features)
-
nif_reference
は、ElixirからCのリソースを参照するためのリファレンスです。上記ではロードしたモデルを特定するために使っています - まとまったデータに対する予測はCLIを使う方が早いですが、都度都度予測するようなケースにおいては、C-APIの方が早いです(CLIを使うと予測対象データのファイル書き込みが都度発生するためです)
(3)補足:C-APIを使ったモデル情報の取得
C-APIを使うと、作成した(ロードした)モデルのクラス数などを取得できます。
num_classes = LightGBM_API.get_num_classes(nif_reference)
num_iterations = LightGBM_API.get_num_iterations(nif_reference)
num_features = 4
feature_importance = LightGBM_API.get_feature_importance(nif_reference, num_features)
{
num_iterations,
num_classes,
feature_importance
}
(4)補足:アーリーストッピングを使ったモデル構築
アーリーストッピングを使う場合には、fit
に検証用データを渡します。
# 訓練用と検証用の分離(記事都合上の準備)
{features, labels} = Scidata.Iris.download()
data_size = Enum.count(labels)
validation_size = div(data_size, 10)
shuffled_indexes = Enum.shuffle(0..(data_size - 1))
[train_indexes, validation_indexes] = Enum.chunk_every(shuffled_indexes, data_size - validation_size)
{train_features, train_labels} =
Enum.reduce(train_indexes, {[], []}, fn index, {acc_x, acc_y} ->
x = Enum.at(features, index)
y = Enum.at(labels, index)
{acc_x ++ [x], acc_y ++ [y]}
end)
{validation_features, validation_labels} =
Enum.reduce(validation_indexes, {[], []}, fn index, {acc_x, acc_y} ->
x = Enum.at(features, index)
y = Enum.at(labels, index)
{acc_x ++ [x], acc_y ++ [y]}
end)
# アーリーストッピングを使ったモデル構築
{:ok, model_file, num_iteration, eval_value} =
LightGBM_CLI.fit(workdir, {train_features, validation_features}, {train_labels, validation_labels}, [
objective: "multiclass",
metric: "multi_logloss",
num_class: 3,
num_iterations: 1000,
num_leaves: 5,
min_data_in_leaf: 1,
early_stopping_round: 2
])
- アーリーストッピングを使った場合は、モデルファイルパスのほかに、
num_iteration
とeval_value
が返ってきます- // (ライブラリ内部の話ですが)CLIの出力ログをパースするしか方法がなさそうでした...
(5)補足:パラメータ探索
LightGBMにはパラメータがたくさんありますので、適当なパラメータを探索するケースがあります。そのような際にはrefit
を使って、すでに作成済みの訓練データに対して、違うパラメータでモデルを再構築できます。
下記は、max_depthとmin_data_in_leafを探索している例です。
base_params = [
objective: "multiclass",
metric: "multi_logloss",
num_class: 3,
num_iterations: 1000,
early_stopping_round: 2,
num_leaves: 5,
min_data_in_leaf: 1
]
max_depth_list = [3, 5, 7]
min_data_in_leaf_list = [1, 2, 3]
for max_depth <- max_depth_list, min_data_in_leaf <- min_data_in_leaf_list do
params = Keyword.merge(
base_params,
max_depth: max_depth,
min_data_ln_leaf: min_data_in_leaf
)
{:ok, _model_file, num_iteration, eval_value} = LightGBM_CLI.refit(workdir, params)
{max_depth, min_data_in_leaf, {:ok, num_iteration, eval_value}}
end
-
refit
もモデルファイルを更新しているため、良さそうなパラメータが見つかった場合は、改めて見つかったパラメータでfit
orrefit
が必要です。
(完)
Discussion
docker環境で準備する場合のメモです。