☰
Python词云7案例:中文分词、蒙版取色与TF-IDF权重实战
2026/10/28 5:49:16 网站建设 项目流程

简介:《python实现词云完整教程7个案例.pdf》面向文本挖掘与数据可视化初学者及中级Python用户,以7个递进案例讲解词云从生成到定制的完整流程。内容涵盖文本预处理、停用词过滤、词频与权重计算、图像展示与保存,并从最简调用延伸到中文分词、蒙版形状、自定义颜色和关键词权重等进阶用法,每例配有代码与运行结果。资源包为1个PDF文件,约2.07MB,便于集中查阅并按案例动手实践;其中还注明PyCharm、Python 3.7、wordcloud 1.9.3和matplotlib 3.5.3环境,降低复现门槛。已有108人学习,可用于项目报告、社交媒体分析、教学演示等场景,也为后续文本分析提供可参考的脚本思路。

1. 一份语料,一张图,三天交付期

课程设计开题两周,手里攒了几万字的调研问答、访谈记录或者爬下来的短评,导师要的是“文本数据可视化”,可这些数据没有坐标轴,能交上去的只有一个字符图——这就是 Python 词云最典型的落地面。这套 7 个案例的 Python 课程资源把 wordcloud + matplotlib + jieba 这条链路从最简调用一路推到蒙版形状、蒙版取色、自定义颜色函数和关键词权重提取,每一段代码换掉语料就能跑。它适合的不是想了解词云是什么的人,而是要在短时间内交出可运行代码、可解释图表、可答辩说明的那批人:课程设计的关键词章节、毕设的可视化部分、部门汇报首页的那张关键词分布图,都在这套流程的覆盖范围内。

2. 技术选型与流水线设计:三个库的分工和一份参数矩阵

词云这块可选的东西不少,pyecharts 能做交互式词云、stylecloud 一行代码出图、前端也有现成的词云组件,最后落到这套方案却只用了 wordcloud、matplotlib、jieba 三个库,原因很实际:只有 wordcloud 同时把“蒙版形状”“按蒙版图取色”“自定义取色函数”“外部权重喂入”这四个能力开在原生的 API 上。交互式词云在网页里好看,但形状只支持矩形,做不出案例 04 往后那些东西;stylecloud 本质是 wordcloud 的封装,参数被藏进一个函数签名的字符串里,答辩时被问“这个字号是怎么定出来的”很容易答不上来。用原生库多写几行,换来的是每个参数都能解释、每次改动都能追溯。

三个库的职责边界很清楚,想清楚这一点,后面 7 个案例就不会看串。wordcloud 负责“算布局、定字号、上颜色、出位图”,它是唯一直接产出图像对象的角色;matplotlib 只负责把已经生成好的图像对象显示出来或者存盘,它不参与词云的任何计算;jieba 负责在 wordcloud 之前把中文长串切成可统计的词,并在案例 07 里额外提供 TF-IDF 权重。这层分工意味着:图丑了先查 wordcloud 的参数,中文字变方块先查字体,词的位置稀稀拉拉先查分词结果。落到排查上,比一堆库混写省事得多。

2.1 依赖版本与工程目录

这套案例给的环境版本是 Python 3.7、wordcloud 1.9.3、matplotlib 3.5.3。版本号在词云这种场景下不是摆设,wordcloud 1.9.x 对 mask 的灰度判定和早期版本有差别,matplotlib 3.5 之后plt.axis()的简写行为也调整过,跨版本复现同一张图经常会差一点。安装时把版本钉死,别让pip install wordcloud一路升到最新。

# 按教程给定的版本锁定,避免跨版本渲染差异 pip install wordcloud==1.9.3 pip install matplotlib==3.5.3 # jieba 做中文分词,Pillow 和 numpy 供蒙版读图与数组运算使用 pip install jieba pillow numpy

前两条锁版本,后三个不锁,因为它们在这套流程里只用到最基础的能力,跨版本没有行为差异。装完立刻验证一次,比写代码时报ModuleNotFoundError再去回滚省时间:

# 环境自检:导入即验证版本,跑不通就不是代码问题 import wordcloud, matplotlib, jieba, numpy, PIL print(wordcloud.__version__, matplotlib.__version__)

目录结构也要提前定,7 个案例共用一套语料、字体和蒙版图,文件位置一旦散掉,font_path和mask的相对路径就会成为第一批翻车点。我一般会按下面的清单摆:

