您好,欢迎来到微智科技网。
搜索
您的当前位置:首页hanlp中文分词器解读

hanlp中文分词器解读

来源:微智科技网
中文分词器解析

hanlp分词器接口设计:

提供外部接口:

分词器封装为静态工具类,并提供了简单的接口

标准分词 标准分词是最常用的分词器,基于HMM-Viterbi实现,开启了中国人名识别和音译人名识别,调用方法如下:

List termList = HanLP.segment(\"商品和服务\"); System.out.println(termList); 

HanLP.segment 其实是对 StandardTokenizer.segment 的包装。

/** * 分词 *

* @param text 文本 * @return切分后的单词 */

publicstaticListsegment(Stringtext) {

returnStandardTokenizer.segment(text.toCharArray()); } /**

* 创建一个分词器
* 这是一个工厂方法

* 与直接new一个分词器相比,使用本方法的好处是,以后HanLP升级了,总能用上最合适的分词器

* @return一个分词器 */

publicstaticSegmentnewSegment() { returnnewViterbiSegment();// Viterbi分词器是目前效率和效果的最佳平衡 }

publicclassStandardTokenizer { /**

* 预置分词器 */

publicstaticfinalSegmentSEGMENT = HanLP.newSegment(); /** * 分词

* @param text 文本 * @return分词结果 */

publicstaticListsegment(Stringtext) {

returnSEGMENT.seg(text.toCharArray()); } /** * 分词

* @param text 文本 * @return分词结果 */

publicstaticListsegment(char[]text) {

returnSEGMENT.seg(text); } /**

* 切分为句子形式 * @param text 文本

* @return句子列表 */

publicstaticList>seg2sentence(Stringtext) {

returnSEGMENT.seg2sentence(text); } }

publicstaticSegmentnewSegment() {

returnnewViterbiSegment();// Viterbi分词器是目前效率和效果的最佳平衡 } /**

* Viterbi分词器

* 也是最短路分词,最短路求解采用Viterbi算法 *

* @authorhankcs */

publicclassViterbiSegmentextendsWordBasedGenerativeModelSegment

NLP分词 NLP分词 NLPTokenizer 会执行全部命名实体识别和词性标注。,调用方法如下: List termList = NLPTokenizer.segment(\"中国科学院计算技术研究所的宗成庆教授正在教授自然语言处理课程\"); System.out.println(termList); NLP分词 NLPTokenizer 会执行全部命名实体识别和词性标注。  所以速度比标准分词慢,并且有误识别的情况。

publicclassNLPTokenizer { /**

* 预置分词器 */

publicstaticfinalSegmentSEGMENT =

HanLP.newSegment().enableNameRecognize(true).enableTranslatedNameRecognize(true).enableJapaneseNameRecognize(true).enablePlaceRecognize(true).enableOrganizationRecognize(true).enablePartOfSpeechTagging(true);

publicstaticListsegment(Stringtext) {

returnSEGMENT.seg(text); } /** * 分词

* @param text 文本 * @return分词结果 */

publicstaticListsegment(char[]text) {

returnSEGMENT.seg(text); } /**

* 切分为句子形式 * @param text 文本 * @return句子列表 */

publicstaticList>seg2sentence(Stringtext)

{

returnSEGMENT.seg2sentence(text); } }

索引分词 索引分词 IndexTokenizer 是面向搜索引擎的分词器,能够对长词全切分,另外通过 term.offset 可以获取单词在文本中的偏移量。调用方法如下: ListtermList=IndexTokenizer.segment(\"主副食品\"); for(Termterm:termList) {

System.out.println(term+\"

[\"+term.offset+\":\"+(term.offset+term.word.length())+\"]\"); }

publicclassIndexTokenizer { /**

* 预置分词器 */

publicstaticfinalSegmentSEGMENT = HanLP.newSegment().enableIndexMode(true); publicstaticListsegment(Stringtext) {

returnSEGMENT.seg(text); } /** * 分词

* @param text 文本 * @return分词结果 */

publicstaticListsegment(char[]text) {

returnSEGMENT.seg(text); } /**

* 切分为句子形式 * @param text 文本 * @return句子列表 */

publicstaticList>seg2sentence(Stringtext) {

returnSEGMENT.seg2sentence(text); } }

繁体分词 繁体分词 TraditionalChineseTokenizer 可以直接对繁体进行分词,输出切分后的繁体词语。调用方法如下:

ListtermList=TraditionalChineseTokenizer.segment(\"大衛貝克漢不僅僅是名著名球員,球場以外,其妻為前辣妹合唱團成員維多利亞·碧咸,亦由於他擁有突出外表、百變髮型及正面的形象,以至自己品牌的男士香水等商品,及長期擔任運動品牌Adidas的代言人,因此對大眾傳播媒介和時尚界等方面都具很大的影響力,在足球圈外所獲得的認受程度可謂前所未見。\"); System.out.println(termList);

/**

* 繁体中文分词器 *

* @authorhankcs */

publicclassTraditionalChineseTokenizer { /**

* 预置分词器 */

publicstaticSegmentSEGMENT = HanLP.newSegment();

privatestaticListsegSentence(Stringtext) {

if(text.length()==0)returnCollections.emptyList();

LinkedList>tsList=CommonAhoCorasickSegmentUtil.segment(text,TraditionalChineseDictionary.trie);

StringBuildersbSimplifiedChinese=newStringBuilder(text.length()); booleanequal=true;

for(ResultTermterm:tsList) {

if(term.label==null)term.label=term.word;

elseif(term.label.length()!=term.word.length())equal=false; sbSimplifiedChinese.append(term.label); }

StringsimplifiedChinese=sbSimplifiedChinese.toString(); ListtermList=SEGMENT.seg(simplifiedChinese); if(equal) {

intoffset=0;

for(Termterm:termList) {

term.word=text.substring(offset,offset+term.length()); term.offset=offset; offset+=term.length(); } } else {

IteratortermIterator=termList.iterator();

Iterator>tsIterator=tsList.iterator(); ResultTermtsTerm=tsIterator.next(); intoffset=0;

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; }

publicstaticListsegment(Stringtext) {

ListtermList=newLinkedList();

for(Stringsentence:SentencesUtil.toSentenceList(text)) {

termList.addAll(segSentence(sentence)); }

returntermList; } /** * 分词 *

* @param text 文本 * @return分词结果 */

publicstaticListsegment(char[]text) {

returnsegment(CharTable.convert(text)); } /**

* 切分为句子形式 *

* @param text 文本

* @return句子列表 */

publicstaticList>seg2sentence(Stringtext) {

List>resultList=newLinkedList>(); {

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(); publicstaticListsegment(Stringtext) {

returnSEGMENT.seg(text.toCharArray()); } /** * 分词

* @param text 文本 * @return分词结果 */

publicstaticListsegment(char[]text) {

returnSEGMENT.seg(text); } /**

* 切分为句子形式 * @param text 文本 * @return句子列表 */

publicstaticList>seg2sentence(Stringtext) {

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);

ListtermList=segment.seg(\"你看过穆赫兰道吗\"); System.out.println(termList); for(Termterm:termList) {

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 ListsegSentence(char[] sentence) {

WordNetwordNetOptimum = new WordNet(sentence); WordNetwordNetAll = new WordNet(sentence); // char[] charArray = text.toCharArray(); // Nshort生成粗分结果

List>coarseResult = BiSegment(sentence, 2, wordNetOptimum, wordNetAll);

booleanNERexists = false;

for (ListvertexList : coarseResult) {

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; } } }

ListvertexList = coarseResult.get(0); if (NERexists) {

//命名实体识别后生成更新的词网

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{//Find a whole sentence which separated by ! . \\n \\r

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;IString s=A[I];

for(int j=I+1;js+=A[j]; if(s是一个词组){

把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; //计算平滑值

dValue=-log(dSmoothingPara*(1+dCurFreqency)/(MAX_FREQUENCE+80000)+(1-dSmoothingPara)*((1-dTemp)*nTwoWordsFreq/(1+dCurFreqency)+dTemp)); //-log{a*P(Ci-1)+(1-a)P(Ci|Ci-1)} Note 0if(pCur->nPOS<0)//Unknown words: P(Wi|Ci);while known words:1 dValue+=pCur->value;

//Get the position index of current word in the position map table

nCurWordIndex=BinarySearch(pCur->row*MAX_SENTENCE_LEN+pCur->col,m_npWordPosMapTable,m_nWordCount);

nNextWordIndex=BinarySearch(pNextWords->row*MAX_SENTENCE_LEN+pNextWords->col,m_npWordPosMapTable,m_nWordCount);

//把当前结点在位置表中的位置和下个结点在位置表中的位置及平滑值/词性插入到二叉链表中

aBinaryWordNet.SetElement(nCurWordIndex,nNextWordIndex,dValue,pCur->nPOS);

pNextWords=pNextWords->next;//Get next word }

pCur=pCur->next; }

return true; }

核心词典的二元接续词典CoreNatureDictionary.ngram.txt

图四

最终生成的键表结果如下图五所示:

图五

对应的二维图表表示形式如下图六所示:

图六

其中小数值代表了相临两个词之间的耦合成度,即构成更大长度词的可能性的机率,值越小说明两个词成词的可能性越大。

hanlp解析

public List>BiSegment(char[] sSentence, int nKind, WordNet wordNetOptimum, WordNet wordNetAll) {

List>coarseResult = new LinkedList>(); ////////////////生成词网//////////////////// GenerateWordNet(wordNetAll); ///////////////生成词图//////////////////// Graphgraph = GenerateBiGraph(wordNetAll);

///////////////N-最短路径//////////////////// NShortPathnShortPath = new NShortPath(graph, nKind);

ListspResult = nShortPath.getNPaths(nKind * 2); if (spResult.size() == 0) {

thrownew RuntimeException(nKind + \"-最短路径求解失败,请检查上面的词网是否存在负圈或悬孤节点\"); }

//////////////日期、数字合并策略 for (int[] path : spResult) {

Listvertexes = graph.parsePath(path); GenerateWord(vertexes, wordNetOptimum); coarseResult.add(vertexes); }

return coarseResult; }

为了对比方便放到上面去了,背景黑灰图片的java代码。

至此生成了如下的有向图。

N最短路径:

ICTCLAS和别的分司系统不一样的地方就是于--N最短路径分词算法。所谓N最短路径其实就是最短路径和最大路径的折中,保留前N个最优路径。这样做的目的就是对这两种方法取长补

短,既能达到一个比较理解的分词不达意效果,又能保证分词不达意速度。在此处,我们中国人的中庸思想被完美体现:)。

ICTCLASCNShortPath实现N-short

在源程序中,N最短路径是在CNShortPath类里里面实现的。

bool CSegment::BiSegment(char *sSentence, double dSmoothingPara, CDictionary &dictCore, CDictionary &dictBinary, unsigned int nResultCount) {

......

//调用构造函数,生成一个二维链表,如下图一所示。每个链表节点是一个队列,数据结构如下图二所示

CNShortPath sp(&aBiwordsNet,nResultCount);

//最短路径算法实现 sp.ShortPath();

//输出最短路径

sp.Output(nSegRoute,false,&m_nSegmentCount);

.....

}

图一

图二

对NShortPath的构造函数分进一步分析:

CNShortPath::CNShortPath(CDynamicArray *apCost,unsigned int nValueKind) {

//研究(四)中图五所示的链表

m_apCost=apCost;//Set the cost

m_nValueKind=nValueKind;//Set the value kind //顶点数

m_nVertex=apCost->m_nCol+1; if(m_nVertexm_nRow+1)

m_nVertex=apCost->m_nRow+1;//Get the vertex numbers

m_pParent=new CQueue*[m_nVertex-1];//not including the first node m_pWeight=new ELEMENT_TYPE *[m_nVertex-1];

for(unsigned int i=0;im_pParent[i]=new CQueue[nValueKind];

m_pWeight[i]=new ELEMENT_TYPE[nValueKind]; } }

int CNShortPath::ShortPath() {

unsigned int nCurNode=1,nPreNode,i,nIndex; ELEMENT_TYPE eWeight; PARRAY_CHAIN pEdgeList;

for(;nCurNodeCQueue queWork;

eWeight=m_apCost->GetElement(-1,nCurNode,0,&pEdgeList);//Get all the edges

//对研究(四)中的图六所示的表,按列优进行遍历,把每个列的数据放到一个临时工作队列中

while(pEdgeList!=0 && pEdgeList->col==nCurNode) {

nPreNode=pEdgeList->row;

eWeight=pEdgeList->value;//Get the value of edges for(i=0;iif(nPreNode>0)//Push the weight and the pre node infomation {

if(m_pWeight[nPreNode-1][i]==INFINITE_VALUE) break;

//把行值(ROW)放入队列中,并且保证队列的排序是按Weight值从小到大排列

queWork.Push(nPreNode,i,eWeight+m_pWeight[nPreNode-1][i]); } else {

queWork.Push(nPreNode,i,eWeight); break; }

}//end for

pEdgeList=pEdgeList->next;

}//end while

//Now get the result queue which sort as weight. //Set the current node information for(i=0;im_pWeight[nCurNode-1][i]=INFINITE_VALUE; }

//memset((void *),(int),sizeof(ELEMENT_TYPE)*); //init the weight i=0;

//临时工作队列中取出前面的一个,并记路路径长度的和

while(iif(m_pWeight[nCurNode-1][i]==INFINITE_VALUE) m_pWeight[nCurNode-1][i]=eWeight;

else if(m_pWeight[nCurNode-1][i]i++;//Go next queue and record next weight if(i==m_nValueKind)//Get the last position break;

m_pWeight[nCurNode-1][i]=eWeight; }

m_pParent[nCurNode-1][i].Push(nPreNode,nIndex); } }//end for

return 1; }

m_nParent的最终结果如下图三所示,它在当前位置记录该词的父节点位置(对应于研究(四)中的图四)

图三

int CNShortPath::Output(int **nResult,bool bBest,int *npCount) {//sResult is a string array

......

GetPaths(m_nVertex-1,i,nResult,bBest); }

//获取分词输出路径。指研究(四)中的图四

void CNShortPath::GetPaths(unsigned int nNode,unsigned int nIndex,int **nResult,bool bBest) {

CQueue queResult;

unsigned int nCurNode,nCurIndex,nParentNode,nParentIndex,nResultIndex=0;

if(m_nResultCount>=MAX_SEGMENT_NUM)//Only need 10 result return ;

nResult[m_nResultCount][nResultIndex]=-1;//Init the result queResult.Push(nNode,nIndex); nCurNode=nNode; nCurIndex=nIndex; bool bFirstGet;

while(!queResult.IsEmpty()) {

while(nCurNode>0)//

{//Get its parent and store them in nParentNode,nParentIndex

if(m_pParent[nCurNode-1][nCurIndex].Pop(&nParentNode,&nParentIndex,0,false,true)!=-1) {

nCurNode=nParentNode; nCurIndex=nParentIndex; }

if(nCurNode>0)

queResult.Push(nCurNode,nCurIndex); }

if(nCurNode==0)

{ //Get a path and output

nResult[m_nResultCount][nResultIndex++]=nCurNode;//Get the first node bFirstGet=true;

nParentNode=nCurNode;

while(queResult.Pop(&nCurNode,&nCurIndex,0,false,bFirstGet)!=-1) {

nResult[m_nResultCount][nResultIndex++]=nCurNode; bFirstGet=false; nParentNode=nCurNode; }

nResult[m_nResultCount][nResultIndex]=-1;//Set the end m_nResultCount+=1;//The number of result add by 1

if(m_nResultCount>=MAX_SEGMENT_NUM)//Only need 10 result return ;

nResultIndex=0;

nResult[m_nResultCount][nResultIndex]=-1;//Init the result

if(bBest)//Return the best result, ignore others return ; }

queResult.Pop(&nCurNode,&nCurIndex,0,false,true);//Read the top node while(queResult.IsEmpty()==false&&(m_pParent[nCurNode-1][nCurIndex].IsSingle()||m_pParent[nCurNode-1][nCurIndex].IsEmpty(true))) {

queResult.Pop(&nCurNode,&nCurIndex,0);//Get rid of it

queResult.Pop(&nCurNode,&nCurIndex,0,false,true);//Read the top node

}

if(queResult.IsEmpty()==false&&m_pParent[nCurNode-1][nCurIndex].IsEmpty(true)==false) {

m_pParent[nCurNode-1][nCurIndex].Pop(&nParentNode,&nParentIndex,0,false,false);

nCurNode=nParentNode; nCurIndex=nParentIndex; if(nCurNode>0)

queResult.Push(nCurNode,nCurIndex); } } }

最终得到最短路么(0,1,2,3,6,9,11,12),里面的数值分别对应研究(四)中图四的下标,到此分词的第一大步就结束了,并形成最终结果:始##始/他/说/的/确实/在/理/末##末

基于N-short最短路径方法的中文词语粗分模型

1、引言

“词是最小的能够活动的有意义的语言成分。”[1], 但汉语是以字为基本的书写单位,词语之间没有明显的区分标记,因此,中文词语分析是中文信息处理的基础与关键。而中文词语分析一般包括3个过程:预处理过程的词语粗切分,切分排歧与未登录词识别、词性标注。目前中文词语分析采取的主要步骤是:先采取最大匹配、最短路径、概率统计方法、全切分等方法,得到一个相对最好的粗分结果,然后进行排歧、未登录词识别,最后标注词性。例如:北大计算语言所分词系统采用了统计方法进行词语粗分[2,3,5],北航1983年的CDWS系统则采用了正向或逆向最大匹配方法[4,5],而清华大学的SEGTAG系统采用的是全切分方法[5]。在实际的系统中,这三个过程可能相互交叉、反复融合,也可能不存在明显的先后次序。

预处理过程产生的粗切分结果是后续过程的处理对象,粗分结果的准确性与包容性(即必须涵盖正确结果),直接影响系统最终的准确率、召回率。预处理得到的粗分结果一旦不能成功召回正确的结果,后续处理一般很难补救,最终的词语分析结果必然会导致错误,粗分结果的召回率往往是整个词语分析召回率的上限。同时,粗分结果集的大小也决定了后续处理的搜索空间与时间效率,最终也会影响整个系统的运行效率。因此,词语粗分是后续处

理的基础和前提,其关键在于如何以尽量高的效率寻找数量极少、涵盖最终结果的粗分结果集。

我们采取当前常用的粗分方法,对大规模真实语料库的进行测试实验,词语粗切分的召回率均不足93.50%。因此,改进预处理过程中的汉语词语粗分方法,是提高排岐、未登录词识别、词性标注最终效果的基础性措施,也是提高中文词语分析质量的重要途径。

本文提出了一种旨在提高召回率同时兼顾准确率的词语粗分模型——基于N-最短路径方法的中文词语粗分模型。根据我们针对大规模真实语料库的对比测试,粗分结果的召回率有较大提高,模型的运行效率也令人满意,该方法是行之有效的。本文第二节将系统描述非统计模型的基本思想与实现,然后加入词频信息,得到N-最短路径的一元统计模型,最后给出对比实验的结果及分析。

2、基于N-最短路径的非统计粗分模型

粗切分的目标是:快速(粗分结果集尽量少)、高召回率(即可能的涵盖最终结果)。一个很直接的研究思路是:先快速的找出包含正确结果在内的N(N≥1)种粗分结果。然后综合考虑速度和召回率,通过试验,确定N的最佳值,最终得到涵盖最终结果在内的尽量小的粗分结果集。

2.1基本思想

我们采取的是最短路径的改进方法——N-最短路径方法。其基本思想是:根据词典,找出字串中所有可能的词,构造词语切分有向无环图。每个词对应图中的一条有向边,并赋给相应的边长(权值)。然后针对该切分图,在起点到终点的所有路径中,求出长度值按严格升序排列(任何两个不同位置上的值一定不等,下同)依次为第1,第2,…,第i,…,第N的路径集合作为相应的粗分结果集。如果两条或两条以上路径长度相等,那么他们的长度并列第i,都要列入粗分结果集,而且不影响其他路径的排列序号,最后的粗分结果集合大小大于或等于N。

2.2 模型求解

设待分字串 S=c1 c2……cn,其中ci(i =1,2,…n)为单个的字, n 为串的长度,n≥1。建立一个节点数为n+1的切分有向无环图G,各节点编号依次为V0,V1,V2,…,Vn。

通过以下两种方法建立G所有可能的词边。

(1) 相邻节点Vk-1, Vk之间建立有向边,边的长度值为Lk,边对应的词默认为ck (k=1,2,…n)

(2) 若w=ci ci+1……cj 是一个词,则节点Vi-1, Vj之间建立有向边,边的长度值为Lw,边对应的词为w (0这样,待分字串S中包含的所有词与切分有向无环图G中的边一一对应。如图1所示:

c1 cn cci-1 cj+1 ci V1 2 V0 Vi-1 Vj Vn L1 Ln L2 L i-1 Lj+1 Li w=ci ci+1……cj

Lw 图1切分有向无环图

在非统计粗分模型中,我们假定所有的词都是对等的,为了计算方便,不妨将词的对应边长的边长均设为1。

设:Path(i,j)为所有从Vi到Vj的路径集合;Length(path) 为路径path的长度,其值等于path中所有边的长度之和;LS为G中所有从V0到Vn路径的长度集合;NLS为V0到Vn的N-最短路径长度集合;NSP为V0到Vn的N-最短路径集合;而RS是最终的N-最短路径粗分结果集. 则有:

LS={len| len=Length (path), path∈Path(0,n)}

NLS的定义: NLSLS, | NLS |=min(|LS|,N);  a∈LS-NLS , b∈NLS → a < b NSP={path| path∈Path(0,n), Length(path)∈NLS }

RS={w1w2…wm| wi是path 的第i条边对应的词, i=1,2, … , m , 其中path∈NSP }。

RS是NSP对应的分词结果,即我们所求的粗分结果集。因此,N-最短路径方法词语粗切问题转化为:如何求解有向无环图G的集合NSP。

2.3 N-最短路径求解与复杂度分析

求解有向无环图G的集合NSP,可以采取贪心方法 [6]。我们使用的算法是基于Dijkstra[3]的一种简单扩展。改进的地方在于:每个节点处记录N个最短路径值,并记录相应路径上当前节点的前驱。如果同一长度对应多条路径,必须同时记录这些路径上当前节点的前驱。最后通过回溯即可求出NSP。

我们以“他说的确实在理”为例, 给出了3-最短路径的求解过程。如图2所示。

的确 实在 1 1 的 确 实 在 说 理 他 0 2 3 4 5 6 7 1 1 1 1 1 1 1 1 在理 确实 1 1 Table (4) Table (7) Table (6) Table (5) 编号 长度 1 2

3 4 (2,0) 前驱 (2,1) (3,1) 2 5 编号 长度 1 4 前驱 (3,1) (4,1) (4,2) 3 6 编号 长度 1 2 4 5 前驱 (4,1) (4,2) (5,1) (5,2) 3 (1,0) 图2:“他说的确实在理”的求解过程示例(N=3) (其中虚线是回溯出的是第一条最短路径,对应的粗分结果为:“他/说/的/确实/在理/”)

编号 长度 前驱 1 2 5 6 7 (5,1) (6,1) (5,2) (6,2) (6,3) Dijkstra算法的时间复杂度为O(n2),它求的是图中所有点到单源点的最短路径,而在切分有向图中的应用,有2个本质区别:首先有向边的源点编号均小于终点编号,即所有边的方向一致;其次,我们求的是有向图首尾节点的N-最短路径。所以我们使用的算法中,运行时间与n(字串长度)、N (最短路径数)以及某个字作为末字的平均次数k(等于总词数除以末字总数,对应的是切分图中结点入度的平均值)成正比。整个算法的时间复杂度是O(n*N*k)。

3、基于N-最短路径的统计粗分模型

在非统计模型构建粗切有向无环图有向边的过程中,我们给每个词的对应边长度赋值为1。随着字串长度n和最短路径数N的增大,长度相同的路径数急剧增加,同时粗切分结果数量必然上升。例如,N=2时,句子“在北京人民大会堂会见参加全国工作会议

和全国系统打击经济犯罪先进集体表彰大会代表时要求大家要充分认识打击经济犯罪工作的艰巨性和长期性”的粗切结果居然有138种之多。大量的切分结果对后期的处理,以及整个性能的提高是非常不利的。

3.1基本原理

假定一个词串W经过信道传送,由于噪声干扰而丢失了词界的切分标志,到输出端便成了汉字串C,这是一个典型的噪声-信道问题[7]。N-最短路径方法词语粗分模型可以相应的改进为:求取W,使得概率P(W|C)的值是最大N个。为了简化P(W|C)的计算,我们采用的是一元统计模型,即只引入词频并假定词与词之间是相互。基于以上分析,我们引入词w i的词频信息P(wi),对模型进行了改进,得到一个基于N-最短路径的一元统计模型。

3.2 一元统计粗分模型的求解与实现

P(W|C)的求解如下:

P(W|C)=P(W)P(C|W)/P(C) „„„„„„„„„„„„„„„„„„„„„„„„„① 其中,P(C)是汉字串的概率,它是一个常数,不必考虑。从词串恢复到汉字串的概率P(C|W)=1(只有唯一的一种方式)。

因此,我们的目标就是确定P(W)最大N种的切分结果集合。

W=w1 w2……wm是字串S=c1 c2……cn的一种切分结果。wi 是一个词,P(wi)表示wi的出现的概率。在大规模语料库训练的基础上,根据大数定理[8],即:在大样本统计的前提下,样本的频率接近于其概率值。所以P(wi)的极大似然估计值[9]等于词频,有:

P(wi) ≈ki/

kj0mj(其中ki为wi在训练样本中出现的次数)„„„„„„„„„„②

粗切分阶段,为了简单处理,我们仅仅采取了概率上下文无关文法[10],即假设上下文无关,词与词之间相互。因此,根据①②,我们可以得到:

则W的联合概率P(W)=

P(w)≈ii1i1mm(ki /

mkj0mj)„„„„„„„„„„„„③

m为了处理的方便,令P*(W)= — lnP(W)=

[lnP(w)]≈ii0i0[-ln(ki /

kj0mj)]

=

i0m[ln(

kj0mj)- ln ki]„„„„„„④

那么就可以将③极大值的问题转化为求解④极小值的问题。适当修改切分有向无环图G边的长度(加1主要是为了数据的简单平滑处理):

(1)* 的长度值Lk=-ln (0+1), (k=1,2,…n)

(2)* w=ci ci+1……cj对应的有向边为,其长度值Lw= ln(

kj0mj+m)-ln(ki +1)

针对修改了边长后的切分有向无环图G*,直接使用2.3中的算法,就可以实现问题的最终求解。

未登录词识别:

bool CSpan::PersonRecognize(CDictionary &personDict) {

char sPOS[MAX_WORDS_PER_SENTENCE]=\"z\

//0 1 2 3 4 5

char sPatterns[][5]={ \"BBCD\ \"BXD\

//BBCD BBC BBE BBZ BCD BEE BE BG double dFactor[]={0.003606,0.000021,0.001314,0.000315,0.656624, 0.000021,0.146116,0.009136, // BXD BZ CDCD CD EE FB Y XD 0.000042,0.0371,0,0.090367,0.000273,0.009157,0.034324,0.009735,0 };

//About parameter: /*

BBCD 343 0.003606 BBC 2 0.000021 BBE 125 0.001314 BBZ 30 0.000315 BCD 62460 0.656624 BEE 0 0.000000 BE 139 0.146116 BG 869 0.009136 BXD 4 0.000042 BZ 3707 0.0371 CD 8596 0.090367 EE 26 0.000273 FB 871 0.009157 Y 3265 0.034324 XD 926 0.009735 */

//The person recognition patterns set //BBCD:姓+姓+名1+名2; //BBE: 姓+姓+单名; //BBZ: 姓+姓+双名成词; //BCD: 姓+名1+名2; //BE: 姓+单名;

//BEE: 姓+单名+单名;韩磊磊 //BG: 姓+后缀

//BXD: 姓+姓双名首字成词+双名末字 //BZ: 姓+双名成词; //B: 姓

//CD: 名1+名2; //EE: 单名+单名; //FB: 前缀+姓

//XD: 姓双名首字成词+双名末字 //Y: 姓单名成词

int nPatternLen[]={4,3,3,3,3,3,2,2,3,2,4,2,2,2,1,2,0};

for(int i=1;m_nBestTag[i]>-1;i++)//Convert to string from POS sPOS[i]=m_nBestTag[i]+'A'; sPOS[i]=0;

int j=1,k,nPos;//Find the proper pattern from the first POS

int nLittleFreqCount;//Counter for the person name role with little frequecy bool bMatched=false; while(j0;k++) { if(strncmp(sPatterns[k],sPOS+j,nPatternLen[k])==0&&strcmp(m_sWords[j-1],\"·\")!=0&&strcmp(m_sWords[j+nPatternLen[k]],\"·\")!=0) {//Find the proper pattern k if(strcmp(sPatterns[k],\"FB\")==0&&(sPOS[j+2]=='E'||sPOS[j+2]=='C'||sPOS[j+2]=='G')) {//Rule 1 for exclusion:前缀+姓+名1(名2): 规则(前缀+姓)失效; continue; } /* if((strcmp(sPatterns[k],\"BEE\")==0||strcmp(sPatterns[k],\"EE\")==0)&&strcmp(m_sWords[j+nPatternLen[k]-1],m_sWords[j+nPatternLen[k]-2])!=0) 如:韩磊磊 */

{//Rule 2 for exclusion:姓+单名+单名:单名+单名若EE对应的字不同,规则失效.

}

continue;

if(strcmp(sPatterns[k],\"B\")==0&&m_nBestTag[j+1]!=12)

{//Rule 3 for exclusion: 若姓后不是后缀,规则失效.如:江、刘大娘 continue; }

//Get the possible name

nPos=j;//Record the person position in the tag sequence sPersonName[0]=0;

nLittleFreqCount=0;//Record the number of role with little frequency while(nPosif(m_nBestTag[nPos]<4&&personDict.GetFrequency(m_sWords[nPos],m_nBestTag[nPos])//规则(名1+名2+名1+名2)本身是排除规则:女高音歌唱家迪里拜尔演唱 //Rule 3 for exclusion:含外国人名用字规则适用 //否则,排除规则失效:黑妞白妞姐俩拔了头筹。 if(GetForeignCharCount(sPersonName)>0) j+=nPatternLen[k]-1; continue; }

if(strcmp(sPatterns[k],\"CD\")==0&&IsAllForeign(sPersonName)) {// j+=nPatternLen[k]-1; continue; }

if(nLittleFreqCount==nPatternLen[k]||nLittleFreqCount==3)

//马哈蒂尔;小扎耶德与他的中国阿姨胡彩玲受华黎明大使之邀, //The all roles appear with two lower frequecy,we will ignore them continue; */ m_nUnknownWords[m_nUnknownIndex][0]=m_nWordPosition[j]; m_nUnknownWords[m_nUnknownIndex][1]=m_nWordPosition[j+nPatternLen[k]]; m_dWordsPossibility[m_nUnknownIndex]=-log(dFactor[k])+ComputePossibility(j,nPatternLen[k],personDict); //Mutiply the factor m_nUnknownIndex+=1; j+=nPatternLen[k]; bMatched=true; } }

if(!bMatched)//Not matched, add j by 1 j+=1; }

return true; }

优化二元切分图

bool CSegment::BiOptimumSegment(unsigned int nResultCount,double dSmoothingPara, CDictionary &dictBinary, CDictionary &dictCore) { int **nSegRoute;//The segmentation route nSegRoute=new int*[MAX_SEGMENT_NUM]; for(int i=0;iCDynamicArray aBiwordsNet; BiGraphGenerate(m_graphOptimum,aBiwordsNet,dSmoothingPara,dictBinary,dictCore); //Generate the biword link net CNShortPath sp(&aBiwordsNet,nResultCount); sp.ShortPath(); sp.Output((int **)nSegRoute,false,&m_nSegmentCount); i=0; m_graphSeg.m_segGraph=m_graphOptimum; m_graphOptimum.SetEmpty();//Set graph optimum empty while(i人名角色观察:[ A 22202445 ][毛 B 3430 D 34 C 7 E 6 L 5 ][泽 C 2585 D 401 E 97 ][民 D 1703 C 203 E 94 K 18 L 1 ][13年 A 22202445 ][诞生 L 2 ][ A 22202445 ]

人名角色标注:[ /A ,毛/B ,泽/C ,民/D ,13年/A ,诞生/L , /A]

转移词典

人名分布词典

基于类的隐马分词

词类的隐马标注

//POS tagging with Hidden Markov Model

bool CSpan::POSTagging(PWORD_RESULT pWordItems,CDictionary &dictCore,CDictionary &dictUnknown) {

//pWordItems: Items; nItemCount: the count of items;core dictionary and unknown recognition dictionary

int i=0,j,nStartPos; Reset(false);

while(i>-1&&pWordItems[i].sWord[0]!=0) { nStartPos=i;//Start Position i=GetFrom(pWordItems,nStartPos,dictCore,dictUnknown); GetBestPOS(); switch(m_tagType) { case TT_NORMAL://normal POS tagging j=1; while(m_nBestTag[j]!=-1&&j0&&dictCore.IsExist(pWordItems[j+nStartPos-1].sWord,-1))//Exist and update its frequncy as a POS value pWordItems[j+nStartPos-1].dValue=dictCore.GetFrequency(pWordItems[j+nStartPos-1].sWord,m_nBestTag[j]); j+=1; } break; case TT_PERSON://Person recognition PersonRecognize(dictUnknown); break; case TT_PLACE://Place name recognition case TT_TRANS_PERSON://Transliteration Person PlaceRecognize(dictCore,dictUnknown); break; default: break; } Reset();

} return true; }

词分布词典:CoreNatureDictionary.txt

词性转移概率词典:

参考资料:

ITCLAS c++

Hanlp源码

基于层次隐马模型的汉语词法分析

基于N-最短路径方法的中文词语粗分模型 分词系统研究完整版(ICTCLAS) ICTCLAS学习笔记.pdf Hanlp 主页

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- 7swz.com 版权所有 赣ICP备2024042798号-8

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务