赞
踩
GitHub地址:https://github.com/fanorfan/EnglishWordErrorCorrection
下载地址:https://github.com/fanorfan/EnglishWordErrorCorrection/blob/master/big.txt
取出语料库中所有的单词,并统计其出现的次数。
对于一个给定的单词,不管其是否拼写错误,依次找到和它编辑距离为0、1、2的单词。
考虑以下三个优先顺序来挑选出该单词一个最佳纠正结果:
1). 编辑距离为0的单词(即该单词本身) > 编辑距离为1的单词 > 编辑距离为2的单词
2). 是否在语料库中出现(最起码得是一个正确的存在的单词)
3). 语料库中出现的次数
import re, collections
# text中所有单词转为小写形式并匹配
# text示例:"The Project Gutenberg EBook of The Adventures of Sherlock Holmes\nby Sir Arthur Conan Doyle"
def tokens(text):
return re.findall("[a-z]+", text.lower())
# 打开语料库文件
with open('big.txt', 'r') as f:
words = tokens(f.read())
# 统计语料库中每个单词的个数
word_counts = collections.Counter(words)
# text示例:'fianlly helloa cta'
def correct_text_generic(text):
"""
纠正匹配到的单词中所有拼写错误的单词
"""
return re.sub('[a-zA-Z]+', correct_match, text)
correct_match
参数是一个回调函数,来执行对匹配到的单词的操作,我们创建这个函数如下:
def correct_match(match): """ 替换的回调函数 """ # 匹配到的单词依次取出,第一次取'fianlly' word = match.group() def case_of(text): """ 返回小写 """ return (str.upper if text.isupper() else str.lower if text.islower() else str.title if text.istitle() else str) return case_of(word)(correct(word.lower()))
case_of(word)()
的第二个括号中需要一个函数,我们创建这个函数如下:
def correct(word):
"""
获得输入单词的最佳正确拼写
"""
# 1.是否在语料库中出现
# 2.单词的优先顺序:编辑距离为0的单词(即该单词本身) > 编辑距离为1的单词 > 编辑距离为2的单词
# 3.在语料库中的出现次数
candidates = (known(edits0(word)) or
known(edits1(word)) or
known(edits2(word)) or
{word})
return max(candidates, key=word_counts.get)
candidates
变量来保存符合1). 2).两个条件的单词,最后返回其中在语料库中出现次数最多的单词。
2). 是否在语料库中出现对应的函数如下:
def known(words):
"""
判断words中的每一个单词是否在语料库中出现,若出现就返回此单词
"""
return {w for w in words if w in word_counts}
1). 编辑距离为0的单词(即该单词本身) > 编辑距离为1的单词 > 编辑距离为2的单词 对应的函数如下:
def edits0(word): """ 返回跟输入单词是0距离的单词,也就是自己 """ return {word} def edits1(word): """ 返回跟输入单词是1距离的单词 """ # 26个英文字母 ord():获取'a'的码 chr():通过码还原对应的字符 # 'abcdefghijklmnopqrstuvwxyz' alphabet = ''.join([chr(ord('a') + i) for i in range(26)]) def splits(word): """ 分割单词 以cta为例: [('', 'cta'), ('c', 'ta'), ('ct', 'a'), ('cta', '')] """ return [(word[:i], word[i:]) for i in range(len(word) + 1)] # 分割好的单词 pairs = splits(word) # 删除某个字符 ['ta', 'ca', 'ct'] deletes = [a + b[1:] for (a, b) in pairs if b] # 两个字符换位置 ['tca', 'cat'] transposes = [a + b[1] + b[0] + b[2:] for (a, b) in pairs if len(b) > 1] # 替换某个字符 ['ata', 'bta', 'cta', 'dta', 'eta', 'fta', 'gta', 'hta', 'ita', 'jta', 'kta', 'lta', 'mta', 'nta', 'ota', 'pta', 'qta', 'rta', 'sta', 'tta', 'uta', 'vta', 'wta', 'xta', 'yta', 'zta', 'caa', 'cba', 'cca', 'cda', 'cea', 'cfa', 'cga', 'cha', 'cia', 'cja', 'cka', 'cla', 'cma', 'cna', 'coa', 'cpa', 'cqa', 'cra', 'csa', 'cta', 'cua', 'cva', 'cwa', 'cxa', 'cya', 'cza', 'cta', 'ctb', 'ctc', 'ctd', 'cte', 'ctf', 'ctg', 'cth', 'cti', 'ctj', 'ctk', 'ctl', 'ctm', 'ctn', 'cto', 'ctp', 'ctq', 'ctr', 'cts', 'ctt', 'ctu', 'ctv', 'ctw', 'ctx', 'cty', 'ctz'] replaces = [a + c + b[1:] for (a, b) in pairs for c in alphabet if b] # 插入某个字符 ['acta', 'bcta', 'ccta', 'dcta', 'ecta', 'fcta', 'gcta', 'hcta', 'icta', 'jcta', 'kcta', 'lcta', 'mcta', 'ncta', 'octa', 'pcta', 'qcta', 'rcta', 'scta', 'tcta', 'ucta', 'vcta', 'wcta', 'xcta', 'ycta', 'zcta', 'cata', 'cbta', 'ccta', 'cdta', 'ceta', 'cfta', 'cgta', 'chta', 'cita', 'cjta', 'ckta', 'clta', 'cmta', 'cnta', 'cota', 'cpta', 'cqta', 'crta', 'csta', 'ctta', 'cuta', 'cvta', 'cwta', 'cxta', 'cyta', 'czta', 'ctaa', 'ctba', 'ctca', 'ctda', 'ctea', 'ctfa', 'ctga', 'ctha', 'ctia', 'ctja', 'ctka', 'ctla', 'ctma', 'ctna', 'ctoa', 'ctpa', 'ctqa', 'ctra', 'ctsa', 'ctta', 'ctua', 'ctva', 'ctwa', 'ctxa', 'ctya', 'ctza', 'ctaa', 'ctab', 'ctac', 'ctad', 'ctae', 'ctaf', 'ctag', 'ctah', 'ctai', 'ctaj', 'ctak', 'ctal', 'ctam', 'ctan', 'ctao', 'ctap', 'ctaq', 'ctar', 'ctas', 'ctat', 'ctau', 'ctav'... inserts = [a + c + b for (a, b) in pairs for c in alphabet] # 返回集合 return set(deletes + transposes + replaces + inserts) def edits2(word): """ 返回跟输入单词是2距离的单词 寻找跟word编辑距离为1的单词的编辑距离为1的单词就是编辑距离为2的单词 """ return {e2 for e1 in edits1(word) for e2 in edits1(e1)}
说明:与该单词编辑距离为2的单词,也就是与该单词的编辑距离为1的单词
的编辑距离为1的单词。
最后我们验证一下效果:
if __name__ == '__main__':
original_word = 'fianlly helloa cta'
correct_word = correct_text_generic(original_word)
print('Original word:%s\nCorrect word:%s' % (original_word, correct_word))
输出结果如下:
Original word:fianlly helloa cta
Correct word:finally hello cat
1.导入相关模块,读取doc文档
from docx import Document
from nltk import sent_tokenize, word_tokenize
from CorrectWords import correct_text_generic
from docx.shared import RGBColor
# 文档中修改的单词个数
count_correct = 0
# 获取文档对象
file = Document("ErrorDocument.docx")
# 各种标点符号
punkt_list = r",.?\"'!()/\\-<>:@#$%^&*~"
document = Document() # word文档句柄
2.创建修改每一段中错误单词的函数如下:
def write_correct_paragraph(i): """ 修改一个段落中的错误 """ global count_correct # 每一段的内容 paragraph = file.paragraphs[i].text.strip() # 进行句子划分 sentences = sent_tokenize(text=paragraph) # 词语划分 words_list = [word_tokenize(sentence) for sentence in sentences] # 段落句柄 p = document.add_paragraph(' ' * 7) for word_list in words_list: for word in word_list: if word not in punkt_list: p.add_run(' ') # 修改单词,如果单词正确,则返回原单词 correct_word = correct_text_generic(word) # 每一句话第一个单词的第一个字母大写,并空两格 if word_list.index(word) == 0 and words_list.index(word_list) == 0: correct_word = correct_word[0].upper() + correct_word[1:] # 如果单词有修改,则颜色为红色 if correct_word != word: colored_word = p.add_run(correct_word) font = colored_word.font font.color.rgb = RGBColor(0xFF, 0x00, 0x00) count_correct += 1 else: p.add_run(correct_word) else: p.add_run(word)
count_correct
设置为全局变量,将一段话划分为句子列表,再将每一句话划分为单词列表。correct_text_generic()
函数。最后我们验证一下效果:
if __name__ == '__main__':
print("段落数:" + str(len(file.paragraphs)))
for i in range(len(file.paragraphs)):
write_correct_paragraph(i)
document.save("CorrectDocument.docx")
print("修改并保存文件完毕!")
print("一共修改了%d处。" % count_correct)
输出结果如下:
段落数:4
修改并保存文件完毕!
一共修改了19处。
ErrorDocument.docx文档
CorrectDocument.docx文档
什么是两个字符串的编辑距离(edit distance)?给定字符串s1和s2,以及在s1上的如下操作:
最少需要多少次操作才能使s1转换为s2?
比如:
单词“cat”和“hat”,这样的操作最少需要1次,只需要把“cat”中的“c”替换为“h”即可。
单词“recall”和“call”,这样的操作最少需要2次,只需要把“recall”中的“r”和“e”去掉即可。
单词“Sunday”和“Saturday”,这样的操作最少需要3次,在“Sunday”的“S”和“u”中插入“a”和“t”,再把“n”替换成“r”即可。
以上操作对应的编辑距离分别为1,2,3。
那么,是否存在一种高效的算法,能够快速、准确地计算出两个字符串的编辑距离呢?
我们使用动态规划算法(Dynamic Programming)来计算出两个字符串的编辑距离。
我们从两个字符串s1和s2的最末端向前遍历,假设s1的长度为m,s2的长度为n,算法如下:
1.如果两个字符串的最后一个字符一样,那么,我们就可以递归地计算长度为m-1和n-1的两个字符串的情形;
2.如果两个字符串的最后一个字符不一样,那么,进入以下三种情形(选择其后续总共需要操作步骤最少的):
如果m为0,则至少需要操作n次,即在s1中逐个添加s2的字符,一共是n次;如果n为0,则至少需要操作m次,即把s1的字符逐个删除即可,一共是m次。
计算编辑距离的函数如下:
# 计算编辑距离 def edit_distance(s1, s2): print("---------------------------------------") print("s1的值:", s1) print("s2的值:", s2) # s1 长度m m = len(s1) # s2 长度n n = len(s2) # 如果m = 0,则至少还需要操作n次,即在s1中逐个添加s2的字符 if m == 0: if n > 0: return n else: return 0 # 如果n = 0,则至少还需要操作m次,即在s1中逐个删除字符 # 如果m =0,n = 0,则不需要再操作 if n == 0: if m > 0: return m else: return 0 # s1和s2最后一个字符一样 if s1[-1] == s2[-1]: return edit_distance(s1[:-1], s2[:-1]) else: # 最后一个字符不一样 # 插入 删除 替换 return 1 + min(edit_distance(s1, s2[:-1]), edit_distance(s1[:-1], s2), edit_distance(s1[:-1], s2[:-1]))
以后递归迭代完毕后需要的操作总次数最少
的那个操作。最后我们验证一下效果:
if __name__ == '__main__':
string1 = "Sunday"
string2 = "Saturday"
distance = edit_distance(string1, string2)
print("编辑距离:", distance)
输出结果如下:
编辑距离: 3
参考博客:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。