阿里云天池人工智能大赛:PAI-DSW中使用EasyTransfer快速搭建比赛Baseline

这是一篇介绍如何在PAI-DSW里用EasyTransfer平台训练多任务的教程。只需要一份配置文件,一份ipynb文件,您就可以完成对原始数据的特征提取,网络构建,损失函数及多任务评估/预测的简单调用。

  • 背景介绍

  • 赛题

  • 数据说明
  • 数据格式
  • 基于EasyTransfer快速搭建比赛Baseline

  • 数据处理
  • 定义配置文件
  • 定义多任务数据读取器
  • 定义多任务建模应用
  • 启动训练
  • 启动评估
  • 相关链接

背景介绍

中文预训练模型的质量会影响以AI技术驱动的企业中核心业务算法的性能。比如智能客服问答,搜索推荐,安全风控,舆情分析,文字识别,信息抽取,智能打标等等,这些业务或产品都会或多或少依赖于预训练模型提供的通用语言学知识来做迁移学习。因此打造高质量的预训练模型是当前产业界和学术界的热点。

自从2017年具有划时代意义的Transformer模型问世以来,短短两年多的时间内,如雨后春笋般的出现了大量的预训练模型,比如:Bert,Albert,ELECTRA,RoBERta,T5,GPT3等等。然而当前的基准评测体系存在两个缺陷:评测强度不够,模型不通用。评测强度不够指的是选手只提交结果,不提交inference的代码。模型不通用指的是预训练模型不能保证在相同超参数情况下在所有任务上都获得比较好的性能。以上两点极大限制了预训练技术的应用和发展。

如果我们能通过算法实现泛化能力强的中文预训练模型,这将提高下游业务的准确性,从而提升企业的核心竞争力,并为企业创造更多的价值。

阿里云计算平台PAI团队联合CLUE中文语言理解评测组织和上海乐言信息科技有限公司,共同推出中文预训练模型泛化能力挑战赛,邀请业内算法高手、爱好者一起促进自然语言处理预训练技术的发展。

赛题

本次比赛是CLUE与阿里云平台、乐言科技联合发起的第一场针对中文预训练模型泛化能力的挑战赛。

赛题以自然语言处理为背景,要求选手通过算法实现泛化能力强的中文预训练模型。通过这道赛题可以引导大家更好地理解预训练模型的运作机制,探索深层次的模型构建和模型训练,而不仅仅是针对特定任务进行简单微调。

数据说明

本赛题精选了以下3个具有代表性的任务,要求选手提交的模型能够同时预测每个任务对应的标签:

  1. OCNLI,是第一个非翻译的、使用原生汉语的大型中文自然语言推理数据集;
  2. OCEMOTION,是包含7个分类的细粒度情感性分析数据集;
  3. TNEWS,来源于今日头条的新闻版块,共包含15个类别的新闻;

数据格式

任务1:  OCNLI--中文原版自然语言推理

0    一月份跟二月份肯定有一个月份有.    肯定有一个月份有    0
1    一月份跟二月份肯定有一个月份有.    一月份有    1
2    一月份跟二月份肯定有一个月份有.    一月二月都没有    2
3    一点来钟时,张永红却来了    一点多钟,张永红来了    0
4    不讲社会效果,信口开河,对任何事情都随意发议论,甚至信谣传谣,以讹传讹,那是会涣散队伍、贻误事业的    以讹传讹是有害的    0
(注:id  句子1  句子2  标签)

任务2:  OCEMOTION--中文情感分类    

0    你知道多伦多附近有什么吗?哈哈有破布耶...真的书上写的你听哦...你家那块破布是世界上最大的破布,哈哈,骗你的啦它是说尼加拉瓜瀑布是世界上最大的瀑布啦...哈哈哈''爸爸,她的头发耶!我们大扫除椅子都要翻上来我看到木头缝里有头发...一定是xx以前夹到的,你说是不是?[生病]    sadness
1    平安夜,圣诞节,都过了,我很难过,和妈妈吵了两天,以死相逼才终止战争,现在还处于冷战中。    sadness
2    我只是自私了一点,做自己想做的事情!    sadness
3    让感动的不仅仅是雨过天晴,还有泪水流下来的迷人眼神。    happiness
4    好日子    happiness
(注:id  句子  标签)

