基于BERT预训练的中文命名实体识别TensorFlow实现

BERT-BiLSMT-CRF-NER

Tensorflow solution of NER task Using BiLSTM-CRF model with Google BERT Fine-tuning

GitHub: https://github.com/macanv/BERT-BiLSTM-CRF-NER

本文目录机构:

自己训练模型

说明

结果

使用自己的数据

2019.1.31更新,支持pip install package

现在可以使用下面的命令下载软件包了:

pip install bert-base==0.0.7 -i https://pypi.python.org/simple

1

或者使用基于源代码的安装:

git clone https://github.com/macanv/BERT-BiLSTM-CRF-NER

cd BERT-BiLSTM-CRF-NER/

python3 setup.py install

1

2

3

如果没啥问题,你将会看到这个:

笔者在windows10/ Linux/ Mac OSX上都测试过,安装没有问题。

软件包现在支持的功能

命名实体识别的训练

命名实体识别的服务C/S

继承优秀开源软件:bert_as_service(hanxiao)的BERT所有服务

4. 文本分类服务 (2019.2.19)

内容还会继续补充,同时欢迎大神们分享训练的模型或者新的方法或者数据(弱鸡的我并不会用在商业上,毕竟还是一个毕业即失业的渣渣~~)。

基于命名行训练命名实体识别模型:

安装完bert-base后,会生成两个基于命名行的工具,其中bert-base-ner-train支持命名实体识别模型的训练,你只需要指定训练数据的目录,BERT相关参数的目录即可。可以使用下面的命令查看帮助

bert-base-ner-train -help

1

训练的事例命名如下:

bert-base-ner-train \

-data_dir {your dataset dir}\

-output_dir {training output dir}\

-init_checkpoint {Google BERT model dir}\

-bert_config_file {bert_config.json under the Google BERT model dir} \

-vocab_file {vocab.txt under the Google BERT model dir}

1

2

3

4

5

6

参数说明

其中data_dir是你的数据所在的目录,训练数据,验证数据和测试数据命名格式为:train.txt, dev.txt,test.txt,请按照这个格式命名文件,否则会报错。

训练数据的格式如下:

海 O

钓 O

比 O

赛 O

地 O

点 O

在 O

厦 B-LOC

门 I-LOC

与 O

金 B-LOC

门 I-LOC

之 O

间 O

的 O

海 O

域 O

。 O

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

每行得第一个是字,第二个是它的标签,使用空格’ \'分隔,请一定要使用空格。句与句之间使用空行划分。程序会自动读取你的数据。

output_dir: 训练模型输出的文件路径,模型的checkpoint以及一些标签映射表都会存储在这里,这个路径在作为服务的时候,可以指定为-ner_model_dir

init_checkpoint: 下载的谷歌BERT模型

bert_config_file : 谷歌BERT模型下面的bert_config.json

vocab_file: 谷歌BERT模型下面的vocab.txt

训练完成后,你可以在你指定的output_dir中查看训练结果。

将命名实体识别任务进行服务部署

作为服务的很多代码都来自优秀的开源项目: bert as service of hanxiao 但是我不知道这样改动是不是违反了某些许可规定,如果有违反,请马上告诉我,我将第一时间进行修改.而且服务端的代码很解耦,修改为另外一种任务的服务也是很简单的,例如文本分类,我将会不就提供这一功能,也欢迎愿意分享的童鞋分享模型或者代码。

作为服务的命名是:bert-base-serving-start,同样的,你可以先使用-help查看相关帮助

bert-base-serving-start -help

1

作为命名实体识别任务的服务,这两个目录是你必须指定的:ner_model_dir, bert_model_dir

然后你就可以使用下面的命令启动了:

bert-base-serving-start \

-model_dir C:\workspace\python\BERT_Base\output\ner2 \

-bert_model_dir F:\chinese_L-12_H-768_A-12

-mode NER

1

2

3

4

