目录
1. 词集模型
2. 词袋模型
3. 词袋模型CountVectorizer实现
3.1 词袋化/向量化
3.2 使用现有词袋的特征,对其他文本进行特征提取
4.TF-IDF处理
4.1 简介
4.2 用sklearn进行TF-IDF预处理
(1)CountVectorizer 结合 TfidfTransformer
(2)用 TfidfVectorizer
4.3 TF-IDF小结
文本特征提取有两个非常重要的模型:
词集模型:单词构成的集合,集合自然每个元素都只有一个,也即词集中的每个单词都只有一个。
词袋模型:在词集的基础上如果一个单词在文档中出现不止一次,统计其出现的次数(频数)。
两者本质上的区别:词袋是在词集的基础上增加了频率的维度,词集只关注有和没有,词袋还要关注有几个。
假设我们要对一篇文章进行特征化,最常见的方式就是词袋。
词集模型(Set of Words,简称SoW): 单词构成的集合,每个单词只出现一次。和词袋模型唯一的不同是它仅仅考虑词是否在文本中出现,而不考虑词频。也就是一个词在文本在文本中出现1次和多次特征处理是一样的。在大多数时候,我们使用词袋模型,后面的讨论也是以词袋模型为主。
词袋模型(Bag of Words,简称BoW),即将所有词语装进一个袋子里,不考虑其词法和语序的问题,即每个词语都是独立的,把每一个单词都进行统计,同时计算每个单词出现的次数。也就是说,词袋模型不考虑文本中词与词之间的上下文关系,仅仅只考虑所有词的权重,而权重与词在文本中出现的频率有关。
词袋模型的三部曲:分词(tokenizing),统计修订词特征值(counting)与标准化(normalizing)。
词袋模型首先会进行分词,在分词之后,通过统计每个词在文本中出现的次数,我们就可以得到该文本基于词的特征,如果将各个文本样本的这些词与对应的词频放在一起,就是我们常说的向量化。向量化完毕后一般也会使用TF-IDF进行特征的权重修正,再将特征进行标准化。 再进行一些其他的特征工程后,就可以将数据带入机器学习算法进行分类聚类了。
当然,词袋模型有很大的局限性,因为它仅仅考虑了词频,没有考虑上下文的关系,因此会丢失一部分文本的语义。但是大多数时候,如果我们的目的是分类聚类,则词袋模型表现的很好。
直接用scikit-learn的CountVectorizer类来完成,这个类可以帮我们完成文本的词频统计与向量化。CountVectorize函数比较重要的几个参数为:
decode_error,处理解码失败的方式,分为‘strict’、‘ignore’、‘replace’三种方式。
strip_accents,在预处理步骤中移除重音的方式。默认为None,可设为ascii或unicode,将使用ascii或unicode编码在预处理步骤去除raw document中的重音符号。
max_features,词袋特征个数的最大值。
stop_words,设置停用词,设为english将使用内置的英语停用词,设为一个list可自定义停用词,设为None不使用停用词,设为None且max_df∈[0.7, 1.0)将自动根据当前的语料库建立停用词表。
max_df,可以设置为范围在[0.0 1.0]的float,也可以设置为没有范围限制的int,默认为1.0。这个参数的作用是作为一个阈值,当构造语料库的关键词集的时候,如果某个词的document frequence大于max_df,这个词不会被当作关键词。如果这个参数是float,则表示词出现的次数与语料库文档数的百分比,如果是int,则表示词出现的次数。如果参数中已经给定了vocabulary,则这个参数无效。
min_df,类似于max_df,参数min_df=n表示词必须要在至少n个文档中出现过,否则就不考虑。
binary,默认为False,当与TF-IDF结合使用时需要设置为True。
CountVectorizer是通过fit_transform()函数将文本中的词语转换为词频矩阵,矩阵元素a[i][j] 表示j词在第i个文本下的词频。即各个词语出现的次数,通过get_feature_names()可看到所有文本的关键字,通过toarray()可看到词频矩阵的结果。
from sklearn.feature_extraction.text import CountVectorizer
# 实例化分词对象
vec = CountVectorizer(min_df=1)
# 将文本进行词袋处理
corpus = ['This is the first document.',
'This is the second second document.',
'And the third one.',
'Is this the first document?']
X = vec.fit_transform(corpus)
print(X)
本例中处理的数据集均为英文,所以针对解码失败直接忽略,使用ignore方式,stop_words的方式使用english,strip_accents方式为ascii方式。
对于上面4个文本的处理输出如下:
(0, 2) 1
(0, 3) 1
(0, 6) 1
(0, 4) 1
(0, 7) 1
(1, 5) 2
(1, 2) 1
(1, 6) 1
(1, 4) 1
(1, 7) 1
(2, 9) 1
(2, 0) 1
(2, 8) 2
(2, 1) 1
(3, 2) 1
(3, 3) 1
(3, 6) 1
(3, 4) 1
(3, 7) 1
可以看出4个文本的词频已经统计出,输出中,左边的括号中的第一个数字是文本的序号,第2个数字是词的序号,注意词的序号是基于所有的文档的。第三个数字就是我们的词频。
获取对应的特征名称,即各个特征代表的词:
fnames = vec.get_feature_names()
print(fnames)
['china', 'come', 'document', 'first', 'is', 'second', 'the', 'this', 'to', 'travel']
获取词袋数据,即每个文本的词向量特征:
arr = X.toarray()
print(arr)
[[0 0 1 1 1 0 1 1 0 0]
[0 0 1 0 1 2 1 1 0 0]
[1 1 0 0 0 0 0 0 2 1]
[0 0 1 1 1 0 1 1 0 0]]
至此已经完成了词袋化。
可以看到一共有10个词,所以4个文本都是10维的特征向量。而每一维的向量依次对应了上面的10个词。另外由于词"I"在英文中是停用词,不参加词频的统计。
由于大部分的文本都只会使用词汇表中的很少一部分的词,因此词向量中会有大量的0。也就是说词向量是稀疏的。在实际应用中一般使用稀疏矩阵来存储。
将文本做了词频统计后,我们一般会通过TF-IDF进行词特征值修订。
向量化的方法很好用,也很直接,但是在有些场景下很难使用,比如分词后的词汇表非常大,达到100万+,此时如果我们直接使用向量化的方法,将对应的样本对应特征矩阵载入内存,有可能将内存撑爆,在这种情况下怎么办呢?第一反应是要进行特征的降维,说的没错!而Hash Trick就是非常常用的文本特征降维方法。
如何可以使用现有的词袋的特征,对其他文本进行特征提取呢?
我们定义词袋的特征空间叫做词汇表vocabulary:vocabulary=vec.vocabulary_
对其他文本进行词袋处理时,可以直接使用现有的词汇表:
vocabulary=vec.vocabulary_
new_vectorizer = CountVectorizer(min_df=1, vocabulary=vocabulary)
corpus2=["I come to China to travel",
"This is a car polupar in China",
"I love tea and Apple ",
"The work is to write some papers in science"]
arr2 = new_vectorizer.fit_transform(corpus2).toarray()
print(arr2)
fnames2 = vec.get_feature_names()
print(fnames2)
[[1 1 0 0 0 0 0 0 2 1]
[1 0 0 0 1 0 0 1 0 0]
[0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 1 0 1 0 1 0]]
['china', 'come', 'document', 'first', 'is', 'second', 'the', 'this', 'to', 'travel']
可以看出,各个特征代表的词不变 ,还是之前词袋的特征。
有些词在文本中尽管词频高,但是并不重要,这个时候就可以用TF-IDF技术,进行特征的权重修正。
TF-IDF(Term Frequency–Inverse Document Frequency),即“词频-逆文本频率”,是一种用于资讯检索与文本挖掘的常用加权技术。它由两部分组成,TF和IDF。
TF-IDF是一种统计方法,用以评估一个字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。TF-IDF加权的各种形式常被搜索引擎应用,作为文件与用户查询之间相关程度的度量或评级。
TF-IDF的主要思想是:如果某个词或短语在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。TF-IDF实际上是:TF * IDF。
(1)词频(Term Frequency,TF):指的是某一个给定的词语在该文件中出现的频率。
(2)逆向文件频率(Inverse Document Frequency,IDF):是一个词语普遍重要性的度量。某一特定词语的IDF,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取对数得到。IDF反应了一个词在所有文本中出现的频率,如果一个词在很多的文本中出现,那么它的IDF值应该低,比如“to”;反过来,如果一个词在比较少的文本中出现,那么它的IDF值应该高。比如一些专业的名词如“Machine Learning”。一个极端的情况,如果一个词在所有的文本中都出现,那么它的IDF值应该为0。公式如下:
其中,N代表语料库中文本的总数,而N(x)代表语料库中包含词x的文本总数。
上面的IDF公式已经可以使用了,但是在一些特殊的情况会有一些小问题,比如某一个生僻词在语料库中没有,这样分母为0, IDF没有意义了。所以常用的IDF需要做一些平滑,使语料库中没有出现的词也可以得到一个合适的IDF值。平滑的方法有很多种,最常见的IDF平滑公式之一为:
某一特定文件内的高词语频率,以及该词语在整个文件集合中的低文件频率,可以产生出高权重的TF-IDF。因此,TF-IDF倾向于过滤掉常见的词语,保留重要的词语。
高权重的TF-IDF = 某一特定文件内的高词语频率 + 该词语在整个文件集合中的低文件频率
第一种方法是在用 CountVectorizer 类向量化之后再调用 TfidfTransformer 类进行预处理。第二种方法是直接用 TfidfVectorizer 完成向量化与 TF-IDF 预处理。
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_extraction.text import CountVectorizer
corpus = ['This is the first document.',
'This is the second second document.',
'And the third one.',
'Is this the first document?',
]
# 词袋化
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(corpus)
print(X)
# TF-IDF
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(X)
print(tfidf)
词袋化结果:
(0, 1) 1
(0, 2) 1
(0, 6) 1
(0, 3) 1
(0, 8) 1
(1, 5) 2
(1, 1) 1
(1, 6) 1
(1, 3) 1
(1, 8) 1
(2, 4) 1
(2, 7) 1
(2, 0) 1
(2, 6) 1
(3, 1) 1
(3, 2) 1
(3, 6) 1
(3, 3) 1
(3, 8) 1
输出的各个文本各个词的 TF-IDF 值如下:
(0, 8) 0.4387767428592343
(0, 3) 0.4387767428592343
(0, 6) 0.35872873824808993
(0, 2) 0.5419765697264572
(0, 1) 0.4387767428592343
(1, 8) 0.27230146752334033
(1, 3) 0.27230146752334033
(1, 6) 0.2226242923251039
(1, 1) 0.27230146752334033
(1, 5) 0.8532257361452784
(2, 6) 0.2884767487500274
(2, 0) 0.5528053199908667
(2, 7) 0.5528053199908667
(2, 4) 0.5528053199908667
(3, 8) 0.4387767428592343
(3, 3) 0.4387767428592343
(3, 6) 0.35872873824808993
(3, 2) 0.5419765697264572
(3, 1) 0.4387767428592343
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf2 = TfidfVectorizer()
corpus = ['This is the first document.',
'This is the second second document.',
'And the third one.',
'Is this the first document?',
]
re = tfidf2.fit_transform(corpus)
print(re)
(0, 8) 0.4387767428592343
(0, 3) 0.4387767428592343
(0, 6) 0.35872873824808993
(0, 2) 0.5419765697264572
(0, 1) 0.4387767428592343
(1, 8) 0.27230146752334033
(1, 3) 0.27230146752334033
(1, 6) 0.2226242923251039
(1, 1) 0.27230146752334033
(1, 5) 0.8532257361452784
(2, 6) 0.2884767487500274
(2, 0) 0.5528053199908667
(2, 7) 0.5528053199908667
(2, 4) 0.5528053199908667
(3, 8) 0.4387767428592343
(3, 3) 0.4387767428592343
(3, 6) 0.35872873824808993
(3, 2) 0.5419765697264572
(3, 1) 0.4387767428592343
可见,两种方法的结果是一样的。
TF-IDF是常用的文本挖掘预处理基本步骤,但是如果预处理中使用了Hash Trick,则一般就无法使用TF-IDF了,因为Hash Trick后已经无法得到哈希后的各特征的IDF的值。使用了IF-IDF并标准化以后,就可以使用各个文本的词特征向量作为文本的特征,进行分类或者聚类分析。
当然TF-IDF不光可以用于文本挖掘,在信息检索等很多领域都有使用。因此值得好好的理解这个方法的思想。
结语:
统计个数和计算频率两种方法虽然非常实用,但是也由其局限性导致词汇量可能变得非常大。词汇量过大又将导致需要非常大的矢量来编码文档,从而对内存产生很大的要求,同时拖慢算法的速度。