Application project: Wired Brain Coffee (Blazor .NET 9 application)
Table of Contents
- Understanding Machine Learning and ML.NET
- Using Data Classification
- Working with Image Classification
- Final Project Structure
- Summary and Key Concepts
1. Understanding Machine Learning and ML.NET
What is Machine Learning?
Machine learning (ML) is a branch of artificial intelligence. It enables machines to learn so they can perform tasks autonomously. In other words, machine learning lets you program the unprogrammable.
Examples of Problems Solved by ML
With classic programming (if/else, switch, loops), the following questions are difficult to answer:
| Question | ML Scenario |
|---|---|
| Is there a coffee in this image? | Image classification |
| What language is this text written in? | Data/Text classification |
| Are comments on our site negative? | Binary classification |
| What should the price of our new product be? | Regression |
| Does this X-ray show a bone fracture? | Image classification |
Machine Learning Workflow
flowchart LR
A["๐ฏ Define\nthe Problem"] --> B["๐ Prepare\nthe Data"]
B --> C["๐ง Choose\nan Algorithm"]
C --> D["โ๏ธ Tune\nParameters"]
D --> E["โ
Evaluate\nthe Model"]
E --> F{Good\nenough?}
F -- No --> C
F -- Yes --> G["๐ Use\nthe Model"]
style A fill:#4A90D9,color:#fff
style B fill:#4A90D9,color:#fff
style C fill:#E8A838,color:#fff
style D fill:#E8A838,color:#fff
style E fill:#E8A838,color:#fff
style F fill:#aaa,color:#fff
style G fill:#27AE60,color:#fff
Note: The orange steps (algorithm selection, parameter tuning, evaluation) require machine learning expertise. This is exactly where AutoML comes in.
Understanding ML.NET
ML.NET is the open-source machine learning framework for .NET developers.
graph TD
A["๐ ML.NET\n(Microsoft.ML)"] --> B["Open-source"]
A --> C["NuGet package: Microsoft.ML"]
A --> D["Works offline\n(cloud + on-premises)"]
A --> E["Cross-platform\nWindows / Linux / macOS"]
A --> F["Resource: dot.net/ml"]
Basic ML.NET Code Flow
// 1. Create an ML context
var mlContext = new MLContext();
// 2. Load training data
var data = mlContext.Data.LoadFromTextFile<ModelInput>(dataPath, separatorChar: ';', hasHeader: true);
// 3. Build a transformation and training pipeline
var pipeline = mlContext.Transforms.Text.FeaturizeText("Features", "Message")
.Append(mlContext.MulticlassClassification.Trainers.LbfgsMaximumEntropy());
// 4. Train the model
var model = pipeline.Fit(data);
// 5. Use the model to make predictions
var predEngine = mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(model);
var prediction = predEngine.Predict(sampleData);
The Algorithm Selection Problem
For a simple binary classification with ML.NET, here are some available algorithms:
Binary Classification:
โโโ LbfgsLogisticRegression
โโโ SdcaLogisticRegression
โโโ AveragedPerceptron
โโโ FastTree
โโโ FastForest
โโโ LightGbm
โโโ ... (and many more)
Choosing the right algorithm requires specialized expertise. This is why AutoML exists.
AutoML and Model Builder
graph BT
A["ML.NET\nMicrosoft.ML"] --> B["AutoML\nMicrosoft.ML.AutoML"]
B --> C["Model Builder\n(Visual Studio 2022 Extension)"]
B --> D["ML.NET CLI\n(command line)"]
style A fill:#0078D4,color:#fff
style B fill:#E8A838,color:#fff
style C fill:#27AE60,color:#fff
style D fill:#27AE60,color:#fff
AutoML (Automated Machine Learning):
- Provides methods and processes to make ML accessible to non-experts
- You define the problem and prepare the data
- AutoML chooses the algorithm, tunes parameters, and evaluates the model
Model Builder:
- Visual Studio extension with a graphical interface
- Built on AutoML and ML.NET
- Official documentation: โYou donโt need machine learning expertise to use Model Builderโ
Setting Up the Environment
Prerequisites
| Component | Details |
|---|---|
| Visual Studio 2022 | Community, Professional, or Enterprise โ visualstudio.com |
| Workload | ASP.NET and web development |
| Extension | ML.NET Model Builder 2022 (in Individual components) |
โ ๏ธ Make sure to install the extension with the 2022 suffix (not the Visual Studio 2019 extension).
Verifying the Installation
- Open the Visual Studio Installer
- Click Modify
- Verify the ASP.NET and web development workload
- Go to Individual components > search for ML.NET
- Confirm that ML.NET Model Builder 2022 is installed
Exploring the Starter Project
The WiredBrain.WebApp project is a Blazor application (.NET 9) used as a base throughout the course.
Implemented ML Scenarios
graph LR
App["๐ง WiredBrain.WebApp\n(Blazor)"] --> LD["๐ Language Detection\nData Classification"]
App --> CD["โ Coffee Detection\nImage Classification"]
| Scenario | ML Type | Description |
|---|---|---|
| Language detection | Data/Text classification | Detect the language of entered text |
| Coffee detection | Image classification | Detect whether an image contains coffee |
2. Using Data Classification
Understanding the Task
Data classification allows classifying data into two or more categories. In this module, the goal is to detect the language of a text.
- Text classification: for raw textual data only
- Data classification: for tabular data (numbers + text)
Both can be used for this language detection scenario.
Languages to Detect
| Language | Example Message |
|---|---|
| ๐ฉ๐ช German | Ich programmiere gerne mit C#, F#, und Machine Learning mit ML.NET... |
| ๐ซ๐ท French | J'aime programmer en C#, F#, et l'apprentissage machine avec ML.NET... |
| ๐ฎ๐น Italian | Mi piace programmare con C#, F#, e il Machine Learning con ML.NET... |
| ๐ฌ๐ง English | I like programming with C#, F#, and Machine Learning with ML.NET... |
Creating a Model Builder Configuration File
To add machine learning to a .NET project:
- Right-click on the project in Solution Explorer
- Select Add > Machine Learning Model
- Name the file (e.g.,
LanguageDetectionModel) - Click Add
This menu item is only available if the ML.NET Model Builder 2022 extension is installed.
The .mbconfig file created is actually a JSON file:
{
"Scenario": "Classification",
"DataSource": {
"Version": 3,
"Type": "TabularFile",
"FilePath": "D:\\TrainingData\\languageDetection.csv",
"Delimiter": ";",
"HasHeader": true,
"ColumnProperties": [
{
"ColumnName": "Language",
"ColumnPurpose": "Label",
"ColumnDataFormat": "String"
},
{
"ColumnName": "Message",
"ColumnPurpose": "Feature",
"ColumnDataFormat": "String"
}
]
},
"Environment": {
"Type": "LocalCPU",
"Version": 1
}
}
Selecting a Scenario
The Model Builder interface presents the ML workflow on the left:
flowchart TD
S1["1๏ธโฃ Scenario"] --> S2["2๏ธโฃ Environment"]
S2 --> S3["3๏ธโฃ Data"]
S3 --> S4["4๏ธโฃ Train"]
S4 --> S5["5๏ธโฃ Evaluate"]
S5 --> S6["6๏ธโฃ Consume"]
Available Scenario Categories
Model Builder Scenarios:
โ
โโโ ๐ Tabular (CSV or SQL data)
โ โโโ Data classification โ our choice for language detection
โ โโโ Value prediction (regression)
โ โโโ Recommendation
โ
โโโ ๐ผ๏ธ Computer Vision
โ โโโ Image classification โ used in module 3
โ โโโ Object detection (Preview)
โ
โโโ ๐ฌ Natural Language Processing (Preview)
โโโ Text classification
โโโ ...
For our language detection scenario: Data classification with Local (CPU) environment.
Specifying Training Data
Training CSV File Format
The languageDetection.csv file uses ; as separator:
Language;Message
German;Ich programmiere gerne mit C#, F#, und Machine Learning mit ML.NET finde ich echt richtig gut.
French;J'aime programmer en C#, F#, et l'apprentissage machine avec ML.NET est vraiment bon.
Italian;Mi piace programmare con C#, F#, e il Machine Learning con ML.NET รจ davvero buono.
English;I like programming with C#, F#, and Machine Learning with ML.NET is really good.
German;Wenn ich mal nicht am Computer sitze, dann geniesse ich draussen die Sonne.
French;Quand je ne suis pas assis devant l'ordinateur, je profite du soleil dehors.
Italian;Quando non sono seduto al computer, mi godo il sole fuori.
English;When I am not sitting at the computer, I enjoy the sun outside.
German;Hallo, ich bin Thomas. Wer bist Du?
French;Salut, je m'appelle Thomas. Qui รชtes-vous?
Italian;Ciao, sono Thomas. Chi sei?
English;Hi, I'm Thomas. Who are you?
Column Roles (Column Purposes)
| Purpose | Description |
|---|---|
| Label | The column to predict (only one per model) โ here: Language |
| Feature | Column used to make the prediction โ here: Message |
| Ignore | Column not needed for prediction |
Advanced Data Options
In Advanced data options > Data formatting:
- Enable โDoes the data contain column headers?โ โ Yes
In Column settings:
Languageโ Purpose: LabelMessageโ Purpose: Feature
Training the Model
Training Parameters
| Parameter | Recommended Value | Description |
|---|---|---|
| Time to train | 20+ seconds | Longer = more algorithms explored |
| Metric | Default | Used to find the best model |
| Trainers | All included | Option to exclude algorithms |
Model Builder automatically selects a training time based on dataset size. Longer durations allow exploring more models.
Training Results
After training (~20 seconds), 69 models were explored. The best selected model:
Best model: LbfgsMaximumEntropyMulti
Training duration: 18.9 seconds
Models explored: 69
Files Generated by Model Builder
WiredBrain.WebApp/
โโโ LanguageDetectionModel.mbconfig โ JSON configuration
โโโ LanguageDetectionModel.mlnet โ trained binary model
โโโ LanguageDetectionModel.consumption.cs โ C# code to use the model
โโโ LanguageDetectionModel.training.cs โ C# code to retrain
โโโ LanguageDetectionModel.evaluate.cs โ C# code to evaluate (PFI)
Generated Transformation Pipeline (training.cs)
public static IEstimator<ITransformer> BuildPipeline(MLContext mlContext)
{
var pipeline = mlContext.Transforms.Text
// 1. Transform text into numerical features
.FeaturizeText(inputColumnName: @"Message", outputColumnName: @"Message")
// 2. Concatenate features
.Append(mlContext.Transforms.Concatenate(@"Features", new[] { @"Message" }))
// 3. Map textual labels to numerical keys
.Append(mlContext.Transforms.Conversion.MapValueToKey(
outputColumnName: @"Language",
inputColumnName: @"Language",
addKeyValueAnnotationsAsText: false))
// 4. Train with LbfgsLogisticRegression (One-vs-All)
.Append(mlContext.MulticlassClassification.Trainers.OneVersusAll(
binaryEstimator: mlContext.BinaryClassification.Trainers.LbfgsLogisticRegression(
new LbfgsLogisticRegressionBinaryTrainer.Options()
{
L1Regularization = 0.03125F,
L2Regularization = 0.0324988F,
LabelColumnName = @"Language",
FeatureColumnName = @"Features"
}),
labelColumnName: @"Language"))
// 5. Convert predicted keys back to textual labels
.Append(mlContext.Transforms.Conversion.MapKeyToValue(
outputColumnName: @"PredictedLabel",
inputColumnName: @"PredictedLabel"));
return pipeline;
}
Evaluating the Model
In the Evaluate section of Model Builder, you can test the model directly:
| Test Message | Result | Probability |
|---|---|---|
I enjoy learning with Visual Studio Code | English | 86% |
Ich lerne gerne mit ML.NET | German | 90% |
J'aime apprendre avec C# | French | 56% |
If the model is insufficient, go back to Train to increase training time or exclude underperforming algorithms.
Evaluation via Permutation Feature Importance (PFI)
The LanguageDetectionModel.evaluate.cs file contains a method to calculate feature importance:
public static List<Tuple<string, double>> CalculatePFI(
MLContext mlContext,
IDataView trainData,
ITransformer model,
string labelColumnName)
{
var preprocessedTrainData = model.Transform(trainData);
var permutationFeatureImportance =
mlContext.MulticlassClassification
.PermutationFeatureImportance(
model,
preprocessedTrainData,
labelColumnName: labelColumnName);
var featureImportanceMetrics = permutationFeatureImportance
.Select((kvp) => new { kvp.Key, kvp.Value.MacroAccuracy })
.OrderByDescending(f => Math.Abs(f.MacroAccuracy.Mean));
var featurePFI = new List<Tuple<string, double>>();
foreach (var feature in featureImportanceMetrics)
{
var pfiValue = Math.Abs(feature.MacroAccuracy.Mean);
featurePFI.Add(new Tuple<string, double>(feature.Key, pfiValue));
}
return featurePFI;
}
Consuming the Model
Auto-Generated Classes
// LanguageDetectionModel.consumption.cs โ auto-generated by ML.NET Model Builder
public partial class LanguageDetectionModel
{
// Input class
public class ModelInput
{
[LoadColumn(0)]
[ColumnName(@"Language")]
public string Language { get; set; }
[LoadColumn(1)]
[ColumnName(@"Message")]
public string Message { get; set; }
}
// Output class
public class ModelOutput
{
[ColumnName(@"Language")]
public uint Language { get; set; }
[ColumnName(@"Message")]
public float[] Message { get; set; }
[ColumnName(@"Features")]
public float[] Features { get; set; }
[ColumnName(@"PredictedLabel")]
public string PredictedLabel { get; set; } // โ predicted label
[ColumnName(@"Score")]
public float[] Score { get; set; }
}
// Path to the binary model file
private static string MLNetModelPath = Path.GetFullPath("LanguageDetectionModel.mlnet");
// Lazy PredictionEngine (thread-safe initialization)
public static readonly Lazy<PredictionEngine<ModelInput, ModelOutput>> PredictEngine =
new Lazy<PredictionEngine<ModelInput, ModelOutput>>(() => CreatePredictEngine(), true);
private static PredictionEngine<ModelInput, ModelOutput> CreatePredictEngine()
{
var mlContext = new MLContext();
ITransformer mlModel = mlContext.Model.Load(MLNetModelPath, out var _);
return mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(mlModel);
}
// Main prediction method
public static ModelOutput Predict(ModelInput input)
{
var predEngine = PredictEngine.Value;
return predEngine.Predict(input);
}
}
Blazor Component โ LanguageDetection.razor (final version)
@page "/languagedetection"
@using WiredBrain_WebApp
@rendermode InteractiveServer
<PageTitle>Language Detection</PageTitle>
<h1>Language Detection</h1>
<h2>Scenario: Data classification (or alternatively text classification)</h2>
<div>Please enter your text here:</div>
<textarea class="form-control" style="height:150px;margin-top:5px"
spellcheck="false"
@bind="inputText"
@bind:event="oninput"
@bind:after="TextChanged" />
<div style="margin-top:20px;">
Detected language:
<span style="font-weight:bold">@detectedLanguage</span>
</div>
@if (allLabels is not null)
{
<ul>
@foreach (var label in allLabels)
{
<li>@label.Key - @(Math.Round(label.Value * 100, 2))%</li>
}
</ul>
}
@code {
private string? inputText;
private string? detectedLanguage = "None";
private IOrderedEnumerable<KeyValuePair<string, float>>? allLabels;
private void TextChanged()
{
if (inputText is null or "")
{
detectedLanguage = "None";
allLabels = null;
return;
}
// 1. Create input object
var input = new LanguageDetectionModel.ModelInput
{
Message = inputText
};
// 2. Make the prediction (single label)
var output = LanguageDetectionModel.Predict(input);
detectedLanguage = output.PredictedLabel;
// 3. Get all labels with their scores
allLabels = LanguageDetectionModel.PredictAllLabels(input);
}
}
Prediction Call Flow
sequenceDiagram
participant U as User (Browser)
participant R as LanguageDetection.razor
participant M as LanguageDetectionModel
participant E as PredictionEngine
U->>R: Enters text (oninput)
R->>R: TextChanged() triggered
R->>M: Predict(ModelInput { Message })
M->>E: predEngine.Predict(input)
E-->>M: ModelOutput { PredictedLabel, Score[] }
M-->>R: output.PredictedLabel
R->>M: PredictAllLabels(input)
M->>E: predEngine.Predict(input)
E-->>M: Scores with labels
M-->>R: IOrderedEnumerable<KeyValuePair<string, float>>
R-->>U: Displays detected language + all percentages
Required NuGet Packages (module 2)
<!-- WiredBrain.WebApp.csproj -->
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.ML" Version="4.0.0" />
</ItemGroup>
<ItemGroup Label="LanguageDetectionModel">
<None Include="LanguageDetectionModel.mlnet">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
Predicting All Labels
The PredictAllLabels method returns an IOrderedEnumerable<KeyValuePair<string, float>> where:
- Key = label (e.g., โEnglishโ)
- Value = score between 0 and 1 (0% to 100%)
public static IOrderedEnumerable<KeyValuePair<string, float>> PredictAllLabels(ModelInput input)
{
var predEngine = PredictEngine.Value;
var result = predEngine.Predict(input);
return GetSortedScoresWithLabels(result);
}
public static IOrderedEnumerable<KeyValuePair<string, float>> GetSortedScoresWithLabels(ModelOutput result)
{
var unlabeledScores = result.Score;
var labelNames = GetLabels(result);
Dictionary<string, float> labeledScores = new Dictionary<string, float>();
for (int i = 0; i < labelNames.Count(); i++)
{
var labelName = labelNames.ElementAt(i);
labeledScores.Add(labelName.ToString(), unlabeledScores[i]);
}
return labeledScores.OrderByDescending(c => c.Value);
}
private static IEnumerable<string> GetLabels(ModelOutput result)
{
var schema = PredictEngine.Value.OutputSchema;
var labelColumn = schema.GetColumnOrNull("Language");
if (labelColumn == null)
throw new Exception("Language column not found.");
var keyNames = new VBuffer<ReadOnlyMemory<char>>();
labelColumn.Value.GetKeyValues(ref keyNames);
return keyNames.DenseValues().Select(x => x.ToString());
}
Display in Blazor Component
@if (allLabels is not null)
{
<ul>
@foreach (var label in allLabels)
{
<!-- Example: English - 86.42% -->
<li>@label.Key - @(Math.Round(label.Value * 100, 2))%</li>
}
</ul>
}
Retraining the Model
To add a new language (e.g., Spanish), simply update the training CSV without changing the application code.
Adding Spanish to the CSV File (languageDetection_vNext.csv)
Spanish;Me gusta programar con C#, F#, y me gusta mucho el aprendizaje automรกtico con ML.NET.
Spanish;Cuando no estoy frente al ordenador, disfruto del sol al aire libre.
Spanish;ยฟQuiรฉn registrรณ este cรณdigo? ยฟFuiste tรบ? Oh, fui yo.
Spanish;Anoche empecรฉ a leer un libro nuevo. ยฟTรบ tambiรฉn estรกs leyendo un libro?
Spanish;Gracias por visitar thomasclaudiushuber.com.
Spanish;Hola, soy Thomas. ยฟY tรบ quiรฉn eres?
Retraining Steps
flowchart LR
A["๐ Add Spanish data\nto CSV"] --> B["Double-click\nLanguageDetectionModel.mbconfig"]
B --> C["Go to Train step"]
C --> D["Click\n'Train again'"]
D --> E["New best model:\nSdcaMaximumEntropyMulti"]
E --> F["โ
Model now detects\n5 languages"]
Result: After retraining, the model now detects: German, French, Italian, English and Spanish โ without any code change!
Model comparison:
| Training | Selected Algorithm | Languages |
|---|---|---|
| 1st | LbfgsMaximumEntropyMulti | DE, FR, IT, EN |
| 2nd (with ES) | SdcaMaximumEntropyMulti | DE, FR, IT, EN, ES |
Retraining via Code
// LanguageDetectionModel.training.cs
public static void Train(
string outputModelPath,
string inputDataFilePath = RetrainFilePath,
char separatorChar = RetrainSeparatorChar,
bool hasHeader = RetrainHasHeader,
bool allowQuoting = RetrainAllowQuoting)
{
var mlContext = new MLContext();
var data = LoadIDataViewFromFile(mlContext, inputDataFilePath, separatorChar, hasHeader, allowQuoting);
var model = RetrainModel(mlContext, data);
SaveModel(mlContext, model, data, outputModelPath);
}
public static ITransformer RetrainModel(MLContext mlContext, IDataView trainData)
{
var pipeline = BuildPipeline(mlContext);
var model = pipeline.Fit(trainData);
return model;
}
public static void SaveModel(MLContext mlContext, ITransformer model, IDataView data, string modelSavePath)
{
DataViewSchema dataViewSchema = data.Schema;
using (var fs = File.Create(modelSavePath))
{
mlContext.Model.Save(model, dataViewSchema, fs);
}
}
3. Working with Image Classification
Understanding the Task
The goal is to enable Wired Brain Coffee to detect images containing coffee. We want to classify images into 3 categories: coffee, goat, other.
Training Image Data Structure
Images/
โโโ coffee/ (15 coffee images)
โ โโโ image1.jpg
โ โโโ image2.jpg
โ โโโ ...
โโโ goat/ (6 goat images)
โ โโโ image1.jpg
โ โโโ ...
โโโ other/ (7 miscellaneous images)
โโโ image1.jpg
โโโ ...
The subfolder names automatically become the modelโs labels.
Selecting the Scenario and Configuring the GPU
Environment Options for Image Classification
| Environment | Speed | Requirements |
|---|---|---|
| Local (CPU) | Slow | None |
| Local (GPU) | Fast โ | NVIDIA CUDA 10.1 + cuDNN 7.6.4 |
| Azure | Variable | Azure subscription |
GPU Prerequisites (NVIDIA CUDA)
GPU configuration for ML.NET Model Builder:
โโโ CUDA 10.1 โ developer.nvidia.com/cuda-10.1-download-archive
โโโ cuDNN 7.6.4 โ developer.nvidia.com/cudnn (exact version required)
โโโ Compatible GPU โ NVIDIA GeForce with CUDA support
(e.g.: GTX 1650, RTX 30xx, etc.)
CUDA = Compute Unified Device Architecture: framework for accessing raw GPU computing power.
cuDNN = CUDA Deep Neural Network: library of primitives for deep neural networks, built on CUDA.
Steps in Model Builder
- Right-click on the project โ Add > Machine Learning Model
- Name the model:
CoffeeDetectionModel - Click Add
- Select the scenario: Image classification
- Select the environment: Local (GPU) (or CPU if no NVIDIA GPU)
Specifying Training Data
The Images/ folder must have this structure (subfolders = labels):
Images/
โโโ coffee/ โ label "coffee"
โโโ goat/ โ label "goat"
โโโ other/ โ label "other"
Advanced Options: Validation Data
| Split | Usage |
|---|---|
| 80% | Training data |
| 20% | Validation data |
Training the Model
Additional NuGet Packages for Image Classification
<!-- WiredBrain.WebApp.csproj โ additions for module 3 -->
<ItemGroup>
<PackageReference Include="Microsoft.ML" Version="4.0.0" />
<PackageReference Include="Microsoft.ML.Vision" Version="4.0.0" />
<PackageReference Include="SciSharp.TensorFlow.Redist" Version="2.3.1" />
</ItemGroup>
<ItemGroup Label="CoffeeDetectionModel">
<None Include="CoffeeDetectionModel.mlnet">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
Training Pipeline for Image Classification (CoffeeDetectionModel.training.cs)
public static IEstimator<ITransformer> BuildPipeline(MLContext mlContext)
{
var pipeline = mlContext.Transforms.Conversion
// 1. Map textual labels to numerical keys
.MapValueToKey(
outputColumnName: @"Label",
inputColumnName: @"Label",
addKeyValueAnnotationsAsText: false)
// 2. Train with ImageClassification (transfer learning)
.Append(mlContext.MulticlassClassification.Trainers.ImageClassification(
labelColumnName: @"Label",
scoreColumnName: @"Score",
featureColumnName: @"ImageSource"))
// 3. Convert predicted keys back to textual labels
.Append(mlContext.Transforms.Conversion.MapKeyToValue(
outputColumnName: @"PredictedLabel",
inputColumnName: @"PredictedLabel"));
return pipeline;
}
Loading Images from a Folder
public static IDataView LoadImageFromFolder(MLContext mlContext, string folder)
{
var res = new List<ModelInput>();
var allowedImageExtensions = new[] { ".png", ".jpg", ".jpeg", ".gif" };
DirectoryInfo rootDirectoryInfo = new DirectoryInfo(folder);
DirectoryInfo[] subDirectories = rootDirectoryInfo.GetDirectories();
if (subDirectories.Length == 0)
throw new Exception("No subdirectories found");
foreach (DirectoryInfo directory in subDirectories)
{
var imageList = directory.EnumerateFiles()
.Where(f => allowedImageExtensions.Contains(f.Extension.ToLower()));
if (imageList.Count() > 0)
{
res.AddRange(imageList.Select(i => new ModelInput
{
Label = directory.Name, // subfolder name = label
ImageSource = File.ReadAllBytes(i.FullName), // image as byte[]
}));
}
}
return mlContext.Data.LoadFromEnumerable(res);
}
Generated Files for Image Classification
WiredBrain.WebApp/
โโโ CoffeeDetectionModel.mbconfig โ JSON configuration
โโโ CoffeeDetectionModel.mlnet โ trained binary model
โโโ CoffeeDetectionModel.consumption.cs โ code to use the model
โโโ CoffeeDetectionModel.training.cs โ code to retrain
๐ No
.evaluate.csfile for Image Classification (unlike Data Classification).
Evaluating the Model
In the Evaluate section of Model Builder, you can test with images:
| Tested Image | Result | Accuracy |
|---|---|---|
| Coffee in a cup | coffee | 100% |
| Thomas drinking coffee (image 8) | coffee | 97% |
| Goat image (image 16) | goat | 97% |
| โOtherโ image | other | ~85%+ |
| Thomas photo (not in training) | other | 62% |
| Wired Brain Coffee logo | coffee | 82% |
Recommendation: Always test with images outside the training dataset for a realistic evaluation.
Consuming the Model
Auto-Generated Classes (CoffeeDetectionModel.consumption.cs)
public partial class CoffeeDetectionModel
{
// Input class โ image is passed as byte[]
public class ModelInput
{
[LoadColumn(0)]
[ColumnName(@"Label")]
public string Label { get; set; }
[LoadColumn(1)]
[ColumnName(@"ImageSource")]
public byte[] ImageSource { get; set; } // โ image as byte array
}
// Output class
public class ModelOutput
{
[ColumnName(@"Label")]
public uint Label { get; set; }
[ColumnName(@"ImageSource")]
public byte[] ImageSource { get; set; }
[ColumnName(@"PredictedLabel")]
public string PredictedLabel { get; set; } // โ "coffee", "goat", or "other"
[ColumnName(@"Score")]
public float[] Score { get; set; }
}
private static string MLNetModelPath = Path.GetFullPath("CoffeeDetectionModel.mlnet");
public static readonly Lazy<PredictionEngine<ModelInput, ModelOutput>> PredictEngine =
new Lazy<PredictionEngine<ModelInput, ModelOutput>>(() => CreatePredictEngine(), true);
private static PredictionEngine<ModelInput, ModelOutput> CreatePredictEngine()
{
var mlContext = new MLContext();
ITransformer mlModel = mlContext.Model.Load(MLNetModelPath, out var _);
return mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(mlModel);
}
public static ModelOutput Predict(ModelInput input)
{
var predEngine = PredictEngine.Value;
return predEngine.Predict(input);
}
}
Blazor Component โ CoffeeDetection.razor (final version)
@page "/coffeedetection"
@using WiredBrain_WebApp
@rendermode InteractiveServer
<PageTitle>Coffee Detection</PageTitle>
<h1>Coffee Detection</h1>
<h2>Scenario: Image classification</h2>
<!-- Select image file -->
<InputFile OnChange="LoadFileAsync" class="form-control" />
@if (errorMessage is not null)
{
<div class="alert alert-danger" style="margin-top:10px">@errorMessage</div>
}
@if (base64ImageSource is not null)
{
<div style="margin-top:10px">
<img src="@base64ImageSource" />
</div>
}
<div style="margin-top:20px;">
Detected content:
<span style="font-weight:bold">@detectedContent</span>
</div>
<!-- Display all labels with their percentages -->
@if (allLabels is not null)
{
<ul>
@foreach (var label in allLabels)
{
<li>@label.Key - @(Math.Round(label.Value * 100, 2))%</li>
}
</ul>
}
@code {
private const long maxFileSize = 2 * 1024 * 1024; // 2 MB
private string? base64ImageSource;
private string? detectedContent = "None";
private string? errorMessage;
private IOrderedEnumerable<KeyValuePair<string, float>>? allLabels;
private async Task LoadFileAsync(InputFileChangeEventArgs e)
{
base64ImageSource = null;
detectedContent = "None";
if (!IsFileSizeValid(e.File.Size)) return;
// 1. Read image as byte[]
byte[] image = await GetImageByteArray(e.File);
// 2. Prepare base64 display
SetBase64ImageSource(e.File.ContentType, image);
// 3. Create input object
var input = new CoffeeDetectionModel.ModelInput
{
ImageSource = image
};
// 4. Make the prediction
var output = CoffeeDetectionModel.Predict(input);
detectedContent = output.PredictedLabel;
// 5. Get all labels with their scores
allLabels = CoffeeDetectionModel.PredictAllLabels(input);
}
private async Task<byte[]> GetImageByteArray(IBrowserFile browserFile)
{
using var stream = browserFile.OpenReadStream(maxAllowedSize: maxFileSize);
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
return ms.ToArray();
}
private void SetBase64ImageSource(string contentType, byte[] image)
{
var base64String = Convert.ToBase64String(image);
base64ImageSource = $"data:{contentType};base64,{base64String}";
}
private bool IsFileSizeValid(long fileSize)
{
errorMessage = null;
if (fileSize > maxFileSize)
{
var oneMegaByte = 1024m * 1024m;
var fileSizeInMB = fileSize / oneMegaByte;
errorMessage = $"File has {fileSizeInMB:N2}MB, but maximum upload size is 2MB";
}
return errorMessage is null;
}
}
Call Flow for Image Classification
sequenceDiagram
participant U as User (Browser)
participant R as CoffeeDetection.razor
participant M as CoffeeDetectionModel
participant E as PredictionEngine
U->>R: Selects an image file (InputFile)
R->>R: LoadFileAsync() triggered
R->>R: Reads image โ byte[]
R->>R: SetBase64ImageSource() (display)
R->>M: Predict(ModelInput { ImageSource: byte[] })
M->>E: predEngine.Predict(input)
E-->>M: ModelOutput { PredictedLabel: "coffee" }
M-->>R: output.PredictedLabel
R->>M: PredictAllLabels(input)
M-->>R: IOrderedEnumerable (coffee: 98%, goat: 1.2%, other: 0.8%)
R-->>U: Displays detected content + all percentages
Predicting All Labels
The mechanism is identical to Data Classification:
public static IOrderedEnumerable<KeyValuePair<string, float>> PredictAllLabels(ModelInput input)
{
var predEngine = PredictEngine.Value;
var result = predEngine.Predict(input);
return GetSortedScoresWithLabels(result);
}
private static IEnumerable<string> GetLabels(ModelOutput result)
{
var schema = PredictEngine.Value.OutputSchema;
var labelColumn = schema.GetColumnOrNull("Label");
if (labelColumn == null)
throw new Exception("Label column not found.");
var keyNames = new VBuffer<ReadOnlyMemory<char>>();
labelColumn.Value.GetKeyValues(ref keyNames);
return keyNames.DenseValues().Select(x => x.ToString());
}
Example result for a coffee image:
coffee - 98.00%
goat - 1.20%
other - 0.80%
4. Final Project Structure
Complete Architecture
WiredBrain.WebApp/
โ
โโโ ๐ Program.cs โ Blazor configuration
โโโ ๐ WiredBrain.WebApp.csproj โ NuGet packages
โ
โโโ ๐ง LanguageDetectionModel.mbconfig โ Model Builder config
โโโ ๐ง LanguageDetectionModel.mlnet โ Binary model
โโโ ๐ LanguageDetectionModel.consumption.cs โ Prediction API
โโโ ๐ LanguageDetectionModel.training.cs โ Retraining code
โโโ ๐ LanguageDetectionModel.evaluate.cs โ PFI evaluation
โ
โโโ ๐ง CoffeeDetectionModel.mbconfig โ Model Builder config
โโโ ๐ง CoffeeDetectionModel.mlnet โ Binary model
โโโ ๐ CoffeeDetectionModel.consumption.cs โ Prediction API
โโโ ๐ CoffeeDetectionModel.training.cs โ Retraining code
โ
โโโ Components/
โโโ Layout/
โ โโโ NavMenu.razor โ Navigation menu
โ โโโ MainLayout.razor
โโโ Pages/
โโโ Home.razor โ Home page
โโโ LanguageDetection.razor โ Scenario 1
โโโ CoffeeDetection.razor โ Scenario 2
Program.cs Configuration
using WiredBrain.WebApp.Components;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAntiforgery();
app.MapStaticAssets();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.Run();
Comparison of the Two ML Scenarios
graph TB
subgraph "๐ Language Detection"
LD1["Input: text (string)"]
LD2["Algorithm: LbfgsMaximumEntropyMulti\nor SdcaMaximumEntropyMulti"]
LD3["Output: predicted language\n(German / French / Italian / English / Spanish)"]
LD4["Data: CSV with columns\nLanguage + Message"]
LD1 --> LD2 --> LD3
end
subgraph "โ Coffee Detection"
CD1["Input: image (byte[])"]
CD2["Algorithm: ImageClassification\n(Transfer Learning / Deep Learning)"]
CD3["Output: predicted category\n(coffee / goat / other)"]
CD4["Data: image folders\nby label"]
CD1 --> CD2 --> CD3
end
| Aspect | Language Detection | Coffee Detection |
|---|---|---|
| Scenario type | Data Classification | Image Classification |
| Data format | CSV (tabular text) | Image folders |
| Input type | string (message) | byte[] (image) |
| Environment | Local CPU | Local GPU (recommended) |
| Main package | Microsoft.ML | Microsoft.ML + Microsoft.ML.Vision + SciSharp.TensorFlow.Redist |
| evaluate.cs file | โ Yes | โ No |
| Easy retraining | Add CSV rows | Add images to folders |
5. Summary and Key Concepts
Glossary
| Term | Definition |
|---|---|
| Machine Learning (ML) | Branch of AI enabling machines to learn from data |
| ML.NET | Microsoftโs open-source ML framework for .NET developers |
| AutoML | Automated Machine Learning โ automates algorithm selection, tuning, and evaluation |
| Model Builder | Visual Studio graphical extension for creating ML models with ML.NET |
| MLContext | Central context for all ML.NET operations |
| IDataView | Lazy and composable data structure for ML.NET |
| ITransformer | Represents a trained ML model capable of transforming data |
| PredictionEngine | Prediction engine for single-item inference (one prediction at a time) |
| Pipeline | Sequence of transformations + training in ML.NET |
| Feature | Input variable used to make a prediction |
| Label | Output variable to predict |
| Binary Classification | Classification into 2 categories (0/1, positive/negative) |
| Multiclass Classification | Classification into 3+ categories |
| Transfer Learning | Reusing a pre-trained model for a new task |
| CUDA | Compute Unified Device Architecture โ NVIDIA framework for GPU computing |
| cuDNN | CUDA Deep Neural Network โ library of primitives for deep learning |
| PFI | Permutation Feature Importance โ measures feature importance |
| .mbconfig | JSON configuration file for Model Builder |
| .mlnet | Binary file containing the trained ML model |
Complete ML Development Process with Model Builder
flowchart TD
A["๐ฏ Define the problem\n(e.g.: detect language)"] --> B["๐ Prepare the data\n(CSV or image folders)"]
B --> C["โ Add > Machine Learning Model\n(create the .mbconfig)"]
C --> D["๐ฌ Select the scenario\n(Data / Image Classification)"]
D --> E["๐ฅ๏ธ Choose the environment\n(CPU / GPU / Azure)"]
E --> F["๐ Specify the data\n(CSV file or folder)"]
F --> G["โก Train the model\n(AutoML explores algorithms)"]
G --> H["๐ Evaluate the model\n(test examples)"]
H --> I{Satisfied?}
I -- No --> G
I -- Yes --> J["๐ป Consume the model\n(copy generated code)"]
J --> K["๐ Integrate into the application\n(Blazor, WPF, API, etc.)"]
style A fill:#4A90D9,color:#fff
style B fill:#4A90D9,color:#fff
style C fill:#9B59B6,color:#fff
style D fill:#9B59B6,color:#fff
style E fill:#9B59B6,color:#fff
style F fill:#9B59B6,color:#fff
style G fill:#E8A838,color:#fff
style H fill:#E8A838,color:#fff
style I fill:#aaa,color:#fff
style J fill:#27AE60,color:#fff
style K fill:#27AE60,color:#fff
Key Points to Remember
- ML.NET is available via the
Microsoft.MLNuGet package โ works offline, cross-platform. - AutoML automates the complex parts: algorithm selection, hyperparameter tuning, evaluation.
- Model Builder enables using ML.NET without machine learning expertise, via a graphical interface.
- The
.mbconfigfile orchestrates the process โ it is an editable JSON. - The
.mlnetfile is the binary model โ it must be copied to the output folder (CopyToOutputDirectory). PredictionEngine<TIn, TOut>is the single-item prediction engine โ initialized asLazy<>for performance.- Retraining a model = update the data + click โTrain againโ โ without changing the application code.
- For Image Classification, ML.NET uses Transfer Learning via TensorFlow under the hood.
PredictAllLabels()returns scores for all categories, not just the main prediction.- To change the data path in a
.mbconfig, edit the JSON directly or use the Model Builder interface.
Additional Resources
- ML.NET: dot.net/ml
- Model Builder: learn.microsoft.com/dotnet/machine-learning/automate-training-with-model-builder
Search Terms
apps ยท ml.net ยท ml ยท platforms ยท deployment ยท machine ยท data ยท science ยท model ยท classification ยท image ยท builder ยท blazor ยท component ยท flow ยท generated ยท options ยท retraining ยท scenario ยท additional ยท auto-generated ยท call ยท classes ยท configuration