【NLP】Datawhale-AI夏令营Day4打卡:预训练+微调范式

简介: 【NLP】Datawhale-AI夏令营Day4打卡:预训练+微调范式


1. 学习内容

AI夏令营第三期–基于论文摘要的文本分类与关键词抽取挑战赛教程

✅ Bert - 预训练+微调范式

✏️:BERT(Bidirectional Encoder Representations from Transformers)的意思是基于双向Transformer的编码器表示,BERT的核心思想是使用双向Transformer来编码文本数据,从而获得文本中每个词的上下文相关的向量表示,然后将这些向量表示作为输入,用于不同的下游任务,如文本分类、文本生成、文本摘要等。

✏️:BERT的 预训练(Pre-training) 过程是指在大规模的通用数据集上进行无监督或自监督的学习,目的是让模型学习到通用的知识和能力,如词汇、语法、语义、逻辑、常识等。

BERT使用了两种预训练任务,分别是:

掩码语言模型(Masked Language Model,MLM):这个任务是指在输入的文本中随机地遮盖一些词,然后让模型根据上下文来预测被遮盖的词。这个任务可以让模型学习到词汇和语法的知识。

下一个句子预测(Next Sentence Prediction,NSP):这个任务是指给定两个句子A和B,让模型判断B是否是A的下一个句子。这个任务可以让模型学习到语义和逻辑的知识。

BERT使用了数TB甚至数PB的数据集来进行预训练,如英文维基百科、书籍语料库等。BERT使用了数千甚至数万个GPU或TPU等高性能计算设备来进行并行计算和优化。BERT预训练后得到了一个通用的编码器模型,它可以将任意长度的文本转换为固定长度的向量表示。

✏️:BERT的 微调(Fine-tuning) 过程是指在有标注的数据上进行有监督的学习,目的是让模型适应特定的任务和场景,如文本分类、文本生成、文本摘要等。BERT使用了一种简单而有效的微调方法,即在预训练好的编码器模型上添加一个简单的输出层,然后根据不同的任务和场景来调整输出层的结构和参数。例如,在文本分类任务中,输出层可以是一个全连接层或者一个softmax层;在文本生成任务中,输出层可以是一个解码器或者一个线性层等。

BERT使用了少量标注好的数据来进行微调,如GLUE、SQuAD等公开数据集。BERT使用了相对较少的计算资源来进行微调,一般只需要几个小时或几天就可以完成。BERT微调后得到了一个针对特定任务和场景的模型,它可以根据输入的文本来产生相应的输出或行为。

✏️:以上就是BERT处理文本分类任务的预训练和微调过程。从这个过程中可以看出,BERT利用了“大规模预训练+微调”的范式,在预训练阶段学习到通用的知识和能力,在微调阶段适应特定的任务和场景,在各种领域和场景中都能够展现出惊人的效果。事实上,BERT不仅在文本分类任务上表现优异,还在文本生成、文本摘要、机器翻译、问答系统等任务上刷新了多项记录,成为了自然语言处理领域的一个里程碑技术。

🔗 什么是AI大模型:大规模预训练+微调

✅ Transformer

BERT 乃至目前正火的 LLM 的成功,都离不开 Attention 机制与基于 Attention 机制搭建的 Transformer架构。

在 Attention 机制提出之前,深度学习主要有两种基础架构:卷积神经网络(CNN)与循环神经网络(RNN)。其中,CNN 在CV 领域表现突出,而 RNN 及其变体 LSTM 在 NLP 方向上一枝独秀。然而,RNN 架构存在两个天然缺陷:①序列依序计算的模式限制了计算机并行计算的能力,导致 RNN 为基础架构的模型虽然参数量不算特别大,但计算时间成本却很高。 ② RNN难以捕捉长序列的相关关系。在 RNN 架构中,距离越远的输入之间的关系就越难被捕捉,同时RNN需要将整个序列读入内存依次计算,也限制了序列的长度。

