美文网首页
基于语言模型和Noisy Channel Model的拼写纠错(

基于语言模型和Noisy Channel Model的拼写纠错(

作者: 学人工智能的菜菜 | 来源:发表于2020-04-04 17:48 被阅读0次

今天是清明节,由于疫情原因就没有回家,但是也不能放弃学习,今天是这两周以来,天气最好的一天,心情倍爽,早上起来跳了一千个绳子,要坚持运动鸭。

可能下面会用到Noisy Channel Model,可能会有疑惑,先来解释一下陌生词语是什么意思。由于拼写纠错,如果判断哪个词是最合适的,这时候要结合上下文(前后词,比如i am paly sports 和i am playing sports ) 两个词可能都是正确的词,但是哪个是我们要纠正的呢,我们观看就可以知道,是playing,所以要在语料库里面判断前后两个词的概率,这就要用到语言模型。在计算概率的时候,用到语言模型叫做Noisy Channel Model

先来解释一下什么叫做语言模型?
语言模型用来判断:是否一句话从语法上通顺
语言模型可以帮忙判断这条句子是否通顺 所以语言模型是很重要的。
语言模型有三种类型uni-gram->bi-gram->n-gram 从简单到难
unigram 是只求每个元素的边缘概率,都是独立的,不依赖其他的元素。
unigram 是只求每个元素的边缘概率,都是独立的,不依赖其他的元素。
P(W1,W2,W3,W4,W5,W6,W7,W8,W9)=P(W1)P(W2)P(W3)P(W4)P(W5)P(W6)P(W7)P(W8)P(W9)
unigram缺点 没有语义之间的区别。所以很难区别两个句子是否通顺。如果所有单词都一样,顺序不一样,其实概率一样,那这样子句子的概率一样,那这样子就是不合理的。
bigram 是基于马尔可夫模型下的“一阶依赖”求概率(大部分都是应用这个语言模型)
n-gram 更高级的语言模型,就是说在语义上面更好,依赖性更强,n>2
n=3就是二阶马尔可夫模型。
估计语言模型的概率:
uni-gram 就是计算每个词的词频/总共的词
bi-gram 一阶的依赖概率在单词里面计算,比如算p(是|明天)
n-gram 跟bi-gram类推
smoothing(平滑方法):add-one smoothing/add-k smoothing / interpolation/good-turning smoothing
平滑的操作:为了不让之出现一堆0的情况,则计算方式在分子+1,分母加V(语料库),下面计算用的是Add-one Smoothing for语言模型

应用场景
语音识别、机器翻译、拼写纠错、OCR、密码破解
咱们来实验一下拼写纠错吧。

先来一下说明:

1、本文针对的是英文的,因为英文语料库实在是非常多,不过你会了原理,则中文也差不多实现。
2、语言模型的语料库是nltk的,都可以去下载
3、下面是用编辑距离为1生成纠错集合的,为了更需要,可以用编辑距离2,3等等操作实现,可能就更有说服力
4、所用语言,Python3.7.4

下面是拼写纠错流程:
  • 先读取字典库的字典vocab
  • 生成所有的候选集合
  • 统计用户打错概率
  • 计算概率最大值
拼写纠错的概率计算,求得最大值

max(𝑃(𝑡𝑟𝑢𝑒|𝑓𝑎𝑙𝑠𝑒)=𝑃(𝑓𝑎𝑙𝑠𝑒|𝑡𝑟𝑢𝑒).𝑃(𝑡𝑟𝑢𝑒)÷𝑃(𝑓𝑎𝑙𝑠𝑒) )
由于𝑃(𝑓𝑎𝑙𝑠𝑒)是大家共有的,我们只是比较大小,所以可以忽略不计
则我们简化为计算max(𝑃(𝑡𝑟𝑢𝑒|𝑓𝑎𝑙𝑠𝑒)=𝑃(𝑓𝑎𝑙𝑠𝑒|𝑡𝑟𝑢𝑒).𝑃(𝑡𝑟𝑢𝑒) )
其中𝑃(𝑡𝑟𝑢𝑒) 是用到语言模型计算,𝑃(𝑓𝑎𝑙𝑠𝑒|𝑡𝑟𝑢𝑒)是打错概率可得,这个比较简单,主要是求语言模型的值
𝑃(候选集合的一个单词(简称单词)) = P(单词|前一个单词).P(后一个单词|单词)为了方便起见,我只算 P(单词|前一个单词)
则P(单词) = P(单词|前一个单词)
由贝叶斯公式可知:
加入add-one平滑后
P(单词|前一个单词) = P(单词.前一个单词)+1/P(前一个单词)+V

好了,废话那么多,可以开始代码展示啦。

#读取字典库
vocab = {line.rstrip() for line in open('./data/vocab.txt')}
#用set来存储不用list是因为查找的时候set时间复杂度是O(1),List是O(n)
结果:{'trails', 'congregational', 'partisan', 'Flor', 'Wire'}
#生成所有的候选集合 candidate
def candidate(word):
    #生成编辑距离为1的所有集合,选取存在字典里的词
    # 1、insert 2、delete 3、replace
    #appl insert:aappl bappl cappl dappl....
    #     delete:ppl apl app... 
    #     replace:bppl cppl dppl eppl....
    
    #假设有26个字母
    letters = 'abcdefghijklmnopqrstuvwxyz'
    #把word拆分
    splits = [(word[:i],word[i:]) for i in range(len(word)+1)]
    #insert的集合
    inserts = [L+c+R for L,R in splits for c in letters]
    #replace的集合
    replaces = [L+c+R[1:] for L,R in splits if R for c in letters]
    
    #delete的集合
    deteles = [L+R[1:] for L,R in splits if R]
    candidates =  set(inserts+replaces+deteles)
    #过滤掉不存在字典库里的词
    return [word for word in candidates if word in vocab]

 candidate('appl')
运行输出结果:['apple', 'apply']
#感觉还算蛮对的哈哈哈,可能这个比较简单的错误,大家都可以多试试一下
#用户打错概率统计- channel probability
channel_prod = {}
for line in open('./data/spell-errors.txt'):
    #读取文文件分割correct and mistakes
    items = line.split(':')
    correct = items[0]
    mistakes = [item.strip() for item in items[1].strip().split(',')]
    #计算概率
    channel_prod[correct] = {}
    for mis in mistakes:
        channel_prod[correct][mis] = 1.0/len(mistakes)
print(channel_prod)
输出结果:
{'raining': {'rainning': 0.5, 'raning': 0.5}, 'writings': {'writtings': 1.0}, 'disparagingly': {'disparingly': 1.0}}

计算语言模型,下面用的是nltk的语料库

from nltk.corpus import reuters

# 读取语料库
categories = reuters.categories()
#corpus语料库是list,每一句话或者一个文档都是一个list,单词独立分开的,所以直接计算即可
corpus = reuters.sents(categories=categories)
# 构建语言模型: bigram
term_count = {}
bigram_count = {}
for doc in corpus:
    doc = ['<s>'] + doc
    for i in range(0, len(doc)-1):
        # bigram: [i,i+1]
        term = doc[i]
        bigram = doc[i:i+2]
        
        if term in term_count:
            term_count[term]+=1
        else:
            term_count[term]=1
        bigram = ' '.join(bigram)
        if bigram in bigram_count:
            bigram_count[bigram]+=1
        else:
            bigram_count[bigram]=1
print(term_count)
print(bigram_count)
#输出结果:
{'<s>': 54716, 'ASIAN': 12, 'EXPORTERS': 46, 'FEAR': 2,}
 {'tariff|violates': 1, 'so|recommend': 1, 'recommend|within': 1, 'Reagan|retaliatory': 1, 'against|Canada': 1, 'At|noon': 1, 'noon|the': 1, 'shortfall|at': 1, '<s>|KNIGHT': 1, 'KNIGHT|-': 1, '-|RIDDER': 1, 'RIDDER|INC': 1, ';|KRN': 1, 'KRN|>': 1, 'CELLULAR|SERVICE': 1, 'SERVICE|INC': 1, '507|Revs': 1, '141|Avg': 1, 'loss|811': 1, '573|Avg': 1, '<s>|AUTOMOTIVE': 1, 'AUTOMOTIVE|TECHNOLOGIES': 1, 'diluted|41': 1}

计算概率

#计算纠错概率
import numpy as np
# 测试用的句子 
sentence = '''
They told Reuter correspondents in Asian capitals a U.S.  Move against Japn might boost protectionst sentiment in the  U.S. And lead to curbs on American imports of their products.
'''
#所有单词的集合
V = len(term_count.keys())
#切割句子 split默认把空字符进行切片
sentence = sentence.split()
#存储计算处理的scores和纠正词和对应的score
scores = []
scores_dict = {}
#找出该句子中错别字,然后纠错
for (index,word) in enumerate(sentence):
    #判断找到不在字典里面的词,则开始纠错
    if word not in vocab:
        #该word就是错误的词
        #1、计算p(wrong|true)
        #2、计算p(true) 语言模型计算
        #3、计算p(wrong|true).p(true)
        #为了方便计算和数字客观性,则用加log,就变成相加了
        
        #找出纠正的候选集合
        candidate_list = candidate(word)
        #如果找不出候选集合,那就要加长步伐 比如2,目前只有1,为了方便起见,这样子设置,但是不建议这样子
        if len(candidate_list) == 0:
            continue
        for cadi in candidate_list:
            #定义每一个cadi单词的分数
            score = 0.0
            #计算p(wrong|true) 错误和正确值都要存在则可计算
            if cadi in channel_prod and word in channel_prod:
                #如果存在,在则计算该值
                score += np.log(channel_prod[cadi][word])
            else:
                #否则取一个很小的值,防止没有值或者0
                score += np.log(0.00001)
            
            #计算p(true)
            #拿到前一个单词
            preWord = sentence[index-1] if index > 0 else '<s>'
            #计算p(单词|前一个单词)
            bigram_term_string = preWord+'|'+cadi
            #计算概率
            if bigram_term_string in bigram_count:
                score+=np.log(bigram_count[bigram_term_string]+1/term_count[preWord]+V)
            else:
                score+=np.log(1.0/+V)
            scores.append(score)
            scores_dict[cadi] = score
            print(scores)
        max_index = scores.index(max(scores))
        best_candicate = candidate_list[max_index]
        print(word, '-->', best_candicate)
        print(scores_dict)
输出结果:
[-0.8769738526651665]
[-0.8769738526651665, -22.147794848296215]
[-0.8769738526651665, -22.147794848296215, -22.147794848296215]
Japn --> Japan
{'Japan': -0.8769738526651665, 'Jan': -22.147794848296215, 'Japs': -22.147794848296215}
[-0.8769738526651665, -22.147794848296215, -22.147794848296215, -0.8780319012270343]
protectionst --> protectionist
{'Japan': -0.8769738526651665, 'Jan': -22.147794848296215, 'Japs': -22.147794848296215, 'protectionist': -0.8780319012270343}
[-0.8769738526651665, -22.147794848296215, -22.147794848296215, -0.8780319012270343, -0.8778154709365786]
products. --> products
{'Japan': -0.8769738526651665, 'Jan': -22.147794848296215, 'Japs': -22.147794848296215, 'protectionist': -0.8780319012270343, 'products': -0.8778154709365786}

哈哈哈哈,好啦,拼写纠错就先告一段落啦,咱们下期再见。

相关文章

网友评论

      本文标题:基于语言模型和Noisy Channel Model的拼写纠错(

      本文链接:https://www.haomeiwen.com/subject/wsmjphtx.html