hanlp分词器接口设计:
提供外部接口:
分词器封装为静态工具类,并提供了简单的接口
标准分词 标准分词是最常用的分词器,基于HMM-Viterbi实现,开启了中国人名识别和音译人名识别,调用方法如下:
List HanLP.segment 其实是对 StandardTokenizer.segment 的包装。 /** * 分词 * * @param text 文本 * @return切分后的单词 */ publicstaticList returnStandardTokenizer.segment(text.toCharArray()); } /** * 创建一个分词器 * 与直接new一个分词器相比,使用本方法的好处是,以后HanLP升级了,总能用上最合适的分词器 * @return一个分词器 */ publicstaticSegmentnewSegment() { returnnewViterbiSegment();// Viterbi分词器是目前效率和效果的最佳平衡 } publicclassStandardTokenizer { /** * 预置分词器 */ publicstaticfinalSegmentSEGMENT = HanLP.newSegment(); /** * 分词 * @param text 文本 * @return分词结果 */ publicstaticList returnSEGMENT.seg(text.toCharArray()); } /** * 分词 * @param text 文本 * @return分词结果 */ publicstaticList returnSEGMENT.seg(text); } /** * 切分为句子形式 * @param text 文本 * @return句子列表 */ publicstaticList returnSEGMENT.seg2sentence(text); } } publicstaticSegmentnewSegment() { returnnewViterbiSegment();// Viterbi分词器是目前效率和效果的最佳平衡 } /** * Viterbi分词器 * 也是最短路分词,最短路求解采用Viterbi算法 * * @authorhankcs */ publicclassViterbiSegmentextendsWordBasedGenerativeModelSegment NLP分词 NLP分词 NLPTokenizer 会执行全部命名实体识别和词性标注。,调用方法如下: List publicclassNLPTokenizer { /** * 预置分词器 */ publicstaticfinalSegmentSEGMENT = HanLP.newSegment().enableNameRecognize(true).enableTranslatedNameRecognize(true).enableJapaneseNameRecognize(true).enablePlaceRecognize(true).enableOrganizationRecognize(true).enablePartOfSpeechTagging(true); publicstaticList returnSEGMENT.seg(text); } /** * 分词 * @param text 文本 * @return分词结果 */ publicstaticList returnSEGMENT.seg(text); } /** * 切分为句子形式 * @param text 文本 * @return句子列表 */ publicstaticList { returnSEGMENT.seg2sentence(text); } } 索引分词 索引分词 IndexTokenizer 是面向搜索引擎的分词器,能够对长词全切分,另外通过 term.offset 可以获取单词在文本中的偏移量。调用方法如下: List System.out.println(term+\" [\"+term.offset+\":\"+(term.offset+term.word.length())+\"]\"); } publicclassIndexTokenizer { /** * 预置分词器 */ publicstaticfinalSegmentSEGMENT = HanLP.newSegment().enableIndexMode(true); publicstaticList returnSEGMENT.seg(text); } /** * 分词 * @param text 文本 * @return分词结果 */ publicstaticList returnSEGMENT.seg(text); } /** * 切分为句子形式 * @param text 文本 * @return句子列表 */ publicstaticList returnSEGMENT.seg2sentence(text); } } 繁体分词 繁体分词 TraditionalChineseTokenizer 可以直接对繁体进行分词,输出切分后的繁体词语。调用方法如下: List /** * 繁体中文分词器 * * @authorhankcs */ publicclassTraditionalChineseTokenizer { /** * 预置分词器 */ publicstaticSegmentSEGMENT = HanLP.newSegment(); privatestaticList if(text.length()==0)returnCollections.emptyList(); LinkedList StringBuildersbSimplifiedChinese=newStringBuilder(text.length()); booleanequal=true; for(ResultTerm if(term.label==null)term.label=term.word; elseif(term.label.length()!=term.word.length())equal=false; sbSimplifiedChinese.append(term.label); } StringsimplifiedChinese=sbSimplifiedChinese.toString(); List intoffset=0; for(Termterm:termList) { term.word=text.substring(offset,offset+term.length()); term.offset=offset; offset+=term.length(); } } else { Iterator Iterator while(termIterator.hasNext()) { Termterm=termIterator.next(); term.offset=offset; if(offset>tsTerm.offset+tsTerm.word.length())tsTerm=tsIterator.next(); if(offset==tsTerm.offset&&term.length()==tsTerm.label.length()) { term.word=tsTerm.word; } elseterm.word=SimplifiedChineseDictionary.convertToTraditionalChinese(term.word); offset+=term.length(); } } returntermList; } publicstaticList List for(Stringsentence:SentencesUtil.toSentenceList(text)) { termList.addAll(segSentence(sentence)); } returntermList; } /** * 分词 * * @param text 文本 * @return分词结果 */ publicstaticList returnsegment(CharTable.convert(text)); } /** * 切分为句子形式 * * @param text 文本 * @return句子列表 */ publicstaticList List for(Stringsentence:SentencesUtil.toSentenceList(text)) { resultList.add(segment(sentence)); } } returnresultList; } } 极速词典分词 极速分词是词典最长分词,速度极其快,精度一般。调用方法如下: String text =\"江西鄱阳湖干枯,中国最大淡水湖变成大草原\"; System.out.println(SpeedTokenizer.segment(text)); long start = System.currentTimeMillis(); int pressure =1000000; for(int i =0; i < pressure;++i) { SpeedTokenizer.segment(text); } double costTime =(System.currentTimeMillis()- start)/(double)1000; System.out.printf(\"分词速度:%.2f字每秒\", text.length()* pressure / costTime); 在i7上跑出了2000万字每秒的速度。 使用的算法是 《Aho Corasick自动机结合DoubleArrayTrie极速多模式匹配》 /** * 极速分词,基于Double Array Trie实现的词典分词,适用于“高吞吐量”“精度一般”的场合 * @authorhankcs */ publicclassSpeedTokenizer { /** * 预置分词器 */ publicstaticfinalSegmentSEGMENT = new DoubleArrayTrieSegment(); publicstaticList returnSEGMENT.seg(text.toCharArray()); } /** * 分词 * @param text 文本 * @return分词结果 */ publicstaticList returnSEGMENT.seg(text); } /** * 切分为句子形式 * @param text 文本 * @return句子列表 */ publicstaticList returnSEGMENT.seg2sentence(text); } } 接下来介绍的分词器是由用户动态创建,使用场景不常见的分词器。 N-最短路径分词 N最短路分词器 NShortSegment 比最短路分词器( DijkstraSegment )慢,但是效果稍微好一些,对命名实体识别能力更强。调用方法如下: Segment nShortSegment =new NShortSegment().enableCustomDictionary(false).enablePlaceRecognize(true).enableOrganizationRecognize(true); Segment shortestSegment =new ViterbiSegment().enableCustomDictionary(false).enablePlaceRecognize(true).enableOrganizationRecognize(true); String[] testCase =new String[]{ \"刘喜杰石国祥会见吴亚琴先进事迹报告团成员\", }; for(String sentence : testCase) { System.out.println(\"N-最短分词:\"+ nShortSegment.seg(sentence)+\"\\n最短路分词:\"+ shortestSegment.seg(sentence)); } 一般场景下最短路分词的精度已经足够,而且速度比N最短路分词器快几倍,请酌情选择。 /** * N最短分词器 * * @authorhankcs */ publicclassNShortSegmentextendsWordBasedGenerativeModelSegment /** * 最短路径分词 * @authorhankcs */ publicclassDijkstraSegmentextendsWordBasedGenerativeModelSegment { CRF分词 基于CRF模型和BEMS标注训练得到的分词器。调用方法如下: Segmentsegment=newCRFSegment(); segment.enablePartOfSpeechTagging(true); List if(term.nature==null) { System.out.println(\"识别到新词:\"+term.word); } } publicclassCRFSegmentextendsCharacterBasedGenerativeModelSegment 动态创建和配置 在上面的例子中,一些工具类包装了配置好的分词器。HanLP同时支持用户动态创建分词器和配置分词器。 创建分词器 既可以用new创建,也可以用工具类创建,推荐后者,可以应对未来的版本升级。 通过new关键字 分词器是Java对象,可以用传统的new关键字创建任意的分词器。 ViterbiSegment 也是最短路分词,最短路求解采用Viterbi算法: Segmentsegment=newViterbiSegment() DijkstraSegment 依然是最短路分词,最短路求解采用Dijkstra算法: Segmentsegment=newDijkstraSegment() DijkstraSegment比ViterbiSegment慢一点,但是调试信息更加丰富。 NShortSegment N最短分词器: Segmentsegment=newNShortSegment() 算法很美,速度很慢。 AhoCorasickSegment 使用AhoCorasickDoubleArrayTrie实现的最长分词器: Segmentsegment=newAhoCorasickSegment() 应该是速度最快的词典分词了。 CRFSegment 基于CRF的分词器: Segmentsegment=newCRFSegment() 应用场景不多。 通过HanLP.newSegment 通过此工厂方法得到的是当前版本速度和效果最平衡的分词器: Segmentsegment=HanLP.newSegment(); 推荐用户始终通过工具类HanLP调用,这么做的好处是,将来HanLP升级后,用户无需修改调用代码。 配置分词器 所有分词器都是 Segment 的子类, Segment 提供以下配置接口: /** * 设为索引模式 * * @return */ publicSegmentenableIndexMode(booleanenable) /** * 开启词性标注 * @param enable * @return */ publicSegmentenablePartOfSpeechTagging(booleanenable) /** * 开启人名识别 * @param enable * @return */ publicSegmentenableNameRecognize(booleanenable) /** * 开启地名识别 * @param enable * @return */ publicSegmentenablePlaceRecognize(booleanenable) /** * 开启机构名识别 * @param enable * @return */ publicSegmentenableOrganizationRecognize(booleanenable) /** * 是否启用用户词典 * * @param enable */ publicSegmentenableCustomDictionary(booleanenable) /** * 是否启用音译人名识别 * * @param enable */ publicSegmentenableTranslatedNameRecognize(booleanenable) /** * 是否启用日本人名识别 * * @param enable */ publicSegmentenableJapaneseNameRecognize(booleanenable) /** * 是否启用偏移量计算(开启后Term.offset才会被计算) * @param enable * @return */ publicSegmentenableOffset(booleanenable) /** * 是否启用所有的命名实体识别 * @param enable * @return */ publicSegmentenableAllNamedEntityRecognize(booleanenable) 用户可以使用链式语法对Segment执行创建和配置操作,一气呵成: SegmentshortestSegment=newViterbiSegment().enableCustomDictionary(false).enablePlaceRecognize(true).enableOrganizationRecognize(true); 对于工具类中的分词器,也可以使用暴露出来的SEGMENT成员对其进行配置: Stringtext=\"泽田依子是上外日本文化经济学院的外教\"; System.out.println(StandardTokenizer.segment(text)); StandardTokenizer.SEGMENT.enableAllNamedEntityRecognize(true); System.out.println(StandardTokenizer.segment(text)); 工具类: 字符集识别工具类 字节转换工具类 词典与词语和词性相关的工具类 计算权值工具类 预定义 分割成句子工具类 文本工具类 层叠马尔科夫分词-张华平分词 张华平分词流程图 代码详解: 主要思想 该分词系统的主要是思想是先通过CHMM(层叠形马尔可夫模型)进行分词,通过分层,既增加了分词的准确性,又保证了分词的效率.共分五层,如下图所示: 实现思路 基本思路:先进行原子切分,然后在此基础上进行N-最短路径粗切分,找出前N个最符合的切分结果,生成二元分词表,然后生成分词结果,接着进行词性标注并完成主要分词步骤. Java 代码梳理 Segsentence函数: Segsentence 流程: public List WordNetwordNetOptimum = new WordNet(sentence); WordNetwordNetAll = new WordNet(sentence); // char[] charArray = text.toCharArray(); // Nshort生成粗分结果 List booleanNERexists = false; for (List if (HanLP.Config.DEBUG) { System.out.println(\"粗分结果\" + convert(vertexList, false)); } // 实体命名识别 if (config.ner) { wordNetOptimum.addAll(vertexList); intpreSize = wordNetOptimum.size(); if (config.nameRecognize) { PersonRecognition.Recognition(vertexList, wordNetOptimum, wordNetAll); } if (config.translatedNameRecognize) { TranslatedPersonRecognition.Recognition(vertexList, wordNetOptimum, wordNetAll); } if (config.japaneseNameRecognize) { JapanesePersonRecognition.Recognition(vertexList, wordNetOptimum, wordNetAll); } if (config.placeRecognize) { PlaceRecognition.Recognition(vertexList, wordNetOptimum, wordNetAll); } if (config.organizationRecognize) { // 层叠隐马模型——生成输出作为下一级隐马输入 vertexList = Dijkstra.compute(GenerateBiGraph(wordNetOptimum)); wordNetOptimum.addAll(vertexList); OrganizationRecognition.Recognition(vertexList, wordNetOptimum, wordNetAll); } if (!NERexists && preSize != wordNetOptimum.size()) { NERexists = true; } } } List //命名实体识别后生成更新的词网 Graphgraph = GenerateBiGraph(wordNetOptimum); vertexList = Dijkstra.compute(graph); if (HanLP.Config.DEBUG) { System.out.printf(\"细分词网:\\n%s\\n\System.out.printf(\"细分词图:%s\\n\ } } // 数字识别 if (config.numberQuantifierRecognize) { mergeNumberQuantifier(vertexList, wordNetAll, config); } // 如果是索引模式则全切分 if (config.indexMode) { returndecorateResultForIndexMode(vertexList, wordNetAll); } // 是否标注词性 if (config.speechTagging) { speechTagging(vertexList); } returnconvert(vertexList, config.offset); } } C++ 代码流程梳理 //Paragraph Segment and POS Tagging bool CResult::ParagraphProcessing(char *sParagraph,char *sResult) { ........ Processing(sSentence,1); //Processing and output the result of current sentence. Output(m_pResult[0],sSentenceResult,bFirstIgnore); //Output to the imediate result ....... } 3.主要的分词处理是在Processing()方法里面发生的,下面我们对它进行进一步的分析. bool CResult::Processing(char *sSentence,unsigned int nCount) { ...... //进行二叉分词 m_Seg.BiSegment(sSentence, m_dSmoothingPara,m_dictCore,m_dictBigram,nCount); ...... //在此处进行词性标注 m_POSTagger.POSTagging(m_Seg.m_pWordSeg[nIndex],m_dictCore,m_dictCore); ...... } 原子切分: 根据分隔符断句 ICTCLAS分词的第一步就是原子分词。但在进行原子切分之前,首先要进行断句处理。所谓断句,就是根据分隔符、回车换行符等语句的分隔标志,把源字符串分隔成多个稍微简单一点的短句,再进行分词处理,最后再把各个分词结果合起来,形成最终的分词结果。 //Paragraph Segment and POS Tagging bool CResult::ParagraphProcessing(char *sParagraph,char *sResult) { char *sSentence,sChar[3]; char *sSentenceResult; unsigned int nLen=strlen(sParagraph)+13; sSentence=new char[nLen];//malloc buffer sSentenceResult=new char[nLen*3];//malloc buffer sSentence[0]=0; unsigned int nPosIndex=0,nParagraphLen=strlen(sParagraph),nSentenceIndex=0; sChar[2]=0; sResult[0]=0;//Init the result bool bFirstIgnore=true; strcpy(sSentence,SENTENCE_BEGIN);//Add a sentence begin flag int ntest=strlen(sSentence); //把文章按照标点分隔符切割成一段段,进行处理 while(nPosIndex sChar[0]=sParagraph[nPosIndex];//Get a char sChar[1]=0; if(sParagraph[nPosIndex]<0) {//double byte char nPosIndex+=1; sChar[1]=sParagraph[nPosIndex]; } nPosIndex+=1; /* #define SEPERATOR_C_SENTENCE \"。!?:;„\" #define SEPERATOR_C_SUB_SENTENCE \"、,()“”‘’\" #define SEPERATOR_E_SENTENCE \"!?:;\" #define SEPERATOR_E_SUB_SENTENCE \ #define SEPERATOR_LINK \"\\n\\r \" */ 分隔符断句 if(CC_Find(SEPERATOR_C_SENTENCE,sChar)||CC_Find(SEPERATOR_C_SUB_ SENTENCE,sChar)||strstr(SEPERATOR_E_SENTENCE,sChar)||strstr(SEPERATOR_E_SUB_SENTENCE,sChar)||strstr(SEPERATOR_LINK,sChar)) {//Reach end of a sentence.Get a whole sentence if(!strstr(SEPERATOR_LINK,sChar))//Not link seperator { strcat(sSentence,sChar); } if(sSentence[0]!=0&&strcmp(sSentence,SENTENCE_BEGIN)!=0) { if(!strstr(SEPERATOR_C_SUB_SENTENCE,sChar)&&!strstr(SEPERATOR_E_SU B_SENTENCE,sChar)) strcat(sSentence,SENTENCE_END);//Add sentence ending flag Processing(sSentence,1);//Processing and output the result of current sentence. Output(m_pResult[0],sSentenceResult,bFirstIgnore);//Output to the imediate result //bFirstIgnore=true; strcat(sResult,sSentenceResult);//Store in the result buffer } if(strstr(SEPERATOR_LINK,sChar))//Link the result with the SEPERATOR_LINK { strcat(sResult,sChar); strcpy(sSentence,SENTENCE_BEGIN);//Add a sentence begin flag //sSentence[0]=0;//New sentence, and begin new segmentation //bFirstIgnore=false; } else if(strstr(SEPERATOR_C_SENTENCE,sChar)||strstr(SEPERATOR_E_SENTENCE,sChar)) { strcpy(sSentence,SENTENCE_BEGIN);//Add a sentence begin flag //sSentence[0]=0;//New sentence, and begin new segmentation //bFirstIgnore=false; } else { strcpy(sSentence,sChar);//reset current sentence, and add the previous end at begin position } } else //Other chars and store in the sentence buffer strcat(sSentence,sChar); } //特殊情况处理 if(sSentence[0]!=0&&strcmp(sSentence,SENTENCE_BEGIN)!=0) { strcat(sSentence,SENTENCE_END);//Add sentence ending flag Processing(sSentence,1);//Processing and output the result of current sentence. Output(m_pResult[0],sSentenceResult,bFirstIgnore);//Output to the imediate result strcat(sResult,sSentenceResult);//Store in the result buffer } delete [] sSentence;//FREE sentence buffer delete [] sSentenceResult;//free buffer return true; } 输出实例 \"始##始即可进行原子分词,\" + \",所谓原子,\"+ \是指该短句中不可分割的最小语素单位。末##末\" 原子分词 分成短句之后,即可进行原子分词,所谓原子,是指该短句中不可分割的最小语素单位。一个汉字、短句前后的开始结束标识字段、全角标点符号、连在一起的数字字母单字节字符等。最后一种情况可以举例说明,比如:三星SHX-132型号的手机1元钱,则SHX-132、1都是一个原子,其它的每个汉字是一个原子。 与c++相似的函数实现,为了速度考虑,采用了上述快速原子切分 C++ 预定义 #define CT_SENTENCE_BEGIN 1//Sentence begin #define CT_SENTENCE_END 4//Sentence ending #define CT_SINGLE 5//SINGLE byte #define CT_DELIMITER CT_SINGLE+1//delimiter #define CT_CHINESE CT_SINGLE+2//Chinese Char #define CT_LETTER CT_SINGLE+3//HanYu Pinyin #define CT_NUM CT_SINGLE+4//HanYu Pinyin #define CT_INDEX CT_SINGLE+5//HanYu Pinyin #define CT_OTHER CT_SINGLE+12//Other #define POSTFIX_SINGLE \"坝邦堡杯城池村单岛道堤店洞渡队法峰府冈港阁宫沟国海号河湖环集江奖礁角街井郡坑口矿里岭楼路门盟庙弄牌派坡铺旗桥区渠泉人山省市水寺塔台滩坛堂厅亭屯湾文屋溪峡县线乡巷型洋窑营屿语园苑院闸寨站镇州庄族陂庵町\" #define POSTFIX_MUTIPLE {\"半岛\草原\城市\大堤\大公国\大桥\地区\帝国\渡槽\港口\高速公路\高原\公路\公园\共和国\谷地\广场\国道\海峡\胡同\机场\集镇\教区\街道\口岸\码头\煤矿\牧场\农场\盆地\平原\丘陵\群岛\沙漠\沙洲\山脉\山丘\水库\隧道\特区\铁路\新村\雪峰\盐场\盐湖\渔场\直辖市\自治区\自治县\自治州\ #define TRANS_ENGLISH \"·—阿埃艾爱安昂敖奥澳笆芭巴白拜班邦保堡鲍北贝本比毕彼别波玻博勃伯泊卜布才采仓查差柴彻川茨慈次达大戴代丹旦但当道德得的登迪狄蒂帝丁东杜敦多额俄厄鄂恩尔伐法范菲芬费佛夫福弗甫噶盖干冈哥戈革葛格各根古瓜哈海罕翰汗汉豪合河 赫亨侯呼胡华霍基吉及加贾坚简杰金京久居君喀卡凯坎康考柯科可克肯库奎拉喇莱来兰郎朗劳勒雷累楞黎理李里莉丽历利立力连廉良列烈林隆卢虏鲁路伦仑罗洛玛马买麦迈曼茅茂梅门蒙盟米蜜密敏明摩莫墨默姆木穆那娜纳乃奈南内尼年涅宁纽努诺欧帕潘畔庞培佩彭皮平泼普其契恰强乔切钦沁泉让热荣肉儒瑞若萨塞赛桑瑟森莎沙山善绍舍圣施诗石什史士守斯司丝苏素索塔泰坦汤唐陶特提汀图土吐托陀瓦万王旺威韦维魏温文翁沃乌吾武伍西锡希喜夏相香歇谢辛新牙雅亚彦尧叶依伊衣宜义因音英雍尤于约宰泽增詹珍治中仲朱诸卓孜祖佐伽娅尕腓滕济嘉津赖莲琳律略慕妮聂裴浦奇齐琴茹珊卫欣逊札哲智兹芙汶迦珀琪梵斐胥黛\" #define TRANS_RUSSIAN \"·阿安奥巴比彼波布察茨大德得丁杜尔法夫伏甫盖格哈基加坚捷金卡科可克库拉莱兰勒雷里历利连列卢鲁罗洛马梅蒙米姆娜涅宁诺帕泼普奇齐乔切日萨色山申什斯索塔坦特托娃维文乌西希谢亚耶叶依伊以扎佐柴达登蒂戈果海赫华霍吉季津柯理琳玛曼穆纳尼契钦丘桑沙舍泰图瓦万雅卓兹\" #define TRANS_JAPANESE \"安奥八白百邦保北倍本比滨博步部彩菜仓昌长朝池赤川船淳次村大代岛稻道德地典渡尔繁饭风福冈高工宫古谷关广桂贵好浩和合河黑横恒宏后户荒绘吉纪佳加见健江介金今进井静敬靖久酒菊俊康可克口梨理里礼栗丽利立凉良林玲铃柳隆鹿麻玛美萌弥敏木纳南男内鸟宁朋片平崎齐千前浅桥琴青清庆秋丘曲泉仁忍日荣若三森纱杉山善上伸神圣石实矢世市室水顺司松泰桃藤天田土万望尾未文武五舞西细夏宪相小孝新星行雄秀雅亚岩杨洋阳遥野也叶一伊衣逸义益樱永由有佑宇羽郁渊元垣原远月悦早造则泽增扎宅章昭沼真政枝知之植智治中忠仲竹助椎子佐阪坂堀荻菅薰浜濑鸠筱\" //Translation type #define TT_ENGLISH 0 #define TT_RUSSIAN 1 #define TT_JAPANESE 2 //Seperator type #define SEPERATOR_C_SENTENCE \"。!?:;„\" #define SEPERATOR_C_SUB_SENTENCE \"、,()“”‘’\" #define SEPERATOR_E_SENTENCE \"!?:;\" #define SEPERATOR_E_SUB_SENTENCE \ #define SEPERATOR_LINK \"\\n\\r \" //Sentence begin and ending string #define SENTENCE_BEGIN \"始##始\" #define SENTENCE_END \"末##末\" //Seperator between two words #define WORD_SEGMENTER \"@\" 原子分词后的实例如下图二所示: 查词典,生成词网 经过原子分词后,源字符串成了一个个的最小语素单位。下面的初次切分,就是把原子之间所有可能的组合都先找出来。算法是用两个循环来实现,第一层遍历整个原子单位,第二层是当找到一个原子时,不断把后面相邻的原子和该原子组合到一起,访问词典库看它能否构成一个有意义有词组。 用数学方法可以做如下描述: 有一个原子序列:A(n)(0<=n for(int I=0;I for(int j=I+1;j 把s加入到初次切分的列表中; 记录该词组的词性; 记录该词组所在表中的坐标位置及其它信息; } else break; } } 核心词典:CoreNatureDictionary.txt 初次切分后的数据结构如下图一所示: 图一 分词用例”他说的确实在理”经过初次切分后的结果如下图二所示: 图二 用二维表来表示图一中的链表结构如下图二所示: 图三 从上图三可以看出,在二维表中,初次切分后的词组,第一次字相同的在同一行,最后一个字相同的在同一列,原来的原子在对称轴上. 对上述过程进行处理的参考源代码如下: ICTCLAS解析 bool CSegment::BiSegment(char *sSentence, double dSmoothingPara, CDictionary &dictCore, CDictionary &dictBinary, unsigned int nResultCount) { ...... //在此处完成上图一的处理结果,生成一个链表结构 m_graphSeg.GenerateWordNet(sSentence,dictCore,true);//Generate words array ...... 在生成图二所示的表结构之后,进一步生成二叉图表. .... //Generate the biword link net BiGraphGenerate(m_graphSeg.m_segGraph,aBiwordsNet,dSmoothingPara,dictBinary,dictCore); .... 对该函数进行深入分析: bool CSegment::BiGraphGenerate(CDynamicArray &aWord, CDynamicArray &aBinaryWordNet,double dSmoothingPara,CDictionary &DictBinary,CDictionary &DictCore) { ...... //获取链表的长度 m_nWordCount=aWord.GetTail(&pTail);//Get tail element and return the words count if(m_npWordPosMapTable) {//free buffer delete [] m_npWordPosMapTable; m_npWordPosMapTable=0; } //分配一个数组,存贮图二中每个结点的词的位置,如下图四所示 if(m_nWordCount>0)//Word count is greater than 0 m_npWordPosMapTable=new int[m_nWordCount];//Record the position of possible words //把指针指向当前链表的开头,并计算每个词的位置,然后把它放到数组中 pCur=aWord.GetHead(); while(pCur!=NULL)//Set the position map of words { m_npWordPosMapTable[nWordIndex++]=pCur->row*MAX_SENTENCE_LEN+pCur->col; pCur=pCur->next; } //遍历所有的结点,并计算相临两个词之间的平滑值 pCur=aWord.GetHead(); while(pCur!=NULL)// { if(pCur->nPOS>=0)//It's not an unknown words dCurFreqency=pCur->value; else//Unknown words dCurFreqency=DictCore.GetFrequency(pCur->sWord,2); //取得和当前结点列值(col)相同的下个结点 aWord.GetElement(pCur->col,-1,pCur,&pNextWords); while(pNextWords&&pNextWords->row==pCur->col)//Next words { //前后两个词用@分隔符连接起来 strcpy(sTwoWords,pCur->sWord); strcat(sTwoWords,WORD_SEGMENTER); strcat(sTwoWords,pNextWords->sWord); //计算两个连接词的边长 nTwoWordsFreq=DictBinary.GetFrequency(sTwoWords,3); //Two linked Words frequency dTemp=(double)1/MAX_FREQUENCE; //计算平滑值
* 这是一个工厂方法>seg2sentence(Stringtext) {
>seg2sentence(Stringtext)
>seg2sentence(Stringtext) {
>seg2sentence(Stringtext) {
>resultList=newLinkedList
>(); {
>seg2sentence(Stringtext) {
>coarseResult = BiSegment(sentence, 2, wordNetOptimum, wordNetAll);
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- 7swz.com 版权所有 赣ICP备2024042798号-8
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务