チュートリアル: ML.NET で製品売上の異常を検出する

製品売上データの異常検出アプリケーションを構築する方法について説明します。 このチュートリアルでは、Visual Studio の C# を使って .NET Core コンソール アプリケーションを作成します。

このチュートリアルでは、次の作業を行う方法について説明します。

  • データを読み込む
  • スパイクの異常検出のために変換を作成する
  • 変換を使用してスパイクの異常を検出する
  • 変化点の異常検出のために変換を作成する
  • 変換を使用して変化点の異常を検出する

このチュートリアルのソース コードは dotnet/samples リポジトリで確認できます。

必須コンポーネント

注意

product-sales.csv のデータ形式は、DataMarket が出典であるデータセット "Shampoo Sales Over a Three Year Period" (過去 3 年間のシャンプーの売上) に基づいており、Rob Hyndman が作成した Time Series Data Library (TSDL) で提供されています。 "Shampoo Sales Over a Three Year Period" データセットは、DataMarket Default Open License の下でライセンスを受けています。

コンソール アプリケーションを作成する

  1. "ProductSalesAnomalyDetection" という .NET Core コンソール アプリケーション を作成します。

  2. データ セット ファイルを保存するために、プロジェクトに Data という名前のディレクトリを作成します。

  3. Microsoft.ML NuGet パッケージ をインストールします。

    注意

    このサンプルでは、特に明記されていない限り、記載されている最新の安定バージョンの NuGet パッケージを使用します。

    ソリューション エクスプローラーで、プロジェクトを右クリックし、 [NuGet パッケージの管理] を選択します。 [パッケージ ソース] として [nuget.org] を選択します。[参照] タブを選択し、「Microsoft.ML」を検索し、 [インストール] ボタンを選択します。 [変更のプレビュー] ダイアログの [OK] を選択します。表示されているパッケージのライセンス条項に同意する場合は、 [ライセンスの同意] ダイアログの [同意する] を選択します。 Microsoft.ML.TimeSeries についてもこれらの手順を繰り返します。

  4. Program.cs の先頭に次の using ステートメントを追加します。

    using System;
    using System.IO;
    using Microsoft.ML;
    using System.Collections.Generic;
    

データをダウンロードする

  1. データセットをダウンロードし、以前に作成した Data フォルダーに保存します。

    • product-sales.csv を右クリックし、[名前を付けてリンクを保存 (またはターゲット)] を選択します。

      *.csv ファイルを Data フォルダーに保存したことを確認します。または他の場所に保存した後に、*.csv ファイルを Data フォルダーに移動します。

  2. ソリューション エクスプローラーで、*.csv ファイルを右クリックし、 [プロパティ] を選択します。 [詳細設定] で、 [出力ディレクトリにコピー] の値を [新しい場合はコピーする] に変更します。

次の表は、*.csv ファイルのデータのプレビューです。

ProductSales
1-Jan 271
2-Jan 150.9
..... .....
1-Feb 199.3
..... .....

クラスを作成してパスを定義する

次に、入力クラスと予測クラスのデータ構造を定義します。

プロジェクトに新しいクラスを追加します。

  1. ソリューション エクスプローラー で、プロジェクトを右クリックして、 [追加]、[新しいアイテム] の順に選びます。

  2. [新しい項目の追加] ダイアログ ボックス で、 [クラス] を選択し、 [名前] フィールドを「ProductSalesData.cs」に変更します。 次に [追加] を選択します。

    コード エディターで ProductSalesData.cs ファイルが開きます。

  3. 次の using ステートメントを ProductSalesData.cs の先頭に追加します。

    using Microsoft.ML.Data;
    
  4. 既存のクラス定義を削除し、ProductSalesDataProductSalesPrediction の 2 つのクラスを含む次のコードを ProductSalesData.cs ファイルに追加します。

    public class ProductSalesData
    {
        [LoadColumn(0)]
        public string Month;
    
        [LoadColumn(1)]
        public float numSales;
    }
    
    public class ProductSalesPrediction
    {
        //vector to hold alert,score,p-value values
        [VectorType(3)]
        public double[] Prediction { get; set; }
    }
    

    ProductSalesData は入力データ クラスを指定します。 LoadColumn 属性は、データセット内のどの列 (列インデックス) を読み込むかを指定します。

    ProductSalesPrediction では予測データ クラスを指定します。 異常検出の場合、予測は異常、生スコア、p 値があるかどうかを示すアラートで構成されます。 p 値が 0 に近いほど、異常が発生する可能性が高くなります。

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

    • _dataPath には、モデルのトレーニングに使用するデータ セットのパスが含まれます。
    • _docsize はデータセット ファイル内のレコード数です。 _docSizepvalueHistoryLength の計算に使用します。
  6. Main メソッドのすぐ上にある行に次のコードを追加して、それらのパスを指定します。

    static readonly string _dataPath = Path.Combine(Environment.CurrentDirectory, "Data", "product-sales.csv");
    //assign the Number of records in dataset file to constant variable
    const int _docsize = 36;
    