参数解释

bert_model_dir: 谷歌BERT模型的解压路径,可以在这里下载 https://github.com/google-research/bert

model_dir: 训练好的NER模型或者文本分类模型的路径,对于上面的output_dir

model_pd_dir: 运行模型优化代码后, 经过模型压缩后的存储路径,例如运行上面的命令后改路径下会产生 ner_model.pb 这个二进制文件

mode:NER 或者是BERT这两个模式,类型是字符串,如果是NER,那么就会启动NER的服务,如果是BERT,那么具体参数将和[bert as service] 项目中得一样。

我提供了命名实体识别pb模型下载:https://pan.baidu.com/s/1m9VcueQ5gF-TJc00sFD88w, 提取码: guqq

文本分类模型:https://pan.baidu.com/s/1oFPsOUh1n5AM2HjDIo2XCw, 提取码: bbu8

文本分类使用的是bert中的demo:run_classxxx.py,在运行的时候使用Pickle序列化了label_list和id2label折两个变量。

将 ner_mode.pb/classification_model.pb 文件放到 model_pd_dir目录下,将命名识别的label_list.pkl和id2map.pkl不同的模型不同的文件夹,因为他们同名,但是内容不一样,需要区分开来

命名实体识别模型只支持人名,地名,住址机构名的识别,在我的测试数据上有95.6%的F1值(实体级别的得分)

文本分类模型数据来自清华大学的文本分类数据:http://thuctc.thunlp.org/ , 在测试数据上准确率为98%~99%的准确率

肥肠欢迎大家分享你们训练的更好的模型。

如果使用的下载的模型,你可以使用下面的命令启动,替换你自己的路径即可:

bert-base-serving-start -model_pd_dir /home/macan/ml/workspace/BERT_Base/output/predict_optimizer \

-bert_model_dir /home/macan/ml/data/chinese_L-12_H-768_A-12/ \

-ner_model_dir /home/macan/ml/data/bert_ner \

-num_worker 8

-mode NER

1

2

3

4

5

你将会看到下面的启动信息(启动log有点多,分两张图截):

在本地连接服务端进行命名实体识别的测试

你可以使用下面的代码进行服务的连接,在本地进行NER测试,客户端代码如下:

import time

from bert_base.client import BertClient

# 指定服务器的IP

