【NLP】讯飞英文学术论文分类挑战赛Top10开源多方案--6 提分方案

本文涉及的产品
NLP 自学习平台,3个模型定制额度 1个月
NLP自然语言处理_基础版,每接口每天50万次
NLP自然语言处理_高级版,每接口累计50万次
简介: 在讯飞英文学术论文分类挑战赛中的提分技巧和实现方法,包括数据增强、投票融合、伪标签等策略,以及加快模型训练的技巧,如混合精度训练和使用AdamW优化器等。

1 相关信息

2 引言

在该次英文文本分类任务中,数据增强在bert中带来了0.1的增益,伪标签在bert中带来了0.2+增益,在TextCNN等传统深度学习模型,带来了0.3+的增益。但是数据增强在传统深度学习模型并没有带来增益。投票融合也带来0.1+的增益。Stacking也带来了增益,但是本次任务中,没有完全正确实现,也带来了0.1+的增益,但是没有投票融合的效果好。

3 提分技巧及实现

3.1 数据增强

Github源码下载

参考队友写的文章NLP 英文文本数据增强

  • 第一种方式:英文文本随机删除、同义词替换、随机插入、随机交换
########################################################################
# 随机删除
# 以概率p删除语句中的词
########################################################################
#这里传入的sentences是一个英文句子
def random_deletion(sentences, p):
        words = sentences.split()
    if len(words) == 1:
        return words

    new_words = []
    for word in words:
        r = random.uniform(0, 1)
        if r > p:
            new_words.append(word)

    if len(new_words) == 0:
        rand_int = random.randint(0, len(words)-1)
        return [words[rand_int]]

    return " ".join(new_words)
########################################################################
# 随机交换
# 随机交换几次
########################################################################
#这里传入的sentences是一个英文句子
def random_swap(sentences, n):
      words = sentences.split()
    new_words = words.copy()
    for _ in range(n):
        new_words = swap_word(new_words)
    return " ".join(new_words)

def swap_word(new_words):
    random_idx_1 = random.randint(0, len(new_words)-1)
    random_idx_2 = random_idx_1
    counter = 0
    while random_idx_2 == random_idx_1:
        random_idx_2 = random.randint(0, len(new_words)-1)
        counter += 1
        if counter > 3:
            return new_words
    new_words[random_idx_1], new_words[random_idx_2] = new_words[random_idx_2], new_words[random_idx_1] 
    return new_words
########################################################################
# 同义词替换
# 替换一个语句中的n个单词为其同义词
########################################################################

from nltk.corpus import stopwords#引入停用词,因为对停用词进行数据增强相当于没有增强
from nltk.corpus import wordnet as wn#引入同义词
import random
stop_words=stopwords.words('english')
for w in ['!',',','.','?','-s','-ly','</s>','s']:
    stop_words.add(w)

words = sentences.split()
def synonym_replacement(sentences, n):
      words = sentences.split()
    new_words = words.copy()
    random_word_list = list(set([word for word in words if word not in stop_words]))     
    random.shuffle(random_word_list)
    num_replaced = 0  
    for random_word in random_word_list:          
        synonyms = get_synonyms(random_word)
        if len(synonyms) >= 1:
            synonym = random.choice(synonyms)   
            new_words = [synonym if word == random_word else word for word in new_words]   
            num_replaced += 1
        if num_replaced >= n: 
            break

    sentence = ' '.join(new_words)
    new_words = sentence.split(' ')

    return " ".join(new_words)
#获取同义词
def get_synonyms(word):
    nearbyWordSet=wn.synsets(word)
    return nearbyWordSet[0].lemma_names()
  • 第二种方式:互译,翻译成其他语言,再翻译回英文
#经过测试,这个翻译的包翻译的时间是最短的
from pygtrans import Translate
#这里传入的sentences是一个英文句子
def backTran(sentences):
    client = Translate()
    text1 = client.translate(sentences)

    text2 = client.translate(text1.translatedText, target='en')
    return text2.translatedText
  • 使用方法
train_trans = pd.read_csv('./data/train.csv',sep="\t")
test = pd.read_csv('./data/test.csv', sep='\t')

