作者:徐美兰-lan,华南理工大学

摘要:这是从0实践竞赛第3篇,数据竞赛对理论实践和增加履历有益,为了让初学者也能入门,本文以全球人工智能技术大赛预热赛-中文预训练模型赛事为背景,梳理了nlp赛事的完整实践流程。

今天以天池中文预训练模型泛化能力赛事为背景,介绍一种适用入门nlp的Baseline:如何使用bert简单、快速地完成多任务多目标分类。具体目录如下:

0. 赛事背景

大赛名称:全球人工智能技术创新大赛【热身赛二】- 中文预训练模型泛化能力赛事

大赛地址
https://tianchi.aliyun.com/s/3bd272d942f97725286a8e44f40f3f74(或文末阅读原文)

大赛类型:自然语言处理、预训练模型

1. 赛题分析

1.本次赛题为数据挖掘类型,通过预训练模型调优进行分类。

2.是一个典型的多任务多分类问题。

3.主要应用keras_bert,以及pandas、numpy、matplotlib、seabon、sklearn、keras等数据挖掘常用库或者框架来进行数据挖掘任务。

4.赛题禁止人工标注;微调阶段不得使用外部数据;三个任务只能共用一个bert;只能单折训练。

2. 数据概况

赛题精选了3个具有代表性的任务,要求选手提交的模型能够同时预测每个任务对应的标签。数据下载地址:https://tianchi.aliyun.com/s/3bd272d942f97725286a8e44f40f3f74

数据格式:

任务1:OCNLI–中文原版自然语言推理,包含3个类别

ocnli_train.head(3)
'''
id  content1    content2    label
0   0   一月份跟二月份肯定有一个月份有.    肯定有一个月份有   0
1   1   一月份跟二月份肯定有一个月份有.    一月份有   1
2   2   一月份跟二月份肯定有一个月份有.    一月二月都没有   2
'''
len(ocnli_train['label'].unique())
#3

任务2:OCEMOTION–中文情感分类,包含7个类别

ocemo_train.head(3)
'''
id  content label
0   0   '你知道多伦多附近有什么吗?哈哈有破布耶...真的书上写的你听哦...你家那块破布是世界上最...  sadness
1   1   平安夜,圣诞节,都过了,我很难过,和妈妈吵了两天,以死相逼才终止战争,现在还处于冷战中。    sadness
2   2   我只是自私了一点,做自己想做的事情!  sadness
'''
len(ocemo_train['label'].unique())
#7

任务3:TNEWS–今日头条新闻标题分类,包含15个类别

times_train.head(3)
'''
id  content label
0   0   上课时学生手机响个不停,老师一怒之下把手机摔了,家长拿发票让老师赔,大家怎么看待这种事?    108
1   1   商赢环球股份有限公司关于延期回复上海证券交易所对公司2017年年度报告的事后审核问询函的公告  104
2   2   通过中介公司买了二手房,首付都付了,现在卖家不想卖了。怎么处理?    106
'''
len(times_train['label'].unique())
#15

3. 代码实践

Step 1:环境准备

导入相关包

import pandas as pd
import codecs, gc
import numpy as np
from sklearn.model_selection import KFold
from keras_bert import load_trained_model_from_checkpoint, Tokenizer
from keras.metrics import top_k_categorical_accuracy
from keras.layers import *
from keras.callbacks import *
from keras.models import Model
import keras.backend as K
from keras.optimizers import Adam
from keras.utils import to_categorical
from sklearn.preprocessing import LabelEncoder

如果在google colab上运行代码,需要先将数据上传至driver上。执行以下代码挂在driver并配置相关环境。

from google.colab import drivedrive.mount('/content/drive')'''
路径说明:
../code #保存代码
../data #保存数据
../subs #保存数据
../chinese_roberta_wwm_large_ext_L-24_H-1024_A-16 #bert路径
'''pip install keras-bert

Step 2:数据读取