路径作用是否必需
data/novel.txt中文语料,UTF-8 编码必需
fonts/chinese.ttf支持中文的 TTF/OTF 字体文件中文必需
assets/mask_black.png白底黑图的黑白蒙版案例 04 起必需
assets/mask_color.png白底彩图的彩色蒙版案例 05 起必需
out/输出 PNG 的目录建议保留
case01.py~case07.py七个独立可执行脚本必需

注意这里没有把七个案例塞进一个文件。每个案例独立成脚本,是为了让你在改动某一步时能立刻和原始效果对照——调试词云最怕的就是把分词、蒙版、取色三步混在一起改,出了偏差不知道是哪一步带来的。

2.2 词云生成的四个阶段

把 7 个案例抽象一下,它们其实是同一条流水线的四次递进:读文本、切词并算权重、生成布局、渲染输出。案例 01 只做了第一、三、四阶段,案例 03 补上第二阶段,案例 04 起开始改第三阶段的初始条件(给蒙版),案例 05、06 改第四阶段的颜色,案例 07 把第二阶段的权重来源从词频换成 TF-IDF。理解了这条线,七个案例就不是七个孤立脚本,而是一个模块的七次参数替换。

下面这个骨架是这套流水线的函数化版本,后面所有案例都可以往里填:

# -*- coding: utf-8 -*- import os import numpy as np from PIL import Image from wordcloud import WordCloud, ImageColorGenerator import matplotlib.pyplot as plt import jieba def build_text(path, use_jieba=True): """阶段一 + 阶段二:读文本并切成空格分隔的词串""" with open(path, 'r', encoding='utf-8') as f: raw = f.read() # 显式指定 utf-8,避免平台默认编码差异 if use_jieba: return ' '.join(jieba.cut(raw)) # wordcloud 按空格切词,中文必须先补空格 return raw def build_mask(path): """读蒙版图并转成数组;白底区域不落字,非白区域落字""" if not os.path.exists(path): return None # 蒙版缺失时直接退化为矩形画布 return np.array(Image.open(path)) def render(text, mask=None, color_func=None, out='out/wordcloud.png'): """阶段三 + 阶段四:生成、显示、保存""" wc = WordCloud( font_path='fonts/chinese.ttf', # 中文必须指定字体,默认字体不含汉字 width=800, height=600, mode='RGBA', background_color=None, # 透明背景,方便贴进 PPT mask=mask, max_words=300, random_state=42 # 固定随机种子,保证每次布局一致 ).generate(text if color_func is None else text) if color_func is not None: wc.recolor(color_func=color_func) # 颜色在布局完成之后再改 plt.imshow(wc, interpolation='bilinear') plt.axis('off') plt.show() wc.to_file(out) # 用 to_file 存盘,不要用 plt.savefig return wc

这里有几个参数值得单独说。encoding='utf-8'必须在open里写明,Windows 环境下默认编码不一定是 utf-8,语料里一旦出现非 ASCII 字符就会抛UnicodeDecodeError,而且报错位置常常在很靠后的一行,排查起来很费神。background_color=None和mode='RGBA'是一对,只写其中一个拿不到透明底,输出还是带背景色的方块图。random_state给了固定值之后,同一份语料、同一组参数每次出的图完全一致,这一点在需要反复调参的场景下几乎是后悔药——没有它,你改了字体大小之后发现图变丑了,却分不清是参数的问题还是随机布局撞出来的。

2.3 参数矩阵:哪些要动,哪些不要碰

WordCloud 的构造参数有二十来个,实际用得上的就那么十来个,剩下的多数情况下保持默认反而更稳。把常用参数和它们的取值规律摆成一张表,调参的时候按图索骥比翻文档快:

参数作用典型取值
font_path指定字体文件路径fonts/chinese.ttf,中文必填
width/height无蒙版时的画布尺寸800 × 600 起,太小会装不下
mask蒙版数组,决定词云的形状np.array(Image.open(...))
mode色彩模式需要透明底时用'RGBA'
background_color背景色None表示透明,默认是黑色
max_words最多显示多少个词200 ~ 500,按语料规模调
max_font_size最大字号上限不设则由画布高度决定
min_font_size最小字号下限默认 4,蒙版太小时适当调大
color_func自定义取色函数签名固定为六参数
random_state布局随机种子任意整数,固定即复现
prefer_horizontal水平词占比0 ~ 1,默认 0.9
relative_scaling词频与字号的关联强度默认 0.5,要突出头部词就调高

