共计 3932 个字符,预计需要花费 10 分钟才能阅读完成。
朴素贝叶斯算法在文本分类中表现优异,特别适合处理高维稀疏的文本数据。它的计算效率高,训练速度快,即使在小样本情况下也能保持较好的分类效果,是入门机器学习的绝佳选择。

文本预处理
文本预处理是机器学习中的重要环节,对于垃圾邮件过滤尤其关键。我们需要将原始文本转换成算法可以处理的数值特征。
- 分词处理
对于英文文本,可以直接按空格分割单词。但中文需要特殊处理,我们可以使用开源的结巴分词等工具。
public List<string> Tokenize(string text)
{
// 英文简单分词
return text.ToLower()
.Split(new[] {'',',','.','!','?'},
StringSplitOptions.RemoveEmptyEntries)
.ToList();}
- 停用词处理
去除常见但无意义的词汇,如 ”the”、”and” 等。
private HashSet<string> _stopWords = new HashSet<string> {"the", "and", "a" /*...*/};
public List<string> RemoveStopWords(List<string> tokens)
{return tokens.Where(t => !_stopWords.Contains(t)).ToList();}
- 词干提取
将不同形式的单词还原为基本形式,如 ”running”→”run”。
public string Stem(string word)
{
// 简化版词干提取
if (word.EndsWith("ing")) return word.Substring(0, word.Length - 3);
if (word.EndsWith("ed")) return word.Substring(0, word.Length - 2);
return word;
}
核心算法实现
朴素贝叶斯算法的核心是计算先验概率和条件概率。
- 先验概率计算
计算每个类别在训练数据中出现的概率。
private Dictionary<string, int> _classCounts = new Dictionary<string, int>();
private int _totalDocuments;
public void AddTrainingSample(string text, string category)
{if (!_classCounts.ContainsKey(category))
_classCounts[category] = 0;
_classCounts[category]++;
_totalDocuments++;
// 更新词频统计...
}
public double GetPriorProbability(string category)
{return (double)_classCounts[category] / _totalDocuments;
}
- 条件概率计算
计算每个词在每个类别中出现的概率。
private Dictionary<string, Dictionary<string, int>> _wordCategoryCounts =
new Dictionary<string, Dictionary<string, int>>();
private Dictionary<string, int> _categoryWordTotals = new Dictionary<string, int>();
private double GetWordProbability(string word, string category)
{
// 拉普拉斯平滑
int count = _wordCategoryCounts.GetValueOrDefault(category, new Dictionary<string, int>())
.GetValueOrDefault(word, 0);
int total = _categoryWordTotals.GetValueOrDefault(category, 0);
return (double)(count + 1) / (total + _vocabularySize);
}
- 完整分类器实现
public class NaiveBayesClassifier
{
// 类成员变量...
public void AddTrainingSample(string text, string category)
{
// 更新类别计数
if (!_classCounts.ContainsKey(category))
_classCounts[category] = 0;
_classCounts[category]++;
_totalDocuments++;
// 更新词频统计
var tokens = PreprocessText(text);
if (!_wordCategoryCounts.ContainsKey(category))
_wordCategoryCounts[category] = new Dictionary<string, int>();
if (!_categoryWordTotals.ContainsKey(category))
_categoryWordTotals[category] = 0;
foreach (var token in tokens)
{if (!_wordCategoryCounts[category].ContainsKey(token))
_wordCategoryCounts[category][token] = 0;
_wordCategoryCounts[category][token]++;
_categoryWordTotals[category]++;
// 更新词汇表大小
if (!_vocabulary.Contains(token))
{_vocabulary.Add(token);
_vocabularySize++;
}
}
}
public string Classify(string text)
{var tokens = PreprocessText(text);
var categories = _classCounts.Keys;
string bestCategory = null;
double maxScore = double.MinValue;
foreach (var category in categories)
{double score = Math.Log(GetPriorProbability(category));
foreach (var token in tokens)
{score += Math.Log(GetWordProbability(token, category));
}
if (score > maxScore)
{
maxScore = score;
bestCategory = category;
}
}
return bestCategory;
}
}
性能优化
- 内存优化
- 使用高效的哈希函数减少碰撞
-
对于大型数据集,考虑使用稀疏矩阵存储
-
多线程训练
- 将训练数据分片处理
- 使用 ConcurrentDictionary 保证线程安全
- 注意锁的粒度,避免性能下降
端到端示例
// 加载数据集
var dataset = File.ReadAllLines("SMSSpamCollection.txt")
.Select(line => line.Split(new[] {'\t'}, 2))
.Select(parts => new { Label = parts[0], Text = parts[1] })
.ToList();
// 创建并训练分类器
var classifier = new NaiveBayesClassifier();
foreach (var item in dataset)
{classifier.AddTrainingSample(item.Text, item.Label);
}
// 测试分类器
var testMessage = "WINNER!! You've been selected to receive a free vacation!";
var result = classifier.Classify(testMessage);
Console.WriteLine($"Message: {testMessage}");
Console.WriteLine($"Classification: {result}");
生产环境建议
- 特征选择
- 使用卡方检验或信息增益选择最有区分度的特征
-
考虑 n -gram 特征提取
-
处理数值下溢
- 使用对数概率避免连乘导致的数值下溢
-
实现示例中已经采用了这一策略
-
模型持久化
- 使用 Protobuf 进行序列化
- 定期保存模型快照
public void SaveModel(string path)
{using (var file = File.Create(path))
{Serializer.Serialize(file, this);
}
}
public static NaiveBayesClassifier LoadModel(string path)
{using (var file = File.OpenRead(path))
{return Serializer.Deserialize<NaiveBayesClassifier>(file);
}
}
思考题
- 如何改进算法处理一词多义问题?可以考虑引入词向量 (word2vec) 或上下文信息。
- 当新类别出现时如何增量训练?需要设计机制来动态扩展词汇表和类别集合。
通过这个实战项目,我们不仅实现了一个可用的垃圾邮件过滤器,更重要的是掌握了朴素贝叶斯算法的核心思想和实现技巧。虽然这是一个入门级项目,但它包含了机器学习系统开发的完整流程,为后续更复杂的项目打下了坚实基础。
正文完
