Task2 中文预训练模型泛化能力挑战赛

目录

 

赛题描述及数据说明

代码实践

Step 1:环境准备

Step 2:数据读取

1) 数据集合并

2)标签编码

3) 数据信息查看

Step 3: 数据分析(EDA)

1) 子句长度统计分析

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

Step 4: 预训练模型选择

Step 5: 模型构建

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

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

3) 模型搭建

4) 模型训练

5) 输出结果


赛题描述及数据说明

大赛地址:https://tianchi.aliyun.com/s/3bd272d942f97725286a8e44f40f3f74

代码实践

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 drive

drive.mount('/content/drive')

'''
路径说明:
../code #保存代码
../data #保存数据
../subs #保存数据
../chinese_roberta_wwm_large_ext_L-24_H-1024_A-16 #bert路径
'''

pip install keras-bert

Step 2:数据读取

# 将ocnli中content1[0:maxlentext1]+content2作为ocnli任务的content
times_train = pd.read_csv('/data/TNEWS_train1128.csv',  sep='\t', header=None, names=('id', 'content', 'label')).astype(str)
ocemo_train = pd.read_csv('/data/OCEMOTION_train1128.csv',sep='\t', header=None, names=('id', 'content', 'label')).astype(str)
ocnli_train = pd.read_csv('/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('/data/TNEWS_a.csv',  sep='\t', header=None, names=('id', 'content')).astype(str)
ocemo_testa = pd.read_csv('/data/OCEMOTION_a.csv',sep='\t', header=None, names=('id', 'content')).astype(str)
ocnli_testa = pd.read_csv('/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开始
# LabelEncoder(): Encode labels with value between 0 and n_classes-1.
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  object
 1   content  147453 non-null  object
 2   label    147453 non-null  int64 
dtypes: int64(1), object(2)
memory usage: 4.5+ MB
'''

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.000000
 mean        22.171086
 std          7.334206
 min          2.000000
 50%         22.000000
 95%         33.000000
 98%         37.000000
 99%         39.000000
 max        145.000000
 Name: content, dtype: float64, count    35315.000000
 mean        48.214328
 std         84.391942
 min          3.000000
 50%         34.000000
 95%        134.000000
 98%        138.000000
 99%        142.000000
 max      12326.000000
 Name: content, dtype: float64, count    48778.000000
 mean        24.174607
 std         11.515428
 min          8.000000
 50%         22.000000
 95%         46.000000
 98%         49.000000
 99%         50.000000
 max         50.000000
 Name: content1, dtype: float64, count     48778.000000
 mean         15.828529
 std         977.396848
 min           2.000000
 50%          10.000000
 95%          21.000000
 98%          24.000000
 99%          27.000000
 max      215874.000000
 Name: 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 K

def 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 of
    how 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 recall

  def precision(y_true, y_pred):
  	"""Precision metric.

    Only computes a batch-wise average of precision.

    Computes the precision, a metric for multi-label classification of
    how 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 precision
  precision = precision(y_true, y_pred)
  recall = recall(y_true, y_pred)
  return2*((precision*recall)/(precision+recall+K.epsilon()))

keras.backend.clip(x, min_value, max_value)
逐元素clip(将超出指定范围的数强制变为边界值)

参数

x: 张量或变量。
min_value: Python 浮点或整数。
max_value: Python 浮点或整数。
返回一个张量。
keras.backend.round()
将小数取整:四舍六入五取偶
keras.backend.epsilon()
进行除法运算时,通常将其添加到分母中以防止被零除误差。Epsilon是一个很小的值(在TensorFlow Core v2.2.0中为1e-07),与分母的值几乎没有区别,但可确保它不等于零。

keras.backend.round()
将小数取整:四舍六入五取偶
keras.backend.epsilon()
进行除法运算时,通常将其添加到分母中以防止被零除误差。Epsilon是一个很小的值(在TensorFlow Core v2.2.0中为1e-07),与分母的值几乎没有区别,但可确保它不等于零。

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)

to_categorical

to_categorical就是将类别向量转换为二进制(只有0和1)的矩阵类型表示。其表现为将原有的类别向量转换为独热编码的形式。

3) 模型搭建

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

#bert模型设置
def build_bert(nclass):
  global lr_rate
  bert_model = load_trained_model_from_checkpoint(config_path, checkpoint_path, seq_len=None)  # 加载预训练模型

  for l in bert_model.layers:
    l.trainable = True

  x1_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_patience
  global lr_patience
  global max_epochs
  global f1metrics
  global lr_factor
  test_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 model
  gc.collect()   #清理内存
  K.clear_session() #clear_session就是清除一个session

  return 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)

参考链接:https://blog.csdn.net/nanke_4869/article/details/113658868

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本项目主要基于PaddleHub通过训练模型Erine-tiny在中文7情感分类数据集OCEMOTION上进行微调从而完成7分类情感分析模型的搭建,并基于PyQt5完成了最终中文微情感分析系统的开发,支持单条和批量文本细粒度情感分类测,具有前沿性和广的应用价值。同时全流程教程讲解将带你拿下一个完整文本分类项目的开发! PaddleHub实战:基于OCEMOTION的中文微情感分析系统 完整项目地址:https://aistudio.baidu.com/aistudio/projectdetail/2211726 二.项目亮点: a.不同于传统的情感2分类(正向和负向),本项目使用了7分类数据集OCEMOTION可以达到更细粒度的情感分析,从而可以更好分析用户评论中表达情感,具有前沿性和广的应用价值。 b.基于PaddleHub通过训练模型Erine-tiny的微调完成情感分析模型的搭建。基于大规模未标注语料库的训练模型(Pretrained Models, PTM) 能够习得通用的语言表示,将训练模型Fine-tune到下游任务,能够获得比传统分类模型Lstm等更出色的表现,也成为了目前竞赛及项目的主流选择。另外,训练模型能够避免从零开始训练模型。 c.面向小白的全流程实战教程,全流程细致讲解带你拿下一个完整的文本分类实战项目!项目可扩展性高,感兴趣的也可以在其基础上做出更多的优或迁移到类似的文本分类项目中去哦! 三.情感分析研究意义: 在评论网站、论坛、博客和社交媒体中,可以获得大量表达意见的文本。而这些文本数据都是非结构的,没有以先定义的方式组织,数据量庞大通常难以分析、理解和分类,既费时又费钱。而在情感分析系统的帮助下,这种非结构信息可以依靠自动业务流程以有效且低成本的方式大规模转换为结构数据,极大减少人工标注成本,提高效率。情感分析在舆情监控、话题监督、口碑分析等商业分析领域有着非常重要的应用价值。目前该技术也已有着较广的应用,例如新浪微博运用情感分析对全网数据进行挖掘构建舆情大数据平台。电商平台运用情感分析来进行商品评论挖掘,作为推荐系统的一部分提高营销效果。小度机器人通过识别用户在聊天中的情绪,帮助选择出更匹配用户情绪的文本进行回复。在不远的未来,情感分析也将成为现代公司不可或缺的工具。但目前情感分析仍然局限于有限的简单分类主要为2分类,而有限的情感分类并不能很好地挖掘文本中包含的微情感,不能很好地满足需求。故细粒度的情感分析研究具有前沿性和更广的应用价值。
数据增强(Data Augmentation)是一种通过对原始数据进行一系列变换或扰动来扩充训练集的方法,以提高模型能力。常见的数据增强方法包括随机裁剪、旋转、翻转、缩放等操作,这些操作可以增加数据的多样性,使得模型能够更好地适应各种输入样本的变训练(Pre-training)是指在大规模的数据集上训练一个初始模型,然后将该模型的参数作为初始参数,在目标任务的数据集上进行微调。这种方法通常在计算资源受限或数据集规模较小的情况下使用,通过在大规模数据上进行训练,可以提取出通用的特征表示,并且能够更好地到目标任务。 对抗样本能力(Adversarial Sample Generalization)是指模型在面对对抗样本时的鲁棒性和能力。对抗样本是通过对输入数据进行微小的扰动,使得模型产生错误的测结果。为了提高模型的对抗样本能力,可以使用对抗训练(Adversarial Training)的方法,即在训练过程中引入对抗样本,并将其加入到训练集中。对抗训练可以为模型提供正则,并且提高其对抗样本的鲁棒性和能力。 综上所述,数据增强可以增加训练集的多样性,提高模型能力训练可以通过在大规模数据上提取通用特征表示来改善模型的性能;对抗训练可以提高模型的对抗样本能力。这些方法可以在实际应用中结合使用,以提高模型的效果和鲁棒性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [对抗样本(一)以综述入门](https://blog.csdn.net/StardustYu/article/details/104410147)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值