🎯 学习目标
- 理解齐夫定律与词频分布,熟练用 Counter 做词频统计
- 手动推导 TF-IDF,真正理解 IDF 的直觉含义
- 掌握 jieba.analyse 的 TF-IDF 与 TextRank 关键词抽取
- 会用 wordcloud 生成中文词云,并解决字体/蒙版问题
- 配置 matplotlib 中文显示,绘制词频柱状图
- 完成"文章关键词 + 词频图 + 词云"完整分析案例
1词频与齐夫定律
词频(Term Frequency):词在文档中出现的次数。自然语言的词频有一个惊人规律——齐夫定律(Zipf's Law):少数词(的、了、是、在)出现极多次,绝大多数词只出现一两次。
排名 r 的词,其频次 f 大致满足:f × r ≈ 常数
第1高频词频次约为第2的2倍、第3的3倍……
长尾中大量"低频词"恰恰是区分文章主题的关键。
import jieba
from collections import Counter
text = ("数据分析 是 数据科学 的 基础,数据 分析 依赖 统计 方法,"
"好 的 数据 分析 报告 能 指导 业务 决策")
words = [w for w in jieba.lcut(text) if len(w) > 1]
freq = Counter(words)
freq.most_common(5) # 频次 Top5
freq["数据"] # 4 指定词频次
list(freq.elements()) # 展开
sum(freq.values()) # 总词数
# 文档频率 DF:有多少篇文档包含该词(需要多篇文档统计,见下文)
2为什么词频不够:停用词与 TF-IDF
直接用频次选关键词会被"的、了、是、我们"霸榜。停用词表是一种硬过滤,但治标不治本——"数据"在一篇数据类文章里出现 50 次很关键,可它在所有数据类文章里都高频,区分力并不强;而"碳中和"只在少数文章出现,一旦出现就极具区分度。
TF-IDF(词频-逆文档频率,1972 年提出,至今仍是搜索引擎与文本特征的基石)的核心直觉:
权重 = 在本文出现多(TF高) × 在别处很少见(IDF高)
一个词对某文档的重要性 = 它在该文档中出现的频率 × 它在整个文档集合中的稀有程度。
公式与手算
TF(t,d) = 词 t 在文档 d 中出现次数 / d 的总词数(或直接用次数)
IDF(t) = ln( 文档总数 N / (包含词 t 的文档数 DF + 1) ) + 1
(+1 平滑:防止词出现在所有文档中时分母问题、除零)
TF-IDF(t,d) = TF(t,d) × IDF(t)
手算例子:语料库共 1000 篇文章,其中 100 篇含"数据",只有 5 篇含"碳中和"。某篇共 200 词的文章中两词都出现 4 次:
import math
N = 1000
tf_data = tf_carbon = 4 / 200 # 0.02
idf_data = math.log(N / (100 + 1)) + 1 # 含"数据"的文档多 → IDF 小
idf_carbon = math.log(N / (5 + 1)) + 1 # 含"碳中和"的文档少 → IDF 大
print("数据 TF-IDF:", round(tf_data * idf_data, 4)) # 0.0463
print("碳中和 TF-IDF:", round(tf_carbon * idf_carbon, 4)) # 0.1082
# 同样出现 4 次,"碳中和"的权重是"数据"的两倍多!
IDF 的极端情形:所有文档都有的词 IDF≈0(类似停用词,权重自动被压低,无需手工词表);只在一篇出现的词 IDF 最大。注意:TF-IDF 不理解语义,"电脑"和"计算机"会被当成两个无关词——这正是第 7 章词向量要解决的问题。
3jieba 抽取关键词:TF-IDF
import jieba
import jieba.analyse
article = """人工智能正在深刻改变医疗行业。通过大语言模型,医生可以快速分析
电子病历,辅助诊断罕见疾病;计算机视觉技术能够识别CT影像中的早期肿瘤。
多家医院已经开始试点人工智能阅片系统,大幅提升了诊断效率和准确率。
未来,人工智能药物研发也将显著缩短新药上市周期。"""
# TF-IDF 关键词(jieba 内置约 35 万词的 IDF 语料库)
keywords = jieba.analyse.extract_tags(
article,
topK=8, # 返回前 K 个
withWeight=True, # 同时返回权重
allowPOS=() # 允许的词性,默认所有;如 allowPOS=("n","nr","ns","nt","nz","v","vn")
)
for word, weight in keywords:
print(f"{word}: {weight:.4f}")
# 人工智能 / 人工智能药物 / CT / 阅片 / 肿瘤 / 病历 / 罕见 / 影像
# 领域定制:用自己的语料生成 IDF 表,效果更贴合业务
# jieba.analyse.set_idf_path("my_idf.txt")
# my_idf.txt 格式:词语 IDF值,每行一个
# jieba.analyse.set_stop_words("my_stopwords.txt")
# 只用名词类实体词
kw = jieba.analyse.extract_tags(article, topK=10,
allowPOS=("n", "nr", "ns", "nt", "nz", "vn"))
4TextRank:不依赖语料库的图排序
TextRank 源自网页排名算法 PageRank:把词看成节点,共现(窗口内一起出现)的词之间连边;被越多"重要词"连接的词越重要,经过多轮投票迭代收敛得到排序。
构造:① 分词 → ② 取候选词(过滤词性/停用词)
③ 滑动窗口(默认 span=5)内共现则连无向边
④ 类似 PageRank 迭代计算每个词的得分 → 排序取 TopK
优点:单文档即可运行,不依赖外部 IDF 语料
缺点:计算量更大,短文本效果不稳
for word, weight in jieba.analyse.textrank(
article, topK=8, withWeight=True,
allowPOS=("n", "nr", "ns", "nt", "nz", "vn", "v")):
print(f"{word}: {weight:.4f}")
# 医生 / 诊断 / 医疗 / 系统 / 影像 / 识别 / 辅助 / 分析
# TF-IDF 与 TextRank 结果通常互补,工程上可以各取 TopK 做并集/加权融合
| TF-IDF | TextRank | |
|---|---|---|
| 依据 | 词在文档集的稀有度 | 词在本文的共现图结构 |
| 外部语料 | 需要 IDF 表 | 不需要 |
| 计算 | 快 | 迭代较慢 |
| 偏好 | 罕见专名、长尾词 | 语义中心词、连接枢纽词 |
5matplotlib 中文与词频图
import matplotlib
matplotlib.use("Agg") # 无界面环境保存图片用;交互式可省略
import matplotlib.pyplot as plt
from collections import Counter
import jieba
# 中文显示:指定系统中文字体(Windows 自带 SimHei / Microsoft YaHei)
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False # 正常显示负号
words = [w for w in jieba.lcut(article) if len(w) > 1]
top = Counter(words).most_common(10)
labels = [w for w, _ in top][::-1] # 倒序使最高频在顶部
counts = [c for _, c in top][::-1]
plt.figure(figsize=(8, 5))
plt.barh(labels, counts, color="#3776AB")
plt.xlabel("出现次数")
plt.title("高频词 Top10")
plt.tight_layout()
plt.savefig("freq_bar.png", dpi=150) # plt.show() 弹窗查看
6词云 wordcloud(中文必踩的坑)
from wordcloud import WordCloud
import jieba
text = "人工智能 人工智能 人工智能 大语言模型 机器学习 深度学习 " \
"自然语言处理 计算机视觉 数据分析 神经网络 智能推荐"
# ① 中文必须先分词,再用空格拼成字符串(WordCloud 默认按空格切)
words = " ".join(jieba.lcut(text))
# ② 必须指定中文字体路径,否则显示成方块/直接报错
# Windows:C:\Windows\Fonts\simhei.ttf 或 msyh.ttc
# Mac:/System/Library/Fonts/PingFang.ttc
# Linux:/usr/share/fonts/ 下放任意中文 ttf
wc = WordCloud(
font_path=r"C:\Windows\Fonts\simhei.ttf",
width=1000, height=600,
background_color="white",
max_words=100,
colormap="viridis", # 配色:viridis/plasma/Set2/RdYlBu...
collocations=False # 关闭二元词组搭配,避免重复词
)
# 方式A:直接吃"空格分隔的文本",内部统计
wc.generate(words)
wc.to_file("wordcloud.png")
# 方式B:直接传 {词: 权重} 字典(推荐!可直接用 TF-IDF 权重控制大小)
# 权重越大概率越高,词越大
import jieba.analyse
kw = dict(jieba.analyse.extract_tags(article, topK=80, withWeight=True))
wc2 = WordCloud(font_path=r"C:\Windows\Fonts\simhei.ttf",
background_color="white", width=1200, height=700)
wc2.generate_from_frequencies(kw)
wc2.to_file("wordcloud_tfidf.png")
中文词云三大坑:① 不指定 font_path 必乱码成方框(WordCloud 只自带英文 DroidSansMono);② 必须先 jieba 分词再空格拼接,否则整句被当作一个"词";③ 形状蒙版 PNG 需要白底,代码 from imageio import imread; mask=imread("mask.png") 后传 mask=mask。
形状蒙版与停用词
from imageio import imread
from wordcloud import WordCloud, STOPWORDS
mask = imread("cloud_mask.png") # 白色背景 + 实心形状
stop = set(STOPWORDS) | {"一个", "我们", "可以"} # 中英文停用词合并
wc = WordCloud(font_path=r"C:\Windows\Fonts\msyh.ttc",
mask=mask, background_color="white",
stopwords=stop, contour_width=2, contour_color="#3776AB")
wc.generate_from_frequencies(freq_dict)
wc.to_file("masked_wc.png")
7综合案例:一键文章分析报告
import jieba, jieba.analyse
from collections import Counter
from wordcloud import WordCloud
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
def analyze(article, out_prefix="report"):
# 分词
tokens = [w for w in jieba.lcut(article) if len(w.strip()) > 1]
# 关键词:TF-IDF + TextRank 融合
tfidf = dict(jieba.analyse.extract_tags(article, 20, withWeight=True))
tr = dict(jieba.analyse.textrank(
article, 20, withWeight=True,
allowPOS=("n", "nr", "ns", "nt", "nz", "vn", "v")))
print("【词频 Top5】", Counter(tokens).most_common(5))
print("【TF-IDF 关键词】", list(tfidf)[:8])
print("【TextRank 关键词】", list(tr)[:8])
# 词频柱状图
top = Counter(tokens).most_common(10)[::-1]
plt.figure(figsize=(8, 5))
plt.barh([w for w, _ in top], [c for _, c in top], color="#7c3aed")
plt.title("高频词 Top10"); plt.tight_layout()
plt.savefig(f"{out_prefix}_bar.png"); plt.close()
# 词云(用 TF-IDF 权重)
WordCloud(font_path=r"C:\Windows\Fonts\simhei.ttf",
width=1000, height=600, background_color="white"
).generate_from_frequencies(tfidf
).to_file(f"{out_prefix}_wc.png")
analyze(article)
print("报告图片已生成:report_bar.png / report_wc.png")
8本章小结与练习
📌 关键结论
- 权重思想:本文多 × 全局少 = TF-IDF;手算公式要能默写。
extract_tags快且偏长尾专名;textrank无需语料、偏语义中心。- 中文词云两个必须:先分词拼空格、指定中文字体路径。
- TF-IDF 不懂同义语义,下一章的词向量解决这个问题。