赞
踩
最近在学习自然语言处理也可以说是入门,找到了一本觉得比较好的书《自然语言处理导论》,但是我在阅读的过程中发现缺少了对应的代码实践,所以就萌生了完成对应算法的Python实践的想法。通过复现对应的算法也能够加深对算法的理解。本系列将根据《自然语言处理导论》书中的脉络复现对应的算法,也是非常适合小白入门学习,我们大家一起学习,如有错误的地方欢迎在评论区指正。此外,本系列教程将从书中开始有算法的地方进行介绍,略过了一些理论部分,包括自然语言处理的基本概念、处理范式;等前置基础知识,大家可以通过阅读《自然语言处理导论》来跟上本教程的节奏。
本系列为自然语言处理导论与Python实践系列的第一篇,主要对词汇分析进行介绍,一些语言方面的基础知识(词性、词语规范化)需要大家自行阅读《自然语言处理导论》第二章。词汇分析主要包含分词与词性标注两部分,分词部分将以中文分词为例进行介绍。
中文分词(Chinese Word Segmentation,CWS)是指将连续字序列转换为对应的词序列的过程,也 可以看做在输入的序列中添加空格或其他边界标记的过程。中文分词任务可以形式化表示为:给定中文句子 c 1 , c 2 , ⋅ ⋅ ⋅ , c n , c1,c2,··· ,cn, c1,c2,⋅⋅⋅,cn, 其中 c i ci ci 为单个字符,输出词序列 w 1 , w 2 , ⋅ ⋅ ⋅ , w m , w1,w2,··· ,wm, w1,w2,⋅⋅⋅,wm, 其中 w j wj wj 是中文单词。
例如:
复旦大学是中国人自主创办的第一所高等院校。
分词结果:复旦大学 | 是 | 中国人 | 自主 | 创办 | 的 | 第一 | 所 | 高等 | 院校 |。
最大匹配(Maximum Matching)分词算法主要包含前向最大匹配,后向最大匹配以及双向最大匹配等三类。在这里我们以正向匹配为例对该算法进行介绍。
例如:针对句子 “他是研究生物化学的一位科学家”,前向最大分词的过程如表2.2所示。为简单起见,词典中词语最大长度假设为 4,词表为 {“他”,“是”, “研究”, “生物化学”, “的”, “一”, “位”, “科学家”}。如上图。我们固定住头,即『他』。我们从句子末尾开始向前找,“他是研究”有没有在词典里。有,就当成分词。不是的话前移,直到『他』;依次类推,直到完成整个句子的切分,下面将通过代码展示前向最大匹配算法流程。
前向最大匹配算法(Forward Max Matching,FMM)
dic=['他','是', '研究', '生物化学', '的', '一', '位', '科学家']
sentence = "他是研究生物化学的一位科学家"
start = 0
while start != len(sentence):
index = len(sentence)
for i in range(len(sentence)):
if sentence[start : index] in dic:
print(sentence[start : index], end='/')
start = index
break
index += -1
后向最大匹配算法(Backward Max Matching,BMM)
dic=['他','是', '研究', '生物化学', '的', '一', '位', '科学家'] sentence = "他是研究生物化学的一位科学家" result = [] start = len(sentence) while start != 0: index = 0 for i in range(len(sentence)): if sentence[index: start] in dic: result.append(sentence[index:start]) start = index break index += 1 for i in result[::-1]: print(i,end='/')
在上面的代码中,start为句子的起始下标,index为从后往前移动的下标。while循环每次找出一个分词。当start移到句子的最后一位上结束循环。内部的for循环是index移动过程,sentence[start:index]在词典里面时,就跳出循环。更新start值,寻找下一个分词。反向最大匹配算法与前向最大匹配算法类似,index从前往后移动即可,但要注意输出需要反转一下。
扩展阅读
对于FMM,BMM的分词结果,可以通过指定规则来确定最终结果。两种方法的分词结果,选择分词数最少的;分词数一样的再判断句子中的单字数,谁的单字数越少,就谁的认为分词效果越好。即为双向最大匹配算法(Backward Max Matching,BIMM)
基于词典的分词方法优缺点
简单来说基于线性链条件随机场的中文分词,就是通过条件随机场对句子的输入序列进行建模,完成分词的过程,对序列任务进行建模时,通常使用链式结构,即线性链条件随机场(Linear-chain CRF)。根据中文分词任务定义,我们可以将分词过程看做是对于字的分类。具体来说,对于输入句子中的每一个字$ ci
,根据它在分词结果中的位置赋予不同的标签。可以假设一个字在词语中有四个位置
:
开始
,根据它在分词结果中的位置赋予不同的标签。可以假设一个字在词语中有四 个位置:开始
,根据它在分词结果中的位置赋予不同的标签。可以假设一个字在词语中有四个位置:开始(B)
、中间
、中间
、中间(I)
、结尾
、结尾
、结尾(E)
以及单独成词
以及单独成词
以及单独成词(S)$。
例如:
输入句子:他是研究生物化学的一位科学家。
分词结果:他 | 是 | 研究 | 生物化学 | 的 | 一 | 位 | 科学家 |。
对应标记:他/S 是/S 研/B 究/E 生/B 物/I 化/I 学/E 的/S 一/B 位/E 科/B 学/I 家/E 。/S
这里的“字”不仅包含汉字,还包含英文字母、数字、标点符号等所有可能出现在汉语文本中的符号。通过 B I E S BIES BIES 标签可以将分词问题转换为字的分类问题。
条件随机场(Conditional Random Field,CRF)试图对多个变量在给定观测值后的条件概率进行建模。 x = x l , x 2 , . . . , x n x = {xl, x2, . . . , xn} x=xl,x2,...,xn 为观测序列, y = y l , y 2 , . . . , y n y = {yl, y2, . . . , yn} y=yl,y2,...,yn 为对应的标记序列,条件随机场的目标是构建条件概率模型 P ( y ∣ x ) P (y|x) P(y∣x)。在中文分词任务中,观察序列 x x x 对应输入的字序列 c 1 , c 2 , ⋅ ⋅ ⋅ , c n {c1, c2, · · · , cn} c1,c2,⋅⋅⋅,cn,标记序列为每个字对应的 B I E S BIES BIES 标签。在实际应用中,对序列任务进行建模时,通常使用如下图示的链式结构,即线性链条件随机场(Linear-chain CRF)。
条件随机场(CRF)由Lafferty等人于2001年提出,结合了最大熵模型和隐马尔可夫模型的特点,是一种无向图模型,常用于标注或分析序列资料,如自然语言文字或是生物序列。近年来在分词、词性标注和命名实体识别等序列标注任务中取得了很好的效果。
安装CRF++的python库
!pip install crfpy
将训练语料标注成B M E S存储
这里使用人民日报语料库,数据如下图所示:
(图片无法显示原文内查看)
def character_tagging(input_file,output_file): input_data = open(input_file,'r',encoding='UTF-8-sig') output_data = open(output_file,'w',encoding='UTF-8-sig') for line in input_data.readlines(): word_list = line.strip().split() for word in word_list: if len(word)==1: output_data.write(word+'\tS\n') else: output_data.write(word[0]+'\tB\n') for w in word[1:len(word)-1]: output_data.write(w+'\tM\n') output_data.write(word[len(word)-1]+'\tE\n') output_data.write('\n') input_data.close() output_data.close() character_tagging('pku_training.utf8','pku_training_out.utf8')
使用CRF++工具训练条件随机场中文分词模型
需要注意的是,模型训练迭代次数默认为10k,此处为了方便演示,仅仅设置了10次,如果需要较好的模型效果,建议按照默认的迭代次数进行训练。另外大家可以在终端中直接输入crf_learn
查看其命令参数的含义。
!crf_learn -f 3 -c 4.0 template pku_training_out.utf8 crf_model
使用训练好的模型输出结果
!python2 crf_out.py
crf_out.py
注意: 因为 2和3的str类型不同,修改的地方是, 加了版本的判断, python3 不需要encode
import codecs import sys import CRFPP def crf_segmenter(input_file, output_file, tagger): input_data = codecs.open(input_file, 'r', 'utf-8') output_data = codecs.open(output_file, 'w', 'utf-8') for line in input_data.readlines(): tagger.clear() for word in line.strip(): word = word.strip() if word: tagger.add((word + "\to\tB").encode('utf-8')) tagger.parse() size = tagger.size() xsize = tagger.xsize() for i in range(0, size): for j in range(0, xsize): char = tagger.x(i, j).decode('utf-8') tag = tagger.y2(i) if tag == 'B': output_data.write(' ' + char) elif tag == 'M': output_data.write(char) elif tag == 'E': output_data.write(char + ' ') else: output_data.write(' ' + char + ' ') output_data.write('\n') input_data.close() output_data.close() crf_model = 'crf_model' input_file = 'pku_training_out.utf8' output_file = 'pku_training_out_crf.utf8' tagger = CRFPP.Tagger("-m " + crf_model) crf_segmenter(input_file, output_file, tagger)
CRF++库简介
CRF++ 是条件随机场 (CRF) 的简单、可定制和开源实现,用于分割/标记顺序数据。 CRF++ 专为通用目的而设计,将应用于各种 NLP 任务,例如命名实体识别、信息提取和文本分块。
基于线性链条件随机场中文分词方法可以有效地平衡训练语料中出现的词语和未登录词,并且可以使用模板特征引入词典信息。相较于基于词典的方法,基于线性链条件随机场中文分词方法通常可以省略未登录词的识别模块。但是需要注意的是,基于线性链条件随机场的方法只能使用字作为特征。
经过上面的介绍,相信大家对中文分词方法已经有了一些认识,但是一些其他的基础知识需要大家仔细阅读相关书籍,包括什么是未登录词、词素、汉藏语系、闪含语系、印欧语系等,大家都要有了解。下一期将和大家一起学习基于感知机的中文分词算法,并且会根据相关论文进行介绍。大家一起加油哦!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。