見出し画像

ML.netでCSVファイルの異常値を検出する その1

何かしら連続した時系列のデータがあるとします。
この時系列データから異常を検出したい場合がありますね。

例えば、毎日体重を測定しているとしましょう。
普段ならば、70kg前後の数値で計測しているはずですが、別の人の体重を測定したために100kgになったとか。

こういう極端な数字の変化を自動で検出して、警告してくれたら便利ですよね。
世の中には色々な計測を行う装置があるので、自動監視をしてくれるプログラムは重要です。

いつものように、VB.netからC#で作られたクラスライブラリーを利用する方式にします。
C#で作るクラスライブラリーはNuGetでMicrosoft.ML.TimeSeriesのインストールが必要です。


こちらのソースコードは、以下で公開されているサンプルソースを元に作成しています。
https://docs.microsoft.com/ja-jp/dotnet/machine-learning/

資料を印刷して読みたい方、PDFファイルのアドレスは以下です。
https://docs.microsoft.com/ja-jp/dotnet/opbuildpdf/machine-learning/toc.pdf?branch=live


あと、サンプルデータは以下のアドレスですが、githubなのでRAWを右クリックしてファイルの保存をしてください。
https://github.com/dotnet/samples/blob/master/machine-learning/tutorials/ProductSalesAnomalyDetection/Data/product-sales.csv

例によって、そのままでは動作しません。
データファイルの指定など、次回以降に書きます。

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

単純なVB.net側のソースコードは以下です。


Imports System.Reflection
Imports System.Windows.Forms
Imports System.IO
Imports System.Text
Imports Microsoft.VisualBasic.FileIO
Imports System.Globalization


Public Class Form1


Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

'クラスライブラリーを使う宣言
Dim ClassLibraryObject As Object = New ClassLibrary1.Class1

'C#のクラスライブラリーで機械学習を行わせる
Dim BooleanReturn As Boolean = ClassLibraryObject.SampleMachineLearning()

End Sub
End Class


C#で作られたサンプルソースコードを元に、PDFに書かれた解説を加えたコードが以下です。

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