max_words和max_font_size是一对互相牵制的参数。词太多、字号上限又大,布局算法在画布里排不下就会不停重试,最终要么丢掉一部分词,要么直接把图撑变形。蒙版越精细(比如人物剪影这种细长形状),可绘制面积越小,这两个值就要一起往下压。relative_scaling则影响“第一名”和“第十名”的视觉差距,默认 0.5 会让排名靠后的词也保持相当的字号,想让头部的几个关键词一眼跳出来,把它提到 0.7 以上。

3. 基础案例实现:从最简调用到中文分词的三级台阶

前三个案例是一条向上的台阶,每一级都在补上一级缺的东西。案例 01 验证“这套库能不能跑”,案例 02 暴露“中文为什么不能直接跑”,案例 03 解决“中文怎么切”。跳过任何一级,后面的蒙版和取色都会在没有地基的情况下堆参数,出了问题只能靠猜。我见过不少人一上来就抄案例 05 的代码,结果图里全是方块,然后怀疑是蒙版有问题——其实是第一级就没走通。

3.1 案例 01:最小闭环和它的默认值边界

案例 01 只有十来行,意义在于把“读文本、生成、显示、保存”四步串成一条通路,跑出来就说明环境和库都没问题。这段代码几乎是这套方案的最小可运行单元:

# -*- coding: utf-8 -*- from wordcloud import WordCloud import matplotlib.pyplot as plt # 读取英文语料;英文单词自带空格,无需分词 with open('data/demo_text.txt', 'r', encoding='utf-8') as f: text = f.read() # 全部使用默认参数生成词云对象 wc = WordCloud().generate(text) # 显示:bilinear 插值让缩放后的边缘更平滑 plt.imshow(wc, interpolation='bilinear') plt.axis('off') plt.show() # 保存到文件,扩展名决定格式 wc.to_file('out/case01.png')

WordCloud()不带参数调用时,用的是 400 × 200 的画布、最多 200 个词、黑色背景、库自带的默认字体。这几个默认值决定了案例 01 的适用边界:英文语料可以,中文语料跑出来就是一片方块;小语料可以,几万字的语料会挤成一团;用来验证环境可以,用来交作业明显不够看。interpolation='bilinear'这个参数的收益在缩小显示时最明显,不用它,词与词的边缘会出现锯齿,导进 Word 里放大看很明显。

wc.to_file()和plt.savefig()的区别值得单独记一笔。前者把 wordcloud 内部生成的图像对象原样写出,后者截的是 matplotlib 的画布,如果savefig写在show之后,经常得到一张空白图——这是案例 01 到案例 07 全程都要守住的规矩:存盘走to_file,显示走imshow,两者的职责不要串。

3.2 案例 02:中文不分词,先看清字体和编码

案例 02 的价值不在“能出图”,而在“故意不做分词”,让你亲眼看看不切词的中文喂进 wordcloud 会发生什么。结果通常是两种:要么整段文本被当成一个超长字符串,画布中央出现一行小到看不清的字;要么在中途被某些字符切碎,出现一堆毫无意义的片段。这个反例是后面所有中文案例的对照组。

# -*- coding: utf-8 -*- from wordcloud import WordCloud import matplotlib.pyplot as plt # 显式 utf-8 读取,路径用相对工程根目录的写法 with open('data/novel.txt', 'r', encoding='utf-8') as f: text = f.read() wc = WordCloud( font_path='fonts/chinese.ttf', # 关键:换成含中文的字体文件 width=800, height=600, # 画布放大,避免长句挤成一团 mode='RGBA', # 透明底需要 RGBA 模式 background_color=None # 与上一条配对使用 ).generate(text) plt.imshow(wc, interpolation='bilinear') plt.axis('off') plt.show() wc.to_file('out/case02.png')

font_path是这一级的全部重点。wordcloud 默认加载的是库内自带的西文字体,字符集里没有汉字,遇到中文就退化成方块或者直接丢弃,图上是空的、或者一片豆腐块。路径写法上有个常见误解:相对路径是相对于“运行时的工作目录”,不是相对于脚本文件所在目录。用 PyCharm 直接运行脚本时工作目录通常是工程根目录,用命令行cd到别的目录再执行,同一个路径就失效了。稳妥的做法是拼绝对路径,或者在脚本开头用os.chdir()把工作目录钉死。

