全文内容概要:
-
TF-IDF都能做什么
-
TF-IDF的数学原理
-
TF-IDF在《民法典》中的python实战
一、TF-IDF都能做什么
TF-IDF(Term Frequency-Inverse Document Frequency)是一种用于信息检索与文本挖掘的常用加权技术,用于评估一个词对于一个文档集或语料库中的一份文档的重要程度。
- 信息检索与搜索引擎优化
它的核心功能是通过计算词语的TF-IDF值,搜索引擎能更精准地排序搜索结果,将相关性高的文档优先展示。广泛地应用于网页排名;
并且能够匹配用户查询关键词与文档内容。它的优势在于降低高频但无意义词的权重,突出关键主题词。
- 文本分类与聚类
所谓文本分类是指将文档归类到预定义的类别(如新闻分类、垃圾邮件过滤),例如可以通过TF-IDF向量化文本,训练分类模型(如SVM支持向量机、朴素贝叶斯,关于此部分涉及算法AI的内容后续文章讲解);
文本聚类主要用于从海量无标签文本中自动识别潜在主题或话题、对文档库自动分类,提升检索效率、个性化推荐和舆情监控等,而TF-IDF作为主要工具之一发挥着巨大的作用。
- 关键词提取与摘要生成
关键词提取:自动识别文档中的核心词汇。采用方法:选取TF-IDF值最高的词作为关键词(适用于单文档或语料库)。摘要生成:结合句子中词的TF-IDF值权重,抽取重要句子形成摘要。
- 推荐系统
内容推荐:分析用户浏览或搜索的文本内容(如商品描述、文章),计算TF-IDF相似度以推荐相关内容。如:新闻推荐、电商商品推荐。还可以用于协同过滤补充,TF-IDF可提供基于内容的初始推荐依据。
- 自然语言处理(NLP)预处理
将文本转化为数值特征(TF-IDF矩阵),供机器学习模型使用。用于降维与优化,可结合PCA主成分分析或LDA线性判别分析(这部分内容将在后续算法文章中进一步讲解)进一步压缩特征维度。作为词嵌入(如Word2Vec)的补充或替代方案。
虽然TF-IDF仅考虑词频和文档频率,忽略语义和上下文关系(需结合BERT等现代模型弥补)。但是它依然在日志分析、专利检索、学术论文查重甚至是NLP、AI领域发挥着举足轻重的作用。
二、TF-IDF的数学原理
以下内容可能很枯燥,我尽量用通俗的方式来讲解,实在提不起兴趣的也可以看个大概,不必细究
1. 基本公式
TF-IDF由两部分组成:
词频(TF - Term Frequency) :衡量一个词在某一个文档中出现的频率
其中 是词 t在文档 d中的出现次数,分母为文档总词数,其计算结果是体现词在某一个文档中的重要性
逆文档频率(IDF - Inverse Document Frequency) :衡量一个词在整个文档集合中的普遍重要性
其中 N是语料库中文档总数,分母为包含词 t的文档数,其计算结果是降低常见词的重要性,提高稀有词的权重。
最终计算公式TF-IDF:在单个文档中出现频率高的词更重要,在文档集合中出现频率低的词更有区分度
其中t:词项(term)、d:单个文档、D:文档集合(语料库)
2. 变体
TF变体
布尔频率(Boolean Frequency) :目的仅关注是否出现,忽略重复次数
对数缩放(Logarithmic Scaling) :目的抑制高频词绝对数值的影响
增强归一化(Augmented Normalization) :目的平衡长文档与短文档的词频差异
IDF变体
平滑IDF(Add-1 Smoothing) :避免未登录词({d}=0)的除零错误,适合动态更新语料库,对小规模的语料库更加稳定。
概率IDF(Probabilistic IDF) :强化稀有词权重(如医学术语“细胞瘤”在病例中的区分度),需要强惩罚高频词的领域(如垃圾邮件过滤)
最大IDF(Max IDF) :消除语料库规模影响,以最高文档频率为基准,适用于跨数据集对比(如合并多来源新闻)
双对数IDF(Double Log IDF) :进一步压缩高频词权重,降低极端值影响,应用于高维稀疏特征,如N-gram(后续文章详述),适合社交媒体短文本(如话题挖掘)
基于信息熵的IDF(Information Entropy-based) :基于信息熵的IDF通过分布熵与文档频率的双重约束,更精准量化词的重要性,尤其适合需要细粒度权重调整的场景(如学术文献挖掘、垂直领域搜索)
专业术语提取:低熵高IDF词(如“量子纠缠”)比均匀分布的高频词(如“研究”)权重更高。
垃圾内容检测:广告词(集中出现在少数文档)会被强化识别。
长尾关键词挖掘:小众但分布集中的词(如方言词汇)获得合理权重。
词t的文档分布熵
N 语料库总文档数
包含词t的文档数
平衡系数(0.5-1.0)
其中熵的计算公式是
:包含t的文档数
:词t在文档d中的频次TF值
:词t在语料库的总频次
熵值H(t) 越高,表明词t的分布越均匀,反之则越集中。同标准IDF相比,关键词区分度除了依赖于稀有性之外,还依赖于分布集中性。并且抗停用词干扰能力远高于标准IDF。
对应几种IDF变体,对比如下
| 变体 | 核心调整 | 推荐场景 |
|---|---|---|
| 标准IDF | 原始对数比 | 通用文本分类、搜索引擎 |
| 平滑IDF | 避免除零错误 | 小规模/动态语料库 |
| 概率IDF | 强惩罚高频词 | 垃圾内容检测、异常词挖掘 |
| 最大IDF | 动态基准调整 | 增量学习、流数据处理 |
| 双对数IDF | 压缩权重范围 | 高维特征(如生物医学文本) |
| 信息熵IDF | 考虑词分布均匀性 | 关键词提取、主题建模 |
三、 《民法典》的python实战
中华人民共和国民法典共分七编,我们每一编形成一个word文件,统一放在一个文件夹内,我们的需求是计算出词对应的TF-IDF值,为后续的法条智能检索系统做准备。相应文件可以到下面网盘下载。
(pan.baidu.com/s/1Xo0uVC1n… 提取码: rock)
1. 实现步骤
需要做的步骤如下:
(1)读入每一个word文件
《中华人民共和国民法典》共七编,我们每一编形成一个word文件,构成了我们要使用的语料库:
-
第一编总则.docx
-
第二编物权.docx
-
第三编合同.docx
-
第四编人格权.docx
-
第五编婚姻家庭.docx
-
第六编继承.docx
-
第七编侵权责任.docx
(2)文本清洗预处理
所谓文本清洗就是对语料库中进行必要的清理工作,对于中文文档,清理过程包括:保留中文汉字,去除标点、空格、换行、特殊符号、非汉字字符等等
(3)分词
由于信息检索及文本挖掘等基本对象是词,需要把语料库里的句子拆分成一个个的词汇。对于英文文章的分词简单,词之间是以空格隔开的,但是中文就麻烦了,需要合理的分词技术和手段
(4)去重
对于TF-IDF的应用场景来讲,分析的事一个个的词汇个体,因此每一篇文章的词要去除重复
(5)过滤停用词
有些词汇对应用目的来讲是无意义的,例如(序数词、的、了、从、给、他等等),这些词是不需要计算的,要排除掉
(6)计算TF、IDF、TF-IDF
按前述计算公式及变体计算每一个词汇的TF、IDF、TF-IDF值
2. 文档预处理
读入每一个word文件
import os
from docx import Document
def read_docx(file_path):
"""
读取 Word 文档,并返回一个字符串,该字符串包含文档中的所有内容
args:
file_path: Word 文档的路径
return:
str: 文档中的段落内容用空格连接起来,返回一个字符串
"""
try:
doc = Document(file_path)
return " ".join([para.text for para in doc.paragraphs])
except KeyError:
print(f"警告:无法读取文件 {file_path},可能是损坏的 Word 文件。")
return ""
def load_documents_from_folder(folder_path):
"""
从文件夹加载所有docx文档
args:
folder_path: 文件夹路径
return:
dict: 键为文件名,值为文档内容
"""
dict_documents = {}
for filename in os.listdir(folder_path):
if filename.endswith(".docx"):
file_path = os.path.join(folder_path, filename)
dict_documents[filename] = read_docx(file_path)
return dict_documents
调用
def main():
# 设置docx文件所在文件夹路径
folder_path = "E:\\training\\projects\\jupyter\\tfidf\\docx"
dict_docs = load_documents_from_folder(folder_path)
print(f"已加载 {len(dict_docs)} 个文档")
for filename, doc in dict_docs.items():
print(f"文档: {filename}-有字符数: {len(doc)}")
文本清洗预处理
import re
def preprocess_text(text):
"""
预处理文本:移除标点符号和特殊字符,保留字母、数字、和中文字符
args:
text: 输入的文本
return:
list: 处理后的单词列表
"""
return re.sub(r"[^\w\u4e00-\u9fff]", "", text)
这里使用了正则表达式来把匹配上的文本替换成空字符
调用
def main():
.............................
for filename, doc in dict_docs.items():
print(f"文档: {filename}-有字符数: {len(doc)}")
processed_docs = {
filename: preprocess_text(doc) for filename, doc in dict_docs.items()
}
for filename, doc in processed_docs.items():
print(f"预处理后的文档: {filename}-有字符数: {len(doc)}")
分词
import jieba
def cut_word(text):
"""
分词:使用 jieba 精确模式分词
args:
text: 输入的文本
return:
list: 分词后的单词列表
"""
return list(jieba.cut(text, cut_all=False))
这里使用了“结巴”分词库,可选的中文分词还有PKUSEG北大分词、FudanNLP复旦分词和多种专业领域的分词工具;
另外要注意的是jieba.cut方法返回的是个生成器Generator,如果对结果要多次访问的话,要转成List
调用
def main():
.............................
for filename, doc in processed_docs.items():
print(f"预处理后的文档: {filename}-有字符数: {len(doc)}")
cuted_docs = {filename: cut_word(doc) for filename, doc in processed_docs.items()}
for filename, doc in cuted_docs.items():
print(f"{filename}-分出来{len(doc)}个词")
去重
def deduplication(words):
"""
去重:使用 set 集合去重
args:
words: 输入的单词列表
return:
list: 去重后的单词列表
"""
return list(set(word for word in words))
去重的原理是把元素词汇放在set内,天然去重了
过滤停用词
def filter_stop_words(words):
"""
过滤停用词:使用 合并的 chinese_stop_words.txt 文件进行过滤
args:
words: 输入的单词列表
return:
list: 过滤停用词后的单词列表
"""
try:
with open("chinese_stop_words.txt", "r", encoding="utf-8") as f:
chinese_stop_words = set(line.strip() for line in f if line.strip())
return [word for word in words if word not in chinese_stop_words]
except FileNotFoundError:
print("未找到中文停用词文件 chinese_stop_words.txt,将不使用中文停用词过滤")
return list()
这里的中文停用词文件是结合了百度停用词、武汉大学停用词、英文停用词合并而来的,你也可以根据需要自行添加停用词
以上python代码中充分使用了列表推导式、字典推导式和生成器表达式这些非常pythonic的语法功能,简化了代码。
3. 计算TF
原始公式TF
def tf_original(words, doc):
"""
计算 TF(词频),使用原始计算公式TF(t,d) = (词t在文档d中出现的次数) / (文档d中所有词的总数)
:param words: 去重、去停的词语列表
:param doc: 要计算的目标文档
:return: TF(词频)列表,列表中每一个元素是一个元组,元组中第一个元素是词语,第二个元素是 TF(词频)
"""
word_tf_list = []
counter = Counter(doc)
word_count = len(doc)
for word in words:
word_tf_list.append((word, counter[word] / word_count))
return word_tf_list
调用
def main():
.............................
for filename, words in filtered_words.items():
print(f"{filename}-去停用词后有{len(words)}个词")
for filename, words in filtered_words.items():
word_tf_list = tf_original(words, cuted_docs[filename])
word_tf_list.sort(key=lambda x: x[1], reverse=True)
print(f"{filename}-tf值最大的10个词:")
print(
"\n".join(
f"tf前10个词:{word},tf值:{tf:.6f}" for word, tf in word_tf_list[:10]
)
)
运行结果如下
| 第一编总则.docx-tf值最大的10个词:tf前10个词:权利,tf值:0.001193tf前10个词:职责,tf值:0.001193tf前10个词:合法权益,tf值:0.001193tf前10个词:核心,tf值:0.001193tf前10个词:申请,tf值:0.001193tf前10个词:显失,tf值:0.001193tf前10个词:损害,tf值:0.001193tf前10个词:收养,tf值:0.001193tf前10个词:还,tf值:0.001193tf前10个词:健康,tf值:0.001193第七编侵权责任.docx-tf值最大的10个词:tf前10个词:职责,tf值:0.001931tf前10个词:多人,tf值:0.001931tf前10个词:合法权益,tf值:0.001931tf前10个词:权利,tf值:0.001931tf前10个词:水平,tf值:0.001931tf前10个词:损害,tf值:0.001931tf前10个词:还,tf值:0.001931tf前10个词:健康,tf值:0.001931tf前10个词:消毒,tf值:0.001931tf前10个词:社会,tf值:0.001931第三编合同.docx-tf值最大的10个词:tf前10个词:权利,tf值:0.000659tf前10个词:合法权益,tf值:0.000659tf前10个词:封存,tf值:0.000659tf前10个词:收件人,tf值:0.000659tf前10个词:悬赏,tf值:0.000659tf前10个词:之日起,tf值:0.000659tf前10个词:转让权,tf值:0.000659tf前10个词:社会,tf值:0.000659tf前10个词:疫情,tf值:0.000659 | 第五编婚姻家庭.docx-tf值最大的10个词:tf前10个词:权利,tf值:0.001953tf前10个词:所负,tf值:0.001953tf前10个词:医治,tf值:0.001953tf前10个词:合法权益,tf值:0.001953tf前10个词:职责,tf值:0.001953tf前10个词:申请,tf值:0.001953tf前10个词:消失,tf值:0.001953tf前10个词:相差,tf值:0.001953tf前10个词:吸毒,tf值:0.001953tf前10个词:日常生活,tf值:0.001953第六编继承.docx-tf值最大的10个词:tf前10个词:权利,tf值:0.003067tf前10个词:职责,tf值:0.003067tf前10个词:申请,tf值:0.003067tf前10个词:损害,tf值:0.003067tf前10个词:书写,tf值:0.003067tf前10个词:正当理由,tf值:0.003067tf前10个词:指定,tf值:0.003067tf前10个词:二为,tf值:0.003067tf前10个词:民政部门,tf值:0.003067tf前10个词:养,tf值:0.003067第四编人格权.docx-tf值最大的10个词:tf前10个词:行踪,tf值:0.002119tf前10个词:权利,tf值:0.002119tf前10个词:科学研究,tf值:0.002119tf前10个词:合法权益,tf值:0.002119tf前10个词:申请,tf值:0.002119tf前10个词:损害,tf值:0.002119tf前10个词:健康,tf值:0.002119tf前10个词:社会,tf值:0.002119tf前10个词:肖像权,tf值:0.002119tf前10个词:身心健康,tf值:0.002119 |
|---|---|
标准的TF计算公式优点:简单直观,保留原始统计信息。缺点:对长文档偏向性大(长文档词频天然更高),需配合其他变体来优化。
对数缩放TF
def tf_logarithmic_scaling(words, doc):
"""
计算 TF(词频),使用对数计算公式tf(t,d) = log(1 + f(t,d))
:param words: 去重、去停的词语列表
:param doc: 要计算的目标文档
:return: TF(词频)列表,列表中每一个元素是一个元组,元组中第一个元素是词语,第二个元素是 TF(词频)
"""
word_tf_list = []
counter = Counter(doc)
word_count = len(doc)
for word in words:
word_tf_list.append((word, math.log(1 + counter[word] / word_count)))
return word_tf_list
原始计数保留原始频次,可能放大长文档偏差,而对数缩放压缩增长趋势,削弱绝对优势。
应用场景与效果差异
(1) 长文档 vs 短文档
原始计数:
长文档问题:若某词在长文档中出现100次,短文档出现5次,直接计数会导致长文档权重过高(如 100100 vs 55)。
适用场景:需精确量化词频的任务(如词云生成、热点词统计)。
对数缩放:
平滑效果:将100次和5次分别映射为 log(101)≈4.62 和 log(6)≈1.79,差异从95缩小到2.83。
适用场景:文本分类、信息检索等需平衡长短文档权重的任务。
(2) 高频词敏感性
原始计数:
对停用词(如“的”“是”)敏感,可能因高频但无意义的词干扰模型。
对数缩放:
通过对数压缩,减少高频停用词的绝对影响(如100次→4.62,10次→2.40),保留相对重要性。
(3) 计算效率
原始计数:计算更快(无需对数运算),适合实时系统。
对数缩放:需额外计算,但现代硬件下开销可忽略。
实际案例对比
案例1:新闻分类
文档A(体育新闻):"比赛"出现50次,"进球"出现20次。
文档B(科技新闻):"算法"出现10次,"数据"出现15次。
TF值计算:
| 词 | 原始计数(A/B) | 对数缩放(A/B) |
|---|---|---|
| 比赛 | 50 / 0 | 3.93 / 0 |
| 进球 | 20 / 0 | 3.04 / 0 |
| 算法 | 0 / 10 | 0 / 2.40 |
| 数据 | 0 / 15 | 0 / 2.77 |
分析:
原始计数下,体育新闻因高频词主导,可能掩盖科技新闻特征;对数缩放后,权重差异更合理。
案例2:搜索引擎排序
文档1:"Python"出现30次,"教程"出现5次。
文档2:"Python"出现10次,"教程"出现10次。
TF值对比:
| 词 | 原始计数(文档1/2) | 对数缩放(文档1/2) |
|---|---|---|
| Python | 3/1 | 3.43 / 2.40 |
| 教程 | 1/2 | 1.79 / 2.40 |
原始计数:文档1因"Python"高频排名更高,但可能内容冗余。
对数缩放:文档2因"教程"权重提升,更符合用户意图(均衡需求)。
选择建议
| 选择对数缩放 | 选择原始计数 |
|---|---|
| 需平衡长短文档权重 | 需保留绝对词频信息(如词频统计) |
| 高频词可能干扰模型(如停用词) | 任务依赖精确频次(如热点分析) |
| 文本分类/聚类等需标准化场景 | 实时系统要求极低计算延迟 |
增强归一化 TF
增强归一化通过文档内相对词频压缩和最低权重保护,解决了原始计数的长度偏向性和低频词忽略问题,更适合公平性要求高的场景
def tf_augmented_normalization(words, doc):
"""
计算 TF(词频),使用增强的归一化公式tf(t,d) = 0.5 + 0.5 × (f(t,d)/max{f(w,d):w∈d})
:param words: 去重、去停的词语列表
:param doc: 要计算的目标文档
:return: TF(词频)列表,列表中每一个元素是一个元组,元组中第一个元素是词语,第二个元素是 TF(词频)
"""
word_tf_list = tf_original(words, doc)
max_tf = max([tf for _, tf in word_tf_list])
return [(word, 0.5 + 0.5 * (tf / max_tf)) for word, tf in word_tf_list]
应用场景与效果差异
(1) 长文档与短文档的公平性
原始计数:
长文档中高频词的绝对优势明显(如某词出现100次 vs 短文档的5次),导致长文档主导排序。
适用场景:需要绝对频次统计的任务(如词频热点分析)。
增强归一化:
将词频除以文档内最大词频,消除文档长度差异(如100次→1.0,5次→0.5),再通过线性变换保留最低权重。
适用场景:跨文档比较(如搜索引擎结果排序)、长短文本混合的数据集。
(2) 高频词与低频词的平衡
原始计数:
停用词(如“的”)或超高频主题词可能垄断权重。
增强归一化:
最高频词权重为1,其他词按比例分配,且最低0.5,避免低频词被完全忽略。
(3) 抗噪声能力
增强归一化对以下场景更鲁棒:
重复刷关键词的垃圾文本(如“优惠 优惠 优惠...”会被压缩到1.0)。
短文本中偶然出现的低频词(至少保留0.5权重)。
实际案例对比
案例1:电商评论分析
文本:
评论A(长):"好 好 好 好 好 好 好 好 好 好"(“好”出现10次,其他词各1次)。
评论B(短):"好 差"(“好”“差”各1次)。
| 方法 | 评论A的“好” | 评论A的其他词 | 评论B的“好” | 评论B的“差” |
|---|---|---|---|---|
| 原始计数 | 10 | 1 | 1 | 1 |
| 增强归一化 | 1 | 0.55 | 1 | 1 |
原始计数下,评论A因刷词垄断权重;增强归一化后,评论B的“差”未被淹没,更适合情感分析。
案例2:学术论文检索
查询词:"深度学习 模型"
论文1:"深度学习"出现20次(最大词频),"模型"出现10次。
论文2:"深度学习"出现5次(最大词频),"模型"出现4次。
| 词 | 原始计数(论文1/2) | 增强归一化(论文1/2) |
|---|---|---|
| 深度学习 | 4/1 | 1.0 / 1.0 |
| 模型 | 5/2 | 0.75 / 0.9 |
增强归一化更突出论文2中“模型”的相对重要性,避免论文1仅因篇幅长排名靠前。
选择建议
| 选择增强归一化 | 选择原始计数 |
|---|---|
| 需消除文档长度差异的跨文档比较 | 依赖绝对频次(如词频统计报告) |
| 保护低频词信号(如短文本、稀疏特征) | 实时系统要求极简计算 |
| 抗刷词干扰的场景(如评论、社交媒体) | 长文档内部词频分析(如主题建模) |