最大概率分词是一种最基本的统计方法分词。一个待分割的字符串有多种分词结果,最大概率分词的原则是将其中概率最大的那个作为该字符串的分词结果。

第一部分 理论基础

如对一个字符串:

S:有意见分歧

分词结果1: w1:有/ 意见/ 分歧/

分词结果2: w2:有意/ 见/ 分歧/

最大概率分词就是要求得 Max(P(w1|s),P(w2|s)) 。

根据贝叶斯公式:

P(w|s)=P(s|w)P(w)/P(s)                                                                      (公式1)

在公式1中,因为P(s)和P(w|s)都基本一样,因此,就求最大的P(w)即可。根据一元语法,词之间出现的概率互相独立,因此有下面的公式成:

P(w)=P(w1,w2,…,w3)=P(w1)P(w2)…P(w3)                                   (公式2)

即字符串出现的概率就是构成字符串的各个词的概率之积。而一个词的概率可以按照其出现的次数除以语料中总的词数得到。

分析下面的例子,我们可以计算得到各个词的概率为:

有:0.018

有意:0.0005

意见:0.001

见:0.0002

分歧:0.0001

则根据公式2有:

P(w1)=p(有)P(意见)P(分歧)=0.018*0.001*0.0001=1.8*10^(-9)

P(w2)=P(有意)P(见)P(分歧)=0.0005*0.0002*0.0001=1*10^(-11)

由于P(w1)>P(w2),故w1为该字符串的分词结果。

当然,在实际操作过程中,如果字符串比较长,分词的形式就会非常多,计算量和长度呈指数增长关系,因此需要采用一定的算法来减少运算量,我们可以看到字符串的概率是累计相乘的,因此可以采用动态规划的方法来减少运算量。

这里记P`(w)为到达候选词wi时的累计概率,则

P`(wi)=P`(wi-1)P(wi)                                             (公式3)

根据公式3,有P`(意见)=P`(有)P(意见)

第二部分 算法实现

在算法的实现思路上,基本上是先记录所有可能出现的词,以及其对应的概率,也就是分段的代价函数,同时寻找每一个词的最佳的前趋词。然后就是回溯,从字符串的尾部向前搜索最优路径即可。这也是动态规划的一般实现方法。

1.思路说明

(1)获取候选词

获取句子中可能出现的所有词作为候选词,但要满足下列条件:如果是长度大于1的词,则必须在词典中出现;如果是长度等于1,即为单字,可以不在词典中出现。

(2)构造前趋词:

假定字符串从左到右进行扫描,可以得到w1,w2,…,wi-1,wi,….等若干候选词,如果wi-1的尾字根wi的首字邻接,就称wi-1为wi的前趋词。比如上面例中,候选词“有”就是候选词“意见”的前趋词,“意见”和“见”都是“分歧”的前趋词。字串最左边的词没有前趋词。

(3)寻找最佳前趋词:

如果某个候选词wi有若干个前趋词wj,wk,…..等等,其中累计概率最大的候选词称为wi的最佳前趋词。比如候选词“意见”只有一个前趋词“有”,因此“有”同时也就是“意见”的最佳前趋词;候选词“分歧”有两个前趋词“意见”和“见”,其中“意见”的累计概率大于“见”累计概率,因此“意见”是“分歧”的最佳前趋词。

(4)确定最优路径

回溯,从字符串的尾部按照最佳前趋词的指引,向前搜索最优路径。

2.具体步骤

(1)对一个待分词的字串S,按照从左到右的顺序取出全部候选词w1,w2,….,wi,…,wn;

(2)到词典中查出每个候选词的概率值P(wi)。

(3)按照公式3计算每个候选词的累计概率,同时比较得到每个词的最佳前趋词。

(4)如果当前词wn是字符串S的尾词,且累计概率P’(wn)最大,则wn就是S的终点词。

(5)从wn开始,按照从右到左的顺序,因此将每个词的最佳前趋输出,即为S的分词结果。

例子:

(1)对“有意见分歧”,从左到右进行一遍扫描,得到全部候选词:“有”,“有意”,“意见”,“见”,“分歧”;

(2)对每个候选词,记录下它的概率值,并将累计概率赋初值为0;

(3)顺次计算各个候选词的累计概率值,同时记录每个候选词的最佳前趋词:

P`(有)=P(有),