mode='RGBA'加上background_color=None得到的是透明底 PNG,这个细节在做汇报材料时能省掉一轮返工——不透明背景的词云贴到深色模板上会多出一个白色方块,剪裁也剪不干净。

3.3 案例 03:jieba 分词三种模式与词表清洗

中文词云的质量,八成取决于分词结果。jieba 提供三种模式,用法上的差别直接决定词云长什么样。精确模式按最合理的边界切分,适合做词频统计;全模式把所有可能的词都扫出来,速度快但会切出大量重叠片段;搜索引擎模式在精确模式的基础上对长词再切一刀,召回率高,做关键词云时更合适。

# -*- coding: utf-8 -*- import jieba import jieba.analyse from collections import Counter with open('data/novel.txt', 'r', encoding='utf-8') as f: text = f.read() # 精确模式:最常用,切分结果稳定 words = list(jieba.cut(text)) # 停用词与单字过滤:中文里单字词几乎没有信息量 stopwords = set(['的', '了', '是', '在', '和', '就', '不', '也', '有', '我']) cleaned = [w for w in words if len(w) > 1 and w not in stopwords and w.strip()] # 转成空格分隔的字符串,wordcloud 的 generate 靠空格切词 seg_text = ' '.join(cleaned) print(seg_text[:100]) # 先看前 100 字,确认切分没跑偏 # 顺带统计词频,方便核对最大字号对应的是哪个词 print(Counter(cleaned).most_common(20))

' '.join()这一行是中文词云的枢纽。wordcloud 的generate()内部按空格切分文本,中文原串里没有空格,不补分隔符就等于没分词。cleaned那一步的两个过滤条件也值得保留:单字过滤能去掉大量“的、了、是”这类高频虚词,长度阈值设成 2 已经很能改善观感;w.strip()用来清掉换行符和空格残留,否则会出现只占一个空格位的“词”。

想看切分有没有跑偏,最直接的办法是在生成词云之前先打印前 100 字和词频 Top 20。这一步花不了十秒钟,但能省掉后面反复调参数的时间:如果 Top 20 里全是虚词,说明停用词表不够;如果全是人名碎片,说明该考虑往 jieba 的自定义词典里加词了。

4. 进阶案例实现:蒙版形状、蒙版取色与关键词权重

案例 04 到案例 07 是这套方案真正拉开差距的部分。前三个案例做出来的图是矩形,能说明问题但不好看;这四个案例把图从“结果”变成“作品”,代价是要多理解两件事:蒙版的灰度规则和颜色的注入时机。这两件事一旦搞清楚,参数就是可解释的,改哪一项知道会往哪个方向变;搞不清楚,就只能靠反复试。

4.1 案例 04:蒙版的灰度规则与形状控制

蒙版的工作原理很朴素:把一张图片读成 numpy 数组,wordcloud 取它的灰度值作为“可绘制区域”的判定依据——纯白区域(灰度 255)不落字,越暗的区域越优先落字。所以做蒙版图的规则是“白底 + 深色图形”,黑色剪影是最省事的做法。反过来用黑底白图,得到的效果就是词只挤在一个小角落里,或者干脆空白。

# -*- coding: utf-8 -*- import numpy as np from PIL import Image from wordcloud import WordCloud import matplotlib.pyplot as plt import jieba with open('data/novel.txt', 'r', encoding='utf-8') as f: text = ' '.join(jieba.cut(f.read())) # 蒙版必须是白底深色图形,白色部分会被判定为不可绘制 mask = np.array(Image.open('assets/mask_black.png')) print(mask.shape) # 先确认形状:(H, W) 或 (H, W, 3)/(H, W, 4) wc = WordCloud( mask=mask, font_path='fonts/chinese.ttf', mode='RGBA', background_color=None, max_words=300, random_state=42 ).generate(text) plt.imshow(wc, interpolation='bilinear') plt.axis('off') plt.show() wc.to_file('out/case04.png')