任务3:TNEWS--今日头条新闻标题分类

0    上课时学生手机响个不停,老师一怒之下把手机摔了,家长拿发票让老师赔,大家怎么看待这种事?    108
1    商赢环球股份有限公司关于延期回复上海证券交易所对公司2017年年度报告的事后审核问询函的公告    104
2    通过中介公司买了二手房,首付都付了,现在卖家不想卖了。怎么处理?    106
3    2018年去俄罗斯看世界杯得花多少钱?    112
4    剃须刀的个性革新,雷明登天猫定制版新品首发    109
(注:id  句子  标签)

基于EasyTransfer快速搭建比赛Baseline

数据处理

1. 配置文件示例

以OCNLI--中文原版自然语言推理任务为例:

{
  "preprocess_config": {
    "preprocess_input_fp": "tianchi_datasets/OCNLI/train.csv",
    "preprocess_output_fp": "tianchi_datasets/OCNLI/train.tfrecord",
    "preprocess_batch_size": 16,
    "input_schema": "idx:str:1,sentence1:str:1,sentence2:str:1,label:str:1",
    "tokenizer_name_or_path": "google-bert-base-zh",
    "first_sequence": "sentence1",
    "second_sequence": "sentence2",
    "label_name":"label",
    "label_enumerate_values": "0,1,2",
    "sequence_length": 128,
    "output_schema": "input_ids,input_mask,segment_ids,label_id"
  }
}

2. 数据转换成tfrecord

然后运行下面脚本,即可将数据转换成tfrecord格式,每个样本将包含input_ids/input_mask/segment_ids/label_id这四个特征,并会将生成的数据链接到train.list_tfrecord上。

sh run_convert_csv_to_tfrecords.sh

定义配置文件

config_json = {
        "worker_hosts": "localhost",
        "task_index": 1,
        "job_name": "chief",
        "num_gpus": 1,
        "num_workers": 1,
        "preprocess_config": {
            "input_schema": "input_ids:int:128,input_mask:int:128,segment_ids:int:128,label_id:int:1",
            "sequence_length": 128
        },
    
        "model_config": {
            "pretrain_model_name_or_path": "pai-bert-tiny-zh",
        },
        
        "train_config": {
            "train_input_fp": "./data/train.list_tfrecord",
            "train_batch_size": 2,
            "num_epochs": 0.01,
            "model_dir": "model_dir",
            "optimizer_config": {
                "learning_rate": 1e-5
            }
        },
        
        "predict_config": {
            "predict_checkpoint_path": None,
            "predict_input_fp": "./data/dev.list_tfrecord",
            "predict_batch_size": 2
        }
    }

定义多任务数据读取器

import sys
import os
import tensorflow as tf
from easytransfer import base_model, Config, FLAGS
from easytransfer import layers
from easytransfer import model_zoo
from easytransfer import preprocessors
from easytransfer.datasets import TFRecordReader
from easytransfer.losses import softmax_cross_entropy
from sklearn.metrics import classification_report
import numpy as np


class MultiTaskTFRecordReader(TFRecordReader):
    def __init__(self, input_glob, batch_size, is_training=False,**kwargs):
        super(MultiTaskTFRecordReader, self).__init__(input_glob, batch_size, is_training, **kwargs)
        self.task_fps = []
        with tf.gfile.Open(input_glob, 'r') as f:
            for line in f:
                line = line.strip()
                self.task_fps.append(line)

    def get_input_fn(self):
        def input_fn():
            num_datasets = len(self.task_fps)
            datasets = []
            for input_glob in self.task_fps:
                dataset = tf.data.TFRecordDataset(input_glob)
                dataset = self._get_data_pipeline(dataset, self._decode_tfrecord)
                datasets.append(dataset)
            choice_dataset = tf.data.Dataset.range(num_datasets).repeat()
            return tf.data.experimental.choose_from_datasets(datasets, choice_dataset)
        return input_fn

定义多任务建模应用

- base_model: 所有应用都需要继承的父类
- Config:用来解析配置文件的父类
- layers:基础组件。比如Embedding,Attention等
- model_zoo: 管理预训练模型的组件库,通过get_pretrained_model方法可调用bert模型
- preprocessors:管理各种应用的预处理逻辑
- softmax_cross_entropy:用于分类任务的损失函数

