C# 朴素贝叶斯分类器实战:从零构建垃圾邮件过滤器

1次阅读
没有评论

共计 1925 个字符,预计需要花费 5 分钟才能阅读完成。

image.webp

朴素贝叶斯算法在文本分类中表现优异,尤其适合处理小样本数据和高维特征。它的计算效率极高,且对缺失数据不敏感,这些特点使其成为垃圾邮件过滤等场景的理想选择。下面我们就用 C# 一步步实现这个经典算法。

C# 朴素贝叶斯分类器实战:从零构建垃圾邮件过滤器

文本预处理

  1. 分词处理
    对于英文文本,我们可以直接用空格分割,但中文需要额外分词库:

    // 使用 Jieba.NET 中文分词
    var segmenter = new JiebaSegmenter();
    var words = segmenter.Cut("您的信用卡账单已生成");

  2. 停用词过滤
    常见无意义词会降低模型效果:

    var stopWords = new HashSet<string>{"的", "和", "是"};
    words = words.Where(w => !stopWords.Contains(w));

  3. 词干提取
    英文需要统一单词形式(如 running→run):

    var stemmer = new PorterStemmer();
    string stemmed = stemmer.StemWord("running");

核心算法实现

我们先定义一个朴素贝叶斯类框架:

public class NaiveBayesClassifier
{private Dictionary<string, int> spamWordCounts = new();
    private Dictionary<string, int> hamWordCounts = new();
    private int spamTotal, hamTotal;

    // 添加训练样本
    public void AddTrainingSample(string text, bool isSpam) {/*...*/}

    // 进行分类预测
    public bool Classify(string text) {/*...*/}
}

关键步骤拆解

  1. 先验概率计算

    // P(Spam) = 垃圾邮件总数 / 总邮件数
    double pSpam = (double)spamTotal / (spamTotal + hamTotal);

  2. 条件概率计算
    使用拉普拉斯平滑避免零概率问题:

    // P(word|Spam) = (词在垃圾邮件中出现次数 + 1) / (垃圾邮件总词数 + 唯一词数)
    double pWordGivenSpam = (spamWordCounts.GetValueOrDefault(word, 0) + 1) 
                           / (double)(spamTotal + vocabularySize);

  3. 联合概率计算
    对文本中所有词的概率取对数相加,防止数值下溢:

    // 使用 log 转换乘法为加法
    double spamScore = Math.Log(pSpam);
    foreach(var word in words)
        spamScore += Math.Log(GetWordProbability(word, true));

性能优化技巧

  1. 内存优化
  2. 使用哈希值存储长词
  3. 定期清理低频词(出现次数 <3)

  4. 多线程训练

    Parallel.ForEach(trainingData, item => {lock (lockObj) {classifier.AddTrainingSample(item.Text, item.IsSpam);
        }
    });

端到端示例

加载公开数据集并训练:

var dataset = File.ReadLines("SMSSpamCollection")
    .Select(line => line.Split('\t'))
    .Select(parts => new {IsSpam = parts[0] == "spam",
        Text = parts[1]
    });

var nb = new NaiveBayesClassifier();
foreach(var item in dataset)
    nb.AddTrainingSample(item.Text, item.IsSpam);

// 测试
Console.WriteLine(nb.Classify("WIN FREE IPHONE")); // true
Console.WriteLine(nb.Classify("Meeting at 3pm"));  // false

生产环境建议

  1. 特征选择
  2. 使用卡方检验选取 Top 500 最具区分度的词
  3. 去除 URL、特殊符号等干扰项

  4. 处理数值下溢

  5. 始终使用对数概率计算
  6. 设置最小概率阈值(如 1e-10)

  7. 模型持久化

    // 使用 ProtoBuf.NET 序列化
    Serializer.SerializeToFile("model.bin", classifier);

思考与改进

  1. 一词多义问题可以通过:
  2. 引入词性标注
  3. 结合上下文窗口(n-gram)

  4. 增量训练建议:

  5. 定期用新数据微调模型
  6. 实现模型版本控制

通过这个实战项目,我们发现朴素贝叶斯虽然简单,但在文本分类任务中表现惊人。建议读者尝试调整特征选择策略,或者将该分类器扩展到其他语言场景。

正文完
 0
评论(没有评论)