mask.shape这个打印建议每次都留着。蒙版是单通道灰度图时形状是(H, W),彩色图是(H, W, 3),带透明通道的 PNG 是(H, W, 4)。后两种会被 wordcloud 内部转成灰度处理,但带 alpha 通道的图经常因为透明区域被当成“可绘制”而出现意外效果。稳妥的做法是提前用图像工具另存为不带透明通道的白底黑图,或者读进来之后手动转一次灰度。

画布尺寸这时会被蒙版的尺寸覆盖。案例 04 里再写width和height是无效的,实际画布就是蒙版图的像素尺寸。这也意味着蒙版图不能太小——一张 200 × 200 的蒙版放不下 300 个词,要么大量词被丢弃,要么字号被压到看不清。

4.2 案例 05:ImageColorGenerator 的取色逻辑

ImageColorGenerator做的事情是按每个词的落点坐标去蒙版图上采样,把该区域的 RGB 均值作为这个词的颜色。所以用彩色蒙版加上这个生成器,出来的词云颜色和原图是对应的:原图这里是红色,落在这个区域的词就是红色系。这个效果在人物、建筑、标志性图案上特别讨巧,也是这套案例里视觉收益最高的一步。

# -*- coding: utf-8 -*- import numpy as np from PIL import Image from wordcloud import WordCloud, ImageColorGenerator import matplotlib.pyplot as plt import jieba with open('data/novel.txt', 'r', encoding='utf-8') as f: text = ' '.join(jieba.cut(f.read())) # 彩色蒙版:既决定形状,也提供颜色来源 mask = np.array(Image.open('assets/mask_color.png')) wc = WordCloud( mask=mask, font_path='fonts/chinese.ttf', mode='RGBA', background_color=None, random_state=42 ).generate(text) # 必须在 generate 之后 recolor,顺序颠倒会不生效 image_colors = ImageColorGenerator(mask) wc.recolor(color_func=image_colors) plt.imshow(wc, interpolation='bilinear') plt.axis('off') plt.show() wc.to_file('out/case05.png')

顺序是这一级唯一的坑:recolor()必须写在generate()之后。写成链式调用WordCloud(...).generate(text).recolor(...)没问题,但反过来先设颜色再生成,颜色会被生成过程覆盖掉,图出来还是默认配色。另一个容易忽略的点是取色区域——如果蒙版图的边缘有大片纯白,落在这些位置的词采样到的是白色,在白底环境里等于隐形。所以做彩色蒙版时,图形本身要尽量占满画布,减少无效白边。

4.3 案例 06:自定义 color_func 与可控的随机

color_func是 wordcloud 留出来的完全自定义入口,签名固定为(word, font_size, position, orientation, font_path, random_state)六个参数,返回一个颜色字符串。案例 06 用 HSL 拼随机色,把饱和度和亮度限制在一个区间里,避免出现太淡看不清或者太黑糊成一团的颜色。

# -*- coding: utf-8 -*- import random import numpy as np from PIL import Image from wordcloud import WordCloud import matplotlib.pyplot as plt import jieba # 固定随机种子,保证每次跑出来的配色一致 random.seed(42) with open('data/novel.txt', 'r', encoding='utf-8') as f: text = ' '.join(jieba.cut(f.read())) def random_color(word, font_size, position, orientation, font_path, random_state): """六参数签名不可改,返回 hsl 颜色字符串""" h = random.randint(0, 360) # 色相全范围 s = random.randint(60, 80) # 饱和度限制在中高区间 l = random.randint(40, 70) # 亮度避开两端,保证可读性 return 'hsl(%d, %d%%, %d%%)' % (h, s, l) mask = np.array(Image.open('assets/mask_color.png')) wc = WordCloud( mask=mask, font_path='fonts/chinese.ttf', color_func=random_color, # 构造时直接传函数 mode='RGBA', background_color=None, random_state=42 ).generate(text) plt.imshow(wc, interpolation='bilinear') plt.axis('off') plt.show() wc.to_file('out/case06.png')

六个参数的顺序不能动,WordCloud 内部是按位置传参的。position和font_size在被调用时已经确定,所以可以基于它们做位置相关的配色,比如让画布上方的词偏冷、下方的词偏暖。实践中真正需要控制的是饱和度与亮度的区间:亮度低于 30 的颜色在投影仪上基本是一片黑,高于 85 的颜色在白底上就看不见了,卡在 40 到 70 之间最稳。