完整的训练/评估/预测/链路,由四个函数构成:

- build_logits: 构图
- build_loss:定义损失函数
- build_eval_metrics:定义评估指标
- build_predictions:定义预测输出

class Application(base_model):
    def __init__(self, **kwargs):
        super(Application, self).__init__(**kwargs)
        self.user_defined_config = kwargs["user_defined_config"]

    def build_logits(self, features, mode=None):
        preprocessor = preprocessors.get_preprocessor(self.pretrain_model_name_or_path,
                                   user_defined_config=self.user_defined_config)
            model = model_zoo.get_pretrained_model(self.pretrain_model_name_or_path)
            global_step = tf.train.get_or_create_global_step()
        tnews_dense = layers.Dense(15,
                     kernel_initializer=layers.get_initializer(0.02),
                     name='tnews_dense')

        ocemotion_dense = layers.Dense(7,
                             kernel_initializer=layers.get_initializer(0.02),
                             name='ocemotion_dense')

        ocnli_dense = layers.Dense(3,
                             kernel_initializer=layers.get_initializer(0.02),
                             name='ocnli_dense')

        input_ids, input_mask, segment_ids, label_ids = preprocessor(features)
        outputs = model([input_ids, input_mask, segment_ids], mode=mode)
        pooled_output = outputs[1]

        if mode == tf.estimator.ModeKeys.TRAIN:
            pooled_output = tf.nn.dropout(pooled_output, keep_prob=0.9)

        logits = tf.case([(tf.equal(tf.mod(global_step, 3), 0), 
                                   lambda: tnews_dense(pooled_output)),
                    (tf.equal(tf.mod(global_step, 3), 1), 
                                   lambda: ocemotion_dense(pooled_output)),
                    (tf.equal(tf.mod(global_step, 3), 2), 
                                   lambda: ocnli_dense(pooled_output)),], exclusive=True)

        if mode == tf.estimator.ModeKeys.PREDICT:
            ret = {
                "tnews_logits": tnews_dense(pooled_output),
                "ocemotion_logits": ocemotion_dense(pooled_output),
                "ocnli_logits": ocnli_dense(pooled_output),
                "label_ids": label_ids
            }
            return ret

        return logits, label_ids

    def build_loss(self, logits, labels):
        global_step = tf.train.get_or_create_global_step()
        return tf.case([(tf.equal(tf.mod(global_step, 3), 0), 
                                  lambda : softmax_cross_entropy(labels, 15, logits)),
                   (tf.equal(tf.mod(global_step, 3), 1), 
                                  lambda : softmax_cross_entropy(labels, 7, logits)),
                   (tf.equal(tf.mod(global_step, 3), 2), 
                                  lambda : softmax_cross_entropy(labels, 3, logits))],
                            exclusive=True)


    def build_predictions(self, output):
        tnews_logits = output['tnews_logits']
        ocemotion_logits = output['ocemotion_logits']
        ocnli_logits = output['ocnli_logits']
        tnews_predictions = tf.argmax(tnews_logits, axis=-1, output_type=tf.int32)
        ocemotion_predictions = tf.argmax(ocemotion_logits, 
                                              axis=-1, 
                                              output_type=tf.int32)
        ocnli_predictions = tf.argmax(ocnli_logits, axis=-1, output_type=tf.int32)

        ret_dict = {
            "tnews_predictions": tnews_predictions,
            "ocemotion_predictions": ocemotion_predictions,
            "ocnli_predictions": ocnli_predictions,
            "label_ids": output['label_ids']
        }
        return ret_dict

启动训练

config = Config(mode="train", config_json=config_json)
app = Application(user_defined_config=config)

train_reader = MultiTaskTFRecordReader(input_glob=app.train_input_fp,
                                           is_training=True,
                                           input_schema=app.input_schema,
                                           batch_size=app.train_batch_size)

app.run_train(reader=train_reader)

启动评估

config = Config(mode="predict", config_json=config_json)
app = Application(user_defined_config=config)
    
predict_reader = MultiTaskTFRecordReader(input_glob=app.predict_input_fp,
                             is_training=False,                                                      input_schema=app.input_schema,
                             batch_size=app.predict_batch_size)

