一个简单的拼写检查器

简介:

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

原作者python代码:

复制代码
import re, collections

def 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 model

NWORDS = 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<String, Integer> nWords = new HashMap<String, Integer>();

    //读取语料库,存储在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<String> edits(String word){
        HashSet<String> 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<String> set = edits(word);
        HashMap<Integer, String> candidates = new HashMap<Integer, String>();
        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<string> 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<string,int> nWords)
        {
            Func<IEnumerable<string>, IEnumerable<string>> 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();
        }
    }
}
复制代码

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

语料库下载:big.txt

 

 

参考:

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

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

 

    本文转自阿凡卢博客园博客,原文链接:http://www.cnblogs.com/luxiaoxun/p/4099567.html,如需转载请自行联系原作者



相关文章
|
前端开发 NoSQL Redis
网页设计,若依修改05(It must be done)-----强退用户
网页设计,若依修改05(It must be done)-----强退用户
|
存储 索引
Elasticsearch中父子文档的关联:利用Join类型赋予文档的层级关系
Elasticsearch中父子文档的关联:利用Join类型赋予文档的层级关系
|
Rust 并行计算 安全
Rust中的并行与并发优化:释放多核性能
Rust语言以其内存安全和高效的并发模型在并行计算领域脱颖而出。本文深入探讨了Rust中的并行与并发优化技术,包括使用多线程、异步编程、以及并行算法等。通过理解并应用这些技术,Rust开发者可以有效地利用多核处理器,提高程序的性能和响应能力。
|
机器学习/深度学习 算法 C++
「算法」方阵打印数字问题
想当初我是真的暴力到了机制,面对一个vs的图标,一行一行找规律把它打印出来,简直佛了...🤺。这里先汇总两种题: 方阵蛇形填数, 矩阵上三角。
269 0
「算法」方阵打印数字问题
|
SQL 移动开发 开发框架
1小时入门天猫精灵有屏音箱语音技能开发
本文将教你在天猫精灵上怎么开发技能或者应用。文中使用PHP的知名框架:Laravel,只需1小时帮你入门天猫精灵有屏技能开发。支持语音交互。欢迎大家转发,分享,文末还有源码共享,欢迎大家下载。
1小时入门天猫精灵有屏音箱语音技能开发
|
弹性计算 负载均衡 容灾
阿里云服务器地域和可用区怎么选择?地域节点测试IP地址
阿里云服务器地域是什么?阿里云可用区是什么?阿里云服务器地域和可用区如何选择?
1323 0
阿里云服务器地域和可用区怎么选择?地域节点测试IP地址
|
安全 Ubuntu Shell
Vulhub靶场搭建
安装Docker 在安装、使用Docker的过程中出现错误比较多,所以这一节来说明一下如何正确安装最新版本的Docker,(国内机器)并且配置加速器。 一键安装Docker 这是推荐方式。在未安装过Docker的机器上,root权限执行如下命令即可一键安装最新版Docker: curl -s https://get.docker.com/ | sh 如果你已经安装过老版本Docker(且不是用这个一键安装脚本安装的),请先卸载Docker(例如sudo apt purge --autoremove docker.io)。 如果你不想使用这种方式安装Docker,也可以使用系统自带的包管理工具来
916 1
|
SQL 开发框架 弹性计算
Asp.Net Web 项目部署到阿里云 Windows版本服务器
Asp.Net Web 项目部署到阿里云 Windows版本服务器 前言:网上Asp.Net Web 项目部署到阿里云 Windows版本服务器的说法不一,最后参考多方上传后终于部署成功,写此文章总结一下网上的知识和自己的部署经验,以防自己忘记 工具 1. Visual Stuio 2019 2. sql server2019 3. 阿里云服务器 ECS 4. windows 11家庭版
|
弹性计算 Ubuntu 安全
阿里云服务器可选操作系统汇总(公共镜像类型)
本文介绍了阿里云服务器公共镜像类型可选系统汇总,可供新手用户了解当前选择公共镜像类镜像时有哪些可选的操作系统。
1585 0
阿里云服务器可选操作系统汇总(公共镜像类型)