前言

在掌握了如何使用hugging face上模型的用法包括:如何将模型下载到本地、进行简单的推理,以及如何加载数据集、vocab字典操作,有了模型、数据、编码格式的基本认识后,就可以开始训练定制化的模型,即基于BERT的中文评价情感分析模型。

模型微调的基本概念

微调是指在预训练模型的基础上,通过进一步的训练来适应特定的下游任务。BERT 模型通过预训练来学习语言的通用模式,然后通过微调来适应特定任务,如情感分析、命名实体识别等。微调过程中,通常冻结 BERT 的预训练层,只训练与下游任务相关的层。

准备并载入数据集

情感分析任务的数据通常包括文本及其对应的情感标签。使用 Hugging Face 的 datasets 库可以轻松地加载和处理数据集。

首先下载数据集(需要将整个data文件都下载下来):

链接: https://pan.baidu.com/s/1UWOLi52u-GlKW6D_uMGmmA 提取码: y8a5

数据集的text和label分别是评价的内容、消极或者积极评论的标识

接着载入数据集:

在项目中新建一个python文件并命名MyData用于设计dataset类,使用pytorch进行数据加载:

from torch .utils.data import Dataset
from  datasets import load_from_disk #从磁盘导入数据集

设计dataset类(通过pytorch固定模板):

from torch .utils.data import Dataset
from  datasets import load_from_disk #从磁盘导入数据集


class MyDataset(Dataset):
    #初始化数据
    def __init__(self,spilt):#设置一个参数spilt判断要用数据集干什么
        #加载从磁盘数据
       self.dataset= load_from_disk(r"/home/chn/PycharmProjects/demo_2/data/ChnSentiCorp")
       if spilt=="train":
           self.dataset=self.dataset["train"]
       elif spilt=="validation":   #等于验证集
           self.dataset=self.dataset["validation"]
       elif spilt=="test": #测试集
            self.dataset=self.dataset["test"]
       else:
           print("数据集名称错误!")

    #获取数据集大小
    def __len__(self):
        return len(self.dataset)


    #对数据做定制化处理
    def __getitem__(self, item):#处理自然语义返回数据和标签对
        text=self.dataset[item]["text"]#text从数据集的dataset_info.json里面获得
        label=self.dataset[item]["label"]#同上
        return text,label
#数据示例:  {"text":"诉讼诉讼诉讼诉讼","label":"0"}

if __name__=="__main__":#验证数据集,如果正常加载运行后会输出验证集的内容
    dataset=MyDataset("validation")
    for data in dataset:
        print(data)

正常加载数据集后的输出为:

下游任务模型设计

在微调 BERT 模型之前,需要设计一个适应情感分析任务的下游模型结构。通常包括一个或多个全连接层,用于将 BERT 输出的特征向量转换为分类结果。由于数据只有negative和positive两个分类,所以之后要设计的是二分类的下游模型

模型结构

下游任务模型通常包括以下几个部分:
BERT 模型:用于生成文本的上下文特征向量。
Dropout 层:用于防止过拟合,通过随机丢弃一部分神经元来提高模型的泛化能力。
全连接层:用于将 BERT 的输出特征向量映射到具体的分类任务上。

创建名为net的python文件用于设计模型

#加载预训练模型,由于我指导知道上有上游模型Bert,所以直接调用
from transformers import BertModel
import torch

#定义训练设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

pretrained = BertModel.from_pretrained("/home/chn/PycharmProjects/demo_2/model/bert-base-chinese").to(device)

print(pretrained)

#定义下游任务模型(将主干网络所提取的特征进行分类)
class Model(torch.nn.Module):
    #模型结构设计,将768个特征输入进来,得到两个输出做二分类
    def __init__(self):
        super().__init__()
        self.fc=torch.nn.Linear(768,2)#分类任务用全连接来做,bert输出就是全连接特征

    def forward(self,input_ids,attention_mask,token_type_ids):#前向推理过程,把bert模型的输入输出搞清楚
        #上游任务不参与训练,只需加载
        with torch.no_grad():#权重锁死就能不参与训练,这句话的意思是不进行梯度更新
            out=pretrained(input_ids=input_ids,attention_mask=attention_mask,token_type_ids=token_type_ids)#进行前向计算的参数,得到上游人任务的输出
        #下游任务参与训练
        out=self.fc(out.last_hidden_state[:,0])#传入pretrained输出结果,因为是个序列结果,所以之取最后一层的状态
        out=out.softmax(dim=1)
        return out

