見出し画像

VB.netからC#で書かれた時系列予測(AI・機械学習)の処理を呼び出す・その3

このコード、以下のWebアドレスに掲載されているものを基本にしています。
https://docs.microsoft.com/ja-jp/dotnet/machine-learning/

印刷して学びたい場合もPDFファイルがあるので参考になります。
https://docs.microsoft.com/ja-jp/dotnet/opbuildpdf/machine-learning/toc.pdf?branch=live


拡張子mdf、SQL Serverのローカルファイルなのですが、一般的に使われているフォーマットなのでしょうか。
SQL Serverを使ったことはありますが、ローカルデータベースであってもネットワーク経由で使うのですが。

SQL Serverのローカルファイルを直接扱うのは、正直初めてです。
これ、もっと汎用的なソースコードに変換できないのかなと思います。

汎用的なファイル形式、CSVファイルに変換できればなと思います。
さて、どうやって拡張子mdfのデータを取り出そうかなと考えて色々とやりましたが無駄でした。

もう、仕方ないので、自分で適当なCSVファイルを作成しました。
こんな感じの内容にしています。

RENTALDATE,YEAR,TOTALRENTALS
2011/1/1,0,71
2011/1/2,0,61
省略
2012/12/29,1,12
2012/12/30,1,153.5
2012/12/31,1,479

なんとなくコードを作ってみたところ、mlContext.Data.LoadFromTextFileで例外エラーが発生しました。
System.InvalidOperationException: 'Property 'RentalDate' is missing the LoadColumnAttribute attribute'

RentalDateのLoadColumnAttributeの属性が無いそうです。
LoadColumnAttributeってなんだろうと検索したけれど、良く分かりません。

格闘すること数分、学習用のデータクラスに読み込むカラムの指定がなかったと気が付きました。

#学習 #勉強 #AI #機械学習 #プログラミング #Visual #CSharp #BASIC #ソースコード #Windows #自動判定 #時間 #予測 #次の日の値


試行錯誤して出来上がったC#のコードがこれです。

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using Microsoft.ML;
using Microsoft.ML.Data;
using Microsoft.ML.Transforms.TimeSeries;