aug_train = pd.DataFrame(columns=['title','abstract','categories'])
aug_test = pd.DataFrame(columns=['title','abstract'])

# 互译处理,其他处理方式也一样
aug_train["title"] = train["title"].progress_apply(lambda x: backTran(x))
aug_train["abstract"] = train["abstract"].progress_apply(lambda x: backTran(x))
aug_test["title"] = test["title"].progress_apply(lambda x: backTran(x))
aug_test["abstract"] = test["abstract"].progress_apply(lambda x: backTran(x))
  • 第三种方式:对抗训练

该方案只在传统的深度学习模型方案中使用,暂时不知如何在bert使用。有两种方法,分别是FGM和PGD。在本次任务中,PGD效果较为好一些,FGM通过实验没有带来任何增益。且加入对抗训练后,模型收敛变慢,需要加深训练深度,加大epoch.

原理参考队友文章NLP 英文文本数据增强

本次任务使用,参考Github源码

3.2 投票融合

Github源码下载

(1)原理解析

我使用该方式的时候,已经用TextCNN、Fasttext、bert_base、bert_large、roberta_large分别得到了0.79+的结果。通过将提交结果文件放在同一个文件夹后。提交文件样式如下。

1.png

对categories列进行唯一类别特征编码,并命名为label。

2.png

同理将多个提交结果都编码并存放到同一个csv文件中,如下所示。投票的原理就是每一行进行投票,多数者即为该行的label。如下第一行0是5列中全票,9995行,12的票数多余29的票数,该行的label为12。投票融合的条件是模型之间差异越大,融合效果越好。

3.png

(2)实现

import pandas as pd
import numpy as np
import os
from pprint import pprint
DATA_DIR = 'voting_data/'#'./ensemble_submit/8298/'
files = os.listdir(DATA_DIR)
files = [i for i in files if i[0]!='.']
print(len(files))
pprint(files)
# 读取原始文件进行编码
train = pd.read_csv('./data/train.csv', sep='\t')
#将标签进行转换
label_id2cate = dict(enumerate(train.categories.unique()))
label_cate2id = {value: key for key, value in label_id2cate.items()}

# 读取提交文件,也可以自定义空的csv文件,该文件将会存储多个结果
sub_exp_df = pd.read_csv('./data/sample_submit.csv')
df_merged = sub_exp_df.drop(['categories'], axis=1)
for file in files:
    tmp_df = pd.read_csv(DATA_DIR + file)
    tmp_df['label'] = tmp_df['categories'].map(label_cate2id)
    tmp_df = tmp_df.drop(['categories'], axis=1)
    df_merged = df_merged.merge(tmp_df, how='left', on='paperid')
df_merged.head()

# 进行计票
def work(pres):
    count = [0]*39
    for i in pres:
        count[i] += 1
    out = count.index(max(count))
    return out
tmp_arr = np.array(df_merged.iloc[:,1:])
# 转为list
label_voted = [work(line) for line in tmp_arr]
# 反编码,生成提交文件
sub_exp_df['categories'] = label_voted
sub_exp_df['categories'] = sub_exp_df['categories'].map(label_id2cate)
# 存储提交文件
savepatch =  "./ensemble_submit/8279_voting.csv"
sub_exp_df = sub_exp_df.drop(['label'], axis=1)
sub_exp_df.to_csv(savepatch, index=False)

3.2 伪标签

参考伪标签(Pseudo-Labelling)——锋利的匕首

(1)原理解析

在文本分类任务中,在比赛最初是没法使用的,因为要获得准确的伪标签,一般选择多模型结果投票的方式获得高质量的标签。是在已经从多个方案中得到了较高的预测结果后,通过投票的方式得到高质量标签。具体来说,我使用该方式的时候,已经用TextCNN、Fasttext、bert_base、bert_large、roberta_large分别得到了0.79+的结果。利用以上的投票原理,选择多个模型都投票的数据作为该行数据的label。然后将该行数据加入到训练集中,重新训练模型。

4.jpeg