针对上述两个问题, 2017年 Vaswani 等人发表了论文《Attention Is All You Need》,创造性提出了 Attention 机制并完全抛弃了 RNN 架构。Attention机制最先源于计算机视觉领域,其核心思想为当我们关注一张图片,我们往往无需看清楚全部内容而仅将注意力集中在重点部分即可。而在自然语言处理领域,我们往往也可以通过将重点注意力集中在一个或几个token,从而取得更高效高质的计算效果

Attention 机制的特点是通过计算 Query(查询值) 与 Key(键值) 的相关性为真值加权求和,从而拟合序列中每个词同其他词的相关关系。其大致计算过程如图:

具体而言,可以简单理解为一个输入序列通过不同的参数矩阵映射为 Q、K、V 三个矩阵,其中,Q 是计算注意力的另一个句子(或词组),V 为待计算句子,K 为待计算句子中每个词的对应键。通过对 Q 和 K 做点积,可以得到待计算句子(V)的注意力分布(即哪些部分更重要,哪些部分没有这么重要),基于注意力分布对 V 做加权求和即可得到输入序列经过注意力计算后的输出,其中与 Q (即计算注意力的另一方)越重要的部分得到的权重就越高。

✅ Attention

Transformer 基于 Attention 机制搭建了 Encoder-Decoder(编码器-解码器) 结构,主要适用于 Seq2Seq(序列到序列) 任务,即输入是一个自然语言序列,输出也是一个自然语言序列。其整体架构如下:

Transformer 由一个 Encoder,一个 Decoder 外加一个 Softmax 分类器与两层编码层构成。上图中左侧方框为 Encoder,右侧方框为 Decoder。

由于是一个 Seq2Seq 任务,在训练时,Transformer 的训练语料为若干个句对,具体子任务可以是机器翻译、阅读理解、机器对话等。

下图是一个英语与德语的机器翻译任务。在训练时,句对会被划分为输入语料和输出语料,输入语料将从左侧通过编码层进入 Encoder,输出语料将从右侧通过编码层进入 Decoder。Encoder 的主要任务是对输入语料进行编码再输出给 Decoder,Decoder 再根据输出语料的历史信息与 Encoder 的输出进行计算,输出结果再经过一个线性层和 Softmax 分类器即可输出预测的结果概率,整体逻辑如下图:

✅ 预训练任务

BERT 的模型架构直接使用了 Transformer 的 Encoder 作为整体架构,其最核心的思想在于提出了两个新的预训练任务—— MLM(Masked Language Model,掩码模型)NSP(Next Sentence Prediction,下个句子预测),而不是沿用传统的 LM(语言模型)。

MLM 任务,是 BERT 能够深层拟合双向语义特征的基础。简单来讲,MLM 任务即以一定比例对输入语料的部分 token 进行遮蔽,替换为 (MASK)标签,再让模型基于其上下文预测还原被遮蔽的单词,即做一个完形填空任务。由于在该任务中,模型需要针对 (MASK) 标签左右的上下文信息来预测标签本身,从而会充分拟合双向语义信息。

例如,原始输入为 I like you。以30%的比例进行遮蔽,那么遮蔽之后的输入可能为:I (MASK) you。而模型的任务即为基于该输入,预测出 (MASK) 标签对应的单词为 like。

NSP 任务,是 BERT 用于解决句级自然语言处理任务的预训练任务。BERT 完全采用了预训练+微调的范式,因此着重通过预训练生成的模型可以解决各种多样化的下游任务。MLM 对 token 级自然语言处理任务(如命名实体识别、关系抽取等)效果极佳,但对于句级自然语言处理任务(如句对分类、阅读理解等),由于预训练与下游任务的模式差距较大,因此无法取得非常好的效果。NSP 任务,是将输入语料都整合成句对类型,句对中有一半是连贯的上下句,标记为 IsNext,一半则是随机抽取的句对,标记为 NotNext。模型则需要根据输入的句对预测是否是连贯上下句,即预测句对的标签。