Main で変数を初期化する

  1. Main メソッドの Console.WriteLine("Hello World!") の行は、mlContext 変数を宣言して初期化する次のコードに置き換えます。

    MLContext mlContext = new MLContext();
    

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

データを読み込む

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

  1. Main() メソッドの次の行として次のコードを追加します。

    IDataView dataView = mlContext.Data.LoadFromTextFile<ProductSalesData>(path: _dataPath, hasHeader: true, separatorChar: ',');
    

    LoadFromTextFile() は、データ スキーマを定義し、ファイルを読み取ります。 データ パス変数を取得して、IDataView を返します。

時系列の異常検出

異常検出によって、予期しない、または通常とは異なるイベントや動作にフラグが立てられます。 これは、問題を探す場所の手がかりとなり、"これはおかしいだろうか" という問いの答えを見つけるために役立ちます。

"これはおかしいだろうか" 異常検出の例。

異常検出は、時系列データの異常値 (指定された入力の時系列上で、動作が予期されたものではない点、つまり "おかしい" 点) を検出するプロセスです。

異常検出は、さまざまな場面で役立ちます。 たとえば、次のようになります。

車を持っている場合に知りたいこと:このオイル ゲージの表示値は正常か。それとも漏れがあるか。 電力消費量を監視している場合に知りたいこと:停電はあるか。

検出できる時系列の異常には 2 つの種類があります。

  • スパイク は、システム内の異常動作の一時的なバーストを示します。

  • 変化点 は、システム内での長期にわたる永続的な変化の始まりを示します。

ML.NET では、独立した同一分散のデータセットには IID Spike Detection (IID スパイク検出) アルゴリズムまたは IID Change point Detection (IID 変化点検出) アルゴリズムが適しています。 入力データは、1 つの固定分布から独立してサンプリングされるデータ ポイントのシーケンスである必要があります。

他のチュートリアルのモデルとは異なり、時系列の異常検出器の変換は入力データで直接操作されます。 IEstimator.Fit() メソッドでは、変換を生成するためにトレーニング データを必要としません。 データ スキーマは必要ですが、ProductSalesData の空のリストから生成されたデータ ビューによって提供されます。

スパイクと変化点の検出には、同じ製品売上データを分析します。 構築とトレーニング モデル プロセスは、スパイク検出と変化点検出で同じです。主な違いは、使用される具体的な検出アルゴリズムです。

スパイク検出

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

2 つのスパイク検出を示すスクリーンショット。

CreateEmptyDataView () メソッドを追加する

次のメソッドを Program.cs に追加します。

static IDataView CreateEmptyDataView(MLContext mlContext) {
    // Create empty DataView. We just need the schema to call Fit() for the time series transforms
    IEnumerable<ProductSalesData> enumerableData = new List<ProductSalesData>();
    return mlContext.Data.LoadFromEnumerable(enumerableData);
}

CreateEmptyDataView() では、IEstimator.Fit() メソッドへの入力として使用される正しいスキーマで、空のデータ ビュー オブジェクトを生成します。

DetectSpike() メソッドを作成します。

