Analyze Customer Sentiment at Scale
Sentiment analysis classifies text as positive, negative, or neutral. LM-Kit.NET provides dedicated APIs for sentiment analysis and emotion detection that work out of the box with no training data, no prompt engineering, and no cloud dependencies. This tutorial builds a working system that processes customer feedback, detects emotions, and exports results.
Why Local Sentiment Analysis Matters
Two enterprise problems that on-device sentiment analysis solves:
- Customer data privacy. Support tickets, NPS surveys, and product reviews contain personally identifiable information. Sending them to cloud APIs creates data processing agreements, GDPR notifications, and vendor lock-in. Local inference keeps customer data entirely within your infrastructure.
- High-volume processing without per-call costs. Cloud sentiment APIs charge per request. At scale (100K+ reviews, continuous social monitoring), costs compound. A local model runs unlimited classifications at zero marginal cost after the initial hardware investment.
Prerequisites
| Requirement | Minimum |
|---|---|
| .NET SDK | 8.0+ |
| VRAM | 2+ GB |
| Disk | ~2 GB free for model download |
Step 1: Create the Project
dotnet new console -n SentimentQuickstart
cd SentimentQuickstart
dotnet add package LM-Kit.NET
Step 2: Basic Sentiment Analysis
This program classifies text as positive, negative, or neutral with a confidence score.
using System.Text;
using LMKit.Model;
using LMKit.TextAnalysis;
LMKit.Licensing.LicenseManager.SetLicenseKey("");
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
// ──────────────────────────────────────
// 1. Load model
// ──────────────────────────────────────
Console.WriteLine("Loading model...");
using LM model = LM.LoadFromModelID("gemma3:1b",
downloadingProgress: (_, len, read) =>
{
if (len.HasValue) Console.Write($"\r Downloading: {(double)read / len.Value * 100:F1}% ");
return true;
},
loadingProgress: p => { Console.Write($"\r Loading: {p * 100:F0}% "); return true; });
Console.WriteLine("\n");
// ──────────────────────────────────────
// 2. Create sentiment analyzer
// ──────────────────────────────────────
var sentiment = new SentimentAnalysis(model)
{
NeutralSupport = true // enable positive/negative/neutral (set false for binary)
};
// ──────────────────────────────────────
// 3. Classify sample reviews
// ──────────────────────────────────────
string[] reviews =
{
"This product exceeded my expectations. The build quality is outstanding.",
"Terrible customer service. Waited 3 hours on hold and got no help.",
"The item arrived on time. It works as described.",
"I love this app! It saved me hours of work every week.",
"Broken on arrival. Returning immediately. Very disappointed.",
"It's okay. Nothing special but gets the job done."
};
Console.WriteLine("Analyzing customer reviews:\n");
foreach (string review in reviews)
{
SentimentCategory category = sentiment.GetSentimentCategory(review);
float confidence = sentiment.Confidence;
Console.ForegroundColor = category switch
{
SentimentCategory.Positive => ConsoleColor.Green,
SentimentCategory.Negative => ConsoleColor.Red,
_ => ConsoleColor.Yellow
};
Console.Write($" [{category,-8}]");
Console.ResetColor();
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.Write($" ({confidence:P0}) ");
Console.ResetColor();
string preview = review.Length > 60 ? review.Substring(0, 60) + "..." : review;
Console.WriteLine(preview);
}
// ──────────────────────────────────────
// 4. Interactive mode
// ──────────────────────────────────────
Console.WriteLine("\nEnter text to analyze (or 'quit' to exit):\n");
while (true)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("Text: ");
Console.ResetColor();
string? input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.Equals("quit", StringComparison.OrdinalIgnoreCase))
break;
SentimentCategory result = sentiment.GetSentimentCategory(input);
Console.WriteLine($" Sentiment: {result} (confidence: {sentiment.Confidence:P0})\n");
}
Step 3: Emotion Detection
Go beyond positive/negative with fine-grained emotion classification: happiness, anger, sadness, fear, or neutral.
using LMKit.TextAnalysis;
var emotion = new EmotionDetection(model)
{
NeutralSupport = true
};
string[] messages =
{
"I'm so excited about the new features in this release!",
"This bug has been driving me crazy for weeks.",
"We lost a major client today. The team is devastated.",
"What if the deployment fails during peak hours?",
"The update installed successfully. No issues to report."
};
foreach (string message in messages)
{
EmotionCategory category = emotion.GetEmotionCategory(message);
float confidence = emotion.Confidence;
Console.WriteLine($" {category,-10} ({confidence:P0}) {message}");
}
Expected output:
Happiness (94%) I'm so excited about the new features in this release!
Anger (89%) This bug has been driving me crazy for weeks.
Sadness (91%) We lost a major client today. The team is devastated.
Fear (85%) What if the deployment fails during peak hours?
Neutral (88%) The update installed successfully. No issues to report.
Step 4: Batch Processing a CSV File
Process a file of customer feedback and export results:
using LMKit.TextAnalysis;
var sentiment = new SentimentAnalysis(model) { NeutralSupport = true };
// Read feedback from a file (one review per line)
string[] lines = File.ReadAllLines("feedback.csv");
var results = new List<string>();
results.Add("text,sentiment,confidence");
int positive = 0, negative = 0, neutral = 0;
foreach (string line in lines)
{
if (string.IsNullOrWhiteSpace(line)) continue;
SentimentCategory category = sentiment.GetSentimentCategory(line);
float confidence = sentiment.Confidence;
results.Add($"\"{line.Replace("\"", "\"\"")}\",{category},{confidence:F3}");
switch (category)
{
case SentimentCategory.Positive: positive++; break;
case SentimentCategory.Negative: negative++; break;
default: neutral++; break;
}
}
File.WriteAllLines("results.csv", results);
int total = positive + negative + neutral;
Console.WriteLine($"Processed {total} reviews:");
Console.WriteLine($" Positive: {positive} ({(double)positive / total:P0})");
Console.WriteLine($" Negative: {negative} ({(double)negative / total:P0})");
Console.WriteLine($" Neutral: {neutral} ({(double)neutral / total:P0})");
Console.WriteLine($"Results saved to results.csv");
Step 5: Combining Sentiment with Custom Classification
Use Categorization alongside sentiment analysis to understand both the topic and the tone:
using LMKit.TextAnalysis;
var sentiment = new SentimentAnalysis(model) { NeutralSupport = true };
var categorizer = new Categorization(model);
string[] topics = { "product quality", "customer service", "pricing", "delivery", "usability" };
string feedback = "The interface is confusing and I keep getting lost. Very frustrating.";
// What is the topic?
int topicIndex = categorizer.GetBestCategory(topics, feedback);
string topic = topics[topicIndex];
float topicConfidence = categorizer.Confidence;
// What is the sentiment?
SentimentCategory tone = sentiment.GetSentimentCategory(feedback);
float sentimentConfidence = sentiment.Confidence;
Console.WriteLine($"Topic: {topic} ({topicConfidence:P0})");
Console.WriteLine($"Sentiment: {tone} ({sentimentConfidence:P0})");
// Output: Topic: usability (92%) Sentiment: Negative (95%)
This combination is the building block for customer feedback dashboards: categorize by topic, then aggregate sentiment per topic to identify the areas that need attention.
Model Selection for Sentiment Analysis
| Model ID | VRAM | Speed | Accuracy | Best For |
|---|---|---|---|---|
gemma3:1b |
~1.5 GB | Fastest | Good | High-volume batch processing |
qwen3:1.7b |
~1.5 GB | Very fast | Good | Multilingual feedback |
gemma3:4b |
~3.5 GB | Fast | Very good | General use (recommended) |
qwen3:4b |
~3.5 GB | Fast | Very good | Mixed-language datasets |
For sentiment and emotion detection, smaller models (1B-4B) perform well because the task is straightforward. Use a 1B model for batch processing large datasets where speed matters more than nuance.
Common Issues
| Problem | Cause | Fix |
|---|---|---|
| Always returns Neutral | NeutralSupport is false (default) |
Set NeutralSupport = true for three-way classification |
| Low confidence on short texts | Not enough signal in 2-3 words | Provide more context; consider using EmotionDetection for short messages |
| Wrong sentiment on sarcasm | Model takes text literally | Use a larger model (4B+) for better sarcasm understanding |
| Slow batch processing | Model too large for the task | Use gemma3:1b for bulk operations |
Next Steps
- Extract Structured Data from Unstructured Text: pull structured fields from feedback (names, dates, product IDs).
- Build Semantic Search with Embeddings: search feedback by meaning to find similar complaints.
- Samples: Sentiment Analysis: full sentiment analysis demo.
- Samples: Emotion Detection: full emotion detection demo.