You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Naive Bayes分类器实现存疑?求排查与优化建议

Naive Bayes文本分类器准确率优化问题

我正在为学校项目构建基于Naive Bayes的文本分类器,测试集准确率为89.4%,但教授指出正确实现的话准确率应高出约1个百分点。现请教我的Naive Bayes实现是否存在问题,或是否需应用文档归一化技术?训练集与测试集包含两列:第一列为文件路径,第二列为文档所属类别。其余函数多为模型调优相关,若需完整代码或补充说明可告知。

核心实现代码

def getStatistics(trainSet):
  # 计算文档属于某类别的先验概率:训练集中该类别文档占比
  probCateg = trainSet["Category"].value_counts()
  probCateg = probCateg/probCateg.sum()
  probCateg = probCateg.to_dict()

  # 构建词-类别词频映射:wordFreq[word][categ] = 词word在类别categ中的出现次数
  wordFreq = {}

  lemmatizer = WordNetLemmatizer()

  # 遍历训练集每个文档
  for index, row in trainSet.iterrows():
      # 读取文档内容
      with open(row["Filename"], 'r') as f:
          text = f.read()
          f.close()

      # 文本预处理:分词、去重、小写、去停用词/标点、词形还原
      tokens = nltk.word_tokenize(text)
      # 注释提到计数词频能提升准确率,但此处转集合抹除了词频
      tokens = list(set(tokens))
      
      tokens = [token.lower() for token in tokens]
      tokens = [token for token in tokens if (token not in punctuation) and (token not in stopwords)]
      tokens = [(lemmatizer.lemmatize(word)) for word in tokens]
      # 再次转集合,重复去重操作
      tokens = list(set(tokens))
        
      # 更新词频统计
      for word in tokens:
        if not (word in wordFreq):
          wordFreq[word] = dict(zip(probCateg.keys(), [0]*len(probCateg.keys())))
        wordFreq[word][row["Category"]] += 1

  return wordFreq, probCateg

# 文档分类函数:输入文件路径,返回最可能的类别
def classifyDoc(wordFreq, probCateg, filename, eps, trainSize):
  # 读取文档内容
  with open(filename, 'r') as f:
        text = f.read()
        f.close()
  
  # 与训练阶段一致的文本预处理
  lemmatizer = WordNetLemmatizer()
  tokens = nltk.word_tokenize(text)
  tokens = list(set(tokens))
  tokens = [token.lower() for token in tokens]
  tokens = [token for token in tokens if (token not in punctuation) and (token not in stopwords)]
  tokens = [(lemmatizer.lemmatize(word)) for word in tokens]
  tokens = list(set(tokens))
        
  # 初始化对数概率:先验概率的对数
  logProb = dict(zip(probCateg.keys(), [math.log(probCateg[key]) for key in probCateg.keys()]))
  
  secCorp = "O" in probCateg.keys()

  # 遍历每个类别计算后验对数概率
  for categ in probCateg.keys():
    # 用文档数作为分母(错误:应为类别总词数)
    denom = trainSize*probCateg[categ]
    if secCorp:
      denom += len(wordFreq)*eps

    # 遍历文档中的每个词
    for word in tokens:
      if word in wordFreq:
        logProb[categ] += math.log(wordFreq[word][categ] + eps)
      else:
        logProb[categ] += math.log(eps)
      # 每个词都减去分母的对数(错误:重复减分)
      logProb[categ] -= math.log(denom)

  # 返回概率最高的类别
  return max(logProb, key=logProb.get) 

问题分析与优化建议

一、核心实现错误

  1. 词频统计逻辑矛盾
    注释明确提到“计数单词在文档中出现的次数能提升4%准确率”,但代码中两次执行tokens = list(set(tokens)),直接抹除词频信息,退化为只记录词是否出现的词集模型,丢失了关键特征,这是准确率不达标的核心原因之一。

  2. 拉普拉斯平滑公式错误
    标准拉普拉斯平滑公式为:
    $$P(t|c) = \frac{C(t,c) + \alpha}{C(c) + \alpha \times V}$$
    其中:

  • $C(t,c)$是词t在类别c中的总出现次数
  • $C(c)$是类别c中所有词的总出现次数(而非文档数量)
  • $V$是词汇表总大小

你的实现中:

  • 分母用trainSize*probCateg[categ]计算的是类别c的文档数,而非总词数,完全不符合公式要求;
  • 仅针对含"O"的语料才添加平滑项,逻辑不统一,且平滑项计算方式错误。
  1. 对数概率计算重复减分母
    在classifyDoc的单词循环中,每次都执行logProb[categ] -= math.log(denom),相当于把分母的对数重复减去了len(tokens)次。正确做法是先累加所有分子的对数和,最后统一减去一次分母的对数。

二、优化步骤

  1. 切换为词袋模型
    删除两次tokens = list(set(tokens))的操作,统计每个词在文档中的出现次数,修改训练阶段的词频累加逻辑:
# 替换原有的tokens转集合代码,改为统计词频
from collections import Counter
token_counts = Counter(tokens)
for word, count in token_counts.items():
    if word not in wordFreq:
        wordFreq[word] = dict(zip(probCateg.keys(), [0]*len(probCateg.keys())))
    wordFreq[word][row["Category"]] += count
  1. 修正拉普拉斯平滑实现
  • 训练阶段新增每个类别的总词数统计:
# 在getStatistics函数中初始化
total_words_per_categ = {categ:0 for categ in probCateg.keys()}
# 遍历文档时累加总词数
total_words_per_categ[row["Category"]] += sum(token_counts.values())
# 最后将total_words_per_categ作为第三个返回值
return wordFreq, probCateg, total_words_per_categ
  • 分类阶段修正平滑计算:
# 修改classifyDoc函数,接收total_words_per_categ参数
def classifyDoc(wordFreq, probCateg, total_words_per_categ, filename, eps):
    # ... 预处理部分保持不变 ...
    logProb = dict(zip(probCateg.keys(), [math.log(probCateg[key]) for key in probCateg.keys()]))
    vocab_size = len(wordFreq)
    for categ in probCateg.keys():
        alpha = eps
        denom = total_words_per_categ[categ] + alpha * vocab_size
        denom_log = math.log(denom)
        for word in tokens:
            if word in wordFreq:
                logProb[categ] += math.log(wordFreq[word][categ] + alpha)
            else:
                logProb[categ] += math.log(alpha)
        # 最后统一减去分母的对数
        logProb[categ] -= denom_log
    return max(logProb, key=logProb.get)
  1. 其他优化点
  • 文本预处理:可添加数字去除逻辑,若任务不需要数字特征;
  • 文档归一化:Naive Bayes本身不需要TF-IDF,但修正核心错误后可尝试引入TF-IDF加权,观察是否提升准确率;
  • 效率优化:用pandas的批量操作代替iterrows()遍历,提升训练速度。

内容的提问来源于stack exchange,提问作者snowball

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 19:44:59