Skip to main content

NLP with C# and ML.NET: Complete Guide with Examples

Learn natural language processing in C# with ML.NET: text classification, sentiment analysis, tokenization and TF-IDF with runnable code. Start building today.

Natural language processing (NLP) in C# used to mean calling a Python service or a cloud API. That is no longer the case. With ML.NET, Microsoft's open-source machine learning framework for .NET, you can train and run natural language processing in C# entirely inside your own application—no Python, no external runtime, and no per-request fees. This complete guide walks through the core NLP concepts, builds a working sentiment analysis model, shows multi-class text classification, and covers the best practices and pitfalls you will hit in production.

What Is Natural Language Processing in C# and Why ML.NET?

NLP is the branch of machine learning that teaches software to understand human text: classifying reviews as positive or negative, routing support tickets, detecting spam, extracting topics, and more. Under the hood, every NLP model does the same two things:

  • Featurization – convert raw text into numbers (vectors) a model can learn from.
  • Learning – fit an algorithm (logistic regression, gradient boosting, a neural network) to those vectors.

ML.NET is compelling for .NET developers for three reasons. First, it is native: models are trained and consumed with ordinary C# classes and run on the same CLR as your ASP.NET Core or desktop app. Second, it ships production-grade text transforms (FeaturizeText, n-grams, TF-IDF, stop-word removal) as composable pipeline steps. Third, since ML.NET 2.0 it includes a TextClassification trainer that fine-tunes a pretrained NAS-BERT transformer, giving you modern deep-learning accuracy with a few lines of code.

Setting Up ML.NET for NLP

Create a console project and add the packages:

dotnet new console -n NlpDemo
cd NlpDemo
dotnet add package Microsoft.ML

Every ML.NET program starts with an MLContext, the entry point for loading data, building pipelines, and evaluating models. Seed it for reproducible results while you experiment:

using Microsoft.ML;
using Microsoft.ML.Data;

var mlContext = new MLContext(seed: 42);

Defining Input and Output Schemas

ML.NET is strongly typed. You describe your training rows and prediction output as plain C# classes. LoadColumn maps a CSV/TSV column to a property; ColumnName lets the output match the names the trainer produces.

public class ReviewData
{
    [LoadColumn(0)] public string Text { get; set; } = string.Empty;
    [LoadColumn(1)] public bool Label { get; set; }   // true = positive
}

public class ReviewPrediction
{
    [ColumnName("PredictedLabel")] public bool IsPositive { get; set; }
    public float Probability { get; set; }
    public float Score { get; set; }
}

C# Sentiment Analysis: Your First NLP Model

Sentiment analysis is binary classification, which makes it the ideal first project. The example below trains on an in-memory list so it runs anywhere; in real projects you would call mlContext.Data.LoadFromTextFile<ReviewData>(path, separatorChar: '\t', hasHeader: true) against a dataset with thousands of rows.

using Microsoft.ML;
using Microsoft.ML.Data;

var mlContext = new MLContext(seed: 42);

var samples = new List<ReviewData>
{
    new() { Text = "Absolutely loved this product, works perfectly", Label = true },
    new() { Text = "Fantastic quality and fast shipping", Label = true },
    new() { Text = "Great value, would buy again", Label = true },
    new() { Text = "Exceeded my expectations in every way", Label = true },
    new() { Text = "Terrible, broke after two days", Label = false },
    new() { Text = "Waste of money, very disappointed", Label = false },
    new() { Text = "Customer service was rude and unhelpful", Label = false },
    new() { Text = "Cheap plastic, stopped working immediately", Label = false },
};

IDataView data = mlContext.Data.LoadFromEnumerable(samples);
var split = mlContext.Data.TrainTestSplit(data, testFraction: 0.25);

// 1. Featurize text -> numeric vector. 2. Train a binary classifier.
var pipeline = mlContext.Transforms.Text
    .FeaturizeText("Features", nameof(ReviewData.Text))
    .Append(mlContext.BinaryClassification.Trainers
        .SdcaLogisticRegression(labelColumnName: "Label", featureColumnName: "Features"));