注意:图中的第一个Model和第二个模型不是同一个模型,第二个Model是加入伪标签后重新训练出来的模型

(2)实现
Github源码下载

  • 模型结果合并
import pandas as pd
import numpy as np
import os
from pprint import pprint

DATA_DIR = './submit/'
files = os.listdir(DATA_DIR)
files = [i for i in files if i[0]!='.']
print(len(files))
pprint(files)

train = pd.read_csv('./data/train.csv', sep='\t')
#将标签进行转换
label_id2cate = dict(enumerate(train.categories.unique()))
label_cate2id = {value: key for key, value in label_id2cate.items()}

sub_exp_df = pd.read_csv('./data/sample_submit.csv')
df_merged = sub_exp_df.drop(['categories'], axis=1)
for file in files:
    tmp_df = pd.read_csv(DATA_DIR + file)
    tmp_df['label'] = tmp_df['categories'].map(label_cate2id)
    tmp_df = tmp_df.drop(['categories'], axis=1)
    df_merged = df_merged.merge(tmp_df, how='left', on='paperid')
df_merged.head()
  • 构造高质量伪造标签
#计票。
def work_high(pres):
    count = [0]*39
    for i in pres:
        count[i] += 1
    p = 7 # 可根据融合模型的数量选择该数的大小。当共有8个模型融合,>7表示必须有8个模型都投票的才被选择加入伪标签数据
    if max(count) >p:
        out = count.index(max(count))
    else:
        out = -1
    return out

tmp_arr = np.array(df_merged.iloc[:,1:])
label_voted = [work_high(line) for line in tmp_arr]
# 计算有多少数据不被加入伪标签数据
print(label_voted.count(-1))
# 读取测试集
test_data = train = pd.read_csv('./data/test.csv', sep='\t')
test_data['categories'] = label_voted
test_data = test_data.drop(test_data[test_data['categories']==-1].index)
# 计算有多少伪标签数据
len(test_data)
# 反编码映射
test_data['categories'] = test_data['categories'].map(label_id2cate)
# 合并训练集
train = pd.read_csv('./data/train.csv')
# 存储新的训练集
pseudo_label_train = pd.concat([train,test_data])
model_name = "./data/pseudo_train_data"
pseudo_label_train.to_csv('{}.csv'.format(model_name),sep="\t", index=False)

4 加快训练

4.1 混合精度训练

(1)引言

参考资料pytorch混合精度训练

使用fp16存储网络的权重值、激活值和梯度值进行网络训练,好处是:

减少显存占用:上面的图已经很明显的可以看出,fp16的存储空间为fp32的一半,如果使用fp16进行训练,那么可以减少一半的显存占用,因此也就可以使用更大的batchsize进行大模型的训练;

加快训练和推理速度:fp16可以提高模型的训练和推理的速度。

(2)实现

有两种方法实现,nvidia版本和pytorch版本。这里采用了第一种,其他的方案参考pytorch混合精度训练。实现只需要在pytroch代码上,修改三行代码即可

from apex import amp
model, optimizer = amp.initialize(model, optimizer, opt_level="O1") # 这里是“欧一”,不是“零一”
with amp.scale_loss(loss, optimizer) as scaled_loss:
    scaled_loss.backward()

opt_level参数:

O0:纯FP32训练,可以作为accuracy的baseline;
O1:混合精度训练(推荐使用),根据黑白名单自动决定使用FP16(GEMM, 卷积)还是FP32(Softmax)进行计算;
O2:“几乎FP16”混合精度训练,不存在黑白名单,除了Batch norm,几乎都是用FP16计算;
O3:纯FP16训练,很不稳定,但是可以作为speed的baseline。

(3)具体实现

