BERT模型从训练到部署全流程
BERT模型从训练到部署全流程
Tag: BERT 训练 部署
缘起
在群⾥看到许多朋友在使⽤BERT模型,⽹上多数⽂章只提到了模型的训练⽅法,后⾯的⽣产部署及调⽤并没有说明。有毅力
这段时间使⽤BERT模型完成了从数据准备到⽣产部署的全流程,在这⾥整理出来,⽅便⼤家参考。
在下⾯我将以⼀个“⼿机评论的情感分类”为例⼦,简要说明从训练到部署的全部流程。最终完成后可以使⽤⼀个⽹页进⾏交互,实时地对输⼊的评论语句进⾏分类判断。
什么的历史基本架构
基本架构为:
BERT模型服务端
API服务端资助政策
应⽤端
+-------------------+
| 应⽤端(HTML) |
+-------------------+
^^
||
VV
+-------------------+
| API服务端 |
+-------------------+
^^
||
VV
+-------------------+
| BERT模型服务端 |
+-------------------+
架构说明:
BERT模型服务端
加载模型,进⾏实时预测的服务;
使⽤的是 BERT-BiLSTM-CRF-NER
猪的成语API服务端
调⽤实时预测服务,为应⽤提供API接⼝的服务,⽤flask编写;
应⽤端
最终的应⽤端;
我这⾥使⽤⼀个HTML⽹页来实现;
本项⽬完整源码地址:
项⽬博客地址:
附件:
本例中训练完成的模型⽂件.ckpt格式及.pb格式⽂件,由于⽐较⼤,已放到⽹盘提供下载:
链接:/s/1DgVjRK7zicbTlAAkFp7nWw
提取码:8iaw
宋朝皇帝列表及简介
如果你想跳过前⾯模型的训练过程,可以直接使⽤训练好的模型,来完成后⾯的部署。
关键节点
主要包括以下关键节点:
数据准备
模型训练
模型格式转化
服务端部署与启动
API服务编写与部署
客户端(⽹页端的编写与部署)
数据准备
这⾥⽤的数据是⼿机的评论,数据⽐较简单,三个分类: -1,0,1 表⽰负⾯,中性与正⾯情感
数据格式如下:
1 ⼿机很好,漂亮时尚,赠品⼀般
1 ⼿机很好。包装也很完美,赠品也是收到货后马上就发货了
1 第⼀次在第三⽅买的⼿机开始很担⼼不过查⼀下是正品很满意
1 很不错续航好系统流畅
1 不知道真假,相信店家吧
1 快递挺快的,荣耀10⼿感还是不错的,玩了会王者还不错,就是前后玻璃,
1 流很快,⼿机到⼿感觉很酷,⽩⾊适合⼥⼠,很惊艳!常好,运⾏速度快,流畅!
1 ⽤了⼀天才来评价,都还可以,很满意
1 幻影蓝很好看啊,炫彩系列时尚时尚最时尚,速度快,配送运⾏?做活动优惠买的,开⼼?
1 快递速度快,很赞!软件更新到最新版。安装上软胶保护套拿⼿上不容易滑落。
0 ⼿机出⼚贴膜好薄啊,感觉像塑料膜。其他不能发表
0 ⽤了⼀段时间,除了⼿机续航其它还不错。
0 做⼯⼀般
1 挺好的,赞⼀个,⼿机很好,很喜欢
0 ⼿机还⾏,但是⼿机刚开箱时屏幕和背⾯有很多指纹痕迹,⼿机壳跟**在地上磨过似的,好⼏条印⼦。要不是看在能把这些痕迹擦掉,和闲退货⿇烦,就给退了。就不能规规矩矩做⽣意么。还有送的都是什么吊东西,运动⼿环垃圾⼀⽐,贴在⼿机后⾯的固定⼿环还**是塑料的渡了⼀层银⾊,⽿机也和图⽚描述不符,碎屏险已经注册,不知道怎么样。讲真的,要不就别送或者少送,要不,就规规矩矩的,不然到最后还让⼈觉得不舒服。其他没什么。
-1 ⼿机整体还可以,拍照也很清楚,也很流畅⽀持华为。给⼀星是因为有缺陷,送的⽿机是坏的!评论区好评太多,需要⼀些差评来提醒下,以后更加注意细节,提升质量。
0 前天刚买的,看着还⾏,指纹解锁反应不错。
1 ⾼端⼤⽓上档次。
-1 各位⼩主,注意啦,⽿机是没有的,需要单独买
0 外观不错,感觉很耗电啊,在使⽤段时间评价
1 ⼿机⾮常好,很好⽤
-1 没有发票,图⽚与实物不⼀致
1 习惯在京东采购物品,⽅便快捷,及时开票进⾏报销,配送员服务也很周到!就是⼿机收到时没有电,感觉不⼤正常
1 ⾼端⼤⽓上档次啊!看电影玩游戏估计很爽!屏幕够⼤!
数据总共8097条,按6:2:2的⽐例拆分成train.tsv,test.tsv ,dev.tsv三个数据⽂件
模型训练
训练模型就直接使⽤BERT的分类⽅法,把原来的run_classifier.py 复制出来并修改为 run_mobile.py。关于训练的代码⽹上很多,就不展开说明了,主要有以下⽅法:
#-----------------------------------------
#⼿机评论情感分类数据处理 2019/3/12
#labels: -1负⾯ 0中性 1正⾯
class SetimentProcessor(DataProcessor):
def get_train_examples(lf, data_dir):
"""See ba class."""
return lf._create_examples(
lf._read_tsv(os.path.join(data_dir,"train.tsv")),"train")
def get_dev_examples(lf, data_dir):
“”“See ba class.”""
return lf._create_examples(
lf._read_tsv(os.path.join(data_dir, “dev.tsv”)), “dev”)
def get_test_examples(lf, data_dir):
“”“See ba class.”""
return lf._create_examples(
lf._read_tsv(os.path.join(data_dir, “test.tsv”)), “test”)
def get_labels(lf):
“”“See ba class.”""
<span class="token triple-quoted-string string">"""南京理工大学录取分数线
if not ists(os.path.join(FLAGS.output_dir, 'label_list.pkl')):
with codecs.open(os.path.join(FLAGS.output_dir, 'label_list.pkl'), 'wb') as fd:
pickle.dump(lf.labels, fd)
"""</span>
<span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token string">"-1"</span><span class="tok en punctuation">,</span> <span class="token string">"0"</span><span class="token punctuation">,</span> <span class="token string">"1& #34;</span><span class="token punctuation">]</span>
def _create_examples(lf, lines, t_type):
“”“Creates examples for the training and dev ts.”""
examples = []
for (i, line) in enumerate(lines):
if i == 0:
continue
guid = “%s-%s” % (t_type, i)
<span class="token comment">#debug (by xmxoxo)</span>
<span class="token comment">#print("read line: No.%d" % i)</span>
text_a <span class="token operator">=</span> tokenization<span class="token punctuation">.</span>convert_to_unicode<span class="token punctu ation">(</span>line<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span clas s="token punctuation">)</span>
<span class="token keyword">if</span> t_type <span class="token operator">==</span> <span class="token string">"test"</span>< span class="token punctuation">:</span>
label <span class="token operator">=</span> <span class="token string">"0"</span>
<span class="token keyword">el</span><span class="token punctuation">:</span>
label <span class="token operator">=</span> tokenization<span class="token punctuation">.</span>convert_to_unicode<span class="token punctu ation">(</span>line<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span clas s="token punctuation">)</span>
examples<span class="token punctuation">.</span>append<span class="token punctuation">(</spa
n>
InputExample<span class="token punctuation">(</span>guid<span class="token operator">=</span>guid<span class="token punctuation">,</span > text_a<span class="token operator">=</span>text_a<span class="token punctuation">,</span> label<span class="token operator">=</span>lab el<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token keyword">return</span> examples
#-----------------------------------------
在他们的世界里然后添加⼀个⽅法:
processors ={
"cola": ColaProcessor,
"mnli": MnliProcessor,
"mrpc": MrpcProcessor,
"xnli": XnliProcessor,
"timent": SetimentProcessor,#2019/3/27 add by Echo
}
特别说明,这⾥有⼀点要注意,在后期部署的时候,需要⼀个label2id的字典,所以要在训练的时候就保存起来,在convert_single_example 这个⽅法⾥增加⼀段:
#--- save label2id.pkl ---
#在这⾥输出label2id.pkl , add by xmxoxo 2019/2/27
output_label2id_file = os .path .join (FLAGS .output_dir , "label2id.pkl")
if not os .path .exists (output_label2id_file ):
with open (output_label2id_file ,'wb') as w :
pickle .dump (label_map ,w )
#— Add end —
这样训练后就会⽣成这个⽂件了。
使⽤以下命令训练模型,⽬录参数请根据各⾃的情况修改:
cd /mnt/sda1/transdat/bert-demo/bert/
export BERT_BASE_DIR =/mnt/sda1/transdat/bert-demo/bert/chine_L-12_H-768_A-12
export GLUE_DIR =/mnt/sda1/transdat/bert-demo/bert/data
export TRAINED_CLASSIFIER =/mnt/sda1/transdat/bert-demo/bert/output
export EXP_NAME =mobile_0
sudo python run_mobile.py
–task_name=timent
–do_train=true
–do_eval=true
–data_dir=EXP_NAME
–vocab_file=KaTeX par error: Undefined control quence: \ at position 32: …pan>/ \ --
bert_config…BERT_BASE_DIR/bert_config.json
–init_checkpoint=KaTeX par error: Undefined control quence: \ at position 38: …ert_model.ckpt \ --max_q_len…TRAINED_CLASSIFIER/$EXP_NAME
由于数据⽐较⼩,训练是⽐较快的,训练完成后,可以在输出⽬录得到模型⽂件,这⾥的模型⽂件格式是.ckpt的。
训练结果:
eval_accuracy = 0.861643
eval_f1 = 0.9536328
eval_loss = 0.56324786
eval_precision = 0.9491279
eval_recall = 0.9581805
global_step = 759
loss = 0.5615213
【注:很多童鞋说这⾥的F1计算有问题,其实这个F1是我加上去的,具体的请看源码】
可以使⽤以下语句来进⾏预测:
sudo python run_mobile.py \
--task_name =timent \
--do_predict =true \
--data_dir =$GLUE_DIR /$EXP_NAME \
--vocab_file =$BERT_BASE_DIR / \
--bert_config_file =$BERT_BASE_DIR /bert_config.json \
-
-init_checkpoint =$TRAINED_CLASSIFIER /$EXP_NAME \
--max_q_length =128 \
--output_dir =$TRAINED_CLASSIFIER /$EXP_NAME
模型格式转化
到这⾥我们已经训练得到了模型,但这个模型是.ckpt的⽂件格式,⽂件⽐较⼤,并且有三个⽂件:
GLUE IR <D /span >/<spanclass ="tokenvariable ">
-rw-r--r-- 1 root root 1227239468 Apr 15 17:46 model.ckpt-759.data-00000-of-00001
-rw-r--r-- 1 root root 22717 Apr 15 17:46 model.ckpt-759.index
-rw-r--r-- 1 root root 3948381 Apr 15 17:46 a
可以看到,模板⽂件⾮常⼤,⼤约有1.17G。
后⾯使⽤的模型服务端,使⽤的是.pb格式的模型⽂件,所以需要把⽣成的ckpt格式模型⽂件转换成.pb格式的模型⽂件。
我这⾥提供了⼀个转换⼯具:freeze_graph.py,使⽤如下:
usage: freeze_graph.py [-h] -bert_model_dir BERT_MODEL_DIR -model_dir
MODEL_DIR [-model_pb_dir MODEL_PB_DIR]
[-max_q_len MAX_SEQ_LEN][-num_labels NUM_LABELS]
[-verbo]
女性欲这⾥要注意的参数是:
model_dir 就是训练好的.ckpt⽂件所在的⽬录
max_q_len 要与原来⼀致;
num_labels 是分类标签的个数,本例中是3个
python freeze_graph.py \
-bert_model_dir $BERT_BASE_DIR \
-
model_dir $TRAINED_CLASSIFIER/$EXP_NAME \
-max_q_len 128 \
-num_labels 3
执⾏成功后可以看到在model_dir⽬录会⽣成⼀个classification_model.pb ⽂件。
转为.pb格式的模型⽂件,同时也可以缩⼩模型⽂件的⼤⼩,可以看到转化后的模型⽂件⼤约是390M。
-rw-rw-r-- 1 hexi hexi 409326375 Apr 15 17:58 classification_model.pb
服务端部署与启动
这⾥要说明⼀下,我们经常会看到bert-as-rvice 这个项⽬的介绍,它只能加载BERT的预训练模型,输出⽂本向量化的结果。⽽如果要加载fine-turing后的模型,就要⽤到 bert-ba 了,详请请见:
下载代码并安装 :
pip install bert-ba==0.0.7 -i pypi.python/simple
或者
git /macanv/BERT-BiLSTM-CRF-NER
cd BERT-BiLSTM-CRF-NER/
python3 tup.py install
使⽤ bert-ba 有三种运⾏模式,分别⽀持三种模型,使⽤参数-mode 来指定:
NER 序列标注类型,⽐如命名实体识别;
CLASS 分类模型,就是本⽂中使⽤的模型
BERT 这个就是跟bert-as-rvice ⼀样的模式了
之所以要分成不同的运⾏模式,是因为不同模型对输⼊内容的预处理是不同的,命名实体识别NER是要进⾏序列标注;
⽽分类模型只要返回label就可以了。
安装完后运⾏服务,同时指定监听 HTTP 8091端⼝,并使⽤GPU 1来跑;
cd /mnt/sda1/transdat/bert-demo/bert/bert_svr