类似的博客很多,本文重点在于第一次将词袋模型是如何一步步到朴素贝叶斯算法并最终实现文本分类的
文章目录
从词袋模型到朴素贝叶斯算法
1. 词袋模型
1.1 说明
1.1.1 TF
TF(term-frequency) 表示词频
在scikit-learn里是函数CountVectorizer()
这里不需要公式吧,就是一个词在一个文本中出现的次数,一般都是 [0,1]值
1.1.2 TF-IDF
IDF(inverse document-frequency) 表示逆文档频率
在scikit-learn里是函数TfidfVectorizer()
在语料库中,某些词会经常出现,比如“a”,“the”;但这些词又没有实际意义,对文本分类不仅没有任何好处,反而还会影响更有用的词频,因此才引入了tf-idf变换
用公式表示为:
idf
(
t
)
=
log
1
+
n
1
+
df
(
t
)
+
1
tf-idf
(
t
,
d
)
=
tf
(
t
,
d
)
×
idf
(
t
)
\text{idf}(t)=\log \frac{1+n}{1+\text{df}(t)}+1\\ \text{tf-idf}(t,d)=\text{tf}(t,d) \times \text{idf}(t)
idf(t)=log1+df(t)1+n+1tf-idf(t,d)=tf(t,d)×idf(t)
其中:
n
n
n 为文本总数,
df(t)
\text{df(t)}
df(t) 是有多少个文本包含了词语
t
\text{t}
t
对结果还要进行欧几里得归一化
公式与其他地方可能有出入
在下一章节会解释TF-IDF比TF好在哪里
1.2 例子
我们现在给定下面这样一个文本集
因为词袋模型不关心词语的位置信息,所以可以是一段不通顺的话
文本编号 | 文档内容 |
---|---|
1 | This is the first document. |
2 | This is the second second document. |
3 | And the third one. |
4 | Is this the first document? |
1.2.1 代码算
# 使用 CountVectorizer()
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.toarray())
>>>[[0 1 1 1 0 0 1 0 1]
[0 1 0 1 0 2 1 0 1]
[1 0 0 0 1 0 1 1 0]
[0 1 1 1 0 0 1 0 1]]
# 使用 TfidfVectorizer()
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = [
'This is the first document.',
'This is the second second document.',
'And the third one.',
'Is this the first document?',
]
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(corpus)
print(X.toarray())
>>>[[0. 0.43877674 0.54197657 0.43877674 0. 0.
0.35872874 0. 0.43877674]
[0. 0.27230147 0. 0.27230147 0. 0.85322574
0.22262429 0. 0.27230147]
[0.55280532 0. 0. 0. 0.55280532 0.
0.28847675 0.55280532 0. ]
[0. 0.43877674 0.54197657 0.43877674 0. 0.
0.35872874 0. 0.43877674]]
1.2.2 手算
上述两种方法得到的特征名称是一样的,均为['and', 'document', 'first', 'is', 'one', 'second', 'the', 'third', 'this']
我们现在来尝试计算文本1中的词document
tf = 1 \text{tf}=1 tf=1
n = 4 n = 4 n=4
df ( t ) = 3 \text{df}(t) = 3 df(t)=3
idf ( t ) = log 1 + n 1 + df ( t ) + 1 = log ( 5 4 ) + 1 = 1.09691 \text{idf}(t)=\log \frac{1+n}{1+\text{df}(t)}+1=\log(\frac{5}{4})+1=1.09691 idf(t)=log1+df(t)1+n+1=log(45)+1=1.09691
tf-idf = tf × idf = 1.09691 \text{tf-idf}=\text{tf} \times \text{idf}= 1.09691 tf-idf=tf×idf=1.09691
同理得到整个文本1的
tf-idf
\text{tf-idf}
tf-idf 为:[0, 1.09691, 1.221849, 1.09691, 0, 0, 1, 0, 1.09691]
再L2归一化得到:[0, 0.444033079, 0.494608725, 0.444033079, 0, 0, 0.404803561, 0, 0.444033079]
与代码计算结果一致(差异应该来自手算的精度)!
1.3 细节
1、transform方法的调用中,训练语料库中未出现的单词将被完全忽略
1.4 高级
在上述的处理过程中存在一个问题,不知道大家有没有发现,就是第一个文本和最后一个文本具有完全相同的词,因此被编码为相同的向量。但最后一个文本是疑问的语气,应该和第一个文本有区别,所以需要思考如何保留更多的信息。
解决办法是:除了单字,我们还可以保留多字。
在前面的基础上我们补充代码
bigram_vectorizer = CountVectorizer(ngram_range=(1, 2),
token_pattern=r'\b\w+\b', min_df=1)
X_2 = bigram_vectorizer.fit_transform(corpus)
print(X_2.toarray())
>>>[[0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0],
[0, 0, 1, 0, 0, 1, 1, 0, 0, 2, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0],
[1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1]]
具体区别在哪里呢
原方法处理文档4为:['is', 'this', 'the', 'first', 'document']
新方法处理文档4为:['is', 'this', 'the', 'first', 'document', 'is this', 'this the', 'the first', 'first document']
2. 朴素贝叶斯
2.1 说明
相信大家对贝叶斯公式都不陌生
P
(
类
别
∣
特
征
)
=
P
(
特
征
∣
类
别
)
P
(
类
别
)
P
(
特
征
)
P(类别 | 特征)=\frac{P(特征 | 类别) P(类别)}{P(特征)}
P(类别∣特征)=P(特征)P(特征∣类别)P(类别)
朴素的含义在于当有很多个特征的时候,这些特征都是独立分布的,因此会有
P
(
特
征
∣
类
别
)
=
P
(
特
征
1
,
特
征
2
,
…
,
特
征
n
∣
类
别
)
=
∏
i
=
1
n
P
(
特
征
i
∣
类
别
)
P(特征 | 类别) = P(特征1,特征2,\dots,特征n | 类别) = \prod_{i=1}^n P(特征i | 类别)
P(特征∣类别)=P(特征1,特征2,…,特征n∣类别)=i=1∏nP(特征i∣类别)
我们再接着看文本分类中的应用,我将从问题根源开始说起
2.2 例子
2.2.1 手算
因为上面的例子不好加类别标签,于是我们重新给定一个文本集
docID | words in document | China? |
---|---|---|
1 | Chinese Beijing Chinese | yes |
2 | Chinese Chinese Shanghai | yes |
3 | Chinese Macao | yes |
4 | Tokyo Japan Chinese | no |
5 | Chinese Chinese Chinese Tokyo Japan | ? |
前四个为训练集,最后一个为测试集
很容易就能发现,有Chinese,Beijing,Shanghai,Macao时,类别就是yes;有Tokyo,Japan时,类别就是no,因此单词的出现是影响类别判断的标准。我们用上述的词袋模型把问题简化,用词频矩阵表示为:
b
e
i
j
i
n
g
c
h
i
n
e
s
e
j
a
p
a
n
m
a
c
a
o
s
h
a
n
g
h
a
i
t
o
k
y
o
1
2
0
0
0
0
0
2
0
0
1
0
0
1
0
1
0
0
0
1
1
0
0
1
\begin{array}{cccccc} beijing & chinese & japan & macao & shanghai & tokyo\\ 1 & 2 & 0 & 0 & 0 & 0 \\ 0 & 2 & 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 1 & 0 & 0 \\ 0 & 1 & 1 & 0 & 0 & 1 \end{array}
beijing1000chinese2211japan0001macao0010shanghai0100tokyo0001
我们相当于知道了:
P
(
1
2
0
0
0
0
∣
c
=
y
e
s
)
P
(
0
2
0
0
1
0
∣
c
=
y
e
s
)
P
(
0
1
0
1
0
0
∣
c
=
y
e
s
)
P
(
0
1
1
0
0
1
∣
c
=
n
o
)
P(1 \ 2 \ 0 \ 0 \ 0 \ 0 \ | \ c=yes)\\ P(0 \ 2 \ 0 \ 0 \ 1 \ 0 \ | \ c=yes)\\ P(0 \ 1 \ 0 \ 1 \ 0 \ 0 \ | \ c=yes)\\ P(0 \ 1 \ 1 \ 0 \ 0 \ 1 \ | \ c=no)
P(1 2 0 0 0 0 ∣ c=yes)P(0 2 0 0 1 0 ∣ c=yes)P(0 1 0 1 0 0 ∣ c=yes)P(0 1 1 0 0 1 ∣ c=no)
P ( c = y e s ) = 0.75 P ( c = n o ) = 0.25 P(c=yes)=0.75 \\ P(c=no)=0.25 P(c=yes)=0.75P(c=no)=0.25
想要去求
arg
max
c
∈
C
P
(
c
∣
0
3
1
0
0
1
)
\underset{c \in C}{\arg\max} \ P(c \ | \ 0 \ 3 \ 1 \ 0 \ 0 \ 1)
c∈Cargmax P(c ∣ 0 3 1 0 0 1)
现在最重要的就是去应用朴素性,计算单个词在类别下的条件概率,一种比较合理的计算方法是
P
(
t
i
∣
c
)
=
T
c
i
∑
i
′
=
1
V
T
c
i
′
=
单
词
t
i
在
类
别
c
所
有
文
档
中
出
现
的
次
数
类
别
c
所
有
文
档
的
单
词
总
数
P(t_i | c) = \frac{T_{ci}}{\sum_{i'=1}^{V}{T_{ci'}}}=\frac{单词t_i在类别c所有文档中出现的次数}{类别c所有文档的单词总数}
P(ti∣c)=∑i′=1VTci′Tci=类别c所有文档的单词总数单词ti在类别c所有文档中出现的次数
定义:
t
i
t_i
ti 表示 第
i
i
i 个单词(一共有
V
V
V 个),
d
k
d_k
dk 表示 第
k
k
k 个文档(一共有
N
N
N 个),
c
c
c 表示 类别(yes|no两种)
计算Chinese这个单词 P ( t 2 ∣ y e s ) = 5 8 P(t_2|yes)=\frac{5}{8} P(t2∣yes)=85
上式分子是词频矩阵前三行第二列的总和,分母是前三行所有列的总和
上式是理论表达,在实际应用中,分子会出现 0 的情况,为了避免这一情况,我们对公式做出一点修改
P
(
t
i
∣
c
)
=
T
c
i
+
α
∑
i
′
=
1
V
(
T
c
i
′
+
α
)
P(t_i | c) = \frac{T_{ci}+\alpha}{\sum_{i'=1}^{V}({T_{ci'}}+\alpha)}
P(ti∣c)=∑i′=1V(Tci′+α)Tci+α
这个
α
\alpha
α 叫做平滑因子,一般默认为1,下面的计算均为使用默认值
这样,我们计算出了
P ( t i / c ) P(t_i /c) P(ti/c) | beijing | chinese | japan | macao | shanghai | tokyo |
---|---|---|---|---|---|---|
yes | 2/14 | 6/14 | 1/14 | 2/14 | 2/14 | 1/14 |
no | 1/9 | 2/9 | 2/9 | 1/9 | 1/9 | 2/9 |
对文档5做预测,
N
d
N_d
Nd 是文档
d
d
d 中的总词数,文档5中有5个词
P
(
y
e
s
∣
0
3
1
0
0
1
)
∝
P
(
y
e
s
)
∏
1
≤
i
≤
N
d
P
(
t
i
∣
c
)
=
3
/
4
⋅
(
6
/
14
)
3
⋅
1
/
14
⋅
1
/
14
≈
0.0003
\begin{aligned} P(yes|\ 0 \ 3 \ 1 \ 0 \ 0 \ 1) & \propto P(yes) \prod_{1 \leq i \leq N_d} P(t_{i} | c)\\ & = 3/4 \cdot(6/14)^{3} \cdot 1/14 \cdot 1/14 \approx 0.0003 \end{aligned}
P(yes∣ 0 3 1 0 0 1)∝P(yes)1≤i≤Nd∏P(ti∣c)=3/4⋅(6/14)3⋅1/14⋅1/14≈0.0003
P ( n o ∣ 0 3 1 0 0 1 ) ∝ P ( n o ) ∏ 1 ≤ i ≤ N d P ( t i ∣ c ) = 1 / 4 ⋅ ( 2 / 9 ) 3 ⋅ 2 / 9 ⋅ 2 / 9 ≈ 0.0001 \begin{aligned} P(no | \ 0 \ 3 \ 1 \ 0 \ 0 \ 1) & \propto P(no) \prod_{1 \leq i \leq N_d} P(t_{i} | c)\\ & = 1 / 4 \cdot(2 / 9)^{3} \cdot 2 / 9 \cdot 2 / 9 \approx 0.0001 \end{aligned} P(no∣ 0 3 1 0 0 1)∝P(no)1≤i≤Nd∏P(ti∣c)=1/4⋅(2/9)3⋅2/9⋅2/9≈0.0001
因此我们判断文档5的类别为yes
通常会对上式取对数,累乘转化为累加的形式
2.2.2 代码算
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
import numpy as np
corpus = [
'Chinese Beijing Chinese',
'Chinese Chinese Shanghai',
'Chinese Macao',
'Tokyo Japan Chinese',
]
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(corpus)
clf = MultinomialNB()
clf.fit(X, ['yes','yes','yes','no'])
print(np.exp(clf.feature_log_prob_)) # 原本为log
>>>[[0.11111111 0.22222222 0.22222222 0.11111111 0.11111111 0.22222222]
[0.14285714 0.42857143 0.07142857 0.14285714 0.14285714 0.07142857]]
我们发现与手算结果一模一样
2.3 替换词频模型
上面使用的是词频,同样也可以换成TF-IDF模型,在这里将直接呈现代码和结果,然后分析是否得到了优化
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
import numpy as np
corpus = [
'Chinese Beijing Chinese',
'Chinese Chinese Shanghai',
'Chinese Macao',
'Tokyo Japan Chinese',
]
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(corpus)
clf = MultinomialNB()
clf.fit(X, ['yes','yes','yes','no'])
print(np.exp(clf.feature_log_prob_))
>>>[[0.13032796 0.1754451 0.21678552 0.13032796 0.13032796 0.21678552]
[0.16624155 0.28562042 0.09826111 0.18537427 0.16624155 0.09826111]]
你可以手算,手算结果和代码结果一致
2.4 思考
在这里我觉得应该思考一个问题,透过公式 P ( t i ∣ c ) P(t_i | c) P(ti∣c) 来看, t i t_i ti 并不像是一个特征,因为特征就会有取值,应该诸如 t i = 0 t_i=0 ti=0 or t i = 1 t_i=1 ti=1 这样的(但并没有),同时计算公式中表示为 t i t_i ti 在文档中出现的次数,是与其他 t j t_j tj 同等地位的;所以应该是 所有单词代表一个特征,每个单词是这个特征的取值。那么朴素的假设不就不存在了吗,这些特征的取值本来就是互异的,更不存在多个特征相互独立的情况。
3. 参考链接
一个小彩蛋
第一个例子出自scikit-learn,第二个例子出自stanford,所以不是我原创也不是我胡编乱造