例如,原始输入句对可能是 (I like you ; Because you are so good) 以及 (I like you; Today is a nice day)。而模型的任务即为对前一个句对预测 IsNext 标签,对后一个句对预测 NotNext 标签。

基于上述两个预训练任务,BERT 可以在预训练阶段利用大量无标注文本数据实现深层语义拟合,从而取得良好的预测效果。同时,BERT 追求预训练与微调的深层同步,由于 Transformer 的架构可以很好地支持各类型的自然语言处理任务,从而在 BERT 中,微调仅需要在预训练模型的最顶层增加一个 SoftMax 分类层即可。同样值得一提的是,由于在实际下游任务中并不存在 MLM 任务的遮蔽,因此在策略上进行了一点调整,即对于选定的遮蔽词,仅 80% 的遮蔽被直接遮蔽,其余将有 10% 被随机替换,10% 被还原为原单词。

2. 实践项目

同之前博客介绍的,仍然是任务一,文本分类任务。

【NLP】Datawhale-AI夏令营Day3打卡:Bert模型

3. 实践代码

深度学习方法(Bert模型):

由于本地环境出了一些问题,今天重新安装了 Anaconda + Pytorch,稍微调了一下batch_size和epochs,再次跑了一下baseline代码。

代码包括以下几个部分:

1️⃣ 导入模块

导入我们本次Baseline代码所需的模块

2️⃣ 设置全局配置

3️⃣ 数据收集与准备

在赛题主页下载数据,读取数据集,数据预处理(考虑数据扩增)

4️⃣ 构建训练所需的dataloader与dataset

定义dataset: Pytorch 中自定义dataset需要继承 torch.utils.data.Dataset,继承Dataset这个类时,它要求你必须重写 getitem(self, index)、len(self) 两个方法,前者通过提供索引返回数据,也就是提供 DataLoader获取数据的方式;后者返回数据集的长度,DataLoader依据 len 确定自身索引采样器的长度。

而本次教程中,我们在初始化类方法 init 中引入我们的训练数据,重写 getitem(self, index)方法,保证能够取出index对应行的text 与label 值,也就是我们训练需要的数据以及训练希望得到的结果,在 len 方法中,我们需要返回数据集的总长度,这里直接利用Dataframe数据的len()方法就能完成。

构造Dataloader: 在Dataloader中,我们需要利用Dataloader来加载训练数据与训练目标,需要注意的是,加载完成后的数据需要为tensor(张量)形式,因此在下文中我们定义了collate_fn来帮助完成batch组装以及将文本内容向量化,而文本内容向量化这部分内容,我们利用bert模型来完成,而label值的向量化我们直接使用torch.LongTensor()方法完成。

5️⃣ 定义模型

pytorch中定义模型需要继承nn.Module类,其中需要至少定义两个方法,一个是初始化模型结构的方法__init__,另一个方法forward来完成推理流程。

6️⃣ 模型训练、评估

7️⃣ 结果输出