ITransformer model = pipeline.Fit(split.TrainSet);

// Evaluate
var predictions = model.Transform(split.TestSet);
var metrics = mlContext.BinaryClassification.Evaluate(predictions, labelColumnName: "Label");
Console.WriteLine($"Accuracy: {metrics.Accuracy:P2}  AUC: {metrics.AreaUnderRocCurve:P2}  F1: {metrics.F1Score:P2}");

// Predict a single sentence
var engine = mlContext.Model.CreatePredictionEngine<ReviewData, ReviewPrediction>(model);
var result = engine.Predict(new ReviewData { Text = "This is the best purchase I have made all year" });
Console.WriteLine($"Positive: {result.IsPositive}  Probability: {result.Probability:P1}");

// Persist for later use in a web app or service
mlContext.Model.Save(model, data.Schema, "sentiment.zip");

Why this works: FeaturizeText is a bundle of transforms—text normalization (lower-casing, punctuation removal), tokenization into words, word and character n-gram counting, and TF-IDF-style weighting—that produces a sparse float vector. Logistic regression via SDCA (Stochastic Dual Coordinate Ascent) then learns which n-grams push a review toward positive or negative. It trains in milliseconds and is remarkably strong on short text.

Understanding Text Featurization in ML.NET

The one-line FeaturizeText is convenient, but knowing the individual steps lets you tune accuracy and diagnose problems. Here is the same featurization built explicitly:

using Microsoft.ML.Transforms.Text;

var explicitPipeline = mlContext.Transforms.Text
    .NormalizeText("NormText", nameof(ReviewData.Text),
        caseMode: TextNormalizingEstimator.CaseMode.Lower,
        keepDiacritics: false, keepPunctuations: false, keepNumbers: true)
    .Append(mlContext.Transforms.Text.TokenizeIntoWords("Tokens", "NormText"))
    .Append(mlContext.Transforms.Text.RemoveDefaultStopWords("CleanTokens", "Tokens"))
    .Append(mlContext.Transforms.Conversion.MapValueToKey("KeyTokens", "CleanTokens"))
    .Append(mlContext.Transforms.Text.ProduceNgrams("Features", "KeyTokens",
        ngramLength: 2, useAllLengths: true,
        weighting: NgramExtractingEstimator.WeightingCriteria.TfIdf))
    .Append(mlContext.BinaryClassification.Trainers.SdcaLogisticRegression());
  • NormalizeText reduces vocabulary size so "Great" and "great!" become one feature.
  • TokenizeIntoWords splits on whitespace and punctuation.
  • RemoveDefaultStopWords drops "the", "and", "is"—words that carry almost no signal. Be careful: for sentiment, "not" is a stop word in some lists yet flips meaning. Use RemoveStopWords with a custom list if that bites you.
  • ProduceNgrams with ngramLength: 2 captures phrases like "not good", which unigrams alone cannot distinguish from "good".
  • TF-IDF weighting boosts rare, informative words and downweights ones appearing in every document.

You can also plug in pretrained word embeddings (GloVe, fastText) with ApplyWordEmbedding, which maps each token to a dense semantic vector so the model generalizes to words it never saw in training.

Multi-Class Text Classification with ML.NET

Most business NLP is not binary. Support-ticket routing, intent detection, and news categorization all need multi-class classification. ML.NET handles it by mapping string labels to keys, training a one-versus-all or multinomial trainer, and mapping keys back to strings for output.

public class TicketData
{
    [LoadColumn(0)] public string Text { get; set; } = string.Empty;
    [LoadColumn(1)] public string Category { get; set; } = string.Empty;
}

public class TicketPrediction
{
    [ColumnName("PredictedLabel")] public string Category { get; set; } = string.Empty;
    public float[] Score { get; set; } = Array.Empty<float>();
}

var tickets = new List<TicketData>
{
    new() { Text = "I was charged twice on my invoice", Category = "Billing" },
    new() { Text = "Refund has not appeared on my card", Category = "Billing" },
    new() { Text = "The app crashes when I open settings", Category = "Bug" },
    new() { Text = "Login button does nothing on Android", Category = "Bug" },
    new() { Text = "Please add dark mode", Category = "Feature" },
    new() { Text = "It would be great to export to CSV", Category = "Feature" },
};

