聊天机器人学习笔记整理系列-分词

来源:互联网 发布:java继承和多态实例 编辑:程序博客网 时间:2024/05/20 22:36
            聊天机器人学习笔记整理系列-分词                        iminders@qq.com                        作者:iminders声明: 1. 聊天机器人学习系列是整理自网上很大牛和机器学习专家所无私奉献的资料的。    具体引用的资料请看参考文献(点击直接可跳到源位置)。 2. 本文仅供技术术交流,非商用。所以每一部分具体的参考资料并没有详细对应。    如果某部分不小心侵犯了大家的利益,还望海涵,并联系博主删除。 3. 本人才疏学浅,整理总结的时候难免出错,还望各位前辈不吝指正。 4. 阅读本系列需要NLP、机器学习等等基础 5. 此博文若有错误,还需继续修正与增删。    还望大家多多指点。请联系:iminders@qq.com

目录

一、发展历史    1.1  图灵测试    1.2  Loebner Prize    1.3  ELIZA    1.4  AIML    1.5  Watson    1.6  图灵机器人、小I、Jimi、小冰等    1.7  Hubot、Slack    1.8  机器人分类    1.9  好的聊天机器人应该具备的特点    1.10 几种主流技术思路  二、中文分词    2.1  中文分词问题    2.2  评估方法    2.3  基于词表的分词算法        2.3.1 正向最大匹配法        2.3.2 逆向最大匹配法        2.3.3 双向扫描法        2.3.4 最少切分    2.4  基于规则的分词    2.5  基于统计的分词        2.5.1 N元文法模型        2.5.2 隐马尔科夫模型        2.5.3 CRF               2.6  基于神经网络的分词算法        2.7  流行分词器介绍

本节主要参考《统计自然语言处理》宗成庆

2.1 中文分词问题

词是最小的能够独立活动的有意义的语言成分,英文单词之间是以空格作为自然分界符的,而汉语是以字为基本的书写单位,词语之间没有明显的区分标记,因此,中文词语分析是中文信息处理的首要基础性工作。简单地讲,中文自动分词就是让计算机系统在汉语文本中的词与词之间自动加上空格或者其他边界标记。目前的分词技术还不尽人意,其主要困难来自三个方面:分词规范、歧义切分和未登录词。分词规范:    "词是什么"(词的抽象定义)及 "什么是词"(词的具体边界),这两个基本问题有点飘忽不定,迄今拿不出一个公认的、具有权威性的词表来。歧义切分:    交集型切分歧义,例如"结合成"         有2种切分:结合|成 , 结|合成         这种情况在中文文本中较为普遍,如:大学生、研究生物、从小学起、         为人民服务、等    组合型切分歧义:汉字串AB如果满足A、B、AB同时为词。如"起身"未登录词:    未登录词又称为生词,可以有两种解释:一是指已有的词表中没有收录的词;    二是指已有的训练语料中未曾出现过的词。    未登录词粗略分类:        1)新出现的普通词汇,如:博客,超女,恶搞,房奴,给力,然并卵        2)专有名词,人名地名和组织机构名等命名实体        3)专业名词和研究领域名称。如三聚氰胺

2.1 评估方法