模型训练

首先是导入bert模型到GPU上,接着对数据进行编码处理,加载train数据集,使用net中的Model类加载设计好的模型进行训练

import torch
from torch.optim import AdamW

from MyData import MyDataset
from torch.utils.data import DataLoader
from net import Model
from transformers import BertTokenizer,AdamWeightDecay

#定义训练设备
DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
EPOCH=100
token=BertTokenizer.from_pretrained("/home/chn/PycharmProjects/demo_2/model/bert-base-chinese")

#自定义函数,对数据进行编码处理,因为数据集里面都是文本,先处理工作量太大
def collate_fn(data):
    sentes=[i[0] for i in data]
    label =[i[1] for i in data]
    #编码
    data=token.batch_encode_plus(batch_text_or_text_pairs=sentes,
                                 padding="max_length",
                                 truncation=True,
                                 max_length=350,
                                 return_tensors="pt",
                                 return_length=True)
    input_ids=data["input_ids"]
    attention_mask=data["attention_mask"]
    token_type_ids=data["token_type_ids"]
    labels = torch.LongTensor(label)
    return input_ids,attention_mask,token_type_ids,labels

#创建数据集
train_dataset = MyDataset("train")
#创建数据加载器,每次加载32条数据,shuffle打乱数据集
train_loader = DataLoader(train_dataset, batch_size=32,
                          shuffle=True,
                          drop_last=True,
                          collate_fn=collate_fn
                          )

if __name__=="__main__":
    #开始训练
    print(DEVICE)
    model=Model().to(DEVICE)
    optimizer=AdamW(model.parameters(),lr=5e-4)#lr是初始学习率,0.0001
    loss_func=torch.nn.CrossEntropyLoss()

