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)
问题分析与优化建议
一、核心实现错误
词频统计逻辑矛盾
注释明确提到“计数单词在文档中出现的次数能提升4%准确率”,但代码中两次执行tokens = list(set(tokens)),直接抹除词频信息,退化为只记录词是否出现的词集模型,丢失了关键特征,这是准确率不达标的核心原因之一。拉普拉斯平滑公式错误
标准拉普拉斯平滑公式为:
$$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"的语料才添加平滑项,逻辑不统一,且平滑项计算方式错误。
- 对数概率计算重复减分母
在classifyDoc的单词循环中,每次都执行logProb[categ] -= math.log(denom),相当于把分母的对数重复减去了len(tokens)次。正确做法是先累加所有分子的对数和,最后统一减去一次分母的对数。
二、优化步骤
- 切换为词袋模型
删除两次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
- 修正拉普拉斯平滑实现
- 训练阶段新增每个类别的总词数统计:
# 在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)
- 其他优化点
- 文本预处理:可添加数字去除逻辑,若任务不需要数字特征;
- 文档归一化:Naive Bayes本身不需要TF-IDF,但修正核心错误后可尝试引入TF-IDF加权,观察是否提升准确率;
- 效率优化:用
pandas的批量操作代替iterrows()遍历,提升训练速度。
内容的提问来源于stack exchange,提问作者snowball
相关产品推荐
相关产品推荐

