MLE最大似然估计和MAP最大后验概率的区别,利用MAP思想完成词性标注。
最近一直在搞懂啥是MLE,啥是MAP。
MLE,最大似然估计,优化的是,求出它的最大值,其中是参数,D是数据;
MAP,最大后验概率分布,优化的是,其中是参数,D是数据,通过贝叶斯定理可以认为等价于求,其中就是MLE,是先验分布。一般来说,这样就推导完了。MAP可以认为是MLE在多加一个先验概率,即在优化之前我们所掌握的信息。然后就是千篇一律的扔硬币举例,反正现在我明白MAP与MLE之间的关系了,但是我还是想不明白,MLE到底是什么鬼?为什么通过MLE可以完成优化?
是啥?最通俗的讲,对于一个数据集,是先有一个模型,然后这个模型产生了这个数据分布。我们要从参数集合中挑选出一组参数,使得选定的模型应用这个参数后产生这个数据集的概率最大。打个比方:一个模型(叫他盘古吧),产生了一堆数据,一天,考古学家:逻辑回归,LSTM,GRU 发现了这堆数据,并用出自己的本领通过观察数据想去模仿盘古,但是很可惜,盘古是CNN家族的,任凭三位考古学家怎么使劲也只能达到盘古的60%精确度。这也就是为什么模型的选择很重要,还有测试集与训练集一定要处在同一分布。
呢?刚好相反,是先有数据,然后我们通过数据去推断模型。这个是两种截然不同的思想。这也是为什么通过贝叶斯定理之后MAP会比MLE多一个先验分布。
让我们利用MAP思想来完成一个词性标注:
其中,我们要求给定句子S,对应句子词性Z的概率最大。通过贝叶斯定理,我们可以转换。最后的前半部分,是MLE,它可以完成句子的转换功能(或者翻译功能之类的),后面的可以看作是语言模型,也就是先验概率,即不仅考虑词性标注的准确性,还考虑句子的语法通顺。
解释一下,这里MLE部分首先利用朴素贝叶斯的思想,即各个条件相互独立。语言模型部分采用Bi-gram。
然后利用log函数单调递增,简化函数,也是为了避免连乘之后的值太小。
这一步之后需要继续推导,对于简单的问题,可以用求极值的方法找到最优解,打公式太累了,我就不推了。可以看看结论,
当n无穷大时,先验概率的影响会越来越小,MLE的作用越来越大,MAP会越来越接近MLE。
刚好,由于我们这个问题简单,我可以把上面的概率都统计出来。我们就不用继续推导公式利用求极值或者梯度下降的方法求解了。
对于词性标注,首先求出第一项,第一项是指给定单词z,词性w的概率时多少。语言模型部分第一部分可以这样理解,即词性出现在句首的概率值,第二部分则是在前一个词性的前提下,下一个词性出现的概率是多少。这是一个状态转移矩阵。
我们首先进行单词标记转换,然后统计出现的次数。
import numpy as np
word2idx, idx2word = {}, {}
tag2idx, idx2tag = {}, {}
with open('./input/traindata.txt', 'r', encoding='utf-8') as f:
data = f.readlines()
for line in data:
items = line.strip().split('/')
word = items[0].strip()
tag = items[1].strip()
if word not in word2idx:
word2idx[word] = len(word2idx)
idx2word[len(idx2word)] = word
if tag not in tag2idx:
tag2idx[tag] = len(tag2idx)
idx2tag[len(idx2tag)] = tag
print(tag2idx)
print(idx2tag)
n_tag = len(tag2idx)
n_word = len(word2idx)
con = np.zeros(n_tag)
like_hood = np.zeros(shape=(n_tag, n_word))
bi_gram = np.zeros(shape=(n_tag, n_tag))
prev_tag = ''
for line in data:
items = line.strip().split('/')
word = items[0].strip()
tag = items[1].strip()
if prev_tag == '':
con[tag2idx[tag]] += 1
like_hood[tag2idx[tag]][word2idx[word]] += 1
else:
like_hood[tag2idx[tag]][word2idx[word]] += 1
bi_gram[tag2idx[prev_tag]][tag2idx[tag]] += 1
if word == '.':
prev_tag = ''
else:
prev_tag = tag
将出现的次数除以总数,即得到了概率值。为了避免0值的出现,添加了平滑项。
con = (con+1)/(np.sum(con)+n_tag)
for i in range(n_tag):
like_hood[i] = (like_hood[i]+1)/(like_hood[i].sum()+n_word)
bi_gram[i] = (bi_gram[i] + 1)/(bi_gram[i].sum()+n_tag)
最后利用动态规划找出最好的解,即概率最大的解。
# 利用动态规划
def viterbi(sentence, con, like_hood, bi_gram):
x = [word2idx[w] for w in sentence.strip().split(' ')]
n = len(x)
dp = np.zeros(shape=(n, n_tag))
pos = np.zeros(shape=(n, n_tag), dtype=np.int)
for i in range(n_tag):
dp[0][i] = np.log(con[i]) + np.log(like_hood[i][x[0]])
for i in range(1, n):
for j in range(n_tag):
dp[i][j] = -9999
for k in range(n_tag):
score = dp[i-1][k] + np.log(like_hood[j][x[i]]) + np.log(bi_gram[k][j])
if score > dp[i][j]:
dp[i][j] = score
pos[i][j] = k
# 找出最好的词性组合
best_seq = [0] * n
# 找到最后一个的位置
best_seq[n-1] = np.argmax(dp[n-1])
# 找到其他的位置
for i in range(n-2, -1, -1):
best_seq[i] = pos[i+1][best_seq[i+1]]
print(best_seq)
best_tag = [idx2tag[_] for _ in best_seq]
print(best_tag)
viterbi('The new ad plan from Newsweek', con, like_hood, bi_gram)
看看结果:
['DT', 'JJ', 'NN', 'NN', 'IN', 'NNP']
The new ad plan from Newsweek
效果还是很好的。注意,我并没有解决未登录词的问题。
如果有疑问,请在下方留言哦。
完整代码和数据链接:
https://github.com/shange1996/Projects-for-NLP
NLP其他项目: