Python:基于词频、TF-IDF 生成词云图
现有一份介绍某地点的 txt 文件,需要编写 Python 程序制作介绍文档的词云图。
读取数据
数据预处理
文本中可能存在着许多特殊符号,这些符号中不蕴含有效信息,且会影响分词效果,所以需要去除。对于空格、换行、制表符等停顿的符号,也需要统一换成中文逗号。由于本次处理的是中文文本,所以文本的某些无意义的英文字母同样也要进行去除。部分评论文本中还存在着部分网站,图片链接,日期,该部分内容穿插在文本中,影响文本处理。
编写 data_process() 函数进行数据预处理,包括删除各种不想要的字符和英文日期等功能,主要是基于正则表达式实现的。其中 str_data 待预处理的字符串,返回值类型为 string,是预处理后的单个句子。
import re
def data_process(str_data):
# 去除换行、空格、网址、参考文献
data_after_process = re.sub(r'\n+', '', str_data)
data_after_process = re.sub(r'\s+', '', data_after_process)
data_after_process = re.sub(r'[a-zA-z]+://[^\s]*', '', data_after_process)
data_after_process = re.sub(r'\[([^\[\]]+)\]', '', data_after_process)
# 删除日期:YY/MM/DD YY-MM-DD YY年MM月DD日
data_after_process = re.sub(r'\d{4}-\d{1,2}-\d{1,2}','',data_after_process)
data_after_process = re.sub(r'\d{4}[-/]\d{2}[-/]\d{2}', '', data_after_process)
# 删除标点符号
punctuation = """"!!??#$%&'()()*+-/:;<=>@[\]^_`●{|}~⦅⦆「」、、〃》「」『』【】〔〕〖〗〘〙〚〛*°▽〜〝〞〟〰〾〿–—‘'‛“”„‟…‧﹏"""
re_punctuation = "[{}]+".format(punctuation)
data_after_process = re.sub(re_punctuation, '', data_after_process)
return data_after_process
读取文件
例如将厦门的百度百科词条保存在 txt 文件中,需要将文件内容读入内存。
读取 txt 文件的数据后进行数据预处理,接着以句号划分句子,返回由句子组成的 list。编写 getText() 函数,参数 filename 为读取的 txt 文件,返回值类型为 list 为划分且预处理过后的句子列表。
def getText(filename):
fp = open(r'./' + filename, 'r', encoding='utf-8')
sentences = fp.readlines()
fp.close()
for i in range(len(sentences)):
sentences[i] = data_process(sentences[i])
sentences = " ".join(sentences).split('。')
return sentences
词频统计
jieba 分词库
分词是自然语言处理的重要步骤,英文以空格作为天然的分割符,利用空格进行分割就能简单的提取出较有完整含义的单词。而现代汉语中,一条语句中没有任何的分割符,只有标点符号用于进行语句的划分,无法简单的提取出单个具有完整含义的词。而如果按标点符号进行划分,句子中承载的信息太多,很难复用分析。词的长度不像句子那么长,但是承载的信息量也比字多,且能够表达完整含义,故以词为单位进行分割是最合适的。
Jieba 分词器属于概率语言模型分词,基于前缀词典实现高效的词图扫描,生成句子中汉字所有可能成词情况构建成有向无环图,然后采用动态规划寻找最大概率路径,找出基于词频的最大切分组合。对于不存在与前缀词典中的词,采用了汉字成词能力的 HMM 模型,使用了 Viterbi 算法。Jieba 的切分模式有全模式、精确模式、搜索引擎模式,更多详细信息可以查看 github 仓库。
函数编写
编写 getWordFrequency() 函数对传入的句子列表统计词频,其中参数 sentences 为存储多个句子的列表,应当已经过预处理,返回值 words_dict 是以 dict 存储的词频。使用 psg.cut() 函数分词之后转换为 list,然后使用 dict 对词进行计数即可。
import jieba.posseg as psg
def getWordFrequency(sentences):
words_dict = {}
for text in sentences:
# 去掉标点
text = re.sub("[\s+\.\!\/_,$%^*(+\"\']+|[+——!,。?、~@#¥%……&*()]+", "",text)
# 结巴分词
wordGen = psg.cut(text)
# 结巴分词的输出结果为一个生成器,把生成器转换为 list
for word, attr in wordGen:
if attr in ['n', 'nr', 'nz']:
if word in words_dict.keys():
words_dict[word] += 1
else:
words_dict[word] = 1
return words_dict
TF-IDF 方法
TF-IDF
TF-IDF 是一种用于信息检索与数据挖掘的常用加权技术,可以评估一个单词对于语料库的重要程度,并给出合适的权重。
其中TF 为词频,为某一单词在语料库中出现的次数,设单词 Ti、在文本 Dj 总次数为 ni、j 和 K 表示该文本单词的个数,计算公式为:
IDF 是逆文档频率,用于描述单词在语料库中的普遍重要性,设 |D| 表示整个语料库,|{j:ti∈dj}| 表示包含单词 ti,在所有文本中的总出现次数,计算公式为:
TF-IDF 可以给出特征词的权重,公式为如下。利用 TF-IDF 核心思想是字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。一个词语在一篇文章中出现次数越多,同时在所有文档中出现次数越少,越能够代表该文章,越能与其它文章区分开来。
函数编写
编写 getTFIDF() 函数对传入的句子列表计算 TF-IDF,其中 sentences 为存储多个句子的列表,应当已经过预处理,返回的 words_dict 是以 dict 存储的 TF-IDF。计算 TF-IDF 时可以将每个句子当做一篇小短文,然后使用 jieba 进行分词,使用 sklearn 的 TfidfTransformer 和 CountVectorizer 进行计算得出。
CountVectorizer 是一个特征数值计算类,能将文本中的词语转换为词频矩阵,通过 fit_transform 函数计算各个词语出现的次数。TfidfTransformer 可以根据输入的词频输出它们的 TF-IDF,更多介绍可以看文末的参考资料。
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_extraction.text import CountVectorizer
def getTFIDF(sentences):
corpus = []
for text in sentences:
# 去掉标点
text = re.sub("[\s+\.\!\/_,$%^*(+\"\']+|[+——!,。?、~@#¥%……&*()]+", "",text)
# 结巴分词
wordGen = psg.cut(text)
# 结巴分词的输出结果为一个生成器,把生成器转换为list
cut_list = []
for word, attr in wordGen:
if attr in ['n', 'nr', 'nz']:
cut_list.append(word)
corpus.append(" ".join(cut_list))
words_dict = {}
# 将文本中的词语转换为词频矩阵,矩阵元素a[i][j] 表示j词在i类文本下的词频
vectorizer = CountVectorizer()
# 计算每个词语的tf-idf权值
transformer = TfidfTransformer()
# 将文本转为词频矩阵
matrix = vectorizer.fit_transform(corpus)
# 计算tf-idf
tfidf = transformer.fit_transform(matrix)
# 获取词袋模型中的所有词语
word = vectorizer.get_feature_names()
# 将tf-idf矩阵抽取出来,元素a[i][j]表示j词在i类文本中的tf-idf权重
weight = tfidf.toarray()
# 打印每类文本的tf-idf词语权重,第一个for遍历所有文本,第二个for遍历某一类文本下的词语权重
for i in range(len(weight)):
for j in range(len(word)):
if word[j] in words_dict:
words_dict[word[j]] += weight[i][j]
else:
words_dict[word[j]] = weight[i][j]
return words_dict
生成词云图
wordCloud 库
Python 可以使用 wordCloud库生成词云图,安装的 pip 命令如下:
pip install wordcloud
wordCloud 库的特点是可以填充所有可用空间、能够使用任意掩码、简单易用,主要的 API 如下:
函数 | 功能 |
---|---|
WordCloud([font_path, width, height, ...]) | 用于生成和绘制的词云对象 |
ImageColorGenerator(image[, default_color]) | 基于彩色图像的颜色生成器 |
random_color_func([word,font_size,...]) | 生成随机色调的颜色 |
get_single_color_func(color) | 函数返回单个色调和饱和度 |
函数编写
首先需要先选择一张用于生成词云图的图片,ImageColorGenerator 将根据图片的形状和颜色进行填充,例如选择皮卡丘的图片。
基于 wordCloud 库编写 showCloud() 函数,对传入的词频生成词云图。其中传入的参数 wordDict 是以 dict 格式存储的词频,filename 是生成的图片的文件名。需要先用 WordCloud() 函数根据词频生成词云图对象,然后用 ImageColorGenerator() 提供颜色和形状,最后保存为图片。
from PIL import Image, ImageSequence
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from wordcloud import WordCloud, ImageColorGenerator
def showCloud(wordDict, filename):
# 根据图片创建 graph 为 nd-array 类型
image = Image.open("./pikachu.jpg")
graph = np.array(image)
# 创建 wordcloud 对象,背景图片为 graph,背景色为白色
wc = WordCloud(mask = graph, background_color = 'white', font_path='STXINWEI.TTF')
# 生成词云
wc.generate_from_frequencies(wordDict)
# 根据 graph 生成颜色
image_color = ImageColorGenerator(graph)
plt.imshow(wc.recolor(color_func=image_color)) #对词云重新着色
plt.axis('off')
# 显示词云图,并保存为 jpg 文件
#plt.show()
wc.to_file(filename + ".jpg")
plt.clf()
词云图生成效果
调用上述函数:
showCloud(getWordFrequency(getTest('厦门.txt')), "xiamen")
showCloud(getTFIDF(getTest('厦门.txt')), "xiamen_tfidf")
基于词频的词云图效果如下:
基于 TF-IDF 的词云图,可以明显地看出基于 TF-IDF 生成的词云图更能凸显出文本的关键信息。
参考资料
github jieba
WordCloud for Python documentation
sklearn——CountVectorizer详解
TfidfVectorizer、CountVectorizer 和TfidfTransformer