Android版中文分词器:原理、接入和启动优化

Android版中文分词器:原理、接入和启动优化

中文分词功能是一项常用的基础功能,有很多开源的工程实现,目前能应用于Android手机端的中文分词器没有很完善的版本。经过调研,我选择了结巴分词,该开源工程思路简单,易于理解,分词效果也还不错,目前有众多语言版本,PYTHON、C++、JAVA、IOS等,暂时还没有Android版本,所以我在Java版本的基础上进行了移植,开发了适用于Android手机的结巴分词Android版(Github)

Android手机分词效果展示

相比于Java版本的实现,Android版将字典文件存放在Asset目录下进行读取,同时对字典加载速度进行了大幅优化。原始的Java版本加载完整的字典文件在测试手机上需要28秒,时间太长,经过优化,成功将加载时间降到1.5秒,分词速度1秒以内,满足了Android手机的启动速度要求。

本文将结合代码通过以下三个方面展开介绍:结巴分词的基本原理,Android版的接入方式,以及启动速度优化的实现。

结巴分词的原理

结巴分词采用两种方式进行分词,基于字典的分词和基于HMM(隐马尔科夫模型)的分词。模型会首先加载词典文件生成一个字典树,并利用该字典树进行一段中文的分词,比如“我要去五道口吃肯德基”被分词成“我/要/去/五道口/吃/肯德基”,其中被分成单蹦个的连续中文字符,如“我/要/去”会继续经过HMM模型进行二次分词,看能不能合并成完整的单词,这种设计是为了对不在字典中的字符提供一种兜底的分词方案,可以尽可能的避免单蹦个的分词结果,优化分词的效果。

下面是进行分词的主函数:

private List<String> sentenceProcess(String sentence) {
   
        List<String> tokens = new ArrayList<String>();
        int N = sentence.length();

        long start = System.currentTimeMillis();
        // 将一段文字转换成有向无环图,该有向无环图包含了跟字典文件得出的所有可能的单词切分
        Map<Integer, List<Integer>> dag = createDAG(sentence);

        Map<Integer, Pair<Integer>> route = calc(sentence, dag);

        int x = 0;
        int y = 0;
        String buf;
        StringBuilder sb = new StringBuilder();
        while (x < N) {
    // 遍历一遍贪心算法生成的最小路径分词结果,对单蹦个的字符看看能不能粘合成一个词汇
            y = route.get(x).key + 1;
            String lWord = sentence.substring(x, y);
            if (y - x == 1)
                sb.append(lWord);
            else {
   
                if (sb.length() > 0) {
   
                    buf = sb.toString();
                    sb = new StringBuilder();
                    if (buf.length() == 1) {
    // 如果两个单词之间只有一个单蹦个的字符,添加
                        tokens.add(buf);
                    } else {
   
                        if (wordDict.containsWord(buf)) {
    // 如果连续单蹦个的字符粘合成的一个单词在字典树里,作为一个单词添加
                            tokens.add(buf);
                        } else {
   
                            finalSeg.cut(buf, tokens); // 如果连续单蹦个的字符粘合成的一个单词不在字典树里,使用维特比算法计算每个字符BMES如何选择使得概率最大
                        }
                    }
                }
                tokens.add(lWord);
            }
            x = y;
        }
        buf = sb.toString();
        if (buf.length() > 0) {
    // 处理余下的部分
            if (buf.length() == 1) {
   
                tokens.add(buf);
            } else {
   
                if (wordDict.containsWord(buf)) {
   
                    tokens.add(buf);
                } else {
   
                    finalSeg.cut(buf, tokens);
                }
            }

        }
        return tokens;
    }

该函数首先通过createDAG将输入的一段文字转换成有向无环图(DAG),该有向无环图包含了根据字典文件得出的所有可能的单词切分,以每个字为单位,比如“我去五道口吃肯德基”,经过createDAG处理后会生成每个字和后面字符可能的单词组合,比如“我/去/五道口/吃/肯德基”/“我去/五道口/吃/肯德基”/“我去/五/道口/吃/肯德基”/“我/去/五道口/吃/肯德/基”等等。

然后经过calc函数,对这个DAG从后向前依据贪婪算法选择一种分词方式。实现比较简单,从最后一个字开始,找出从该字符前面字符跳转到当前字符概率最大的切分方式,然后依次往前走,直到完成整句话的切分。概率的大小依据是字典中该单词的频率值。

   /**
     * 计算有向无环图的一条最大路径,从后向前,利用贪心算法,每一步只需要找出到达该字符的最大概率字符作为所选择的路径
     *
     * @param sentence
     * @param dag
     * @return
     */
    private Map<Integer, Pair<Integer>> calc(String sentence, Map<Integer, List<Integer>> dag) {
   
        int N = sentence.length();
        HashMap<Integer
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值