最大概率法分詞及性能測試


        最大概率分詞是一種最基本的統計方法分詞。一個待分割的字符串有多種分詞結果,最大概率分詞的原則是將其中概率最大的那個作爲該字符串的分詞結果。


第一部分 理論基礎


        如對一個字符串:

        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(詞典頭文件)

#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <map>
#include <cstdlib>

using namespace std;

/*
 * 詞典的定義,用於最大概率分詞
 */
class Dictionary{
	private:
		string strline;			//保存每行內容
		string word;			//保存一個詞語
		map<string, int> word_map;	//詞典,用map表示
	
	public:
		long size;			//詞典規模
		long freq_all;
		long arr_1[20];
		double arr_2[20];
		Dictionary();			//構造函數,初始化詞典
		~Dictionary();
		int findWord(string word);	//在詞典中查找特定的詞語
};

Dictionary::Dictionary(){
	freq_all = 0;
	for(int i = 0; i < 20; i++){
		arr_1[i] = 0;
		arr_2[i] = 0.0;
	}
	//讀取詞典文件
	fstream fin("dict_3.txt");
	if(!fin){
		cerr << "open file error !" << endl;
		exit(-1);
	}
	//將每個詞語加入集合
	while(getline(fin, strline, '\n')){
		istringstream istr(strline);
		istr >> word;		//從流中讀取單詞
		++word_map[word];	//
		++arr_1[word.size()];
		++freq_all;
	}
	fin.close();
	//初始化詞典大小
	size = word_map.size();
	for(int i = 0; i < 20; i++){
		arr_2[i] = (double)arr_1[i]/freq_all;
	}
}

Dictionary::~Dictionary(){
	
}

int Dictionary::findWord(string word){
	map<string, int>::iterator p_cur = word_map.find(word); 
	if(p_cur != word_map.end()){
		return p_cur -> second;
	}else{
		return -1;
	}
}


        2.segmentwords.cpp(三種分詞方法所在的文件)

#include <cmath>
#include <string>
#include <iostream>
#include "dictionary_2.h"

const short MaxWordLength = 20;	//詞典中最大詞的長度
const char Separator = '/';     //詞界標記

Dictionary word_dict;           //初始化一個詞典
 
/*
 * 類定義:候選詞的結構
 */
class Candidate{
	public:
		short pos;	//候選詞在輸入串中的起點
		short length;	//輸入串的長度
		short bestPrev;	//最佳前趨詞的序號
		float fee;	//候選詞的費用
		float sumFee;	//候選詞路徑上的累計費用
		string word;	//候選詞
		int freq;	//候選詞的頻數(不能用short,否則有可能溢出)
};


/*
 * 函數功能:取出字符串中的全部候選詞
 * 函數輸入:字符串的引用
 * 函數輸出:該字符串中含有的所有的存在與詞典中的詞(或者單字,單字可以在詞典中不存在)
 */
vector<Candidate> getTmpWords(const string &s){
	int freq = 0;			//詞典中詞的頻率
	short n = s.length();		//字符串的長度
	string word = "";		//存放候選詞
	Candidate cand;			//存放候選詞屬性
	vector<Candidate> vec_cd;	//候選詞隊列

	//以每個漢字爲起點
	for(short i = 0; i < n; i += 2){
		//詞的長度爲 1~MaxWordLength/2 個漢字
		for(short len = 2; len <= MaxWordLength; len += 2){
			word = s.substr(i, len);
			freq = word_dict.findWord(word);//去詞典中查找出現頻率
			if(len > 2 && freq == -1){
				//若不止一字且詞表中找不到則不予登錄
				continue;
			}
			if(freq == -1){
				//如果爲單字詞,且詞表中找不到
				freq = 0;
			}
			cand.pos = i;			//該候選詞在漢字串中的起點
			cand.length = len;		//該候選詞的長度
			cand.word = word;
			cand.fee = -log((double)(freq*1 + 1)/word_dict.freq_all);//該候選詞的費用
			cand.sumFee = 0.0f;		//該候選詞的累計費用置初值
			cand.freq = freq;
			//將獲取的候選詞加入隊列
			vec_cd.push_back(cand);	
		}
	}

	return vec_cd;
}

/*
 * 函數功能:獲取最佳前趨詞序號
 * 函數輸入:候選詞列表的引用
 * 函數輸出:無
 */
