如果还没有安装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])