Copilotに質問ぶつけながら、まあ、何故ここまで嘘を教えるのか疑問がありますが、間違いを指摘すると修正はしてくれます。しかし、その間違いを見つけるのに当然苦労します。LightGBMをC APIの利用で使おうとしてメソッドの引数の数がそもそも違ったり、順番が違ったりでどうにか動かす事が出来る様にはなりましたが、RMSEがもう一つだし、安定した結果が得られません。
大分苦労してどうにか動く所まではこぎ着けましたが、これが実際にモノになるのかが疑問です。ここまで苦労して結局AutoMLに及ばなかったりModel Builderに及ばなかったり、まあ、そもそもAutoMLで最新LightGBMを採用してくれたりModel Builderで採用してくれたりしたら結果的にはこの苦労が無駄になるのかも😓
// LightGBM.dllの関数をインポート
[DllImport("lib_lightgbm.dll", CallingConvention = CallingConvention.Cdecl)]
// public static extern int LGBM_DatasetCreateFromFile(string filename, string parameters, ref IntPtr handle);
public static extern int LGBM_DatasetCreateFromFile(
string filename,
string parameters,
IntPtr reference,
out IntPtr dataset);
[DllImport("lib_lightgbm.dll", CallingConvention = CallingConvention.Cdecl)]
// public static extern int LGBM_BoosterCreate(IntPtr trainData, string parameters, ref IntPtr handle);
public static extern int LGBM_BoosterCreate(
IntPtr trainData,
string parameters,
out IntPtr booster);
[DllImport("lib_lightgbm.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int LGBM_BoosterUpdateOneIter(
IntPtr booster,
out int isFinished);
[DllImport("lib_lightgbm.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int LGBM_BoosterPredictForMat(
IntPtr booster,
double[] data,
int dataType,
int numRows,
int numCols,
int isRowMajor,
int predictType,
int startIteration,
int numIteration,
string parameters,
out int outLen,
IntPtr outResult);
[DllImport("lib_lightgbm.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr LGBM_GetLastError();
LightGBMの最新バージョンのソースをダウンロードしてDLLをビルド。プロジェクトにlib_lightgbm.dllとlib_lightbgm.pdbを追加する。
string filename = this.CSV_name; // データファイルのパス
var lines = File.ReadAllLines(filename);
var header = lines[0].Split(',');
var data = lines.Skip(1).Select(line => line.Split(',').Select(double.Parse).ToArray()).ToArray();
// データのランダム分割
var rand = new Random();
data = data.OrderBy(x => rand.Next()).ToArray();
int trainSize = (int)(data.Length * 0.8);
var trainData = data.Take(trainSize).ToArray();
var testData = data.Skip(trainSize).ToArray();
// トレーニングデータの保存
string trainFilename = "train_data.csv";
File.WriteAllLines(trainFilename, new[] { string.Join(",", header) }.Concat(trainData.Select(row => string.Join(",", row))));
// テストデータの保存
string testFilename = "test_data.csv";
File.WriteAllLines(testFilename, new[] { string.Join(",", header) }.Concat(testData.Select(row => string.Join(",", row))));
学習データはこんな感じで準備して
// データセットの作成
IntPtr reference = IntPtr.Zero;
IntPtr dataset;
string datasetParameters = "max_bin=255 header=true label_column=name:Souha";
int datasetResult = LGBM_DatasetCreateFromFile(trainFilename, datasetParameters, reference, out dataset);
データセットを作成して
// ブースターの作成
string boosterParameters = "objective=regression;metric=rmse;num_leaves=31;learning_rate=0.05;feature_fraction=0.8";
IntPtr booster;
int boosterResult = LGBM_BoosterCreate(dataset, boosterParameters, out booster);
ブースターの作成? これ学習モデルなんだと思います。
// ブースターのトレーニング
int numIterations = (int)nudRepeat.Value;
int earlyStoppingRounds = 10; // 早期停止のラウンド数
double bestScore = double.MaxValue;
int bestIteration = 0;
for (int i = 0; i < numIterations; i++)
{
int isFinished;
int updateResult = LGBM_BoosterUpdateOneIter(booster, out isFinished);
if (updateResult != 0)
{
rtbLog.AppendText("Failed to update booster at iteration " + i + Environment.NewLine);
break;
}
// 早期停止のチェック
IntPtr outResult = Marshal.AllocHGlobal(sizeof(double) * trainSize);
int outLength;
LGBM_BoosterPredictForMat(booster, trainData.SelectMany(x => x).ToArray(),
1, trainSize, trainData[0].Length,
1, 0, 0, -1, "predict_type=normal", out outLength, outResult);
double[] resultArray = new double[trainSize];
Marshal.Copy(outResult, resultArray, 0, trainSize);
double rmse = CalculateRMSE(trainData.Select(row => row.Last()).ToArray(), resultArray);
if (rmse < bestScore)
{
bestScore = rmse;
bestIteration = i;
}
else if (i - bestIteration >= earlyStoppingRounds)
{
rtbLog.AppendText("Early stopping at iteration " + i + Environment.NewLine);
break;
}
}
過剰学習を防ぐ為(?)に早期停止とかの確認入れてって事で
// テストデータの準備
var testFeatures = testData.Select(row => row.Take(row.Length - 1).ToArray()).ToArray();
var actualValues = testData.Select(row => row.Last()).ToArray();
double[] testDataFlat = testFeatures.SelectMany(x => x).ToArray();
int numRows = testFeatures.Length;
int numCols = testFeatures[0].Length;
int isRowMajor = 1; // 1: 行優先, 0: 列優先
string predictParameters = "predict_type=normal";
IntPtr outResultPtr = Marshal.AllocHGlobal(sizeof(double) * numRows);
int outLen;
// 予測の実行
int predictResult = LGBM_BoosterPredictForMat(booster, testDataFlat, 1, numRows, numCols, isRowMajor, 0, 0, -1, predictParameters, out outLen, outResultPtr);
if (predictResult == 0)
{
// Console.WriteLine("Prediction completed successfully.");
rtbLog.AppendText("Prediction completed successfully." + Environment.NewLine);
// 予測結果を表示
double[] resultArray = new double[numRows];
Marshal.Copy(outResultPtr, resultArray, 0, numRows);
// RMSEの計算
double rmse = CalculateRMSE(actualValues, resultArray);
rtbLog.AppendText("RMSE: " + rmse + Environment.NewLine);
}
else
{
rtbLog.AppendText("Failed to predict." + Environment.NewLine);
}
こんな感じにRMSEも表示出来る。
追記 2024.10.1
早期停止判断時のRMSE用の予想とRMSEの計算時に渡しているものが微妙らしく、Copilotのコードでは判断時のRMSEを表示させてみるとおかしな値になっていたので、終了時にひょうじされるRMSEがそれっぽかったので、そちらを採用して書き直してみた所、やっとそれっぽくなってきた😁