环境准备
注:本文使用Win10环境.
安装Python
https://www.python.org/downloads/windows/
安装pip
https://github.com/BurntSushi/nfldb/wiki/Python-&-pip-Windows-installation
安装numpy
pip install numpy
安装scipy
pip install scipy
安装scikit-learn
pip install -U scikit-learn
安装jieba
结巴是一款Python中文分词组件,可以直接使用pip安装
pip install jieba
安装pycharm
https://www.jetbrains.com/pycharm/
用jieba进行中文分词
import jieba
def word_cut_chinese(corpus, word_separator):
word_cut_results = list()
for document in corpus:
word_cut_results.append(word_separator.join(jieba.cut(document)))
return word_cut_results
WORD_SEPARATOR = ' '
corpus = ['我第一次学习自然语言处理真的有点慌真的好着急',
'不要紧张一切都会好的']
word_cuted_corpus = word_cut_chinese(corpus, WORD_SEPARATOR)
print '--------分词结果-------'
for doc in word_cuted_corpus:
print doc
print '----------------------'
输出如下
--------分词结果-------
我 第一次 学习 自然语言 处理 真的 有点 慌 真的 好 着急
不要 紧张 一切 都 会 好 的
----------------------
用sklearn构建bag of words
生成词典和特征矩阵
使用下面的代码为语料库中的每个文档执行tokenization和counting的过程后,生成词袋。
count_vectorizer = CountVectorizer()
#训练
count_matrix = count_vectorizer.fit_transform(word_cuted_corpus)
然后可以查看整个语料库的tokens
tokens = count_vectorizer.vocabulary_
print '--------语料库的tokens----------'
for (token, token_index) in tokens.items():
print('%s:%s' % (token, token_index))
print '----------------------'
输出结果如下:
--------语料库的tokens----------
有点:4
真的:5
不要:1
一切:0
着急:6
处理:2
第一次:7
学习:3
紧张:8
自然语言:9
----------------------
可以看出这其实是一个包含n元语法词项的词典,每个词项对应一个索引,该索引也是文档词项矩阵的列索引。
注意CountVectorizer默认采用一元文法(对应其实例化时的默认参数ngram_range=(1, 1)),且只包含长度≥2的词(对应其实例化时的默认参数token_pattern=r"(?u)\b\w\w+\b")
如果想包含2元文法,可以在实例化时指定ngram_range
参数
count_vectorizer = CountVectorizer(ngram_range=(1,2))
如果想包含长度为1的词,可以在实例化时指定token_pattern
参数
count_vectorizer = CountVectorizer(token_pattern=r'(?u)\b\w+\b')
我们可以找到词项对应的索引,也可以查找索引对应的词项
print('token\"自然语言\"的索引是%s' % tokens.get(u'自然语言'))
token = filter(lambda x: x[1] == 6, tokens.items())[0][0]
print(u'索引为6的token是:\"%s\"' % token)
输出如下
token"自然语言"的索引是9
索引为6的token是:"着急"
我们可以将语料库对应的特征矩阵打印出来
print '-------Count矩阵密集表示----'
print count_matrix.todense()
print '----------------------'
输出结果
-------Count矩阵密集表示----
[[0 0 1 1 1 2 1 1 0 1]
[1 1 0 0 0 0 0 0 1 0]]
----------------------
该矩阵的第 i i i 行 j j j 列的值表示文档 i i i 中词项 j j j 出现的次数,结合上面的内容可以知道【真的】这个词项在第一个文档中出现了2次。
当语料库比较大时,这个矩阵一般是个非常稀疏的矩阵,因此sklearn采用了特殊的方式来存储稀疏矩阵,从而节省空间。
print '-------Count矩阵稀疏表示----'
print count_matrix
print '----------------------'
输出结果如下,每一行括号中的值表示矩阵的行列索引,后面的值表示该索引位置的值,可以看出它只记录了那些不为0的值。
-------Count矩阵稀疏表示----
(0, 6) 1
(0, 4) 1
(0, 5) 2
(0, 2) 1
(0, 9) 1
(0, 3) 1
(0, 7) 1
(1, 0) 1
(1, 8) 1
(1, 1) 1
----------------------
我们也可以使用训练语料生成的词典为新的语料生成特征矩阵
#应用到新的语料
corpus_new=['我真的很喜欢你']
word_cuted_corpus_new=word_cut_chinese(corpus_new,WORD_SEPARATOR)
matrix_new = count_vectorizer.transform(word_cuted_corpus_new)
print matrix_new.toarray()
输出结果如下,可以看出该语料中只有1个文档,且该文档中只有索引为5的位置计数为1,即词典中只有【真的】这个词在该文档中出现了1次,其他词典词项均未出现。
[[0 0 0 0 0 1 0 0 0 0]]
TF-IDF
Tf–idf的思想其实就是出现的越多,越烂大街的词汇,对于文档主题的意义越小,而那些在其他地方很少出现,但在某个文档中频繁出现的词很可能和这个文档的主题有很大的相关性。所以在文档主题分类中,我们希望让那些重要的词对最终结果的影响更大,而希望让那些较不重要的词对最终结果的影响较小。为了达到这个目的,我们给 tf(term-frequency),而是给它乘一个重要性因子。这个重要性因子就是 idf(inverse document-frequency),即:
tf-idf(t,d)
=
tf(t,d)
×
idf(t)
\text{tf-idf(t,d)}=\text{tf(t,d)}\times\text{idf(t)}
tf-idf(t,d)=tf(t,d)×idf(t)
在sklearn具体代码实现中,没有首先计算 tf(t,d) \text{tf(t,d)} tf(t,d)的值,而是先用 count(t,d) \text{count(t,d)} count(t,d)和 idf(t) \text{idf(t)} idf(t)相乘(通过特征矩阵右乘一个idf的对角阵来实现),最后再通过一个归一化的过程来将其转化为tf-idf值。
if self.use_idf:
...
X = X * self._idf_diag
计算 idf(t)
sklearn中计算 idf(t) \text{idf(t)} idf(t)的代码实现如下:
……
# perform idf smoothing if required
df += int(self.smooth_idf)
n_samples += int(self.smooth_idf)
# log+1 instead of log makes sure terms with zero idf don't get
# suppressed entirely.
idf = np.log(n_samples / df) + 1
……
可见其计算公式如下:
实例化TfidfVectorizer或TfidfTransformer时使用默认参数smooth_idf=True
,即采用参数平滑:
i
d
f
(
t
)
=
l
n
(
n
_
s
a
m
p
l
e
s
+
1
d
f
(
t
)
+
1
)
+
1
idf(t)=ln(\dfrac{n\_samples+1}{df(t)+1})+1
idf(t)=ln(df(t)+1n_samples+1)+1
实例化TfidfVectorizer或TfidfTransformer时指定参数smooth_idf=False
, 即不采用参数平滑:
i
d
f
(
t
)
=
l
n
(
n
_
s
a
m
p
l
e
s
d
f
(
t
)
)
+
1
idf(t)=ln(\dfrac{n\_samples}{df(t)})+1
idf(t)=ln(df(t)n_samples)+1
其中
n
_
s
a
m
p
l
e
s
n\_samples
n_samples表示文档数量,而
d
f
(
t
)
df(t)
df(t)表示包含项
t
t
t 的文档数目.
注意sklearn中tf-idf的计算公式可能与tf-idf的标准计算方式略有不同,不过如果整个过程都在sklearn中完成的话,其实也没啥影响。
归一化
如果在实例化时指定了向量归一参数,norm='l2'
(默认值,L2范式)或者norm='l1'
(L1范式),还会对矩阵中的向量进行归一化处理。
if self.norm:
X = normalize(X, norm=self.norm, copy=False)
其中L2范式归一化公式为:
v
n
o
r
m
=
v
∣
∣
v
∣
∣
2
=
v
v
1
2
+
v
2
2
+
⋯
+
v
n
2
v_{norm} = \frac{v}{||v||_2} = \frac{v}{\sqrt{v{_1}^2 + v{_2}^2 + \dots + v{_n}^2}}
vnorm=∣∣v∣∣2v=v12+v22+⋯+vn2v
L1范式归一化公式为:
v
n
o
r
m
=
v
∑
k
∣
v
k
∣
v_{norm} = \frac{v}{\displaystyle\sum_k|v_k|}
vnorm=k∑∣vk∣v
normalize的默认参数axis=1,表示对行向量(对应每个文档)进行归一化,如果指定axis=0,则会对列向量(对应每个term)进行归一化。
使用sklearn计算tf-idf矩阵
tf_idf_transformer = TfidfTransformer()
tf_idf_matrix = tf_idf_transformer.fit_transform(count_matrix)
print '-------tf-idf矩阵密集表示----'
print tf_idf_matrix.todense()
print '----------------------'
print '-------tf-idf矩阵稀疏表示----'
print tf_idf_matrix
print '----------------------'
输出如下
-------tf-idf矩阵密集表示----
[[0. 0. 0.31622777 0.31622777 0.31622777 0.63245553
0.31622777 0.31622777 0. 0.31622777]
[0.57735027 0.57735027 0. 0. 0. 0.
0. 0. 0.57735027 0. ]]
----------------------
-------tf-idf矩阵稀疏表示----
(0, 9) 0.31622776601683794
(0, 7) 0.31622776601683794
(0, 6) 0.31622776601683794
(0, 5) 0.6324555320336759
(0, 4) 0.31622776601683794
(0, 3) 0.31622776601683794
(0, 2) 0.31622776601683794
(1, 8) 0.5773502691896257
(1, 1) 0.5773502691896257
(1, 0) 0.5773502691896257
----------------------
注:本文完整代码可在我的Github上获取https://github.com/chansonzhang/FirstNLP
参考文献
[1] 利用sklearn做自然语言处理(NLP)——词向量特征构建
[2] Text feature extraction