NLP三种词袋模型CountVectorizer/TfidfTransformer/HashVectorizer

2,654 阅读4分钟

1. CountVectorizer

CountVectorizer类会将文本中的词语转换为词频矩阵。 例如矩阵中包含一个元素a[i][j]a[i][j],它表示jj词在ii类文本下的词频。它通过fit_transform函数计算各个词语出现的次数,通过get_feature_names()可获取词袋中所有文本的关键字,通过toarray()可看到词频矩阵的结果。

from sklearn.feature_extraction.text import CountVectorizer
#语料
corpus = [
    'This is the first document.',
    'This is the this second second document.',
    'And the third one.',
    'Is this the first document?'
]
#将文本中的词转换成词频矩阵
vectorizer = CountVectorizer()
print(vectorizer)
#计算某个词出现的次数
X = vectorizer.fit_transform(corpus)
print(type(X),X)
#获取词袋中所有文本关键词
word = vectorizer.get_feature_names()
print(word)
#查看词频结果
print(X.toarray())

结果:

  (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)	2
  (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
['and', 'document', 'first', 'is', 'one', 'second', 'the', 'third', 'this']
[[0 1 1 1 0 0 1 0 1]
 [0 1 0 1 0 2 1 0 2]
 [1 0 0 0 1 0 1 1 0]
 [0 1 1 1 0 0 1 0 1]]

2. TF-IDF

TF-IDF(term frequency-inverse document frequency)是文本加权方法,采用统计思想,即文本出现的次数和整个语料中文档频率来计算字词的重要度。

优点:过滤一些常见但是无关紧要的字词。

tfidfi,j=tfi,j×idfi,jtfidf_{i,j} = tf_{i,j}\times idf_{i,j}

TF(Term Frequency)表示某个关键词在整篇文章中出现的频率。(某个词在文章中出现的总次数/文章的总词数);

tfi,j=ni,jknk,jt f_{i, j}=\frac{n_{i, j}}{\sum_{k} n_{k, j}}

IDF(Inverse Document Frequency)表示计算倒文本频率。文本频率是指某个关键词在整个语料所有文章中出现的次数。倒文档频率又称为逆文档频率,它是文档频率的倒数,主要用于降低所有文档中一些常见却对文档影响不大的词语的作用。

Transformer 官方文档

默认:

IDF(x)=logN+1N(x)+1+1IDF(x) = log\frac{N+1}{N(x)+1} + 1

这里, NN 为总的文档数, N(x)N(x) 为包含这个词 xx 的文档数。

教科书标准的idf定义:

IDF(x)=logNN(x)+1IDF(x) = log\frac {N}{N(x)+1}

其中, NN代表语料库的文档总数; N(x)N(x) 代表包含该词xx的文档数.

Tfidf 实现,一般是先通过countVectorizer, 然后再通过 tfidfTransformer, 转换成 tfidf 向量; 也有现成的 TfidfVectorizer API。

语句:

TfidfTransformer(norm='l2', use_idf=True, smooth_idf=True, sublinear_tf=False)

示例:

from sklearn.feature_extraction.text import TfidfVectorizer, TfidfTransformer, CountVectorizer
import numpy as np
#语料
cc = [
      'aa bb.',
      'aa cc.'
]
# method 1
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(cc)
print('feature',vectorizer.get_feature_names())
print(X.toarray())

结果:

feature ['aa', 'bb', 'cc']
[[0.57973867 0.81480247 0.        ]
 [0.57973867 0.         0.81480247]]

值得注意的是,默认语料会把单个字符当作停止单词(stop_words)进行过滤,如果需要保留单个字符组成的单词,可以修改分词方式:token_pattern='(?u)\\b\\w+\\b'

此外,上面还可实现如下:

# method 2
vectorizer=CountVectorizer()#token_pattern='(?u)\\b\\w+\\b'
transformer = TfidfTransformer()
cntTf = vectorizer.fit_transform(cc)
print('feature', vectorizer.get_feature_names())
print(cntTf)
cnt_array = cntTf.toarray()
X = transformer.fit_transform(cntTf)
print(X.toarray())

结果:

feature ['aa', 'bb', 'cc']
# 第一个数字表示第几个文档;第二个数字表示第几个feature,结果表示相应的词频。
  (0, 1)        1
  (0, 0)        1
  (1, 2)        1
  (1, 0)        1
[[0.57973867 0.81480247 0.        ]
 [0.57973867 0.         0.81480247]]

为了更加明白TfidfTransformer的操作,进行简单分解实现该功能:

# method 3
vectorizer=CountVectorizer()
cntTf = vectorizer.fit_transform(cc)
tf = cnt_array/np.sum(cnt_array, axis = 1, keepdims = True)
print('tf',tf)
idf = np.log((1+len(cnt_array))/(1+np.sum(cnt_array,axis = 0))) + 1
print('idf', idf)
t = tf*idf
print('tfidf',t)
print('norm tfidf', t/np.sqrt(np.sum(t**2, axis = 1, keepdims=True)))

结果:

tf [[0.5 0.5 0. ]
 [0.5 0.  0.5]]
idf [1.         1.40546511 1.40546511]
tfidf [[0.5        0.70273255 0.        ]
 [0.5        0.         0.70273255]]
norm tfidf [[0.57973867 0.81480247 0.        ]
 [0.57973867 0.         0.81480247]]

也就是说,TfidfTransformer 默认会对获得的向量除以2范数进行归一化。

vnorm =vv2=vv12+v22++vn2v_{\text {norm }}=\frac{v}{\|v\|_{2}}=\frac{v}{\sqrt{v_{1}^{2}+v_{2}^{2}+\cdots+v_{n}^{2}}}

TF-IDF的信息论依据

一个查询(Query)中的每个关键词(Key Word)w的权重应该反映这个词查询来讲提供了多少信息。一个简单方法是用每个词的信息量作为他的权重。 在这里插入图片描述

但是,如果两个词出现的频率 TF相同, 一个是某篇特定文章中的常见词,而另外一个词时分散在多篇文章中,显然第一个词有更高的分辨率,权重应该更大。

在这里插入图片描述

3. HashingVectorizer

语法

HashingVectorizer(alternate_sign=True, analyzer='word', binary=False,
         decode_error='strict', dtype=<class 'numpy.float64'>,
         encoding='utf-8', input='content', lowercase=True,
         n_features=1048576, ngram_range=(1, 1), non_negative=False,
         norm='l2', preprocessor=None, stop_words=None, strip_accents=None,
         token_pattern='(?u)\\b\\w\\w+\\b', tokenizer=None)

特点

普通的CountVectorizer存在但词库很大时,占用大内存,因此,使用hash技巧,并用稀疏矩阵存储编译后的矩阵,能很好解决这个问题。

实现的伪代码为:

 function hashing_vectorizer(features : array of string, N : integer):
     x := new vector[N]
     for f in features:
         h := hash(f)
         x[h mod N] += 1
     return x

这里伪代码没有考虑到hash冲突的情况,实际实现会更加复杂。

from sklearn.feature_extraction.text import HashingVectorizer
corpus = [
     'This is the first document.',
     'This document is the second document.',
     'And this is the third one.',
     'Is this the first document?',
 ]
vectorizer = HashingVectorizer(n_features=2**4)
X = vectorizer.fit_transform(corpus)
print(X.toarray())
print(X.shape)

结果:

[[-0.57735027  0.          0.          0.          0.          0.
   0.          0.         -0.57735027  0.          0.          0.
   0.          0.57735027  0.          0.        ]
 [-0.81649658  0.          0.          0.          0.          0.
   0.          0.          0.          0.          0.          0.40824829
   0.          0.40824829  0.          0.        ]
 [ 0.          0.          0.          0.         -0.70710678  0.70710678
   0.          0.          0.          0.          0.          0.
   0.          0.          0.          0.        ]
 [-0.57735027  0.          0.          0.          0.          0.
   0.          0.         -0.57735027  0.          0.          0.
   0.          0.57735027  0.          0.        ]]
(4, 16)

4. 总结

总的而言,这三种都是词袋模型的方法,其中,由于tfidfvectorizer这种方法可以降低高频信息量少词语的干扰,应用得更多。


reference:

  1. (推荐)sklearn tfidf
  2. TF-IDF blog
  3. 刘建平 博客
  4. (推荐) Sklearn官网 Feature extraction;
  5. 学习sklearn之文本特征提取;
  6. wiki, feature hashing;
  7. 数学之美 吴军