想节省时间,代码在这:
Aistudio项目/代码:Bert迁移训练-文本分类/文本分类蕴含
参考:
PaddleHub实战——使用语义预训练模型ERNIE优化文新闻本分类
PS:这篇是基于paddlehub实现迁移学习/下游任务,之后有时间可能写一篇基于paddle加载预训练模型,实现下游任务
PaddleHub官网
GitHubWiki地址
详情: Bert_chinese_L-12_H-768_A-12
类别文本 - 语义模型
网络BERT
数据集中文维基百科语料
模型概述
BERT是一个迁移能力很强的通用语义表示模型,以Transformer为网络基本组件,以Masked Bi-Language Model和Next Sentence Prediction为训练目标,通过预训练得到通用语义表示,再结合简单的输出层,应用到下游的NLP任务,在多个任务上取得了SOTA的结果。其可用于文本分类、序列标注、阅读理解的任务。预训练数据集为中文维基百科数据。该PaddleHub Module只支持Fine-tune。当该PaddleHub Module用于Fine-tune时,其输入是单文本(如Fine-tune的任务为情感分类等)或文本对(如Fine-tune任务为文本语义相似度匹配等)。
Entailment:输入文本a Premise,文本b Hypothesis,标签label
通过label来判断a和b的关系
本质上也是分类的问题。
经过Bert模型+一层Linear(全连接层)输出分类数
版本:
paddle 1.8以上
paddlehub 1.7以上
hub安装:
pip install paddlehub==1.7 -i https://pypi.tuna.tsinghua.edu.cn/simple
数据集地址:CNLI
下载地址:CNLI dataset
中文-文本蕴含推理数据集,训练集29738,验证集3485
!unzip -o /home/aistudio/data/data47265/CNLI_Data.zip -d /home/aistudio/data/cnli
Archive: /home/aistudio/data/data47265/CNLI_Data.zip extracting:
/home/aistudio/data/cnli/cnli_dev_1.0.txt extracting:
/home/aistudio/data/cnli/cnli_test_1.0.txt extracting:
/home/aistudio/data/cnli/cnli_test_labeled.txt extracting:
/home/aistudio/data/cnli/cnli_train_1.0.txt
每行代表一条数据,以tab隔开
分别是:premise /tab hypothesis /tab label
label分3类:[ ‘contradiction’, ‘entailment’, ‘neutral’]
输入到hub的数据集必须要求:
第一行:
单句分类:
text_a /tab label
一对句子的任务:(文本蕴含/标注等)
text_a /tab text_b /tab label
接下来其他行数据,按照格式用tab分隔即可,代码如下:
import re
#处理CNLI数据集
with open('data/cnli/cnli_train_1.0.txt') as f:
lines=f.readlines()
with open('data/cnli/train.txt','w') as f:
f.write('text_a\ttext_b\tlabel\n')
for line in lines:
split_line=[re.sub('\s','',i) for i in line.split('\t')]
text=split_line[1]+'\t'+split_line[2]+'\t'+split_line[3]+'\n'
f.write(text)
with open('data/cnli/cnli_test_labeled.txt') as f:
lines=f.readlines()
with open('data/cnli/test.txt','w') as f:
f.write('text_a\ttext_b\tlabel\n')
for line in lines:
split_line=[re.sub('\s','',i) for i in line.split('\t')]
text=split_line[1]+'\t'+split_line[2]+'\t'+split_line[3]+'\n'
f.write(text)
with open('data/cnli/cnli_dev_1.0.txt') as f:
lines=f.readlines()
with open('data/cnli/valid.txt','w') as f:
f.write('text_a\ttext_b\tlabel\n')
for line in lines:
split_line=[re.sub('\s','',i) for i in line.split('\t')]
text=split_line[1]+'\t'+split_line[2]+'\t'+split_line[3]+'\n'
f.write(text)
将我们的数据集使用paddlehub来加载
paddlehub自定义数据集方法
from paddlehub.dataset.base_nlp_dataset import BaseNLPDataset
#自定义数据集,一定要注意去掉空格和\t
#文本蕴含 CNLI数据集
#数据格式 text+\t+label
class CNLI(BaseNLPDataset):
def __init__(self):
# 数据集存放位置
self.dataset_dir = "data/cnli"
super(CNLI, self).__init__(
base_path=self.dataset_dir,
train_file="train.txt",
dev_file="valid.txt",
test_file="test.txt",
train_file_with_header=True,
dev_file_with_header=True,
test_file_with_header=True,
# 数据集类别集合
label_list=[ 'contradiction', 'entailment', 'neutral']
)
dataset = CNLI()
for e in dataset.get_train_examples()[:3]:
print("{}\t{}\t{}".format(e.guid, e.text_a, e.text_b,e.label))
输出前三行:
运行耗时: 444毫秒
1 穿红衬衫的男人和拿着白色袋子的女人正在交谈。 两个人在交谈 entailment
2 但同样,我有一些你不知道的事情。 但同样,我有一个你不知道的事。 neutral
3 一个女人拿着纸巾清洁婴儿的脸。 一名女子清洁她的孩子。 neutral
#读入分类数据集
reader = hub.reader.ClassifyReader(
dataset=dataset,
vocab_path=module.get_vocab_path(),
sp_model_path=module.get_spm_path(),
word_dict_path=module.get_word_dict_path(),
max_seq_len=128)
各类reader读取器和参数说明
接着生成一个文本分类的reader,reader负责将dataset的数据进行预处理,首先对文本进行切词,接着以特定格式组织并输入给模型进行训练。
ClassifyReader
的参数有以下三个:
dataset
: 传入PaddleHub Dataset;vocab_path
: 传入ERNIE/BERT模型对应的词表文件路径;max_seq_len
: ERNIE模型的最大序列长度,若序列长度不足,会通过padding方式补到max_seq_len, 若序列长度大于该值,则会以截断方式让序列长度为max_seq_len;sp_model_path
: 传入 ERNIE tiny的subword切分模型路径;word_dict_path
: 传入 ERNIE tiny的词语切分模型路径;通过hub搜索bert模型
!hub search bert
+--------------------------------+----------+----------+---------------------------+
| ResourceName | Type | Version | Summary |
+--------------------------------+----------+----------+---------------------------+
|bert_wwm_ext_chinese_L-12_H-76 | Module | 1.1.0 |bert_wwm_ext_chinese_L-12 |
|8_A-12 | | |_H-768_A-12. 12-layer, 76 |
| | | |8-hidden, 12-heads, 110M |
| | | |parameters |
+--------------------------------+----------+----------+---------------------------+
|bert_wwm_chinese_L-12_H-768_A- | Module | 1.1.0 |bert_wwm_chinese_L-12_H-7 |
|12 | | |68_A-12. 12-layer, 768-hi |
| | | |dden, 12-heads, 110M para |
| | | |meters |
+--------------------------------+----------+----------+---------------------------+
| bert_uncased_L-24_H-1024_A-16 | Module | 1.1.0 |bert_uncased_L-24_H-1024_ |
| | | |A-16, 24 - layer, 1024 - |
| | | |hidden, 16 - heads, 340 M |
| | | |parameters |
+--------------------------------+----------+----------+---------------------------+
| bert_uncased_L-12_H-768_A-12 | Module | 1.1.0 |bert_uncased_L-12_H-768_A |
| | | |-12.12-layer, 768 - hidde |
| | | |n, 12 - heads, 110 Mparam |
| | | |eters |
+--------------------------------+----------+----------+---------------------------+
|bert_multi_uncased_L-12_H-768_ | Module | 1.0.0 |bert_multi_uncased_L-12_H |
|A-12 | | |-768_A-12, 12 - layer, 76 |
| | | |8 - hidden, 12 - heads, 1 |
| | | |10 Mparameters |
+--------------------------------+----------+----------+---------------------------+
|bert_multi_cased_L-12_H-768_A- | Module | 1.0.0 |bert_multi_cased_L-12_H-7 |
|12 | | |68_A-12, 12 - layer, 768 |
| | | |- hidden, 12 - heads, 110 |
| | | | Mparameters |
+--------------------------------+----------+----------+---------------------------+
| bert_chinese_L-12_H-768_A-12 | Module | 1.1.0 |bert_chinese_L-12_H-768_A |
| | | |-12, 12 - layer, 768 - hi |
| | | |dden, 12 - heads, 110 Mpa |
| | | |rameters |
+--------------------------------+----------+----------+---------------------------+
| bert_cased_L-24_H-1024_A-16 | Module | 1.1.0 |bert_cased_L-24_H-1024_A- |
| | | |16, 24 - layer, 1024 - hi |
| | | |dden, 16 - heads, 340 Mpa |
| | | |rameters |
+--------------------------------+----------+----------+---------------------------+
| bert_cased_L-12_H-768_A-12 | Module | 1.1.0 |bert_cased_L-12_H-768_A-1 |
| | | |2.12-layer, 768 - hidden, |
| | | | 12 - heads, 110 Mparamet |
| | | |ers |
+--------------------------------+----------+----------+---------------------------+
读取模型
import paddlehub as hub
module=hub.Module('bert_chinese_L-12_H-768_A-12')
strategy = hub.AdamWeightDecayStrategy(
weight_decay=0.001,
warmup_proportion=0.1,
learning_rate=5e-5,
)
适用于BERT这类Transformer模型的迁移优化策略为AdamWeightDecayStrategy
。详情请查看Strategy。
AdamWeightDecayStrategy
的参数:
learning_rate
: 最大学习率lr_scheduler
: 有linear_decay
和noam_decay
两种衰减策略可选warmup_proprotion
: 训练预热的比例,若设置为0.1, 则会在前10%的训练step中学习率逐步提升到learning_rate
weight_decay
: 权重衰减,类似模型正则项策略,避免模型overfittingoptimizer_name
: 优化器名称config = hub.RunConfig(
use_cuda=True,
num_epoch=2,
batch_size=32,
checkpoint_dir="hub_finetune_ckpt",
strategy=strategy)
在进行Finetune前,我们可以设置一些运行时的配置,例如如下代码中的配置,表示:
use_cuda
:设置为False表示使用CPU进行训练。如果您本机支持GPU,且安装的是GPU版本的PaddlePaddle,我们建议您将这个选项设置为True;
num_epoch
:Finetune时遍历训练集的次数,;
batch_size
:每次训练的时候,给模型输入的每批数据大小为16,模型训练时能够并行处理批数据,因此batch_size越大,训练的效率越高,但是同时带来了内存的负荷,过大的batch_size可能导致内存不足而无法训练,因此选择一个合适的batch_size是很重要的一步;
log_interval
:每隔10 step打印一次训练日志;
eval_interval
:每隔50 step在验证集上进行一次性能评估;
checkpoint_dir
:训练的参数和数据的保存目录;
strategy
:Fine-tune策略;
use_data_parallel
: 设置为False表示单卡训练;设置为True表示多卡训练
use_pyreader
: 设置为False表示不使用py_reader读取数据;设置为True表示使用py_reader读取数据
更多运行配置,请查看RunConfig
inputs, outputs, program = module.context(
trainable=True, max_seq_len=128)
#配置fine-tune任务
# Use "pooled_output" for classification tasks on an entire sentence.
pooled_output = outputs["pooled_output"]
feed_list = [
inputs["input_ids"].name,
inputs["position_ids"].name,
inputs["segment_ids"].name,
inputs["input_mask"].name,
]
cls_task = hub.TextClassifierTask(
data_reader=reader,
feature=pooled_output,
feed_list=feed_list,
num_classes=dataset.num_labels,
config=config,
metrics_choices=["acc"])
有了合适的预训练模型和准备要迁移的数据集后,我们开始组建一个Task。
TextClassifierTask
的参数有:
data_reader
:读取数据的reader;
config
: 运行配置;
feature
:从预训练提取的特征;
feed_list
:program需要输入的变量;
num_classes
:数据集的类别数量;
metric_choic
:任务评估指标,默认为"acc"。metrics_choices支持训练过程中同时评估多个指标,作为最佳模型的判断依据,例如[“matthews”, “acc”],"matthews"将作为主指标,为最佳模型的判断依据;
#fine-tune 训练
run_states = cls_task.finetune_and_eval()
。。。。。。。。。
[2020-07-30 22:25:57,250] [ TRAIN] - step 5570 / 5625: loss=0.49033 acc=0.83125 [step/sec: 4.01]
[2020-07-30 22:25:59,748] [ TRAIN] - step 5580 / 5625: loss=0.40370 acc=0.84375 [step/sec: 4.01]
[2020-07-30 22:26:02,247] [ TRAIN] - step 5590 / 5625: loss=0.44973 acc=0.83125 [step/sec: 4.01]
[2020-07-30 22:26:04,746] [ TRAIN] - step 5600 / 5625: loss=0.46904 acc=0.82812 [step/sec: 4.00]
[2020-07-30 22:26:04,748] [ INFO] - Evaluation on dev dataset start
[2020-07-30 22:26:27,898] [ EVAL] - [dev dataset evaluation result] loss=0.55081 acc=0.79573 [step/sec: 13.53]
[2020-07-30 22:26:30,389] [ TRAIN] - step 5610 / 5625: loss=0.48689 acc=0.81563 [step/sec: 4.02]
[2020-07-30 22:26:32,891] [ TRAIN] - step 5620 / 5625: loss=0.57316 acc=0.78125 [step/sec: 4.00]
[2020-07-30 22:26:34,277] [ INFO] - Evaluation on dev dataset start
[2020-07-30 22:26:57,396] [ EVAL] - [dev dataset evaluation result] loss=0.55080 acc=0.79573 [step/sec: 13.55]
[2020-07-30 22:26:57,398] [ INFO] - Load the best model from hub_finetune_ckpt/best_model
[2020-07-30 22:26:58,001] [ INFO] - Evaluation on test dataset start
[2020-07-30 22:27:21,120] [ EVAL] - [test dataset evaluation result] loss=0.57173 acc=0.78554 [step/sec: 13.55]
[2020-07-30 22:27:21,122] [ INFO] - Saving model checkpoint to hub_finetune_ckpt/step_5626
[2020-07-30 22:27:23,621] [ INFO] - PaddleHub finetune finished.
可以看到准确率:验证集0.795,测试集0.785
当Finetune完成后,我们使用模型来进行预测,整个预测流程大致可以分为以下几步:
import numpy as np
#预测
inv_label_map = {val: key for key, val in reader.label_map.items()}
print(inv_label_map)
# Data to be prdicted
data = list()
test_data_set = list()
#取出三条测试数据
for d in dataset.get_test_examples()[:3]:
data.append([d.text_a,d.text_b])
test_data_set.append((d.text_a,d.text_b,d.label))
#输入训练好的模型进行预测
index = 0
run_states = cls_task.predict(data=data)
results = [run_state.run_results for run_state in run_states]
#输出预测结果
for batch_result in results[0][0]:
# get predict index
print(batch_result)
batch_result = np.argmax(batch_result)
print("%s\tpredict=%s,true=%s" % (test_data_set[index][0:2], inv_label_map[batch_result],test_data_set[index][2]))
index += 1
[2020-07-30 22:27:23,630] [ INFO] - PaddleHub predict start
[2020-07-30 22:27:23,631] [ INFO] - The best model has been loaded
{0: 'contradiction', 1: 'entailment', 2: 'neutral'}
[2020-07-30 22:27:26,898] [ INFO] - PaddleHub predict finished.
[0.9011162 0.00726804 0.09161581]
('一名在隧道内的脚手架上工作的男子。', '他在珠峰山顶上。') predict=contradiction,true=contradiction
[0.01696024 0.01218794 0.9708518 ]
('一位老太太在她的厨房里,穿着围裙,微笑着向往一个锅里舀汤。', '有一位老太太,正在为她的孙子做爱蔬菜汤') predict=neutral,true=entailment
[0.01636307 0.01313861 0.9704983 ]
('一群女性跑马拉松。', '他们中的一个将赢得比赛') predict=neutral,true=neutral
总的来说,PaddleHub完成迁移学习过程只需下图所展示的6步即可完成。