ckpts = set()
with tf.gfile.GFile(os.path.join(app.config.model_dir, "checkpoint"), mode='r') as reader:
    for line in reader:
        line = line.strip()
        line = line.replace("oss://", "")
        ckpts.add(int(line.split(":")[1].strip().replace("\"", "").split("/")[-1].replace("model.ckpt-", "")))

best_macro_f1 = 0
best_ckpt = None
for ckpt in sorted(ckpts):
    checkpoint_path = os.path.join(app.config.model_dir, "model.ckpt-" + str(ckpt))
    tf.logging.info("checkpoint_path is {}".format(checkpoint_path))
    all_tnews_preds = []
    all_tnews_gts = []
    all_ocemotion_preds = []
    all_ocemotion_gts = []
    all_ocnli_preds = []
    all_ocnli_gts = []
    for i, output in enumerate(app.run_predict(reader=predict_reader, checkpoint_path=checkpoint_path)):
        label_ids = np.squeeze(output['label_ids'])
        if i%3 ==0:
            tnews_predictions = output['tnews_predictions']
            all_tnews_preds.extend(tnews_predictions.tolist())
            all_tnews_gts.extend(label_ids.tolist())
        elif i%3==1:
            ocemotion_predictions = output['ocemotion_predictions']
            all_ocemotion_preds.extend(ocemotion_predictions.tolist())
            all_ocemotion_gts.extend(label_ids.tolist())
        elif i%3==2:
            ocnli_predictions = output['ocnli_predictions']
            all_ocnli_preds.extend(ocnli_predictions.tolist())
            all_ocnli_gts.extend(label_ids.tolist())

        if i == 20:
            break

    tnews_report = classification_report(all_tnews_gts, all_tnews_preds, digits=4)
    tnews_macro_avg_f1 = float(tnews_report.split()[-8])

    ocemotion_report = classification_report(all_ocemotion_gts, all_ocemotion_preds, digits=4)
    ocemotion_macro_avg_f1 = float(ocemotion_report.split()[-8])

    ocnli_report = classification_report(all_ocnli_gts, all_ocnli_preds, digits=4)
    ocnli_macro_avg_f1 = float(ocnli_report.split()[-8])

    macro_f1 = (tnews_macro_avg_f1 + ocemotion_macro_avg_f1 + ocnli_macro_avg_f1)/3.0
    if macro_f1 >= best_macro_f1:
        best_macro_f1 = macro_f1
        best_ckpt = ckpt

tf.logging.info("best ckpt {}, best best_macro_f1 {}".format(best_ckpt, best_macro_f1))

相关链接

  1. PAI-DSW大数据开发:https://help.aliyun.com/document_detail/140852.html?spm=5176.10695662.1996646101.searchclickresult.63e44edf4tmWR7
  2. PAI-DSW V1入门版:https://help.aliyun.com/document_detail/95535.html?spm=5176.10695662.1996646101.searchclickresult.63e44edf4tmWR7
  3. PAI-DSW V2专业版概述:https://help.aliyun.com/document_detail/163336.html?spm=5176.10695662.1996646101.searchclickresult.63e44edf4tmWR7
  4. GitHub天池大赛专区:https://github.com/alibaba/EasyTransfer/tree/master/scripts/%E5%A4%A9%E6%B1%A0%E5%A4%A7%E8%B5%9B%E4%B8%93%E5%8C%BA
  5. GitHub-EasyTransfer专区:https://github.com/alibaba/EasyTransfer
  6. 天池大赛答疑群:https://intranetproxy.alipay.com/skylark/lark/0/2020/png/226643/1605110719109-f71b8e4a-ddd4-42ba-8a72-18d3748c9966.png#align=left&display=inline&height=731&margin=%5Bobject%20Object%5D&originHeight=990&originWidth=750&size=0&status=done&style=none&width=554
  7. CLUE官网:www.CLUEbenchmarks.com
NLP中文预训练模型泛化能力挑战赛
NLP中文预训练模型泛化能力挑战赛

多任务课题探索通用的中文预训练模型,考察模型泛化能力。

入门阿里云天池人工智能大赛
暂无评论
暂无评论~