本文运用字标注法进行中文分词,最大熵模型方面使用开源的张乐博士的最大熵模型工具包(Maximum Entropy Modeling Toolkit for Python and C++)。使用的中文语料资源是SIGHAN提供的backoff 2005语料,目前封闭测试最好的结果是4-tag+CFR标注分词,在北大语料库上可以在准确率,召回率以及F值上达到92%以上的效果,在微软语料库上可以到达96%以上的效果。以下我们将转入这篇文章的主题,基于最大熵模型的字标注中文分词。
第一部分 最大熵模型工具包安装说明
下载安装和使用张乐博士的最大熵模型工具包,本文使用的是其在github上的代码:maxent , 安装说明:
1.进入到代码主目录maxent-master后,正常按照“configure & make & (sudo) make install就可以完成C++库的安装。
注意:(1)gcc编译器最好是4.7版本以上,我试过在4.4.3上面是不成功的,升级到4.7之后就可以了,具体请参阅:升级Ubuntu中g++和gcc的版本)。
(2)gcc版本没问题了,如果报出 ./configure错误,请参阅:Linux下./configure错误详解。
2.再进入到子目录python下,安装python包:python setup.py build & (sudo) python setup.py install,这个python库是通过强大的SWIG生成的。
注意:中间如果报出:python.h 没有各个文件或目录 的错误,请参阅:解决python.h 没有那个文件或目录 的方法。
关于这个最大熵模型工具包详情及背景,推荐看官方manual文档,写得非常详细。与“中文分词入门之字标注法2”的做法类似,这里利用这个工具包example里带的英文词性标注脚本来做字标注中文分词,先验证一下是否可行,关于这个case的解读,可以参考manual文档里的“4.6 Case Study: Building a maxent Part-of-Speech Tagger”。
第二部分 分词实践
将backoff2005里的训练数据转化为这个POS Tagger所需的训练数据格式,语料为北京大学提供的中文分词语料(来源:1998年1月《人民日报》),采用4-tag(B(Begin,词首), E(End,词尾), M(Middle,词中), S(Single,单字词))标记集,只处理utf-8编码文本。原始训练集./icwb2-data/training/pku_training.utf8的形式是人工分好词的中文句子形式,如:
迈向 充满 希望 的 新 世纪 —— 一九九八年 新年 讲话 ( 附 图片 1 张 )
** 总书记 、 国家 主席 江 泽民
( 一九九七年 十二月 三十一日 )
12月 31日 , ** 总书记 、 国家 主席 江 泽民 发表 1998年 新年 讲话 《 迈向 充满 希望 的 新 世纪 》 。 ( 新华社 记者 兰 红光 摄 )
同胞 们 、 朋友 们 、 女士 们 、 先生 们 :
使用如下的4-tag的标注脚本 character_tagging.py 对这个训练语料进行标注:
# 4 tags for character tagging: B(Begin), E(End), M(Middle), S(Single)
import codecs
import sys
def character_tagging(input_file, output_file):
input_data = codecs.open(input_file, 'r', 'utf-8')
output_data = codecs.open(output_file, 'w', 'utf-8')
for line in input_data.readlines():
word_list = line.strip().split()
for word in word_list:
if len(word) == 1:
output_data.write(word + "/S ")
else:
output_data.write(word[0] + "/B ")
for w in word[1:len(word)-1]:
output_data.write(w + "/M ")
output_data.write(word[len(word)-1] + "/E ")
output_data.write("\n")
input_data.close()
output_data.close()
if __name__ == '__main__':
if len(sys.argv) != 3:
print "Please use: python character_tagging.py input output"
sys.exit()
input_file = sys.argv[1]
output_file = sys.argv[2]
character_tagging(input_file, output_file)
只需执行“python character_tagging.py icwb2-data/training/pku_training.utf8 pku_training.tagging.utf8” 即可得到最大熵词性标注训练器所需要的输入文件pku_training.tagging.utf8,样例如下:
迈/B 向/E 充/B 满/E 希/B 望/E 的/S 新/S 世/B 纪/E —/B —/E 一/B 九/M 九/M 八/M 年/E 新/B 年/E 讲/B 话/E (/S 附/S 图/B 片/E 1/S 张/S )/S
中/B 共/M 中/M 央/E 总/B 书/M 记/E 、/S 国/B 家/E 主/B 席/E 江/S 泽/B 民/E
(/S 一/B 九/M 九/M 七/M 年/E 十/B 二/M 月/E 三/B 十/M 一/M 日/E )/S
1/B 2/M 月/E 3/B 1/M 日/E ,/S 中/B 共/M 中/M 央/E 总/B 书/M 记/E 、/S 国/B 家/E 主/B 席/E 江/S 泽/B 民/E 发/B 表/E 1/B 9/M 9/M 8/M 年/E 新/B 年/E 讲/B 话/E 《/S 迈/B 向/E 充/B 满/E 希/B 望/E 的/S 新/S 世/B 纪/E 》/S 。/S (/S 新/B 华/M 社/E 记/B 者/E 兰/S 红/B 光/E 摄/S )/S
同/B 胞/E 们/S 、/S 朋/B 友/E 们/S 、/S 女/B 士/E 们/S 、/S 先/B 生/E 们/S :/S
在/S 1/B 9/M 9/M 8/M 年/E 来/B 临/E 之/B 际/E ,/S 我/S 十/B 分/E 高/B 兴/E 地/S 通/B 过/E 中/B 央/E 人/B 民/E 广/B 播/E 电/B 台/E 、/S 中/B 国/E 国/B 际/E 广/B 播/E 电/B 台/E 和/S 中/B 央/E 电/B 视/M 台/E ,/S 向/S 全/B 国/E 各/B 族/E 人/B 民/E ,/S 向/S 香/B 港/E 特/B 别/E 行/B 政/M 区/E 同/B 胞/E 、/S 澳/B 门/E 和/S 台/B 湾/E 同/B 胞/E 、/S 海/B 外/E 侨/B 胞/E ,/S 向/S 世/B 界/E 各/B 国/E 的/S 朋/B 友/E 们/S ,/S 致/B 以/E 诚/B 挚/E 的/S 问/B 候/E 和/S 良/B 好/E 的/S 祝/B 愿/E !/S
现在就可以用张乐博士最大熵模型工具包中自带的PosTagger来训练一个字标注器了:
./maxent-master/example/postagger/postrainer.py -f pku_training.tagging.utf8 --iters 100 pku_tagger.model
这里指定迭代训练100轮,没有什么依据,仅作此次测试之用,训练结束之后,我们得到一个字标注所用的最大熵模型: pku_tagger.model,还有几个副产品。现在我们需要做得是准备一份测试语料,然后利用最大熵模型标注器对测试语料进行标注。原始的测试语料是icwb2-data/testing/pku_test.utf8 ,样例如下:
共同创造美好的新世纪——二○○一年新年贺词
(二○○○年十二月三十一日)(附图片1张)
女士们,先生们,同志们,朋友们:
2001年新年钟声即将敲响。人类社会前进的航船就要驶入21世纪的新航程。中国人民进入了向现代化建设第三步战略目标迈进的新征程。
在这个激动人心的时刻,我很高兴通过中国国际广播电台、*人民广播电台和*电视台,向全国各族人民,向香港特别行政区同胞、澳门特别行政区同胞和*同胞、海外侨胞,向世界各国的朋友们,致以新世纪第一个新年的祝贺!
过去的一年,是我国*改革开放和现代化建设进程中具有标志意义的一年。在中国*的领导下,全国各族人民团结奋斗,国民经济继续保持较快的发展势头,经济结构的战略性调整顺利部署实施。西部大开发取得良好开端。精神文明建设和*法制建设进一步加强。我们在过去几年取得成绩的基础上,胜利完成了第九个五年计划。我国已进入了全面建设小康社会,加快*现代化建设的新的发展阶段。
需要将其单字离散化并添加空格,便于标注,使用如下的python脚本 character_split.py 对测试语料进行处理:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: 52nlpcn@gmail.com
# Copyright 2014 @ YuZhen Technology
#
# split chinese characters and add space between them
import codecs
import sys
def character_split(input_file, output_file):
input_data = codecs.open(input_file, 'r', 'utf-8')
output_data = codecs.open(output_file, 'w', 'utf-8')
for line in input_data.readlines():
for word in line.strip():
output_data.write(word + " ")
output_data.write("\n")
input_data.close()
output_data.close()
if __name__ == '__main__':
if len(sys.argv) != 3:
print "Please use: python character_split.py input output"
sys.exit()
input_file = sys.argv[1]
output_file = sys.argv[2]
character_split(input_file, output_file)
执行“python character_split.py icwb2-data/testing/pku_test.utf8 pku_test.split.utf8”即可得到可用于标注测试的测试语料 pku_test.split.utf8,样例如下:
共 同 创 造 美 好 的 新 世 纪 — — 二 ○ ○ 一 年 新 年 贺 词
( 二 ○ ○ ○ 年 十 二 月 三 十 一 日 ) ( 附 图 片 1 张 )
女 士 们 , 先 生 们 , 同 志 们 , 朋 友 们 :
2 0 0 1 年 新 年 钟 声 即 将 敲 响 。 人 类 社 会 前 进 的 航 船 就 要 驶 入 2 1 世 纪 的 新 航 程 。 中 国 人 民 进 入 了 向 现 代 化 建 设 第 三 步 战 略 目 标 迈 进 的 新 征 程 。
在 这 个 激 动 人 心 的 时 刻 , 我 很 高 兴 通 过 中 国 国 际 广 播 电 台 、 中 央 人 民 广 播 电 台 和 中 央 电 视 台 , 向 全 国 各 族 人 民 , 向 香 港 特 别 行 政 区 同 胞 、 澳 门 特 别 行 政 区 同 胞 和 台 湾 同 胞 、 海 外 侨 胞 , 向 世 界 各 国 的 朋 友 们 , 致 以 新 世 纪 第 一 个 新 年 的 祝 贺 !
过 去 的 一 年 , 是 我 国 社 会 主 义 改 革 开 放 和 现 代 化 建 设 进 程 中 具 有 标 志 意 义 的 一 年 。 在 中 国 共 产 党 的 领 导 下 , 全 国 各 族 人 民 团 结 奋 斗 , 国 民 经 济 继 续 保 持 较 快 的 发 展 势 头 , 经 济 结 构 的 战 略 性 调 整 顺 利 部 署 实 施 。 西 部 大 开 发 取 得 良 好 开 端 。 精 神 文 明 建 设 和 民 主 法 制 建 设 进 一 步 加 强 。 我 们 在 过 去 几 年 取 得 成 绩 的 基 础 上 , 胜 利 完 成 了 第 九 个 五 年 计 划 。 我 国 已 进 入 了 全 面 建 设 小 康 社 会 , 加 快 社 会 主 义 现 代 化 建 设 的 新 的 发 展 阶 段 。
现在执行最大熵标注脚本即可得到字标注结果:
./maxent-master/example/postagger/maxent_tagger.py -m pku_tagger.model pku_test.split.utf8 > pku_test.split.tag.utf8
pku_test.split.tag.utf8即是标注结果,样例如下:
共/B 同/E 创/B 造/E 美/B 好/E 的/S 新/S 世/B 纪/E —/B —/M 二/M ○/M ○/M 一/M 年/E 新/S 年/S 贺/B 词/E
(/S 二/S ○/M ○/M ○/M 年/M 十/E 二/S 月/S 三/B 十/M 一/M 日/E )/S (/S 附/S 图/B 片/E 1/B 张/S )/S
女/B 士/E 们/S ,/S 先/B 生/E 们/S ,/S 同/B 志/E 们/S ,/S 朋/B 友/E 们/S :/S
2/M 0/M 0/M 1/M 年/E 新/S 年/B 钟/E 声/B 即/E 将/S 敲/B 响/E 。/S 人/B 类/E 社/B 会/E 前/B 进/E 的/S 航/B 船/E 就/S 要/S 驶/B 入/E 2/M 1/B 世/M 纪/E 的/S 新/S 航/B 程/E 。/S 中/B 国/E 人/B 民/E 进/B 入/E 了/S 向/S 现/B 代/M 化/E 建/B 设/E 第/B 三/M 步/E 战/B 略/E 目/B 标/E 迈/B 进/E 的/S 新/S 征/B 程/E 。/S
在/S 这/B 个/E 激/B 动/E 人/B 心/E 的/S 时/B 刻/E ,/S 我/S 很/S 高/B 兴/E 通/B 过/E 中/B 国/E 国/B 际/E 广/B 播/E 电/B 台/E 、/S 中/B 央/E 人/B 民/E 广/B 播/E 电/B 台/E 和/S 中/B 央/E 电/B 视/M 台/E ,/S 向/S 全/B 国/E 各/B 族/E 人/B 民/E ,/S 向/S 香/B 港/E 特/B 别/E 行/B 政/M 区/E 同/B 胞/E 、/S 澳/B 门/E 特/B 别/E 行/B 政/M 区/E 同/B 胞/E 和/S 台/B 湾/E 同/B 胞/E 、/S 海/B 外/E 侨/B 胞/E ,/S 向/S 世/B 界/E 各/B 国/E 的/S 朋/B 友/E 们/S ,/S 致/B 以/E 新/S 世/B 纪/E 第/B 一/E 个/S 新/B 年/E 的/S 祝/B 贺/E !/S
过/B 去/E 的/S 一/B 年/E ,/S 是/S 我/B 国/E 社/B 会/M 主/M 义/E 改/B 革/E 开/B 放/E 和/S 现/B 代/M 化/E 建/B 设/E 进/B 程/E 中/S 具/B 有/E 标/B 志/E 意/B 义/E 的/S 一/B 年/E 。/S 在/S 中/B 国/E 共/B 产/M 党/E 的/S 领/B 导/E 下/S ,/S 全/B 国/E 各/B 族/E 人/B 民/E 团/B 结/E 奋/B 斗/E ,/S 国/B 民/E 经/B 济/E 继/B 续/E 保/B 持/E 较/S 快/S 的/S 发/B 展/E 势/B 头/E ,/S 经/B 济/E 结/B 构/E 的/S 战/B 略/M 性/E 调/B 整/E 顺/B 利/E 部/B 署/E 实/B 施/E 。/S 西/B 部/E 大/B 开/E 发/B 取/E 得/S 良/B 好/E 开/B 端/E 。/S 精/B 神/M 文/M 明/E 建/B 设/E 和/S 民/B 主/M 法/M 制/E 建/B 设/E 进/B 一/M 步/E 加/B 强/E 。/S 我/B 们/E 在/S 过/B 去/E 几/S 年/S 取/B 得/E 成/B 绩/E 的/S 基/B 础/E 上/S ,/S 胜/B 利/E 完/B 成/E 了/S 第/B 九/E 个/S 五/S 年/S 计/B 划/E 。/S 我/B 国/E 已/S 进/B 入/E 了/S 全/B 面/E 建/B 设/E 小/B 康/E 社/B 会/E ,/S 加/B 快/E 社/B 会/M 主/M 义/E 现/B 代/M 化/E 建/B 设/E 的/S 新/S 的/S 发/B 展/E 阶/B 段/E 。/S
使用下面的脚本,按标注的词位信息讲这份结果再转化为分词结果,同时去掉文件中的空行, character_2_word.py:
#!/usr/bin/env python
#-*-coding:utf-8-*-
#
#Combining characters based the 4-tag tagging info
import codecs
import sys
def character_2_word(input_file, output_file):
input_data = codecs.open(input_file, 'r', 'utf-8')
output_data = codecs.open(output_file, 'w', 'utf-8')
#4-tags for character tagging: B(Begin), M(Middle), E(End), S(Single)
for line in input_data.readlines():
if len(line) > 1:
char_tag_list = line.strip().split()
for char_tag in char_tag_list:
char_tag_pair = char_tag.split('/')
char = char_tag_pair[0]
tag = char_tag_pair[1]
if tag == 'B':
output_data.write(' ' + char)
elif tag == 'M':
output_data.write(char)
elif tag == 'E':
output_data.write(char + ' ')
else: #tag == 'S':
output_data.write(' ' + char + ' ')
output_data.write("\n")
input_data.close()
output_data.close()
if __name__ == '__main__':
if len(sys.argv) != 3:
print "Usage: python character_2_word.py input output"
sys.exit()
input_file = sys.argv[1]
output_file = sys.argv[2]
character_2_word(input_file, output_file)
执行 “python character_2_word.py pku_test.split.tag.utf8 pku_test.split.tag2word.utf8” 即可得到合并后的分词结果pku_test.split.tag2word.utf8,样例如下:
共同 创造 美好 的 新 世纪 ——二○○一年 新 年 贺词
( 二 ○○○年十 二 月 三十一日 ) ( 附 图片 1 张 )
女士 们 , 先生 们 , 同志 们 , 朋友 们 :
2001年 新 年钟 声即 将 敲响 。 人类 社会 前进 的 航船 就 要 驶入 2 1世纪 的 新 航程 。 中国 人民 进入 了 向 现代化 建设 第三步 战略 目标 迈进 的 新 征程 。
在 这个 激动 人心 的 时刻 , 我 很 高兴 通过 中国 国际 广播 电台 、 * 人民 广播 电台 和 * 电视台 , 向 全国 各族 人民 , 向 香港 特别 行政区 同胞 、 澳门 特别 行政区 同胞 和 * 同胞 、 海外 侨胞 , 向 世界 各国 的 朋友 们 , 致以 新 世纪 第一 个 新年 的 祝贺 !
过去 的 一年 , 是 我国 * 改革 开放 和 现代化 建设 进程 中 具有 标志 意义 的 一年 。 在 中国 * 的 领导 下 , 全国 各族 人民 团结 奋斗 , 国民 经济 继续 保持 较 快 的 发展 势头 , 经济 结构 的 战略性 调整 顺利 部署 实施 。 西部 大开 发取 得 良好 开端 。 精神文明 建设 和 *法制 建设 进一步 加强 。 我们 在 过去 几 年 取得 成绩 的 基础 上 , 胜利 完成 了 第九 个 五 年 计划 。 我国 已 进入 了 全面 建设 小康 社会 , 加快 * 现代化 建设 的 新 的 发展 阶段 。
有了这个字标注分词结果,我们就可以利用backoff2005的测试脚本来测一下这次分词的效果了:
./icwb2-data/scripts/score ./icwb2-data/gold/pku_training_words.utf8 ./icwb2-data/gold/pku_test_gold.utf8 pku_test.split.tag2word.utf8 > pku_maxent_segment.score
从上图中看出,召回率为83.7%,准确率为84.1%。
参考资料: 中文分词之字标注法 http://www.52nlp.cn/中文分词入门之字标注法3