with BertClient(ip=\'XXX,XXX,XXX,XXX\', ner_model_dir=ner_model_dir, show_server_config=False, check_version=False, check_length=False, mode=\'NER\') as bc:

start_t = time.perf_counter()

str = \'1月24日,新华社对外发布了中央对雄安新区的指导意见,洋洋洒洒1.2万多字,17次提到北京,4次提到天津,信息量很大,其实也回答了人们关心的很多问题。\'

rst = bc.encode([str, str]) #测试同时输入两个句子,多个输入同理

print(\'rst:\', rst)

print(time.perf_counter() - start_t)

1

2

3

4

5

6

7

8

9

运行后,会输出下面的信息:

结果说明:

返回的结果就是序列标注的结果,再往后的工作就不准备再写了,因为后面的操作会涉及到一些策略问题,写的太多,影响代码的灵活,例如有童鞋在terminal_predict.py的代码上提了issue,无法应用到自己的数据中。这样看起来,也比较直观吧~~

到此,基于命令行的用法已经讲完,不明白的地方请评论或者在GitHub上提交issue,觉得有用,麻烦在GitHub上点个star吧~~

###########################################################################################

以下是基于源代码的训练和启动服务的教程

###########################################################################################

自己训练命名实体识别模型

使用谷歌的BERT模型在BLSTM-CRF模型上进行预训练用于中文命名实体识别的Tensorflow代码’

代码已经托管到GitHub 代码传送门 大家可以去clone 下来亲自体验一下!

git clone https://github.com/macanv/BERT-BiLSTM-CRF-NER

1

关于BERT的相关理论文章不是本文的主要目的,而且网上简介该部分的文章多如牛毛,大家自行去查看吧,本文着重讲解基于BERT用于中文命名实体的fine-tuning 过程。

1. 下载Google BERT 预训练模型:

下载

wget https://storage.googleapis.com/bert_models/2018_11_03/chinese_L-12_H-768_A-12.zip

解压

unzip chinese_L-12_H-768_A-12.zip

1

2

3

4

2. 训练模型

下载了Google的BERT模型和我的GitHub代码后,就可以开始训练啦

训练之前先在项目目录中新建一个output文件夹,模型的输出,和结构都会保存在这个目录中

mkdir output

1

训练的时候需要一些参数,你可以使用命名行的形式进行模型参数指定,例如下面的方法:

python3 bert_lstm_ner.py \

--task_name="NER" \

--do_train=True \

--do_eval=True \

--do_predict=True

--data_dir=NERdata \

--vocab_file=checkpoint/vocab.txt \

--bert_config_file=checkpoint/bert_config.json \

--init_checkpoint=checkpoint/bert_model.ckpt \

--max_seq_length=128 \

--train_batch_size=32 \

--learning_rate=2e-5 \

--num_train_epochs=3.0 \

--output_dir=./output/result_dir/

1

2

3

4

5

6

7

8

9

10

11

12

13

14

笔者比较菜,选择的是将默认参数写在代码中,开始训练的之前,只需要修改下面的代码即可,代码在bert_lstm_ner.py文件中

if os.name == \'nt\': #windows path config

bert_path = \'{your BERT model path}\'

root_path = \'{project path}\'

else: # linux path config

bert_path = \'{your BERT model path}\'

root_path = \'{project path}\'

1

2

3

4

5

6

os.name==\'nt’是表示识别到的系统是windows,其余的是Linux,这里只需要修改一个,如果你是windows训练修改os.name=\'nt’下面的路径就好了,Linux或者Mac修改else下面的两个路径。

两个路径说明:

bert_path: 就是在步骤1中下载解压的BERT模型的路径,复制绝对路径替换即可,例如我项目中所写的路径

root_path: 这个是项目的路径,也是一个绝对路径,即BERT-BiLSTM-CRF-NER的路径

修改好两个路径后,就可以开始训练了:

python3 bert_lstm_ner.py

1

说明:

模型代码主要在bert_lstm_ner.py中的create_model函数中

下面对该函数逻辑进行讲解:

1使用bert模型对我们的输入进行represent

#使我们的input_ids数据通过bert 网络结构

model = modeling.BertModel(

config=bert_config,

is_training=is_training,

input_ids=input_ids,

input_mask=input_mask,

token_type_ids=segment_ids,

use_one_hot_embeddings=use_one_hot_embeddings

)

# 获取bert 模型最后一层

embedding = model.get_sequence_output()

1

2

3

4

5

6

7

8

9

10

11

bert 的最后一层是所有transformer结果的最后一维,其是一个三维向量维度是:[batch_size, seq_length, embedding_size],可以类比的理解为我们使用tf.nn.embedding_lookup获取的结果。

2 将embedding 作为LSTM结构的输入:

# 加载BLSTM-CRF模型对象

blstm_crf = BLSTM_CRF(embedded_chars=embedding, hidden_unit=FLAGS.lstm_size, cell_type=FLAGS.cell, num_layers=FLAGS.num_layers,

dropout_rate=FLAGS.droupout_rate, initializers=initializers, num_labels=num_labels,

seq_length=max_seq_length, labels=labels, lengths=lengths, is_training=is_training)

# 获取添加我们自己网络结构后的结果,这些结果包括loss, logits, trans, pred_ids

rst = blstm_crf.add_blstm_crf_layer(crf_only=True)

1

2

3

4

5

6

这里有几点需要说明:

因为BERT里面已经存在双向编码,所以LSTM并不是必须的,可以将BERT最后一层的结构直接扔给CRF进行解码。所以在代码中通过在add_blstm_crf_layer函数中的crf_only参数进行控制我们训练的时候使用的是那种网络结构用于最后的fine-tuning.通过两种结构的训练结果对比,其实他们的最后结果相差不大,可以说基本是一样的,足见transformer的强大。

crf_only=True 是我们fine-tuning 只使用CRF进行解码,不再使用传统经典的BLSTM-CRF,False表示使用blstm-crf这样的网络结构。

但是我在试验中发现,只使用CRF的训练时间要比BLSTM-CRF结构的时间要长,这一点我百思不得其解,按理加了BLSTM网络的参数会更多,如果有大佬发现这是个错的观察或者有合理的解释,麻烦不吝赐教。

实验结果

1 基于label计算出来的指标:

In dev data set:

In test data set

2 在很多地方命名实体的结果使用基于实体级别的评测更为合理,下面是实体级别的评测结果。

评测脚本使用的是conlleval.pl, conlleval.py

提供我训练的模型下载:

my model can download from baidu cloud:

链接:https://pan.baidu.com/s/1GfDFleCcTv5393ufBYdgqQ 提取码:4cus

3 在线预测

当你的模型训练完后,可以使用下面的脚本加载模型,进行在线预测

python3 terminal_predict.py

1

使用自己的数据:

BERT的大腿简直太粗了,效果很好有木有,看到这样的效果,是不是很想再自己的数据上进行测试一番呢? 其实改的地方很少,只需要修改bert_lstm_ner.py文件中的几行代码就好啦:

get_labels 函数

def get_labels(self):

return ["O", "B-PER", "I-PER", "B-ORG", "I-ORG", "B-LOC", "I-LOC", "X", "[CLS]", "[SEP]"]

1

2

这里是我数据中所有的标签,其中"X", “[CLS]”, “[SEP]” 是附加的, “[CLS]”, "[SEP]"是句子的开始和结束标志,X是wordpice产生的东西,中文中目前还没有遇到过,可以不用管,大家要改的话,就改前面的标签就好啦。

例如你想加一个时间类型的实体,就加 “B-TIME”, “I-TIME”

如果你想应用于分词中,那就没有-XXX了。就是B,I这些,简而言之,就是你的序列标注数据中的第二列的标签的set集合。

你也可以把get_labels函数写成这样一劳永逸,但是要注意在测试集或者验证机中出现的OOLabel哦:

def get_labels(self):

# 通过读取train文件获取标签的方法会出现一定的风险。

if os.path.exists(os.path.join(FLAGS.output_dir, \'label_list.pkl\')):

with codecs.open(os.path.join(FLAGS.output_dir, \'label_list.pkl\'), \'rb\') as rf:

self.labels = pickle.load(rf)

else:

if len(self.labels) > 0:

self.labels = self.labels.union(set(["X", "[CLS]", "[SEP]"]))

with codecs.open(os.path.join(FLAGS.output_dir, \'label_list.pkl\'), \'wb\') as rf:

pickle.dump(self.labels, rf)

else:

self.labels = ["O", \'B-TIM\', \'I-TIM\', "B-PER", "I-PER", "B-ORG", "I-ORG", "B-LOC", "I-LOC", "X", "[CLS]", "[SEP]"]

return self.labels

1

2

3

4

5

6

7

8

9

10

11

12

13

参考:

The evaluation codes come from:https://github.com/guillaumegenthial/tf_metrics/blob/master/tf_metrics/init.py

https://github.com/google-research/bert

https://github.com/kyzhouhzau/BERT-NER

https://github.com/zjy-ucas/ChineseNER

https://github.com/hanxiao/bert-as-service

---------------------

作者:Macanv

来源:CSDN

原文:https://blog.csdn.net/macanv/article/details/85684284

版权声明:本文为博主原创文章,转载请附上博文链接!