#训练模型
import torch.nn as nn
from apex import amp 
def train_start(EPOCHS,MAX_LEN,BATCH_SIZE,train, test_data_loader, label_id2cate):
    #模型定义
    model = PaperClassifier()
    model = model.to(device)
    k_fold = 5
    predict_all = np.zeros([10000,39])#存储测试集的 预测结果
    for n in range(k_fold):
        train_data_loader, val_data_loader = load_data_kfold(train, BATCH_SIZE,MAX_LEN, k_fold, n)
        #使用差分学习率
        parameters = get_parameters(model, 2e-5, 0.95, 1e-4)
        optimizer = AdamW(parameters)
        # fp16混合精度训练        
        model, optimizer = amp.initialize(model, optimizer, opt_level="O1")
        total_steps = len(train_data_loader) * EPOCHS

        scheduler = get_linear_schedule_with_warmup(
            optimizer,
            num_warmup_steps=0,
            num_training_steps=total_steps
        )
        loss_fn = nn.CrossEntropyLoss().to(device)
        best_accuracy = 0
        for epoch in range(EPOCHS):
            print(f'Epoch {epoch + 1}/{EPOCHS}')
            print('-' * 10)
            train_acc, train_loss = train_epoch(
                model,
                train_data_loader,
                loss_fn,
                optimizer,
                device,
                scheduler
            )

            print(f'Train loss {train_loss} accuracy {train_acc}')
            val_acc, val_loss= eval_model(
                model, val_data_loader, loss_fn, device)
            print(f'Val loss {val_loss} accuracy {val_acc}')

            if val_acc > best_accuracy:
                torch.save(model.state_dict(), 'model/best_model_state_large_aug.bin')
                best_accuracy = val_acc

        #进行预测
        y_pred = model_predictions(model, test_data_loader, device)
        predict_all += np.array(y_pred)
        test_x.extend(y_pred)
def train_epoch(model, data_loader, loss_fn, optimizer, device, scheduler):

    print("start training!")
    model = model.train()
    losses = []
    pred_ls = []
    label_ls = []
    for d in tqdm(data_loader):
        input_ids = d["input_ids"].to(device)
        attention_mask = d["attention_mask"].to(device)
        targets = d["labels"].to(device)
        outputs = model(
            input_ids=input_ids,
            attention_mask=attention_mask
        )
        _, preds = torch.max(outputs, dim=1)
        loss = loss_fn(outputs, targets)
        losses.append(loss.item())
        # fp16混合精度训练
        with amp.scale_loss(loss, optimizer) as scaled_loss:
            scaled_loss.backward() 
#         loss.backward()
        nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
        optimizer.step()
        scheduler.step()
        optimizer.zero_grad()
        label_ls.extend(d["labels"])
        pred_ls.extend(preds.tolist())
    correct_predictions = accuracy_score(label_ls, pred_ls)
    return correct_predictions, np.mean(losses)

4.2 加速训练的其他技巧

详细内容参考让PyTorch训练速度更快,你需要掌握这17种方法

4.2.1 有用到的加速策略

  • 使用schedule学习率 、AadmW优化器、混合精度训练代码
#训练模型
import torch.nn as nn
from apex import amp 
def train_start(EPOCHS,MAX_LEN,BATCH_SIZE,train, test_data_loader, label_id2cate):
    #模型定义
    model = PaperClassifier()
    model = model.to(device)
    k_fold = 5
    predict_all = np.zeros([10000,39])#存储测试集的 预测结果
    for n in range(k_fold):
        train_data_loader, val_data_loader = load_data_kfold(train, BATCH_SIZE,MAX_LEN, k_fold, n)
        #使用差分学习率
        parameters = get_parameters(model, 2e-5, 0.95, 1e-4)
        # AdamW优化器
        optimizer = AdamW(parameters)
        # fp16混合精度训练        
        model, optimizer = amp.initialize(model, optimizer, opt_level="O1")
        total_steps = len(train_data_loader) * EPOCHS
                # schedule学习率 
        scheduler = get_linear_schedule_with_warmup(
            optimizer,
            num_warmup_steps=0,
            num_training_steps=total_steps
        )
        loss_fn = nn.CrossEntropyLoss().to(device)
        best_accuracy = 0
        for epoch in range(EPOCHS):
            print(f'Epoch {epoch + 1}/{EPOCHS}')
            print('-' * 10)
            train_acc, train_loss = train_epoch(
                model,
                train_data_loader,
                loss_fn,
                optimizer,
                device,
                scheduler
            )

            print(f'Train loss {train_loss} accuracy {train_acc}')
            val_acc, val_loss= eval_model(
                model, val_data_loader, loss_fn, device)
            print(f'Val loss {val_loss} accuracy {val_acc}')

            if val_acc > best_accuracy:
                torch.save(model.state_dict(), 'model/best_model_state_large_aug.bin')
                best_accuracy = val_acc

        #进行预测
        y_pred = model_predictions(model, test_data_loader, device)
        predict_all += np.array(y_pred)
        test_x.extend(y_pred)
  • 在 DataLoader 中使用多个 worker 和页锁定内存

