使用padlle hub进行BERT Fine-Tune 中文-文本分类/蕴含 下游任务

2 篇文章 0 订阅
1 篇文章 0 订阅

写在前面

想节省时间,代码在这:
Aistudio项目/代码:Bert迁移训练-文本分类/文本分类蕴含
参考:
PaddleHub实战——使用语义预训练模型ERNIE优化文新闻本分类
PS:这篇是基于paddlehub实现迁移学习/下游任务,之后有时间可能写一篇基于paddle加载预训练模型,实现下游任务

1.相关技术

PaddleHub:

PaddleHub官网
GitHubWiki地址

预训练模型:Bert_chinese_L-12_H-768_A-12

详情: 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任务为文本语义相似度匹配等)。

Bert下游任务

下游任务
Entailment:输入文本a Premise,文本b Hypothesis,标签label
通过label来判断a和b的关系
本质上也是分类的问题。
经过Bert模型+一层Linear(全连接层)输出分类数

2.使用步骤-以文本蕴含为例

环境准备

版本:
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)  

处理后的效果为:
处理后符合hub需要的数据集

自定义Hub数据集

将我们的数据集使用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

PaddleHub分类数据集读取器

#读入分类数据集
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的词语切分模型路径;

读取预训练模型Bert

通过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_decaynoam_decay两种衰减策略可选
  • warmup_proprotion: 训练预热的比例,若设置为0.1, 则会在前10%的训练step中学习率逐步提升到learning_rate
  • weight_decay: 权重衰减,类似模型正则项策略,避免模型overfitting
  • optimizer_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

组建Finetune Task

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。

  1. 获取module的上下文环境,包括输入和输出的变量,以及Paddle Program;
  2. 从输出变量中找到用于情感分类的文本特征pooled_output;
  3. 在pooled_output后面接入一个全连接层,生成Task;

TextClassifierTask的参数有:

  • data_reader:读取数据的reader;

  • config: 运行配置;

  • feature:从预训练提取的特征;

  • feed_list:program需要输入的变量;

  • num_classes:数据集的类别数量;

  • metric_choic:任务评估指标,默认为"acc"。metrics_choices支持训练过程中同时评估多个指标,作为最佳模型的判断依据,例如[“matthews”, “acc”],"matthews"将作为主指标,为最佳模型的判断依据;

Fine-Tune 训练

#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完成后,我们使用模型来进行预测,整个预测流程大致可以分为以下几步:

  1. 构建网络
  2. 生成预测数据的Reader
  3. 切换到预测的Program
  4. 加载预训练好的参数
  5. 运行Program进行预测
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步即可完成。

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值