基于词典规则的中文分词(C语言实现)

3 篇文章 0 订阅

0 引 言

自然语言处理(Natural Language Processing, NLP)是以语言为对象,利用计算机技术来分析、理解和处理自然语言的一门学科,即把计算机作为语言研究的强大工具,在计算机的支持下对语言信息进行定量化的研究,并提供可供人与计算机之间能共同使用的语言描写。包括自然语言理解(NaturalLanguage Understanding, NLU)和自然语言生成(Natural LanguageGeneration, NLG)两部分。它是典型边缘交叉学科,涉及到语言科学、计算机科学、数学、认知学、逻辑学等,关注计算机和人类(自然)语言之间的相互作用的领域。人们把用计算机处理自然语言的过程在不同时期或侧重点不同时又称为自然语言理解(Natural Language Understanding, NLU)、人类语言技术(Human Language Technology, HLT)、计算语言学 Hl(Computational Linguistics)、计量语言学(QuantitativeLinguistics)、数理语言(Mathematical Linguistics)。
中文分词是自然语言处理中的基础的环节,到目前为止已经有不少优秀的分词工具的出现,中文分词算法大致分为基于词典规则与基于机器学习两大派别,不过在实践中多采用结合词典规则和机器 学习的混合分词。由于中文文本是由连续的汉字所组成,因此不能使用类似英文以空格作为分隔符进行分词的方式,中文分词需要考虑语义以及上下文语境。

1 实验部分

基于词典规则的中文分词简单来说就是将中文文本按照顺序切分成连续词序,然后根据规则以及连续词序是否在给定的词典中来决定连续词序是否为最终的分词结果。不同规则对应的最终的分词结果是不一样的。
现在有段中文文本”研究生命相关的文献已报道的结果”,并且词典中包括这段文本的所有组合的可能,基于这个词典可以不需要任何的理论知识可以非常容易的将这段文本分成N 种结果。这 N 种可能的分词结果都是正确的,在中文种越长的词所表达的含义越明确,因此我们在这 N 种可能中取最长词语也称为最长匹配算法。

1.1 正向最长匹配算法

正向最长匹配是从前往后进行取词操作,假设现在对”研究生命相关的文献已报道的结
果”进行分词。
正向最长匹配的基本流程如下:

第一轮匹配:
1“研究生命相”,词典中没有对应的汉字,匹配失败。
2减少一个汉字。“研究生命”,词典中没有对应的汉字,匹配失败。
3减少一个汉字。“研究生命”,词典中没有对应的汉字,匹配失败。
4减少一个汉字。“研究生”,词典中有对应的汉字,匹配成功。
5扫描终止,输出第 1 个汉字 “研究生”,去除第 1 个汉字再进行第二轮扫描。
第二轮匹配:
1“命相关的文”,词典中没有对应的汉字,匹配失败。
2减少一个汉字。“命相关的”,词典中没有对应的汉字,匹配失败。
3减少一个汉字。“命相关”,词典中没有对应的汉字,匹配失败。
4减少一个汉字。“命相”,词典中有对应的汉字,匹配成功。
5扫描终止,输出第 2 个汉字 “命相”,去除第 2 个汉字再进行第二轮扫描
第三轮匹配:
1“关的文献已”,词典中没有对应的汉字,匹配失败。
2减少一个汉字。“关的文献”,词典中没有对应的汉字,匹配失败。
3减少一个汉字。“关的文”,词典中没有对应的汉字,匹配失败。
4减少一个汉字。“关的”,词典中没有对应的汉字,匹配失败。
5减少一个汉字。“关”,词典中有对应的汉字,匹配成功。
6扫描终止,输出第 3 个汉字 “关”,去除第 3 个汉字再进行第二轮扫描。
第四轮匹配:
1“的文献已报”,词典中没有对应的汉字,匹配失败。
2减少一个汉字。“的文献已”,词典中没有对应的汉字,匹配失败。
3减少一个汉字。“的文献”,词典中没有对应的汉字,匹配失败。
4减少一个汉字。“的文”,词典中没有对应的汉字,匹配失败。
5减少一个汉字。“的”,词典中有对应的汉字,匹配成功。
6扫描终止,输出第 4 个汉字 “的”,去除第 4 个汉字再进行第二轮扫描。

以此类推
正向最长匹配的缺点:从上面可以看出正向匹配对后半部分的分词效果较好,对”研究生命相关”分词不太理想,应该是”研究/生命/相关”而不是”研究生/命相/关”,产生这样的原因是因为正向最长匹配算法中”研究生”在词典里匹配成功,直接进入下一轮分词。
实现具体算法如下:

struct dict* forwardMatching(struct dict *dic,char *content){
int i = 0;
int cp_len; //拷贝字符串的长度 0~strlen(content)
struct dict *dic_list;//分词结果
struct dict *new_node; //新节点
struct dict *last_dic; //分词结果最后的节点
char longest_word[20]; //最长匹配字符串
char temp_word[20]; //临时存储查找的字符串
dic_list = (struct dict*)malloc(sizeof(struct dict));
dic_list->next = NULL;
while(*content != '\0'){
	//拷贝个5字符
	if(strlen(content) >= 10){
		memcpy(longest_word,content,10);
		cp_len=10;
	}else{
		memcpy(longest_word,content,strlen(content));
		cp_len=strlen(content);
	}
	longest_word[cp_len]='\0'; //开始的字符
	//字典查找
	//最长字典为个字符
	for(i = 0;i < strlen(longest_word);i+=2){
		memcpy(temp_word,content,cp_len);
		temp_word[cp_len]='\0';
		if(getValue(dic,temp_word) == 1){
			strcpy(longest_word,temp_word);
			break;
		}
		cp_len-=2;
	}
	//将查找到的串添加到链表中
	new_node = (struct dict*)malloc(sizeof(struct dict));
	new_node->next = NULL;
	strcpy(new_node->ch,longest_word);
	last_dic = getLastDic(dic_list); //尾插法
	last_dic->next = new_node;
	content+=strlen(longest_word); //指针后移,分词剩余的单词
}
	return dic_list;
}

运行结果:
在这里插入图片描述

1.2 逆向最长匹配算法

逆向最长匹配是从后往前对文本进行扫描,并保留最长单词,逆向最长匹配与正向最长匹配唯一的区别就在于扫描的方向不同。逆向最长匹配是从后往前进行取词,假设词典中最长单词包含 5 个汉字,依然对"研究生命相关的文献已报道的结果"进行分词。
逆向最长匹配的基本流程如下:

第一轮匹配:
1“报道的结果”,词典中没有对应的汉字,匹配失败。
2减少一个汉字。“道的结果”,词典中没有对应的汉字,匹配失败。
3减少一个汉字。“的结果”,词典中没有对应的汉字,匹配失败。
4减少一个汉字。“结果”,词典中有对应的汉字,匹配成功。
5扫描终止,输出第 1 个汉字 “结果”,去除第 1 个汉字再进行第二轮扫描。
第二轮匹配:
1“献已报道的”,词典中没有对应的汉字,匹配失败。
2减少一个汉字。“已报道的”,词典中没有对应的汉字,匹配失败。
3减少一个汉字。“报道的”,词典中没有对应的汉字,匹配失败。
4减少一个汉字。“道的”,词典中没有对应的汉字,匹配失败。
5减少一个汉字。“的”,词典中有对应的汉字,匹配成功。
6扫描终止,输出第 2 个汉字 “的”,去除第 2 个汉字再进行第二轮扫描。
第三轮匹配:
1“文献已报道”,词典中没有对应的汉字,匹配失败。
2减少一个汉字。“献已报道”,词典中没有对应的汉字,匹配失败。
3减少一个汉字。“已报道”,词典中没有对应的汉字,匹配失败。
4减少一个汉字。“报道”,词典中有对应的汉字,匹配成功。
5扫描终止,输出第 3 个汉字 “报道”,去除第 3 个汉字再进行第二轮扫描。
第四轮匹配:
1“关的文献已”,词典中没有对应的汉字,匹配失败。
2减少一个汉字。“的文献已”,词典中没有对应的汉字,匹配失败。
3减少一个汉字。“文献已”,词典中没有对应的汉字,匹配失败。
4减少一个汉字。“献已”,词典中没有对应的汉字,匹配失败。
5减少一个汉字。“已”,词典中有对应的汉字,匹配成功。
6扫描终止,输出第 4 个汉字 “已”,去除第 4 个汉字再进行第二轮扫描。

依次类推
具体实现算法如下:

struct dict* backwardMatch(struct dict *dic,char *content){
	struct dict *result_list; //分割返回的字符串
	struct dict *new_node; //新节点
	char *longest_word; //最长匹配字符串
	int max_match_size = 5; //截取几个字符
	int i,j,k,str_len,l;
	int match_len = 5;
	int before = 0;
	char *temp_str;
	char *surplus_str; //分词剩余的文本
	str_len = strlen(content);
	//初始化
	result_list = (struct dict*)malloc(sizeof(struct dict));
	result_list->next = NULL;
	surplus_str = (char*) malloc(sizeof(char)*str_len);
	strcpy(surplus_str,content);
	i = str_len >>1;
	//把整个文本分割
	while(i > 0){
	//从文本的后面截取个中文字符
	//如果字符不够长截取剩余字符
	if(i > 5){
		longest_word = get_last_surplus_str(surplus_str,5);
		match_len = 5;
	}else{
		longest_word = get_last_surplus_str(surplus_str,i);
		match_len = i;
	}
	//遍历如果匹配不成功则减少个汉字
	for(j = match_len;j >= 0; j--){
		temp_str = get_last_surplus_str(surplus_str,j);
		if(getValue(dic,temp_str) == 1){
		strcpy(longest_word,temp_str);
		break;
	}
}
	k = str_len - strlen(longest_word) - before;
	memcpy(surplus_str,content,k);
	surplus_str[k] = '\0';
	before+=strlen(longest_word);
	//将查找到的串添加到链表中
	new_node = (struct dict*)malloc(sizeof(struct dict));
	new_node->next = NULL;
	strcpy(new_node->ch,longest_word);
	//头插法
	new_node->next = result_list->next;
	result_list->next = new_node;
	match_len -= strlen(longest_word) >> 1;
	i -= strlen(longest_word) >> 1;
}
return result_list;
}

运行结果:
在这里插入图片描述

1.3 双向最长匹配算法

双向最长匹配算法顾名思义,同时执行正向最长匹配和逆向最长匹配,根据一些指定的规则选择最优的结果,本质是就是在两个最长算法中择优选取。
择优规则:

  • 最长的单词所表达的意义越丰富并且含义越明确。如果正向最长匹配和逆向最长匹配 分词后的词数不同,返回词数更少结果。

  • 非词典词和单字词越少越好,在语言学中单字词的数量要远远小于非单字词。如果正
    向最长匹配和逆向最长匹配分词后的词数相同,返回非词典词和单字词最少的结果。

  • 根据孙松茂教授的统计,逆向最长匹配正确的可能性要比正向最长匹配的可能性要高。如果正向最长匹配的词数以及非词典词和单字词都相同的情况下,优先返回逆向最长
    匹配的结果。
    具体算法实现:

struct dict* bidirectionalMatching(struct dict *dic,char *content){
	struct dict *forward;
	struct dict *backward;
	forward = forwardMatching(dic,content);
	backward=backwardMatch(dic,content);
	if(get_size(forward) < get_size(backward)){
		return forward;
	}else if(get_size(forward) > get_size(backward)){
		return backward;
	}else{
//单字词更少的优先级更高
	if(count_single_char(forward) < count_single_char(backward)){
		return forward;
	}else{
	// 词数以及单字词数量都相等的时候,逆向最长匹配优先级更高
		return backward;
	}
}
return 0;
}

2 结果与讨论

通过上述结果我们可以发现双向最大匹配法是将正向最大匹配法得到的分词结果和逆向最大匹配法得到的结果进行比较,我们以双向最长匹配的结果为准对比正向最长匹配和逆向最长匹配的运行结果我们也可以发现根据切分方式不同最终得到的结果也不相同。
再根据 SunM.S. 和 Benjamin K.T.(1995)的研究表明,中文中 90.0%左右的句子,正向最大匹配和逆向最大匹配法完全重合且正确,只有大概 9.0%的句子两种切分方法得到的结果不一样,但其中必有一个是正确的只有不到 1.0%的句子,或者正向最大匹配法和逆向最大匹配法的切分虽重合却是错的,或者正向最大匹配法和逆向最大匹配法切分不同但两个都不对。
因此双向最大匹配就是对正向最长匹配和逆向最长匹配进行二选一,最终的到较为正
确的分词结果。
缺点:查找效率低,内存较多 优化:可以采用字典树等数据结构进行优化。

3 结 论

通过本次实验得出基于词典的中文分词是可行的。而且基于规则的分词,一般较为简单高效,但是词典的维护需要很大的人力维护,同时对于未登录词也没有很好的解决办法。双向最大匹配结合了正反两种方法的结果,结果较为准确,也减少了歧义的产生,在实用中文信息处理中使用广泛。

完整的代码及详细的分析:https://download.csdn.net/download/weixin_45030840/70744264

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xpq_lrh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值