model.train()
for epoch in range(EPOCH):
    for i,(input_ids,attention_mask,token_type_ids,labels) in enumerate(train_loader):
        #将数据放到device上
        input_ids, attention_mask, token_type_ids, labels=input_ids.to(DEVICE),attention_mask.to(DEVICE),token_type_ids.to(DEVICE),labels.to(DEVICE)

    #执行向前计算得到输出
    out=model(input_ids,attention_mask,token_type_ids)

    loss=loss_func(out,labels)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if i%5==0:
        out=out.argmax(dim=1)
        acc=(out==labels).sum().item()/len(labels)
        print(epoch,i,loss.item(),acc)

    #保存模型参数
    torch.save(model.state_dict(),f"params/{epoch}.pt")
    print(epoch,"参数保存成功!")


    if i%5==0:条件判断,表示每处理5个batch后执行一次监控输出(i是当前batch的序号)

    out=out.argmax(dim=1):对模型输出做argmax操作,将预测概率分布转换为预测类别(适用于分类任务),dim=1 表示在特征维度(通常是类别维度)取最大值索引
    acc=(out==labels).sum().item()/len(labels)
    计算当前batch的准确率:
    out==labels 生成布尔张量(预测正确的为True)
    .sum() 统计正确预测的数量
    .item() 将单值张量转为Python数值
    /len(labels) 计算正确率比例

    print(epoch,i,loss.item(),acc)输出监控信息,包含:
    当前epoch序号
    当前batch序号
    当前batch的损失值(.item()转换)
    当前batch的准确率
    典型输出示例:3 15 0.452 0.875 表示第3个epoch的第15个batch,损失值为0.452,准确率87.5%

    正常运行的结果:

    最终效果评估与测试

    在模型训练完成后,需要评估其在测试集上的性能。通常使用准确率、精确率、召回率和 F1 分数等指标来衡量模型的效果。我这里使用精确率来测试。创建名为test的python文件

    test代码与train代码类似,都是固定写法,值得注意的是加载train后保存的模型参数文件(params中)使用model.load_state_dict(torch.load("params/99.pt",)),加载的是第99个训练周期。用acc/total来输出精确率

    import torch
    from torch.optim import AdamW
    
    from MyData import MyDataset
    from torch.utils.data import DataLoader
    from net import Model
    from transformers import BertTokenizer,AdamWeightDecay
    
    #定义训练设备
    DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    EPOCH=100
    token=BertTokenizer.from_pretrained("/home/chn/PycharmProjects/demo_2/model/bert-base-chinese")
    
    #自定义函数,对数据进行编码处理,因为数据集里面都是文本,先处理工作量太大
    def collate_fn(data):
        sentes=[i[0] for i in data]
        label =[i[1] for i in data]
        #编码
        data=token.batch_encode_plus(batch_text_or_text_pairs=sentes,
                                     padding="max_length",
                                     truncation=True,
                                     max_length=350,
                                     return_tensors="pt",
                                     return_length=True)
        input_ids=data["input_ids"]
        attention_mask=data["attention_mask"]
        token_type_ids=data["token_type_ids"]
        labels = torch.LongTensor(label)
        return input_ids,attention_mask,token_type_ids,labels
    
    #创建数据集
    test_dataset = MyDataset("test")
    #创建数据加载器,每次加载32条数据,shuffle打乱数据集
    test_loader = DataLoader(test_dataset, batch_size=32,
                              shuffle=True,
                              drop_last=True,
                              collate_fn=collate_fn
                              )
    
    if __name__=="__main__":
        acc=0#精度变量
        total=0
        #开始测试
        print(DEVICE)
        model=Model().to(DEVICE)#加载模型
        model.load_state_dict(torch.load("params/99.pt",))#加载模型参数
        model.eval()#开启测试模式
        for i,(input_ids,attention_mask,token_type_ids,labels) in enumerate(test_loader):
            input_ids, attention_mask, token_type_ids, labels=input_ids.to(DEVICE),attention_mask.to(DEVICE),token_type_ids.to(DEVICE),labels.to(DEVICE)
    
        out=model(input_ids,attention_mask,token_type_ids)
    
        out = out.argmax(dim=1)
        acc += (out == labels).sum().item()
        total+=len(labels)
    
        print(acc/total)

    正常运行的结果应该是输出

    说明模型效果还不错,不过也和验证集和训练集的数据相关性有关。

    部署到本地使用(从命令行输入语句让模型判断)

    创建一个名为run的python文件做接口让我们在本队使用该模型。test函数就是实现从命令行输入语句让模型判断最后输出结果

    import torch
    from net import Model
    from transformers import BertTokenizer
    
    DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    names=["负向评价","正向评价"]
    print("模型加载到:",DEVICE)
    model=Model().to(DEVICE)
    
    token=BertTokenizer.from_pretrained("/home/chn/PycharmProjects/demo_2/model/bert-base-chinese")
    
    #自定义函数,对数据进行编码处理,因为数据集里面都是文本,先处理工作量太大
    def collate_fn(data):
        sentes=[]
        sentes.append(data)
        #编码
        data=token.batch_encode_plus(batch_text_or_text_pairs=sentes,
                                     padding="max_length",
                                     truncation=True,
                                     max_length=350,
                                     return_tensors="pt",
                                     return_length=True)
        input_ids=data["input_ids"]
        attention_mask=data["attention_mask"]
        token_type_ids=data["token_type_ids"]
    
        return input_ids,attention_mask,token_type_ids
    
    def test():
        model.load_state_dict(torch.load("params/99.pt"))
        model.eval()
        while True:
            data=input("请输入测试数据(按q退出):")
            if data=="q":
                print("测试结束")
                break
            input_ids, attention_mask, token_type_ids = collate_fn(data)
            input_ids, attention_mask, token_type_ids = input_ids.to(DEVICE), attention_mask.to(
                DEVICE), token_type_ids.to(DEVICE)
    
            with torch.no_grad():
                out=model(input_ids,attention_mask,token_type_ids)
                out=out.argmax(dim=1)
                print("模型判定:",names[out],"\n")
    
    if __name__=="__main__":
        test()

    正常运行结果

    总结

    本文档作为一个记录防止遗忘,实现的功能很简单,意义在于了解怎么微调怎么缝合模型

    Logo

    更多推荐