NLP_拼写纠错

如果还没有安装nltk(Natural Lanuage ToolKit)模块的同学,
参考下面链接中的第二种方法安装:
https://blog.csdn.net/jiajikang_jjk/article/details/83716939

在这里先放源码,有需求者可以自己访问:
https://github.com/aftcool/NLP

一、整体概括

->本项目采用的是英文词典库,用来判断单词的正确性。
->其次需要的文件为用户打错信息表,用来计算单词出错概率,(出错概率越大,说明对应正确单词,更有可能是符合条件,应该替换的)
->最后需要语料库,用来构建语言模型(LM),生成term_count和bigram_count。
term_count[]:单个单词出现的次数
bigram_count[]:连续两个单词出现的次数
->根据错误单词,生成替换候选集,计算 P = P(出错概率)+P(语言模型概率),徐选择候选集中概率最大的单词进行替换。主要知识点在于语言模型概率的计算

①语言模型概率的计算

例如:I like eating appla very much.

很明显,遍历这句话,与词典库中进行对照,appla这个单词是不存在的.

那么,这个单词的替换候选集该如何生成呢?

候选集的生成可以通过对错误单词三种操作生成:
1.插入字符 2.删除字符 3.更改字符
操作++ <=> 编辑距离++,即编辑距离为2生成的单词,代表错误单词经历了上述两部操作)
具体的生成候选集操作,请看下面项目代码详解。

那么,生成完候选集之后,候选集中每个单词的LM概率如何计算呢?

通过得到appla的编辑距离为1的候选单词为:apple和apply
则PLM(apple) = P(apple|eating)*P(very|apple)
换成维特比计算方法(通用计算方法):
log(PLM(apple)) = log(P(apple|eating)) + log(P(very|apple))

针对P(apple|eating)该如何计算?

P(apple|eating) = Count(eating apple) / Count(eating)
而通常需要采用Smothing操作,即:
P(apple|eating) = (Count(eating apple) + 1.0) / (Count(eating) + V)
V:语料库中单词的总数。

二、项目代码

①读取词典库

#词典库  vocab.txt 为词库
vocab = {line.rstrip() for line in open('./data/vocab.txt',encoding='utf-8')}

②生成候选集合
因为存在"只生成编辑距离为1的候选集合时,所有的候选集合中没有一个单词时正确的,即均不在词典库中",这时可以采取在编辑距离为1的单词的基础上,生成编辑距离为2的单词 or 其他的方法!!!

#生成编辑距离为2的候选集合
def generate_candidates_two(word_one):
    letters = 'abcdefghijklmnopqrstuvwxyz'
    candidates_two = []
    for word in word_one:
        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]
        #repalce操作
        replaces = [L+c+R[1:] for L,R in splits if R for c in letters]
        #delete操作
        deletes = [L+R[1:] for L,R in splits if R]
    
        candidates_two += set(inserts + replaces + deletes)
    return [word for word in candidates_two if word in vocab]
#需要生成所有的候选集合
def generate_candidates(word):
    """
    word:给定的输入(错误的输入)
    返回所有(valid)候选集合
    """
    #生成编辑距离为1的单词
    # 1.insert 2.delete 3.replace
    #假设使用26个字符
    letters = 'abcdefghijklmnopqrstuvwxyz'
    
    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]
    #repalce操作
    replaces = [L+c+R[1:] for L,R in splits if R for c in letters]
    #delete操作
    deletes = [L+R[1:] for L,R in splits if R]
    
    candidate_one = set(inserts + replaces + deletes)
    #过滤掉不存在词典库中的单词
    return [word for word in candidate_one if word in vocab]
#     word_one =  [word for word in candidate_one if word in vocab]
#     if len(word_one) < 1:
#         return generate_candidates_two(candidate_one)
#     else:
#         return [word for word in candidate_one if word in vocab]
#generate_candidates("appc")

③构建语言模型
生成文章开头所有的两个词典,term_count 和 bigram_count
本文采用的是,nltk.corpus模块中的 reuters语料库

from nltk.corpus import reuters #路透社语料库
#读取语料库
categories = reuters.categories()
corpus = reuters.sents(categories = categories)
# 构建语言模型 unigram bigram trigram
term_count = {} #单个单词出现的次数
bigram_count = {} #连续两个单词出现的次数
for doc in corpus:
    doc = [''] + 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
# sklearn里面有现成的包

④处理用户打错信息
生成channle_prob[][],二维元组:
第一维:正确单词
第二维:错误单词
val值:对应的概率

#用户打错的概率统计 - channel probability    
channel_prob = {} #就是一个二维元组,第一维是正确单词,第二维是错误单词
for line in open("./data/spell-errors.txt"):
    items = line.split(":")
    correct = items[0].strip()
    mistakes = [word.strip() for word in items[1].strip().split(",")]
    channel_prob[correct] = {}
    for mis in mistakes:
        channel_prob[correct][mis] = 1.0 / len(mistakes)

⑤生成纠错的单词,即应该替换的单词

import numpy as np
V = len(term_count.keys()) #smothing操作中,单词的总数V
file = open("./data/testdata.txt",'r')

for line in file:
    items = line.rstrip().split('\t') # items 是一个三维数组, items[2]是一个句子
    line = items[2].split()
    line = [''] + line  #把获取的一句话,进行与计算bigram_count相同的预处理
    for word in line:
        if word not in vocab: 
            #如果句子中,有单词不在词典中    word:错误的单词
            #step1:生成所有的(valid)候选集合
            candidates = generate_candidates(word)
            if len(candidates) < 1:
                continue
            probs = []
            #对于每一个candidate,计算它的score
            # score = p(correct) * p(mistake|correct)
            #       = log p(correct) + log p(mistake|correct)
            #返回score最大的candidate
            for candi in candidates:
                prob = 0
                #计算 channel概率  出错的概率   
                if candi in channel_prob and word in channel_prob[candi]:#正确单词在channel_prob中,并且也存在对应错误写法
                    prob += np.log(channel_prob[candi][word])
                else:
                    prob += np.log(0.00001)
                # 计算语言模型的概率
                idx = line.index(word) #找到错误单词,在句子中的位置
                #计算idx位置左边的bigram_prob
                left_bigram = line[idx-1] +' ' + candi
                if left_bigram in bigram_count:#判断bigram 是否在bigtam_count中
                    prob += np.log(bigram_count[left_bigram] + 1.0 / (term_count[line[idx-1]] + V))
                else:
                    prob += np.log(1.0 / V) # 不存在,smothing操作
                #计算idx位置右边的bigram_prob
                if idx != len(line)-1:
                    right_bigram = candi + ' '+ line[idx+1]
                    if right_bigram in bigram_count:
                        prob += np.log(bigram_count[right_bigram] + 1.0 /(term_count[candi] + V))
                    else:
                        prob += np.log(1.0 / V)
                probs.append(prob)
            max_index = probs.index(max(probs))
            print(word,candidates[max_index])

你可能感兴趣的:(github,NLP_拼写纠错)