P`(意见)=P(意见),

P`(意见)=P`(有)P(意见),(“意见”的最佳前趋词为“有”)

P`(见)=P`(有意)P(见),(“见”的最佳前趋词为“有意”)

P`(意见) > P`(见)

(4) “分歧”是尾词,“意见”是“分歧”的最佳前趋词,分词过程结束。

第三部分 结果展示

对1998年1月《人民日报》进行分析,其中构造词典和测试用的语料比例为9:1。分别用三种方法进行分词:正向最大概率匹配、逆向最大概率匹配、最大概率法。对它们的分词结果进行比较,结果如下:

第四部分 源代码

源代码分为三个文件,分别是:dictionary_2.h(词典头文件)、segmentwords.cpp(三种分词方法所在的文件)、main.cpp(结果输出、正确性比对等功能)。

1.dictionary_2.h(词典头文件)

[cpp] view plaincopy
  1. #include <iostream>
  2. #include <string>
  3. #include <fstream>
  4. #include <sstream>
  5. #include <map>
  6. #include <cstdlib>
  7. using namespace std;
  8. /*
  9. * 词典的定义,用于最大概率分词
  10. */
  11. class Dictionary{
  12. private:
  13. string strline;         //保存每行内容
  14. string word;            //保存一个词语
  15. map<string, int> word_map;    //词典,用map表示
  16. public:
  17. long size;          //词典规模
  18. long freq_all;
  19. long arr_1[20];
  20. double arr_2[20];
  21. Dictionary();           //构造函数,初始化词典
  22. ~Dictionary();
  23. int findWord(string word);  //在词典中查找特定的词语
  24. };
  25. Dictionary::Dictionary(){
  26. freq_all = 0;
  27. for(int i = 0; i < 20; i++){
  28. arr_1[i] = 0;
  29. arr_2[i] = 0.0;
  30. }
  31. //读取词典文件
  32. fstream fin("dict_3.txt");
  33. if(!fin){
  34. cerr << "open file error !" << endl;
  35. exit(-1);
  36. }
  37. //将每个词语加入集合
  38. while(getline(fin, strline, '\n')){
  39. istringstream istr(strline);
  40. istr >> word;     //从流中读取单词
  41. ++word_map[word];   //
  42. ++arr_1[word.size()];
  43. ++freq_all;
  44. }
  45. fin.close();
  46. //初始化词典大小
  47. size = word_map.size();
  48. for(int i = 0; i < 20; i++){
  49. arr_2[i] = (double)arr_1[i]/freq_all;
  50. }
  51. }
  52. Dictionary::~Dictionary(){
  53. }
  54. int Dictionary::findWord(string word){
  55. map<string, int>::iterator p_cur = word_map.find(word);
  56. if(p_cur != word_map.end()){
  57. return p_cur -> second;
  58. }else{
  59. return -1;
  60. }
  61. }

2.segmentwords.cpp(三种分词方法所在的文件)

[cpp] view plaincopy
  1. #include <cmath>
  2. #include <string>
  3. #include <iostream>
  4. #include "dictionary_2.h"
  5. const short MaxWordLength = 20; //词典中最大词的长度
  6. const char Separator = '/';     //词界标记
  7. Dictionary word_dict;           //初始化一个词典
  8. /*
  9. * 类定义:候选词的结构
  10. */
  11. class Candidate{
  12. public:
  13. short pos;  //候选词在输入串中的起点
  14. short length;   //输入串的长度
  15. short bestPrev; //最佳前趋词的序号
  16. float fee;  //候选词的费用
  17. float sumFee;   //候选词路径上的累计费用
  18. string word;    //候选词
  19. int freq;   //候选词的频数(不能用short,否则有可能溢出)
  20. };
  21. /*
  22. * 函数功能:取出字符串中的全部候选词
  23. * 函数输入:字符串的引用
  24. * 函数输出:该字符串中含有的所有的存在与词典中的词(或者单字,单字可以在词典中不存在)
  25. */
  26. vector<Candidate> getTmpWords(const string &s){
  27. int freq = 0;           //词典中词的频率
  28. short n = s.length();       //字符串的长度
  29. string word = "";       //存放候选词
  30. Candidate cand;         //存放候选词属性
  31. vector<Candidate> vec_cd; //候选词队列
  32. //以每个汉字为起点
  33. for(short i = 0; i < n; i += 2){
  34. //词的长度为 1~MaxWordLength/2 个汉字
  35. for(short len = 2; len <= MaxWordLength; len += 2){
  36. word = s.substr(i, len);
  37. freq = word_dict.findWord(word);//去词典中查找出现频率
  38. if(len > 2 && freq == -1){
  39. //若不止一字且词表中找不到则不予登录
  40. continue;
  41. }
  42. if(freq == -1){
  43. //如果为单字词,且词表中找不到
  44. freq = 0;
  45. }
  46. cand.pos = i;           //该候选词在汉字串中的起点
  47. cand.length = len;      //该候选词的长度
  48. cand.word = word;
  49. cand.fee = -log((double)(freq*1 + 1)/word_dict.freq_all);//该候选词的费用
  50. cand.sumFee = 0.0f;     //该候选词的累计费用置初值
  51. cand.freq = freq;
  52. //将获取的候选词加入队列
  53. vec_cd.push_back(cand);
  54. }
  55. }
  56. return vec_cd;
  57. }
  58. /*
  59. * 函数功能:获取最佳前趋词序号
  60. * 函数输入:候选词列表的引用
  61. * 函数输出:无
  62. */
  63. void getPrew(vector<Candidate> &vec_cd){
  64. short min_id = -1;              //最佳前趋词编号
  65. short j = -1;
  66. short size = (short)vec_cd.size();      //计算队列长度
  67. for(short i = 0; i < size; i++){
  68. if(vec_cd[i].pos == 0){
  69. //如果候选词是汉字串中的首词
  70. vec_cd[i].bestPrev = -1;    //无前趋词
  71. vec_cd[i].sumFee = vec_cd[i].fee;   //累计费用为该词本身费用
  72. }else{
  73. //如果候选词不是汉字串中的首词
  74. min_id = -1;            //初始化最佳前趋词编号
  75. j = i - 1;          //从当前对象向左找
  76. while(j >= 0){
  77. //向左寻找所遇到的所有前趋词
  78. if(vec_cd[j].pos + vec_cd[j].length == vec_cd[i].pos){
  79. if(min_id == -1 || vec_cd[j].sumFee < vec_cd[min_id].sumFee){
  80. min_id = j;
  81. }
  82. }
  83. --j;
  84. }
  85. vec_cd[i].bestPrev = min_id;    //登记最佳前趋编号
  86. vec_cd[i].sumFee = vec_cd[i].fee + vec_cd[min_id].sumFee;//登记最小累计费用
  87. }
  88. }
  89. }
  90. /*
  91. * 函数功能:最大概率法分词
  92. * 函数输入:待切分的字符串
  93. * 函数输出:切分好的字符串
  94. */
  95. string segmentSentence_MP(string s1){
  96. short len = s1.length();
  97. short min_id = -1;      //最小费用路径的终点词的序号
  98. //取出s1中的全部候选词
  99. vector<Candidate> vec_cd = getTmpWords(s1);
  100. //获得最佳前趋词序号、当前词最小累计费用
  101. getPrew(vec_cd);
  102. //确定最小费用路径的终点词的序号
  103. short n = (short)vec_cd.size();
  104. for(short i = 0; i < n; i++){
  105. if(vec_cd[i].pos + vec_cd[i].length == len){
  106. //如果当前词是s1的尾词
  107. if(min_id == -1 || vec_cd[i].sumFee < vec_cd[min_id].sumFee){
  108. //如果是第一个遇到的尾词,或者是当前尾词的最小累计费用小于
  109. //已经遇到过的任一尾词的最小累计费用,则将其序号赋给min_id
  110. min_id = i;
  111. }
  112. }
  113. }
  114. //构造输出串
  115. string s2 = "";     //输出串初始化
  116. for(short i = min_id; i >= 0; i = vec_cd[i].bestPrev){
  117. //注意:是先取后面的词
  118. s2 = s1.substr(vec_cd[i].pos, vec_cd[i].length) + Separator + s2;
  119. }
  120. return s2;
  121. }
  122. /*
  123. * 函数功能:对字符串用最大匹配算法(正向)处理
  124. * 函数输入:汉字字符串
  125. * 函数输出:分好词的字符串
  126. */
  127. string segmentSentence_1(string s1){
  128. string s2 = "";     //用s2存放分词结果
  129. while(!s1.empty()){
  130. int len = s1.length();  //取输入串长度
  131. if(len > MaxWordLength){
  132. len = MaxWordLength;    //只在最大词长范围内进行处理
  133. }
  134. string w = s1.substr(0, len);
  135. int n = word_dict.findWord(w);  //在词典中查找相应的词
  136. while(len > 2 && n == -1){
  137. len -= 2;   //从候选词右边减掉一个汉字,将剩下的部分作为候选词
  138. w = s1.substr(0, len);
  139. n = word_dict.findWord(w);
  140. }
  141. s2 = s2 + w + Separator;
  142. s1 = s1.substr(w.length(), s1.length() - w.length());
  143. }
  144. return s2;
  145. }
  146. /*
  147. * 函数功能:对字符串用最大匹配算法(逆向)处理
  148. * 函数输入:汉字字符串
  149. * 函数输出:分好词的字符串
  150. */
  151. string segmentSentence_2(string s1){
  152. string s2 = "";     //用s2存放分词结果
  153. while(!s1.empty()){
  154. int len = s1.length();  //取输入串长度
  155. if(len > MaxWordLength){
  156. len = MaxWordLength;    //只在最大词长范围内进行处理
  157. }
  158. string w = s1.substr(s1.length() - len, len);
  159. int n = word_dict.findWord(w);  //在词典中查找相应的词
  160. while(len > 2 && n == -1){
  161. len -= 2;   //从候选词左边减掉一个汉字,将剩下的部分作为候选词
  162. w = s1.substr(s1.length() - len, len);
  163. n = word_dict.findWord(w);
  164. }
  165. w = w + Separator;
  166. s2 = w + s2;
  167. s1 = s1.substr(0, s1.length() - len);
  168. }
  169. return s2;
  170. }

3.main.cpp(结果输出、正确性比对等功能)

[cpp] view plaincopy
  1. #include <cstdlib>
  2. #include <vector>
  3. #include <iomanip>
  4. #include <map>
  5. #include <algorithm>
  6. #include <sys/time.h>
  7. #include <sys/stat.h>
  8. #include "segmentwords.cpp"
  9. const long MaxCount = 50000;    //需要切分的最大句子数量,若该值大于文件中
  10. //实际的句子数量,以实际句子数量为准。
  11. //获取当前时间(ms)
  12. long getCurrentTime(){
  13. struct timeval tv;
  14. gettimeofday(&tv, NULL);
  15. return tv.tv_sec*1000 + tv.tv_usec/1000;
  16. }
  17. //获取文件大小
  18. unsigned long getFileSize(string file_path){
  19. unsigned long filesize = -1;
  20. struct stat statbuff;
  21. if(stat(file_path.c_str(), &statbuff) < 0){
  22. return filesize;
  23. }else{
  24. filesize = statbuff.st_size;
  25. }
  26. return filesize;
  27. }
  28. /*
  29. * 函数功能:对句子进行最大匹配法处理,包含对特殊字符的处理
  30. * 函数输入:1.含有汉字、英文符号的字符串
  31. *         2.flag=1调用正向最大匹配算法,flag=2调用逆向最大匹配算法
  32. * 函数输出:分好词的字符串
  33. */
  34. string SegmentSentenceMM(string s1, int flag){
  35. string s2 = ""; //用s2存放分词结果
  36. int i;
  37. int dd;
  38. while(!s1.empty()){
  39. unsigned char ch = (unsigned char)s1[0];
  40. if(ch < 128){
  41. //处理西文字符
  42. i = 1;
  43. dd = s1.length();
  44. while(i < dd && ((unsigned char)s1[i] < 128) && (s1[i] != 10) && (s1[i] != 13)){
  45. //s1[i]不能是换行符或回车符
  46. i++;
  47. }//中止循环条件:出现中文字符、换行或者回车
  48. if(i == 1 && (ch == 10 || ch == 13)){
  49. //如果是换行或回车符,将它拷贝给s2输出
  50. s2 += s1.substr(0, i);
  51. }else{
  52. s2 += s1.substr(0, i) + Separator;
  53. }
  54. s1 = s1.substr(i, dd);
  55. continue;
  56. }else{
  57. if(ch < 176){
  58. //中文标点等非汉字字符
  59. i = 0;
  60. dd = s1.length();
  61. //获取中文双字节特殊字符(非汉字、非中文标点),中止循环条件:超过长度、出现中文标点符号、出现汉字
  62. while(i < dd && ((unsigned char)s1[i] < 176) && ((unsigned char)s1[i] >= 161)
  63. && (!((unsigned char)s1[i] == 161 && ((unsigned char)s1[i+1] >= 162 && (unsigned char)s1[i+1] <= 168)))
  64. && (!((unsigned char)s1[i] == 161 && ((unsigned char)s1[i+1] >= 171 && (unsigned char)s1[i+1] <= 191)))
  65. && (!((unsigned char)s1[i] == 163 && ((unsigned char)s1[i+1] == 161 || (unsigned char)s1[i+1] == 168
  66. ||   (unsigned char)s1[i+1] == 169 || (unsigned char)s1[i+1] == 172 || (unsigned char)s1[i+1] == 186
  67. ||   (unsigned char)s1[i+1] == 187 || (unsigned char)s1[i+1] == 191)))){
  68. //假定没有半个汉字
  69. i = i + 2;
  70. }
  71. //出现中文标点
  72. if(i == 0){
  73. i = i + 2;
  74. }
  75. //中文标点每个加一个分词标记;其他非汉字双字节字符连续输出,只加一个分词标记
  76. s2 += s1.substr(0, i) + Separator;
  77. s1 = s1.substr(i, dd);
  78. continue;
  79. }
  80. }
  81. //以下处理汉字串
  82. i = 2;
  83. dd = s1.length();
  84. while(i < dd && (unsigned char)s1[i] >= 176){
  85. i += 2;
  86. }
  87. if(flag == 1){
  88. //调用正向最大匹配
  89. s2 += segmentSentence_1(s1.substr(0, i));
  90. }else if(flag == 2){
  91. //调用逆向最大匹配
  92. s2 += segmentSentence_2(s1.substr(0, i));
  93. }else if(flag == 3){
  94. //调用最大概率匹配
  95. s2 += segmentSentence_MP(s1.substr(0, i));
  96. }
  97. s1 = s1.substr(i, dd);
  98. }
  99. return s2;
  100. }
  101. /*
  102. * 函数功能:删除分词标记(即去掉字符串中的/)
  103. * 函数输入:含有分词标记的字符串
  104. * 函数输出:不含分词标记的字符串
  105. */
  106. string removeSeparator(string str_in){
  107. char s[10000];
  108. int j = 0;
  109. for(int i = 0; i < str_in.length(); i++){
  110. if(!(str_in[i] == '/')){
  111. s[j] = str_in[i];
  112. j++;
  113. }
  114. }
  115. s[j] = '\0';
  116. string str_out = s;
  117. return str_out;
  118. }
  119. /*
  120. * 函数功能:计算切分标记的位置
  121. * 函数输入:1.strline_in未进行切分的汉字字符串
  122. 2.strline_right进行切分后的汉字字符串
  123. * 函数输出:vecetor,其中存放了strline_in中哪些位置放置了分词标记
  124. *         注意:vector中不包含最后标记的位置,但是包含位置0。
  125. */
  126. vector<int> getPos(string strline_right, string strline_in){
  127. int pos_1 = 0;
  128. int pos_2 = -1;
  129. int pos_3 = 0;
  130. string word = "";
  131. vector<int> vec;
  132. int length = strline_right.length();
  133. while(pos_2 < length){
  134. //前面的分词标记
  135. pos_1 = pos_2;
  136. //后面的分词标记
  137. pos_2 = strline_right.find('/', pos_1 + 1);
  138. if(pos_2 > pos_1){
  139. //将两个分词标记之间的单词取出
  140. word  = strline_right.substr(pos_1 + 1, pos_2 - pos_1 - 1);
  141. //根据单词去输入序列中查出出现的位置
  142. pos_3 = strline_in.find(word, pos_3);
  143. //将位置存入数组
  144. vec.push_back(pos_3);
  145. pos_3 = pos_3 + word.size();
  146. }else{
  147. break;
  148. }
  149. }
  150. return vec;
  151. }
  152. /*
  153. * 获取标准切分和程序切分的结果
  154. */
  155. string getString(string word, int pos, vector<int> vec_right){
  156. char ss[1000];
  157. int i = 0;
  158. int k = 0;
  159. while(vec_right[i] < pos){
  160. i++;
  161. }
  162. for(int j = 0; j < word.size(); j++){
  163. if(j == vec_right[i] - pos){
  164. if(j != 0){
  165. ss[k] = '/';
  166. ++k;
  167. }
  168. ++i;
  169. }
  170. ss[k] = word[j];
  171. ++k;
  172. }
  173. ss[k] = '\0';
  174. string word_str = ss;
  175. return word_str;
  176. }
  177. /*
  178. * 函数功能:获取单个句子切分的结果统计
  179. * 函数输入:1.vec_right 正确的分词标记位置集合
  180. *           2.vec_out   函数切分得到的分词标记位置集合
  181. * 函数输出:返回一个veceor,含有4个元素,分别为:
  182. *          切分正确、组合型歧义、未登录词、交集型歧义的数量
  183. *
  184. */
  185. vector<int> getCount_2(string strline, vector<int> vec_right, vector<int> vec_out, vector<string> &vec_err){
  186. vector<int> vec(4, 0);    //存放计算结果
  187. //建立map
  188. map<int, int> map_result;
  189. for(int i = 0; i < vec_right.size(); i++){
  190. map_result[vec_right[i]] += 1;
  191. }
  192. for(int i = 0; i < vec_out.size(); i++){
  193. map_result[vec_out[i]] += 2;
  194. }
  195. //统计map中的信息
  196. //若value=1,只在vec_right中
  197. //若value=2,只在vec_out中
  198. //若value=3,在vec_right和vec_out中都有
  199. map<int, int>::iterator p_pre, p_cur;
  200. int count_value_1 = 0;
  201. int count_value_2 = 0;
  202. int count_value_3 = 0;
  203. p_pre = map_result.begin();
  204. p_cur = map_result.begin();
  205. while(p_cur != map_result.end()){
  206. while(p_cur != map_result.end() && p_cur -> second == 3){
  207. p_pre = p_cur;
  208. ++count_value_3;    //切分正确的数目
  209. ++p_cur;        //迭代器后移
  210. }
  211. while(p_cur != map_result.end() && p_cur -> second != 3){
  212. if(p_cur -> second == 1){
  213. ++count_value_1;
  214. }else if(p_cur -> second == 2){
  215. ++count_value_2;
  216. }
  217. ++p_cur;
  218. }
  219. //确定切分错误的字符串
  220. if(p_cur == map_result.end() && p_cur == (++p_pre)){
  221. continue;
  222. }
  223. int pos_1 = p_pre -> first;
  224. int pos_2 = p_cur -> first;
  225. string word = strline.substr(pos_1, pos_2 - pos_1); //切分错误的单词
  226. string word_right = getString(word, pos_1, vec_right);  //正确的切分方式
  227. string word_out = getString(word, pos_1, vec_out);  //得到的切分方式
  228. string str_err = "";
  229. //不同的错误类型
  230. if(count_value_1 > 0 && count_value_2 == 0){
  231. str_err = "  组合型歧义: " + word + "    正确切分: " + word_right + "    错误切分: " + word_out;
  232. vec_err.push_back(str_err);
  233. cout << str_err << endl;
  234. vec[1] += count_value_1;
  235. }else if(count_value_1 == 0 && count_value_2 > 0){
  236. str_err = "  未登录词语: " + word + "    正确切分: " + word_right + "    错误切分: " + word_out;
  237. vec_err.push_back(str_err);
  238. cout << str_err << endl;
  239. vec[2] += count_value_2;
  240. }else if(count_value_1 > 0 && count_value_2 > 0){
  241. str_err = "  交集型歧义: " + word + "    正确切分: " + word_right + "    错误切分: " + word_out;
  242. vec_err.push_back(str_err);
  243. cout << str_err << endl;
  244. vec[3] += count_value_2;
  245. }
  246. //计数器复位
  247. count_value_1 = 0;
  248. count_value_2 = 0;
  249. }
  250. vec[0] += count_value_3;
  251. return vec;
  252. }
  253. /*
  254. * 主函数:进行分词并统计分词结果
  255. *
  256. */
  257. int main(int argc, char *argv[]){
  258. long time_1 = getCurrentTime();
  259. string strline_right;   //输入语料:用作标准分词结果
  260. string strline_in;  //去掉分词标记的语料(用作分词的输入)
  261. string strline_out_1;   //正向最大匹配分词完毕的语料
  262. string strline_out_2;   //逆向最大匹配分词完毕的语料
  263. string strline_out_3;   //最大概率方法分词完毕的语料
  264. ifstream fin("test.txt");   //打开输入文件
  265. if(!fin){
  266. cout << "Unable to open input file !" << argv[1] << endl;
  267. exit(-1);
  268. }
  269. /*
  270. ofstream fout("result.txt");    //确定输出文件
  271. if(!fout){
  272. cout << "Unable to open output file !" << endl;
  273. exit(-1);
  274. }
  275. */
  276. long count = 0;         //句子编号
  277. long count_0 = 0;       //三种方法切分都正确的句子总数
  278. long count_1 = 0;       //正向最大匹配完全正确的句子总数
  279. long count_2 = 0;       //逆向最大匹配完全正确的句子总数
  280. long count_3 = 0;       //最大概率方法完全正确的句子总数
  281. long count_right_all = 0;   //准确的切分总数
  282. long count_out_1_all = 0;   //正向最大匹配切分总数
  283. long count_out_2_all = 0;   //逆向最大匹配切分总数
  284. long count_out_3_all = 0;   //最大概率方法切分总数
  285. long count_out_1_right_all = 0; //正向最大匹配切分正确总数
  286. long count_out_2_right_all = 0; //逆向最大匹配切分正确总数
  287. long count_out_3_right_all = 0; //最大概率方法切分正确总数
  288. long count_out_1_fail_1_all = 0;//正向最大匹配(组合型歧义)
  289. long count_out_1_fail_2_all = 0;//正向最大匹配(未登录词语)
  290. long count_out_1_fail_3_all = 0;//正向最大匹配(交集型歧义)
  291. long count_out_2_fail_1_all = 0;//逆向最大匹配(组合型歧义)
  292. long count_out_2_fail_2_all = 0;//逆向最大匹配(未登录词语)
  293. long count_out_2_fail_3_all = 0;//逆向最大匹配(交集型歧义)
  294. long count_out_3_fail_1_all = 0;//最大概率方法(组合型歧义)
  295. long count_out_3_fail_2_all = 0;//最大概率方法(未登录词语)
  296. long count_out_3_fail_3_all = 0;//最大概率方法(交集型歧义)
  297. vector<string> vec_err_1; //正向最大匹配切分错误的词
  298. vector<string> vec_err_2; //逆向最大匹配切分错误的词
  299. vector<string> vec_err_3; //最大概率方法切分错误的词
  300. while(getline(fin, strline_right, '\n') && count < MaxCount){
  301. if(strline_right.length() > 1){
  302. //去掉分词标记
  303. strline_in = removeSeparator(strline_right);
  304. //正向最大匹配分词
  305. strline_out_1 = strline_right;
  306. strline_out_1 = SegmentSentenceMM(strline_in, 1);
  307. //逆向最大匹配分词
  308. strline_out_2 = strline_right;
  309. strline_out_2 = SegmentSentenceMM(strline_in, 2);
  310. //最大概率方法分词
  311. strline_out_3 = strline_right;
  312. strline_out_3 = SegmentSentenceMM(strline_in, 3);
  313. //输出分词结果
  314. count++;
  315. cout << "----------------------------------------------" << endl;
  316. cout << "句子编号:" << count << endl;
  317. cout << endl;
  318. cout << "待分词的句子长度: " << strline_in.length() << "  句子:" << endl;
  319. cout << strline_in << endl;
  320. cout << endl;
  321. cout << "标准比对结果长度: " << strline_right.length() << "  句子:" << endl;
  322. cout << strline_right << endl;
  323. cout << endl;
  324. cout << "正向匹配分词长度: " << strline_out_1.length() << "  句子:" << endl;
  325. cout << strline_out_1 << endl;
  326. cout << endl;
  327. cout << "逆向匹配分词长度: " << strline_out_2.length() << "  句子:" << endl;
  328. cout << strline_out_2 << endl;
  329. cout << endl;
  330. cout << "最大概率分词长度: " << strline_out_3.length() << "  句子:" << endl;
  331. cout << strline_out_3 << endl;
  332. cout << endl;
  333. //输出分词结果的数字序列表示
  334. vector<int> vec_right = getPos(strline_right, strline_in);
  335. vector<int> vec_out_1 = getPos(strline_out_1, strline_in);
  336. vector<int> vec_out_2 = getPos(strline_out_2, strline_in);
  337. vector<int> vec_out_3 = getPos(strline_out_3, strline_in);
  338. cout << "标准结果:" << endl;
  339. for(int i = 0; i < vec_right.size(); i++){
  340. cout << setw(4) << vec_right[i];
  341. }
  342. cout << endl;
  343. cout << "正向匹配结果:" << endl;
  344. for(int i = 0; i < vec_out_1.size(); i++){
  345. cout << setw(4) << vec_out_1[i];
  346. }
  347. cout << endl;
  348. cout << "逆向匹配结果:" << endl;
  349. for(int i = 0; i < vec_out_2.size(); i++){
  350. cout << setw(4) << vec_out_2[i];
  351. }
  352. cout << endl;
  353. cout << "最大概率结果:" << endl;
  354. for(int i = 0; i < vec_out_3.size(); i++){
  355. cout << setw(4) << vec_out_3[i];
  356. }
  357. cout << endl;
  358. //输出匹配的错误列表
  359. if(vec_right == vec_out_1 && vec_right == vec_out_2 && vec_right == vec_out_3){
  360. count_0++;
  361. }
  362. cout << endl;
  363. if(vec_right == vec_out_1){
  364. cout << "正向最大匹配完全正确!" << endl;
  365. count_1++;
  366. }else{
  367. cout << "正向最大匹配错误列表:" << endl;
  368. }
  369. vector<int> vec_count_1 = getCount_2(strline_in, vec_right, vec_out_1, vec_err_1);
  370. cout << endl;
  371. if(vec_right == vec_out_2){
  372. cout << "逆向最大匹配完全正确!" << endl;
  373. count_2++;
  374. }else{
  375. cout << "逆向最大匹配错误列表:" << endl;
  376. }
  377. vector<int> vec_count_2 = getCount_2(strline_in, vec_right, vec_out_2, vec_err_2);
  378. cout << endl;
  379. if(vec_right == vec_out_3){
  380. cout << "最大概率方法完全正确!" << endl;
  381. count_3++;
  382. }else{
  383. cout << "最大概率方法错误列表:" << endl;
  384. }
  385. vector<int> vec_count_3 = getCount_2(strline_in, vec_right, vec_out_3, vec_err_3);
  386. cout << endl;
  387. //准确的切分数量
  388. int count_right = vec_right.size();
  389. //切分得到的数量
  390. int count_out_1 = vec_out_1.size();
  391. int count_out_2 = vec_out_2.size();
  392. int count_out_3 = vec_out_3.size();
  393. //切分正确的数量
  394. int count_out_1_right = vec_count_1[0];
  395. int count_out_2_right = vec_count_2[0];
  396. int count_out_3_right = vec_count_3[0];
  397. cout << "正向最大匹配:" << endl;
  398. cout << "  组合型歧义:" << vec_count_1[1] << endl;
  399. cout << "  未登录词语:" << vec_count_1[2] << endl;
  400. cout << "  交集型歧义:" << vec_count_1[3] << endl;
  401. cout << "逆向最大匹配:" << endl;
  402. cout << "  组合型歧义:" << vec_count_2[1] << endl;
  403. cout << "  未登录词语:" << vec_count_2[2] << endl;
  404. cout << "  交集型歧义:" << vec_count_2[3] << endl;
  405. cout << "最大概率方法:" << endl;
  406. cout << "  组合型歧义:" << vec_count_3[1] << endl;
  407. cout << "  未登录词语:" << vec_count_3[2] << endl;
  408. cout << "  交集型歧义:" << vec_count_3[3] << endl;
  409. count_right_all += count_right;
  410. count_out_1_all += count_out_1;
  411. count_out_2_all += count_out_2;
  412. count_out_3_all += count_out_3;
  413. count_out_1_right_all += count_out_1_right;
  414. count_out_2_right_all += count_out_2_right;
  415. count_out_3_right_all += count_out_3_right;
  416. count_out_1_fail_1_all += vec_count_1[1];
  417. count_out_1_fail_2_all += vec_count_1[2];
  418. count_out_1_fail_3_all += vec_count_1[3];
  419. count_out_2_fail_1_all += vec_count_2[1];
  420. count_out_2_fail_2_all += vec_count_2[2];
  421. count_out_2_fail_3_all += vec_count_2[3];
  422. count_out_3_fail_1_all += vec_count_3[1];
  423. count_out_3_fail_2_all += vec_count_3[2];
  424. count_out_3_fail_3_all += vec_count_3[3];
  425. }
  426. }
  427. long time_2 = getCurrentTime();
  428. unsigned long file_size = getFileSize("test.txt");
  429. //打印错误的切分内容
  430. cout << endl;
  431. cout << "---------------------------------" << endl;
  432. cout << "错误样例(已排序):" << endl;
  433. //选取样本(600个),去掉重复的
  434. //vector<string> vec_small(vec_err.begin(), vec_err.begin() + 600);
  435. //sort(vec_small.begin(), vec_small.end());
  436. //vector<string>::iterator end_unique = unique(vec_small.begin(), vec_small.end());
  437. //对错误切分内容进行排序并掉重复的
  438. sort(vec_err_1.begin(), vec_err_1.end());
  439. sort(vec_err_2.begin(), vec_err_2.end());
  440. sort(vec_err_3.begin(), vec_err_3.end());
  441. vector<string>::iterator end_unique_1 = unique(vec_err_1.begin(), vec_err_1.end());
  442. vector<string>::iterator end_unique_2 = unique(vec_err_2.begin(), vec_err_2.end());
  443. vector<string>::iterator end_unique_3 = unique(vec_err_3.begin(), vec_err_3.end());
  444. int num_1 = end_unique_1 - vec_err_1.begin();
  445. int num_2 = end_unique_2 - vec_err_2.begin();
  446. int num_3 = end_unique_3 - vec_err_3.begin();
  447. cout << "----------------------------------" << endl;
  448. cout << "正向最大匹配切分错误数量:" << num_1 << endl;
  449. for(int i = 0; i < num_1; i++){
  450. cout << vec_err_1[i] << endl;
  451. }
  452. cout << endl;
  453. cout << "----------------------------------" << endl;
  454. cout << "逆向最大匹配切分错误数量:" << num_2 << endl;
  455. for(int i = 0; i < num_2; i++){
  456. cout << vec_err_2[i] << endl;
  457. }
  458. cout << endl;
  459. cout << "----------------------------------" << endl;
  460. cout << "最大概率方法切分错误数量:" << num_3 << endl;
  461. for(int i = 0; i < num_3; i++){
  462. cout << vec_err_3[i] << endl;
  463. }
  464. cout << endl;
  465. //计算准确率和召回率
  466. double kk_1 = (double)count_out_1_right_all / count_out_1_all;  //正向最大匹配准确率
  467. double kk_2 = (double)count_out_1_right_all / count_right_all;  //正向最大匹配召回率
  468. double kk_3 = (double)count_out_2_right_all / count_out_2_all;  //逆向最大匹配准确率
  469. double kk_4 = (double)count_out_2_right_all / count_right_all;  //逆向最大匹配召回率
  470. double kk_5 = (double)count_out_3_right_all / count_out_3_all;  //最大概率方法准确率
  471. double kk_6 = (double)count_out_3_right_all / count_right_all;  //最大概率方法召回率
  472. //集中输出结果
  473. cout << endl;
  474. cout << "---------------------------------" << endl;
  475. cout << "分词消耗时间:" << time_2 - time_1 << "ms" << endl;
  476. cout << "测试文件大小:" << file_size/1024 << " KB" << endl;
  477. cout << "分词速度为:  " << (double)file_size*1000/((time_2 - time_1)*1024) << " KB/s" << endl;
  478. cout << endl;
  479. cout << "词典规模:" << word_dict.size << endl;
  480. cout << endl;
  481. cout << "句子总数:" << count << endl;
  482. cout << "三种方法切分都正确的句子数目:   " << count_0 << "\t ( " << (double)count_0*100/count << " % )" << endl;
  483. cout << "正向最大匹配完全正确的句子数目: " << count_1 << "\t ( " << (double)count_1*100/count << " % )" << endl;
  484. cout << "逆向最大匹配完全正确的句子数目: " << count_2 << "\t ( " << (double)count_2*100/count << " % )" << endl;
  485. cout << "最大概率方法完全正确的句子数目: " << count_3 << "\t ( " << (double)count_3*100/count << " % )" << endl;
  486. cout << endl;
  487. cout << "准确的切分总数:" << count_right_all << endl;        //准确的切分总数
  488. cout << "正向匹配切分总数:" << count_out_1_all << endl;       //正向匹配切分总数
  489. cout << "逆向匹配切分总数:" << count_out_2_all << endl;       //逆向匹配切分总数
  490. cout << "最大概率切分总数:" << count_out_3_all << endl;       //最大概率切分总数
  491. cout << "正向匹配切分正确总数:" << count_out_1_right_all << endl;   //正向匹配切分正确总数
  492. cout << "逆向匹配切分正确总数:" << count_out_2_right_all << endl;   //逆向匹配切分正确总数
  493. cout << "最大概率切分正确总数:" << count_out_3_right_all << endl;   //逆向匹配切分正确总数
  494. cout << endl;
  495. cout << "正向最大匹配:" << endl;
  496. long count_out_1_fail_all = count_out_1_fail_1_all + count_out_1_fail_2_all + count_out_1_fail_3_all;
  497. cout << "  组合型歧义:" << count_out_1_fail_1_all << "\t ( " << (double)count_out_1_fail_1_all*100/count_out_1_fail_all << " % )" << endl;
  498. cout << "  未登录词语:" << count_out_1_fail_2_all << "\t ( " << (double)count_out_1_fail_2_all*100/count_out_1_fail_all << " % )" << endl;
  499. cout << "  交集型歧义:" << count_out_1_fail_3_all << "\t ( " << (double)count_out_1_fail_3_all*100/count_out_1_fail_all << " % )" << endl;
  500. cout << "逆向最大匹配:" << endl;
  501. long count_out_2_fail_all = count_out_2_fail_1_all + count_out_2_fail_2_all + count_out_2_fail_3_all;
  502. cout << "  组合型歧义:" << count_out_2_fail_1_all << "\t ( " << (double)count_out_2_fail_1_all*100/count_out_2_fail_all << " % )" << endl;
  503. cout << "  未登录词语:" << count_out_2_fail_2_all << "\t ( " << (double)count_out_2_fail_2_all*100/count_out_2_fail_all << " % )" << endl;
  504. cout << "  交集型歧义:" << count_out_2_fail_3_all << "\t ( " << (double)count_out_2_fail_3_all*100/count_out_2_fail_all << " % )" << endl;
  505. cout << "最大概率方法:" << endl;
  506. long count_out_3_fail_all = count_out_3_fail_1_all + count_out_3_fail_2_all + count_out_3_fail_3_all;
  507. cout << "  组合型歧义:" << count_out_3_fail_1_all << "\t ( " << (double)count_out_3_fail_1_all*100/count_out_3_fail_all << " % )" << endl;
  508. cout << "  未登录词语:" << count_out_3_fail_2_all << "\t ( " << (double)count_out_3_fail_2_all*100/count_out_3_fail_all << " % )" << endl;
  509. cout << "  交集型歧义:" << count_out_3_fail_3_all << "\t ( " << (double)count_out_3_fail_3_all*100/count_out_3_fail_all << " % )" << endl;
  510. cout << endl;
  511. cout << "统计结果:" << endl;
  512. cout << "正向最大匹配    准确率:" << kk_1*100 << "%  \t召回率:" << kk_2*100 << "%" << endl;
  513. cout << "逆向最大匹配    准确率:" << kk_3*100 << "%  \t召回率:" << kk_4*100 << "%" << endl;
  514. cout << "最大概率方法    准确率:" << kk_5*100 << "%  \t召回率:" << kk_6*100 << "%" << endl;
  515. return 0;
  516. }

最大概率法分词及性能测试相关推荐

  1. 最大概率法分词及性能測试

    最大概率分词是一种最主要的统计方法分词. 一个待切割的字符串有多种分词结果,最大概率分词的原则是将当中概率最大的那个作为该字符串的分词结果. 第一部分 理论基础 如对一个字符串: S:有意见分歧 分词 ...

  2. 最大概率法分词中词频惩罚因子的作用探究

    在最大概率法分词的程序中,由于每个词出现的次数分布非常不均匀,而且我们要计算每个词出现的概率,对于出现次数比较少的词概率就很小,求句子中词的概率之积的时候,需要将好多非常小的数作乘法,可能会将超出计算 ...

  3. 正向(逆向)最大匹配和最大概率法分词的错误分析

    正向最大匹配.逆向最大匹配.最大概率法是最简单的三种分词方式.本文从这三种分词方法产生的错误入手,观察他们分词的优缺点. 1.基本情况 从语料中选取了200个句子作为样本,分别用三种不同的分词方式进行 ...

  4. 基于Tire树和最大概率法的中文分词功能的Java实现

    对于分词系统的实现来说,主要应集中在两方面的考虑上:一是对语料库的组织,二是分词策略的制订. 1.   Tire树 Tire树,即字典树,是通过字串的公共前缀来对字串进行统计.排序及存储的一种树形结构 ...

  5. 卡方分布分位数_卡方检验和精确概率法及两两比较

    看过许多统计教程,这篇是我最推荐的 介    绍 数值变量如果服从正态分布,采用均数±标准差进行统计描述,采用方差分析进行组间比较,如果组间差异有统计学意义,进一步采用LSD法(也可以是其它方法)进行 ...

  6. 正向/逆向最大匹配法分词实现

    最大匹配法:最大匹配是指以词典为依据,取词典中最长单词为第一个次取字数量的扫描串,在词典中进行扫描(为提升扫描效率,还可以跟据字数多少设计多个字典,然后根据字数分别从不同字典中进行扫描).例如:词典中 ...

  7. python:正向最大匹配法分词(以藏文为例)

    ​ 前段时间研究了如何用分词工具进行分词,但是分词中涉及的一些算法,不太了解,所以,准备这段时间专攻分词算法原理,大家有补充,或者建议,欢迎留言. 1. 最大匹配法(Maximum Matching) ...

  8. 模拟退火算法——概率法解全局优化

    1.直观介绍 模拟退火算法来源于固体退火原理,是一种基于概率的算法,将固体加温至充分高,再让其徐徐冷却,加温时,固体内部粒子随温升变为无序状,内能增大,而徐徐冷却时粒子渐趋有序,在每个温度都达到平衡态 ...

  9. 使用概率法计算圆周率

    一:数学原理 如图所示,存在一边长为r的正方形,其内存在一半径为r内切圆.则正方形面积为S1,圆的面积为S2 设事件A为投掷一点至正方形内,恰落入圆内,则 P(A) = (S1 / S2) = (π× ...

最新文章

  1. android应用可以访问/dev下设备节点
  2. 使用SeaJS实现模块化JavaScript开发【转】
  3. 笔记-中项案例题-2018年下-整体管理
  4. 【Python基础】Python 100 例带你入门
  5. 我写的博客居然是百度搜索第一名 - 感谢国家 …
  6. C#LeetCode刷题之#125-验证回文串(Valid Palindrome)
  7. 英雄联盟欧洲赛区_Linux命令简介,欧盟的开源数学工具箱以及更多新闻
  8. 我的docker随笔33:在容器中连接oracle数据库
  9. MQTT工作笔记0004---CONNECT控制报文1
  10. 使用esp8266制作wifi干扰器
  11. Azkaban 安装
  12. 腾讯云html5直播开发,腾讯云IM开发 直播 聊天室
  13. c command语言学例子,语言学资料(一)CHAPTER 4
  14. linux系统vim程序编译器,linux学习日记七 vim程序编辑器使用
  15. Matlab蒙特卡罗模拟
  16. 选择商品的时候,弹不出来商品选择框
  17. MFC简易音乐播放器
  18. 软件测试中的“汽车车载导航系统项目”讲解
  19. Windows搭建nexus3.xx
  20. Java的abstract

热门文章

  1. java stream操作案例
  2. 洛谷P4768 [NOI2018]归程(Kruskal重构树)
  3. 牵引力教育分析UI设计在市场饱和度如何?
  4. Linux 单用户模式修改密码与救援模式修改密码总结
  5. 移动端自动化测试(一)appium环境搭建
  6. vim插件的安装方式 -- vim注释插件和doxygen函数注释生成插件-ctrlp插件-tabular等号对齐 插件...
  7. Flume Sinks官网剖析(博主推荐)
  8. Apache中KeepAlive 配置
  9. Appium官网Introduction
  10. jQuery JavaScript库达到新的里程碑