1、問題與解決方案html
經過多元分類算法進行手寫數字識別,手寫數字的圖片分辨率爲8*8的灰度圖片、已經預先進行過處理,讀取了各像素點的灰度值,並進行了標記。git
其中第0列是序號(不參與運算)、1-64列是像素值、65列是結果。github
咱們以64位像素值爲特徵進行多元分類,算法採用SDCA最大熵分類算法。算法
2、源碼app
先貼出所有代碼:框架
namespace MulticlassClassification_Mnist { class Program { static readonly string TrainDataPath = Path.Combine(Environment.CurrentDirectory, "Data", "optdigits-full.csv"); static readonly string ModelPath = Path.Combine(Environment.CurrentDirectory, "Data", "SDCA-Model.zip"); static void Main(string[] args) { MLContext mlContext = new MLContext(seed: 1); TrainAndSaveModel(mlContext); TestSomePredictions(mlContext); Console.WriteLine("Hit any key to finish the app"); Console.ReadKey(); } public static void TrainAndSaveModel(MLContext mlContext) { // STEP 1: 準備數據 var fulldata = mlContext.Data.LoadFromTextFile(path: TrainDataPath, columns: new[] { new TextLoader.Column("Serial", DataKind.Single, 0), new TextLoader.Column("PixelValues", DataKind.Single, 1, 64), new TextLoader.Column("Number", DataKind.Single, 65) }, hasHeader: true, separatorChar: ',' ); var trainTestData = mlContext.Data.TrainTestSplit(fulldata, testFraction: 0.2); var trainData = trainTestData.TrainSet; var testData = trainTestData.TestSet; // STEP 2: 配置數據處理管道 var dataProcessPipeline = mlContext.Transforms.Conversion.MapValueToKey("Label", "Number", keyOrdinality: ValueToKeyMappingEstimator.KeyOrdinality.ByValue); // STEP 3: 配置訓練算法 var trainer = mlContext.MulticlassClassification.Trainers.SdcaMaximumEntropy(labelColumnName: "Label", featureColumnName: "PixelValues"); var trainingPipeline = dataProcessPipeline.Append(trainer) .Append(mlContext.Transforms.Conversion.MapKeyToValue("Number", "Label")); // STEP 4: 訓練模型使其與數據集擬合 Console.WriteLine("=============== Train the model fitting to the DataSet ==============="); ITransformer trainedModel = trainingPipeline.Fit(trainData); // STEP 5:評估模型的準確性 Console.WriteLine("===== Evaluating Model's accuracy with Test data ====="); var predictions = trainedModel.Transform(testData); var metrics = mlContext.MulticlassClassification.Evaluate(data: predictions, labelColumnName: "Number", scoreColumnName: "Score"); PrintMultiClassClassificationMetrics(trainer.ToString(), metrics); // STEP 6:保存模型 mlContext.ComponentCatalog.RegisterAssembly(typeof(DebugConversion).Assembly); mlContext.Model.Save(trainedModel, trainData.Schema, ModelPath); Console.WriteLine("The model is saved to {0}", ModelPath); } private static void TestSomePredictions(MLContext mlContext) { // Load Model ITransformer trainedModel = mlContext.Model.Load(ModelPath, out var modelInputSchema); // Create prediction engine var predEngine = mlContext.Model.CreatePredictionEngine<InputData, OutPutData>(trainedModel); //num 1 InputData MNIST1 = new InputData() { PixelValues = new float[] { 0, 0, 0, 0, 14, 13, 1, 0, 0, 0, 0, 5, 16, 16, 2, 0, 0, 0, 0, 14, 16, 12, 0, 0, 0, 1, 10, 16, 16, 12, 0, 0, 0, 3, 12, 14, 16, 9, 0, 0, 0, 0, 0, 5, 16, 15, 0, 0, 0, 0, 0, 4, 16, 14, 0, 0, 0, 0, 0, 1, 13, 16, 1, 0 } }; var resultprediction1 = predEngine.Predict(MNIST1); resultprediction1.PrintToConsole(); } } class InputData { public float Serial; [VectorType(64)] public float[] PixelValues; public float Number; } class OutPutData : InputData { public float[] Score; } }
3、分析機器學習
總體流程和二元分類沒有什麼區別,下面解釋一下有差別的兩個地方。ide
一、加載數據學習
// STEP 1: 準備數據 var fulldata = mlContext.Data.LoadFromTextFile(path: TrainDataPath, columns: new[] { new TextLoader.Column("Serial", DataKind.Single, 0), new TextLoader.Column("PixelValues", DataKind.Single, 1, 64), new TextLoader.Column("Number", DataKind.Single, 65) }, hasHeader: true, separatorChar: ',' );
此次咱們不是經過實體對象來加載數據,而是經過列信息來進行加載,其中PixelValues是特徵值,Number是標籤值。測試
二、訓練通道
// STEP 2: 配置數據處理管道 var dataProcessPipeline = mlContext.Transforms.Conversion.MapValueToKey("Label", "Number", keyOrdinality: ValueToKeyMappingEstimator.KeyOrdinality.ByValue)
// STEP 3: 配置訓練算法 var trainer = mlContext.MulticlassClassification.Trainers.SdcaMaximumEntropy(labelColumnName: "Label", featureColumnName: "PixelValues");
var trainingPipeline = dataProcessPipeline.Append(trainer)
.Append(mlContext.Transforms.Conversion.MapKeyToValue("Number", "Label"));
// STEP 4: 訓練模型使其與數據集擬合
ITransformer trainedModel = trainingPipeline.Fit(trainData);
首先經過MapValueToKey方法將Number值轉換爲Key類型,多元分類算法要求標籤值必須是這種類型(相似枚舉類型,二元分類要求標籤爲BOOL類型)。關於這個轉換的緣由及編碼方式,下面詳細介紹。
4、鍵值類型編碼與獨熱編碼
MapValueToKey功能是將(字符串)值類型轉換爲KeyTpye類型。
有時候某些輸入字段用來表示類型(類別特徵),但自己並無特別的含義,好比編號、電話號碼、行政區域名稱或編碼等,這裏須要把這些類型轉換爲1到一個整數如1-300來進行從新編號。
舉個簡單的例子,咱們進行圖片識別的時候,目標結果多是「貓咪」、「小狗」、「人物」這些分類,須要把這些分類轉換爲一、二、3這樣的整數。但本文的標籤值自己就是一、二、3,爲何還要轉換呢?由於咱們這裏的一二三其實不是數學意義上的數字,而是一種標誌,能夠理解爲壹、貳、叄,因此要進行編碼。
MapKeyToValue和MapValueToKey相反,它把將鍵類型轉換回其原始值(字符串)。就是說標籤是文本格式,在運算前已經被轉換爲數字枚舉類型了,此時預測結果爲數字,經過MapKeyToValue將其結果轉換爲對應文本。
MapValueToKey通常是對標籤值進行編碼,通常不用於特徵值,若是是特徵值爲字符串類型的,建議採用獨熱編碼。獨熱編碼即 One-Hot 編碼,又稱一位有效編碼,其方法是使用N位狀態寄存器來對N個狀態進行編碼,每一個狀態都由他獨立的寄存器位,而且在任意時候,其中只有一位有效。例如:
天然狀態碼爲:0,1,2,3,4,5
獨熱編碼爲:000001,000010,000100,001000,010000,100000
怎麼理解這個事情呢?舉個例子,假如咱們要進行人的身材的分析,但咱們但願加入地域特徵,好比:「黑龍江」、「山東」、「湖南」、「廣東」這種特徵,但這種字符串機器學習是不認識的,必須轉換爲浮點數,剛纔提到MapKeyToValue能夠把字符串轉換爲數字,爲何這裏要採用獨熱編碼呢?簡單來講,假設把地域名稱轉換爲1到10幾個數字,在歐氏幾何中1到3的歐拉距離和1到9的歐拉距離是不等的,但通過獨熱編碼後,任意兩點間的歐拉距離都是相等的,而咱們這裏的地域特徵僅僅是想表達分類關係,彼此之間沒有其餘邏輯關係,因此應該採用獨熱編碼。
5、進度調試
通常機器算法的數據擬合過程時間都比較長,有時程序跑了兩個小時還沒結束,也不知道還須要多長時間,着實讓人着急,因此及時瞭解學習進度,是頗有必要的。
因爲機器學習算法通常都有「遞歸直到收斂」這種操做,因此咱們是沒有辦法預先知道最終運算次數的,能作到的只能打印一些過程信息,看到程序在動,內心也有點底,當系統跑過一次以後,基本就大體知道須要多少次擬合了,後面再調試就能夠大體瞭解進度了。補充一句,可不能夠在測試階段先減小樣本數據進行快速調試,調試經過後再切換到全樣本進行訓練?其實不行,有時候樣本數量小,可能會引發指標震盪,時間反而長了。
以前在Githube上看到有人經過MLContext.LOG事件來打印調試信息,我試了一下,發現無法控制篩選內容,不太方便,後來想到一個方法,就是新增一個自定義數據處理通道,這個通道不作具體事情,就打印調試信息。
類定義:
namespace MulticlassClassification_Mnist { public class DebugConversionInput { public float Serial { get; set; } } public class DebugConversionOutput { public float DebugFeature { get; set; } } [CustomMappingFactoryAttribute("DebugConversionAction")] public class DebugConversion : CustomMappingFactory<DebugConversionInput, DebugConversionOutput> { static long TotalCount = 0; public void CustomAction(DebugConversionInput input, DebugConversionOutput output) { output.DebugFeature = 1.0f;
TotalCount++; Console.WriteLine($"DebugConversion.CustomAction's debug info.TotalCount={TotalCount} "); } public override Action<DebugConversionInput, DebugConversionOutput> GetMapping() => CustomAction; } }
使用方法:
var dataProcessPipeline = mlContext.Transforms.CustomMapping(new DebugConversion().GetMapping(), contractName: "DebugConversionAction") .Append(...) .Append(mlContext.Transforms.Concatenate("Features", new string[] { "RealFeatures", "DebugFeature" }));
經過CustomMapping加載咱們自定義的數據處理通道,因爲數據集是懶加載(Lazy)的,因此必須把咱們自定義數據處理通道的輸出加入爲特徵值,才能參與運算,而後算法在操做每一條數據時都會調用到CustomAction方法,這樣就能夠打印進度信息了。爲了避免影響運算結果,咱們把這個數據處理通道的輸出值固定爲1.0f 。
6、資源獲取
源碼下載地址:https://github.com/seabluescn/Study_ML.NET
工程名稱:MulticlassClassification_Mnist