千言数据集:文本相似度——BERT完成NSP任务

以下学习笔记来源于 Coggle 30 Days of ML(22年1&2月)
链接:https://coggle.club/blog/30days-of-ml-202201

比赛链接:https://aistudio.baidu.com/aistudio/competition/detail/45/0/task-definition

了解BERT和NSP

BERT

BERT的全称为Bidirectional Encoder Representation from Transformers,是一个预训练的语言表征模型。它强调了不再像以往一样采用传统的单向语言模型或者把两个单向语言模型进行浅层拼接的方法进行预训练,而是采用新的masked language model(MLM),以致能生成深度的双向语言表征。在多个NLP任务中取得非常好的结果。

Next Sentence Prediction(NSP)

一些如问答、自然语言推断等任务需要理解两个句子之间的关系,而MLM任务倾向于抽取token层次的表征,因此不能直接获取句子层次的表征。为了使模型能够有能力理解句子间的关系,BERT使用了NSP任务来预训练,简单来说就是预测两个句子是否连在一起。具体的做法是:对于每一个训练样例,我们在语料库中挑选出句子A和句子B来组成,50%的时候句子B就是句子A的下一句(标注为IsNext),剩下50%的时候句子B是语料库中的随机句子(标注为NotNext)。接下来把训练样例输入到BERT模型中,用[CLS]对应的C信息去进行二分类的预测。
(参考自https://zhuanlan.zhihu.com/p/98855346)

使用BERT完成NSP任务

NSP简单来说就是预测两个句子是否连在一起,对于文本相似度任务中的两个句子text1和text2,若其相似,我们可以认为text2是text1的next sentence,反之则不是。千言数据集的文本相似度数据有bq、lcqmc和pawsx,我们分别读取这些数据并训练对应数据的模型,最后使用模型预测对应的测试数据集并提交结果(可以尝试将所有训练数据合在一起训练后预测测试数据,目前还没有试,不知道效果如何)。

导入基本库

import pandas as pd
import torch
from sklearn.model_selection import train_test_split
from torch.utils.data import Dataset, DataLoader, TensorDataset
import numpy as np
import pandas as pd
import random
import re

读取数据

#读取tsv文件的方法
def read_tsv(input_file,columns):
    with open(input_file,"r",encoding="utf-8") as file:
        lines = []
        for line in file:
            if len(line.strip().split("\t")) != 1:
                lines.append(line.strip().split("\t"))
        df = pd.DataFrame(lines)
        df.columns = columns
    return df

bq_train=read_tsv('./bq_corpus/train.tsv',['text1','text2','label'])
lcqmc_train=read_tsv('./lcqmc/train.tsv',['text1','text2','label'])
pawsx_train=read_tsv('./paws-x-zh/train.tsv',['text1','text2','label'])

bq_test=read_tsv('./bq_corpus/test.tsv',['text1','text2'])
lcqmc_test=read_tsv('./lcqmc/test.tsv',['text1','text2'])
pawsx_test=read_tsv('./paws-x-zh/test.tsv',['text1','text2'])

自定义DataSet

#数据集读取
class QYanDataSet(Dataset):
    def __init__(self,encodings,labels):
        self.encodings=encodings
        self.labels=labels
    #读取单个样本
    def __getitem__(self,idx):
        item={key:torch.tensor(val[idx]) for key,val in self.encodings.items()}
        item['labels']=torch.tensor(int(self.labels[idx]))
        return item
    def __len__(self):
        return len(self.labels)

定义计算accuracy函数

#accuracy计算
def flat_accuracy(preds,labels):
    '''
    flatten是numpy.ndarray.flatten的一个函数,即返回一个一维数组。
    a.flatten():a是个数组,a.flatten()就是把a降到一维,默认是按行的方向降 。
    '''
    pred_flat=np.argmax(preds,axis=1).flatten()
    labels_flat=labels.flatten()
    return np.sum(pred_flat==labels_flat)/len(labels_flat)

训练&验证函数

def solve_train(df_train,name):
    q1_train, q1_val, q2_train, q2_val, train_label, test_label =  train_test_split(
        df_train['text1'].iloc[:], 
        df_train['text2'].iloc[:],
        df_train['label'].iloc[:],
        test_size=0.1, 
        stratify=df_train['label'].iloc[:] #设置stratify参数,可以处理数据不平衡问题
    )
    # 利用分词器进行编码,encode仅返回input_ids,encode_plus返回所有编码信息
    # input_ids:是单词在词典中的编码
    # token_type_ids:标识是第一个句子还是第二个句子,区分两个句子的编码(上句全为0,下句全为1)
    # attention_mask:标识是不是填充,指定对哪些词进行self-Attention操作
    from transformers import BertTokenizer
    # 分词器,词典
    tokenizer=BertTokenizer.from_pretrained('bert-base-chinese')
    train_encoding=tokenizer(list(q1_train),list(q2_train),truncation=True,
                             padding=True,max_length=100)
    val_encoding=tokenizer(list(q1_val),list(q2_val),truncation=True,
                          padding=True,max_length=100)
    
    train_dataset=QYanDataSet(train_encoding,list(train_label))
    val_dataset=QYanDataSet(val_encoding,list(test_label))
    
    from transformers import BertForNextSentencePrediction,AdamW,get_linear_schedule_with_warmup
    model=BertForNextSentencePrediction.from_pretrained('bert-base-chinese')
    device=torch.device('cuda' if torch.cuda.is_available() else 'cpu')
#     device=torch.device('cpu')
    model.to(device)

    #单个读取到批量读取
    train_loader=DataLoader(train_dataset,batch_size=4,shuffle=True)
    val_loader=DataLoader(val_dataset,batch_size=4,shuffle=True)

    #优化方法
    optim=AdamW(model.parameters(),lr=1e-5)
    
    # 训练函数
    def train():
        model.train()
        total_train_loss = 0
        iter_num = 0
        total_iter = len(train_loader)
        for batch in train_loader:
            # 正向传播
            optim.zero_grad()
            input_ids = batch['input_ids'].to(device)
            attention_mask = batch['attention_mask'].to(device)
            labels = batch['labels'].to(device)
            outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
            loss = outputs[0]
            total_train_loss += loss.item()

            # 反向梯度信息
            loss.backward()
            torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) #梯度剪切函数,为防止梯度爆炸
            
            if hasattr(torch.cuda, 'empty_cache'):
                torch.cuda.empty_cache()
            # 参数更新
            optim.step()
            
            iter_num += 1
            if(iter_num % 100==0):
                print("epoth: %d, iter_num: %d, loss: %.4f, %.2f%%" % (epoch, iter_num, loss.item(), iter_num/total_iter*100))

        print("Epoch: %d, Average training loss: %.4f"%(epoch, total_train_loss/len(train_loader)))

    def validation():
        model.eval()
        total_eval_accuracy = 0
        total_eval_loss = 0
        for batch in val_loader:
            with torch.no_grad():
                # 正常传播
                input_ids = batch['input_ids'].to(device)
                attention_mask = batch['attention_mask'].to(device)
                labels = batch['labels'].to(device)
                outputs = model(input_ids, attention_mask=attention_mask, labels=labels)

            loss = outputs[0]
            logits = outputs[1]

            total_eval_loss += loss.item()
            logits = logits.detach().cpu().numpy()
            label_ids = labels.to('cpu').numpy()
            total_eval_accuracy += flat_accuracy(logits, label_ids)

        avg_val_accuracy = total_eval_accuracy / len(val_loader)
        print("Accuracy: %.4f" % (avg_val_accuracy))
        print("Average testing loss: %.4f"%(total_eval_loss/len(val_loader)))
        print("-------------------------------")


    for epoch in range(5):
        print("------------Epoch: %d ----------------" % epoch)
        train()
        validation()
        torch.save(model.state_dict(), f'model_{name}_{epoch}.pt')

