ML.NET による回帰分析

以降の投稿は、ML.NET 0.1 についてです。現在 (2019/05/19) 時点での最新は、ML.NET 1.0 となります。[ML.NET 1.0 ベースで、同様の投稿](https://qiita.com/hiromasa-masuda/items/c57e9009389f7f1dbe12)を別に行っていますので、必要な方は、そちらを確認ください。

# ML.NET がリリース

2018/05/07 に ML.NET 0.1 がプレビュー リリースとなり、クロスプラットフォームで動作する .NET Core 上で、機械学習が利用可能となりました。 

これまで、機械学習の言語といえば Python がほとんどですが、普段 C# を利用している身からしたら、今後 C# も機械学習で利用できるメジャーな言語となることを期待しています。

ここでは、ML.NET を利用して、ワインの品質を回帰分析で推論する機械学習モデルを作成する手順を紹介します。

データセットは、UCI が公開している [UC Irvine Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php) の [Wine Quality Data Set](https://archive.ics.uci.edu/ml/datasets/Wine+Quality) を利用します。

また、ML.NET は、Visual Studio 2017 15.6 以降が必要となるため、必要に応じて Visual Studio を更新してください。

# .NET Core プロジェクトの作成

Visual Studio 2017 を起動し、[ファイル] - [新規作成] - [Visual C#] - [.NET Core] - [コンソール アプリ (.NET Core)] を選択し、[名前] に任意のプロジェクト名(ここでは、 "WinequalityDemo001" ) を入力し [OK] を選択します

# Microsoft.ML のインストール

[ソリューション エクスプローラー] から、プロジェクトを選択し、右クリックメニューから、[NuGet パッケージの管理] を選択します。

[参照] タブの [検索]　テキスト ボックスに "Microsoft.ML" と入力し、[Microsoft.ML] を選択し、[インストール] を選択し、以降画面の指示に従い "Microsoft.ML" をインストールします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/172205/8fb94d99-807d-fb26-ce9d-2a282a01cf1f.png)

# データの準備

[ソリューション エクスプローラー] から、プロジェクトを選択し、右クリックメニューから、[追加] - [新しいフォルダー] を選択しフォルダーを作成し、任意の名前 (ここでは、"Data") を付けます。

[Wine Quality Data Set](https://archive.ics.uci.edu/ml/datasets/Wine+Quality) - [Data Folder](https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/) から、[winequality-red.csv](https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv) をダウンロードし、作成した "Data" フォルダーに格納します。

[ソリューション エクスプローラー] から、"winequality-red.csv" を選択し、[プロパティ] - [出力ディレクトリにコピー] から、"常にコピーする" を選択します。

# ワイン品質データセットの定義

まず、データセットのカラムの定義を行います。

[ソリューション エクスプローラー] から、プロジェクトを選択し、右クリックメニューから、[追加] - [クラス] - [名前] に "WineQuality" と入力し、[追加] を選択します。

以下のコードを記述します。

```csharp:WineQuality.cs
using Microsoft.ML.Runtime.Api;

namespace WinequalityDemo001
{
    public class WineQualityData
    {
        [Column(ordinal: "0")]
        public float FixedAcidity;

        [Column(ordinal: "1")]
        public float VolatileAcidity;

        [Column(ordinal: "2")]
        public float CitricAcid;

        [Column(ordinal: "3")]
        public float ResidualSugar;

        [Column(ordinal: "4")]
        public float Chlorides;

        [Column(ordinal: "5")]
        public float FreeSulfurDioxide;

        [Column(ordinal: "6")]
        public float TotalSulfurDioxide;

        [Column(ordinal: "7")]
        public float Density;

        [Column(ordinal: "8")]
        public float Ph;

        [Column(ordinal: "9")]
        public float Sulphates;

        [Column(ordinal: "10")]
        public float Alcohol;

        [Column(ordinal: "11")]
        public float Quality;        
    }

    public class WineQualityPrediction
    {
        [ColumnName("Score")]
        public float Quality;
    }
}
```

"WineQualityData" というクラスは、読み込むデータのカラムを定義をしています。
各フィールドの属性でデータファイルに対応するインデックスを定義しています。

"WineQualityPrediction" というクラスでは、推論時に出力するデータセットを定義しています。

# 学習パイプラインの定義

以降から、"Program.cs" にコードを追加していきます。
はじめに、以下のコードで各ライブラリを using しておきます。

```csharp:Program.cs
using System.Linq;
using Microsoft.ML;
using Microsoft.ML.Models;
using Microsoft.ML.Trainers;
using Microsoft.ML.Transforms;
```

"Program.cs" に、学習データのパスを引数にもつ Train メソッドを定義します。

```csharp:Program.cs

public static PredictionModel<WineQualityData, WineQualityPrediction> Train(string trainDataPath)
{

   LearningPipeline pipeline = new LearningPipeline();

   //データファイルのロード
   pipeline.Add(new TextLoader<WineQualityData>(trainDataPath, useHeader: true, separator: ";"));
            
   //予測列の定義
   //予測したい列を "Label" という名前で作成
   //タプルとして定義している点に注意
   pipeline.Add(new ColumnCopier((nameof(WineQualityData.Quality), "Label")));

   //学習に用いるデータの定義
   //"Features" という名前で、各列を一つにまとめる
   pipeline.Add(new ColumnConcatenator("Features",
      nameof(WineQualityData.FixedAcidity),
      nameof(WineQualityData.VolatileAcidity),
      nameof(WineQualityData.CitricAcid),
      nameof(WineQualityData.ResidualSugar),
      nameof(WineQualityData.Chlorides),
      nameof(WineQualityData.FreeSulfurDioxide),
      nameof(WineQualityData.TotalSulfurDioxide),
      nameof(WineQualityData.Ph),
      nameof(WineQualityData.Sulphates),
      nameof(WineQualityData.Alcohol)));

   //アルゴリズムの定義
   pipeline.Add(new FastTreeRegressor());
   //pipeline.Add(new FastForestRegressor());
            
   //学習を実施しモデルを作成
   var model = pipeline.Train<WineQualityData, WineQualityPrediction>();

   return model;
}
```

# モデルの評価

"Program.cs" に、モデルを評価する Evalute メソッドを定義します。
このメソッドは、対象の学習モデル、テストデータのパスを引数に持ちます。

```csharp:Program.cs

public static void Evalute(PredictionModel<WineQualityData, WineQualityPrediction> model, string testDataPath)
{
   //評価用データのロード
   var testData = new TextLoader<WineQualityData>(testDataPath, useHeader: true, separator: ";");

   //評価用のクラスの生成
   var evaluator = new RegressionEvaluator();
   //評価の実行
   var metrics = evaluator.Evaluate(model, testData);

   //評価指標の出力
   Console.WriteLine($"Rms={metrics.Rms}");
   Console.WriteLine($"RSquared={metrics.RSquared}");
}
```
# 学習データ/評価データの分割

以下のスレッドによると、v0.2 でデータの分割がサポートされるようですが、2018/06/04 現在では、データ分割がサポートされていないため、テキストファイルをランダムに分割するカスタムメソッドを定義します。

https://github.com/dotnet/machinelearning/issues/288

SplitData メソッドは、引数にデータファイルのパスと学習に利用するデータの割合を指定します。

データを指定された割合で分割し、学習データと評価データのパスをタプルで返します。

```csharp:Program.cs

public static (string trainDataPath, string testDataPath) SplitData(string dataPath, int trainDatafraction)
{
    string dataLines = null;

    using (System.IO.TextReader reader = new System.IO.StreamReader(dataPath))
    {
        dataLines = reader.ReadToEnd();

        reader.Close();
    }

    Random random = new Random();

    var lines = dataLines.Split("\n").Select(line => (line: line, number: random.Next(10))).ToList();

    var trainData = lines.Where(line => line.number <= trainDatafraction - 1).ToList();
    var testData = lines.Where(line => line.number > trainDatafraction - 1).ToList();

    string trainDataPath = $"{_dataPath}.{"train"}.{DateTimeOffset.Now.Ticks}";
    string testDataPath = $"{_dataPath}.{"test"}.{DateTimeOffset.Now.Ticks}";

    using (System.IO.TextWriter writer = new System.IO.StreamWriter(trainDataPath))
    {
        foreach (var lineData in trainData)
        {
            writer.WriteLine(lineData.line);
        }

        writer.Flush();
        writer.Close();
    }

    using (System.IO.TextWriter writer = new System.IO.StreamWriter(testDataPath))
    {
        foreach (var lineData in testData)
        {
            writer.WriteLine(lineData.line);
        }

        writer.Flush();
        writer.Close();
    }

    return (trainDataPath, testDataPath);
}

```

# 学習と評価の実行

最後に各メソッドを定義します。

```csharp:Program.cs

static void Main(string[] args)
{
    //データの分割
    var data = SplitData(@".\Data\winequality-red.csv", 7);            

    //学習
    var model = Train(data.trainDataPath);
    //評価
    Evalute(model, data.testDataPath);

    Console.ReadLine();
}

```


# 参考サイト

今回の手順においては、以下のサイトを参考に記載しました。

- [Get started with ML.NET in 10 minutes
](https://www.microsoft.com/net/learn/apps/machine-learning-and-ai/ml-dotnet/get-started/windows)
- [[C#]ML.NETを使ってタイタニック号の生存者予測をしてみた](https://qiita.com/koshian2/items/dba923672219515d128f)
- [赤ワインと深層学習をGUIで](https://qiita.com/ttanimura/items/5f844885c36e9fffe28a)
- [Introducing ML.NET: Cross-platform, Proven and Open Source Machine Learning Framework
](https://blogs.msdn.microsoft.com/dotnet/2018/05/07/introducing-ml-net-cross-platform-proven-and-open-source-machine-learning-framework/)