训练自己的分词系统
前言
在中文分词领域,已经有着很多优秀的工具,例如:
- jieba分词
- SnowNLP
- 北京大学PKUse
- 清华大学THULAC
- HanLP
- FoolNLTK
- 哈工大LTP
- 斯坦福分词器CoreNLP
- BaiduLac
这里,我们不使用上述的工具,而是利用bert训练一个自己的分词器。
数据预处理
首先我们查看下初始的数据:data/sighan2005/raw_data/training.txt
1998年 , 中国 人民 将 满怀信心 地 开创 新 的 业绩 。 尽管 我们 在 经济社会 发展 中 还 面临 不少 困难 , 但 我们 有 *理论 的 指引 , 有 改革 开放 近 20 年 来 取得 的 伟大 成就 和 积累 的 丰富 经验 , 还有 其他 的 各种 有利 条件 , 我们 一定 能够 克服 这些 困难 , 继续 稳步前进 。 只要 我们 进一步 解放思想 , 实事求是 , 抓住 机遇 , 开拓进取 , 建设 有 中国 特色 社会主义 的 道路 就 会 越 走 越 宽广 。
实现 祖国 的 完全 统一 , 是 海内外 全体 中国 人 的 共同 心愿 。 通过 中 葡 双方 的 合作 和 努力 , 按照 “ 一国两制 ” 方针 和 澳门 《 基本法 》 , 1999年 12月 澳门 的 回归 一定 能够 顺利 实现 。
台湾 是 中国 领土 不可分割 的 一 部分 。 完成 祖国 统一 , 是 大势所趋 , 民心所向 。 任何 企图 制造 “ 两 个 中国 ” 、 “ 一中一台 ” 、 “ 台湾 独立 ” 的 图谋 , 都 注定 要 更 失败 。 希望 台湾 当局 以 民族 大义 为重 , 拿 出 诚意 , 采取 实际 的 行动 , 推动 两岸 经济 文化 交流 和 人员 往来 , 促进 两岸 直接 通邮 、 通航 、 通商 的 早日 实现 , 并 尽早 回应 我们 发出 的 在 一个 中国 的 原则 下 两岸 进行 谈判 的 郑重 呼吁 。
环顾 全球 , 日益 密切 的 世界 经济 联系 , 日新月异 的 科技 进步 , 正在 为 各国 经济 的 发展 提供 历史 机遇 。 但是 , 世界 还 不 安宁 。 南北 之间 的 贫富 差距 继续 扩大 ; 局部 冲突 时有发生 ; 不 公正 不 合理 的 旧 的 国际 政治经济 秩序 还 没有 根本 改变 ; 发展中国家 在 激烈 的 国际 经济 竞争 中 仍 处于 弱势 地位 ; 人类 的 生存 与 发展 还 面临 种种 威胁 和 挑战 。 和平 与 发展 的 前景 是 光明 的 , 21 世纪 将 是 充满 希望 的 世纪 。 但 前进 的 道路 不 会 也 不 可能 一帆风顺 , 关键 是 世界 各国 人民 要 进一步 团结 起来 , 共同 推动 早日 建立 公正 合理 的 国际 政治经济 新 秩序 。
中国 政府 将 继续 坚持 奉行 独立自主 的 和平 外交 政策 , 在 和平共处 五 项 原则 的 基础 上 努力 发展 同 世界 各国 的 友好 关系 。 中国 愿意 加强 同 联合国 和 其他 国际 组织 的 协调 , 促进 在 扩大 经贸 科技 交流 、 保护 环境 、 消除 贫困 、 打击 国际 犯罪 等 方面 的 国际 合作 。 中国 永远 是 维护 世界 和平 与 稳定 的 重要 力量 。 中国 人民 愿 与 世界 各国 人民 一道 , 为 开创 持久 和平 、 共同 发展 的 新 世纪 而 不懈努力 !
在 这 辞旧迎新 的 美好 时刻 , 我 祝 大家 新年 快乐 , 家庭 幸福 !
谢谢 ! ( 新华社 北京 12月 31日 电 )
在 十五大 精神 指引 下 胜利 前进 —— 元旦 献辞
这里面每一行是一段文本。一段文本中不同的词是以两个空格进行分隔的。接下来,我们要将数据处理成我们需要的样子。在data/sighan2005/raw_data/下新建一个process.py
import json
from pprint import pprint
max_seq_len= 512
# -1:索引为0-511,-2:去掉CLS和SEP,+1:词索引到下一位
max_seq_len = max_seq_len - 1 - 2 + 1
with open("training.txt", "r", encoding="utf-8") as fp:
data = fp.readlines()
res = []
i = 0
for d in data:
d = d.strip().split(" ")
dtype = "word"
start = 0
tmp = []
labels = []
j = 0
for word in d:
start = len("".join(tmp))
tmp.append(word)
end = start + len(word)
labels.append(["T{}".format(j), dtype, start, end, word])
j += 1
if end > max_seq_len:
sub_tmp = tmp[:-1]
sub_labels = labels[:-1]
end = start + len("".join(sub_tmp))
text = "".join(sub_tmp)
res.append({
"id": i,
"text": text,
"labels": sub_labels
})
start = 0
tmp = [word]
end = len("".join(tmp))
labels = [["T{}".format(0), dtype, 0, end, word]]
i += 1
if tmp:
text = "".join(tmp)
res.append({
"id": i,
"text": text,
"labels": labels
})
i += 1
with open("../mid_data/train.json", 'w', encoding="utf-8") as fp:
json.dump(res, fp, ensure_ascii=False)
labels = ["word"]
with open("../mid_data/labels.json", 'w', encoding="utf-8") as fp:
json.dump(labels, fp, ensure_ascii=False)
nor_ent2id = {"O":0, "B-word":1, "I-word":2, "E-word":3, "S-word":4}
with open("../mid_data/nor_ent2id.json", 'w', encoding="utf-8") as fp:
json.dump(nor_ent2id, fp, ensure_ascii=False)
with open("test.txt", "r", encoding="utf-8") as fp:
data = fp.readlines()
res = []
i = 0
for d in data:
d = d.strip().split(" ")
dtype = "word"
start = 0
tmp = []
labels = []
j = 0
for word in d:
start = len("".join(tmp))
tmp.append(word)
end = start + len(word)
labels.append(["T{}".format(j), dtype, start, end, word])
j += 1
if end > max_seq_len:
sub_tmp = tmp[:-1]
sub_labels = labels[:-1]
end = start + len("".join(sub_tmp))
text = "".join(sub_tmp)
res.append({
"id": i,
"text": text,
"labels": sub_labels
})
start = 0
tmp = [word]
end = len("".join(tmp))
labels = [["T{}".format(0), dtype, 0, end, word]]
i += 1
if tmp:
text = "".join(tmp)
res.append({
"id": i,
"text": text,
"labels": labels
})
i += 1
with open("../mid_data/test.json", 'w', encoding="utf-8") as fp:
json.dump(res, fp, ensure_ascii=False)
上述需要我们自己定义一个max_seq_len,然后会对句子进行切分,最后会在data/sighan2005/mid_data/下生成以下文件:train.json、test.json、labels.json、nor_ent2id.json。其中train.json部分数据如下:
[{"id": 0, "text": "迈向充满希望的新世纪——一九九八年新年讲话(附图片1张)", "labels": [["T0", "word", 0, 2, "迈向"], ["T1", "word", 2, 4, "充满"], ["T2", "word", 4, 6, "希望"], ["T3", "word", 6, 7, "的"], ["T4", "word", 7, 8, "新"], ["T5", "word", 8, 10, "世纪"], ["T6", "word", 10, 12, "——"], ["T7", "word", 12, 17, "一九九八年"], ["T8", "word", 17, 19, "新年"], ["T9", "word", 19, 21, "讲话"], ["T10", "word", 21, 22, "("], ["T11", "word", 22, 23, "附"], ["T12", "word", 23, 25, "图片"], ["T13", "word", 25, 26, "1"], ["T14", "word", 26, 27, "张"], ["T15", "word", 27, 28, ")"]]},]
labels.json是实体标签,这里就是一个["word"]。nor_ent2id是实体对应的BIOES标签,这里是:{"O": 0, "B-word": 1, "I-word": 2, "E-word": 3, "S-word": 4}。
有了这些数据之后,就可以生成bert所需要的数据了,具体代码在preprocess.py里面。这里面我们要定义以下参数:
dataset = "sighan2005"
args.max_seq_len = 512 # 和之前process.py里面保持一致
运行之后部分样例如下:
*** train_example-1 ***
[CLS] 迈 向 充 满 希 望 的 新 世 纪 [UNK] [UNK] 一 九 九 八 年 新 年 讲 话 ( 附 图 片 1 张 ) [SEP]
text: [CLS] 迈 向 充 满 希 望 的 新 世 纪 — — 一 九 九 八 年 新 年 讲 话 ( 附 图 片 1 张 ) [SEP]
token_ids: [101, 6815, 1403, 1041, 4007, 2361, 3307, 4638, 3173, 686, 5279, 100, 100, 671, 736, 736, 1061, 2399, 3173, 2399, 6382, 6413, 8020, 7353, 1745, 4275, 8029, 2476, 8021, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,