TF-IDF算法详解与实践总结
作为自然语言处理(NLP)领域的“老将”,TF-IDF以“词频-逆文档频率”的巧妙逻辑,成为连接原始文本与智能分析的桥梁。今天我们来盘一盘TF-IDF。
TF-IDF
简介
TF-IDF(Term Frequency-Inverse Document Frequency,词语频率-逆文档频率)是一种强大的技术,用于分析和理解单词在文档语料库中的重要性。
TF-IDF 最初是为文档搜索和信息检索设计的,其中运行查询,系统必须找到最相关的文档。
TF-IDF 是一种数字统计量,它反映了文档中某个单词相对于文档集合(称为语料库)的重要性。TF-IDF 背后的理念是量化词语在文档中的重要性,包括它在文档中的频率及其在多个文档中的稀有性。
字词的重要性随着它在文档中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。简单的解释为,一个单词在一个文档中出现次数很多,同时在其他文档中出现此时较少,那么我们认为这个单词对该文档是非常重要的。
TF-IDF 在文本挖掘、信息检索和文档分类等任务中发挥着至关重要的作用。
- 信息检索 :搜索引擎通过TF-IDF计算查询词与文档的相关性,排序结果。查询词的 TF-IDF 分数较高的文档被认为更相关,并在搜索结果中排名更高。
- 文本分类 :将文档转化为TF-IDF特征向量后(TF-IDF 用于将文档表示为数值向量),输入分类模型(如SVM、朴素贝叶斯)
- 关键词提取 :选取TF-IDF值最高的若干词项作为文档关键词
- LDA主题模型 :作为预处理步骤,过滤低权重词以提升主题建模效果。识别文档中最重要的句子或短语,帮助生成简洁的摘要
组件
TF(Term Frequency,词频),某个词条在文中出现的次数。
词频( T F ) = 某个词在文档中出现的次数 文档的总词数 词频(TF)=\frac{某个词在文档中出现的次数}{文档的总词数} 词频(TF)=文档的总词数某个词在文档中出现的次数
IDF(Inverse Document Frequency,逆向文档频率),一个词在文档集合中的逆文档频率。罕见词得分高,常见词得分低。衡量词语在文档集合中的稀有性。
逆文档频率( I D F ) = l o g ( 语料库的文档总数 包含该词的文档数 + 1 ) 逆文档频率(IDF)=log(\frac{语料库的文档总数}{包含该词的文档数+1}) 逆文档频率(IDF)=log(包含该词的文档数+1语料库的文档总数)
如果只用词频来表达一个词对一篇文档的重要程度显然是不合适的,因为在一篇文档里面,可能出现频率很高的词是一些没有意义的词语,真正能代表这篇文档的关键词可能只出很少的次数。
如果有太多文档都涵盖了某个单词,这个单词也就越不重要,或者说是这个单词就越没有信息量
因此,我们需要对 TF 的值进行修正,而 IDF 的想法是用 DF (文档频率)的倒数来进行修正。倒数的应用正好表达了这样的思想,DF 值越大越不重要。
结合 TF-IDF
文档中某个术语的 TF-IDF 分数是通过将 TF 和 IDF 分数相乘来获得。
T F − I D F ( t , d , D ) = T F ( t , d ) × I D F ( t , D ) TF-IDF(t,d,D)=TF(t,d)×IDF(t,D) TF−IDF(t,d,D)=TF(t,d)×IDF(t,D)
步骤
- 预处理数据:清洗数据、规范化数据、词形还原、去除停用词等
- 词频分词
- 计算TF
- 计算IDF
- 向量化词汇
优缺点
-
优点:易于计算
-
缺点:
- 不能捕捉词在文档中的位置,无法获取语义。
- TF-IDF 算法主要适用于英文,中文首先要分词,分词后要解决多词一义,以及一词多义问题,这两个问题通过简单的TF-IDF方法不能很好的解决。
- 对文档中出现次数较少的重要人名、地名的提取效果不佳
案例
提取关键词【纯python】
预处理
import jieba
import math
import numpy as np
from collections import defaultdict
import nltk
from nltk.corpus import stopwords# 下载 NLTK 中文停用词(需确认资源是否存在)
try:nltk.download('stopwords')stopwords_cn = stopwords.words('chinese')print("已下载 NLTK 中文停用词")
except:# 若 nltk 中文停用词缺失,手动补充常用中文停用词stopwords_cn = ['的', '了', '和', '是', '在', '中', '也', '都', '而', '就','那', '这', '之', '为', '对', '与', '于', '上', '下', '前']# 中文文本预处理(分词 + 去停用词)
def preprocess(text):tokens = jieba.cut(text)return [token for token in tokens if token not in stopwords_cn and token.strip()]
TF-DF基类
class TFIDFBase:def __init__(self, corpus):self.corpus = corpusself.tokenized_corpus = [preprocess(doc) for doc in corpus]self.N = len(corpus) # 文档总数self.df = self._compute_df()self.idf = self._compute_idf()def _compute_df(self):"""计算文档频率 (Document Frequency)"""df = defaultdict(int)for tokens in self.tokenized_corpus:unique_tokens = set(tokens)for token in unique_tokens:df[token] += 1return dfdef _compute_idf(self):"""计算逆文档频率 (IDF)"""idf = defaultdict(float)for token, count in self.df.items():idf[token] = math.log(self.N / (count + 1)) + 1 # 平滑处理return idfdef compute_tf(self, tokens):"""计算词频 (TF)"""tf = defaultdict(int)total = len(tokens)for token in tokens:tf[token] += 1 / total # 归一化return tfdef compute_tfidf(self, tf):"""计算 TF-IDF 向量"""return {token: tf[token] * self.idf[token] for token in tf}def get_tfidf_vector(self):"""生成整个语料库的 TF-IDF 向量"""tfidf_vectors = []for tokens in self.tokenized_corpus:tf = self.compute_tf(tokens)tfidf = self.compute_tfidf(tf)tfidf_vectors.append(tfidf)return tfidf_vectors
提取关键词
class KeywordExtractor(TFIDFBase):def extract_keywords(self, top_k=5):"""提取每个文档的 top-k 关键词"""tfidf_vectors = self.get_tfidf_vector()results = []for idx, tfidf in enumerate(tfidf_vectors):sorted_keywords = sorted(tfidf.items(), key=lambda x: -x[1])[:top_k]results.append([kw[0] for kw in sorted_keywords])return results
结果
Document 1 Keywords: ['人工智能', '领域', '一个']
Document 2 Keywords: ['技术', '文档', '找到']
Document 3 Keywords: ['算法', '常用', '经典']
Document 4 Keywords: ['中文', '分词', '基础']
Document 5 Keywords: ['搜索引擎', '搜索', '相关性']
Document 6 Keywords: ['TF', '-', 'IDF']
Document 7 Keywords: ['深度', '学习', '进展']
Document 8 Keywords: ['倒排', '索引', '核心技术']
Document 9 Keywords: ['查询', '扩展', '提高']
Document 10 Keywords: ['准确率', '评价', '指标']
提取关键词【调用依赖】
安装相关依赖
pip install sklearn
实现TF-IDF向量化
from sklearn.feature_extraction.text import TfidfVectorizer# 使用TF-IDF向量化器
vectorizer = TfidfVectorizer(tokenizer=preprocess, # 指定自定义分词函数token_pattern=r"(?u)\b\w+\b", # 匹配中文分词结果max_features=1000 # 控制词汇表大小
)# 拟合语料库并生成TF-IDF矩阵
tfidf_matrix = vectorizer.fit_transform(corpus)
提取关键词
for i, text in enumerate(corpus):feature_index = tfidf_matrix[i, :].nonzero()[1] # 非零元素的索引tfidf_scores = zip(feature_index, [tfidf_matrix[i, x] for x in feature_index]) # (词索引, 权重)sorted_tfidf = sorted(tfidf_scores, key=lambda x: x[1], reverse=True) # 按权重排序top_n = 3 # 提取每个文档的前3个关键词keywords = [vectorizer.get_feature_names_out()[idx] for idx, score in sorted_tfidf[:top_n]]print(f"文档 {i + 1} 的关键词: {keywords}")
结果
文档 1 的关键词: ['人工智能', '领域', '一个']
文档 2 的关键词: ['技术', '文档', '找到']
文档 3 的关键词: ['算法', '常用', '经典']
文档 4 的关键词: ['中文', '分词', '基础']
文档 5 的关键词: ['搜索引擎', '搜索', '相关性']
文档 6 的关键词: ['tf', '-', 'idf']
文档 7 的关键词: ['深度', '学习', '进展']
文档 8 的关键词: ['倒排', '索引', '核心技术']
文档 9 的关键词: ['查询', '扩展', '提高']
文档 10 的关键词: ['准确率', '评价', '指标']
我们写的纯python实现与调用依赖实现的结果一致
面试高频问题
1. 为什么TF要进行标准化操作?
解决长文档、短文档问题,仅用频次来表示,长文本中出现频次高的概率会更大,这一点会影响到不同文档词权值的比较。(文档有长短之分,为了便于不同文档的比较)
2. 为什么要取对数?
- 使用log可以防止权重爆炸。如果某些词只出现一篇或者少数几篇文档中(比如错别字),在没有log的情况下,IDF就会非常小(分母过小),从而影响其权重,而使用log能减轻这种影响。(平滑)
- 利用对数来达到非线性增长的目的,压缩了数据之间的距离,使数据更加平稳,削弱他们之间的差异性。缩小数据的绝对数值,方便计算(缩放)
3. 为什么IDF分母中要进行+1(IDF如何进行平滑处理的)?
- 采用了拉普拉斯平滑(分母+ 1)是为了避免分母为 0(即所有文档都不包含该词),增强了算法的健壮性。如果一个词越常见那么分母就越大,逆文档频率就越小越接近 0。
4. 为什么要词频 * 逆文档频率(TF-IDF要用乘法)?
-
T F × I D F TF \times IDF TF×IDF 的乘积可以提高重要性较高的词的权重,同时降低重要性较低的词的权重,从而更好地区分不同的词语。
文档中的停用词(如“的”,“地”,“了”)词频很高,文档集中含停用词的数量约等于总的文档集数量,即 N n ≃ \frac{N}{n} \simeq nN≃ 1( N n \frac{N}{n} nN恒大于1)。在只使用TF的情况下,停用词所占的权重很大,但是它并没有区分能力,与我们的期望不符。使用IDF之后,由于log(1)=0,则停用词通过TF-IDF计算出的权重就为0(根号达不到这样的效果),就可以消除很多停用词的影响。。
-
涉及概率,用乘法,简单有效。
结语
TF-IDF作为信息检索与文本挖掘领域的经典算法,凭借其简单直观的逻辑和强大的实用性,成为从海量文本中提取价值的“黄金法则”。
然而,它并非万能钥匙——面对复杂的语义关系和动态变化的文本数据,TF-IDF的局限性也逐渐显现(例如无法捕捉词序、上下文关联等)。
关于我
RESOURCES
- https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/AI%e6%8a%80%e6%9c%af%e5%86%85%e5%8f%82/031%20%e7%bb%8f%e5%85%b8%e6%90%9c%e7%b4%a2%e6%a0%b8%e5%bf%83%e7%ae%97%e6%b3%95%ef%bc%9aTF-IDF%e5%8f%8a%e5%85%b6%e5%8f%98%e7%a7%8d.md
- https://blog.csdn.net/qq_52181283/article/details/125013666
- https://www.cnblogs.com/chenying99/p/4596707.html
- https://52opencourse.com/187/idf%E9%80%86%E6%96%87%E6%A1%A3%E9%A2%91%E7%8E%87%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E7%94%A8log