void getPrew(vector<Candidate> &vec_cd){
	short min_id = -1;				//最佳前趨詞編號
	short j = -1;
	short size = (short)vec_cd.size();		//計算隊列長度
	for(short i = 0; i < size; i++){
		if(vec_cd[i].pos == 0){
			//如果候選詞是漢字串中的首詞
			vec_cd[i].bestPrev = -1;	//無前趨詞
			vec_cd[i].sumFee = vec_cd[i].fee;	//累計費用爲該詞本身費用
		}else{
			//如果候選詞不是漢字串中的首詞
			min_id = -1;			//初始化最佳前趨詞編號
			j = i - 1;			//從當前對象向左找
			while(j >= 0){
				//向左尋找所遇到的所有前趨詞
				if(vec_cd[j].pos + vec_cd[j].length == vec_cd[i].pos){
					if(min_id == -1 || vec_cd[j].sumFee < vec_cd[min_id].sumFee){
						min_id = j;
					}
				}
				--j;
			}

			vec_cd[i].bestPrev = min_id;	//登記最佳前趨編號
			vec_cd[i].sumFee = vec_cd[i].fee + vec_cd[min_id].sumFee;//登記最小累計費用
		}
	}
}


/*
 * 函數功能:最大概率法分詞
 * 函數輸入:待切分的字符串
 * 函數輸出:切分好的字符串
 */
string segmentSentence_MP(string s1){
	short len = s1.length();
	short min_id = -1;		//最小費用路徑的終點詞的序號
	
	//取出s1中的全部候選詞
	vector<Candidate> vec_cd = getTmpWords(s1);

	//獲得最佳前趨詞序號、當前詞最小累計費用
	getPrew(vec_cd);

	//確定最小費用路徑的終點詞的序號
	short n = (short)vec_cd.size();
	for(short i = 0; i < n; i++){
		if(vec_cd[i].pos + vec_cd[i].length == len){
			//如果當前詞是s1的尾詞
			if(min_id == -1 || vec_cd[i].sumFee < vec_cd[min_id].sumFee){
				//如果是第一個遇到的尾詞,或者是當前尾詞的最小累計費用小於
				//已經遇到過的任一尾詞的最小累計費用,則將其序號賦給min_id
				min_id = i;
			}
		}
	}

	//構造輸出串
	string s2 = "";		//輸出串初始化
	for(short i = min_id; i >= 0; i = vec_cd[i].bestPrev){
		//注意:是先取後面的詞
		s2 = s1.substr(vec_cd[i].pos, vec_cd[i].length) + Separator + s2;
	}
		
	return s2;
}



/*
 * 函數功能:對字符串用最大匹配算法(正向)處理
 * 函數輸入:漢字字符串
 * 函數輸出:分好詞的字符串
 */
string segmentSentence_1(string s1){
	string s2 = "";		//用s2存放分詞結果
	
	while(!s1.empty()){
		int len = s1.length();	//取輸入串長度
		if(len > MaxWordLength){
			len = MaxWordLength;	//只在最大詞長範圍內進行處理
		}
		
		string w = s1.substr(0, len);
		int n = word_dict.findWord(w);	//在詞典中查找相應的詞
		while(len > 2 && n == -1){
			len -= 2;	//從候選詞右邊減掉一個漢字,將剩下的部分作爲候選詞
			w = s1.substr(0, len);
			n = word_dict.findWord(w);
		}

		s2 = s2 + w + Separator;
		s1 = s1.substr(w.length(), s1.length() - w.length());
	}
	
	return s2;
}


/*
 * 函數功能:對字符串用最大匹配算法(逆向)處理
 * 函數輸入:漢字字符串
 * 函數輸出:分好詞的字符串
 */
string segmentSentence_2(string s1){
	string s2 = "";		//用s2存放分詞結果
	
	while(!s1.empty()){
		int len = s1.length();	//取輸入串長度
		if(len > MaxWordLength){
			len = MaxWordLength;	//只在最大詞長範圍內進行處理
		}
		
		string w = s1.substr(s1.length() - len, len);
		int n = word_dict.findWord(w);	//在詞典中查找相應的詞
		while(len > 2 && n == -1){
			len -= 2;	//從候選詞左邊減掉一個漢字,將剩下的部分作爲候選詞
			w = s1.substr(s1.length() - len, len);
			n = word_dict.findWord(w);
		}

		w = w + Separator;
		s2 = w + s2;
		s1 = s1.substr(0, s1.length() - len);
	}
	
	return s2;
}


        3.main.cpp(結果輸出、正確性比對等功能)