path = "/content/drive/My Drive/天池nlp预训练/"#将ocnli中content1[0:maxlentext1]+content2作为ocnli任务的content
times_train = pd.read_csv(path + '/data/TNEWS_train1128.csv',  sep='\t', header=None, names=('id', 'content', 'label')).astype(str)
ocemo_train = pd.read_csv(path + '/data/OCEMOTION_train1128.csv',sep='\t', header=None, names=('id', 'content', 'label')).astype(str)
ocnli_train = pd.read_csv(path + '/data/OCNLI_train1128.csv',  sep='\t', header=None, names=('id', 'content1', 'content2', 'label')).astype(str)
ocnli_train['content'] = ocnli_train['content1'] + ocnli_train['content2']#.apply( lambda x: x[:maxlentext1] )times_testa = pd.read_csv(path + '/data/TNEWS_a.csv',  sep='\t', header=None, names=('id', 'content')).astype(str)
ocemo_testa = pd.read_csv(path + '/data/OCEMOTION_a.csv',sep='\t', header=None, names=('id', 'content')).astype(str)
ocnli_testa = pd.read_csv(path + '/data/OCNLI_a.csv',  sep='\t', header=None, names=('id', 'content1', 'content2')).astype(str)
ocnli_testa['content'] = ocnli_testa['content1']+ ocnli_testa['content2']#.apply( lambda x: x[:maxlentext1] )

1) 数据集合并

分别将三个任务的content、label列按行concat在一起作为训练集和标签、测试集,以此简单地将三任务转化为单任务。

#合并三个任务的训练、测试数据
train_df = pd.concat([times_train, ocemo_train, ocnli_train[['id','content', 'label']]], axis=0).copy()testa_df = pd.concat([times_testa, ocemo_testa, ocnli_testa[['id', 'content']]], axis=0).copy()

2)标签编码

#LabelEncoder处理标签,因为bert输入的label需要从0开始
encode_label = LabelEncoder()
train_df['label'] = encode_label.fit_transform(train_df['label'].apply(str))

3) 数据信息查看

train_df.info()
'''
<class 'pandas.core.frame.DataFrame'>
Int64Index: 147453 entries, 0 to 48777
Data columns (total 3 columns):#   Column   Non-Null Count   Dtype
---  ------   --------------   ----- 0   id       147453 non-null  object1   content  147453 non-null  object2   label    147453 non-null  int64
dtypes: int64(1), object(2)
memory usage: 4.5+ MB
'''

数据为id、content、label三列,无子句为空的行。

Step 3: 数据分析(EDA)

1) 子句长度统计分析

统计子句长度主要用于设置输入bert的序列长度。

times_train['content'].str.len().describe(percentiles=[.95, .98, .99])\
,ocemo_train['content'].str.len().describe(percentiles=[.95, .98, .99])\
,ocnli_train['content1'].str.len().describe(percentiles=[.95, .98, .99])\
,ocnli_train['content2'].str.len().describe(percentiles=[.95, .98, .99])
'''
(count    63360.000000mean        22.171086std          7.334206min          2.00000050%         22.00000095%         33.00000098%         37.00000099%         39.000000max        145.000000Name: content, dtype: float64, count    35315.000000mean        48.214328std         84.391942min          3.00000050%         34.00000095%        134.00000098%        138.00000099%        142.000000max      12326.000000Name: content, dtype: float64, count    48778.000000mean        24.174607std         11.515428min          8.00000050%         22.00000095%         46.00000098%         49.00000099%         50.000000max         50.000000Name: content1, dtype: float64, count     48778.000000mean         15.828529std         977.396848min           2.00000050%          10.00000095%          21.00000098%          24.00000099%          27.000000max      215874.000000Name: content2, dtype: float64)
'''

从上可以看出,当设置bert序列长度为142时即可覆盖约99%子句的全部内容。

2)统计标签的基本分布信息

train_df['label'].value_counts() / train_df.shape[0]
'''
1     0.113467
0     0.109940
17    0.107397
23    0.084603
21    0.060318
10    0.047771
6     0.041749
4     0.039918
13    0.039036
8     0.033292
3     0.032668
5     0.032268
11    0.029487
19    0.029481
9     0.027690
18    0.027588
12    0.027541
16    0.027460
22    0.027412
15    0.022923
7     0.016853
2     0.008993
24    0.006097
20    0.004001
14    0.002048
Name: label, dtype: float64
'''

由上可以看出,标签占比差距非常大。在拆分训练集与验证集时如果简单地采用随机拆分,可能会导致验证集不存在部分标签的情况。

Step 4: 预训练模型选择

1)模型选择

在众多nlp预训练模型中,本文baseline选择了哈工大与讯飞联合发布的基于全词遮罩(Whole Word Masking)技术的中文预训练模型:RoBERTa-wwm-ext-large。点击以下链接了解更多详细信息:

  • 论文地址:https://arxiv.org/abs/1906.08101

  • 开源模型地址:https://github.com/ymcui/Chinese-BERT-wwm

  • 哈工大讯飞联合实验室的项目介绍:https://mp.weixin.qq.com/s/EE6dEhvpKxqnVW_bBAKrnA

