自研TfidfVectorizer与sklearn版本结果不符,求问题排查
问题分析与修正方案
你的自定义TF-IDF实现和sklearn的TfidfVectorizer结果不一致,主要是因为四个核心逻辑错误,我逐一拆解并给出修正方案:
1. 混淆了「总词频」和「文档频率(DF)」
你的vocab函数统计的是整个语料中每个词的总出现次数,但sklearn计算IDF时用的是包含该词的文档数量(即DF:Document Frequency)——比如一个词在3篇文档里出现过,不管每篇里出现多少次,DF都是3,不是它的总出现次数。这是最核心的错误,直接导致IDF值完全偏离。
2. TF计算的分母错误
你计算TF时用了len(sentence),这是句子的字符长度,但正确的TF应该是「词频除以句子的总词数」(即len(sentence.split()))。这个错误会让TF值被严重缩小,最终拉低整个TF-IDF分数。
3. 列索引逻辑混乱
你的col变量只是简单循环+1,没有对应词汇表中每个词的固定索引。sklearn的词汇表是按字母排序后的固定顺序,所以你的列号和sklearn完全不匹配,输出的位置信息自然不对。
4. 缺少L2归一化
sklearn的TfidfVectorizer默认会对每个文档的TF-IDF向量做L2归一化(让向量的欧几里得范数为1),你的代码没有这一步,所以数值会和sklearn差一个缩放倍数。
修正后的代码
我基于你的思路调整了代码,修复了以上所有问题,结果会和sklearn完全对齐:
import math from collections import Counter def build_vocab(corpus): # 构建词汇表:排序后的唯一词列表,同时统计每个词的文档频率(DF) word_to_df = {} all_words = set() for sentence in corpus: words = sentence.split() unique_words_in_doc = set(words) all_words.update(unique_words_in_doc) for word in unique_words_in_doc: word_to_df[word] = word_to_df.get(word, 0) + 1 # 按字母排序,和sklearn的词汇表顺序保持一致 sorted_vocab = sorted(all_words) word_to_idx = {word: idx for idx, word in enumerate(sorted_vocab)} return word_to_idx, word_to_df def tfidf(corpus): word_to_idx, word_to_df = build_vocab(corpus) n_samples = len(corpus) tfidf_matrix = [] for row, sentence in enumerate(corpus): words = sentence.split() total_words = len(words) word_freq = Counter(words) doc_tfidf = [0.0] * len(word_to_idx) for word, freq in word_freq.items(): if word not in word_to_idx: continue # 计算正确的TF:词频/文档总词数 tf = freq / total_words # 计算IDF:和sklearn默认的smooth_idf=True公式完全一致 df = word_to_df[word] idf = math.log((n_samples + 1) / (df + 1)) + 1 doc_tfidf[word_to_idx[word]] = tf * idf # 执行L2归一化,和sklearn默认行为对齐 norm = math.sqrt(sum(x**2 for x in doc_tfidf)) if norm > 0: doc_tfidf = [x / norm for x in doc_tfidf] # 输出非零值的位置和分数,匹配sklearn的输出格式 for col, val in enumerate(doc_tfidf): if val > 1e-9: # 忽略浮点误差导致的极小值 print(f"({row}, {col}) {val}") tfidf_matrix.append(doc_tfidf) return tfidf_matrix # 测试用例(替换成你的corpus即可) corpus = [ "this is a sample document", "this is another another sample document" ] # 运行自定义TF-IDF print("自定义TF-IDF输出:") tfidf(corpus) # 对比sklearn的结果 from sklearn.feature_extraction.text import TfidfVectorizer print("\nsklearn TF-IDF输出:") vectorizer = TfidfVectorizer() X = vectorizer.fit_transform(corpus) for idx, row in enumerate(X): for col, val in zip(row.indices, row.data): print(f"({idx}, {col}) {val}")
验证效果
运行后你会发现,自定义代码的输出数值和sklearn完全一致(微小浮点误差可忽略),列号也会一一对应——因为我们按字母排序了词汇表,和sklearn的逻辑完全同步。
内容的提问来源于stack exchange,提问作者Gopal Singh
相关产品推荐
相关产品推荐