DetectSpike() メソッド:

  • エスティメーターから変換を作成します。
  • 売上データ履歴に基づいてスパイクを検出します。
  • 結果を表示します。
  1. Main() メソッドの直後に、次のコードを使用して DetectSpike() メソッドを作成します。

    static void DetectSpike(MLContext mlContext, int docSize, IDataView productSales)
    {
    
    }
    
  2. スパイク検出のために、IidSpikeEstimator を使用してモデルをトレーニングします。 それを次のコードを使用して DetectSpike() メソッドに追加します。

    var iidSpikeEstimator = mlContext.Transforms.DetectIidSpike(outputColumnName: nameof(ProductSalesPrediction.Prediction), inputColumnName: nameof(ProductSalesData.numSales), confidence: 95, pvalueHistoryLength: docSize / 4);
    
  3. DetectSpike() メソッドの次のコード行として以下を追加して、スパイク検出の変換を作成します。

    ヒント

    confidencepvalueHistoryLength のパラメーターは、スパイク検出の方法に影響します。 confidence は、モデルのスパイクに対する感度を決定します。 信頼度が低いほど、アルゴリズムが "小さい" スパイクを検出する可能性が高くなります。 pvalueHistoryLength パラメーターは、スライディング ウィンドウ内のデータ ポイントの数を定義します。 このパラメーターの値は、通常、データセット全体の割合です。 pvalueHistoryLength が小さいほど、モデルが以前の大きなスパイクを忘れる速度が速くなります。

    ITransformer iidSpikeTransform = iidSpikeEstimator.Fit(CreateEmptyDataView(mlContext));
    
  4. 次のコード行を追加して、productSales データを DetectSpike() メソッドの次の行に変換します。

    IDataView transformedData = iidSpikeTransform.Transform(productSales);
    

    前のコードでは、Transform() メソッドを使用して、データセットの複数の入力行の予測を行います。

  5. 表示を簡単にするために、次のコードを使用し、CreateEnumerable() メソッドを使って transformedData を厳密に型指定された IEnumerable に変換します。

    var predictions = mlContext.Data.CreateEnumerable<ProductSalesPrediction>(transformedData, reuseRowObject: false);
    
  6. 次の Console.WriteLine() コードを使用して表示ヘッダー行を作成します。

    Console.WriteLine("Alert\tScore\tP-Value");
    

    スパイクの検出結果には、次の情報が表示されます。

    • Alert は、特定のデータ ポイントに対するスパイク アラートを示します。
    • Scoreは、データセット内の特定のデータ ポイントに対する ProductSales 値です。
    • P-Value "P" は確率を表します。 p 値が 0 に近いほど、データ ポイントが異常になる可能性が高くなります。
  7. 次のコードを使用して 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("");
    
  8. DetectSpike() メソッドに対する呼び出しを Main() メソッドに追加します。

    DetectSpike(mlContext, _docsize, dataView);
    

スパイクの検出結果

結果は以下のようになるはずです。 処理中にメッセージが表示されます。 警告または処理メッセージが表示されることがありますが、 わかりやすくするために、一部のメッセージは次の結果から削除してあります。

Detect temporary changes in pattern
=============== Training the model ===============
=============== End of training process ===============
Alert   Score   P-Value
0       271.00  0.50
0       150.90  0.00
0       188.10  0.41
0       124.30  0.13
0       185.30  0.47
0       173.50  0.47
0       236.80  0.19
0       229.50  0.27
0       197.80  0.48
0       127.90  0.13
1       341.50  0.00 <-- Spike detected
0       190.90  0.48
0       199.30  0.48
0       154.50  0.24
0       215.10  0.42
0       278.30  0.19
0       196.40  0.43
0       292.00  0.17
0       231.00  0.45
0       308.60  0.18
0       294.90  0.19
1       426.60  0.00 <-- Spike detected
0       269.50  0.47
0       347.30  0.21
0       344.70  0.27
0       445.40  0.06
0       320.90  0.49
0       444.30  0.12
0       406.30  0.29
0       442.40  0.21
1       580.50  0.00 <-- Spike detected
0       412.60  0.45
1       687.00  0.01 <-- Spike detected
0       480.30  0.40
0       586.30  0.20
0       651.90  0.14

変化点検出

Change points は、レベルの変化や傾向など、時系列のイベント ストリームの値分布の永続的な変化です。 こうした永続的な変化は、spikes よりもはるかに長く続き、壊滅的なイベントを示している可能性があります。 通常、Change points は目視ではわかりませんが、次のような方法を使用してデータから検出できます。 次の図は、変化点検出の例です。

2 つの変更点検出を示すスクリーンショット。

DetectChangepoint() メソッドを作成します。