#import 相关库
#导入前置依赖
import os
import pandas as pd
import torch
from torch import nn
from torch.utils.data import Dataset, DataLoader
# 用于加载bert模型的分词器
from transformers import AutoTokenizer
# 用于加载bert模型
from transformers import BertModel
from pathlib import Path
batch_size = 8
# 文本的最大长度
text_max_length = 128
# 总训练的epochs数
epochs = 10
# 学习率
lr = 3e-5
# 取多少训练集的数据作为验证集
validation_ratio = 0.1
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 每多少步,打印一次loss
log_per_step = 50
# 数据集所在位置
dataset_dir = Path("./Dataset")
os.makedirs(dataset_dir) if not os.path.exists(dataset_dir) else ''
# 模型存储路径
model_dir = Path("./model/bert_checkpoints")
# 如果模型目录不存在,则创建一个
os.makedirs(model_dir) if not os.path.exists(model_dir) else ''
print("Device:", device)
# 读取数据集,进行数据处理
pd_train_data = pd.read_csv('./Dataset/train.csv')
pd_train_data['title'] = pd_train_data['title'].fillna('')
pd_train_data['abstract'] = pd_train_data['abstract'].fillna('')
test_data = pd.read_csv('./Dataset/testB.csv')
test_data['title'] = test_data['title'].fillna('')
test_data['abstract'] = test_data['abstract'].fillna('')
pd_train_data['text'] = pd_train_data['title'].fillna('') + ' ' +  pd_train_data['author'].fillna('') + ' ' + pd_train_data['abstract'].fillna('')+ ' ' + pd_train_data['Keywords'].fillna('')
test_data['text'] = test_data['title'].fillna('') + ' ' +  test_data['author'].fillna('') + ' ' + test_data['abstract'].fillna('')+ ' ' + pd_train_data['Keywords'].fillna('')
# 从训练集中随机采样测试集
validation_data = pd_train_data.sample(frac=validation_ratio)
train_data = pd_train_data[~pd_train_data.index.isin(validation_data.index)]
# 构建Dataset
class MyDataset(Dataset):
    def __init__(self, mode='train'):
        super(MyDataset, self).__init__()
        self.mode = mode
        # 拿到对应的数据
        if mode == 'train':
            self.dataset = train_data
        elif mode == 'validation':
            self.dataset = validation_data
        elif mode == 'test':
            # 如果是测试模式,则返回内容和uuid。拿uuid做target主要是方便后面写入结果。
            self.dataset = test_data
        else:
            raise Exception("Unknown mode {}".format(mode))
    def __getitem__(self, index):
        # 取第index条
        data = self.dataset.iloc[index]
        # 取其内容
        text = data['text']
        # 根据状态返回内容
        if self.mode == 'test':
            # 如果是test,将uuid做为target
            label = data['uuid']
        else:
            label = data['label']
        # 返回内容和label
        return text, label
    def __len__(self):
        return len(self.dataset)
train_dataset = MyDataset('train')
validation_dataset = MyDataset('validation')
train_dataset.__getitem__(0)
#获取Bert预训练模型
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
#接着构造我们的Dataloader。
#我们需要定义一下collate_fn,在其中完成对句子进行编码、填充、组装batch等动作:
def collate_fn(batch):
    """
    将一个batch的文本句子转成tensor,并组成batch。
    :param batch: 一个batch的句子,例如: [('推文', target), ('推文', target), ...]
    :return: 处理后的结果,例如:
             src: {'input_ids': tensor([[ 101, ..., 102, 0, 0, ...], ...]), 'attention_mask': tensor([[1, ..., 1, 0, ...], ...])}
             target:[1, 1, 0, ...]
    """
    text, label = zip(*batch)
    text, label = list(text), list(label)
    # src是要送给bert的,所以不需要特殊处理,直接用tokenizer的结果即可
    # padding='max_length' 不够长度的进行填充
    # truncation=True 长度过长的进行裁剪
    src = tokenizer(text, padding='max_length', max_length=text_max_length, return_tensors='pt', truncation=True)
    return src, torch.LongTensor(label)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, collate_fn=collate_fn)
validation_loader = DataLoader(validation_dataset, batch_size=batch_size, shuffle=False, collate_fn=collate_fn)
inputs, targets = next(iter(train_loader))
print("inputs:", inputs)
print("targets:", targets)
#定义预测模型,该模型由bert模型加上最后的预测层组成
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        # 加载bert模型
        self.bert = BertModel.from_pretrained('bert-base-uncased', mirror='tuna')
        # 最后的预测层
        self.predictor = nn.Sequential(
            nn.Linear(768, 256),
            nn.ReLU(),
            nn.Linear(256, 1),
            nn.Sigmoid()
        )
    def forward(self, src):
        """
        :param src: 分词后的推文数据
        """
        # 将src直接序列解包传入bert,因为bert和tokenizer是一套的,所以可以这么做。
        # 得到encoder的输出,用最前面[CLS]的输出作为最终线性层的输入
        outputs = self.bert(**src).last_hidden_state[:, 0, :]
        # 使用线性层来做最终的预测
        return self.predictor(outputs)
