模型的建立
一句话中出现的汉字构成观察序列,如“希腊的经济结构较特殊”对应的观察序列O={希,腊,的,经,济,结,构,较,特,殊}。所有观察值的集合至少应该包含训练集和测试集中出现的所有汉字。
状态有4种:B表示词首的汉字;M表示词语中间的汉字;E表示词尾的汉字;S表示单独的汉字构成一个词。
举例:希/B腊/E 的/S 经/B济/M结/M构/E 较/S 特/B殊/E
文本的预处理
语料库用的是使用msr_training.utf8和msr_test.utf8
由于要做分词,我们的观察值是一个一个的汉字,从文本中提前汉字的方法自然是一次读取3个字节。如果文本中含有英文符号、英文字母、阿拉伯数字等对会对提取汉字的工作造成干扰。有一种去除单字节编码字符的方法是:先利用ICTCLAS进行分词和词性标注(wordseg.cpp),然后去除词性以下列字母开关的词(posfilter.cpp):m:数词,里面通常包含数字x:字符串,包含英文字母w:标点符号,可能包含英文标点符号t:时间,可能包含数字另外词性为nrf(音译人名,如“阿沛·阿旺晋美”)的词也应该去掉,因为包含一个点。wordseg.cpp
#include#include #define OS_LINUX#include "ICTCLAS50.h"using namespace std; int main(int argc, char *argv[]){ if (argc < 2) { //命令行中需要给定要处理的文件名 cout << "Usage:command filename" << endl; return 1; } string filename = argv[1]; string outfile = filename + ".ws"; string initPath = "/home/orisun/master/ICTCLAS50_Linux_RHAS_32_C/API"; if (!ICTCLAS_Init(initPath.c_str())) { cout << "Init fails" << endl; return -1; } ICTCLAS_FileProcess(filename.c_str(), outfile.c_str(), CODE_TYPE_UTF8,1); ICTCLAS_Exit(); return 0;}
posfilter.cpp
#include#include #include #include #include using namespace std;int main(int argc,char *argv[]){ set filter_set; filter_set.insert('m'); filter_set.insert('x'); filter_set.insert('w'); filter_set.insert('t'); if(argc<2){ cout<<"usage: "< <<" inputfile"< >word){ string::size_type pos=word.find("/"); string post=word.substr(pos+1); char c=post.at(0); if(c=='w') line_out+=" "; if(filter_set.find(c)==filter_set.end() && post!="nrf"){ //词性不在被过滤的集合当中 line_out+=word.substr(0,pos); //对于训练集要追加空格,对测试集不能追加空格 } } ofs< <
另外由于ICTCKLAS词性标注也不是100%准确,采用上述方法并不能将单字节编码的字符去除干净,在BMES.cpp中会进行最后的检查,找到单字节字符后再手动将其删除即可。
最后请在train文档中手动去除℃和/BMES.cpp
#include#include #include #include using namespace std;int main(int argc,char *argv[]){ if(argc<3){ cout<<"Usage: "< <<" inputfile outputfile"< >word){ if(word.size()%3!=0){ cout< <<": "< <
同样要把train文本和test文本中的所有汉字录入GDBM数据库中,然后对所有汉字标记序号。
train2dict.cpp
#include#include #include #include #include #include #define DB_FILE_BLOCK "dict_db"int main(int argc,char* argv[]){ if(argc<2){ printf("Usage: %s BMES_marked_file.\n",argv[0]); exit(1); } FILE *fp; if((fp=fopen(argv[1],"r"))==NULL){ perror("fopen"); exit(1); } GDBM_FILE dbm_ptr; dbm_ptr = gdbm_open(DB_FILE_BLOCK,0,GDBM_WRCREAT,S_IRUSR | S_IWUSR,NULL); char *v="w"; datum key,value; value.dptr=v; value.dsize=1; char word[3]={0}; char *line=NULL; //循环从输入文件中读取一行,放在line中 ssize_t read=0; size_t needlen=0; char slash='/'; int line_no=0; while((read=getline(&line,&needlen,fp))!=-1){ line_no++; char *begin=line; char *end=NULL; while((end=strchr(begin,slash))!=NULL){ if(end-begin<3){ printf("%d:%s\n",line_no,begin); break; } strncpy(word,end-3,3); key.dptr=word; key.dsize=3; //char tmp[4]={0}; //strncpy(tmp,key.dptr,3); //printf("%s\t",tmp); gdbm_store(dbm_ptr,key,value,GDBM_REPLACE); begin=end+2; } } free(line); fclose(fp); gdbm_close(dbm_ptr); return 0;}
test2dict.cpp
#include#include #include #include #include #include #include #include #include using namespace std;int main(int argc,char* argv[]){ if(argc<2){ cout<<"Usage: "< <<" inputfile"< >str){ if(str.size()%3!=0){ cout<<"size="< <<"\t"<<"|"< <<"|"<
indexword.cpp
#include#include #include #include #include #include #define DB_FILE_BLOCK "dict_db"int main(int argc,char* argv[]){ GDBM_FILE dbm_ptr; dbm_ptr = gdbm_open(DB_FILE_BLOCK,0,GDBM_WRCREAT,S_IRUSR | S_IWUSR,NULL); datum key,data; long index=0; //从0开始编号 char index_str[10]={0}; for(key=gdbm_firstkey(dbm_ptr);key.dptr;key=gdbm_nextkey(dbm_ptr,key)){ data=gdbm_fetch(dbm_ptr,key); bzero(index_str,sizeof(index_str)); sprintf(index_str,"%ld",index++); data.dptr=index_str; data.dsize=sizeof(index_str); gdbm_store(dbm_ptr,key,data,GDBM_REPLACE); } gdbm_close(dbm_ptr); return 0;}
我处理好的语料库可以在这里下载:
二阶HMM中文分词
利用Maxmum Likelihood学习二阶HMM模型参数
AMatrix.cpp
#include#include #include #include #include using namespace std;const int SNUM=4; //SNUM种隐藏状态const char state[SNUM]={'B','M','E','S'};int A1[SNUM][SNUM]; //记录一阶Markov状态转移的次数int A2[SNUM][SNUM][SNUM]; //记录二阶Markov状态转移的次数int PI[SNUM]; //记录各种状态出现的次数inline int stateIndex(char state){ switch(state){ case 'B':return 0; break; case 'M':return 1; break; case 'E':return 2; break; case 'S':return 3; break; default:return -1; break; }}//由于隐藏状态只有4种,训练语料足够多,所有可能的状态转移在训练语料中都会出现,所以不使用任何平滑算法inline void noneturing(const int count[],double prob[],int len){ double total=0.0; for(int i=0;i
gt.h包含Good-Turing平滑算法
#ifndef _HEADER_H#define _HEADER_H#include#include #include