首先,谈一下这个概念是怎么走进我的脑袋的。要解决的问题是,土木专业有一些专业学术的文档需要根据一些特殊的实体进行标注,以前都是一些老师傅或者前辈人工进行工作,这样费人费力,并且人非圣贤孰能无过。所以,我们作为计算机一员,伸出了橄榄枝,希望通过计算机最大程度地正确快速进行实体标注。一开始,需求提出,我们就开始寻找匹配算法,网络资源虽然很多,但是并不知道怎么去查找。过程不细说,之后我莫名地就进入了CRF算法的了解与学习中。
CRF的使用场景:
假设你有许多小明同学一天内不同时段的照片,从小明提裤子起床到脱裤子睡觉各个时间段都有(小明是照片控!)。现在的任务是对这些照片进行分类。比如有的照片是吃饭,那就给它打上吃饭的标签;有的照片是跑步时拍的,那就打上跑步的标签;有的照片是开会时拍的,那就打上开会的标签。问题来了,你准备怎么干?一个简单直观的办法就是,不管这些照片之间的时间顺序,想办法训练出一个多元分类器。就是用一些打好标签的照片作为训练数据,训练出一个模型,直接根据照片的特征来分类。例如,如果照片是早上6:00拍的,且画面是黑暗的,那就给它打上睡觉的标签;如果照片上有车,那就给它打上开车的标签。这样可行吗?乍一看可以!但实际上,由于我们忽略了这些照片之间的时间顺序这一重要信息,或者说,照片之间的一些联系。我们的分类器会有缺陷的。举个例子,假如有一张小明闭着嘴的照片,怎么分类?显然难以直接判断,需要参考闭嘴之前的照片,如果之前的照片显示小明在吃饭,那这个闭嘴的照片很可能是小明在咀嚼食物准备下咽,可以给它打上吃饭的标签;如果之前的照片显示小明在唱歌,那这个闭嘴的照片很可能是小明唱歌瞬间的抓拍,可以给它打上唱歌的标签。所以,为了让我们的分类器能够有更好的表现,在为一张照片分类时,我们必须将与它相邻的照片的标签信息考虑进来。这——就是条件随机场(CRF)大显身手的地方!
(注:示例摘自:https://www.zhihu.com/question/35866596/answer/139485548)
分析:先补充一下,我们整个设计过程中拥有的输入,一份需要标注的doc文档;一份xlsx格式的实体库。那么,结合示例和我们拥有的输入分析,CRF算法是需要一个训练模型的,也就是说要使用CRF算法实现分词,必须有一个训练模型作为输入,这个训练模型说的通俗点,就是经过可以确定的一些相互关系实现了分词的文档。但是我们的输入,与训练模型概念相匹配的就是那个xlsx格式的实体库,但是这个实体库只是一些没有相互关系的词组,是一个个逻辑上没有联系的个体,所以我们无法将它作为训练模型。
小结一下:讲了这么多,是因为想说明,从实现分词的角度上,CRF这个基于统计以及机器学习的分词方法,或许在准确度或者更多的方面要优于其他算法。但是我们要追求,对症下药。
下面描述一下网上我最接收的中文分词算法的分类
中文分词算法大概分为三大类,第一类是基于字符串匹配,即扫描字符串,如果发现字符串的子串和词典中的词相同,就算匹配,比如机械分词方法。这类分词通常会加入一些启发式规则,比如“正向/反向最大匹配”,“长词优先”等。第二类是基于统计以及机器学习的分词方法,它们基于人工标注的词性和统计特征,对中文进行建模,即根据观测到的数据(标注好的语料)对模型参数进行训练,在分词阶段再通过模型计算各种分词出现的概率,将概率最大的分词结果作为最终结果。常见的序列标注模型有HMM和CRF。这类分词算法能很好处理歧义和未登录词问题,效果比前一类效果好,但是需要大量的人工标注数据,以及较慢的分词速度。第三类是通过让计算机模拟人对句子的理解,达到识别词的效果,由于汉语语义的复杂性,难以将各种语言信息组织成机器能够识别的形式,目前这种分词系统还处于试验阶段。
由此可以看来,问题的解决办法趋向于使用机械分词的算法,即这中间最简单的正逆向匹配算法。这个网上也有各种语言的实现过程,异曲同工。
正向最大匹配算法(MM):按照人得而自然阅读顺序从左往右对一段话甚至文章进行词库匹配切分。
设MaxLen为最大词长,D为分词词典
(1)从待切分语料中按正向取长度为MaxLen的字符串str,令Len =MaxLen;
(2)将str与D中的词语互相匹配;
(3)if匹配成功,将指针向前移Len个汉字,并返回到(1);
(4)if 不成功:
if( Len>1):
Len = Len-1;
从待切分语料中取长度为Len的字符串str,并返回(2);
else:
得到单个汉字,指针向前移一个汉字,并返回(1)
逆向最大匹配算法(RMM):主要原理与正向最大匹配算法一致,只是切分方向相反,从文章的尾部开始匹配。
(注:这不是我总结的_,来源也是一篇博客,看的多了,源地址一时找不到了...)
下面放出我的实现代码
match_result.py(正向匹配)
# -*- coding: utf-8 -*-
import xlrd # 处理xlsx文件
import codecs
from win32com import client # 处理word文档
import sys
# 打开xlsx文件
def open_excel(file_name):
try:
data = xlrd.open_workbook(file_name)
print("[data]:", data)
return data
except (Exception, e):
print(e)
return False
# 解析xlsx文件中的内容
def excel_table_byindex(file_name, colnameindex=0, by_index=0):
try:
data = open_excel(file_name)
except:
return False
if not data:
return False
table = data.sheets()[by_index]
#print("[table]:", table)
nrows = table.nrows # 行数
print("[nrows]:", nrows)
colnames = table.row_values(colnameindex) # 第一行数据(表头)
#print("[colnames]:", colnames)
ret_list = []
for rownum in range(1, nrows): # 对于表中的一行进行操作
row = table.row_values(rownum) # 一行的数据
max_len = 0
for item in row:
item_no_blank = item.strip()
if len(item_no_blank) > max_len:
max_len = len(item_no_blank)
ret_list.append(item_no_blank)
return ret_list,max_len
# 进行匹配
# 这里有个坑解决了好长时间,temp.txt是本目录下的,但是win32这个模块在使用的时候必须
# 要写全路径,不然报错:File "<COMObject <unknown>>", line 8, in Open
def match_result(input_file, output_file, word_dict, max_len):
word = client.Dispatch('Word.Application')
input_data = word.Documents.Open(input_file)
input_data.SaveAs(r"C:\Users\Maria\Desktop\研究生资料\正向匹配\temp.txt", 4)
temp_file = open("temp.txt", "r")
txt_content = temp_file.read()
data_len = len(txt_content)
temp_file.close()
input_data.Close()
word.Quit()
word_list = ""
cur = 0
while cur < data_len:
data = None
for data in range(max_len, 0, -1):
if txt_content[cur: cur+data] in word_dict:
word_list += txt_content[cur: cur+data] + "/"
break
cur += data
output_data = open(output_file, "w")
output_data.write(word_list)
output_data.close()
if __name__ == '__main__':
if len(sys.argv) != 3:
print("Usage:Need two arguments!")
sys.exit()
input_file = sys.argv[1]
output_file = sys.argv[2]
data,max_len = excel_table_byindex("实体库.xlsx")
#print(data)
match_result(input_file, output_file, data, max_len)
逆向匹配
reverse_match_result.py
def reverse_match_result(input_file, output_file, word_dict, max_len):
word = client.Dispatch('Word.Application')
input_data = word.Documents.Open(input_file)
input_data.SaveAs(r"C:\Users\Maria\Desktop\研究生资料\正向匹配\temp.txt", 4)
temp_file = open("temp.txt", "r")
txt_content = temp_file.read()
data_len = len(txt_content)
temp_file.close()
input_data.Close()
word.Quit()
word_list = []
cur = data_len
while cur > 0:
data = None
if max_len > cur:
max_len = cur
for data in range(max_len, 0, -1):
if txt_content[cur-data: cur] in word_dict:
word_list.insert(0, str(txt_content[cur-data: cur]) + "/")
break
cur -= data
output_data = open(output_file, "w")
output_data.write("".join(word_list))
output_data.close()
注:两点,1.正逆向匹配算法简单说起来就是字符串匹配,一种很机械化地分词方式,所以只是在于编写代码的语言相应代码的多少和使用的数据结构上有的选,或者一些程度上的优化。2.代码实现过程中可能有些繁琐,希望提出意见。
最后,虽然CRF算法直接使用有点不太合适,但是,优先在待分析字符串中识别和切分出一些带有明显特征的词,以这些词作为断点,可将原字符串分为较小的串再来进机械分词,从而减少匹配的错误率,这也是一个不错的选择,只不过这样的话,我们就需要有一个这个行业的训练模型了。
这是CRF算法中可以下载的一些训练模型(我自己的腾讯云服务器没有实现训练,可能是内存的关系吧,对于CRF的使用,网上有很多教程,搜索即可。)
下载中文分词语料库地址:http://sighan.cs.uchicago.edu/bakeoff2005/,语料来自:
台湾中央研究院(Academia Sinica)、香港城市大学(City University of Hong Kong)、北京大学(Peking University)及微软亚洲研究院(Microsoft Research)