random.seed(42)这一行不是可选项。没有它,同一份代码每次跑出来的配色都不一样,你想微调某个参数时,没法判断看到的变化是参数带来的还是随机配色带来的。这类“玄学”问题在词云里特别常见,固定种子是最便宜的解法。

4.4 案例 07:关键词权重与外部频率

前六个案例的字号都来自词频,而词频高不等于重要——“我们”“可以”这类词在任何语料里词频都不低,但对主题贡献为零。案例 07 换成jieba.analyse.extract_tags提取 TF-IDF 权重,再用generate_from_frequencies直接把“词 → 权重”的字典喂给 wordcloud,绕过内部词频统计。

# -*- coding: utf-8 -*- import numpy as np from PIL import Image from wordcloud import WordCloud, ImageColorGenerator import matplotlib.pyplot as plt import jieba.analyse with open('data/novel.txt', 'r', encoding='utf-8') as f: text = f.read() # topK 控制候选词数量,withWeight=True 返回 (词, 权重) 元组列表 freq = jieba.analyse.extract_tags(text, topK=200, withWeight=True) print(freq[:20]) # 先看一眼权重排序是否合理 # generate_from_frequencies 要求字典形式,这里做一次结构转换 freq_dict = {w: float(v) for w, v in freq} mask = np.array(Image.open('assets/mask_color.png')) wc = WordCloud( mask=mask, font_path='fonts/chinese.ttf', mode='RGBA', background_color=None, random_state=42 ).generate_from_frequencies(freq_dict) # 取色仍然在生成之后注入 wc.recolor(color_func=ImageColorGenerator(mask)) plt.imshow(wc, interpolation='bilinear') plt.axis('off') plt.show() wc.to_file('out/case07.png')

generate_from_frequencies的意义在于把“权重从哪来”这个决定权交回给你。除了 TF-IDF,也可以换成 TextRank 的结果、自己按业务规则打的分、甚至是外部模型输出的概率值,只要组织成“词 → 浮点数”的字典就能直接渲染。这也让词云从“统计图”变成“结论图”:字号不再反映出现次数,而是反映你认为的重要性。

topK设成 200 是个经验值。设太小,图上的词不够铺满蒙版,留白很明显;设太大,靠后的词权重趋近于零,字号被压到最小也看不清,反而占掉了布局空间。可以先按 200 出一版,看蒙版填充度再上下调整。

5. 踩坑清单:这 7 个案例里最容易翻车的五个点

这套流程的代码量不大,但每一步都有各自的失败模式,而且很多失败不报错,只是图不对。下面五条是我在实际跑这套案例时反复遇到的,按“现象 → 原因 → 解决”记下来,出问题时可以直接对照。

5.1 中文与字体类故障

现象一:图里全是方块,或者只有几个英文字母,中文一个都不显示。原因九成是font_path没生效。要么路径写的是相对路径而运行时工作目录变了,要么指向的字体文件本身不含汉字(西文字体、图标字体都会出现这个问题)。解决方式是在构造之前先做一次断言,把问题挡在生成之前:

import os font = 'fonts/chinese.ttf' # 路径存在性检查,比看方块图猜原因快得多 assert os.path.exists(font), '字体文件不存在:%s' % font assert os.path.getsize(font) > 100 * 1024, '字体文件过小,可能不含中文字形'

字体文件大小是个粗略但有效的判断,含完整汉字集的 TTF 通常在几 MB 级别,几百 KB 的字体基本可以断定没有中文字形。另外,路径里含有中文目录时在部分环境下也会读不到,工程目录尽量全英文。

现象二:读文件时报UnicodeDecodeError,或者中文读进来变成乱码。原因是open()没指定encoding,走了系统默认编码;Windows 下默认可能是 GBK,而语料是 UTF-8。解决方式是所有读写中文文本的地方都显式写encoding='utf-8',包括写文件的场景。语料来源混杂时,可以先做一次编码探测再决定用哪种方式打开,别指望默认值。

5.2 蒙版与布局类故障

现象三:加了蒙版之后,词只挤在一个很小的区域,或者整张图几乎空白。这是蒙版反色造成的——用了黑底白图,wordcloud 把黑色区域判为不可绘制,只有白色图形部分能落字,可绘制面积一下子缩到图形本身。解决办法是把蒙版换成白底深色图形;如果手上只有黑底图,读进来之后反转一次灰度:

