CountVectorizer详解
郝伟 2021/06/07
在 sklearn 库中,有一个非常常用的类 CountVectorizer,其作用是根据词的数量创建词的数量矢量。
CountVectorizer 中文可以译为 词频矢量。词频矢量顾名思义就是对每个单词出现的频率进行统计,以并生成一个特征矩阵。具体过程为先训练文本中的单词(不考虑顺序),将每个出现过的词汇单独视为一列特征,构成一个词汇表(vocabulary list),所以该方法又称为词袋法(Bag of Words),本文就其原理和使用方法进行介绍。
CountVectorizer的详细构造函数如下所示:
class sklearn.feature_extraction.text.CountVectorizer( input=u'content', encoding=u'utf-8', decode_error=u'strict', strip_accents=None, lowercase=True, preprocessor=None, tokenizer=None, stop_words=None, token_pattern=u'(?u)\b\w\w+\b', ngram_range=(1, 1), analyzer=u'word', max_df=1.0, min_df=1, max_features=None, vocabulary=None, binary=False, dtype=<type 'numpy.int64'>)
其具体作用先让我们通过一个示例来进行了解。
from sklearn.feature_extraction.text import CountVectorizer texts=["This is a book.", "That is a pen.", "What is that? It is a pen.", "这是一本书。"] #创建词袋对象 cv = CountVectorizer() # 对texts进行拟合 cv.fit(texts) # 输出所有的特征名称:['book', 'is', 'pen', 'that', 'this', 'what', '这是一本书'] print(cv.get_feature_names()) # 输出词汇字典 # {'this': 4, 'is': 1, 'book': 0, 'that': 3, 'pen': 2, 'what': 5, '这是一本书': 6} print(cv.vocabulary_) # 利用已经拟合好的cv将texts转换为词频矢量 text_matrix=cv.transform(texts) # 显示 cv_fit 稀疏矩阵 print(text_matrix) # 输出二维矩阵 print(text_matrix.toarray()) # fit_transform(texts) 等价于 cv.fit(texts) + cv.transform(texts) #cv_fit=cv.fit_transform(texts)
输出如下
['book', 'is', 'it', 'pen', 'that', 'this', 'what', '这是一本书']
{'this': 5, 'is': 1, 'book': 0, 'what': 6, 'it': 2, 'pen': 3, 'that': 4, '这是一本书': 7}
(0, 0) 1
(0, 1) 1
(0, 5) 1
(1, 1) 2
(1, 2) 1
(1, 3) 1
(1, 5) 1
(1, 6) 1
(2, 1) 2
(2, 2) 1
(2, 3) 1
(2, 4) 1
(2, 6) 1
(3, 7) 1
[[1 1 0 0 0 1 0 0]
[0 1 0 1 1 0 0 0]
[0 2 1 1 1 0 1 0]
[0 0 0 0 0 0 0 1]]
我们分析的目标是理解CountVectorizer的具体的工作方式。
首先,在第9行初始化一个CountVectorizer的实例cv。这与一般面向对象的类的实例化是完全一样的。
第12行就是进行拟合,拟合的原理是对所有的训练文本的词频进行统计。训练完成后,就可以得到训练文本的特征信息,包括所有单词和词汇字典。
cv.get_feature_names() 查看,见第15行;cv.vocabulary_ 查看,见第19行。所以拟合的作用就是学习输入的训练文本的特征,因此在 cv.fit(texts) 完成后 cv 中已经包含了这些重要的特征信息。
拟合 cv 的目标就是为了将目标文本转换为词频矢量,具体实现见第22行。转换后,输出为一个矢量的二维矩阵。需要注意的是,当文本量很大的时候,这个矩阵基本都是非常稀疏的矩阵(可以思考下这是为什么)。所以使用二维数组的方式会占用内存空间,因此这里使用的是行矩阵进行表示,输出为:
(0, 0) 1
(0, 1) 1
(0, 5) 1
(1, 1) 1
(1, 3) 1
(1, 4) 1
(2, 1) 2
(2, 2) 1
(2, 3) 1
(2, 4) 1
(2, 6) 1
(3, 7) 1
在第28行调用了 text_matrix.toarray() 方法以后,即可生成二维矩阵:
[[1 1 0 0 0 1 0 0]
[0 1 0 1 1 0 0 0]
[0 2 1 1 1 0 1 0]
[0 0 0 0 0 0 0 1]]
通过对比
{
'book': 0,
'is': 1,
'it': 2,
'pen': 3,
'that': 4,
'this': 5,
'what': 6,
'这是一本书': 7
}
可以发现,每个单词都有一个对应的唯一编号,一共有8个单词。所以每个字符串就可以使用长度为8的字符串表示,即:
[0 0 0 0 0 0 0 0]
中的每一位分别对应下面的每个单词。
['book', 'is', 'it', 'pen', 'that', 'this', 'what']
举例来说,第1个句子 This is a book. 中 This 表示第6位为1, is 表示第1位为1,book表示第0位为1,所以对应的矢量就是 [1 1 0 0 0 1 0 0]。
后面的句子以此类推,从而得到
[[1 1 0 0 0 1 0 0] # This5 is1 a book0.
[0 1 0 1 1 0 0 0] # That4 is1 a pen3
[0 2 1 1 1 0 1 0] # What6 is1 that4? It2 is1 a pen3.
[0 0 0 0 0 0 0 1]] # 这是一本书7
上面单词后面的数字表示对应的位置的值设置为1. 我们还可以发现,矩阵中有一个2,这是因为在句子 what is that? It is a pen. 中 is 出现了2次。
使用训练好的cv可以对其他的文本进行矢量转换,请看以下示例。
print('tranform demo'.center(60, "*")) s=['What is a pencil?'] m1 = cv.transform(s) print(m1.toarray())
输出为:
***********************tranform demo************************
[[0 1 0 0 0 0 1 0]]
唯一需要注意的是如果文本内容在cv的特征词中不存在则不显示,比如 pencil。
is;lowercase=False 进行区分。顺便再提一句,由于词袋法不考虑顺序,所以就诞生了另一种流程的词向量法:One-Hot编码。
[1] CountVectorizer API 介绍, https://zhuanlan.zhihu.com/p/37644086