F# mnist sample (#374)
* F# mnist sample * add F# mnist sample to main readme * fix key ordering
This commit is contained in:
Родитель
b9f389cbdd
Коммит
1c3e4baa46
|
@ -57,7 +57,7 @@ The official ML.NET samples are divided in multiple categories depending on the
|
|||
<h4>Issues classification
|
||||
<a href="samples/csharp/end-to-end-apps/MulticlassClassification-GitHubLabeler">C#</a> <a href="samples/fsharp/end-to-end-apps/MulticlassClassification-GitHubLabeler">F#</a> <img src="images/app-type-e2e.png" alt="End-to-end app icon"></h4>
|
||||
<h4>Iris flowers classification <a href="samples/csharp/getting-started/MulticlassClassification_Iris">C#</a> <a href="samples/fsharp/getting-started/MulticlassClassification_Iris">F#</a> <img src="images/app-type-getting-started.png" alt="Getting started icon"></h4>
|
||||
<h4>MNIST <a href="samples/csharp/getting-started/MulticlassClassification_mnist">C#</a> <img src="images/app-type-getting-started.png" alt="Getting started icon"></h4>
|
||||
<h4>MNIST <a href="samples/csharp/getting-started/MulticlassClassification_mnist">C#</a> <a href="samples/fsharp/getting-started/MulticlassClassification_mnist">F#</a> <img src="images/app-type-getting-started.png" alt="Getting started icon"></h4>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
# MNIST Classification
|
||||
|
||||
| ML.NET version | API type | Status | App Type | Data type | Scenario | ML Task | Algorithms |
|
||||
|----------------|-------------------|-------------------------------|-------------|-----------|---------------------|---------------------------|-----------------------------|
|
||||
| v1.0.0-preview | Dynamic API | Up-to-date | Console app | .csv files | MNIST classification | Multi-class classification | Sdca Multi-class |
|
||||
|
||||
In this introductory sample, you'll see how to use [ML.NET](https://www.microsoft.com/net/learn/apps/machine-learning-and-ai/ml-dotnet) to classify handwritten digits from 0 to 9 using the MNIST dataset. This is a **multiclass classification** problem that we will solve using SDCA (Stochastic Dual Coordinate Ascent) algorithm.
|
||||
|
||||
## Problem
|
||||
|
||||
The MNIST data set contains handwritten images of digits, ranging from 0 to 9.
|
||||
|
||||
The MNIST dataset we are using contains 65 columns of numbers. The first 64 columns in each row are integer values in the range from 0 to 16. These values are calculated by dividing 32 x 32 bitmaps into non-overlapping blocks of 4 x 4. The number of ON pixels is counted in each of these blocks, which generates an input matrix of 8 x 8. The last column in each row is the number that is represented by the values in the first 64 columns. These first 64 columns are our features and our ML model will use these features to classifiy the testing images. The last column in our training and validation datasets is the label - the actual number that we will predict using our ML model.
|
||||
|
||||
the ML model that we will build will return probabilities for a given image of being one of the numbers from 0 to 9 as explained above.
|
||||
|
||||
## ML task - Multiclass classification
|
||||
The generalized problem of **multiclass classification** is to classify items into one of three or more classes. (Classifying items into one of the two classes is called **binary classification**).
|
||||
|
||||
## Solution
|
||||
To solve this problem, first we will build an ML model. Then we will train the model on existing data, evaluate how good it is, and lastly we'll consume the model to predict a number the given image represents.
|
||||
|
||||
![Build -> Train -> Evaluate -> Consume](../shared_content/modelpipeline.png)
|
||||
|
||||
### 1. Build model
|
||||
|
||||
Building a model includes:
|
||||
* Uploading data (`optdigits-train.csv`) with (`DataReader`)
|
||||
* Create an Estimator and transform the data in the first 64 columns to one column so it can be used effectively by an ML algorithm (with `Concatenate`)
|
||||
* Choosing a learning algorithm (`StochasticDualCoordinateAscent`).
|
||||
|
||||
|
||||
The initial code is similar to the following:
|
||||
```fsharp
|
||||
// STEP 1: Common data loading configuration
|
||||
let trainData = mlContext.Data.LoadFromTextFile<Input>(trainDataPath, separatorChar=',', hasHeader=false)
|
||||
let testData = mlContext.Data.LoadFromTextFile<Input>(testDataPath, separatorChar=',', hasHeader=false)
|
||||
|
||||
// STEP 2: Common data process configuration with pipeline data transformations
|
||||
// Use in-memory cache for small/medium datasets to lower training time. Do NOT use it (remove .AppendCacheCheckpoint()) when handling very large datasets.
|
||||
let dataProcessPipeline =
|
||||
EstimatorChain()
|
||||
.Append(mlContext.Transforms.Conversion.MapValueToKey("Label", "Number"))
|
||||
.Append(mlContext.Transforms.Concatenate("Features", "PixelValues"))
|
||||
.AppendCacheCheckpoint(mlContext)
|
||||
|
||||
// STEP 3: Set the training algorithm, then create and config the modelBuilder
|
||||
let trainer = mlContext.MulticlassClassification.Trainers.SdcaMaximumEntropy("Label", "Features")
|
||||
let trainingPipeline =
|
||||
dataProcessPipeline
|
||||
.Append(trainer)
|
||||
.Append(mlContext.Transforms.Conversion.MapKeyToValue("Number", "Label"))
|
||||
```
|
||||
|
||||
### 2. Train model
|
||||
Training the model is a process of running the chosen algorithm on a training data to tune the parameters of the model. Our training data consists of pixel values and the digit they represent. It is implemented in the `Fit()` method from the Estimator object.
|
||||
|
||||
To perform training we just call the method providing the training dataset (optdigits-train.csv file) in a DataView object.
|
||||
|
||||
```fsharp
|
||||
// STEP 4: Train the model fitting to the DataSet
|
||||
let watch = System.Diagnostics.Stopwatch.StartNew();
|
||||
printfn "=============== Training the model ==============="
|
||||
let trainedModel = trainingPipeline.Fit(trainData)
|
||||
```
|
||||
### 3. Evaluate model
|
||||
We need this step to conclude how accurate our model operates on new data. To do so, the model from the previous step is run against another dataset that was not used in training (`optdigits-val.csv`). `MulticlassClassification.Evaluate` calculates the difference between known types and values predicted by the model in various metrics.
|
||||
|
||||
```fsharp
|
||||
let predictions = trainedModel.Transform(testData)
|
||||
let metrics = mlContext.MulticlassClassification.Evaluate(predictions, "Number", "Score")
|
||||
|
||||
Common.ConsoleHelper.printMultiClassClassificationMetrics (trainer.ToString()) metrics
|
||||
```
|
||||
|
||||
>*To learn more on how to understand the metrics, check out the Machine Learning glossary from the [ML.NET Guide](https://docs.microsoft.com/en-us/dotnet/machine-learning/) or use any available materials on data science and machine learning*.
|
||||
|
||||
If you are not satisfied with the quality of the model, there are a variety of ways to improve it, which will be covered in the *examples* category.
|
||||
|
||||
### 4. Consume model
|
||||
After the model is trained, we can use the `Predict()` API to predict the probability of being correct digit.
|
||||
|
||||
```fsharp
|
||||
let loadedTrainedModel, modelInputSchema = mlContext.Model.Load modelPath
|
||||
|
||||
// Create prediction engine related to the loaded trained model
|
||||
let predEngine = mlContext.Model.CreatePredictionEngine<Input, Output>(loadedTrainedModel)
|
||||
|
||||
sampleData
|
||||
|> Array.iter
|
||||
(fun (n,dat) ->
|
||||
let p = predEngine.Predict dat
|
||||
printfn "Actual: %d Predicted probability: zero: %.4f" n p.Score.[0]
|
||||
["one:"; "two:"; "three:"; "four:"; "five:"; "six:"; "seven:"; "eight:"; "nine:"]
|
||||
|> List.iteri
|
||||
(fun i w ->
|
||||
let i = i + 1
|
||||
printfn " %-6s %.4f" w p.Score.[i]
|
||||
)
|
||||
printfn ""
|
||||
)
|
||||
```
|
||||
|
||||
Where `sampleData` stores the pixel values of the digit that want to predict using the ML model.
|
||||
|
||||
```fsharp
|
||||
let sampleData =
|
||||
[|
|
||||
7, {
|
||||
Number = 0.f
|
||||
PixelValues = [|0.f;0.f;0.f;0.f;14.f;13.f;1.f;0.f;0.f;0.f;0.f;5.f;16.f;16.f;2.f;0.f;0.f;0.f;0.f;14.f;16.f;12.f;0.f;0.f;0.f;1.f;10.f;16.f;16.f;12.f;0.f;0.f;0.f;3.f;12.f;14.f;16.f;9.f;0.f;0.f;0.f;0.f;0.f;5.f;16.f;15.f;0.f;0.f;0.f;0.f;0.f;4.f;16.f;14.f;0.f;0.f;0.f;0.f;0.f;1.f;13.f;16.f;1.f;0.f|]
|
||||
}
|
||||
//...
|
||||
|]
|
||||
```
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,2 @@
|
|||
This folder is empty until you run the training console app and create/train and save the models as .ZIP files.
|
||||
Those model .ZIP files should appear in this folder.
|
|
@ -0,0 +1,111 @@
|
|||
open Microsoft.ML
|
||||
open Microsoft.ML.Data
|
||||
open System
|
||||
open System.IO
|
||||
open Microsoft.ML.Transforms
|
||||
|
||||
[<CLIMutable>]
|
||||
type Input =
|
||||
{
|
||||
[<LoadColumn(0,63); VectorType(64)>]
|
||||
PixelValues : float32 []
|
||||
[<LoadColumn(64)>]
|
||||
Number : float32
|
||||
}
|
||||
[<CLIMutable>]
|
||||
type Output = {Score : float32 []}
|
||||
|
||||
let sampleData =
|
||||
[|
|
||||
1, {
|
||||
Number = 0.f
|
||||
PixelValues = [|0.f;0.f;0.f;0.f;14.f;13.f;1.f;0.f;0.f;0.f;0.f;5.f;16.f;16.f;2.f;0.f;0.f;0.f;0.f;14.f;16.f;12.f;0.f;0.f;0.f;1.f;10.f;16.f;16.f;12.f;0.f;0.f;0.f;3.f;12.f;14.f;16.f;9.f;0.f;0.f;0.f;0.f;0.f;5.f;16.f;15.f;0.f;0.f;0.f;0.f;0.f;4.f;16.f;14.f;0.f;0.f;0.f;0.f;0.f;1.f;13.f;16.f;1.f;0.f|]
|
||||
}
|
||||
7, {
|
||||
Number = 0.f
|
||||
PixelValues = [|0.f;0.f;1.f;8.f;15.f;10.f;0.f;0.f;0.f;3.f;13.f;15.f;14.f;14.f;0.f;0.f;0.f;5.f;10.f;0.f;10.f;12.f;0.f;0.f;0.f;0.f;3.f;5.f;15.f;10.f;2.f;0.f;0.f;0.f;16.f;16.f;16.f;16.f;12.f;0.f;0.f;1.f;8.f;12.f;14.f;8.f;3.f;0.f;0.f;0.f;0.f;10.f;13.f;0.f;0.f;0.f;0.f;0.f;0.f;11.f;9.f;0.f;0.f;0.f|]
|
||||
}
|
||||
9, {
|
||||
Number = 0.f
|
||||
PixelValues = [|0.f;0.f;6.f;14.f;4.f;0.f;0.f;0.f;0.f;0.f;11.f;16.f;10.f;0.f;0.f;0.f;0.f;0.f;8.f;14.f;16.f;2.f;0.f;0.f;0.f;0.f;1.f;12.f;12.f;11.f;0.f;0.f;0.f;0.f;0.f;0.f;0.f;11.f;3.f;0.f;0.f;0.f;0.f;0.f;0.f;5.f;11.f;0.f;0.f;0.f;1.f;4.f;4.f;7.f;16.f;2.f;0.f;0.f;7.f;16.f;16.f;13.f;11.f;1.f|]
|
||||
}
|
||||
|]
|
||||
|
||||
let assemblyFolderPath = Path.GetDirectoryName(Reflection.Assembly.GetExecutingAssembly().Location)
|
||||
|
||||
let baseDatasetsRelativePath = @"../../../Data"
|
||||
let trianDataRealtivePath = Path.Combine(baseDatasetsRelativePath, "optdigits-train.csv")
|
||||
let testDataRealtivePath = Path.Combine(baseDatasetsRelativePath, "optdigits-val.csv")
|
||||
let trainDataPath = Path.Combine(assemblyFolderPath, trianDataRealtivePath)
|
||||
let testDataPath = Path.Combine(assemblyFolderPath, testDataRealtivePath)
|
||||
|
||||
let baseModelsRelativePath = @"../../../MLModels";
|
||||
let modelRelativePath = Path.Combine(baseModelsRelativePath, "Model.zip")
|
||||
let modelPath = Path.Combine(assemblyFolderPath, modelRelativePath)
|
||||
|
||||
let mlContext = new MLContext()
|
||||
|
||||
|
||||
// STEP 1: Common data loading configuration
|
||||
let trainData = mlContext.Data.LoadFromTextFile<Input>(trainDataPath, separatorChar=',', hasHeader=false)
|
||||
let testData = mlContext.Data.LoadFromTextFile<Input>(testDataPath, separatorChar=',', hasHeader=false)
|
||||
|
||||
// STEP 2: Common data process configuration with pipeline data transformations
|
||||
// Use in-memory cache for small/medium datasets to lower training time. Do NOT use it (remove .AppendCacheCheckpoint()) when handling very large datasets.
|
||||
let dataProcessPipeline =
|
||||
EstimatorChain()
|
||||
.Append(mlContext.Transforms.Conversion.MapValueToKey("Label", "Number", keyOrdinality=ValueToKeyMappingEstimator.KeyOrdinality.ByValue))
|
||||
.Append(mlContext.Transforms.Concatenate("Features", "PixelValues"))
|
||||
.AppendCacheCheckpoint(mlContext)
|
||||
|
||||
// STEP 3: Set the training algorithm, then create and config the modelBuilder
|
||||
let trainer = mlContext.MulticlassClassification.Trainers.SdcaMaximumEntropy("Label", "Features")
|
||||
let trainingPipeline =
|
||||
dataProcessPipeline
|
||||
.Append(trainer)
|
||||
.Append(mlContext.Transforms.Conversion.MapKeyToValue("Number", "Label"))
|
||||
|
||||
// STEP 4: Train the model fitting to the DataSet
|
||||
let watch = System.Diagnostics.Stopwatch.StartNew();
|
||||
printfn "=============== Training the model ==============="
|
||||
let trainedModel = trainingPipeline.Fit(trainData)
|
||||
|
||||
printfn "***** Training time: %d seconds *****" watch.Elapsed.Seconds
|
||||
|
||||
printfn "===== Evaluating Model's accuracy with Test data ====="
|
||||
let predictions = trainedModel.Transform(testData)
|
||||
let metrics = mlContext.MulticlassClassification.Evaluate(predictions, "Number", "Score")
|
||||
|
||||
Common.ConsoleHelper.printMultiClassClassificationMetrics (trainer.ToString()) metrics
|
||||
|
||||
mlContext.Model.Save(trainedModel, trainData.Schema, modelPath)
|
||||
|
||||
printfn "The model is saved to %s" modelPath
|
||||
|
||||
// Test some predicitions
|
||||
|
||||
let loadedTrainedModel, modelInputSchema = mlContext.Model.Load modelPath
|
||||
|
||||
// Create prediction engine related to the loaded trained model
|
||||
let predEngine = mlContext.Model.CreatePredictionEngine<Input, Output>(loadedTrainedModel)
|
||||
|
||||
sampleData
|
||||
|> Array.iter
|
||||
(fun (n,dat) ->
|
||||
let p = predEngine.Predict dat
|
||||
printfn "Actual: %d Predicted probability: zero: %.4f" n p.Score.[0]
|
||||
["one:"; "two:"; "three:"; "four:"; "five:"; "six:"; "seven:"; "eight:"; "nine:"]
|
||||
|> List.iteri
|
||||
(fun i w ->
|
||||
let i = i + 1
|
||||
printfn " %-6s %.4f" w p.Score.[i]
|
||||
)
|
||||
printfn ""
|
||||
)
|
||||
|
||||
|
||||
printfn "Hit any key to finish the app"
|
||||
Console.ReadKey() |> ignore
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\..\common\ConsoleHelper.fs" Link="Common\ConsoleHelper.fs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.ML" Version="$(MicrosoftMLVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.fs" />
|
||||
<Folder Include="Common\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -51,6 +51,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHubIssuesLabeler.Solutio
|
|||
EndProject
|
||||
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "GitHubLabeler", "end-to-end-apps\MulticlassClassification-GitHubLabeler\GitHubLabeler\GitHubLabelerConsoleApp\GitHubLabeler.fsproj", "{F1262B9E-1DBD-4219-BB01-A3F8226A0AF7}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MNIST.Solution", "MNIST.Solution", "{8F35CF6E-5813-43D7-AE24-A493AB14A3C9}"
|
||||
EndProject
|
||||
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "MNIST", "getting-started\MulticlassClassification_mnist\mnist\MNIST.fsproj", "{99E19BEE-2719-46EC-A146-D74FAB3260F1}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ProductRecommendation.Solution", "ProductRecommendation.Solution", "{D45A09B8-F6EA-4B2F-85A1-F1E6DB836DAE}"
|
||||
EndProject
|
||||
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "ProductRecommender", "getting-started\MatrixFactorization_ProductRecommendation\ProductRecommender\ProductRecommender.fsproj", "{5FF1F5D5-CBE0-4083-95CA-B5228349DF4A}"
|
||||
|
@ -168,6 +171,10 @@ Global
|
|||
{F1262B9E-1DBD-4219-BB01-A3F8226A0AF7}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{F1262B9E-1DBD-4219-BB01-A3F8226A0AF7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F1262B9E-1DBD-4219-BB01-A3F8226A0AF7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{99E19BEE-2719-46EC-A146-D74FAB3260F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{99E19BEE-2719-46EC-A146-D74FAB3260F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{99E19BEE-2719-46EC-A146-D74FAB3260F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{99E19BEE-2719-46EC-A146-D74FAB3260F1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F1262B9E-1DBD-4219-BB01-A3F8226A0AF7}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{F1262B9E-1DBD-4219-BB01-A3F8226A0AF7}.Release|x64.Build.0 = Release|Any CPU
|
||||
{5FF1F5D5-CBE0-4083-95CA-B5228349DF4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
@ -200,6 +207,7 @@ Global
|
|||
{880A505E-177F-4E6D-A563-2C527F8CCADD} = {4BCB9F94-ACBA-4437-B592-2E08E5C58CC4}
|
||||
{F2D39895-9C19-4468-A7BB-62207CC5BCD6} = {40328BCB-2F8E-4189-879C-697A6002C1E6}
|
||||
{F1262B9E-1DBD-4219-BB01-A3F8226A0AF7} = {959E87F1-8A76-4B9F-9DE4-E4D115BC58AC}
|
||||
{99E19BEE-2719-46EC-A146-D74FAB3260F1} = {8F35CF6E-5813-43D7-AE24-A493AB14A3C9}
|
||||
{5FF1F5D5-CBE0-4083-95CA-B5228349DF4A} = {D45A09B8-F6EA-4B2F-85A1-F1E6DB836DAE}
|
||||
{45A7CA79-4B9D-4855-A703-2559DC932EE6} = {A4E15AA2-F36A-49D4-A28A-8FCDD8BE5597}
|
||||
EndGlobalSection
|
||||
|
|
Загрузка…
Ссылка в новой задаче