找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 4082|回复: 0

贝叶斯公式与拼写检查器

[复制链接]
发表于 2011-12-28 10:01:15 | 显示全部楼层 |阅读模式
今天复习了下概率论中贝叶斯的基础知识,动手写了个Java版本的简单的拼写检查器。
我们在使用Google时,当我们输入一个错误的单词,经常可以看到Google提示我们是不是要查找什么什么。
它是怎样做到的呢?现在我们就来实现一个简单的拼写检查器。




1. 什么是贝叶斯公式?


来看来自维基百科的定义:


贝叶斯定理

贝叶斯定理由英国数学家贝叶斯 ( Thomas Bayes 1702-1761 ) 发展,用来描述两个条件概率之间的关系,比如 P(A|B) 和 P(B|A)。按照定理 6 的乘法法则,P(A∩B)=P(A)·P(B|A)=P(B)·P(A|B),可以立刻导出贝叶斯定理:


如上公式也可变形为


另一个例子,现分别有 AB 两个容器,在容器 A 里分别有 7 个红球和 3 个白球,在容器 B 里有 1 个红球和 9 个白球,现已知从这两个容器里任意抽出了一个球,且是红球,问这个红球是来自容器 A 的概率是多少?

假设已经抽出红球为事件 B,从容器 A 里抽出球为事件 A,则有:P(B) = 8 / 20,P(A) = 1 / 2,P(B | A) = 7 / 10,按照公式,则有:



在上面的例子中,
事件A:要猜测事件的概率(从容器A里抽出球 - 要猜测和计算的事件)
事件B:现实已发生事件的概率(抽出红球 - 已经抽出红球,已经发生的事件)

正因贝叶斯公式可用于事件发生概率的推测,因此它广泛应用于计算机领域。从垃圾邮件的过滤,中文分词,机器翻译等等。下面的拼写检查器可以说是牛刀小试了。


2. 拼写检查器

第一步,以一个比较大的文本文件big.txt作为样本,分析每个单词出现的概率作为语言模型(Language Model)和词典。
big.txt的地址是:http://norvig.com/big.txt

第二步,如果用户输入的单词不在词典中,则产生编辑距离(Edit Distance)为2的所有可能单词。所谓编辑距离为1就是对用户输入的单词进行删除1个字符、添加1个字符、交换相邻字符、替换1个字符产生的所有单词。而编辑距离为2就是对这些单词再进行一次上述所有变换,因此最后产生的单词集会很大。可以与词典作差集,只保留词典中存在的单词。

第三步,假设事件c是我们猜测用户可能想要输入的单词,而事件w是用户实际输入的错误单词,根据贝叶斯公式可知:
     P(c|w) = P(w|c) * P(c) / P(w)。
这里的P(w)对于每个单词都是一样的,可以忽略。而P(w|c)是误差模型(Error Model),是用户想要输入w却输入c的概率,这是需要大量样本数据和事实依据来得到的,为了简单起见也忽略掉。因此,我们可以找出编辑距离为2的单词集中P(c)概率最大的几个来提示用户。


3. Java代码实现
  1. package com.cdai.studio.spellcheck;
  2. import java.io.BufferedReader;
  3. import java.io.FileReader;
  4. import java.io.IOException;
  5. import java.io.InputStreamReader;
  6. import java.util.Collections;
  7. import java.util.Comparator;
  8. import java.util.HashMap;
  9. import java.util.HashSet;
  10. import java.util.LinkedList;
  11. import java.util.List;
  12. import java.util.Map;
  13. import java.util.Map.Entry;
  14. import java.util.Set;
  15. import java.util.regex.Pattern;
  16. public class SpellCheck {
  17.        private static final char[] ALPHABET = "abcdefghijklmnopqrstuvwxyz".toCharArray();
  18.       
  19.        public static void main(String[] args) throws Exception {
  20.               new SpellCheck().start();
  21.        }
  22.       
  23.        public void start() throws IOException {
  24.               // 1.Build language model
  25.               Map<String, Double> langModel =
  26. buildLanguageModel("big.txt");
  27.               Set<String> dictionary =
  28. langModel.keySet();
  29.               
  30.               // 2.Read user input loop
  31.               BufferedReader reader
  32. = new BufferedReader(new InputStreamReader(System.in));
  33.               String input;
  34.               while ((input = reader.readLine()) != null)
  35. {
  36.                      input =
  37. input.trim().toLowerCase();
  38.                      if ("bye".equals(input))
  39.                            break;
  40.                      if (dictionary.contains(input))
  41.                            continue;
  42.                      long startTime = System.currentTimeMillis();
  43.                      
  44.                      // 3.Build set for word in edit distance and remove
  45. inexistent in dictionary
  46.                      Set<String>
  47. wordsInEditDistance2 = buildEditDistance2Set(langModel,
  48. input);
  49.                      wordsInEditDistance2.retainAll(dictionary);
  50.                      
  51.                      // 4.Calculate Bayes's probability
  52.                      // c - correct word we guess, w - wrong word user input
  53. in reality
  54.                      // argmax P(c|w) = argmax P(w|c) * P(c) / P(w)
  55.                      // we ignore P(w) here, because it's the same for all
  56. words
  57.                      List<String> guessWords =
  58. guessCorrectWord(langModel, wordsInEditDistance2);
  59.                      System.out.printf("Do you mean %s ? Cost time: %.3f
  60. second(s)\n",
  61.                                   guessWords.toString(),
  62. (System.currentTimeMillis() - startTime) / 1000D);
  63.               }
  64.               
  65.        }
  66.       
  67.        private Map<String, Double> buildLanguageModel(String
  68. sample)
  69.                      throws IOException {
  70.               Map<String, Double> langModel
  71. = new HashMap<String, Double>();
  72.               BufferedReader reader
  73. = new BufferedReader(new FileReader(sample));
  74.               Pattern pattern = Pattern.compile("[a-zA-Z]+");
  75.               String line;
  76.               int totalCnt = 0;
  77.               while ((line = reader.readLine()) != null)
  78. {
  79.                      String[] words =
  80. line.split(" ");
  81.                      for (String word : words) {
  82.                            if (pattern.matcher(word).matches()) {
  83.                                   word =
  84. word.toLowerCase();
  85.                                   Double wordCnt =
  86. langModel.get(word);
  87.                                   if (wordCnt == null)
  88.                                          langModel.put(word,
  89. 1D);
  90.                                   else
  91.                                          langModel.put(word,
  92. wordCnt + 1D);
  93.                                   totalCnt++;
  94.                            }
  95.                      }
  96.               }
  97.               reader.close();
  98.               
  99.               for (Entry<String, Double> entry :
  100. langModel.entrySet())
  101.                      entry.setValue(entry.getValue() /
  102. totalCnt);
  103.               
  104.               return langModel;
  105.        }
  106.       
  107.        private Set<String>
  108. buildEditDistance1Set(
  109.                      Map<String, Double>
  110. langModel,
  111.                      String input) {
  112.               Set<String> wordsInEditDistance
  113. = new HashSet<String>();
  114.               char[]
  115. characters = input.toCharArray();
  116.               
  117.               // Deletion: delete letter[i]
  118.               for (int i =
  119. 0; i < input.length(); i++)
  120.                      wordsInEditDistance.add(input.substring(0,i)
  121. + input.substring(i+1));
  122.               
  123.               // Transposition: swap letter[i] and
  124. letter[i+1]
  125.               for (int i =
  126. 0; i < input.length()-1; i++)
  127.                      wordsInEditDistance.add(input.substring(0,i)
  128. + characters[i+1] +
  129.                                   characters[i] +
  130. input.substring(i+2));
  131.               
  132.               // Alteration: change letter[i] to
  133. a-z
  134.               for (int i =
  135. 0; i < input.length(); i++)
  136.                      for (char c
  137. : ALPHABET)
  138.                            wordsInEditDistance.add(input.substring(0,i)
  139. + c + input.substring(i+1));
  140.               
  141.               // Insertion: insert new letter a-z
  142.               for (int i =
  143. 0; i < input.length(); i++)
  144.                      for (char c
  145. : ALPHABET)
  146.                            wordsInEditDistance.add(input.substring(0,i)
  147. + c + input.substring(i));
  148.               
  149.               return wordsInEditDistance;
  150.        }
  151.       
  152.        private Set<String>
  153. buildEditDistance2Set(
  154.                      Map<String, Double>
  155. langModel,
  156.                      String input) {
  157.               Set<String> wordsInEditDistance1 =
  158. buildEditDistance1Set(langModel, input);
  159.               Set<String> wordsInEditDistance2
  160. = new HashSet<String>();
  161.               for (String editDistance1 :
  162. wordsInEditDistance1)
  163.                      wordsInEditDistance2.addAll(buildEditDistance1Set(langModel,
  164. editDistance1));
  165.               wordsInEditDistance2.addAll(wordsInEditDistance1);
  166.               return wordsInEditDistance2;
  167.        }
  168.       
  169.        private List<String> guessCorrectWord(
  170.                      final Map<String, Double> langModel,
  171.                      Set<String>
  172. wordsInEditDistance) {
  173.               List<String> words = new LinkedList<String>(wordsInEditDistance);
  174.               Collections.sort(words, new Comparator<String>() {
  175.                      @Override
  176.                      public int compare(String word1, String word2)
  177. {
  178.                            return langModel.get(word2).compareTo(langModel.get(word1));
  179.                      }
  180.               });
  181.               return words.size() > 5 ? words.subList(0, 5) : words;
  182.        }
  183.       
  184. }
复制代码
运行结果:

raechel
Do you mean [reached, reaches, ranches, rachel] ? Cost time: 0.219 second(s)
thew
Do you mean [the, that, he, her, they] ? Cost time: 0.062 second(s)


虽然不是很准确,但是不是很有趣呢?如果感兴趣,我们可以继续深入学习。
有了兴趣和求知欲,并不断实践,才能学好编程。


参考文章

怎样写一个拼写检查器 http://blog.youxu.info/spell-correct.html



作者:dc_726 发表于2011-12-27 22:34:22 原文链接
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-4-25 14:19 , Processed in 0.015101 second(s), 6 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表