评测组织:  SIGHAN bakeoff(http://www.sighan.org/)  CLP(http://www.cipsc.org.cn/clp2014/webpage/en/home_en.htm)为了公平地对比各种分词方法,一般采用SIGHAN规定的"封闭测试"原则:模型训练和测试过程中,仅允许使用SIGHAN提供的数据集进行训练和测试,其他任何语料、语典、人工知识和语言学规则都不能使用。评测指标:准确率(P)、召回率(R)F值(F-measure)

(P)=×100%

(P)=×100%

F=(β2+1)×P×Rβ2×P+R×100%

通常
β1
所以,F值又称F1值
F=2×P×RP+R×100%

2.3 基于字符串匹配

这种方法又叫做机械分词方法,它是按照一定的策略将待分析的汉字串与一个"充分大的"机器词典中的词条进行配,若在词典中找到某个字符串,则匹配成功(识别出一个词)按照扫描方向的不同,串匹配分词方法可以分为正向匹配和逆向匹配;按照不同长度优先匹配的情况,可以分为最大(最长)匹配和最小(最短)匹配;按照是否与词性标注过程相结合,又可以分为单纯分词方法和分词与标注相结合的一体化方法。
2.3.1 正向最大匹配法
正向最大匹配算法:从左到右将待分词文本中的几个连续字符与词表匹配,如果匹配上,则切分出一个词。但这里有一个问题:要做到最大匹配,并不是第一次匹配到就可以切分的 。我们来举个例子:待分词文本:content[]={"中","华","民","族","从","此","站","起","来","了","。"}词表:dict[]={"中华", "中华民族" , "从此","站起来"}(1) 从content[1]开始,当扫描到content[2]的时候,发现"中华"已经在词表dict[]中了。但还不能切分出来,因为我们不知道后面的词语能不能组成更长的词(最大匹配)。(2) 继续扫描content[3],发现"中华民"并不是dict[]中的词。但是我们还不能确定是否前面找到的"中华"已经是最大的词了。因为"中华民"是dict[2]的前缀。(3) 扫描content[4],发现"中华民族"是dict[]中的词。继续扫描下去。(4) 当扫描content[5]的时候,发现"中华民族从"并不是词表中的词,也不是词的前缀。因此可以切分出前面最大的词——"中华民族"。由此可见,最大匹配出的词必须保证下一个扫描不是词表中的词或词的前缀才可以结束。
算法实现

词表的内存表示: 很显然,匹配过程中是需要找词前缀的,因此我们不能将词
表简单的存储为Hash结构。在这里我们考虑一种高效的字符串前缀处理结构
——Trie树《Trie Tree 串集合查找》。这种结构使得查找每一个词
的时间复杂度为O(word.length),而且可以很方便的判断是否匹配成功或匹配
到了字符串的前缀。
下图是我们建立的Trie结构词典的部分,(词语例子:”中华”,”中华名族”,”中
间”,”感召”,”感召力”,”感受”)。
这里写图片描述

java实现关键部分参才杨尚川老师的中文分词算法 之 基于词典的正向最大匹配算法以及
Heart.X.Raid的正向最大匹配中文分词算法

//杨尚川老师对Trie树进行了优化,但是分词可以从左到右判断,//复杂度从O(n*n)降到O(n)    public List<Word> segImpl(String text) {        List<Word> result = new ArrayList<>();        // 文本长度        final int textLen = text.length();        // 分词时截取的字符串的最大长度=词典中最长词的长度        int len = getInterceptLength();        // 剩下未分词的文本的索引        int start = 0;        LOGGER.info("textLen:" + textLen);        // 只要有词未切分完就一直继续        while (start < textLen) {            // 当前查找位置            int currentLen = 1;            // 最近一次最大匹配位置            int lastContains = 1;            while (currentLen < len) {                // 在词典中没有找到,已经超过最大词典长度,应该按最近一次最大匹配切分                if (currentLen >= len || (start + currentLen) >= textLen) {                    addWord(result, text, start, lastContains);                    start += lastContains;                    break;                }                logInfo(text, start, currentLen, textLen);                // 在词典中找到,并且不是前辍,说明已经最大匹配                if (getDictionary().contains(text, start, currentLen)                        && !getDictionary().containsPrefix(text, start,                                currentLen)) {                    addWord(result, text, start, currentLen);                    // 从待分词文本中向后移动索引,滑过已经分词的文本                    start += currentLen;                    break;                }                // 在词典中找到,并且是前辍,并不确定是否为最大匹配,继续向后搜索                else if (getDictionary().contains(text, start, currentLen)                        && getDictionary().containsPrefix(text, start,                                currentLen)) {                    lastContains = currentLen;                    currentLen++;                }                // 在词典中没有找到,是前辍,继续向后搜索                else if (!getDictionary().contains(text, start, currentLen)                        && getDictionary().containsPrefix(text, start,                                currentLen)) {                    currentLen++;                }                // 在词典中没有找到,也不是前辍,并且不是字符,应该按最近一次最大匹配切分                else if (!getDictionary().contains(text, start, currentLen)                        && !getDictionary().containsPrefix(text, start,                                currentLen)) {                    addWord(result, text, start, lastContains);                    // 从待分词文本中向后移动索引,滑过已经分词的文本                    start += lastContains;                    break;                }            }        }        return result;    }
2.3.2 逆向最大匹配法
逆向最大匹配法与正向最大匹配法相似,不同点在于从后往前判断。对于汉语来说,逆向最大匹配算法比(正向)最大匹配算法更有效。
    public List<Word> segImpl(String text) {        Stack<Word> result = new Stack<>();        //文本长度        final int textLen=text.length();        //从未分词的文本中截取的长度        int len=getInterceptLength();        //剩下未分词的文本的索引        int start=textLen-len;        //处理文本长度小于最大词长的情况        if(start<0){            start=0;        }        if(len>textLen-start){            //如果未分词的文本的长度小于截取的长度            //则缩短截取的长度            len=textLen-start;        }        //只要有词未切分完就一直继续        while(start>=0 && len>0){            //用长为len的字符串查词典,并做特殊情况识别            while(!getDictionary().contains(text, start, len) && !RecognitionTool.recog(text, start, len)){                //如果长度为一且在词典中未找到匹配                //则按长度为一切分                if(len==1){                    break;                }                //如果查不到,则长度减一                //索引向后移动一个字,然后继续                len--;                start++;            }            addWord(result, text, start, len);            //每一次成功切词后都要重置截取长度            len=getInterceptLength();                        if(len>start){                //如果未分词的文本的长度小于截取的长度                //则缩短截取的长度                len=start;            }            //每一次成功切词后都要重置开始索引位置            //从待分词文本中向前移动最大词长个索引            //将未分词的文本纳入下次分词的范围            start-=len;        }        len=result.size();        List<Word> list = new ArrayList<>(len);        for(int i=0;i<len;i++){            list.add(result.pop());        }        return list;            }  
0 0
原创粉丝点击