DetectChangepoint() メソッドは次のタスクを実行します。

  • エスティメーターから変換を作成します。
  • 売上データ履歴に基づいて変更点を検出します。
  • 結果を表示します。
  1. Main() メソッドの直後に、次のコードを使用して DetectChangepoint() メソッドを作成します。

    static void DetectChangepoint(MLContext mlContext, int docSize, IDataView productSales)
    {
    
    }
    
  2. 次のコードを使って DetectChangepoint() メソッドで iidChangePointEstimator を作成します。

    var iidChangePointEstimator = mlContext.Transforms.DetectIidChangePoint(outputColumnName: nameof(ProductSalesPrediction.Prediction), inputColumnName: nameof(ProductSalesData.numSales), confidence: 95, changeHistoryLength: docSize / 4);
    
  3. 前に行ったように、DetectChangePoint() メソッドに次のコード行を追加して、エスティメーターから変換を作成します。

    ヒント

    モデルでは、現在の偏差が永続的な変化であり、アラート作成前の単なるランダムなスパイクではないことを確認する必要があるため、変更ポイントの検出でわずかな遅延が発生します。 この遅延の量は、changeHistoryLength パラメーターと等しくなります。 このパラメーターの値を増やすと、より永続的な変更に対する変更検出アラートが生成されますが、代わりに遅延が長くなります。

    var iidChangePointTransform = iidChangePointEstimator.Fit(CreateEmptyDataView(mlContext));
    
  4. Transform() メソッドを使用して、次のコードを DetectChangePoint() に追加してデータを変換します。

    IDataView transformedData = iidChangePointTransform.Transform(productSales);
    
  5. 以前と同様に、表示を簡単にするために、次のコードを使用し、CreateEnumerable() メソッドを使って transformedData を厳密に型指定された IEnumerable に変換します。

    var predictions = mlContext.Data.CreateEnumerable<ProductSalesPrediction>(transformedData, reuseRowObject: false);
    
  6. DetectChangePoint() メソッドの次の行として次のコードを使用して、表示ヘッダーを作成します。

    Console.WriteLine("Alert\tScore\tP-Value\tMartingale value");
    

    変化点の検出結果には、次の情報が表示されます。

    • Alert は、特定のデータ ポイントに対する変化点アラートを示します。
    • Scoreは、データセット内の特定のデータ ポイントに対する ProductSales 値です。
    • P-Value "P" は確率を表します。 P 値が 0 に近いほど、データ ポイントが異常になる可能性が高くなります。
    • Martingale value は、一連の P 値に基づいて、データ ポイントがどの程度 "おかしい" かを特定するために使用されます。
  7. 次のコードを使用して 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("");
    
  8. 次の DetectChangepoint() メソッドに対する呼び出しを Main() メソッドに追加します。

    DetectChangepoint(mlContext, _docsize, dataView);
    

変化点の検出結果

結果は以下のようになるはずです。 処理中にメッセージが表示されます。 警告または処理メッセージが表示されることがありますが、 わかりやすくするために、一部のメッセージは次の結果から削除してあります。

Detect Persistent changes in pattern
=============== Training the model Using Change Point Detection Algorithm===============
=============== End of training process ===============
Alert   Score   P-Value Martingale value
0       271.00  0.50    0.00
0       150.90  0.00    2.33
0       188.10  0.41    2.80
0       124.30  0.13    9.16
0       185.30  0.47    9.77
0       173.50  0.47    10.41
0       236.80  0.19    24.46
0       229.50  0.27    42.38
1       197.80  0.48    44.23 <-- alert is on, predicted changepoint
0       127.90  0.13    145.25
0       341.50  0.00    0.01
0       190.90  0.48    0.01
0       199.30  0.48    0.00
0       154.50  0.24    0.00
0       215.10  0.42    0.00
0       278.30  0.19    0.00
0       196.40  0.43    0.00
0       292.00  0.17    0.01
0       231.00  0.45    0.00
0       308.60  0.18    0.00
0       294.90  0.19    0.00
0       426.60  0.00    0.00
0       269.50  0.47    0.00
0       347.30  0.21    0.00
0       344.70  0.27    0.00
0       445.40  0.06    0.02
0       320.90  0.49    0.01
0       444.30  0.12    0.02
0       406.30  0.29    0.01
0       442.40  0.21    0.01
0       580.50  0.00    0.01
0       412.60  0.45    0.01
0       687.00  0.01    0.12
0       480.30  0.40    0.08
0       586.30  0.20    0.03
0       651.90  0.14    0.09

おめでとうございます! これで、売上データのスパイクや変化点の異常を検出するための機械学習モデルを適切に構築できました。

このチュートリアルのソース コードは dotnet/samples リポジトリで確認できます。

このチュートリアルでは、次の作業を行う方法を学びました。

  • データを読み込む
  • スパイクの異常検出のためにモデルをトレーニングする
  • トレーニング済みモデルを使用してスパイクの異常を検出する
  • 変化点の異常検出のためにモデルをトレーニングする
  • トレーニング済みモデルを使用して変化点の異常を検出する

次の手順

Machine Learning サンプルの GitHub リポジトリを確認し、季節性データの異常検出サンプルを調べてください。