#include <cstdlib>
#include <vector>
#include <iomanip>
#include <map>
#include <algorithm>
#include <sys/time.h>
#include <sys/stat.h>
#include "segmentwords.cpp"

const long MaxCount = 50000;	//需要切分的最大句子數量,若該值大於文件中
				//實際的句子數量,以實際句子數量爲準。

//獲取當前時間(ms)
long getCurrentTime(){
	struct timeval tv;
	gettimeofday(&tv, NULL);
	return tv.tv_sec*1000 + tv.tv_usec/1000;
}

//獲取文件大小
unsigned long getFileSize(string file_path){
	unsigned long filesize = -1;
	struct stat statbuff;
	if(stat(file_path.c_str(), &statbuff) < 0){
		return filesize;
	}else{
		filesize = statbuff.st_size;
	}
		return filesize;
}



/*
 * 函數功能:對句子進行最大匹配法處理,包含對特殊字符的處理
 * 函數輸入:1.含有漢字、英文符號的字符串
 *         2.flag=1調用正向最大匹配算法,flag=2調用逆向最大匹配算法
 * 函數輸出:分好詞的字符串
 */
string SegmentSentenceMM(string s1, int flag){
	string s2 = "";	//用s2存放分詞結果
	int i;
	int dd;
	while(!s1.empty()){
		unsigned char ch = (unsigned char)s1[0];
		if(ch < 128){
			//處理西文字符
			i = 1;
			dd = s1.length();

			while(i < dd && ((unsigned char)s1[i] < 128) && (s1[i] != 10) && (s1[i] != 13)){
				//s1[i]不能是換行符或回車符
				i++;
			}//中止循環條件:出現中文字符、換行或者回車

			if(i == 1 && (ch == 10 || ch == 13)){
				//如果是換行或回車符,將它拷貝給s2輸出
				s2 += s1.substr(0, i);
			}else{
				s2 += s1.substr(0, i) + Separator;
			}
			
			s1 = s1.substr(i, dd);
			continue;
		}else{
			if(ch < 176){
				//中文標點等非漢字字符
				i = 0;
				dd = s1.length();
			
				//獲取中文雙字節特殊字符(非漢字、非中文標點),中止循環條件:超過長度、出現中文標點符號、出現漢字
				while(i < dd && ((unsigned char)s1[i] < 176) && ((unsigned char)s1[i] >= 161)
					&& (!((unsigned char)s1[i] == 161 && ((unsigned char)s1[i+1] >= 162 && (unsigned char)s1[i+1] <= 168)))
					&& (!((unsigned char)s1[i] == 161 && ((unsigned char)s1[i+1] >= 171 && (unsigned char)s1[i+1] <= 191)))
					&& (!((unsigned char)s1[i] == 163 && ((unsigned char)s1[i+1] == 161 || (unsigned char)s1[i+1] == 168
					||   (unsigned char)s1[i+1] == 169 || (unsigned char)s1[i+1] == 172 || (unsigned char)s1[i+1] == 186 
					||   (unsigned char)s1[i+1] == 187 || (unsigned char)s1[i+1] == 191)))){
					//假定沒有半個漢字
					i = i + 2;
				}
				
				//出現中文標點
				if(i == 0){
					i = i + 2;
				}

				//中文標點每個加一個分詞標記;其他非漢字雙字節字符連續輸出,只加一個分詞標記
				s2 += s1.substr(0, i) + Separator;
				

				s1 = s1.substr(i, dd);
				continue;
			}
		}
		
		//以下處理漢字串
		i = 2;
		dd = s1.length();
		while(i < dd && (unsigned char)s1[i] >= 176){
			i += 2;
		}

		if(flag == 1){
			//調用正向最大匹配
			s2 += segmentSentence_1(s1.substr(0, i));
		}else if(flag == 2){
			//調用逆向最大匹配
			s2 += segmentSentence_2(s1.substr(0, i));
		}else if(flag == 3){
			//調用最大概率匹配
			s2 += segmentSentence_MP(s1.substr(0, i));
		}

		s1 = s1.substr(i, dd); 
	}

	return s2;
}