var ticketData = mlContext.Data.LoadFromEnumerable(tickets);

var multiPipeline = mlContext.Transforms.Conversion
    .MapValueToKey("Label", nameof(TicketData.Category))
    .Append(mlContext.Transforms.Text.FeaturizeText("Features", nameof(TicketData.Text)))
    .Append(mlContext.MulticlassClassification.Trainers
        .SdcaMaximumEntropy(labelColumnName: "Label", featureColumnName: "Features"))
    .Append(mlContext.Transforms.Conversion.MapKeyToValue("PredictedLabel"));

var ticketModel = multiPipeline.Fit(ticketData);
var ticketEngine = mlContext.Model
    .CreatePredictionEngine<TicketData, TicketPrediction>(ticketModel);

var p = ticketEngine.Predict(new TicketData { Text = "My subscription was billed at the wrong price" });
Console.WriteLine($"Routed to: {p.Category}");

Use mlContext.MulticlassClassification.Evaluate(...) and inspect MacroAccuracy and LogLoss. Macro accuracy averages per-class accuracy, so it exposes a model that only ever predicts the majority class—a common failure with imbalanced ticket data.

Advanced NLP in C#: Transformer-Based Text Classification

N-gram models plateau on nuanced text: sarcasm, long documents, domain jargon. ML.NET 2.0+ adds a TextClassification trainer that fine-tunes a pretrained NAS-BERT model via TorchSharp. Accuracy jumps significantly, at the cost of larger dependencies and slower training.

// dotnet add package Microsoft.ML.TorchSharp
// dotnet add package TorchSharp-cpu   (or TorchSharp-cuda-windows for GPU)
using Microsoft.ML.TorchSharp;

var bertPipeline = mlContext.Transforms.Conversion
    .MapValueToKey("Label", nameof(TicketData.Category))
    .Append(mlContext.MulticlassClassification.Trainers.TextClassification(
        labelColumnName: "Label",
        sentence1ColumnName: nameof(TicketData.Text),
        maxEpochs: 10,
        batchSize: 16))
    .Append(mlContext.Transforms.Conversion.MapKeyToValue("PredictedLabel"));

var bertModel = bertPipeline.Fit(ticketData);

Reach for this when you have a few thousand labelled examples and accuracy matters more than a 300 MB model footprint. Also note the CPU/GPU package: install TorchSharp-cpu for portability, or a CUDA package when a GPU is available.

Best Practices for Production NLP with ML.NET

  • Never use PredictionEngine as a singleton in ASP.NET Core. It is not thread-safe. Add Microsoft.Extensions.ML and register services.AddPredictionEnginePool<ReviewData, ReviewPrediction>().FromFile("sentiment.zip"), then inject PredictionEnginePool into controllers. The pool also supports hot-reloading a retrained model file.
  • Use real datasets and a held-out test set. The toy lists above demonstrate the API; accuracy numbers only mean something with hundreds or thousands of rows. Use CrossValidate for small datasets to get a stable estimate.
  • Automate model selection with AutoML. Microsoft.ML.AutoML tries featurizers and trainers for you: mlContext.Auto().CreateBinaryClassificationExperiment(60).Execute(train, "Label").
  • Version your models. Save the model zip alongside the training data hash and metrics so you can reproduce and roll back.
  • Clean input the same way at inference time. Because transforms live inside the saved pipeline, this happens automatically—one of ML.NET's biggest advantages over hand-rolled preprocessing.
  • Watch class imbalance. If 95% of tickets are "Bug", accuracy is meaningless; track F1, macro accuracy, and the confusion matrix instead.