当使用 torch.utils.data.DataLoader 时,设置 num_workers > 0,而不是默认值 0,同时设置 pin_memory=True,而不是默认值 False。人们选择 worker 数量的经验法则是将其设置为可用 GPU 数量的四倍,大于或小于这个数都会降低训练速度。

    return DataLoader(
        ds,
        batch_size=batch_size,
        sampler = sampler,
        num_workers=4,  # 多线程
        pin_memory=True  # 页锁定内存
      )
  • 把 batch 调到最大

把 batch 调到最大是一个颇有争议的观点。一般来说,如果在 GPU 内存允许的范围内将 batch 调到最大,你的训练速度会更快。但是,你也必须调整其他超参数,比如学习率。一个比较好用的经验是,batch 大小加倍时,学习率也要加倍。

  • 使用自动混合精度(AMP)

与单精度 (FP32) 相比,某些运算在半精度 (FP16) 下运行更快,而不会损失准确率。AMP 会自动决定应该以哪种精度执行哪种运算。这样既可以加快训练速度,又可以减少内存占用。

  • 使用AdamW优化器

Adam,在 PyTorch 中以 torch.optim.AdamW 实现。AdamW 似乎在误差和训练时间上都一直优于 Adam。

  • 小心 CPU 和 GPU 之间频繁的数据传输

当频繁地使用 tensor.cpu() 将张量从 GPU 转到 CPU(或使用 tensor.cuda() 将张量从 CPU 转到 GPU)时,代价是非常昂贵的。item() 和 .numpy() 也是一样可以使用. detach() 代替。

  • 在验证期间关闭梯度计算

在验证期间关闭梯度计算,设置:torch.no_grad() 。

4.2.2 未用到的加速策略

  • 使用梯度积累

增加 batch 大小的另一种方法是在调用 optimizer.step() 之前在多个. backward() 传递中累积梯度。

  • 使用分布式数据并行进行多 GPU 训练

加速分布式训练可能有很多方法,但是简单的方法是使用 torch.nn.DistributedDataParallel 而不是 torch.nn.DataParallel。这样一来,每个 GPU 将由一个专用的 CPU 核心驱动,避免了 DataParallel 的 GIL 问题。

  • 设置梯度为 None 而不是 0

梯度设置为. zero_grad(set_to_none=True) 而不是 .zero_grad()。这样做可以让内存分配器处理梯度,而不是将它们设置为 0。正如文档中所说,将梯度设置为 None 会产生适度的加速,但不要期待奇迹出现。注意,这样做也有缺点,详细信息请查看文档。

  • 使用. as_tensor() 而不是. tensor()

torch.tensor() 总是会复制数据。如果你要转换一个 numpy 数组,使用 torch.as_tensor() 或 torch.from_numpy() 来避免复制数据。

  • 使用梯度裁剪

关于避免 RNN 中的梯度爆炸的问题,已经有一些实验和理论证实,梯度裁剪(gradient = min(gradient, threshold))可以加速收敛。HuggingFace 的 Transformer 实现就是一个非常清晰的例子,说明了如何使用梯度裁剪。本文中提到的其他一些方法,如 AMP 也可以用。在 PyTorch 中可以使用 torch.nn.utils.clip_grad_norm_来实现。

  • 在 BatchNorm 之前关闭 bias

在开始 BatchNormalization 层之前关闭 bias 层。对于一个 2-D 卷积层,可以将 bias 关键字设置为 False:torch.nn.Conv2d(…, bias=False, …)。

  • 使用输入和 batch 归一化