2)调优参数配置

为方便调优,在同一代码块中配置调优的参数。

#一些调优参数
er_patience = 2 #early_stopping patience
lr_patience = 5 #ReduceLROnPlateau patience
max_epochs  = 2 #epochs
lr_rate   = 2e-6#learning rate
batch_sz  = 4   #batch_size
maxlen    = 256 #设置序列长度为,base模型要保证序列长度不超过512
lr_factor = 0.85  #ReduceLROnPlateau factor
maxlentext1 = 200 #选择ocnli子句一的长度
n_folds   = 10    #设置验证集的占比:1/n_folds

Step 5: 模型构建

1)切分数据集(Train,Val)进行模型训练、评价

采用StratifiedKFold分层抽样抽取10%的训练数据作为验证集。

###采用分层抽样的方式,从训练集中抽取10%作为验证机
from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=222)X_trn = pd.DataFrame()
X_val = pd.DataFrame()for train_index, test_index in skf.split(train_df.copy(), train_df['label']):X_trn, X_val = train_df.iloc[train_index], train_df.iloc[test_index]break#不能多折训练

采用f1值做为评价指标,当评价指标不在提升时,降低学习率。

from keras import backend as Kdef f1(y_true, y_pred):def recall(y_true, y_pred):"""Recall metric.Only computes a batch-wise average of recall.Computes the recall, a metric for multi-label classification ofhow many relevant items are selected."""true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))recall = true_positives / (possible_positives + K.epsilon())return recalldef precision(y_true, y_pred):"""Precision metric.Only computes a batch-wise average of precision.Computes the precision, a metric for multi-label classification ofhow many selected items are relevant."""true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))precision = true_positives / (predicted_positives + K.epsilon())return precisionprecision = precision(y_true, y_pred)recall = recall(y_true, y_pred)return 2*((precision*recall)/(precision+recall+K.epsilon()))

2)构造输入bert的数据格式

#标签类别个数
n_cls = len( train_df['label'].unique() )#训练数据、测试数据和标签转化为模型输入格式
#训练集每行的content、label转为tuple存入list,再转为numpy array
TRN_LIST = []
for data_row in X_trn.iloc[:].itertuples():TRN_LIST.append((data_row.content, to_categorical(data_row.label, n_cls)))
TRN_LIST = np.array(TRN_LIST)#验证集每行的content、label转为tuple存入list,再转为numpy array
VAL_LIST = []
for data_row in X_val.iloc[:].itertuples():VAL_LIST.append((data_row.content, to_categorical(data_row.label, n_cls)))
VAL_LIST = np.array(VAL_LIST)#测试集每行的content、label转为tuple存入list,再转为numpy array,其中label全为0
DATA_LIST_TEST = []
for data_row in testa_df.iloc[:].itertuples():DATA_LIST_TEST.append((data_row.content, to_categorical(0, n_cls)))
DATA_LIST_TEST = np.array(DATA_LIST_TEST)

3)模型搭建

在bert后接一层Lambda层取出[CLS]对应的向量,再接一层Dense层用于分类输出。

#bert模型设置
def build_bert(nclass):global lr_ratebert_model = load_trained_model_from_checkpoint(config_path, checkpoint_path, seq_len=None)  #加载预训练模型for l in bert_model.layers:l.trainable = Truex1_in = Input(shape=(None,))x2_in = Input(shape=(None,))x = bert_model([x1_in, x2_in])x = Lambda(lambda x: x[:, 0])(x) #取出[CLS]对应的向量用来做分类p = Dense(nclass, activation='softmax')(x) #直接dense层softmax输出model = Model([x1_in, x2_in], p)model.compile(loss='categorical_crossentropy',optimizer=Adam(lr_rate),    #选择优化器并设置学习率metrics=['accuracy', f1])print(model.summary())return model

4)模型训练

使用google colab 上的V100卡训练一个epoch需要约1.5小时,跑两个epoch即可。