/*
 * 函數功能:刪除分詞標記(即去掉字符串中的/)
 * 函數輸入:含有分詞標記的字符串
 * 函數輸出:不含分詞標記的字符串
 */
string removeSeparator(string str_in){
	char s[10000];
	int j = 0;
	for(int i = 0; i < str_in.length(); i++){
		if(!(str_in[i] == '/')){
			s[j] = str_in[i];
			j++;
		}
	}
	s[j] = '\0';
	string str_out = s;
	return str_out;
}


/*
 * 函數功能:計算切分標記的位置
 * 函數輸入:1.strline_in未進行切分的漢字字符串
           2.strline_right進行切分後的漢字字符串
 * 函數輸出:vecetor,其中存放了strline_in中哪些位置放置了分詞標記
 *         注意:vector中不包含最後標記的位置,但是包含位置0。
 */
vector<int> getPos(string strline_right, string strline_in){
	int pos_1 = 0;
	int pos_2 = -1;
	int pos_3 = 0;
	string word = "";
	vector<int> vec;

	int length = strline_right.length();
	while(pos_2 < length){
		//前面的分詞標記
		pos_1 = pos_2;
		
		//後面的分詞標記
		pos_2 = strline_right.find('/', pos_1 + 1);

		if(pos_2 > pos_1){
			//將兩個分詞標記之間的單詞取出
			word  = strline_right.substr(pos_1 + 1, pos_2 - pos_1 - 1);
			//根據單詞去輸入序列中查出出現的位置
			pos_3 = strline_in.find(word, pos_3);
			//將位置存入數組
			vec.push_back(pos_3);
			pos_3 = pos_3 + word.size();
		}else{
			break;
		}
	}
	
	return vec;
}


/*
 * 獲取標準切分和程序切分的結果
 */
string getString(string word, int pos, vector<int> vec_right){
	char ss[1000];
	int i = 0;
	int k = 0;
	while(vec_right[i] < pos){
		i++;
	}
	for(int j = 0; j < word.size(); j++){
		if(j == vec_right[i] - pos){
			if(j != 0){
				ss[k] = '/';
				++k;
			}
			++i;
		}
		ss[k] = word[j];
		++k;
	}
	ss[k] = '\0';
	string word_str = ss;

	return word_str;
}

/*
 * 函數功能:獲取單個句子切分的結果統計
 * 函數輸入:1.vec_right 正確的分詞標記位置集合
 *           2.vec_out   函數切分得到的分詞標記位置集合
 * 函數輸出:返回一個veceor,含有4個元素,分別爲:
 *          切分正確、組合型歧義、未登錄詞、交集型歧義的數量
 *
 */
vector<int> getCount_2(string strline, vector<int> vec_right, vector<int> vec_out, vector<string> &vec_err){
	vector<int> vec(4, 0);	//存放計算結果
	//建立map
	map<int, int> map_result;
	for(int i = 0; i < vec_right.size(); i++){
		map_result[vec_right[i]] += 1;
	}
	for(int i = 0; i < vec_out.size(); i++){
		map_result[vec_out[i]] += 2;
	}

	//統計map中的信息
	//若value=1,只在vec_right中
	//若value=2,只在vec_out中
	//若value=3,在vec_right和vec_out中都有
	map<int, int>::iterator p_pre, p_cur;
	int count_value_1 = 0;
	int count_value_2 = 0;
	int count_value_3 = 0;
	p_pre = map_result.begin();
	p_cur = map_result.begin();
	while(p_cur != map_result.end()){
		while(p_cur != map_result.end() && p_cur -> second == 3){
			p_pre = p_cur;
			++count_value_3;	//切分正確的數目
			++p_cur;		//迭代器後移
		}
		
		while(p_cur != map_result.end() && p_cur -> second != 3){
			if(p_cur -> second == 1){
				++count_value_1;
			}else if(p_cur -> second == 2){
				++count_value_2;
			}
			++p_cur;
		}
		
		//確定切分錯誤的字符串
		if(p_cur == map_result.end() && p_cur == (++p_pre)){
			continue;
		}
		int pos_1 = p_pre -> first;
		int pos_2 = p_cur -> first; 
		string word = strline.substr(pos_1, pos_2 - pos_1);	//切分錯誤的單詞
		string word_right = getString(word, pos_1, vec_right);	//正確的切分方式
		string word_out = getString(word, pos_1, vec_out);	//得到的切分方式
 
		string str_err = "";
		//不同的錯誤類型		
		if(count_value_1 > 0 && count_value_2 == 0){
			str_err = "  組合型歧義: " + word + "    正確切分: " + word_right + "    錯誤切分: " + word_out;
			vec_err.push_back(str_err);
			cout << str_err << endl;
			vec[1] += count_value_1;		
		}else if(count_value_1 == 0 && count_value_2 > 0){
			str_err = "  未登錄詞語: " + word + "    正確切分: " + word_right + "    錯誤切分: " + word_out;
			vec_err.push_back(str_err);
			cout << str_err << endl;
			vec[2] += count_value_2;
		}else if(count_value_1 > 0 && count_value_2 > 0){
			str_err = "  交集型歧義: " + word + "    正確切分: " + word_right + "    錯誤切分: " + word_out;
			vec_err.push_back(str_err);
			cout << str_err << endl;
			vec[3] += count_value_2;	
		}

		//計數器復位
		count_value_1 = 0;
		count_value_2 = 0;
	}

	vec[0] += count_value_3;	

	return vec;
}