预测函数

def solve_predict(df_test,model_path,save_path):
    from transformers import BertTokenizer,BertForNextSentencePrediction
    model=BertForNextSentencePrediction.from_pretrained('bert-base-chinese')
    model.load_state_dict(torch.load(model_path))

    df_test['label']=[0.0 for i in range(len(df_test))]
    tokenizer=BertTokenizer.from_pretrained('bert-base-chinese')
    test_encoding=tokenizer(list(df_test['text1']),list(df_test['text2']),truncation=True,
                                padding=True,max_length=100)
    test_dataset=QYanDataSet(test_encoding,list(df_test['label']))
    test_loader=DataLoader(test_dataset,batch_size=16,shuffle=False)

    device=torch.device('cpu')
    def predict():
        model.eval()
        test_predict = []
        for batch in test_loader:
            with torch.no_grad():
                # 正常传播
                input_ids = batch['input_ids'].to(device)
                attention_mask = batch['attention_mask'].to(device)
                labels = batch['labels'].to(device)
                outputs = model(input_ids, attention_mask=attention_mask, labels=labels)

            loss = outputs[0]
            logits = outputs[1]

            logits = logits.detach().cpu().numpy()
            #label_ids = labels.to('cpu').numpy()
            test_predict += list(np.argmax(logits, axis=1).flatten())

        return test_predict

    test_label = predict()
    tmp=[i for i in range(len(test_label))]
    pd.DataFrame({'index':tmp,'prediction':test_label}).to_csv(save_path,index=None, sep='\t')