namespace ClassLibrary1
{

//入力データクラスを指定します
//LoadColumn 属性は、データセット内の、どの列 (列インデックス)を読み込むかを指定します。
public class ProductSalesData
{
[LoadColumn(0)]
public string Month;

[LoadColumn(1)]
public float numSales;
}


//予測データクラスを指定します
//異常検出の場合、予測は異常、生スコア、p 値があるかどうかを示すアラートで構成されます。
//p 値が 0 に近いほど、異常が発生する可能性が高くなります。
public class ProductSalesPrediction
{
//vector to hold alert,score,p-value values
[VectorType(3)]
public double[] Prediction { get; set; }
}

public class Class1
{

//データセット ファイルのパスと保存したモデル ファイルのパスを保持するために、
//2 つのグローバル フィールドを作成します。

//モデルのトレーニングに使用するデータ セットのパス
static readonly string _dataPath = Path.Combine(Environment.CurrentDirectory, "Data", "productsales.csv");

//assign the Number of records in dataset file to constant variable
//データセット ファイル内のレコード数です。 _docSize を pvalueHistoryLength の計算に使用します。
const int _docsize = 36;


public void SampleMachineLearning()
{

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

//データを読み込む
//ML.NET 内のデータは、IDataView クラスとして表されます。
//IDataView は、表形式のデータ(数値とテキスト) を表すための柔軟で効率的な方法です。
//データはテキスト ファイルから、または他のソース(SQL データベースやログ ファイルなど) から
//IDataView オブジェクトに読み込むことができます。

//LoadFromTextFile() は、データ スキーマを定義し、ファイルを読み取ります。
//データ パス変数を取得して、IDataView を返します。
//ファイルパス、1行目飛ばす、カンマ区切り指定
IDataView dataView = mlContext.Data.LoadFromTextFile<ProductSalesData>(path: _dataPath, hasHeader:true, separatorChar: ',');

//DetectSpike() メソッドに対する呼び出しを追加します。
DetectSpike(mlContext, _docsize, dataView);

//DetectChangepoint() メソッドに対する呼び出しを追加します。
DetectChangepoint(mlContext, _docsize, dataView);

}


//スパイク検出
//スパイク検出の目的は、時系列データ値の大部分と大きく異なる突然の一時的なバーストを特定することです。
//このような疑わしいまれな項目、イベント、または観測値を適時に検出して最小限に抑えることが重要です。
//次のようなアプローチを利用すると、停電、サイバー攻撃、バイラル Web コンテンツなど、さまざまな異常を検出できます。

//CreateEmptyDataView () メソッドを追加する
static IDataView CreateEmptyDataView(MLContext mlContext)
{
// Create empty DataView. We just need the schema to call Fit() for the time series transforms
//CreateEmptyDataView() では、IEstimator.Fit() メソッドへの入力として使用される正しいスキーマで、
//空のデータ ビュー オブジェクトを生成します。
IEnumerable<ProductSalesData> enumerableData = new List<ProductSalesData>();
return mlContext.Data.LoadFromEnumerable(enumerableData);
}


//DetectSpike() メソッドを作成します。
//・エスティメーターから変換を作成します。
//・売上データ履歴に基づいてスパイクを検出します。
//・結果を表示します。
static void DetectSpike(MLContext mlContext, int docSize, IDataView productSales)
{
//スパイク検出のために、IidSpikeEstimator を使用してモデルをトレーニングします。
var iidSpikeEstimator = mlContext.Transforms.DetectIidSpike(outputColumnName:nameof(ProductSalesPrediction.Prediction),
inputColumnName: nameof(ProductSalesData.numSales),confidence: 95, pvalueHistoryLength: docSize / 4);

//スパイク検出の変換を作成します。
ITransformer iidSpikeTransform = iidSpikeEstimator.Fit(CreateEmptyDataView(mlContext));

//productSales データを DetectSpike() メソッドの次の行に変換します。
//Transform() メソッドを使用して、データセットの複数の入力行の予測を行います。
IDataView transformedData = iidSpikeTransform.Transform(productSales);

//CreateEnumerable() メソッドを使って transformedData を厳密に型指定された IEnumerable に変換します。
var predictions = mlContext.Data.CreateEnumerable<ProductSalesPrediction>(transformedData,reuseRowObject: false);

//表示ヘッダー行を作成します。
Console.WriteLine("Alert\tScore\tP-Value");

//スパイクの検出結果には、次の情報が表示されます。
//Alert は、特定のデータ ポイントに対するスパイク アラートを示します。
//Score は、データセット内の特定のデータ ポイントに対する ProductSales 値です。
//P - Value "P" は確率を表します。 p 値が 0 に近いほど、データ ポイントが異常になる可能性が高くなります。

//predictions と IEnumerable を反復処理し、結果を表示します。
foreach (var p in predictions)
{
var results = $"{p.Prediction[0]}\t{p.Prediction[1]:f2}\t{p.Prediction[2]:F2}";
if (p.Prediction[0] == 1)
{
results += " <-- Spike detected";
}
Console.WriteLine(results);
}
Console.WriteLine("");


}


//DetectChangepoint() メソッドを作成します。
//DetectChangepoint() メソッドは次のタスクを実行します。
//・エスティメーターから変換を作成します。
//・売上データ履歴に基づいて変更点を検出します。
//・結果を表示します。
static void DetectChangepoint(MLContext mlContext, int docSize, IDataView productSales)
{
//iidChangePointEstimator を作成します。
var iidChangePointEstimator = mlContext.Transforms.DetectIidChangePoint(outputColumnName:nameof(ProductSalesPrediction.Prediction),
inputColumnName: nameof(ProductSalesData.numSales),confidence: 95, changeHistoryLength: docSize / 4);

//エスティメーターから変換を作成します。
var iidChangePointTransform = iidChangePointEstimator.Fit(CreateEmptyDataView(mlContext));

//Transform() メソッドを使用してデータを変換します。
IDataView transformedData = iidChangePointTransform.Transform(productSales);

//表示を簡単にするために、CreateEnumerable() メソッドを使ってtransformedData を厳密に型指定された IEnumerable に変換します。
var predictions = mlContext.Data.CreateEnumerable<ProductSalesPrediction>(transformedData,reuseRowObject: false);

//表示ヘッダーを作成します。
Console.WriteLine("Alert\tScore\tP-Value\tMartingale value");

//変化点の検出結果には、次の情報が表示されます。
//・Alert は、特定のデータ ポイントに対する変化点アラートを示します。
//・Score は、データセット内の特定のデータ ポイントに対する ProductSales 値です。
//・P - Value "P" は確率を表します。 P 値が 0 に近いほど、データ ポイントが異常になる可能性が高くなります。
//・Martingale value は、一連の P 値に基づいて、データ ポイントがどの程度 "おかしい" かを特定するために使用されます。

//predictions と IEnumerable を反復処理し、結果を表示します。
foreach (var p in predictions)
{
var results = $"{ p.Prediction[0]}\t{ p.Prediction[1]:f2}\t{ p.Prediction[2]:F2}\t{ p.Prediction[3]:F2}";
if (p.Prediction[0] == 1)
{
results += " <-- alert is on, predicted changepoint";
}
Console.WriteLine(results);
}
Console.WriteLine("");
}


}
}


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