namespace ClassLibrary1
{

//学習を行うためのデータクラス
public class ModelInput
{
//観察の日付
[LoadColumn(0)]
public DateTime RentalDate { get; set; }

//観察のエンコードされた年 (0 = 2011、1 = 2012)
[LoadColumn(1)]
public float Year { get; set; }

//その日にレンタルされた自転車の合計数
[LoadColumn(2)]
public float TotalRentals { get; set; }
}


public class ModelOutput
{
//予測期間の予測値
public float[] ForecastedRentals { get; set; }

//予測期間の予測される最小値
public float[] LowerBoundRentals { get; set; }

//予測期間の予測される最大値
public float[] UpperBoundRentals { get; set; }
}

public class Class1
{
public void SampleMachineLearning(string SpecifyWorkingFolder)
{

//csvの物理的な場所
string csvFilePath = Path.Combine(SpecifyWorkingFolder, "DailyDemand.csv");

//トレーニング済みのモデルを保存する場所
string modelPath = Path.Combine(SpecifyWorkingFolder, "MLModel.zip");

////SQL Server接続文字列
//var connectionString = $"Data Source=(LocalDB)\\MSSQLLocalDB;AttachDbFilename={dbFilePath};Integrated Security = True; Connect Timeout = 30; ";

//MLContext の新しいインスタンスで mlContext 変数を初期化します。
//MLContext クラスは、すべての ML.NET 操作の開始点で、mlContext を初期化することで、
//モデル作成ワークフローのオブジェクト間で共有できる新しい ML.NET 環境が作成されます。
//これは Entity Framework における DBContext と概念的には同じです。
MLContext mlContext = new MLContext();


//データを読み込んで変換する
//ML.NET では、数値またはテキストの表形式データを記述する柔軟で効率的な方法として、IDataView クラスを使います。
//IDataView は、テキスト ファイルを読み込むか、またはリアルタイムで読み込むことができます
//(たとえば、SQL データベースまたはログ ファイル)。
//入力データのクラス TaxiTrip
//トレーニング用CSV dataPath
//CSVにヘッダがあるか true=ある false=なし
//CSVの区切文字 ,
IDataView dataView = mlContext.Data.LoadFromTextFile <ModelInput>(csvFilePath, hasHeader: true, separatorChar: ',');

//5. このデータセットには 2 年分のデータが含まれています。
//トレーニングに使用されるのは1年目のデータのみで、
//2年目のデータは、モデルによって生成された予測と実際の値を比較するために保持されます。
//FilterRowsByColumn 変換を使用してデータをフィルター処理します。

//1 年目に対しては、upperBound パラメーターを 1 に設定することによって、Year 列の 1 未満の値のみが選択されます。
IDataView firstYearData = mlContext.Data.FilterRowsByColumn(dataView, "Year", upperBound: 1);

//反対に、2 年目に対しては、lowerBound パラメーターを 1 に設定することによって、1 以上の値が選択されます。
IDataView secondYearData = mlContext.Data.FilterRowsByColumn(dataView, "Year", lowerBound: 1);


//時系列解析パイプラインを定義する
//1. SsaForecastingEstimator を使用して時系列データセット内の値を予測するパイプラインを定義します。

//forecastingPipeline では、1 年目用の 365 のデータ ポイントとサンプルを取得するか、
//seriesLength パラメーターで指定された 30 日(月単位) 間隔に時系列データセットを分割します。
//これらの各サンプルは、毎週または 7 日間の期間で解析されます。
//次の期間の予測値がどのようになるかを判断する際には、前の 7 日間の値を使用して予測が行われます。
//モデルは、horizon パラメーターで定義されているように、7 つの期間を将来まで予測するように設定されています。
//予測は情報に基づいた推測であるため、100 % 正確であるとは限りません。
//したがって、上限と下限によって定義される最善のシナリオと最悪のシナリオにおける値の範囲を把握しておくことをお勧めします。
//この場合、下限と上限の信頼レベルは95 % に設定されています。
//信頼レベルは、状況に応じて増減できます。
//値が大きいほど、望ましい信頼レベルを達成するために上限と下限の範囲が広くなります。

var forecastingPipeline = mlContext.Forecasting.ForecastBySsa(
//変換後のカラム名
outputColumnName: "ForecastedRentals",

//変換前のカラム名
inputColumnName: "TotalRentals",

//ウインドウの長さ、7日間のデータから予測
windowSize: 7,

//バッファーに保持される長さ
seriesLength: 30,

//トレーニングに使用される長さ
trainSize: 365,

//何日間の予測をするか
horizon: 7,

//予測の信頼度
confidenceLevel: 0.95f,

//信頼度の下限を設定するカラム名
confidenceLowerBoundColumn: "LowerBoundRentals",

//信頼度の上限を設定するカラム名
confidenceUpperBoundColumn: "UpperBoundRentals");


//2. Fit メソッドを使用して、モデルをトレーニングし、以前に定義した forecastingPipeline にデータを適合させます。
SsaForecastingTransformer forecaster = forecastingPipeline.Fit(firstYearData);


//モデルを評価する
Evaluate(secondYearData, forecaster, mlContext);


//TimeSeriesPredictionEngine を作成します。
//TimeSeriesPredictionEngine は、単一の予測を行うための便利な方法です。
var forecastEngine = forecaster.CreateTimeSeriesEngine<ModelInput, ModelOutput>(mlContext);

//以前に定義した modelPath 変数によって指定された MLModel.zip という名前のファイルにモデルを保存します。
//Checkpoint メソッドを使用してモデルを保存します。
forecastEngine.CheckPoint(mlContext, modelPath);

//実際に予測させます
Forecast(secondYearData, 7, forecastEngine, mlContext);

}

//モデルを評価する
//来年のデータを予測し、それを実際の値と比較することによって、モデルのパフォーマンスを評価します。
static void Evaluate(IDataView testData, ITransformer model, MLContext mlContext)
{
//トレーニング済みのモデルで Transform メソッドを使用して、2 年目のデータを予測します。
IDataView predictions = model.Transform(testData);

//CreateEnumerable メソッドを使用して、データから実際の値を取得します。
IEnumerable<float> actual =mlContext.Data.CreateEnumerable<ModelInput>(testData, true).Select(observed => observed.TotalRentals);

//CreateEnumerable メソッドを使用して、予測値を取得します。
IEnumerable<float> forecast =mlContext.Data.CreateEnumerable<ModelOutput>(predictions, true).Select(prediction => prediction.ForecastedRentals[0]);

//実際の値と予測値の差を計算します (一般的にエラーと呼ばれます)。
var metrics = actual.Zip(forecast, (actualValue, forecastValue) => actualValue - forecastValue);

//平均絶対誤差値と二乗平均平方根誤差値を計算して、パフォーマンスを測定します。

//平均絶対誤差: 予測が実際の値にどれだけ近いかを測定します。
//この値の範囲は 0 から無限大です。
//0 に近いほど、モデルの品質が高くなります。
var MAE = metrics.Average(error => Math.Abs(error)); // Mean Absolute Error

//二乗平均平方根誤差: モデル内のエラーを集約します。
//この値の範囲は 0 から無限大です。
//0 に近いほど、モデルの品質が高くなります。
var RMSE = Math.Sqrt(metrics.Average(error => Math.Pow(error, 2))); // Root Mean Squared Error

//メトリックをコンソールに出力します。
Console.WriteLine("Evaluation Metrics");
Console.WriteLine("---------------------");
Console.WriteLine($"Mean Absolute Error: {MAE:F3}");
Console.WriteLine($"Root Mean Squared Error: {RMSE:F3}\n");

}


//モデルを使用して需要を予測する

static void Forecast(IDataView testData, int horizon, TimeSeriesPredictionEngine<ModelInput, ModelOutput> forecaster, MLContext mlContext)
{
//Predict メソッドを使用して、次の 7 日間のレンタルを予測します。
ModelOutput forecast = forecaster.Predict();

//7 つの期間の実際の値と予測値を合わせます。
IEnumerable<string> forecastOutput = mlContext.Data.CreateEnumerable<ModelInput>(testData, reuseRowObject: false)
.Take(horizon)
.Select((ModelInput rental, int index) =>
{
string rentalDate = rental.RentalDate.ToShortDateString();
float actualRentals = rental.TotalRentals;
float lowerEstimate = Math.Max(0, forecast.LowerBoundRentals[index]);
float estimate = forecast.ForecastedRentals[index];
float upperEstimate = forecast.UpperBoundRentals[index];
return $"Date: {rentalDate}\n" +
$"Actual Rentals: {actualRentals}\n" +
$"Lower Estimate: {lowerEstimate}\n" +
$"Forecast: {estimate}\n" +
$"Upper Estimate: {upperEstimate}\n";
});

//予測出力を反復処理し、コンソールに表示します。
Console.WriteLine("Rental Forecast");
Console.WriteLine("---------------------");
foreach (var prediction in forecastOutput)
{
Console.WriteLine(prediction);
}
}

}
}


この記事が気に入ったらサポートをしてみませんか?