#模型训练函数
def run_nocv(nfold, trn_data, val_data, data_labels, data_test, n_cls):global er_patienceglobal lr_patienceglobal max_epochsglobal f1metricsglobal lr_factortest_model_pred = np.zeros((len(data_test), n_cls))model = build_bert(n_cls)#下行代码用于加载保存的权重继续训练#model.load_weights(path + '/subs/model.epoch01_val_loss0.9911_val_acc0.6445_val_f10.6276.hdf5')early_stopping = EarlyStopping(monitor= "val_f1", patience=er_patience) #早停法,防止过拟合 #'val_accuracy'plateau = ReduceLROnPlateau(monitor="val_f1", verbose=1, mode='max', factor=lr_factor, patience=lr_patience) #当评价指标不在提升时,降低学习率 checkpoint = ModelCheckpoint(path + "/subs/model.epoch{epoch:02d}_val_loss{val_loss:.4f}_val_acc{val_accuracy:.4f}_val_f1{val_f1:.4f}.hdf5", monitor="val_f1", verbose=2, save_best_only=True, mode='max', save_weights_only=True) #保存val_f1最好的模型权重#训练跟验证集可shuffle打乱,测试集不可打乱(否则在生成结果文件的时候没法跟ID对应上)train_D = data_generator(trn_data, shuffle=True)valid_D = data_generator(val_data, shuffle=True)test_D = data_generator(data_test, shuffle=False)#模型训练model.fit_generator(train_D.__iter__(),steps_per_epoch=len(train_D),epochs=max_epochs,validation_data=valid_D.__iter__(),validation_steps=len(valid_D),callbacks=[early_stopping, plateau, checkpoint],)#模型预测test_model_pred = model.predict_generator(test_D.__iter__(), steps=len(test_D), verbose=1)train_model_pred = test_model_pred#model.predict(train_D.__iter__(), steps=len(train_D), verbose=1)del modelgc.collect()   #清理内存K.clear_session() #clear_session就是清除一个sessionreturn test_model_pred, train_model_pred

调用上述函数进行训练与预测。

cvs = 1
#输出为numpy array格式的25列概率
test_model_pred, train_model_pred = run_nocv(cvs, TRN_LIST, VAL_LIST, None, DATA_LIST_TEST, n_cls)

5)输出结果

#将结果转为DataFrame格式
preds_tst_df = pd.DataFrame(test_model_pred)#再将range(0,25)做encode_label逆变换作为该DataFrame的列名
preds_col_names = encode_label.inverse_transform( range(0,n_cls) )
preds_tst_df.columns = preds_col_names#从每个任务对应的概率标签列中找出最大的概率对应的列名作为预测结果
'''
如ocnli任务的预测结果只能为0、1、2,那么从preds_tst_df中选择0-1-2三列中每行概率最大的列名作为ocnli任务的测试集预测结果,其它两个任务依此类推。
'''
times_preds = preds_tst_df.head(times_testa.shape[0])[times_train['label'].unique().tolist()]
times_preds = times_preds.eq(times_preds.max(1), axis=0).dot(times_preds.columns)ocemo_preds = preds_tst_df.head(times_testa.shape[0] + ocemo_testa.shape[0]).tail(ocemo_testa.shape[0])[ocemo_train['label'].unique().tolist()]
ocemo_preds = ocemo_preds.eq(ocemo_preds.max(1), axis=0).dot(ocemo_preds.columns)ocnli_preds = preds_tst_df.tail(ocnli_testa.shape[0])[ocnli_train['label'].unique().tolist()]
ocnli_preds = ocnli_preds.eq(ocnli_preds.max(1), axis=0).dot(ocnli_preds.columns)#输出任务tnews的预测结果
times_sub = times_testa[['id']].copy()
times_sub['label'] = times_preds.values
times_sub.to_json(path + "/subs/tnews_predict.json", orient='records', lines=True)
#输出任务ocemo的预测结果
ocemo_sub = ocemo_testa[['id']].copy()
ocemo_sub['label'] = ocemo_preds.values
ocemo_sub.to_json(path + "/subs/ocemotion_predict.json", orient='records', lines=True)
#输出任务ocnli的预测结果
ocnli_sub = ocnli_testa[['id']].copy()
ocnli_sub['label'] = ocnli_preds.values
ocnli_sub.to_json(path + "/subs/ocnli_predict.json", orient='records', lines=True)

6)线上评分

第一阶段线上评分:0.6342。

希望能帮助你完整实践一场NLP赛事。