训练模型

因为自己的笔记本跑的比较慢,就只跑了一个epoch,可以多几轮,取效果较好的模型进行预测。

solve_train(pawsx_train,'pawsx')

请添加图片描述

solve_train(bq_train,'bq')

请添加图片描述

solve_train(lcqmc_train,'lcqmc')

请添加图片描述

预测结果

solve_predict(pawsx_test,f'model_pawsx_0.pt','./submit/paws-x.tsv')
solve_predict(bq_test,f'model_bq_0.pt','./submit/bq_corpus.tsv')
solve_predict(lcqmc_test,f'model_lcqmc_0.pt','./submit/lcqmc.tsv')

提交结果

请添加图片描述
0.8062的分数,比之前的树模型提升了挺多的。

改进方法

  • 可以尝试将数据中的停用词、特殊符号等进行相应处理。
  • 适当的增加训练轮数
  • 改进模型(才开始学,不太会)
  • 进行模型融合
  • 5
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
BERT(Bidirectional Encoder Representations from Transformers)是一个深度双向转换器,它在自然语言处理任务中表现出色。BERT模型已经在多个任务中取得了最新的最佳性能,包括文本相似度任务。微调BERT文本相似度任务需要以下步骤: 1. 准备数据集:您需要准备一个包含一对文本和相应标签的数据集。标签可以是二元分类标签,表示两个文本是相似的还是不相似的。您可以使用公开可用的文本相似度数据集,例如STS-Benchmark,Quora Question Pairs等。 2. 预处理数据:对原始数据进行清洗、分词、转换等预处理操作,使其适合BERT模型的输入格式。BERT模型的输入格式是一个包含特殊标记的文本序列,其中包括CLS标记(分类标记)和SEP标记(分隔标记)。 3. 加载BERT模型:从预训练的BERT模型中加载预训练的权重,并在顶部添加一个全连接层,用于对输入序列进行分类。 4. Fine-tune模型:使用加载的BERT模型和预处理的数据集进行微调,以最大程度地提高模型在文本相似度任务中的性能。微调过程中,您需要优化模型的损失函数,通常使用交叉熵损失函数。 5. 评估模型:使用测试集评估微调后的BERT模型在文本相似度任务中的性能。 6. 预测:使用微调后的BERT模型对新的文本对进行预测,以确定它们是否相似。 以上是微调BERT模型的一般步骤。您可以使用开源的深度学习框架(如Tensorflow,PyTorch等)和相应的BERT模型库(如Hugging Face Transformers)来实现这些步骤。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值