model = MyModel()
model = model.to(device)
#定义出损失函数和优化器。这里使用Binary Cross Entropy:
criteria = nn.BCELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
# 由于inputs是字典类型的,定义一个辅助函数帮助to(device)
def to_device(dict_tensors):
    result_tensors = {}
    for key, value in dict_tensors.items():
        result_tensors[key] = value.to(device)
    return result_tensors
#定义一个验证方法,获取到验证集的精准率和loss
def validate():
    model.eval()
    total_loss = 0.
    total_correct = 0
    for inputs, targets in validation_loader:
        inputs, targets = to_device(inputs), targets.to(device)
        outputs = model(inputs)
        loss = criteria(outputs.view(-1), targets.float())
        total_loss += float(loss)
        correct_num = (((outputs >= 0.5).float() * 1).flatten() == targets).sum()
        total_correct += correct_num
    return total_correct / len(validation_dataset), total_loss / len(validation_dataset)
if hasattr(torch.cuda, 'empty_cache'):
  torch.cuda.empty_cache()
# 首先将模型调成训练模式
model.train()
# 清空一下cuda缓存
if torch.cuda.is_available():
    torch.cuda.empty_cache()
# 定义几个变量,帮助打印loss
total_loss = 0.
# 记录步数
step = 0
# 记录在验证集上最好的准确率
best_accuracy = 0
# 开始训练
for epoch in range(epochs):
    model.train()
    for i, (inputs, targets) in enumerate(train_loader):
        # 从batch中拿到训练数据
        inputs, targets = to_device(inputs), targets.to(device)
        # 传入模型进行前向传递
        outputs = model(inputs)
        # 计算损失
        loss = criteria(outputs.view(-1), targets.float())
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()
        total_loss += float(loss)
        step += 1
        if step % log_per_step == 0:
            print("Epoch {}/{}, Step: {}/{}, total loss:{:.4f}".format(epoch+1, epochs, i, len(train_loader), total_loss))
            total_loss = 0
        del inputs, targets
    # 一个epoch后,使用过验证集进行验证
    accuracy, validation_loss = validate()
    print("Epoch {}, accuracy: {:.4f}, validation loss: {:.4f}".format(epoch+1, accuracy, validation_loss))
    torch.save(model, model_dir / f"model_{epoch}.pt")
    # 保存最好的模型
    if accuracy > best_accuracy:
        torch.save(model, model_dir / f"model_best.pt")
        best_accuracy = accuracy
#加载最好的模型,然后进行测试集的预测
model = torch.load(model_dir / f"model_best.pt")
model = model.eval()
test_dataset = MyDataset('test')
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, collate_fn=collate_fn)
results = []
for inputs, ids in test_loader:
    outputs = model(inputs.to(device))
    outputs = (outputs >= 0.5).int().flatten().tolist()
    ids = ids.tolist()
    results = results + [(id, result) for result, id in zip(outputs, ids)]
test_label = [pair[1] for pair in results]
test_data['label'] = test_label
test_data['Keywords'] = test_data['title'].fillna('')
test_data[['uuid', 'Keywords', 'label']].to_csv('submit_task_bert.csv', index=None)

4. 遇到的问题和解决方法

都是小问题

问题1:

显存不足

解决1:

改小 batch_size

关掉正在运行的其他进程

问题2:

ModuleNotFoundError: No module named ‘sklearn’

解决2:

conda install scikit-learn

5. 实践成绩

8.19凌晨 bert模型返回分数:0.89562(batch_size=1, epochs = 10)

8.19 bert模型返回分数:0.99452 (batch_size=8, epochs = 10)

相比之前的机器学习方法,效果显著提升,但运行时间也变长了

8.14 submit_task1.csv 得分:0.67116

第一次测试是基于逻辑回归模型的,没有考虑停用词;

8.16 submit_task_LR.csv 得分:0.67435

第二次测试也是基于逻辑回归模型的,但考虑了停用词;

8.16 submit_task_SVM.csv 得分:0.6778

