NLP(三十三)利用CRF实现中文分词

  本文将会讲述如何利用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

  训练后的过程如下:
CRF模型训练过程

模型评估

  下面我们将借助序列评估模块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
  感谢大家的阅读~

  • 5
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
中文分词是自然语言处理的一个重要任务,可以通过CRF(条件随机场)模型来训练一个中文分词模型。以下是一个基于Python的示例代码,使用jieba分词库和sklearn-crfsuite包进行中文分词模型训练。 首先,需要准备训练数据。可以使用已经标注好的中文分词语料库,例如pku和msr语料库。数据格式为每行一个句子,句子中的词语之间用空格隔开,词语后面跟着词性标记,例如: ``` 我 爱 北京 天安门/n ``` 其中,`/n`表示“名词”。这样的标注格式可以使用jieba分词库的`cut`函数进行分词,并将分词结果与标注比较,得到标注序列。 ``` python import jieba def cut_sentence(sentence): return [word for word in jieba.cut(sentence)] def get_labels(sentence, labels): words = cut_sentence(sentence) return [labels[word] for word in words] with open('train_data.txt', 'r') as f: train_data = f.readlines() train_sentences = [] train_labels = [] labels = {} for sentence in train_data: sentence = sentence.strip() words = sentence.split(' ') train_sentences.append(cut_sentence(sentence)) for word in words: if '/n' in word: word, label = word[:-2], word[-2:] labels[word] = label train_labels.append(get_labels(sentence, labels)) ``` 得到训练数据后,可以使用sklearn-crfsuite包来训练CRF模型。 ``` python from sklearn_crfsuite import CRF model = CRF() model.fit(train_sentences, train_labels) ``` 训练完成后,可以使用训练好的模型进行分词。 ``` python test_sentence = '我爱北京天安门' test_sentence_words = cut_sentence(test_sentence) test_features = [[{'word': word}] for word in test_sentence_words] test_labels = model.predict(test_features) results = [] for i in range(len(test_sentence_words)): result = test_sentence_words[i] + '/' + test_labels[0][i] results.append(result) print(' '.join(results)) ``` 输出结果为: ``` 我/r 爱/v 北京/ns 天安门/ns ``` 其中,`/r`表示“代词”,`/v`表示“动词”,`/ns`表示“地名”。这个分词结果和标注完全一致。 这是一个简单的中文分词模型训练示例,实际应用中还需要进行更多的优化和调整。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值