/*
 * 主函數:進行分詞並統計分詞結果
 *
 */
int main(int argc, char *argv[]){
	long time_1 = getCurrentTime();
	
	string strline_right;	//輸入語料:用作標準分詞結果
	string strline_in;	//去掉分詞標記的語料(用作分詞的輸入)
	string strline_out_1;	//正向最大匹配分詞完畢的語料
	string strline_out_2;	//逆向最大匹配分詞完畢的語料
	string strline_out_3;	//最大概率方法分詞完畢的語料
	
	ifstream fin("test.txt");	//打開輸入文件
	if(!fin){
		cout << "Unable to open input file !" << argv[1] << endl;
		exit(-1);
	}

	/*
	ofstream fout("result.txt");	//確定輸出文件
	if(!fout){
		cout << "Unable to open output file !" << endl;
		exit(-1);
	}
	*/

	long count = 0;			//句子編號
	long count_0 = 0;		//三種方法切分都正確的句子總數
	long count_1 = 0;		//正向最大匹配完全正確的句子總數
	long count_2 = 0;		//逆向最大匹配完全正確的句子總數
	long count_3 = 0;		//最大概率方法完全正確的句子總數

	long count_right_all = 0;	//準確的切分總數
	long count_out_1_all = 0;	//正向最大匹配切分總數
	long count_out_2_all = 0;	//逆向最大匹配切分總數
	long count_out_3_all = 0;	//最大概率方法切分總數
	long count_out_1_right_all = 0;	//正向最大匹配切分正確總數
	long count_out_2_right_all = 0;	//逆向最大匹配切分正確總數
	long count_out_3_right_all = 0;	//最大概率方法切分正確總數
	long count_out_1_fail_1_all = 0;//正向最大匹配(組合型歧義)
	long count_out_1_fail_2_all = 0;//正向最大匹配(未登錄詞語)
	long count_out_1_fail_3_all = 0;//正向最大匹配(交集型歧義)
	long count_out_2_fail_1_all = 0;//逆向最大匹配(組合型歧義)
	long count_out_2_fail_2_all = 0;//逆向最大匹配(未登錄詞語)
	long count_out_2_fail_3_all = 0;//逆向最大匹配(交集型歧義)
	long count_out_3_fail_1_all = 0;//最大概率方法(組合型歧義)
	long count_out_3_fail_2_all = 0;//最大概率方法(未登錄詞語)
	long count_out_3_fail_3_all = 0;//最大概率方法(交集型歧義)

	vector<string> vec_err_1;	//正向最大匹配切分錯誤的詞
	vector<string> vec_err_2;	//逆向最大匹配切分錯誤的詞
	vector<string> vec_err_3;	//最大概率方法切分錯誤的詞

	while(getline(fin, strline_right, '\n') && count < MaxCount){
		if(strline_right.length() > 1){
			
			//去掉分詞標記
			strline_in = removeSeparator(strline_right);

			//正向最大匹配分詞
			strline_out_1 = strline_right;
			strline_out_1 = SegmentSentenceMM(strline_in, 1);
			
			//逆向最大匹配分詞
			strline_out_2 = strline_right;
			strline_out_2 = SegmentSentenceMM(strline_in, 2);

			//最大概率方法分詞
			strline_out_3 = strline_right;
			strline_out_3 = SegmentSentenceMM(strline_in, 3);

			//輸出分詞結果
			count++;
			cout << "----------------------------------------------" << endl;
			cout << "句子編號:" << count << endl;
			cout << endl;
			cout << "待分詞的句子長度: " << strline_in.length() << "  句子:" << endl;
			cout << strline_in << endl;
			cout << endl;
			cout << "標準比對結果長度: " << strline_right.length() << "  句子:" << endl;
			cout << strline_right << endl;
			cout << endl;
			cout << "正向匹配分詞長度: " << strline_out_1.length() << "  句子:" << endl;
			cout << strline_out_1 << endl;
			cout << endl;
			cout << "逆向匹配分詞長度: " << strline_out_2.length() << "  句子:" << endl;
			cout << strline_out_2 << endl;
			cout << endl;
			cout << "最大概率分詞長度: " << strline_out_3.length() << "  句子:" << endl;
			cout << strline_out_3 << endl;
			cout << endl;

			//輸出分詞結果的數字序列表示
			vector<int> vec_right = getPos(strline_right, strline_in);
			vector<int> vec_out_1 = getPos(strline_out_1, strline_in);
			vector<int> vec_out_2 = getPos(strline_out_2, strline_in);
			vector<int> vec_out_3 = getPos(strline_out_3, strline_in);

			cout << "標準結果:" << endl;
			for(int i = 0; i < vec_right.size(); i++){
				cout << setw(4) << vec_right[i];
			}
			cout << endl;
			cout << "正向匹配結果:" << endl;
			for(int i = 0; i < vec_out_1.size(); i++){
				cout << setw(4) << vec_out_1[i];
			}
			cout << endl;
			cout << "逆向匹配結果:" << endl;
			for(int i = 0; i < vec_out_2.size(); i++){
				cout << setw(4) << vec_out_2[i];
			}
			cout << endl;
			cout << "最大概率結果:" << endl;
			for(int i = 0; i < vec_out_3.size(); i++){
				cout << setw(4) << vec_out_3[i];
			}
			cout << endl;

			//輸出匹配的錯誤列表
			if(vec_right == vec_out_1 && vec_right == vec_out_2 && vec_right == vec_out_3){
				count_0++;
			}

			cout << endl;
			if(vec_right == vec_out_1){
				cout << "正向最大匹配完全正確!" << endl;
				count_1++;
			}else{
				cout << "正向最大匹配錯誤列表:" << endl;
			}
			vector<int> vec_count_1 = getCount_2(strline_in, vec_right, vec_out_1, vec_err_1);
			
			cout << endl;
			if(vec_right == vec_out_2){
				cout << "逆向最大匹配完全正確!" << endl;
				count_2++;
			}else{
				cout << "逆向最大匹配錯誤列表:" << endl;
			}
			vector<int> vec_count_2 = getCount_2(strline_in, vec_right, vec_out_2, vec_err_2);
			cout << endl;
			if(vec_right == vec_out_3){
				cout << "最大概率方法完全正確!" << endl;
				count_3++;
			}else{
				cout << "最大概率方法錯誤列表:" << endl;
			}
			
			vector<int> vec_count_3 = getCount_2(strline_in, vec_right, vec_out_3, vec_err_3);
			cout << endl;

			//準確的切分數量
			int count_right = vec_right.size();
			//切分得到的數量
			int count_out_1 = vec_out_1.size();
			int count_out_2 = vec_out_2.size();
			int count_out_3 = vec_out_3.size();
			//切分正確的數量
			int count_out_1_right = vec_count_1[0];
			int count_out_2_right = vec_count_2[0];
			int count_out_3_right = vec_count_3[0];

			cout << "正向最大匹配:" << endl;	
			cout << "  組合型歧義:" << vec_count_1[1] << endl;
			cout << "  未登錄詞語:" << vec_count_1[2] << endl;
			cout << "  交集型歧義:" << vec_count_1[3] << endl;
			cout << "逆向最大匹配:" << endl;	
			cout << "  組合型歧義:" << vec_count_2[1] << endl;
			cout << "  未登錄詞語:" << vec_count_2[2] << endl;
			cout << "  交集型歧義:" << vec_count_2[3] << endl;
			cout << "最大概率方法:" << endl;	
			cout << "  組合型歧義:" << vec_count_3[1] << endl;
			cout << "  未登錄詞語:" << vec_count_3[2] << endl;
			cout << "  交集型歧義:" << vec_count_3[3] << endl;
			
			count_right_all += count_right;
			count_out_1_all += count_out_1;
			count_out_2_all += count_out_2;
			count_out_3_all += count_out_3;
			count_out_1_right_all += count_out_1_right;
			count_out_2_right_all += count_out_2_right;
			count_out_3_right_all += count_out_3_right;
			count_out_1_fail_1_all += vec_count_1[1];
			count_out_1_fail_2_all += vec_count_1[2];
			count_out_1_fail_3_all += vec_count_1[3];
			count_out_2_fail_1_all += vec_count_2[1];
			count_out_2_fail_2_all += vec_count_2[2];
			count_out_2_fail_3_all += vec_count_2[3];
			count_out_3_fail_1_all += vec_count_3[1];
			count_out_3_fail_2_all += vec_count_3[2];
			count_out_3_fail_3_all += vec_count_3[3];
			
		}
	}
	
	long time_2 = getCurrentTime();
	unsigned long file_size = getFileSize("test.txt");


	//打印錯誤的切分內容	
	cout << endl;
	cout << "---------------------------------" << endl;
	cout << "錯誤樣例(已排序):" << endl;

	//選取樣本(600個),去掉重複的
	//vector<string> vec_small(vec_err.begin(), vec_err.begin() + 600);
	//sort(vec_small.begin(), vec_small.end());
	//vector<string>::iterator end_unique = unique(vec_small.begin(), vec_small.end());

	//對錯誤切分內容進行排序並掉重複的
	sort(vec_err_1.begin(), vec_err_1.end());
	sort(vec_err_2.begin(), vec_err_2.end());
	sort(vec_err_3.begin(), vec_err_3.end());
	vector<string>::iterator end_unique_1 = unique(vec_err_1.begin(), vec_err_1.end());
	vector<string>::iterator end_unique_2 = unique(vec_err_2.begin(), vec_err_2.end());
	vector<string>::iterator end_unique_3 = unique(vec_err_3.begin(), vec_err_3.end());

	int num_1 = end_unique_1 - vec_err_1.begin();
	int num_2 = end_unique_2 - vec_err_2.begin();
	int num_3 = end_unique_3 - vec_err_3.begin();

	cout << "----------------------------------" << endl;
	cout << "正向最大匹配切分錯誤數量:" << num_1 << endl;
	for(int i = 0; i < num_1; i++){
		cout << vec_err_1[i] << endl;
	}
	cout << endl;

	cout << "----------------------------------" << endl;
	cout << "逆向最大匹配切分錯誤數量:" << num_2 << endl;
	for(int i = 0; i < num_2; i++){
		cout << vec_err_2[i] << endl;
	}
	cout << endl;

	cout << "----------------------------------" << endl;
	cout << "最大概率方法切分錯誤數量:" << num_3 << endl;
	for(int i = 0; i < num_3; i++){
		cout << vec_err_3[i] << endl;
	}
	cout << endl;

	//計算準確率和召回率
	double kk_1 = (double)count_out_1_right_all / count_out_1_all;	//正向最大匹配準確率
	double kk_2 = (double)count_out_1_right_all / count_right_all;	//正向最大匹配召回率
	double kk_3 = (double)count_out_2_right_all / count_out_2_all;	//逆向最大匹配準確率
	double kk_4 = (double)count_out_2_right_all / count_right_all;	//逆向最大匹配召回率
	double kk_5 = (double)count_out_3_right_all / count_out_3_all;	//最大概率方法準確率
	double kk_6 = (double)count_out_3_right_all / count_right_all;	//最大概率方法召回率

	//集中輸出結果
	cout << endl;
	cout << "---------------------------------" << endl;
	cout << "分詞消耗時間:" << time_2 - time_1 << "ms" << endl;
	cout << "測試文件大小:" << file_size/1024 << " KB" << endl;
	cout << "分詞速度爲:  " << (double)file_size*1000/((time_2 - time_1)*1024) << " KB/s" << endl;

	cout << endl;
	cout << "詞典規模:" << word_dict.size << endl;

	cout << endl;
	cout << "句子總數:" << count << endl;
	cout << "三種方法切分都正確的句子數目:   " << count_0 << "\t ( " << (double)count_0*100/count << " % )" << endl;
	cout << "正向最大匹配完全正確的句子數目: " << count_1 << "\t ( " << (double)count_1*100/count << " % )" << endl;
	cout << "逆向最大匹配完全正確的句子數目: " << count_2 << "\t ( " << (double)count_2*100/count << " % )" << endl;
	cout << "最大概率方法完全正確的句子數目: " << count_3 << "\t ( " << (double)count_3*100/count << " % )" << endl;
	cout << endl;

	cout << "準確的切分總數:" << count_right_all << endl;		//準確的切分總數
	cout << "正向匹配切分總數:" << count_out_1_all << endl;		//正向匹配切分總數
	cout << "逆向匹配切分總數:" << count_out_2_all << endl;		//逆向匹配切分總數
	cout << "最大概率切分總數:" << count_out_3_all << endl;		//最大概率切分總數
	cout << "正向匹配切分正確總數:" << count_out_1_right_all << endl;	//正向匹配切分正確總數
	cout << "逆向匹配切分正確總數:" << count_out_2_right_all << endl;	//逆向匹配切分正確總數
	cout << "最大概率切分正確總數:" << count_out_3_right_all << endl;	//逆向匹配切分正確總數

	cout << endl;
	cout << "正向最大匹配:" << endl;
	long count_out_1_fail_all = count_out_1_fail_1_all + count_out_1_fail_2_all + count_out_1_fail_3_all;	
	cout << "  組合型歧義:" << count_out_1_fail_1_all << "\t ( " << (double)count_out_1_fail_1_all*100/count_out_1_fail_all << " % )" << endl;
	cout << "  未登錄詞語:" << count_out_1_fail_2_all << "\t ( " << (double)count_out_1_fail_2_all*100/count_out_1_fail_all << " % )" << endl;
	cout << "  交集型歧義:" << count_out_1_fail_3_all << "\t ( " << (double)count_out_1_fail_3_all*100/count_out_1_fail_all << " % )" << endl;
	cout << "逆向最大匹配:" << endl;	
	long count_out_2_fail_all = count_out_2_fail_1_all + count_out_2_fail_2_all + count_out_2_fail_3_all;	
	cout << "  組合型歧義:" << count_out_2_fail_1_all << "\t ( " << (double)count_out_2_fail_1_all*100/count_out_2_fail_all << " % )" << endl;
	cout << "  未登錄詞語:" << count_out_2_fail_2_all << "\t ( " << (double)count_out_2_fail_2_all*100/count_out_2_fail_all << " % )" << endl;
	cout << "  交集型歧義:" << count_out_2_fail_3_all << "\t ( " << (double)count_out_2_fail_3_all*100/count_out_2_fail_all << " % )" << endl;
	cout << "最大概率方法:" << endl;	
	long count_out_3_fail_all = count_out_3_fail_1_all + count_out_3_fail_2_all + count_out_3_fail_3_all;	
	cout << "  組合型歧義:" << count_out_3_fail_1_all << "\t ( " << (double)count_out_3_fail_1_all*100/count_out_3_fail_all << " % )" << endl;
	cout << "  未登錄詞語:" << count_out_3_fail_2_all << "\t ( " << (double)count_out_3_fail_2_all*100/count_out_3_fail_all << " % )" << endl;
	cout << "  交集型歧義:" << count_out_3_fail_3_all << "\t ( " << (double)count_out_3_fail_3_all*100/count_out_3_fail_all << " % )" << endl;

	cout << endl;		
	cout << "統計結果:" << endl;
	cout << "正向最大匹配    準確率:" << kk_1*100 << "%  \t召回率:" << kk_2*100 << "%" << endl;
	cout << "逆向最大匹配    準確率:" << kk_3*100 << "%  \t召回率:" << kk_4*100 << "%" << endl;
	cout << "最大概率方法    準確率:" << kk_5*100 << "%  \t召回率:" << kk_6*100 << "%" << endl;

	return 0;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章