第三次测试是基于SVM模型的,考虑了停用词;

8.16 submit_task_AdaBoost.csv 得分:0.76263

第四次测试是基于AdaBoost模型的,考虑了停用词;

8.17 尝试了XGBoost模型、GBDT模型,并且稍微调整了一下AdaBoost参数,但效果不行,得分比baseline低了将近0.01,这里就不展示了。

相关文章
|
7月前
|
存储 机器学习/深度学习 算法
​​LLM推理效率的范式转移:FlashAttention与PagedAttention正在重塑AI部署的未来​
本文深度解析FlashAttention与PagedAttention两大LLM推理优化技术:前者通过分块计算提升注意力效率,后者借助分页管理降低KV Cache内存开销。二者分别从计算与内存维度突破性能瓶颈,显著提升大模型推理速度与吞吐量,是当前高效LLM系统的核心基石。建议收藏细读。
1435 125
|
7月前
|
消息中间件 人工智能 运维
事件驱动重塑 AI 数据链路:阿里云 EventBridge 发布 AI ETL 新范式
“一个简单的数据集成任务,开始时总是轻松愉快的,但随着业务扩展,数据源越来越多,格式越来越乱,整个数据链路就会变得一团糟。”陈涛在演讲中指出了当前 AI 数据处理的普遍困境。扩展难、运维难、稳定性差,这三大挑战已成为制约 AI 应用创新和落地的关键瓶颈。针对这些痛点,在2025云栖大会期间,阿里云重磅发布了事件驱动 AI ETL 新范式,其核心产品 EventBridge 通过深度集成 AI 能力,为开发者提供了一套革命性的解决方案,旨在彻底改变 AI 时代的数据准备与处理方式。
732 61
|
6月前
|
SQL 人工智能 机器人
AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建
FastGPT 与 MCP 协议结合,打造工具增强型智能体新范式。MCP 如同 AI 领域的“USB-C 接口”,实现数据与工具的标准化接入。FastGPT 可调用 MCP 工具集,动态执行复杂任务,亦可作为 MCP 服务器共享能力。二者融合推动 AI 应用向协作式、高复用、易集成的下一代智能体演进。
876 0
|
11月前
|
云安全 人工智能 安全
AI 云盾(Cloud Shield for AI)重磅发布,打造安全新范式
提供大模型应用端到端的安全解决方案
3673 48
|
人工智能 Cloud Native 搜索推荐
【2025云栖大会】阿里云AI搜索年度发布:开启Agent时代,重构搜索新范式
2025云栖大会阿里云AI搜索专场上,发布了年度AI搜索技术与产品升级成果,推出Agentic Search架构创新与云原生引擎技术突破,实现从“信息匹配”到“智能问题解决”的跨越,支持多模态检索、百亿向量处理,助力企业降本增效,推动搜索迈向主动服务新时代。
726 0
|
9月前
|
机器学习/深度学习 人工智能 编解码
智谱AI发布新版VLM开源模型GLM-4.1V-9B-Thinking,引入思考范式,性能提升8倍
视觉语言大模型(VLM)已经成为智能系统的关键基石。
1433 0
|
11月前
|
人工智能 JavaScript Devops
云效 MCP Server:AI 驱动的研发协作新范式
云效MCP Server是阿里云云效平台推出的模型上下文协议(Model Context Protocol)标准化接口系统,作为AI助手与DevOps平台的核心桥梁。通过该协议,AI大模型可无缝集成云效DevOps平台,直接访问和操作包括项目管理、代码仓库、工作项等关键研发资产,实现智能化全生命周期管理。其功能涵盖代码仓库管理、代码评审、项目管理和组织管理等多个方面,支持如创建分支、合并请求、查询工作项等具体操作。用户可通过通义灵码内置的MCP市场安装云效MCP服务,并配置个人访问令牌完成集成。实际场景中,AI助手可自动分析需求、生成代码、创建功能分支并提交合并请求,极大提升研发效率。
|
7月前
|
云安全 人工智能 自然语言处理
下一篇
开通oss服务