贝叶斯算法概述
贝叶斯要解决的问题:
正向概率:假设袋子里面有N个白球,M个黑球,伸手进去摸球,摸出黑球的概率是多大
逆向概率:如果我们事先并不知道袋子里面黑白球的比例,而是闭着眼睛摸出一个(或好几个)球,观察这些取出来的球的颜色之后,那么我们可以就此对袋子里面的黑白球的比例作出什么样的推测
例子:
条件:在一个学校中,男生占60%,女生占40%,男生总是穿长裤,女生则一半穿长裤一半穿裙子
问题:
正向概率:随机选取一个学生,他(她)穿长裤的概率和穿裙子的概率是多大
逆向概率:迎面走来一个穿长裤的学生,只看得见他(她)穿的是否长裤,而无法确定他(她)的性别,能够推断出他(她)是女生的概率是多大吗?
假设学校里面人的总数是 U 个
穿长裤的男生: U ∗ P ( B o y ) ∗ P ( P a n t s ∣ B o y ) U*P(Boy)*P(Pants|Boy) U∗P(Boy)∗P(Pants∣Boy)
- P ( B o y ) P(Boy) P(Boy) 是男生的概率,为 60%
- P ( P a n t s ∣ B o y ) P(Pants|Boy) P(Pants∣Boy) 是条件概率,即在 B o y Boy Boy 条件下,穿长裤的概率是多大,为100%
穿长裤的女生: U ∗ P ( G i r l ) ∗ P ( P a n t s ∣ G i r l ) U*P(Girl)*P(Pants|Girl) U∗P(Girl)∗P(Pants∣Girl)
- P ( G i r l ) P(Girl) P(Girl) 是女生的概率,为 40%
- P ( P a n t s ∣ G i r l ) P(Pants|Girl) P(Pants∣Girl) 是条件概率,即在 G i r l Girl Girl 条件下,穿长裤的概率是多大,为50%
求解:穿长裤的人里面有多少女生?
穿长裤总人数: U ∗ P ( B o y ) ∗ P ( P a n t s ∣ B o y ) + U ∗ P ( G i r l ) ∗ P ( P a n t s ∣ G i r l ) U*P(Boy)*P(Pants|Boy)+U*P(Girl)*P(Pants|Girl) U∗P(Boy)∗P(Pants∣Boy)+U∗P(Girl)∗P(Pants∣Girl)
穿长裤的人里面女生人数:
P
(
G
i
r
l
∣
P
a
n
t
s
)
=
U
∗
P
(
G
i
r
l
)
∗
P
(
P
a
n
t
s
∣
G
i
r
l
)
穿
长
裤
总
人
数
P
(
G
i
r
l
∣
P
a
n
t
s
)
=
U
∗
P
(
G
i
r
l
)
∗
P
(
P
a
n
t
s
∣
G
i
r
l
)
U
∗
P
(
B
o
y
)
∗
P
(
P
a
n
t
s
∣
B
o
y
)
+
U
∗
P
(
G
i
r
l
)
∗
P
(
P
a
n
t
s
∣
G
i
r
l
)
P
(
G
i
r
l
∣
P
a
n
t
s
)
=
P
(
G
i
r
l
)
∗
P
(
P
a
n
t
s
∣
G
i
r
l
)
P
(
B
o
y
)
∗
P
(
P
a
n
t
s
∣
B
o
y
)
+
P
(
G
i
r
l
)
∗
P
(
P
a
n
t
s
∣
G
i
r
l
)
P
(
G
i
r
l
∣
P
a
n
t
s
)
=
P
(
G
i
r
l
)
∗
P
(
P
a
n
t
s
∣
G
i
r
l
)
P
(
P
a
n
t
s
)
\begin{aligned} P(Girl|Pants)&=\frac{U*P(Girl)*P(Pants|Girl)}{穿长裤总人数} \\ P(Girl|Pants)&=\frac{U*P(Girl)*P(Pants|Girl)}{U*P(Boy)*P(Pants|Boy)+U*P(Girl)*P(Pants|Girl)} \\ P(Girl|Pants)&=\frac{P(Girl)*P(Pants|Girl)}{P(Boy)*P(Pants|Boy)+P(Girl)*P(Pants|Girl)} \\ P(Girl|Pants)&=\frac{P(Girl)*P(Pants|Girl)}{P(Pants)} \end{aligned}
P(Girl∣Pants)P(Girl∣Pants)P(Girl∣Pants)P(Girl∣Pants)=穿长裤总人数U∗P(Girl)∗P(Pants∣Girl)=U∗P(Boy)∗P(Pants∣Boy)+U∗P(Girl)∗P(Pants∣Girl)U∗P(Girl)∗P(Pants∣Girl)=P(Boy)∗P(Pants∣Boy)+P(Girl)∗P(Pants∣Girl)P(Girl)∗P(Pants∣Girl)=P(Pants)P(Girl)∗P(Pants∣Girl)
贝叶斯公式
P ( A ∣ B ) = P ( A ) ∗ P ( B ∣ A ) P ( B ) P(A|B)=\frac{P(A)*P(B|A)}{P(B)} P(A∣B)=P(B)P(A)∗P(B∣A)
实例
拼写纠正实例:
问题:检测到用户输入了一个不在字典中的单词,我们需要去猜
测:“用户到底真正想输入的单词是什么呢?
P(我们猜测他想输入的单词 | 他实际输入的单词)
用户实际输入的单词记为 D ( D 代表 Data ,即观测数据)
猜测1:
P
(
h
1
∣
D
)
P(h_1| D)
P(h1∣D)
猜测2:
P
(
h
2
∣
D
)
P(h_2 | D)
P(h2∣D)
猜测3:
P
(
h
3
∣
D
)
P(h_3| D)
P(h3∣D)…
这里我们统一为:P(h | D)
P
(
h
∣
D
)
=
P
(
h
)
∗
P
(
D
∣
h
)
P
(
D
)
P(h | D) = \frac{P(h) * P(D | h)}{P(D)}
P(h∣D)=P(D)P(h)∗P(D∣h)
对于不同的具体猜测
h
1
,
h
2
,
h
3
.
.
.
h_1,h_2,h_3...
h1,h2,h3... ,
P
(
D
)
P(D)
P(D) 都是一样的,所以在比较
P
(
h
1
∣
D
)
P(h_1 | D)
P(h1∣D) 和
P
(
h
2
∣
D
)
P(h_2 | D)
P(h2∣D) 的时候我们可以忽略这个常数
所以他们之间成正比例关系:
P ( h ∣ D ) ∝ P ( h ) ∗ P ( D ∣ h ) P(h|D)\propto P(h)*P(D|h) P(h∣D)∝P(h)∗P(D∣h)
对于给定观测数据,一个猜测是好是坏,取决于“这个猜测本身独
立的可能性大小(先验概率)”和“这个猜测生成我们观测
到的数据的可能性大小。
贝叶斯方法计算: P ( h ) ∗ P ( D ∣ h ) P(h) * P(D | h) P(h)∗P(D∣h), P ( h ) P(h) P(h) 是特定猜测的先验概率
在计算完各个猜测的概率,进行比较再给出一个最终结果
垃圾邮件过滤实例:
问题:给定一封邮件,判定它是否属于垃圾邮件
D 来表示这封邮件,注意 D 由 N 个单词组成,我们用 h+ 来表示垃圾邮件,h- 表示正常邮件
P
(
h
+
∣
D
)
=
P
(
h
+
)
∗
P
(
D
∣
h
+
)
P
(
D
)
P
(
h
−
∣
D
)
=
P
(
h
−
)
∗
P
(
D
∣
h
−
)
P
(
D
)
P(h+|D)=\frac{P(h+)*P(D|h+)}{P(D)} \\ P(h-|D)=\frac{P(h-)*P(D|h-)}{P(D)}
P(h+∣D)=P(D)P(h+)∗P(D∣h+)P(h−∣D)=P(D)P(h−)∗P(D∣h−)
先验概率:
P
(
h
+
)
P(h+)
P(h+) 和
P
(
h
−
)
P(h-)
P(h−) 这两个先验概率都是很容易求出来的,只
需要计算一个邮件库里面垃圾邮件和正常邮件的比例就可以
假设 D 里面有 N 个单词 d 1 , d 2 , d 3 , . . . , d n d_1,d_2,d_3,...,d_n d1,d2,d3,...,dn, P ( D ∣ h + ) = P ( d 1 , d 2 , . . , d n ∣ h + ) P(D|h+) = P(d_1,d_2,..,d_n|h+) P(D∣h+)=P(d1,d2,..,dn∣h+), P ( d 1 , d 2 , . . , d n ∣ h + ) P(d_1,d_2,..,d_n|h+) P(d1,d2,..,dn∣h+) 就是说在垃圾邮件当中出现跟 D 这封邮件一模一样的一封邮件的概率是多大
P ( d 1 , d 2 , . . , d n ∣ h + ) P(d_1,d_2,..,d_n|h+) P(d1,d2,..,dn∣h+) 可扩展为: P ( d 1 ∣ h + ) ∗ P ( d 2 ∣ d 1 ∣ h + ) ∗ P ( d 3 ∣ d 2 ∣ d 1 ∣ h + ) ∗ . . . ∗ P ( d n ∣ d n − 1 ∣ . . . ∣ d 3 ∣ d 2 ∣ d 1 ∣ h + ) P(d_1|h+) * P(d_2|d_1|h+) * P(d_3|d_2|d_1|h+) *...*P(d_n|d_{n-1}|...|d_3|d_2|d_1|h+) P(d1∣h+)∗P(d2∣d1∣h+)∗P(d3∣d2∣d1∣h+)∗...∗P(dn∣dn−1∣...∣d3∣d2∣d1∣h+)
展开公式中,每一个词的累加导致很难计算,这里使用朴素贝叶斯理论,假设特征之间是独立、互不影响的,可化简为: P ( d 1 ∣ h + ) ∗ P ( d 2 ∣ h + ) ∗ P ( d 3 ∣ h + ) ∗ . . . ∗ P ( d n ∣ h + ) P(d_1|h+) * P(d_2|h+) * P(d_3|h+) *...*P(d_n|h+) P(d1∣h+)∗P(d2∣h+)∗P(d3∣h+)∗...∗P(dn∣h+) ,只要统计到 d i d_i di 这些单词在垃圾邮件中出现的概率即可
再对
d
i
d_i
di 这些单词做在正常邮件中的概率统计
P
(
d
1
∣
h
−
)
∗
P
(
d
2
∣
h
−
)
∗
P
(
d
3
∣
h
−
)
∗
.
.
.
∗
P
(
d
n
∣
h
−
)
P(d_1|h-) * P(d_2|h-) * P(d_3|h-) *...*P(d_n|h-)
P(d1∣h−)∗P(d2∣h−)∗P(d3∣h−)∗...∗P(dn∣h−)
P
(
h
+
∣
D
)
=
P
(
h
+
)
∗
P
(
D
∣
h
+
)
P
(
D
)
P
(
h
−
∣
D
)
=
P
(
h
−
)
∗
P
(
D
∣
h
−
)
P
(
D
)
P(h+|D)=\frac{P(h+)*P(D|h+)}{P(D)} \\ P(h-|D)=\frac{P(h-)*P(D|h-)}{P(D)}
P(h+∣D)=P(D)P(h+)∗P(D∣h+)P(h−∣D)=P(D)P(h−)∗P(D∣h−)
最后计算出
P
(
h
∗
∣
D
)
P(h*|D)
P(h∗∣D),进行比较判断是否为垃圾邮件
代码实现拼写纠正
求解: a r g m a x c P ( c ∣ w ) − > a r g m a x c P ( w ∣ c ) P ( c ) / P ( w ) argmaxc P(c|w) -> argmaxc P(w|c) P(c) / P(w) argmaxcP(c∣w)−>argmaxcP(w∣c)P(c)/P(w)
- P ( c ) P(c) P(c), 文章中出现一个正确拼写词 c 的概率, 也就是说, 在英语文章中, c 出现的概率有多大
- P ( w ∣ c ) P(w|c) P(w∣c), 在用户想键入 c 的情况下敲成 w 的概率. 因为这个是代表用户会以多大的概率把 c 敲错成 w
- a r g m a x c argmaxc argmaxc, 用来枚举所有可能的 c 并且选取概率最大的
先验概率
# 把语料中的单词全部抽取出来, 转成小写, 并且去除单词中间的特殊符号
def words(text): return re.findall('[a-z]+', text.lower())
def train(features):
model = collections.defaultdict(lambda: 1)
for f in features:
model[f] += 1
return model
NWORDS = train(words(open('big.txt').read()))
按照原逻辑,只要是传进来一个词,就要返回出现这个词的原概率,但是如果传进来一个从来没有过见过的新词,返回的这个词的先验概率就是0了,这个情况不太妙, 因为概率为0这个代表了这个事件绝对不可能发生, 而在我们的概率模型中, 我们期望用一个很小的概率来代表这种情况. lambda: 1
编辑距离
两个词之间的编辑距离定义为使用了几次插入(在词中插入一个单字母)、删除(删除一个单字母)、交换(交换相邻两个字母)、替换(把一个字母换成另一个)的操作从一个词变到另一个词
编辑距离为 1:
#返回所有与单词 w 编辑距离为 1 的集合
def edits1(word):
n = len(word)
return set([word[0:i]+word[i+1:] for i in range(n)] + # deletion
[word[0:i]+word[i+1]+word[i]+word[i+2:] for i in range(n-1)] + # transposition
[word[0:i]+c+word[i+1:] for i in range(n) for c in alphabet] + # alteration
[word[0:i]+c+word[i:] for i in range(n+1) for c in alphabet]) # insertion
编辑距离为 2:
在 edits1()
的基础上再做一次循环
#返回所有与单词 w 编辑距离为 2 的集合
#在这些编辑距离小于2的词中间, 只把那些正确的词作为候选词
def edits2(word):
return set(e2 for e1 in edits1(word) for e2 in edits1(e1))
这种编辑距离的方法,在对长单词的计算会非常大量,比如 something 编辑距离为2的单词居然达到了 114,324 个,所以还需要进行一个优化,在这些编辑距离小于2的词中间, 只把那些正确的词作为候选词,只能返回 4 个单词:
{‘seething’, ‘smoothing’, ‘something’, ‘soothing’}
正常来说把一个元音拼成另一个的概率要大于辅音 (因为人常常把 hello 打成 hallo 这样); 把单词的第一个字母拼错的概率会相对小, 等等
但是为了简单起见, 选择了一个简单的方法: 编辑距离为1的正确单词比编辑距离为2的优先级高, 而编辑距离为0的正确单词优先级比编辑距离为1的高
# 优化:在这些编辑距离小于2的词中间, 只把那些正确的词作为候选词
def known(words): return set(w for w in words if w in NWORDS)
#如果known(set)非空, candidate 就会选取这个集合, 而不继续计算后面的
def correct(word):
candidates = known([word]) or known(edits1(word)) or known_edits2(word) or [word]
return max(candidates, key=lambda w: NWORDS[w])
测试
correct('appla')
'apply'
correct('learw')
'learn'