博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一个简单的拼写检查器
阅读量:5290 次
发布时间:2019-06-14

本文共 6609 字,大约阅读时间需要 22 分钟。

记得以前就看过这篇文章:,文章将贝叶斯原理运用于拼写检查,二十几行简单的Python的代码就实现了一个拼写检查器。

原作者python代码:

import re, collectionsdef words(text): return re.findall('[a-z]+', text.lower()) def train(features):    model = collections.defaultdict(lambda: 1)    for f in features:        model[f] += 1    return modelNWORDS = train(words(file('big.txt').read()))alphabet = 'abcdefghijklmnopqrstuvwxyz'def edits1(word):   splits     = [(word[:i], word[i:]) for i in range(len(word) + 1)]   deletes    = [a + b[1:] for a, b in splits if b]   transposes = [a + b[1] + b[0] + b[2:] for a, b in splits if len(b)>1]   replaces   = [a + c + b[1:] for a, b in splits for c in alphabet if b]   inserts    = [a + c + b     for a, b in splits for c in alphabet]   return set(deletes + transposes + replaces + inserts)def known_edits2(word):    return set(e2 for e1 in edits1(word) for e2 in edits1(e1) if e2 in NWORDS)def known(words): return set(w for w in words if w in NWORDS)def correct(word):    candidates = known([word]) or known(edits1(word)) or known_edits2(word) or [word]    return max(candidates, key=NWORDS.get)

读完整篇文章,感叹数学的美妙之外,也很喜欢类似这样的文章,将一个问题的原理讲清楚,再配上一些代码实例做说明,从小见大,从浅入深,对人很有启发。

这里简单的介绍下基于贝叶斯原理的拼写检查原理,再给出一个java版和C#版的实现。

拼写检查器的原理:给定一个单词,选择和它最相似的拼写正确的单词,需要使用概率论,而不是基于规则的判断。给定一个词 w,在所有正确的拼写词中,我们想要找一个正确的词 c, 使得对于 w 的条件概率最大, 也就是说:

argmaxc P(c|w)

按照 贝叶斯理论 上面的式子等价于:

argmaxc P(w|c) P(c) / P(w)

因为用户可以输错任何词, 因此对于任何 c 来讲, 出现 w 的概率 P(w) 都是一样的, 从而我们在上式中忽略它, 写成:

argmaxc P(w|c) P(c)

这个式子有三个部分, 从右到左, 分别是:

1. P(c), 文章中出现一个正确拼写词 c 的概率, 也就是说, 在英语文章中, c 出现的概率有多大呢? 因为这个概率完全由英语这种语言决定, 我们称之为做语言模型. 好比说, 英语中出现 the 的概率  P('the') 就相对高, 而出现  P('zxzxzxzyy') 的概率接近0(假设后者也是一个词的话).

2. P(w|c), 在用户想键入 c 的情况下敲成 w 的概率. 因为这个是代表用户会以多大的概率把 c 敲错成 w, 因此这个被称为误差模型.
3. argmaxc, 用来枚举所有可能的 c 并且选取概率最大的, 因为我们有理由相信, 一个(正确的)单词出现的频率高, 用户又容易把它敲成另一个错误的单词, 那么, 那个敲错的单词应该被更正为这个正确的.

最终计算的就是argmaxc P(w|c) P(c),在原文中,以“编辑距离”的大小来确定求最大概率的优先级: 编辑距离为1的正确单词比编辑距离为2的优先级高, 而编辑距离为0的正确单词优先级比编辑距离为1的高。

java版实现:

public class SpellCorrect {    private final HashMap
nWords = new HashMap
(); //读取语料库,存储在nWords中 public SpellCorrect(String file) throws IOException { BufferedReader in = new BufferedReader(new FileReader(file)); Pattern p = Pattern.compile("\\w+"); for(String temp = ""; temp != null; temp = in.readLine()){ Matcher m = p.matcher(temp.toLowerCase()); while(m.find()) nWords.put((temp = m.group()), nWords.containsKey(temp) ? nWords.get(temp) + 1 : 1); } in.close(); } //和word编辑距离为1的单词 private final HashSet
edits(String word){ HashSet
result = new HashSet<>(); for(int i=0; i < word.length(); ++i) //删除 result.add(word.substring(0, i) + word.substring(i+1)); for(int i=0; i < word.length()-1; ++i) //交换 result.add(word.substring(0, i) + word.substring(i+1, i+2) + word.substring(i, i+1) + word.substring(i+2)); for(int i=0; i < word.length(); ++i) //替换 for(char c='a'; c <= 'z'; ++c) result.add(word.substring(0, i) + String.valueOf(c) + word.substring(i+1)); for(int i=0; i <= word.length(); ++i) //插入 for(char c='a'; c <= 'z'; ++c) result.add(word.substring(0, i) + String.valueOf(c) + word.substring(i)); return result; } public final String correct(String word) { if(nWords.containsKey(word)) //如果在语料库中,直接返回 return word; HashSet
set = edits(word); HashMap
candidates = new HashMap
(); for(String s : set) //在语料库中找编辑距离为1且出现次数最多的单词为正确单词 if(nWords.containsKey(s)) candidates.put(nWords.get(s),s); if(candidates.size() > 0) return candidates.get(Collections.max(candidates.keySet())); for(String s : set) //在语料库中找编辑距离为2且出现次数最多的单词为正确单词 for(String w : edits(s)) if(nWords.containsKey(w)) candidates.put(nWords.get(w),w); return candidates.size() > 0 ? candidates.get(Collections.max(candidates.keySet())) : word; } public static void main(String args[]) throws IOException { SpellCorrect spellCorrect = new SpellCorrect("big.txt"); System.out.println(spellCorrect.correct("spel")); System.out.println(spellCorrect.correct("speling")); }}

C#版实现:

namespace SpellCorrect{    class Program    {        const string Alphabet = "abcdefghijklmnopqrstuvwxyz";        static IEnumerable
Edits1(string w) { // Deletion return (from i in Enumerable.Range(0, w.Length) select w.Substring(0, i) + w.Substring(i + 1)) // Transposition .Union(from i in Enumerable.Range(0, w.Length - 1) select w.Substring(0, i) + w.Substring(i + 1, 1) + w.Substring(i, 1) + w.Substring(i + 2)) // Alteration .Union(from i in Enumerable.Range(0, w.Length) from c in Alphabet select w.Substring(0, i) + c + w.Substring(i + 1)) // Insertion .Union(from i in Enumerable.Range(0, w.Length + 1) from c in Alphabet select w.Substring(0, i) + c + w.Substring(i)); } static string Correct(string word, Dictionary
nWords) { Func
, IEnumerable
> nullIfEmpty = c => c.Any() ? c : null; var candidates = nullIfEmpty(new[] { word }.Where(nWords.ContainsKey)) ?? nullIfEmpty(Edits1(word).Where(nWords.ContainsKey)) ?? nullIfEmpty((from e1 in Edits1(word) from e2 in Edits1(e1) where nWords.ContainsKey(e2) select e2).Distinct()); return candidates == null ? word : (from cand in candidates orderby (nWords.ContainsKey(cand) ? nWords[cand] : 1) descending select cand).First(); } static void Main(string[] args) { var nWords = (from Match m in Regex.Matches(File.ReadAllText("big.txt").ToLower(), "[a-z]+") group m.Value by m.Value) .ToDictionary(gr => gr.Key, gr => gr.Count()); Console.WriteLine(Correct("spel",nWords)); Console.WriteLine(Correct("speling",nWords)); Console.ReadKey(); } }}

两个版本的实现都参考了网上其他人的代码,略有修改。

语料库下载:

 

 

参考:

http://norvig.com/spell-correct.html

http://blog.youxu.info/spell-correct.html

 

转载于:https://www.cnblogs.com/luxiaoxun/p/4099567.html

你可能感兴趣的文章
php部分,一个用递归无限分类的方法
查看>>
android,eclipse
查看>>
SpringBoot 上下文获取注入的Bean
查看>>
归并排序的进一步理解
查看>>
C - Wooden Sticks
查看>>
Spring boot中普通工具类不能使用@Value注入yml文件中的自定义参数的问题
查看>>
[8.3] Magic Index
查看>>
(转·)WMPLib
查看>>
C语言结构体对齐
查看>>
跨应用Session共享
查看>>
Vue动态路由
查看>>
电脑小窍门
查看>>
IDEA环境设置
查看>>
Oracle行列转换小结
查看>>
W-D-S-链接地址
查看>>
3、图片处理
查看>>
HTML-日记3
查看>>
java enum 用法
查看>>
java常见文件操作
查看>>
python虚拟环境的安装和配置
查看>>