本文将会讲述如何利用CRF模型来实现中文分词。
所谓中文分词,就是将连续的中文汉字序列按照一定的规范重新组合成词序列的过程。关于CRF模型的介绍以及CRF实现工具CRF++的使用方法,读者可以参考文章NLP入门(八)使用CRF++实现命名实体识别(NER) 。
以下将详细讲述如何使用CRF++来实现中文分词。
语料选择
中分分词的语料,这里选择人民日报分词语料和微软中文分词语料,语料的下载方式可以参看文章最后给出的Github地址。
我们将语料加工成CRF++支持的格式,以句子迈向充满希望的新世纪——一九九八年新年讲话(附图片1张)
为例,我们加工后的结果如下:
迈 n B-Char
向 n I-Char
充 n B-Char
满 n I-Char
希 n B-Char
望 n I-Char
的 n B-Char
新 n B-Char
世 n B-Char
纪 n I-Char
— n B-Char
— n I-Char
一 n B-Char
九 n I-Char
九 n I-Char
八 n I-Char
年 n I-Char
新 n B-Char
年 n I-Char
讲 n B-Char
话 n I-Char
( n B-Char
附 n B-Char
图 n B-Char
片 n I-Char
1 n B-Char
张 n B-Char
) n B-Char
这里需要稍作说明,即我们的标签体系采用最简单的BI
体系,将词语的开头用B-Char
标签表示,词语的中间和结尾用I-Char
表示。
语料总共约10w多个样本,我们将数据集分为训练集(train.txt)和测试集(predict.txt),比例为9:1。
模型训练
我们对训练集进行训练,训练的CRF++的模板与文章NLP入门(八)使用CRF++实现命名实体识别(NER)
中一样。训练的命令如下:
crf_learn -f 3 -c 4.0 template train.data model -t
训练后的过程如下:
模型评估
下面我们将借助序列评估模块seqeval
对分词模型的预测能力进行评估。
我们先用以下命令生成模型预测文件(crf_pred.txt
):
crf_test -m model predict.txt > crf_pred.txt
接着,我们使用如下脚本进行模型评估:
# -*- coding: utf-8 -*-
from seqeval.metrics import f1_score
from seqeval.metrics import precision_score
from seqeval.metrics import accuracy_score
from seqeval.metrics import recall_score
from seqeval.metrics import classification_report
with open("crf_pred.txt", "r", encoding="utf-8") as f:
content = [_.strip() for _ in f.readlines()]
y_pred = []
y_true = []
for line in content:
if line:
y_pred.append(line.split("\t")[-1])
y_true.append(line.split("\t")[-2])
print("accuary: ", accuracy_score(y_true, y_pred))
print("p: ", precision_score(y_true, y_pred))
print("r: ", recall_score(y_true, y_pred))
print("f1: ", f1_score(y_true, y_pred))
print("classification report: ")
print(classification_report(y_true, y_pred))
模型评估的结果如下:
accuary: 0.96405717503858
p: 0.9184067155248071
r: 0.9206969935013926
f1: 0.9195504284452864
classification report:
precision recall f1-score support
Char 0.92 0.92 0.92 350075
micro avg 0.92 0.92 0.92 350075
macro avg 0.92 0.92 0.92 350075
可以发现,Char字段的F1值92%,相当不错的结果。
模型预测
接下来,我们利用上面训练好的CRF模型,对新的句子进行预测,看看其在新句子上的分词能力。
我们的预测脚本如下:
# -*- coding: utf-8 -*-
import os
text = "上海野生动物园群熊伤人事件救援画面曝光"
# 生成待预测的文本
with open("predict.data", "w", encoding="utf-8") as g:
for char in text:
g.write("%s\tn\tB-Char\n" % char)
# 利用CRF模型,调用命令行进行预测
os.system("crf_test -m model predict.data > predict_new.txt")
# 处理预测后的进行,并将其加工成中文分词后的结果
with open("predict_new.txt", "r", encoding="utf-8") as f:
content = [_.strip() for _ in f.readlines()]
predict_tags = []
for line in content:
predict_tags.append(line.split("\t")[-1])
words = []
for i in range(len(predict_tags)):
word = ""
if predict_tags[i] == "B-Char":
word += text[i]
j = i + 1
while j < len(text) and predict_tags[j] == "I-Char":
word += text[j]
j += 1
if word:
words.append(word)
# 输出预测结果
print("原句:%s" % text)
print("分词结果:%s" % ("/".join(words)))
我们从新闻中选择10个句子,对其进行分词,结果如下:
原句:上海野生动物园群熊伤人事件救援画面曝光
分词结果:上海/野生动物园/群/熊/伤/人/事件/救援/画面/曝光
原句:浙江正筹划疫苗接种工作,当地相关部门:仍仅限于紧急接种小部分人群
分词结果:浙江/正/筹划/疫苗/接种/工作/,/当地/相关/部门/:/仍/仅/限于/紧急/接种/小部分/人群
原句:亚阿停火4分钟又开炮?在法亚美尼亚人抗议 阿塞拜疆使馆前举旗
分词结果:亚阿/停火/4/分钟/又/开炮/?/在/法/亚美尼亚/人/抗议/阿塞拜疆/使馆/前/举旗
原句:土耳其被曝秘密试射俄制S400防空导弹 美方高层放出狠话
分词结果:土耳其/被/曝/秘密/试/射/俄/制/S/4/0/0/防空/导弹/美/方/高层/放/出/狠话
原句:国家税务总局稽查局副局长林枫接受纪律审查和监察调查
分词结果:国家税务总局/稽查局/副/局长/林/枫/接受/纪律/审查/和/监察/调查
原句:科技助力产业兴 澜沧旧貌换新颜
分词结果:科技/助力/产业/兴/澜沧/旧貌/换/新颜
原句:羊毛党不过是流量
分词结果:羊毛/党/不过是/流量
原句:广西龙胜金秋梯田美如画
分词结果:广西/龙/胜/金秋/梯田/美/如/画
原句:早在上映第四天,《姜子牙》累计票房就已经高达10.36亿,后来单日票房一路下跌,最低时跌至500万左右,是同档期对手《我和我的家乡》的五分之一,甚至还不及小成本电影《一点就到家》,位列国庆档新片倒数第二。用“断崖式下跌”来形容这部国漫新作的单日票房走向毫不为过。
分词结果:早/在/上映/第四天/,/《/姜子牙/》/累计/票房/就/已经/高/达/10/./3/6亿/,/后来/单/日/票房/一路/下跌/,/最低/时/跌/至/5/0/0万/左右/,/是/同/档期/对手/《/我/和/我/的/家乡/》/的/五分之一/,/甚至/还/不及/小/成本/电影/《/一点/就/到/家/》/,/位/列国庆档/新/片/倒数/第二/。/用/“/断崖式/下跌/”/来/形容/这/部/国/漫/新作/的/单/日/票房/走向/毫不/为/过/。
原句:15日,莘庄工业区新时代文明实践的“大本营”——坐落于颛盛路745号的莘庄工业区新时代文明实践分中心正式落成。
分词结果:1/5日/,莘庄/工业区/新时代/文明/实践/的/“/大本营/”/——/坐落/于/颛盛/路/7/4/5号/的莘庄/工业区/新时代/文明/实践/分/中心/正式/落成/。
添加用户词典
从上面的结果可以看出,CRF模型实现的中文分词模型效果确实不错,但也存在着一些词语没有被切分正确的情况,比如伤人、S400、林枫、羊毛党、龙胜、国庆档、颛盛路、745号等词。
笔者将会模仿结巴分词等模块,实现用户词典功能。
假设我们的用户词典文件为user_dict.txt
,加上用户词典后的预测脚本如下:
# -*- coding: utf-8 -*-
import os
text = "上海野生动物园群熊伤人事件救援画面曝光"
# 生成待预测的文本
with open("predict.data", "w", encoding="utf-8") as g:
for char in text:
g.write("%s\tn\tB-Char\n" % char)
# 利用CRF模型,调用命令行进行预测
os.system("crf_test -m model predict.data > predict_new.txt")
# 处理预测后的进行,并将其加工成中文分词后的结果
with open("predict_new.txt", "r", encoding="utf-8") as f:
content = [_.strip() for _ in f.readlines()]
predict_tags = []
for line in content:
predict_tags.append(line.split("\t")[-1])
# 通过修改预测标签实现用户词典功能
with open("user_dict.txt", "r", encoding="utf-8") as h:
user_words = [_.strip() for _ in h.readlines()]
for word in user_words:
t = len(word)
for i in range(len(text)-t):
if text[i:i+t] == word:
predict_tags[i] = "B-Char"
for j in range(i+1, i+t):
predict_tags[j] = "I-Char"
if i+t+1 < len(text):
predict_tags[i+t+1] = "I-Char"
# 对预测标签进行后处理,得到中文分词后的结果
words = []
for i in range(len(predict_tags)):
word = ""
if predict_tags[i] == "B-Char":
word += text[i]
j = i + 1
while j < len(text) and predict_tags[j] == "I-Char":
word += text[j]
j += 1
if word:
words.append(word)
print("原句:%s" % text)
print("分词结果:%s" % ("/".join(words)))
添加用户词典后,中文分词的结果如下:
原句:上海野生动物园群熊伤人事件救援画面曝光
分词结果:上海/野生动物园/群/熊/伤人/事件/救援/画面/曝光
原句:土耳其被曝秘密试射俄制S400防空导弹 美方高层放出狠话
分词结果:土耳其/被/曝/秘密/试/射/俄/制/S400/防空/导弹/美/方/高层/放/出/狠话
原句:国家税务总局稽查局副局长林枫接受纪律审查和监察调查
分词结果:国家税务总局/稽查局/副/局长/林枫/接受/纪律/审查/和/监察/调查
原句:羊毛党不过是流量
分词结果:羊毛党/不过是/流量
原句:广西龙胜金秋梯田美如画
分词结果:广西/龙胜/金秋/梯田/美/如/画
原句:早在上映第四天,《姜子牙》累计票房就已经高达10.36亿,后来单日票房一路下跌,最低时跌至500万左右,是同档期对手《我和我的家乡》的五分之一,甚至还不及小成本电影《一点就到家》,位列国庆档新片倒数第二。用“断崖式下跌”来形容这部国漫新作的单日票房走向毫不为过。
分词结果:早/在/上映/第四天/,/《/姜子牙/》/累计/票房/就/已经/高/达/10/./3/6亿/,/后来/单/日/票房/一路/下跌/,/最低/时/跌/至/5/0/0万/左右/,/是/同/档期/对手/《/我/和/我/的/家乡/》/的/五分之一/,/甚至/还/不及/小/成本/电影/《/一点/就/到/家/》/,/位/列/国庆档/新片/倒数/第二/。/用/“/断崖式/下跌/”/来/形容/这/部/国/漫/新作/的/单/日/票房/走向/毫不/为/过/。
原句:15日,莘庄工业区新时代文明实践的“大本营”——坐落于颛盛路745号的莘庄工业区新时代文明实践分中心正式落成。
分词结果:1/5日/,莘庄/工业区/新时代/文明/实践/的/“/大本营/”/——/坐落/于/颛盛路/745号/的莘庄/工业区/新时代/文明/实践/分/中心/正式/落成/。
可以看到,用户词典功能也已经生效了。
总结
本次分享利用CRF实现了中文分词这个最基本的NLP任务。
本文对应的Github地址为:https://github.com/percent4/CRF-Chinese-Word-Segment 。
感谢大家的阅读~