利用CountVectorizer词袋模型,如何对中文语料库词频统计?
发布于 作者:苏南大叔 来源:程序如此灵动~
CountVectorizer被称之为词袋模型,它来自于sklearn,日常用于词频统计。苏南大叔在之前也写过有关CountVectorizer词频统计的文章。不过,现在对CountVectorizer又有了新的理解,所以,这里再次写文章总结对CountVectorizer新的理解。

苏南大叔的“程序如此灵动”技术博客,记录苏南大叔的代码经验总结。本文测试环境:win10,python@3.11.0,scikit-learn@1.2.2。
语料库分词
如果是英文这种以空格做单词分割的语种,是不需要做这一步分词的处理的。但是,如果是中文这种没有天然空格做分割的语料库,还是需要使用:结巴分词/snownlp/其它分词工具,进行分词处理的。分词结果可以使用空格或者英文逗号或中文逗号进行分割。
参考文章:
下面的代码中,语料库变量是苏南大叔手工分词的结果:
corpus = [
"老许 是 一只 黑色 猫咪",
"虎子,是,一只,黑色,狗子",
"哈吉米,是,一只,黑色,猫咪",
"老许 非常 可爱",
"虎子,同样,非常,可爱",
"哈吉米,也,很,可爱",
] # 语料库,空格或者中英文逗号分割,都是可以的。初始化词袋模型
下面出场的是本文的主角CountVectorizer词袋模型,它有很多参数。不过,大多数都暂时用不到。可以直接初始化:
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer() # 词袋模型最常用的就是一个stop_words,停用词列表,可以过滤掉一些无用的词儿,以便于更加精准的了解数据。比如:
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer(stop_words=["一只", "非常"]) # 词袋模型然而,经过实际运行会发现:一些单字的词儿(比如语料库中的“是/也/很”),即使没有在stop_words中设置,也会被过滤掉。那么,如果您介意的话,可以通过下面的参数,把这些单字的中文词儿找回来。这个正则表达式,对其它一些非中文语种可能不是很合适。所以,视需求进行修改即可。
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer(token_pattern="[\u4e00-\u9fa5_a-zA-Z0-9]{1,}") # 词袋模型填充数据进行转换
cv_fit = cv.fit_transform(corpus) # (0, 6) 1 (0, 0) 1或者下面这样调用,也是可以的,两者等效。
cv.fit(corpus)
cv_fit = cv.transform(corpus) # (0, 6) 1 (0, 0) 1特征及词汇表
print(cv.get_feature_names_out(), len(cv.get_feature_names_out()))
# 模型默认认为:单字的词儿,无法构成feature特征
# ['一只' '可爱' '同样' '哈吉米' '狗子' '猫咪' '老许' '虎子' '非常' '黑色'] 10
print(cv.vocabulary_, len(cv.vocabulary_))
# 词汇表就是:{特征:索引}
# {'老许': 6, '一只': 0, '黑色': 9, '猫咪': 5, '虎子': 7, '狗子': 4, '哈吉米': 3, '非常': 8, '可爱': 1, '同样': 2} 10从结果可以看到,这个结果是自动过滤了那些单字的词儿(token)的。所以,可能的原因是:
- 设置了停用词
stop_words,包含了目标词汇。 - 没有设置
token_pattern,没有把目标词汇包含进来。
print(cv.get_stop_words())stop_words是自己设置的,为啥还要再求解一次呢?没有设置的话,返回None,也就是大家通常上理解的Null。
cv_fit 格式及类型
print(cv_fit,type(cv_fit)) # (0, 6) 1 (0, 0) 1 <class 'scipy.sparse._csr.csr_matrix'>输出:
(0, 6) 1
(0, 0) 1
(0, 9) 1
(0, 5) 1
(1, 0) 1
(1, 9) 1
(1, 7) 1
(1, 4) 1
(2, 0) 1
(2, 9) 1
(2, 5) 1
(2, 3) 1
(3, 6) 1
(3, 8) 1
(3, 1) 1
(4, 7) 1
(4, 8) 1
(4, 1) 1
(4, 2) 1
(5, 3) 1
(5, 1) 1 <class 'scipy.sparse._csr.csr_matrix'>对于cv_fit的输出的正确理解,还是需要结合词汇表cv.vocabulary_的结果的。词汇表cv.vocabulary_输出是:
{'老许': 6, '一只': 0, '黑色': 9, '猫咪': 5, '虎子': 7, '狗子': 4, '哈吉米': 3, '非常': 8, '可爱': 1, '同样': 2}- 其格式是:
(语料库的id,词汇表的索引) 当前语料库id里面,该词汇出现了几次。 cv_fit类型是一个没见过的新类型:<class 'scipy.sparse._csr.csr_matrix'>。
例如,cv_fit的条目(2, 0) 1:
2表示语料库索引为2的语料(就是第三条语料),"哈吉米,是,一只,黑色,猫咪"。0根据词汇表可以知道:是“一只”。1,就是表示“一只”这个token在"哈吉米,是,一只,黑色,猫咪"语料中,出现了一次。
cv_fit.toarray()
下面的就是苏南大叔自己写的分析代码了,主要功能就是对官方函数输出的结果,再组合解释了一下。cv_fit.toarray()是每条语料化身为词汇表索引的结果,对应索引位置上显示计数值。
print(cv_fit.toarray()) # [[1 0 0 0 0 1 1 0 0 1] [1 0 0 0 1 0 0 1 0 1]][1 0 0 0 0 1 1 0 0 1]翻译回来的预料是:一只,,,,,猫咪,老许,,,黑色。1表示:对应的词儿是出现的词儿出现了一次。0表述没有出现。
所以,它可能另外的表现形式是:[2,0,3,5,6,1]这样其它的数字组合。
print(cv_fit.toarray().sum(axis=0)) # 每个词在所有文档中的词频 [3 3 1 2 1 2 2 2 2 3]如果把数据理解到excel里面,就很好理解cv_fit.toarray().sum(axis=0),就是账单方向的求和。
- 数据表的每行是第一条语料库。
- 数据表的每列是词汇表里面的每个词汇(
token)。
数据分析最终结果
_cnt = cv_fit.toarray().sum(axis=0)
_dic = cv.vocabulary_
res = {v: k for k, v in _dic.items()}
res2 = [(k, res[k]) for k in sorted(res.keys())]
# print(res)
# print(res2)
for key, value in res2:
print(value, ",词频是:", _cnt[key])输出:
一只 ,词频是: 3
可爱 ,词频是: 3
同样 ,词频是: 1
哈吉米 ,词频是: 2
狗子 ,词频是: 1
猫咪 ,词频是: 2
老许 ,词频是: 2
虎子 ,词频是: 2
非常 ,词频是: 2
黑色 ,词频是: 3这里涉及了下面两篇文章:
完整代码

可能存在的问题 .get_feature_names_out()
cv.get_feature_names_out(),在老版本里面,其函数原型是:cv.get_feature_names()。
所以,如果还使用以前的文章里面的代码的话,会得到如下错误提示信息:
AttributeError: 'CountVectorizer' object has no attribute 'get_feature_names'. Did you mean: 'get_feature_names_out'?解决方案就是:给get_feature_names()函数换个名字,换成get_feature_names_out()。
结语
苏南大叔的更多sklearn机器学习的经验文章,请点击下面的链接: