基于贝叶斯公式的拼音输入法二元模型实现

和英语的直接输入不同,汉语输入法是通过拼音转译输入,而由于不同的拼音可以对应同一个字,而不同的字也可能存在多个拼音,是一个多对多的关系,因此在获得拼音的时候,需要能够准确地将其转换为正确的汉字,这就是拼音输入法的作用。本文介绍了一种基于贝叶斯公式的全拼拼音输入法实现,全拼也就是输入完整的拼音,不考虑缩写等情况。


以下是一段拼音:

huan ying guan zhu wo de bo ke

对于我们人来说,可以很容易的知道每个拼音对应的是哪个字:

欢迎关注我的博客

但是对于计算机,拼音所对应的每个汉字都是等价的,比如huan可以是“欢”,也可以是“换”,那么如果让计算机知道在这个句子里huan是“欢”?参考一下人将拼音转化为汉字的过程,可以发现在判断某个拼音的时候,我们并不是仅仅根据那一个拼音去判断,而是将其放在整个句子的上下文环境里去判断,也就是说在给定拼音句子的情况下,找到最大概率的汉字句子。翻译成数学语言就是,令X为汉字句子,Y为拼音句子,求

X = \arg\max P(X|Y)

根据贝叶斯公式

P(X|Y) = \frac{P(X)P(Y|X)}{P(Y)}

其中

P(Y)

为常量,因为拼音已经固定,

P(Y|X)

​​​​使用识别信度代替,

P(X) = P(w_1w_2 \dots w_n) = P(w_1)P(w_2|w_1)P(w_3|w_1w_2)\dots P(w_n|w_1w_2 \dots w_{n-1})

考虑到随着n的增加,该计算会越来越复杂,并且会增加数据稀疏性,因此使用二元模型,即每个字的概率只和它前一个字相关(当然也可以使用三元模型,即每个字的概率只和它前两个字相关),简化后

P(X) = P(w_1)P(w_2|w_1)P(w_3|w_2)\dots P(w_n|w_{n-1})

所以问题转化为

\prod_{i=1}^nP(w_{i}|w_{i-1})CF(w_i)

其中CF识别信度表示当汉字确定时,其拼音为该拼音的概率,在此可以默认为1,故问题的关键就是

P(w_{i}|w_{i-1})

又由概率公式

P(w_{i}|w_{i-1}) = \frac{P(w_iw_{i-1})}{P(w_{i-1})}

其中

P(w_iw_{i-1})

可以根据统计的思想认为代表w_{i-1}w_i在语料库同时出现的次数

P(w_{i-1})

代表w_{i-1}出现的次数。考虑到w_{i-1}w_i在语料库中可能从来没有同时出现过,因此将改写为

P = \alpha P(w_{i}|w_{i-1}) + (1-\alpha)P(w_i)

这样算法的思路就很清楚了,首先获得语料库,可以是大量的新闻稿,遍历所以新闻稿,统计每个字出现的次数以及每两个连续的字出现的次数,然后读入新的拼音句子从统计结果中选出最佳的汉字。需要注意的是我们需要最大化的是

\prod_{i=1}^nP(w_{i}|w_{i-1})

而不是

P(w_{i}|w_{i-1})

也就是说需要找到是整个句子的概率最大的,而不是某个字概率最大的汉字,这一点可以通过动态规划实现。


以下上代码:

记录每个拼音对应的汉字集

private void initPinyin(){
    char[] cs = Utility.readStrFromFile(Utility.HANZI_TABLE).toCharArray();
    List<String> ls = Utility.readListFromFile(Utility.PINYIN_TABLE);
    String[] ps = new String[ls.size()];
    String[] hs = new String[ls.size()];
    for (int i = 0; i < ls.size(); ++i){
        int border = ls.get(i).indexOf(" ");
        ps[i] = ls.get(i).substring(0, border);
        hs[i] = ls.get(i).substring(border + 1);
    }
    p_map = new HashMap<String, List<Character>>();
    for (int i = 1; i < cs.length; ++i){
        for (int j = 0; j < hs.length; ++j){
            if(hs[j].indexOf(cs[i]) != -1){
                if(!p_map.containsKey(ps[j])){
                    p_map.put(ps[j], new ArrayList<Character>());
                }
                p_map.get(ps[j]).add(cs[i]);
            }
        }
    }

    writeMap(p_map, Utility.PINYIN_MAP);
}

根据语料库进行统计(语料库为json格式)

private void initHanzi(){
    char[] cs = Utility.readStrFromFile(Utility.HANZI_TABLE).toCharArray();
    c_map = new HashMap<Character, Integer>();
    w_map = new HashMap<Character, Map<Character, Integer>>();
    for (int i = 1; i < cs.length; ++i){
        c_map.put(cs[i], 0);
        w_map.put(cs[i], new HashMap<Character, Integer>());
    }
    w_map.put('#', new HashMap<Character, Integer>());

    writeMap(c_map, Utility.HANZI_MAP);
    writeMap(w_map, Utility.TWO_HANZI_MAP);
}

private void updateHanzi(String news_file) {
    List<String> ss = Utility.readListFromFile(news_file);
    c_map = readMap(Utility.HANZI_MAP);
    w_map = readMap(Utility.TWO_HANZI_MAP);
    for (String s: ss) {
        s = s.substring(s.indexOf("{\"html\":"));
        JSONObject object = new JSONObject(s);
        String content = object.getString("html");
        char[] hanzi = content.toCharArray();
        char before = '#';
        for (char h : hanzi) {
            if (isChinese(h)) {
                Integer count = c_map.get(h);
                if (count != null) {
                    c_map.replace(h, count + 1);
                    if ((count = w_map.get(before).get(h)) != null) {
                        w_map.get(before).replace(h, count + 1);
                    } else {
                        w_map.get(before).put(h, 1);
                    }
                    before = h;
                } else {
                    before = '#';
                }
            } else {
                before = '#';
            }
        }
     }
     writeMap(c_map, Utility.HANZI_MAP);
     writeMap(w_map, Utility.TWO_HANZI_MAP);
}

使用动态规划进行翻译

public String[] predict(String pinyin){
    String[] ps = pinyin.split(" ");
    Queue<Possibility> queue = new PriorityQueue<Possibility>();
    queue.add(new Possibility("#", Utility.INIT_POSSIBILITY));
    return dynamicPlanning(ps, 0, queue);
}
  
private String[] dynamicPlanning(String[] ps, int index, Queue<Possibility> queue){
    if(index == ps.length){
        String[] result = new String[Math.min(queue.size(), Utility.OUT_SIZE)];
        for (int i = 0; i < result.length; ++i){
            result[i] = queue.poll().str.substring(1);
        }
        return result;
    }
    Possibility[] before = new Possibility[Math.min(queue.size(), Utility.MAX_SIZE)];
    double norm = queue.peek().p;
    for (int i = 0; i < before.length; ++i){
        before[i] = queue.poll();
        before[i].p /= norm;
    }
    queue.clear();
    List<Character> cs = p_map.get(ps[index]);
    for (Possibility s : before) {
        for (Character c : cs) {
            Possibility possibility = s.generateNext(c);
            if(!Double.isNaN(possibility.p)){
                queue.add(possibility);
            }
        }
    }
    return dynamicPlanning(ps, index + 1, queue);
}

class Possibility implements Comparable<Possibility> {
        String str;
        double p;

    Possibility(String str, double p) {
        this.str = str;
        this.p = p;
    }

    Possibility generateNext(char next){
        char before = str.charAt(str.length() - 1);
        return new Possibility(str + next, p * getPossibility(before, next));
    }

    private double getPossibility(char before, char next){
        int n_count = c_map.get(next);
        if(before == '#'){
            return n_count / Utility.RATIO;
        }else {
            int b_count = c_map.get(before);
            Integer integer = w_map.get(before).get(next);
            int bn_count = integer == null ? 0 : integer;
            return Utility.ALPHA * bn_count / b_count + (1 - Utility.ALPHA) * n_count / Utility.RATIO;
        }
    }
    
    public int compareTo(Possibility o) {
        return Double.compare(o.p, p);
    }
}

按概率高低输出概率最高的5个汉字句子

项目地址https://github.com/ChenErTong/PinYin

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
拼音输入法二元模型是指在拼音输入时,根据前一个拼音和当前拼音的组合,预测用户输入的词语。该模型可以提高输入法的准确性和输入速度。 在拼音输入法中,用户通过按键输入拼音输入法根据用户输入的拼音,从词库中筛选出可能的词语,并给出联想结果供用户选择。而二元模型则是在选择候选词时,结合前一个拼音和当前拼音的组合进行预测,提高输入法的准确性。 举个例子来说,当用户输入“shi”的时候,在二元模型中,根据前一个拼音和当前拼音的组合,“shi”的下一个可能是“shi”,也可能是“shou”或其他词语。输入法可以根据用户的习惯和使用情境来推测用户的意图,给出最合适的候选词供用户选择。 二元模型实现主要依赖于大规模语料库的统计分析和训练。通过分析大量的拼音输入数据,统计每个拼音出现在其他拼音后面的概率,然后根据这些概率来生成预测。这样就可以根据用户输入的前一个拼音和当前拼音的组合,预测下一个可能的拼音,从而提高输入法的准确性。 总的来说,拼音输入法二元模型通过预测用户输入的词语,提高输入法的准确性和输入速度。通过大规模语料库的统计分析和训练,二元模型可以根据前一个拼音和当前拼音的组合,预测下一个可能的拼音,给出最合适的候选词供用户选择。这样,用户在使用拼音输入法时,可以更加方便地输入所需的词语。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值