Common Pitfalls and How to Avoid Them

  • Schema mismatches: the label column must be bool for binary trainers and a key type (via MapValueToKey) for multi-class trainers. A string label passed straight to a trainer throws a confusing schema exception.
  • Forgetting MapKeyToValue: without it, your multi-class predictions come back as key integers instead of category names.
  • Overfitting on tiny data: 100% training accuracy with eight samples is not a good model. Check test metrics.
  • Stop-word removal killing negation: verify "not", "no", and "never" survive preprocessing for sentiment tasks.
  • Slow first prediction: model loading and JIT warm-up take time; load the model at startup, not on the first request.
  • Locale issues: numeric CSV parsing is culture-sensitive in some environments; set CultureInfo.InvariantCulture when loading data yourself.

Conclusion: Key Takeaways for Natural Language Processing in C#

Natural language processing in C# is fully practical today thanks to ML.NET. You define typed schemas, chain text transforms into a pipeline, train with a classic or transformer-based trainer, evaluate with meaningful metrics, and deploy the saved model in any .NET app. Key takeaways:

  • FeaturizeText plus SdcaLogisticRegression gets you a fast, solid sentiment analysis baseline in under 20 lines.
  • Understand the featurization steps—normalization, tokenization, n-grams, TF-IDF—so you can tune them when accuracy plateaus.
  • Use MapValueToKey/MapKeyToValue for multi-class text classification and evaluate with macro accuracy.
  • Upgrade to the TextClassification transformer trainer when you have enough labelled data and need state-of-the-art accuracy.
  • In production, use PredictionEnginePool, real datasets, model versioning, and imbalance-aware metrics.

Start with the sentiment example above, swap in your own data, and you will have a working NLP model running natively in C# before the end of the day.

About csharp-coder.com
Your go-to resource for C#, .NET, and modern software development. Follow along for daily tutorials, tips, and real-world examples.

Comments

Popular posts from this blog

.NET MAUI Tutorial 2026: Build Cross-Platform Apps in C#

Learn .NET MAUI in 2026 to build iOS, Android, Windows & Mac apps from one C# codebase. Start this cross-platform tutorial with code examples today. .NET MAUI (Multi-platform App UI) is Microsoft's framework for building native iOS, Android, Windows, and macOS apps from a single C# codebase . If you've ever wanted to ship a mobile app without learning Swift, Kotlin, and Win32 separately, this .NET MAUI tutorial for 2026 is your starting point. In this guide you'll learn what .NET MAUI is, why it matters for cross-platform app development in C#, and how to build your first working app — with runnable code examples and the best practices senior engineers actually use in production. What Is .NET MAUI and Why Use It in 2026? .NET MAUI is the evolution of Xamarin.Forms, fully integrated into the modern .NET runtime. With one project and one language — C# — you target four platforms. The framework compiles to native UI controls on each device, so a button on iOS...

Angular 14 : 404 error during refresh page after deployment

In this article, We will learn how to solve 404 file or directory not found angular error in production.  Refresh browser angular 404 file or directory not found error You have built an Angular app and created a production build with ng build --prod You deploy it to a production server. Everything works fine until you refresh the page. The app throws The requested URL was not found on this server message (Status code 404 not found). It appears that angular routing not working on the production server when you refresh the page. The error appears on the following scenarios When you type the URL directly in the address bar. When you refresh the page The error appears on all the pages except the root page.   Reason for the requested URL was not found on this server error In a Multi-page web application, every time the application needs to display a page it has to send a request to the web server. You can do that by either typing the URL in the address bar, clicking on the Me...

Angular 14 CRUD Operation with Web API .Net 6.0

How to Perform CRUD Operation Using Angular 14 In this article, we will learn the angular crud (create, read, update, delete) tutorial with ASP.NET Core 6 web API. We will use the SQL Server database and responsive user interface for our Web app, we will use the Bootstrap 5. Let's start step by step. Step 1 - Create Database and Web API First we need to create Employee database in SQL Server and web API to communicate with database. so you can use my previous article CRUD operations in web API using net 6.0 to create web API step by step. As you can see, after creating all the required API and database, our API creation part is completed. Now we have to do the angular part like installing angular CLI, creating angular 14 project, command for building and running angular application...etc. Step 2 - Install Angular CLI Now we have to install angular CLI into our system. If you have already installed angular CLI into your system then skip this step.  To install angular CLI ope...