TF-IDF算法详解与实践总结

590 阅读8分钟

作为自然语言处理(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,词频) ,某个词条在文中出现的次数。

词频(TF=某个词在文档中出现的次数文档的总词数词频(TF)=\frac{某个词在文档中出现的次数}{文档的总词数}

IDF(Inverse Document Frequency,逆向文档频率) ,一个词在文档集合中的逆文档频率。罕见词得分高,常见词得分低。衡量词语在文档集合中的稀有性。

逆文档频率(IDF=log(语料库的文档总数包含该词的文档数+1)逆文档频率(IDF)=log(\frac{语料库的文档总数}{包含该词的文档数+1})

如果只用词频来表达一个词对一篇文档的重要程度显然是不合适的,因为在一篇文档里面,可能出现频率很高的词是一些没有意义的词语,真正能代表这篇文档的关键词可能只出很少的次数。

如果有太多文档都涵盖了某个单词,这个单词也就越不重要,或者说是这个单词就越没有信息量

因此,我们需要对 TF 的值进行修正,而 IDF 的想法是用 DF (文档频率)的倒数来进行修正。倒数的应用正好表达了这样的思想,DF 值越大越不重要。

结合 TF-IDF

文档中某个术语的 TF-IDF 分数是通过将 TF 和 IDF 分数相乘来获得。

TFIDF(t,d,D)=TF(t,d)×IDF(t,D)TF-IDF(t,d,D)=TF(t,d)×IDF(t,D)

步骤

  1. 预处理数据:清洗数据、规范化数据、词形还原、去除停用词等
  2. 词频分词
  3. 计算TF
  4. 计算IDF
  5. 向量化词汇

优缺点

  • 优点:易于计算

  • 缺点:

    • 不能捕捉词在文档中的位置,无法获取语义。
    • 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 = corpus
         self.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] += 1
         return df
 ​
     def _compute_idf(self):
         """计算逆文档频率 (IDF)"""
         idf = defaultdict(float)
         for token, count in self.df.items():
             idf[token] = math.log(self.N / (count + 1)) + 1  # 平滑处理
         return idf
 ​
     def compute_tf(self, tokens):
         """计算词频 (TF)"""
         tf = defaultdict(int)
         total = len(tokens)
         for token in tokens:
             tf[token] += 1 / total  # 归一化
         return tf
 ​
     def 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要用乘法)?

  • TF×IDFTF \times IDF 的乘积可以提高重要性较高的词的权重,同时降低重要性较低的词的权重,从而更好地区分不同的词语。

    文档中的停用词(如“的”,“地”,“了”)词频很高,文档集中含停用词的数量约等于总的文档集数量,即Nn1Nn恒大于1\frac{N}{n} \simeq 1(\frac{N}{n}恒大于1)。在只使用TF的情况下,停用词所占的权重很大,但是它并没有区分能力,与我们的期望不符。使用IDF之后,由于log(1)=0,则停用词通过TF-IDF计算出的权重就为0(根号达不到这样的效果),就可以消除很多停用词的影响。。

  • 涉及概率,用乘法,简单有效。

结语

TF-IDF作为信息检索与文本挖掘领域的经典算法,凭借其简单直观的逻辑和强大的实用性,成为从海量文本中提取价值的“黄金法则”。 然而,它并非万能钥匙——面对复杂的语义关系和动态变化的文本数据,TF-IDF的局限性也逐渐显现(例如无法捕捉词序、上下文关联等)。

关于我

RESOURCES