机器学习入门:TF-IDF
前言:本文是“机器学习入门”系列的第十站。前几篇我们学习了各种分类和聚类算法,但你会发现一个问题——这些算法处理的都是数值型数据。如果我们面对的是文本数据(如新闻、评论、邮件),该怎么办?计算机不认识文字,它只认识数字。本篇我们将学习如何把文本变成数字——这就是TF-IDF的作用。它是文本挖掘中最经典、最基础的特征提取方法,能将一篇文章“翻译”成一个数值向量,让后续的机器学习模型能够理解和处理文本数据。
目录
- 一、认识 TF-IDF
- 二、TF-IDF 的核心原理
- 三、TF-IDF 的优缺点
- 四、典型应用场景
- 五、核心 API 速查
- 六、实战案例:红楼梦各回关键词提取
- 七、总结
一、认识 TF-IDF
1.1 什么是 TF-IDF?
TF-IDF(Term Frequency-Inverse Document Frequency,词频-逆文档频率)是一种文本特征提取技术。它的作用是将一段文本转换成一个数值向量,同时评估每个词对这篇文档的重要性。
通俗理解:想象你在读一篇关于“机器学习”的文章。如果“算法”这个词出现了很多次,它可能很重要——这是词频。但如果“的”、“是”这类词也出现很多次,它们其实没什么用。TF-IDF 会惩罚这些太常见的词,提升真正有区分度的词的权重——这是逆文档频率。
TF-IDF 用于特征提取,是连接“原始文本”和“机器学习模型”的桥梁。
1.2 为什么需要 TF-IDF?
计算机不认识文字,只认识数字。如果我们要用机器学习处理文本(如垃圾邮件分类、新闻分类、情感分析),第一步就是把文本变成数字向量。
最简单的想法是直接统计每个词在文档中出现的次数——出现次数越多,就认为这个词越重要。但这种方法有一个问题:像“的”、“是”、“在”这类词几乎每篇文档都有,频率很高,但对区分文档主题毫无帮助。
TF-IDF 的巧妙之处在于:它会自动降低那些在很多文档中都出现的词的权重,提升那些只在少数文档中出现的词的权重。这样,真正能代表文档主题的关键词就被突显出来了。
二、TF-IDF 的核心原理
2.1 词频(TF,Term Frequency)
词频指的是某一个给定的词语在该文件中出现的次数。这个数字通常会被归一化(一般是词频除以文章总词数),以防止它偏向长的文件。
词频 ( T F ) = 某个词在文章中的出现次数 文章的总词数 \text{词频}(TF) = \frac{\text{某个词在文章中的出现次数}}{\text{文章的总词数}}词频(TF)=文章的总词数某个词在文章中的出现次数
直观理解:一个词在文档中出现次数越多,它在这篇文档中就越“重要”。
但仅靠 TF 是不够的——像 “this”、“is”、“the” 这类词虽然出现频繁,但没有任何区分度。
2.2 逆文档频率(IDF,Inverse Document Frequency)
IDF 的核心思想是:如果包含词条 t 的文档越少,IDF 越大,则说明词条具有很好的类别区分能力。
逆文档频率 ( I D F ) = log ( 语料库的文档总数 包含该词的文档数 + 1 ) \text{逆文档频率}(IDF) = \log\left(\frac{\text{语料库的文档总数}}{\text{包含该词的文档数} + 1}\right)逆文档频率(IDF)=log(包含该词的文档数+1语料库的文档总数)
| IDF 值 | 含义 |
|---|---|
| 高 IDF | 该词出现在很少的文档中 → 稀有、有区分度 →重要 |
| 低 IDF | 该词出现在很多文档中 → 常见、区分性弱 →不重要 |
2.3 TF-IDF
因此,TF-IDF 倾向于过滤掉常见的词语,保留重要的词语。
T F − I D F = 词频 ( T F ) × 逆文档频率 ( I D F ) TF-IDF = \text{词频}(TF) \times \text{逆文档频率}(IDF)TF−IDF=词频(TF)×逆文档频率(IDF)
总结:TF-IDF 值越高,说明该词在当前文档中既频繁出现,又具有区分度——它就是这篇文档的关键词。
2.4 计算示例
假设有以下 6 篇文档:
| 文档编号 | 内容 |
|---|---|
| D1 | This is the first document |
| D2 | This document is the second document |
| D3 | And this is the third one |
| D4 | Is this the first document |
| D5 | This line has several words |
| D6 | This is the final document |
计算单词 “first” 在 D1 中的 TF-IDF:
Step 1:计算 TF
D1 总词数为 5,“first” 出现 1 次 → TF = 1/5 = 0.2
Step 2:计算 IDF
总文档数 N = 6,包含 “first” 的文档为 D1、D4(共 2 篇)→ IDF = log(6/(2+1)) = log(2) ≈ 0.301
Step 3:计算 TF-IDF
TF-IDF = 0.2 × 0.301 ≈ 0.060
再对比单词 “document” 在 D1 中的 TF-IDF:
- TF = 1/5 = 0.2(同样出现 1 次)
- IDF = log(6/(4+1)) = log(1.2) ≈ 0.079(在 D1、D2、D4、D6 中出现,共 4 篇)
- TF-IDF = 0.2 × 0.079 ≈ 0.016
再对比单词 “this” 在 D1 中的 TF-IDF:
- TF = 1/5 = 0.2(同样出现 1 次)
- IDF = log(6/(6+1)) = log(0.857) ≈ -0.067(出现在所有 6 篇文档中,无区分度,IDF 接近 0)
- TF-IDF = 0.2 × (-0.067) ≈ -0.013
结果对比:
| 单词 | TF | 出现文档数 | IDF | TF-IDF | 说明 |
|---|---|---|---|---|---|
| “first” | 0.2 | 2 篇 | 0.301 | 0.060 | 只在少数文档出现,区分度强,权重最高 |
| “document” | 0.2 | 4 篇 | 0.079 | 0.016 | 在多篇文档出现,区分度中等,权重中等 |
| “this” | 0.2 | 6 篇 | -0.067 | -0.013 | 在所有文档出现,无区分度,权重为负 |
这个示例说明:仅仅出现频率高并不代表重要,只有在特定文档中频繁出现且在其他文档中少见的词,TF-IDF 才会赋予高权重。TF-IDF 正是通过这种方式,过滤掉常见的词语,保留重要的词语。
三、TF-IDF 的优缺点
3.1 优点
- 简单高效:计算速度快,易于理解实现。
- 自动降权通用词:能自动降低“的”、“是”等高频无意义词的权重,突出关键词。
- 无需训练:不依赖任何模型,直接基于统计计算。
- 可解释性强:每个特征的权重都有明确含义。
3.2 缺点
- 忽略词序和语义:“我打你”和“你打我”的 TF-IDF 向量可能相同,但意思完全相反。
- 维度高:词汇量通常很大,导致特征向量非常稀疏。
- 丢失上下文信息:无法理解同义词和一词多义。
四、典型应用场景
- 文本分类:将新闻、邮件、评论自动分类。
- 信息检索:搜索引擎判断文档与查询词的相关性。
- 关键词提取:自动提取一篇文章的核心关键词。
- 文本聚类:将相似主题的文档归为一组。
- 垃圾邮件过滤:判断邮件是否为垃圾邮件。
五、核心 API 速查
5.1 导包方式
fromsklearn.feature_extraction.textimportTfidfVectorizer5.2 核心参数详解
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
max_features | int / None | None | 最大特征数,保留最重要的 N 个词,用于限制词汇量、降维 |
stop_words | str / list | None | 停用词,'english'使用内置停用词表,或自定义列表 |
ngram_range | tuple | (1, 1) | 词组合范围,(1, 2) 表示同时考虑单个词和相邻双词组合 |
max_df | float / int | 1.0 | 忽略在超过此比例的文档中出现的词,用于过滤通用词 |
min_df | float / int | 1 | 忽略在少于此数量的文档中出现的词,用于过滤太生僻的词 |
use_idf | bool | True | 是否使用 IDF 加权 |
smooth_idf | bool | True | 平滑 IDF 计算,避免除零 |
sublinear_tf | bool | False | 使用亚线性 TF 缩放 log(1+TF) |
5.3 常用属性
| 属性名 | 说明 |
|---|---|
get_feature_names_out() | 返回所有特征词(词汇表) |
vocabulary_ | 词到索引的映射字典 |
idf_ | 每个特征的 IDF 值 |
5.4 常用方法
| 方法名 | 说明 |
|---|---|
fit_transform(X) | 训练并返回 TF-IDF 矩阵 |
transform(X) | 将新文档转换为 TF-IDF 向量(用已训练的参数) |
六、实战案例:红楼梦各回关键词提取
6.1 案例背景
《红楼梦》作为中国古典文学的巅峰之作,全书共120回,每一回都有其独特的情节重点和核心人物。本案例利用 TF-IDF 对《红楼梦》每一回的内容进行关键词提取,帮助我们快速了解每一回的核心内容,为文学分析提供量化视角。
6.2 数据说明
| 数据 | 说明 |
|---|---|
| 分卷目录 | 每回一个文本文件,共120个文件 |
| 每回内容 | 包含回目名称和正文内容 |
| 红楼梦词库 | 自定义词典,确保人名、地名等专有名词被正确识别 |
| 停用词表 | 过滤“的”、“了”、“是”等无意义词 |
6.3 完整代码
importosimportjiebaimportpandasaspdfromsklearn.feature_extraction.textimportTfidfVectorizer# ===================读取红楼梦分卷数据=========================filePaths=[]# 存储文件路径fileContents=[]# 存储文件内容forroot,dirs,filesinos.walk(r'.\红楼梦\分卷'):fornameinfiles:filePath=os.path.join(root,name)filePaths.append(filePath)withopen(filePath,'r',encoding='utf-8')asf:lines=f.read().splitlines()content=''.join(lines[2:])# 跳过前两行回目信息content=content.replace(' ','').replace('\t','')fileContents.append(content)corpos=pd.DataFrame({'filePath':filePaths,'fileContent':fileContents})print(f"共加载{len(corpos)}回内容")# ===================加载自定义词库与停用词=========================jieba.load_userdict(r'.\红楼梦\红楼梦词库.txt')# 加载红楼梦专属词库stopwords=pd.read_csv(r'.\红楼梦\StopwordsCN.txt',encoding='utf-8',engine='python',index_col=False)# ===================分词处理=========================corpos['cut_words']=''withopen(r'.\红楼梦\分词后汇总.txt','w',encoding='utf-8')asfile_to_jieba:forindex,rowincorpos.iterrows():juan_ci=''fileContent=row['fileContent']segs=jieba.cut(fileContent)# jieba分词forseginsegs:ifsegnotinstopwords.stopword.valuesandlen(seg.strip())>0:juan_ci+=seg+' 'file_to_jieba.write(juan_ci+'\n')corpos.loc[index,'cut_words']=juan_ci.strip()print("分词完成")# ===================读取分词结果=========================withopen(r'.\红楼梦\分词后汇总.txt','r',encoding='utf-8')asf:corpus=f.readlines()# ===================TFIDF向量化=========================vectorizer=TfidfVectorizer()tfidf=vectorizer.fit_transform(corpus)# 训练并转换为TFIDF矩阵wordlist=vectorizer.get_feature_names_out()# 获取词汇表df=pd.DataFrame(tfidf.T.todense(),index=wordlist)# 转为DataFrameprint(f"词汇表大小:{len(wordlist)}")print(f"TFIDF矩阵形状:{tfidf.shape}")# ===================各回提取Top10关键词=========================print("\n===== 各回 Top 10 关键词 =====")foriinrange(len(corpus)):featurelist=df.iloc[:,i].to_list()# 获取第i回所有词的TFIDF值resdict={}forjinrange(len(wordlist)):resdict[wordlist[j]]=featurelist[j]# 构建词-权重字典resdict=sorted(resdict.items(),key=lambdax:x[1],reverse=True)# 按权重降序排序print(f"\n第{i+1}回")forword,scoreinresdict[:10]:# 取前10个关键词print(f"{word}:{score:.4f}")输出示例: 共加载 120 回内容 Building prefix dict from the default dictionary ... Loading model from cache C:\Users\Lenovo\AppData\Local\Temp\jieba.cache Loading model cost 0.983 seconds. Prefix dict has been built successfully. 分词完成 词汇表大小: 40260 TFIDF矩阵形状: (120, 40260) ===== 各回 Top 10 关键词 ===== 第1回 士隐: 0.4481 雨村: 0.1591 弟子: 0.1399 道人: 0.1270 那僧: 0.1224 英莲: 0.1117 那僧道: 0.1049 一段: 0.0979 封肃: 0.0931 空空道人: 0.0931 ...... 第120回 贾母: 0.2430 宝玉: 0.2287 牛黄: 0.2101 贾政道: 0.1893 薛姨妈: 0.1632 巧姐儿: 0.1625 凤姐: 0.1574 老太太: 0.1331 破题: 0.1313 贾政: 0.12176.4 关键步骤说明
| 步骤 | 说明 |
|---|---|
| 数据加载 | 遍历分卷目录,读取每回内容,跳过回目信息行 |
| 自定义词典 | 加载红楼梦专属词库,确保人名、地名被正确分词 |
| 停用词过滤 | 删除“的”、“了”、“是”等1000+个无意义词 |
| 分词处理 | 使用 jieba 分词,结果写入文件并存入 DataFrame |
| TF-IDF 提取 | 将分词后的文本转为 TF-IDF 向量,提取每回 Top 10 关键词 |
七、总结
核心知识点速查
| 知识点 | 关键概念 |
|---|---|
| TF-IDF | 文本特征提取方法,将文本转换为数值向量 |
| TF(词频) | 词在当前文档中的出现频率 |
| IDF(逆文档频率) | 惩罚高频通用词,奖励稀有区分词 |
| TfidfVectorizer | sklearn 实现 TF-IDF 的工具类 |
| max_features | 限制最大特征数,控制维度 |
| ngram_range | 考虑单词组合,捕获短语信息 |
核心 API 一览
| 用途 | 对应模块 / 方法 |
|---|---|
| 模型 | sklearn.feature_extraction.text.TfidfVectorizer |
| 训练并转换 | fit_transform(X) |
| 转换新文档 | transform(X) |
| 特征词列表 | get_feature_names_out() |
| 词汇表 | vocabulary_ |
注意事项
| 要点 | 说明 |
|---|---|
| 中文需先分词 | 中文没有空格分隔,需使用 jieba 等分词工具 |
| 停用词过滤 | 必须去除“的”、“是”等无意义词,否则干扰结果 |
| 特征维度控制 | 用max_features或max_df/min_df控制维度 |
| 训练集/测试集分离 | fit_transform用于训练集,transform用于测试集 |
系列直达
- 上篇:机器学习入门:DBSCAN 聚类
- 本篇:机器学习入门:TF-IDF(本文)
- 下篇:敬请期待