import numpy as np from PIL import Image mask = np.array(Image.open('assets/mask_raw.png').convert('L')) # 黑底白图转白底深色图:255 减去原灰度值 mask = 255 - mask

.convert('L')这一步很关键,带 alpha 通道的 PNG 直接取数组会得到四维数据,透明度区域会被误判成可绘制区,加上各种边缘模糊,最终形状会糊掉。

现象四:背景不透明,导出的图带一块白底或黑底。原因是mode和background_color没配对。只写background_color=None而保持默认的 RGB 模式,得到的是黑底图;只写mode='RGBA'而不改背景色,得到的还是不透明背景。两个参数必须同时给。判断是否真的透明,别靠肉眼,用代码验一下最直接:

from PIL import Image img = Image.open('out/case04.png') # 打印色彩模式与是否含透明通道 print(img.mode, 'A' in img.mode)

现象五:图上的词太大,只有寥寥几个,或者布局算法直接抛出“词排不下”的错误。原因是画布(或蒙版可绘制面积)和max_words、max_font_size的组合超出了容量。词云内部是按字号从大到小逐个尝试放置的,放不下就跳过,跳过的词多了就只剩几个大字。解决顺序是:先把蒙版放大到 800 像素以上,再把max_words降到 200 以内,最后用max_font_size给字号设个上限。prefer_horizontal调到 1 也能缓解,全水平排列的空间利用率比混合排列高。

5.3 输出与复现类故障

现象六:每次运行出来的图都不一样,或者保存下来的文件是空白的。两个独立原因。图不一样是因为random_state和color_func里的随机数都没固定,布局和配色都在变,微调参数时根本分不清是谁的影响。保存空白是因为用了plt.savefig()并且在plt.show()之后调用,此时画布已经被清空。解决方式是把random_state写死、颜色函数里加random.seed(),保存统一走wc.to_file(),savefig只在确实需要导出 matplotlib 画布时使用。

这五条覆盖了这套案例里绝大多数失败场景。判断故障归属有个简单办法:先用默认参数跑通案例 01,确认环境没问题;再逐步叠加字体、蒙版、取色,每次只加一步。哪一步加上去图变了,问题就在那一步,比对着七个脚本一起改要高效得多。

6. 验收与答辩:怎么证明这张图不是随手截的

词云这类可视化最容易在答辩上吃亏,因为看起来“谁都能做”。评委真正想确认的是三件事:这张图能不能复现、字号对应什么、结论站不站得住。把这三件事提前准备好,问答环节基本不会被动。

先做一次“换机复现”验证。把脚本、语料、字体、蒙版打包,在一台没装过这些库的机器上按requirements重新装一遍,从空目录跑出图。这一步能暴露掉大部分硬编码的绝对路径和版本差异——本地跑得好好的代码,换台机器就崩,通常就是路径和版本两个原因。验收清单我一般按下面这几项过:

验收项判定标准检查方式
环境可复现干净环境按版本装完能跑通新建虚拟环境重跑案例 01
中文正常无方块字、无乱码打印分词结果前 100 字
输出合规PNG 透明底、尺寸与蒙版一致读图打印mode和size
结果可复现连跑两次文件一致固定random_state后比对
参数可解释每个非默认参数能说出理由口头过一遍参数表

再说答辩。被问到“这张图说明了什么”,只说“关键词一目了然”是过不了关的,得往数据来源上引:语料多少字、用什么分词模式、停用词表怎么定的、字号对应的是词频还是 TF-IDF 权重。有一个问题几乎必被问到——“为什么最大的词是这个”,答案必须落在预处理规则上,而不是“算法算出来的”。如果最大的是个虚词,说明停用词表没做干净,这时候主动承认并给出改进方案,比硬解释强。

最后要能说清局限性。词云不表达词与词之间的关系,长词在布局里天然吃亏,蒙版形状精细时容易丢掉低频词,这些都是客观存在的。主动讲出来,再补一句“所以我在报告里配了词频表一起看”,反而显得方案是完整的。

从那以后我每次交这类图,都强制走一遍“换台机器重跑 + 记录随机种子 + 把参数表和结论写在同一页”的流程,血泪经验就是别指望临场解释。这套东西准备起来不超过半小时,能省掉答辩现场最难受的那十分钟。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询