往期精彩回顾适合初学者入门人工智能的路线及资料下载机器学习及深度学习笔记等资料打印机器学习在线手册深度学习笔记专辑《统计学习方法》的代码复现专辑
AI基础下载机器学习的数学基础专辑
本站知识星球“黄博的机器学习圈子”(92416895)
本站qq群704220115。
加入微信群请扫码:

【NLP】从0梳理1场NLP赛事!相关推荐

  1. 从0梳理1场NLP赛事!

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:徐美兰-lan,华南理工大学 摘要:这是从0实践竞赛第3篇,数据竞 ...

  2. 从0梳理1场数据挖掘赛事!

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:王茂霖,华中科技大学,Datawhale成员 摘要:数据竞赛对于大 ...

  3. 【数据竞赛】从0梳理1场数据挖掘赛事!

    作者:王茂霖,华中科技大学,Datawhale成员 摘要:数据竞赛对于大家理论实践和增加履历帮助比较大,但许多读者反馈不知道如何入门,本文以河北高校数据挖掘邀请赛为背景,完整梳理了从环境准备.数据读取 ...

  4. 从0梳理1场时间序列赛事!

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:杰少,南京大学硕士 本文基于 2021 "AI Eart ...

  5. 【数据竞赛】从0梳理1场时间序列赛事!

    作者:杰少,南京大学硕士 本文基于 2021 "AI Earth"人工智能创新挑战赛-AI助力精准气象和海洋预测,梳理了时间序列赛事的实践和分析过程,提供了完整baseline方案 ...

  6. 从0梳理1场CV大赛(Top 3%)!

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:阿水,北京航空航天大学 ,Datawhale成员 摘要:数据竞赛对 ...

  7. 从0梳理1场CV缺陷检测赛事!

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:江保祥,厦门大学 一.布匹缺陷检测比赛分析 1. 赛题背景 去年的 ...

  8. 【数据竞赛】从0梳理1场CV缺陷检测赛事!

    作者:江保祥,厦门大学 一.布匹缺陷检测比赛分析 1. 赛题背景 去年的广东工业大赛已入选到全球人工智能技术大赛热身赛,大赛聚焦布匹疵点智能检测,要求选手研究开发高效可靠的计算机视觉算法,提升布匹疵点 ...

  9. 深度学习与自然语言处理教程(8) - NLP中的卷积神经网络(NLP通关指南·完结)

    作者:韩信子@ShowMeAI 教程地址:https://www.showmeai.tech/tutorials/36 本文地址:https://www.showmeai.tech/article-d ...

最新文章

  1. C语言下标要求数组或指针,c语言改错 error C2109: 下标要求数组或指针类型怎么改?...
  2. 22.Chain of Responsibility(职责链)模式
  3. WINDOWS SERVER 2003从入门到精通之使用证书在WEB服务器上设置SSL(下)
  4. 测量一组5层网络的迭代次数
  5. laravel框架安装(奶妈式手把手一步步操作)
  6. python Chrome + selenium自动化测试与python爬虫获取网页数据
  7. dhcp服务器显示2个ip,下列关于Windows 2003系统DHCP服务器的描述中,错误的是( )。A.DHCP服务器负责多个网段IP地址分配_考题宝...
  8. OSPF路由协议概念及工作原理
  9. 种草笔记App放话:要让一万创作者月入过万
  10. 长连接与心跳包 Persistent connection and HearBeats
  11. Eclipse/NSight: methond could not resolved
  12. 各种 Python 库/模块/工具
  13. 使用Cython将python文件打包成.so文件
  14. ofs.open函数
  15. SDN控制器技术综述:SDN交换机配置技术与控制技术的关系—Vecloud
  16. 阿里云CDN和全站加速区别在哪?使用有感
  17. 阿里技术实战:一些云上资源调度的经验谈
  18. python多级雷达图绘制解析_Python实例15:霍兰德人格分析雷达图
  19. Vue 3 快速上手
  20. NLP中的数据增强方法

热门文章

  1. Android 应用间的集成
  2. 用C#实现C/S模式下软件自动在线升级[转载]
  3. PHP内置函数生成随机数的方法汇总
  4. 第十七次ScrumMeeting会议
  5. Verilog中wire与reg类型的区别
  6. psd页面切割成html技巧总结
  7. sprytabbedpanels.js库之在页面中插入Tabbed Panels
  8. ASP.NET MVC3 + Ninject.Mvc3 依赖注入原来可以这么简单
  9. FckEditor的安装与设置
  10. “MSDN 开发论坛”大煞风景