【NLP】从0梳理1场NLP赛事!
作者:徐美蘭-lan,華南理工大學
摘要:這是從0實踐競賽第3篇,數(shù)據(jù)競賽對理論實踐和增加履歷有益,為了讓初學者也能入門,本文以全球人工智能技術(shù)大賽預熱賽-中文預訓練模型賽事為背景,梳理了nlp賽事的完整實踐流程。
今天以天池中文預訓練模型泛化能力賽事為背景,介紹一種適用入門nlp的Baseline:如何使用bert簡單、快速地完成多任務多目標分類。具體目錄如下:
0. 賽事背景
大賽名稱:全球人工智能技術(shù)創(chuàng)新大賽【熱身賽二】-?中文預訓練模型泛化能力賽事
大賽地址:
https://tianchi.aliyun.com/s/3bd272d942f97725286a8e44f40f3f74(或文末閱讀原文)
大賽類型:自然語言處理、預訓練模型
1. 賽題分析
1.本次賽題為數(shù)據(jù)挖掘類型,通過預訓練模型調(diào)優(yōu)進行分類。
2.是一個典型的多任務多分類問題。
3.主要應用keras_bert,以及pandas、numpy、matplotlib、seabon、sklearn、keras等數(shù)據(jù)挖掘常用庫或者框架來進行數(shù)據(jù)挖掘任務。
4.賽題禁止人工標注;微調(diào)階段不得使用外部數(shù)據(jù);三個任務只能共用一個bert;只能單折訓練。
2. 數(shù)據(jù)概況
賽題精選了3個具有代表性的任務,要求選手提交的模型能夠同時預測每個任務對應的標簽。數(shù)據(jù)下載地址:https://tianchi.aliyun.com/s/3bd272d942f97725286a8e44f40f3f74
數(shù)據(jù)格式:
任務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 平安夜,圣誕節(jié),都過了,我很難過,和媽媽吵了兩天,以死相逼才終止戰(zhàn)爭,現(xiàn)在還處于冷戰(zhàn)中。 sadness 2 2 我只是自私了一點,做自己想做的事情! sadness ''' len(ocemo_train['label'].unique()) #7任務3:TNEWS–今日頭條新聞標題分類,包含15個類別
times_train.head(3) ''' id content label 0 0 上課時學生手機響個不停,老師一怒之下把手機摔了,家長拿發(fā)票讓老師賠,大家怎么看待這種事? 108 1 1 商贏環(huán)球股份有限公司關于延期回復上海證券交易所對公司2017年年度報告的事后審核問詢函的公告 104 2 2 通過中介公司買了二手房,首付都付了,現(xiàn)在賣家不想賣了。怎么處理? 106 ''' len(times_train['label'].unique()) #153. 代碼實踐
Step 1:環(huán)境準備
導入相關包
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上運行代碼,需要先將數(shù)據(jù)上傳至driver上。執(zhí)行以下代碼掛在driver并配置相關環(huán)境。
from google.colab import drivedrive.mount('/content/drive')''' 路徑說明: ../code #保存代碼 ../data #保存數(shù)據(jù) ../subs #保存數(shù)據(jù) ../chinese_roberta_wwm_large_ext_L-24_H-1024_A-16 #bert路徑 '''pip install keras-bertStep 2:數(shù)據(jù)讀取
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) 數(shù)據(jù)集合并
分別將三個任務的content、label列按行concat在一起作為訓練集和標簽、測試集,以此簡單地將三任務轉(zhuǎn)化為單任務。
#合并三個任務的訓練、測試數(shù)據(jù) 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) 數(shù)據(jù)信息查看
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 '''數(shù)據(jù)為id、content、label三列,無子句為空的行。
Step 3: 數(shù)據(jù)分析(EDA)
1) 子句長度統(tǒng)計分析
統(tǒng)計子句長度主要用于設置輸入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%子句的全部內(nèi)容。
2)統(tǒng)計標簽的基本分布信息
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選擇了哈工大與訊飛聯(lián)合發(fā)布的基于全詞遮罩(Whole Word Masking)技術(shù)的中文預訓練模型:RoBERTa-wwm-ext-large。點擊以下鏈接了解更多詳細信息:
論文地址:https://arxiv.org/abs/1906.08101
開源模型地址:https://github.com/ymcui/Chinese-BERT-wwm
哈工大訊飛聯(lián)合實驗室的項目介紹:https://mp.weixin.qq.com/s/EE6dEhvpKxqnVW_bBAKrnA
2)調(diào)優(yōu)參數(shù)配置
為方便調(diào)優(yōu),在同一代碼塊中配置調(diào)優(yōu)的參數(shù)。
#一些調(diào)優(yōu)參數(shù) 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_foldsStep 5: 模型構(gòu)建
1)切分數(shù)據(jù)集(Train,Val)進行模型訓練、評價
采用StratifiedKFold分層抽樣抽取10%的訓練數(shù)據(jù)作為驗證集。
###采用分層抽樣的方式,從訓練集中抽取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)構(gòu)造輸入bert的數(shù)據(jù)格式
#標簽類別個數(shù) n_cls = len( train_df['label'].unique() )#訓練數(shù)據(jù)、測試數(shù)據(jù)和標簽轉(zhuǎn)化為模型輸入格式 #訓練集每行的content、label轉(zhuǎn)為tuple存入list,再轉(zhuǎn)為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轉(zhuǎn)為tuple存入list,再轉(zhuǎn)為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轉(zhuǎn)為tuple存入list,再轉(zhuǎn)為numpy array,其中l(wèi)abel全為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), #選擇優(yōu)化器并設置學習率metrics=['accuracy', f1])print(model.summary())return model4)模型訓練
使用google colab 上的V100卡訓練一個epoch需要約1.5小時,跑兩個epoch即可。
#模型訓練函數(shù) 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)#下行代碼用于加載保存的權(quán)重繼續(xù)訓練#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最好的模型權(quán)重#訓練跟驗證集可shuffle打亂,測試集不可打亂(否則在生成結(jié)果文件的時候沒法跟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() #清理內(nèi)存K.clear_session() #clear_session就是清除一個sessionreturn test_model_pred, train_model_pred調(diào)用上述函數(shù)進行訓練與預測。
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)輸出結(jié)果
#將結(jié)果轉(zhuǎn)為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#從每個任務對應的概率標簽列中找出最大的概率對應的列名作為預測結(jié)果 ''' 如ocnli任務的預測結(jié)果只能為0、1、2,那么從preds_tst_df中選擇0-1-2三列中每行概率最大的列名作為ocnli任務的測試集預測結(jié)果,其它兩個任務依此類推。 ''' 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的預測結(jié)果 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的預測結(jié)果 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的預測結(jié)果 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賽事。
往期精彩回顧適合初學者入門人工智能的路線及資料下載機器學習及深度學習筆記等資料打印機器學習在線手冊深度學習筆記專輯《統(tǒng)計學習方法》的代碼復現(xiàn)專輯 AI基礎下載機器學習的數(shù)學基礎專輯 本站知識星球“黃博的機器學習圈子”(92416895) 本站qq群704220115。 加入微信群請掃碼: 與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的【NLP】从0梳理1场NLP赛事!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: foxmail邮件怎样打印日历
- 下一篇: tim怎么设置检测到新版本自动安装 ti