要再三检查一下输入是否归一化?是否使用了 batch 归一化?

目录
相关文章
|
4月前
|
数据采集 机器学习/深度学习 存储
【NLP】讯飞英文学术论文分类挑战赛Top10开源多方案–5 Bert 方案
在讯飞英文学术论文分类挑战赛中使用BERT模型进行文本分类的方法,包括数据预处理、模型微调技巧、长文本处理策略以及通过不同模型和数据增强技术提高准确率的过程。
42 0
|
4月前
|
机器学习/深度学习 数据采集 自然语言处理
【NLP】讯飞英文学术论文分类挑战赛Top10开源多方案–4 机器学习LGB 方案
在讯飞英文学术论文分类挑战赛中使用LightGBM模型进行文本分类的方案,包括数据预处理、特征提取、模型训练及多折交叉验证等步骤,并提供了相关的代码实现。
53 0
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
AI技术在自然语言处理中的应用与挑战
【10月更文挑战第3天】本文将探讨AI技术在自然语言处理(NLP)领域的应用及其面临的挑战。我们将分析NLP的基本原理,介绍AI技术如何推动NLP的发展,并讨论当前的挑战和未来的趋势。通过本文,读者将了解AI技术在NLP中的重要性,以及如何利用这些技术解决实际问题。
|
3月前
|
机器学习/深度学习 数据采集 自然语言处理
深度学习在自然语言处理中的应用与挑战
本文探讨了深度学习技术在自然语言处理(NLP)领域的应用,包括机器翻译、情感分析和文本生成等方面。同时,讨论了数据质量、模型复杂性和伦理问题等挑战,并提出了未来的研究方向和解决方案。通过综合分析,本文旨在为NLP领域的研究人员和从业者提供有价值的参考。
|
2月前
|
自然语言处理 算法 Python
自然语言处理(NLP)在文本分析中的应用:从「被动收集」到「主动分析」
【10月更文挑战第9天】自然语言处理(NLP)在文本分析中的应用:从「被动收集」到「主动分析」
50 4
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
探索AI在自然语言处理中的创新应用
【10月更文挑战第7天】本文将深入探讨人工智能在自然语言处理领域的最新进展,揭示AI技术如何改变我们与机器的互动方式,并展示通过实际代码示例实现的具体应用。
42 1
|
3月前
|
机器学习/深度学习 人工智能 自然语言处理
AI技术在自然语言处理中的应用
【9月更文挑战第17天】本文主要介绍了AI技术在自然语言处理(NLP)领域的应用,包括文本分类、情感分析、机器翻译和语音识别等方面。通过实例展示了AI技术如何帮助解决NLP中的挑战性问题,并讨论了未来发展趋势。
|
5天前
|
机器学习/深度学习 人工智能 自然语言处理
AI技术在自然语言处理中的应用
随着人工智能技术的不断发展,自然语言处理(NLP)已经成为了一个重要的应用领域。本文将介绍一些常见的NLP任务和算法,并通过代码示例来展示如何实现这些任务。我们将讨论文本分类、情感分析、命名实体识别等常见任务,并使用Python和相关库来实现这些任务。最后,我们将探讨NLP在未来的发展趋势和挑战。
|
24天前
|
机器学习/深度学习 自然语言处理 监控
探索深度学习在自然语言处理中的应用与挑战
本文深入分析了深度学习技术在自然语言处理(NLP)领域的应用,并探讨了当前面临的主要挑战。通过案例研究,展示了如何利用神经网络模型解决文本分类、情感分析、机器翻译等任务。同时,文章也指出了数据稀疏性、模型泛化能力以及计算资源消耗等问题,并对未来的发展趋势进行了展望。
|
28天前
|
人工智能 自然语言处理 API
探索AI在自然语言处理中的应用
【10月更文挑战第34天】本文将深入探讨人工智能(AI)在自然语言处理(NLP)领域的应用,包括语音识别、机器翻译和情感分析等方面。我们将通过代码示例展示如何使用Python和相关库进行文本处理和分析,并讨论AI在NLP中的优势和挑战。