
Mlnet
- 17 installs
- 466 repo stars
- Updated July 25, 2026
- managedcode/dotnet-skills
Helps with ai & agent building tasks.
About
mlnet is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- mlnet
- AI & Agent Building
- AI-coding skill
Mlnet by the numbers
- 17 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #10,861 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/managedcode/dotnet-skills --skill mlnetAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 17 |
|---|---|
| repo stars | ★ 466 |
| Last updated | July 25, 2026 |
| Repository | managedcode/dotnet-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
ML.NET
Trigger On
- integrating machine learning into a .NET application
- training or retraining ML.NET models from local data
- reviewing inference pipelines, model loading, or AutoML-generated code
Workflow
1. Start from the prediction task and data quality, not the algorithm or package list. 2. Separate training code from inference code so the production path stays lean and predictable. 3. Review feature engineering, normalization, label quality, and evaluation metrics before trusting model output. 4. Use Model Builder or the ML.NET CLI when they speed up exploration, but inspect the generated C# before treating it as production architecture. 5. Plan how the model is loaded, versioned, and refreshed in the application lifecycle. 6. Validate with representative datasets and explicit evaluation, not only with a sample that happens to run.
Deliver
- ML.NET pipelines that fit the prediction task
- production-usable inference integration
- evaluation evidence tied to the business scenario
Validate
- model quality is measured, not assumed
- training and inference responsibilities are separated
- deployment and versioning expectations are explicit
References
- patterns.md - Data loading, training pipelines, evaluation metrics, deployment strategies, and feature engineering patterns
- examples.md - Complete examples for sentiment analysis, price prediction, image classification, anomaly detection, recommendations, clustering, fraud detection, text classification, object detection, and AutoML
{
"version": "1.0.0",
"category": "AI",
"package_prefix": "Microsoft.ML"
}
ML.NET Examples
This reference provides complete examples for common machine learning scenarios using ML.NET.
Sentiment Analysis
Binary classification for detecting positive or negative sentiment in text.
public class SentimentData(string Text, bool Sentiment)
{
[LoadColumn(0)]
public string Text { get; } = Text;
[LoadColumn(1), ColumnName("Label")]
public bool Sentiment { get; } = Sentiment;
}
public class SentimentPrediction
{
[ColumnName("PredictedLabel")]
public bool Prediction { get; set; }
public float Probability { get; set; }
public float Score { get; set; }
}
public class SentimentAnalysisService(MLContext mlContext)
{
public ITransformer TrainModel(string dataPath)
{
var data = mlContext.Data.LoadFromTextFile<SentimentData>(
dataPath,
hasHeader: true,
separatorChar: '\t');
var split = mlContext.Data.TrainTestSplit(data, testFraction: 0.2);
var pipeline = mlContext.Transforms.Text
.FeaturizeText("Features", nameof(SentimentData.Text))
.Append(mlContext.BinaryClassification.Trainers.SdcaLogisticRegression(
labelColumnName: "Label",
featureColumnName: "Features"));
var model = pipeline.Fit(split.TrainSet);
// Evaluate
var predictions = model.Transform(split.TestSet);
var metrics = mlContext.BinaryClassification.Evaluate(predictions, "Label");
Console.WriteLine($"Accuracy: {metrics.Accuracy:P2}");
Console.WriteLine($"AUC: {metrics.AreaUnderRocCurve:P2}");
Console.WriteLine($"F1 Score: {metrics.F1Score:P2}");
return model;
}
public SentimentPrediction Predict(ITransformer model, string text)
{
var engine = mlContext.Model.CreatePredictionEngine<SentimentData, SentimentPrediction>(model);
return engine.Predict(new SentimentData(text, false));
}
}
// Usage
public class SentimentAnalysisExample
{
public void Run()
{
var mlContext = new MLContext(seed: 42);
var service = new SentimentAnalysisService(mlContext);
var model = service.TrainModel("sentiment_data.tsv");
var result = service.Predict(model, "This product is amazing!");
Console.WriteLine($"Sentiment: {(result.Prediction ? "Positive" : "Negative")}");
Console.WriteLine($"Probability: {result.Probability:P2}");
}
}Price Prediction (Regression)
Predicting house prices based on features.
public class HouseData(
float Size,
float Bedrooms,
float Bathrooms,
float Age,
string Location,
float Price)
{
[LoadColumn(0)]
public float Size { get; } = Size;
[LoadColumn(1)]
public float Bedrooms { get; } = Bedrooms;
[LoadColumn(2)]
public float Bathrooms { get; } = Bathrooms;
[LoadColumn(3)]
public float Age { get; } = Age;
[LoadColumn(4)]
public string Location { get; } = Location;
[LoadColumn(5)]
public float Price { get; } = Price;
}
public class HousePricePrediction
{
[ColumnName("Score")]
public float PredictedPrice { get; set; }
}
public class HousePricePredictionService(MLContext mlContext)
{
public ITransformer TrainModel(IDataView data)
{
var split = mlContext.Data.TrainTestSplit(data, testFraction: 0.2);
var pipeline = mlContext.Transforms.Categorical
.OneHotEncoding("LocationEncoded", nameof(HouseData.Location))
.Append(mlContext.Transforms.Concatenate(
"Features",
nameof(HouseData.Size),
nameof(HouseData.Bedrooms),
nameof(HouseData.Bathrooms),
nameof(HouseData.Age),
"LocationEncoded"))
.Append(mlContext.Transforms.NormalizeMinMax("Features"))
.Append(mlContext.Regression.Trainers.FastTree(
labelColumnName: nameof(HouseData.Price),
featureColumnName: "Features",
numberOfLeaves: 20,
numberOfTrees: 100,
minimumExampleCountPerLeaf: 10,
learningRate: 0.2));
var model = pipeline.Fit(split.TrainSet);
// Evaluate
var predictions = model.Transform(split.TestSet);
var metrics = mlContext.Regression.Evaluate(
predictions,
labelColumnName: nameof(HouseData.Price));
Console.WriteLine($"R-Squared: {metrics.RSquared:F4}");
Console.WriteLine($"RMSE: {metrics.RootMeanSquaredError:F2}");
Console.WriteLine($"MAE: {metrics.MeanAbsoluteError:F2}");
return model;
}
public float PredictPrice(ITransformer model, HouseData house)
{
var engine = mlContext.Model.CreatePredictionEngine<HouseData, HousePricePrediction>(model);
return engine.Predict(house).PredictedPrice;
}
}Image Classification
Classifying images using transfer learning with pre-trained models.
public class ImageData(string ImagePath, string Label)
{
public string ImagePath { get; } = ImagePath;
public string Label { get; } = Label;
}
public class ImagePrediction
{
[ColumnName("Score")]
public float[] Score { get; set; } = [];
public string PredictedLabel { get; set; } = string.Empty;
}
public class ImageClassificationService(MLContext mlContext)
{
public ITransformer TrainModel(string imagesFolder)
{
var images = LoadImagesFromDirectory(imagesFolder);
var data = mlContext.Data.LoadFromEnumerable(images);
var split = mlContext.Data.TrainTestSplit(data, testFraction: 0.2);
var pipeline = mlContext.Transforms.Conversion
.MapValueToKey("LabelKey", nameof(ImageData.Label))
.Append(mlContext.Transforms.LoadRawImageBytes(
"Image",
imagesFolder,
nameof(ImageData.ImagePath)))
.Append(mlContext.MulticlassClassification.Trainers.ImageClassification(
featureColumnName: "Image",
labelColumnName: "LabelKey",
arch: ImageClassificationTrainer.Architecture.ResnetV2101,
epoch: 100,
batchSize: 10,
learningRate: 0.01f,
validationSet: split.TestSet))
.Append(mlContext.Transforms.Conversion.MapKeyToValue(
"PredictedLabel",
"PredictedLabel"));
return pipeline.Fit(split.TrainSet);
}
private static IEnumerable<ImageData> LoadImagesFromDirectory(string folder)
{
var extensions = new[] { ".jpg", ".jpeg", ".png", ".bmp" };
return Directory.GetDirectories(folder)
.SelectMany(labelFolder =>
{
var label = Path.GetFileName(labelFolder);
return Directory.GetFiles(labelFolder)
.Where(file => extensions.Contains(Path.GetExtension(file).ToLower()))
.Select(file => new ImageData(file, label));
});
}
}Anomaly Detection
Detecting anomalies in time series data.
public class TimeSeriesData(DateTime Timestamp, float Value)
{
public DateTime Timestamp { get; } = Timestamp;
public float Value { get; } = Value;
}
public class AnomalyPrediction
{
[VectorType(3)]
public double[] Prediction { get; set; } = [];
}
public class AnomalyDetectionService(MLContext mlContext)
{
public ITransformer TrainSpikeDetector(IDataView data, int pvalueHistoryLength = 30)
{
return mlContext.Transforms.DetectIidSpike(
outputColumnName: nameof(AnomalyPrediction.Prediction),
inputColumnName: nameof(TimeSeriesData.Value),
confidence: 95.0,
pvalueHistoryLength: pvalueHistoryLength)
.Fit(data);
}
public ITransformer TrainChangePointDetector(IDataView data)
{
return mlContext.Transforms.DetectIidChangePoint(
outputColumnName: nameof(AnomalyPrediction.Prediction),
inputColumnName: nameof(TimeSeriesData.Value),
confidence: 95.0,
changeHistoryLength: 15)
.Fit(data);
}
public IEnumerable<(DateTime Timestamp, bool IsAnomaly, double Score)> DetectAnomalies(
ITransformer model,
IDataView data)
{
var transformedData = model.Transform(data);
var timestamps = mlContext.Data
.CreateEnumerable<TimeSeriesData>(data, reuseRowObject: false)
.Select(d => d.Timestamp)
.ToList();
var predictions = mlContext.Data
.CreateEnumerable<AnomalyPrediction>(transformedData, reuseRowObject: false)
.ToList();
for (var i = 0; i < timestamps.Count; i++)
{
var prediction = predictions[i].Prediction;
var isAnomaly = prediction[0] == 1;
var score = prediction[1];
yield return (timestamps[i], isAnomaly, score);
}
}
}
// Usage
public class AnomalyDetectionExample
{
public void Run()
{
var mlContext = new MLContext();
var service = new AnomalyDetectionService(mlContext);
// Generate sample data with anomalies
var data = GenerateTimeSeriesData();
var dataView = mlContext.Data.LoadFromEnumerable(data);
var model = service.TrainSpikeDetector(dataView);
var anomalies = service.DetectAnomalies(model, dataView);
foreach (var (timestamp, isAnomaly, score) in anomalies.Where(a => a.IsAnomaly))
{
Console.WriteLine($"Anomaly at {timestamp:g}: Score = {score:F4}");
}
}
private static IEnumerable<TimeSeriesData> GenerateTimeSeriesData()
{
var random = new Random(42);
var baseTime = DateTime.Now.AddDays(-100);
for (var i = 0; i < 100; i++)
{
var value = 100 + (float)(Math.Sin(i * 0.1) * 10) + (float)(random.NextDouble() * 5);
// Inject anomalies
if (i == 25 || i == 75)
{
value += 50;
}
yield return new TimeSeriesData(baseTime.AddDays(i), value);
}
}
}Recommendation Engine
Product recommendations using matrix factorization.
public class ProductRating(uint UserId, uint ProductId, float Rating)
{
[LoadColumn(0)]
public uint UserId { get; } = UserId;
[LoadColumn(1)]
public uint ProductId { get; } = ProductId;
[LoadColumn(2)]
public float Rating { get; } = Rating;
}
public class RatingPrediction
{
public float Score { get; set; }
}
public class RecommendationService(MLContext mlContext)
{
public ITransformer TrainModel(IDataView data)
{
var split = mlContext.Data.TrainTestSplit(data, testFraction: 0.2);
var pipeline = mlContext.Recommendation().Trainers.MatrixFactorization(
labelColumnName: nameof(ProductRating.Rating),
matrixColumnIndexColumnName: nameof(ProductRating.UserId),
matrixRowIndexColumnName: nameof(ProductRating.ProductId),
numberOfIterations: 20,
approximationRank: 100);
var model = pipeline.Fit(split.TrainSet);
// Evaluate
var predictions = model.Transform(split.TestSet);
var metrics = mlContext.Regression.Evaluate(
predictions,
labelColumnName: nameof(ProductRating.Rating),
scoreColumnName: "Score");
Console.WriteLine($"R-Squared: {metrics.RSquared:F4}");
Console.WriteLine($"RMSE: {metrics.RootMeanSquaredError:F4}");
return model;
}
public float PredictRating(ITransformer model, uint userId, uint productId)
{
var engine = mlContext.Model.CreatePredictionEngine<ProductRating, RatingPrediction>(model);
return engine.Predict(new ProductRating(userId, productId, 0)).Score;
}
public IEnumerable<(uint ProductId, float PredictedRating)> GetTopRecommendations(
ITransformer model,
uint userId,
IEnumerable<uint> candidateProducts,
int topN = 10)
{
var engine = mlContext.Model.CreatePredictionEngine<ProductRating, RatingPrediction>(model);
return candidateProducts
.Select(productId => (
ProductId: productId,
PredictedRating: engine.Predict(new ProductRating(userId, productId, 0)).Score))
.OrderByDescending(x => x.PredictedRating)
.Take(topN);
}
}Customer Segmentation (Clustering)
Grouping customers based on behavior patterns.
public class CustomerData(
float TotalPurchases,
float AverageOrderValue,
float DaysSinceLastPurchase,
float PurchaseFrequency)
{
public float TotalPurchases { get; } = TotalPurchases;
public float AverageOrderValue { get; } = AverageOrderValue;
public float DaysSinceLastPurchase { get; } = DaysSinceLastPurchase;
public float PurchaseFrequency { get; } = PurchaseFrequency;
}
public class ClusterPrediction
{
[ColumnName("PredictedLabel")]
public uint ClusterId { get; set; }
[ColumnName("Score")]
public float[] Distances { get; set; } = [];
}
public class CustomerSegmentationService(MLContext mlContext)
{
public ITransformer TrainModel(IDataView data, int numberOfClusters = 4)
{
var pipeline = mlContext.Transforms.Concatenate(
"Features",
nameof(CustomerData.TotalPurchases),
nameof(CustomerData.AverageOrderValue),
nameof(CustomerData.DaysSinceLastPurchase),
nameof(CustomerData.PurchaseFrequency))
.Append(mlContext.Transforms.NormalizeMinMax("Features"))
.Append(mlContext.Clustering.Trainers.KMeans(
featureColumnName: "Features",
numberOfClusters: numberOfClusters));
return pipeline.Fit(data);
}
public uint PredictCluster(ITransformer model, CustomerData customer)
{
var engine = mlContext.Model.CreatePredictionEngine<CustomerData, ClusterPrediction>(model);
return engine.Predict(customer).ClusterId;
}
public Dictionary<uint, List<CustomerData>> SegmentCustomers(
ITransformer model,
IEnumerable<CustomerData> customers)
{
var engine = mlContext.Model.CreatePredictionEngine<CustomerData, ClusterPrediction>(model);
return customers
.GroupBy(c => engine.Predict(c).ClusterId)
.ToDictionary(g => g.Key, g => g.ToList());
}
}
// Usage with cluster analysis
public class CustomerSegmentationExample
{
public void Run()
{
var mlContext = new MLContext(seed: 42);
var service = new CustomerSegmentationService(mlContext);
var customers = GenerateSampleCustomers();
var data = mlContext.Data.LoadFromEnumerable(customers);
var model = service.TrainModel(data, numberOfClusters: 4);
var segments = service.SegmentCustomers(model, customers);
foreach (var (clusterId, clusterCustomers) in segments)
{
Console.WriteLine($"\nCluster {clusterId}: {clusterCustomers.Count} customers");
Console.WriteLine($" Avg Total Purchases: {clusterCustomers.Average(c => c.TotalPurchases):F2}");
Console.WriteLine($" Avg Order Value: {clusterCustomers.Average(c => c.AverageOrderValue):F2}");
}
}
private static List<CustomerData> GenerateSampleCustomers()
{
var random = new Random(42);
var customers = new List<CustomerData>();
// High-value frequent buyers
for (var i = 0; i < 50; i++)
{
customers.Add(new CustomerData(
(float)(5000 + random.NextDouble() * 5000),
(float)(200 + random.NextDouble() * 100),
(float)(1 + random.NextDouble() * 7),
(float)(20 + random.NextDouble() * 10)));
}
// Low-value occasional buyers
for (var i = 0; i < 100; i++)
{
customers.Add(new CustomerData(
(float)(100 + random.NextDouble() * 500),
(float)(20 + random.NextDouble() * 30),
(float)(30 + random.NextDouble() * 60),
(float)(1 + random.NextDouble() * 3)));
}
return customers;
}
}Fraud Detection
Real-time fraud detection with probability scoring.
public class TransactionData(
float Amount,
float HourOfDay,
float DayOfWeek,
float DistanceFromHome,
float DistanceFromLastTransaction,
float RatioToMedianPurchase,
bool IsFraud)
{
public float Amount { get; } = Amount;
public float HourOfDay { get; } = HourOfDay;
public float DayOfWeek { get; } = DayOfWeek;
public float DistanceFromHome { get; } = DistanceFromHome;
public float DistanceFromLastTransaction { get; } = DistanceFromLastTransaction;
public float RatioToMedianPurchase { get; } = RatioToMedianPurchase;
[ColumnName("Label")]
public bool IsFraud { get; } = IsFraud;
}
public class FraudPrediction
{
[ColumnName("PredictedLabel")]
public bool IsFraud { get; set; }
public float Probability { get; set; }
public float Score { get; set; }
}
public class FraudDetectionService(MLContext mlContext)
{
public ITransformer TrainModel(IDataView data)
{
var split = mlContext.Data.TrainTestSplit(data, testFraction: 0.2);
var pipeline = mlContext.Transforms.Concatenate(
"Features",
nameof(TransactionData.Amount),
nameof(TransactionData.HourOfDay),
nameof(TransactionData.DayOfWeek),
nameof(TransactionData.DistanceFromHome),
nameof(TransactionData.DistanceFromLastTransaction),
nameof(TransactionData.RatioToMedianPurchase))
.Append(mlContext.Transforms.NormalizeMinMax("Features"))
.Append(mlContext.BinaryClassification.Trainers.FastTree(
labelColumnName: "Label",
featureColumnName: "Features",
numberOfLeaves: 20,
numberOfTrees: 100,
minimumExampleCountPerLeaf: 10));
var model = pipeline.Fit(split.TrainSet);
// Evaluate with focus on precision/recall for fraud cases
var predictions = model.Transform(split.TestSet);
var metrics = mlContext.BinaryClassification.Evaluate(predictions, "Label");
Console.WriteLine($"Accuracy: {metrics.Accuracy:P2}");
Console.WriteLine($"AUC: {metrics.AreaUnderRocCurve:P2}");
Console.WriteLine($"Precision (fraud): {metrics.PositivePrecision:P2}");
Console.WriteLine($"Recall (fraud): {metrics.PositiveRecall:P2}");
Console.WriteLine($"F1 Score: {metrics.F1Score:P2}");
return model;
}
public FraudPrediction EvaluateTransaction(ITransformer model, TransactionData transaction)
{
var engine = mlContext.Model.CreatePredictionEngine<TransactionData, FraudPrediction>(model);
return engine.Predict(transaction);
}
public (bool ShouldBlock, string Reason) MakeDecision(FraudPrediction prediction, float threshold = 0.7f)
{
if (prediction.Probability >= threshold)
{
return (true, $"High fraud probability: {prediction.Probability:P2}");
}
if (prediction.Probability >= 0.5f)
{
return (false, $"Medium risk - requires manual review: {prediction.Probability:P2}");
}
return (false, "Transaction approved");
}
}Text Classification (Multi-Class)
Categorizing support tickets into departments.
public class SupportTicket(string Description, string Department)
{
public string Description { get; } = Description;
[ColumnName("Label")]
public string Department { get; } = Department;
}
public class TicketPrediction
{
[ColumnName("PredictedLabel")]
public string Department { get; set; } = string.Empty;
public float[] Score { get; set; } = [];
}
public class TicketClassificationService(MLContext mlContext)
{
public ITransformer TrainModel(IDataView data)
{
var split = mlContext.Data.TrainTestSplit(data, testFraction: 0.2);
var pipeline = mlContext.Transforms.Conversion
.MapValueToKey("Label")
.Append(mlContext.Transforms.Text.FeaturizeText(
"Features",
nameof(SupportTicket.Description)))
.Append(mlContext.MulticlassClassification.Trainers.SdcaMaximumEntropy(
labelColumnName: "Label",
featureColumnName: "Features"))
.Append(mlContext.Transforms.Conversion.MapKeyToValue("PredictedLabel"));
var model = pipeline.Fit(split.TrainSet);
// Evaluate
var predictions = model.Transform(split.TestSet);
var metrics = mlContext.MulticlassClassification.Evaluate(predictions, "Label");
Console.WriteLine($"Macro Accuracy: {metrics.MacroAccuracy:P2}");
Console.WriteLine($"Micro Accuracy: {metrics.MicroAccuracy:P2}");
Console.WriteLine($"Log Loss: {metrics.LogLoss:F4}");
// Print per-class metrics
Console.WriteLine("\nPer-class metrics:");
foreach (var (className, classMetrics) in metrics.PerClassLogLoss.Select((v, i) => (i, v)))
{
Console.WriteLine($" Class {className}: Log Loss = {classMetrics:F4}");
}
return model;
}
public TicketPrediction ClassifyTicket(ITransformer model, string description)
{
var engine = mlContext.Model.CreatePredictionEngine<SupportTicket, TicketPrediction>(model);
return engine.Predict(new SupportTicket(description, string.Empty));
}
public IEnumerable<(string Department, float Confidence)> GetTopPredictions(
ITransformer model,
string description,
string[] departments,
int topN = 3)
{
var prediction = ClassifyTicket(model, description);
return departments
.Zip(prediction.Score, (dept, score) => (Department: dept, Confidence: score))
.OrderByDescending(x => x.Confidence)
.Take(topN);
}
}Object Detection Integration
Using ONNX models for object detection.
public class ObjectDetectionInput(byte[] Image)
{
[ColumnName("image")]
[ImageType(416, 416)]
public byte[] Image { get; } = Image;
}
public class ObjectDetectionOutput
{
[ColumnName("detected_boxes")]
public float[] DetectedBoxes { get; set; } = [];
[ColumnName("detected_classes")]
public long[] DetectedClasses { get; set; } = [];
[ColumnName("detected_scores")]
public float[] DetectedScores { get; set; } = [];
}
public record DetectedObject(string ClassName, float Confidence, float X, float Y, float Width, float Height);
public class ObjectDetectionService(MLContext mlContext, string onnxModelPath, string[] classNames)
{
public ITransformer LoadModel()
{
var pipeline = mlContext.Transforms.ApplyOnnxModel(
modelFile: onnxModelPath,
outputColumnNames: ["detected_boxes", "detected_classes", "detected_scores"],
inputColumnNames: ["image"]);
var emptyData = mlContext.Data.LoadFromEnumerable(Array.Empty<ObjectDetectionInput>());
return pipeline.Fit(emptyData);
}
public IEnumerable<DetectedObject> DetectObjects(
ITransformer model,
byte[] imageBytes,
float confidenceThreshold = 0.5f)
{
var engine = mlContext.Model.CreatePredictionEngine<ObjectDetectionInput, ObjectDetectionOutput>(model);
var output = engine.Predict(new ObjectDetectionInput(imageBytes));
var detections = new List<DetectedObject>();
for (var i = 0; i < output.DetectedScores.Length; i++)
{
if (output.DetectedScores[i] < confidenceThreshold)
{
continue;
}
var classIndex = (int)output.DetectedClasses[i];
var className = classIndex < classNames.Length ? classNames[classIndex] : $"class_{classIndex}";
var boxIndex = i * 4;
detections.Add(new DetectedObject(
className,
output.DetectedScores[i],
output.DetectedBoxes[boxIndex],
output.DetectedBoxes[boxIndex + 1],
output.DetectedBoxes[boxIndex + 2],
output.DetectedBoxes[boxIndex + 3]));
}
return detections;
}
}AutoML for Model Selection
Using AutoML to automatically select the best model.
public class AutoMLService(MLContext mlContext, uint maxExperimentTimeInSeconds = 600)
{
public ExperimentResult<RegressionMetrics> RunRegressionExperiment(
IDataView data,
string labelColumnName)
{
var settings = new RegressionExperimentSettings
{
MaxExperimentTimeInSeconds = maxExperimentTimeInSeconds,
OptimizingMetric = RegressionMetric.RSquared
};
var experiment = mlContext.Auto().CreateRegressionExperiment(settings);
return experiment.Execute(data, labelColumnName);
}
public ExperimentResult<BinaryClassificationMetrics> RunBinaryClassificationExperiment(
IDataView data,
string labelColumnName)
{
var settings = new BinaryExperimentSettings
{
MaxExperimentTimeInSeconds = maxExperimentTimeInSeconds,
OptimizingMetric = BinaryClassificationMetric.Accuracy
};
var experiment = mlContext.Auto().CreateBinaryClassificationExperiment(settings);
return experiment.Execute(data, labelColumnName);
}
public ExperimentResult<MulticlassClassificationMetrics> RunMulticlassExperiment(
IDataView data,
string labelColumnName)
{
var settings = new MulticlassExperimentSettings
{
MaxExperimentTimeInSeconds = maxExperimentTimeInSeconds,
OptimizingMetric = MulticlassClassificationMetric.MicroAccuracy
};
var experiment = mlContext.Auto().CreateMulticlassClassificationExperiment(settings);
return experiment.Execute(data, labelColumnName);
}
public void PrintExperimentResults<TMetrics>(ExperimentResult<TMetrics> result)
{
Console.WriteLine($"Best run: {result.BestRun.TrainerName}");
Console.WriteLine($"Runtime: {result.BestRun.RuntimeInSeconds:F2}s");
Console.WriteLine($"Metrics: {result.BestRun.ValidationMetrics}");
Console.WriteLine("\nAll runs:");
foreach (var run in result.RunDetails.OrderByDescending(r => r.RuntimeInSeconds))
{
Console.WriteLine($" {run.TrainerName}: {run.RuntimeInSeconds:F2}s");
}
}
}
// Usage
public class AutoMLExample
{
public void Run()
{
var mlContext = new MLContext(seed: 42);
var service = new AutoMLService(mlContext, maxExperimentTimeInSeconds: 300);
// Load data
var data = mlContext.Data.LoadFromTextFile<HouseData>("housing.csv", hasHeader: true);
// Run AutoML experiment
var result = service.RunRegressionExperiment(data, nameof(HouseData.Price));
service.PrintExperimentResults(result);
// Use the best model
var bestModel = result.BestRun.Model;
mlContext.Model.Save(bestModel, data.Schema, "best_model.zip");
}
}Batch Prediction Pipeline
Processing large datasets efficiently with batch predictions.
public class BatchPredictionService(MLContext mlContext, string modelPath)
{
public async IAsyncEnumerable<TOutput> PredictBatchAsync<TInput, TOutput>(
IEnumerable<TInput> inputs,
int batchSize = 1000,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
where TInput : class
where TOutput : class, new()
{
using var stream = File.OpenRead(modelPath);
var model = mlContext.Model.Load(stream, out _);
var engine = mlContext.Model.CreatePredictionEngine<TInput, TOutput>(model);
var batch = new List<TInput>(batchSize);
foreach (var input in inputs)
{
cancellationToken.ThrowIfCancellationRequested();
batch.Add(input);
if (batch.Count >= batchSize)
{
foreach (var item in batch)
{
yield return engine.Predict(item);
}
batch.Clear();
// Allow other operations
await Task.Yield();
}
}
// Process remaining items
foreach (var item in batch)
{
yield return engine.Predict(item);
}
}
public void PredictAndSave<TInput, TOutput>(
IDataView inputData,
string outputPath)
where TInput : class
where TOutput : class, new()
{
using var stream = File.OpenRead(modelPath);
var model = mlContext.Model.Load(stream, out _);
var predictions = model.Transform(inputData);
using var outputStream = File.Create(outputPath);
mlContext.Data.SaveAsText(predictions, outputStream, separatorChar: ',', headerRow: true);
}
}ML.NET Patterns
This reference covers essential ML.NET patterns for data loading, training, evaluation, and deployment.
Data Loading Patterns
Loading from CSV with Schema Definition
public class HousingData(
float Size,
float Price,
float Rooms,
string Neighborhood)
{
[LoadColumn(0)]
public float Size { get; } = Size;
[LoadColumn(1)]
public float Price { get; } = Price;
[LoadColumn(2)]
public float Rooms { get; } = Rooms;
[LoadColumn(3)]
public string Neighborhood { get; } = Neighborhood;
}
public class DataLoaderService(MLContext mlContext)
{
public IDataView LoadFromCsv(string path)
{
return mlContext.Data.LoadFromTextFile<HousingData>(
path,
hasHeader: true,
separatorChar: ',');
}
public IDataView LoadFromCsvWithOptions(string path)
{
var options = new TextLoader.Options
{
HasHeader = true,
Separators = [','],
AllowQuoting = true,
TrimWhitespace = true,
MissingRealsAsNaNs = true
};
return mlContext.Data.LoadFromTextFile<HousingData>(path, options);
}
}Loading from Database
public class DatabaseLoaderService(MLContext mlContext)
{
public IDataView LoadFromDatabase(string connectionString)
{
var loader = mlContext.Data.CreateDatabaseLoader<HousingData>();
var source = new DatabaseSource(
SqlClientFactory.Instance,
connectionString,
"SELECT Size, Price, Rooms, Neighborhood FROM HousingData");
return loader.Load(source);
}
}Loading from In-Memory Collections
public class InMemoryLoaderService(MLContext mlContext)
{
public IDataView LoadFromEnumerable(IEnumerable<HousingData> data)
{
return mlContext.Data.LoadFromEnumerable(data);
}
public IDataView LoadWithDefinedSchema(IEnumerable<HousingData> data)
{
var schema = SchemaDefinition.Create(typeof(HousingData));
return mlContext.Data.LoadFromEnumerable(data, schema);
}
}Streaming Large Datasets
public class StreamingLoaderService(MLContext mlContext, int batchSize = 1000)
{
public IEnumerable<IDataView> LoadInBatches(string path)
{
var allData = mlContext.Data.LoadFromTextFile<HousingData>(path, hasHeader: true);
var batches = mlContext.Data.CreateEnumerable<HousingData>(allData, reuseRowObject: false);
foreach (var batch in batches.Chunk(batchSize))
{
yield return mlContext.Data.LoadFromEnumerable(batch);
}
}
}Training Patterns
Basic Training Pipeline
public class RegressionTrainer(MLContext mlContext)
{
public ITransformer Train(IDataView trainingData)
{
var pipeline = mlContext.Transforms.Categorical
.OneHotEncoding("NeighborhoodEncoded", "Neighborhood")
.Append(mlContext.Transforms.Concatenate(
"Features",
"Size",
"Rooms",
"NeighborhoodEncoded"))
.Append(mlContext.Transforms.NormalizeMinMax("Features"))
.Append(mlContext.Regression.Trainers.Sdca(
labelColumnName: "Price",
featureColumnName: "Features"));
return pipeline.Fit(trainingData);
}
}Cross-Validation Training
public class CrossValidationTrainer(MLContext mlContext)
{
public (ITransformer Model, double AverageRSquared) TrainWithCrossValidation(
IDataView data,
int numberOfFolds = 5)
{
var pipeline = mlContext.Transforms.Concatenate("Features", "Size", "Rooms")
.Append(mlContext.Regression.Trainers.Sdca(
labelColumnName: "Price",
featureColumnName: "Features"));
var cvResults = mlContext.Regression.CrossValidate(
data,
pipeline,
numberOfFolds: numberOfFolds,
labelColumnName: "Price");
var averageRSquared = cvResults.Average(r => r.Metrics.RSquared);
var bestModel = cvResults.OrderByDescending(r => r.Metrics.RSquared).First().Model;
return (bestModel, averageRSquared);
}
}Incremental Training
public class IncrementalTrainer(MLContext mlContext, string modelPath)
{
public ITransformer RetrainModel(IDataView newData)
{
ITransformer existingModel;
DataViewSchema modelSchema;
using (var stream = File.OpenRead(modelPath))
{
existingModel = mlContext.Model.Load(stream, out modelSchema);
}
var predictions = existingModel.Transform(newData);
var retrainedModel = RefitModel(predictions);
return retrainedModel;
}
private ITransformer RefitModel(IDataView data)
{
var pipeline = mlContext.Regression.Trainers.OnlineGradientDescent(
labelColumnName: "Price",
featureColumnName: "Features");
return pipeline.Fit(data);
}
}Multi-Class Classification Training
public class ClassificationData(string Text, uint Label)
{
public string Text { get; } = Text;
public uint Label { get; } = Label;
}
public class MultiClassTrainer(MLContext mlContext)
{
public ITransformer Train(IDataView data)
{
var pipeline = mlContext.Transforms.Text
.FeaturizeText("Features", "Text")
.Append(mlContext.Transforms.Conversion.MapValueToKey("Label"))
.Append(mlContext.MulticlassClassification.Trainers
.SdcaMaximumEntropy("Label", "Features"))
.Append(mlContext.Transforms.Conversion.MapKeyToValue("PredictedLabel"));
return pipeline.Fit(data);
}
}Evaluation Patterns
Regression Evaluation
public record RegressionEvaluationResult(
double RSquared,
double MeanAbsoluteError,
double MeanSquaredError,
double RootMeanSquaredError);
public class RegressionEvaluator(MLContext mlContext)
{
public RegressionEvaluationResult Evaluate(ITransformer model, IDataView testData)
{
var predictions = model.Transform(testData);
var metrics = mlContext.Regression.Evaluate(
predictions,
labelColumnName: "Price",
scoreColumnName: "Score");
return new RegressionEvaluationResult(
metrics.RSquared,
metrics.MeanAbsoluteError,
metrics.MeanSquaredError,
metrics.RootMeanSquaredError);
}
public void PrintMetrics(RegressionEvaluationResult result)
{
Console.WriteLine($"R-Squared: {result.RSquared:F4}");
Console.WriteLine($"MAE: {result.MeanAbsoluteError:F4}");
Console.WriteLine($"MSE: {result.MeanSquaredError:F4}");
Console.WriteLine($"RMSE: {result.RootMeanSquaredError:F4}");
}
}Binary Classification Evaluation
public record BinaryClassificationResult(
double Accuracy,
double AreaUnderRocCurve,
double F1Score,
double PositivePrecision,
double PositiveRecall);
public class BinaryClassificationEvaluator(MLContext mlContext)
{
public BinaryClassificationResult Evaluate(ITransformer model, IDataView testData)
{
var predictions = model.Transform(testData);
var metrics = mlContext.BinaryClassification.Evaluate(
predictions,
labelColumnName: "Label",
scoreColumnName: "Score");
return new BinaryClassificationResult(
metrics.Accuracy,
metrics.AreaUnderRocCurve,
metrics.F1Score,
metrics.PositivePrecision,
metrics.PositiveRecall);
}
public CalibratedBinaryClassificationMetrics EvaluateWithConfusionMatrix(
ITransformer model,
IDataView testData)
{
var predictions = model.Transform(testData);
return mlContext.BinaryClassification.Evaluate(predictions);
}
}Multi-Class Evaluation
public record MultiClassResult(
double MacroAccuracy,
double MicroAccuracy,
double LogLoss,
double LogLossReduction);
public class MultiClassEvaluator(MLContext mlContext)
{
public MultiClassResult Evaluate(ITransformer model, IDataView testData)
{
var predictions = model.Transform(testData);
var metrics = mlContext.MulticlassClassification.Evaluate(
predictions,
labelColumnName: "Label");
return new MultiClassResult(
metrics.MacroAccuracy,
metrics.MicroAccuracy,
metrics.LogLoss,
metrics.LogLossReduction);
}
}Feature Importance Analysis
public class FeatureImportanceAnalyzer(MLContext mlContext)
{
public ImmutableArray<RegressionMetricsStatistics> AnalyzePermutationFeatureImportance(
ITransformer model,
IDataView data)
{
var transformedData = model.Transform(data);
var linearModel = model as ISingleFeaturePredictionTransformer<object>;
if (linearModel is null)
{
throw new InvalidOperationException("Model does not support feature importance analysis");
}
var featureImportance = mlContext.Regression.PermutationFeatureImportance(
linearModel,
transformedData,
labelColumnName: "Price",
permutationCount: 50);
return featureImportance;
}
}Deployment Patterns
Model Persistence
public class ModelPersistenceService(MLContext mlContext)
{
public void SaveModel(ITransformer model, DataViewSchema schema, string path)
{
using var stream = File.Create(path);
mlContext.Model.Save(model, schema, stream);
}
public (ITransformer Model, DataViewSchema Schema) LoadModel(string path)
{
using var stream = File.OpenRead(path);
var model = mlContext.Model.Load(stream, out var schema);
return (model, schema);
}
public void SaveModelAsOnnx(
ITransformer model,
IDataView sampleData,
string path)
{
using var stream = File.Create(path);
mlContext.Model.ConvertToOnnx(model, sampleData, stream);
}
}Prediction Engine Factory
public class HousingPrediction
{
[ColumnName("Score")]
public float PredictedPrice { get; set; }
}
public class PredictionEngineFactory(MLContext mlContext, string modelPath)
{
private readonly Lazy<PredictionEngine<HousingData, HousingPrediction>> _engine = new(() =>
{
using var stream = File.OpenRead(modelPath);
var model = mlContext.Model.Load(stream, out _);
return mlContext.Model.CreatePredictionEngine<HousingData, HousingPrediction>(model);
});
public HousingPrediction Predict(HousingData input)
{
return _engine.Value.Predict(input);
}
}Thread-Safe Prediction Service
public class ThreadSafePredictionService(MLContext mlContext, string modelPath) : IDisposable
{
private readonly ObjectPool<PredictionEngine<HousingData, HousingPrediction>> _enginePool =
CreatePool(mlContext, modelPath);
private static ObjectPool<PredictionEngine<HousingData, HousingPrediction>> CreatePool(
MLContext mlContext,
string modelPath)
{
using var stream = File.OpenRead(modelPath);
var model = mlContext.Model.Load(stream, out _);
return mlContext.Model.CreatePredictionEnginePool<HousingData, HousingPrediction>(model);
}
public HousingPrediction Predict(HousingData input)
{
var engine = _enginePool.Get();
try
{
return engine.Predict(input);
}
finally
{
_enginePool.Return(engine);
}
}
public void Dispose()
{
// Pool handles disposal
}
}ASP.NET Core Integration
public static class MlNetServiceExtensions
{
public static IServiceCollection AddMlNetPrediction<TInput, TOutput>(
this IServiceCollection services,
string modelPath)
where TInput : class
where TOutput : class, new()
{
services.AddSingleton<MLContext>();
services.AddSingleton(sp =>
{
var mlContext = sp.GetRequiredService<MLContext>();
using var stream = File.OpenRead(modelPath);
return mlContext.Model.Load(stream, out _);
});
services.AddPredictionEnginePool<TInput, TOutput>();
return services;
}
}
// Usage in Program.cs
// builder.Services.AddMlNetPrediction<HousingData, HousingPrediction>("model.zip");
public class PredictionController(PredictionEnginePool<HousingData, HousingPrediction> predictionPool)
: ControllerBase
{
[HttpPost("predict")]
public ActionResult<HousingPrediction> Predict([FromBody] HousingData input)
{
var prediction = predictionPool.Predict(input);
return Ok(prediction);
}
}Model Versioning and Hot-Reload
public class VersionedModelService(
MLContext mlContext,
IOptionsMonitor<ModelOptions> options,
ILogger<VersionedModelService> logger) : IDisposable
{
private ITransformer? _currentModel;
private readonly SemaphoreSlim _modelLock = new(1, 1);
private FileSystemWatcher? _watcher;
public async Task InitializeAsync()
{
await LoadModelAsync(options.CurrentValue.ModelPath);
SetupFileWatcher(options.CurrentValue.ModelPath);
}
private async Task LoadModelAsync(string path)
{
await _modelLock.WaitAsync();
try
{
using var stream = File.OpenRead(path);
_currentModel = mlContext.Model.Load(stream, out _);
logger.LogInformation("Model loaded from {Path}", path);
}
finally
{
_modelLock.Release();
}
}
private void SetupFileWatcher(string path)
{
var directory = Path.GetDirectoryName(path) ?? ".";
var fileName = Path.GetFileName(path);
_watcher = new FileSystemWatcher(directory, fileName)
{
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime
};
_watcher.Changed += async (_, _) =>
{
await Task.Delay(500); // Debounce
await LoadModelAsync(path);
};
_watcher.EnableRaisingEvents = true;
}
public async Task<TOutput> PredictAsync<TInput, TOutput>(TInput input)
where TInput : class
where TOutput : class, new()
{
await _modelLock.WaitAsync();
try
{
var engine = mlContext.Model.CreatePredictionEngine<TInput, TOutput>(_currentModel!);
return engine.Predict(input);
}
finally
{
_modelLock.Release();
}
}
public void Dispose()
{
_watcher?.Dispose();
_modelLock.Dispose();
}
}
public class ModelOptions
{
public string ModelPath { get; set; } = "model.zip";
public string Version { get; set; } = "1.0.0";
}Feature Engineering Patterns
Text Featurization
public class TextFeatureEngineer(MLContext mlContext)
{
public IEstimator<ITransformer> CreateTextPipeline(string inputColumn, string outputColumn)
{
return mlContext.Transforms.Text.FeaturizeText(
outputColumn,
new TextFeaturizingEstimator.Options
{
WordFeatureExtractor = new WordBagEstimator.Options
{
NgramLength = 2,
UseAllLengths = true
},
CharFeatureExtractor = new WordBagEstimator.Options
{
NgramLength = 3,
UseAllLengths = false
},
Norm = TextFeaturizingEstimator.NormFunction.L2
},
inputColumn);
}
}Categorical Encoding
public class CategoricalEngineer(MLContext mlContext)
{
public IEstimator<ITransformer> CreateOneHotPipeline(params string[] columns)
{
IEstimator<ITransformer>? pipeline = null;
foreach (var column in columns)
{
var transform = mlContext.Transforms.Categorical.OneHotEncoding(
$"{column}Encoded",
column);
pipeline = pipeline is null ? transform : pipeline.Append(transform);
}
return pipeline ?? mlContext.Transforms.PassThrough();
}
public IEstimator<ITransformer> CreateHashEncodingPipeline(string column, int numberOfBits = 16)
{
return mlContext.Transforms.Categorical.OneHotHashEncoding(
$"{column}Hashed",
column,
numberOfBits: numberOfBits);
}
}Missing Value Handling
public class MissingValueHandler(MLContext mlContext)
{
public IEstimator<ITransformer> CreateReplacementPipeline(
string[] columns,
MissingValueReplacingEstimator.ReplacementMode mode = MissingValueReplacingEstimator.ReplacementMode.Mean)
{
var inputOutputPairs = columns
.Select(c => new InputOutputColumnPair(c, c))
.ToArray();
return mlContext.Transforms.ReplaceMissingValues(inputOutputPairs, mode);
}
public IEstimator<ITransformer> CreateIndicatorPipeline(string[] columns)
{
IEstimator<ITransformer>? pipeline = null;
foreach (var column in columns)
{
var transform = mlContext.Transforms.IndicateMissingValues(
$"{column}Missing",
column);
pipeline = pipeline is null ? transform : pipeline.Append(transform);
}
return pipeline ?? mlContext.Transforms.PassThrough();
}
}Pipeline Composition Patterns
Modular Pipeline Builder
public class PipelineBuilder(MLContext mlContext)
{
private readonly List<IEstimator<ITransformer>> _steps = [];
public PipelineBuilder AddStep(IEstimator<ITransformer> step)
{
_steps.Add(step);
return this;
}
public PipelineBuilder AddNormalization(params string[] columns)
{
foreach (var column in columns)
{
_steps.Add(mlContext.Transforms.NormalizeMinMax(column));
}
return this;
}
public PipelineBuilder AddFeatureConcatenation(string outputColumn, params string[] inputColumns)
{
_steps.Add(mlContext.Transforms.Concatenate(outputColumn, inputColumns));
return this;
}
public IEstimator<ITransformer> Build()
{
if (_steps.Count == 0)
{
return mlContext.Transforms.PassThrough();
}
var pipeline = _steps[0];
for (var i = 1; i < _steps.Count; i++)
{
pipeline = pipeline.Append(_steps[i]);
}
return pipeline;
}
}