bert实战

引言

  • 背景介绍

    • 简要介绍 BERT(Bidirectional Encoder Representations from Transformers)模型及其在自然语言处理(NLP)中的重要性。

    • 说明本文的目标:通过一个完整的项目(包括 main.pytrain.pydata.pymodel.py),讲解如何使用 BERT 进行文本分类。

  • 项目概述

    • 项目是一个二分类任务(例如情感分析、垃圾邮件分类等)。

    • 使用 Hugging Face 的 transformers 库加载预训练的 BERT 模型。

    • 代码结构清晰,分为数据加载、模型定义、训练和主程序四个部分

代码详解

1. data.py:数据加载与预处理

  • 功能

    • 加载文本数据。

    • 对文本进行分词和编码。

    • 构建数据加载器(DataLoader)。

from torch.utils.data import DataLoader, Dataset
from sklearn.model_selection import train_test_split
import torch

def read_file(path):
    data = []
    label = []
    with open(path, "r", encoding="utf-8") as f:
        for i, line in enumerate(f):
            if i == 0:  # 跳过第一行(文件第一行不是数据)
                continue
            if i > 200 and i < 7500:  # 去掉一些数据,方便测试
                continue
            line = line.strip("\n")  # 去掉换行符
            line = line.split(",", 1)  # 按逗号分割一次
            data.append(line[1])  # 文本数据
            label.append(line[0])  # 标签
    print("读取了%d条数据" % len(data))
    return data, label

class jdDataset(Dataset):
    def __init__(self, data, label):
        self.X = data
        self.Y = torch.LongTensor([int(i) for i in label])  # 标签转为整数

    def __getitem__(self, item):
        return self.X[item], self.Y[item]  # 返回文本和标签

    def __len__(self):
        return len(self.Y)  # 返回数据集大小

def get_data_loader(path, batchsize, val_size=0.2):
    data, label = read_file(path)
    # 分割训练集和验证集
    train_x, val_x, train_y, val_y = train_test_split(data, label, test_size=val_size, shuffle=True, stratify=label)
    # 构建数据集
    train_set = jdDataset(train_x, train_y)
    val_set = jdDataset(val_x, val_y)
    # 构建 DataLoader
    train_loader = DataLoader(train_set, batchsize, shuffle=True)
    val_loader = DataLoader(val_set, batchsize, shuffle=True)
    return train_loader, val_loader

具体步骤:

  1. 读取文件:从指定路径的文件中读取数据。每行数据按逗号分割一次,分割后的第一部分是标签,第二部分是文本数据。
  2. 自定义数据集类jdDataset

    1. self.X:存储文本数据。

    2. self.Y:将标签转换为整数并存储为 PyTorch 的 LongTensor

    3. __getitem__:返回指定索引的文本和标签。

    4. __len__:返回数据集的大小。

  3. 构建数据加载器:

    1. train_test_split:将数据分割为训练集和验证集,test_size=0.2 表示验证集占 20%。

    2. shuffle=True:打乱数据顺序。

    3. stratify=label:确保训练集和验证集的标签分布一致。

    4. jdDataset:将分割后的数据包装为自定义数据集。

    5. DataLoader:构建批量数据加载器,支持批量加载和打乱数据。

2. model.py:模型定义

  • 功能

    • 定义基于 BERT 的文本分类模型。

  • 关键点

    • 使用 BertModel 加载预训练 BERT 模型。

    • 在 BERT 的输出后添加一个全连接层(nn.Linear)用于分类。

    • 使用 BertTokenizer 对输入文本进行分词和编码。

2.1 导入依赖库
import torch
import torch.nn as nn
from transformers import BertModel, BertTokenizer
  • torch 和 torch.nn

    • PyTorch 的核心库,用于构建神经网络。

    • nn.Module 是 PyTorch 中所有神经网络模块的基类。

  • transformers

    • Hugging Face 的 transformers 库,提供了预训练的 BERT 模型和分词器。

    • BertModel:BERT 模型的主体部分。

    • BertTokenizer:用于将文本转换为 BERT 模型所需的输入格式。

2.2 定义 BERT 分类模型
class myBertModel(nn.Module):
    def __init__(self, bert_path, num_class, device):
        super(myBertModel, self).__init__()

        self.bert = BertModel.from_pretrained(bert_path)  # 加载预训练 BERT 模型
        self.device = device
        self.cls_head = nn.Linear(768, num_class)  # 分类头
        self.tokennizer = BertTokenizer.from_pretrained(bert_path)  # 分词器
  • 功能

    • 定义一个基于 BERT 的文本分类模型。

  • 关键点

    • bert_path:预训练 BERT 模型的路径(如 bert-base-chinese)。

    • num_class:分类任务的类别数(如二分类任务为 2)。

    • device:模型运行的设备(如 cuda 或 cpu)。

    • self.bert:加载预训练的 BERT 模型。

    • self.cls_head:在 BERT 的输出后添加一个全连接层(nn.Linear),用于分类。

      • 输入维度为 768(BERT 输出的隐藏层大小)。

      • 输出维度为 num_class(分类类别数)。

    • self.tokennizer:加载与 BERT 模型对应的分词器。

BertTokenizer:这是 transformers 库中专门为 BERT 模型设计的分词器类。它的主要作用是将文本字符串分割成一系列的词元(tokens),并将这些词元转换为对应的整数 ID,这些 ID 可以作为 BERT 模型的输入。

from_pretrained 方法:这是一个类方法,用于从预训练的模型路径加载分词器。bert_path 是预训练 BERT 模型的路径,可以是本地路径,也可以是 Hugging Face 模型库中的模型名称(如 'bert-base-uncased')。通过这个方法,分词器会加载与预训练 BERT 模型相同的词表,确保分词结果与模型的输入要求一致。

2.3 前向传播逻辑
    def forward(self, text):
        input = self.tokennizer(text, return_tensors="pt", truncation=True, padding="max_length", max_length=128)
        input_ids = input["input_ids"].to(self.device)
        token_type_ids = input["token_type_ids"].to(self.device)
        attention_mask = input["attention_mask"].to(self.device)
        sequence_out, pooler_out = self.bert(input_ids=input_ids,
                                             token_type_ids=token_type_ids,
                                             attention_mask=attention_mask,
                                             return_dict=False)  # return_dict

        pred = self.cls_head(pooler_out)
        return pred

定义模型的前向传播逻辑,将输入文本转换为分类结果。

关键点

  • 分词与编码

    • self.tokennizer(text, return_tensors="pt", truncation=True, padding="max_length", max_length=128)

      • 将输入文本分词并转换为 BERT 模型所需的输入格式。

      • return_tensors="pt":返回 PyTorch 张量。

      • truncation=True:如果文本长度超过 max_length,则截断。

      • padding="max_length":如果文本长度不足 max_length,则填充。

      • max_length=128:输入文本的最大长度。

    • 返回的 input 包含以下字段:

      • input_ids:分词后的 token ID。

      • token_type_ids:用于区分句子的 token 类型 ID(单句分类任务中通常为全 0)。

      • attention_mask:用于标识哪些 token 是实际文本,哪些是填充部分。

  • 设备迁移

    • input_ids.to(self.device)token_type_ids.to(self.device)attention_mask.to(self.device)

      • 将输入数据迁移到指定设备(如 GPU)。

  • BERT 模型推理

    • sequence_out, pooler_out = self.bert(...)

      • sequence_out:BERT 模型的所有隐藏层输出(形状为 [batch_size, sequence_length, hidden_size])。

      • pooler_out:BERT 模型的池化输出(形状为 [batch_size, hidden_size]),通常用于分类任务。

  • 分类头

    • pred = self.cls_head(pooler_out)

      • 将 BERT 的池化输出通过全连接层,得到分类结果。

  • 返回结果

    • return pred:返回分类结果。

BertModel 是 Hugging Face transformers 库中预训练的 BERT 模型,当调用 BertModel 的前向传播方法时,它会根据输入的文本数据进行特征提取,并返回一系列的输出。在代码中,通过设置 return_dict=FalseBertModel 会以元组的形式返回输出结果,其中第一个元素是 sequence_out,第二个元素是 pooler_out

sequence_out

  • 含义sequence_out 是 BERT 模型最后一层的隐藏状态(hidden states),它包含了输入序列中每个词元(token)的上下文表示。具体来说,sequence_out 是一个形状为 (batch_size, sequence_length, hidden_size) 的张量。
    • batch_size:表示输入的样本数量。
    • sequence_length:表示输入序列的长度,在代码中通过 max_length=128 进行了限定。
    • hidden_size:表示 BERT 模型的隐藏层维度,对于 bert-base 模型,这个值通常是 768。
  • 用途sequence_out 可以用于需要获取每个词元上下文表示的任务,例如命名实体识别(NER)、词性标注(POS tagging)等。在这些任务中,我们需要对输入序列中的每个词元进行分类,因此可以使用 sequence_out 中每个词元的表示作为特征进行分类。

 pooler_out

  • 含义pooler_out 是 BERT 模型对整个输入序列的一个整体表示。它是通过对 [CLS] 标记的隐藏状态进行线性变换和激活函数处理得到的。[CLS] 标记是 BERT 模型在输入序列开头添加的一个特殊标记,用于表示整个序列的分类信息。pooler_out 是一个形状为 (batch_size, hidden_size) 的张量。
  • 用途pooler_out 通常用于需要对整个输入序列进行分类的任务,例如文本分类、情感分析等。在这些任务中,我们只需要一个表示整个序列的特征向量,因此可以使用 pooler_out 作为输入传递给后续的分类器(如代码中的 self.cls_head)进行分类。

这里我们使用 pooler_out 作为输入传递给 self.cls_head 进行分类 

3. train.py 文件详解

  • train.py 文件的主要功能是:

    • 定义训练和验证的逻辑。

    • 计算损失和准确率。

    • 保存最佳模型。

    • 绘制训练和验证的损失和准确率曲线。

3.1 导入依赖库
import torch
import time
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm
  • torch:PyTorch 的核心库,用于张量操作和模型训练。

  • time:用于计算训练时间。

  • matplotlib.pyplot:用于绘制损失和准确率曲线。

  • numpy:用于数值计算。

  • tqdm:用于显示训练进度条。

3.2 定义训练和验证函数
def train_val(para):
    model = para['model']
    train_loader = para['train_loader']
    val_loader = para['val_loader']
    scheduler = para['scheduler']
    optimizer = para['optimizer']
    loss = para['loss']
    epoch = para['epoch']
    device = para['device']
    save_path = para['save_path']
    max_acc = para['max_acc']
    val_epoch = para['val_epoch']

 从参数字典 para 中提取模型、数据加载器、优化器、损失函数等参数。

  • 关键点

    • model:训练和验证的模型。

    • train_loader 和 val_loader:训练集和验证集的数据加载器。

    • scheduler:学习率调度器。

    • optimizer:优化器。

    • loss:损失函数。

    • epoch:训练的总轮数。

    • device:模型运行的设备(如 cuda 或 cpu)。

    • save_path:模型保存路径。

    • max_acc:模型保存的准确率阈值。

    • val_epoch:每隔多少轮进行一次验证。

3.3 初始化变量
    plt_train_loss = []
    plt_train_acc = []
    plt_val_loss = []
    plt_val_acc = []
    val_rel = []

初始化用于存储训练和验证的损失和准确率的列表。

  • plt_train_loss 和 plt_train_acc:存储训练集的损失和准确率。

  • plt_val_loss 和 plt_val_acc:存储验证集的损失和准确率。

  • val_rel:存储验证结果。

3.4 训练循环
    for i in range(epoch):
        start_time = time.time()
        model.train()
        train_loss = 0.0
        train_acc = 0.0
        for batch in tqdm(train_loader):
            model.zero_grad()
            text, labels = batch[0], batch[1].to(device)
            pred = model(text)
            bat_loss = loss(pred, labels)
            bat_loss.backward()
            optimizer.step()
            scheduler.step()  # 调整学习率
            optimizer.zero_grad()
            torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)  # 梯度裁剪
            train_loss += bat_loss.item()
            train_acc += np.sum(np.argmax(pred.cpu().data.numpy(), axis=1) == labels.cpu().numpy())

训练模型,计算损失和准确率。

  • 关键点

    • model.train():将模型设置为训练模式。

    • model.zero_grad():清空梯度。

    • text, labels = batch[0], batch[1].to(device):将数据迁移到指定设备。

    • pred = model(text):前向传播,得到预测结果。

    • bat_loss = loss(pred, labels):计算损失。

    • bat_loss.backward():反向传播,计算梯度。

    • optimizer.step():更新模型参数。

    • scheduler.step():调整学习率。

    • torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0):梯度裁剪,防止梯度爆炸。

    • train_loss += bat_loss.item():累加训练损失。

    • train_acc += np.sum(np.argmax(pred.cpu().data.numpy(), axis=1) == labels.cpu().numpy()):计算训练准确率。

3.5 验证循环
        if i % val_epoch == 0:
            model.eval()
            val_loss = 0.0
            val_acc = 0.0
            with torch.no_grad():
                for batch in tqdm(val_loader):
                    val_text, val_labels = batch[0], batch[1].to(device)
                    val_pred = model(val_text)
                    val_bat_loss = loss(val_pred, val_labels)
                    val_loss += val_bat_loss.cpu().item()
                    val_acc += np.sum(np.argmax(val_pred.cpu().data.numpy(), axis=1) == val_labels.cpu().numpy())
                    val_rel.append(val_pred)

            if val_acc > max_acc:
                torch.save(model, save_path + str(epoch) + "ckpt")
                max_acc = val_acc
            plt_val_loss.append(val_loss / len(val_loader.dataset))
            plt_val_acc.append(val_acc / len(val_loader.dataset))
            print('[%03d/%03d] %2.2f sec(s) TrainAcc : %3.6f TrainLoss : %3.6f | ValAcc: %3.6f ValLoss: %3.6f' %
                  (i, epoch, time.time() - start_time, plt_train_acc[-1], plt_train_loss[-1], plt_val_acc[-1], plt_val_loss[-1]))
        else:
            plt_val_loss.append(plt_val_loss[-1])
            plt_val_acc.append(plt_val_acc[-1])
            print('[%03d/%03d] %2.2f sec(s) TrainAcc : %3.6f TrainLoss : %3.6f' %
                  (i, epoch, time.time() - start_time, plt_train_acc[-1], plt_train_loss[-1]))

 验证模型,计算验证损失和准确率。

  • 关键点

    • model.eval():将模型设置为验证模式。

    • with torch.no_grad():禁用梯度计算,减少内存消耗。

    • val_loss += val_bat_loss.cpu().item():累加验证损失。

    • val_acc += np.sum(np.argmax(val_pred.cpu().data.numpy(), axis=1) == val_labels.cpu().numpy()):计算验证准确率。

    • if val_acc > max_acc:如果验证准确率超过阈值,则保存模型。

    • plt_val_loss.append(val_loss / len(val_loader.dataset)):存储验证损失。

    • plt_val_acc.append(val_acc / len(val_loader.dataset)):存储验证准确率。

3.6 绘制曲线
    plt.plot(plt_train_loss)
    plt.plot(plt_val_loss)
    plt.title('Loss')
    plt.legend(['Train', 'Val'])
    plt.show()

    plt.plot(plt_train_acc)
    plt.plot(plt_val_acc)
    plt.title('Accuracy')
    plt.legend(['Train', 'Val'])
    plt.savefig('acc.png')
    plt.show()

绘制训练和验证的损失和准确率曲线。

  • 关键点

    • plt.plot(plt_train_loss):绘制训练损失曲线。

    • plt.plot(plt_val_loss):绘制验证损失曲线。

    • plt.legend(['Train', 'Val']):添加图例。

    • plt.savefig('acc.png'):保存准确率曲线图。

4. main.py 文件详解

4.1 导入依赖库
import os
import random
import numpy as np
import torch.nn as nn
import torch

from model_utils.data import get_data_loader
from model_utils.model import myBertModel
from model_utils.train import train_val
  • os:用于操作系统相关的功能,如设置环境变量。
  • random 和 numpy:用于设置随机种子,确保实验的可重复性。

  • torch 和 torch.nn:PyTorch 的核心库,用于构建神经网络。

  • get_data_loader:从 model_utils.data 中导入的数据加载函数。

  • myBertModel:从 model_utils.model 中导入的自定义 BERT 模型。

  • train_val:从 model_utils.train 中导入的训练和验证函数。

4.2 设置随机种子
def seed_everything(seed):
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    torch.backends.cudnn.benchmark = False
    torch.backends.cudnn.deterministic = True
    random.seed(seed)
    np.random.seed(seed)
    os.environ['PYTHONHASHSEED'] = str(seed)

seed_everything(0)

设置随机种子,确保实验的可重复性。

  • 关键点

    • torch.manual_seed(seed):设置 PyTorch 的随机种子。

    • torch.cuda.manual_seed(seed) 和 torch.cuda.manual_seed_all(seed):设置 GPU 的随机种子。

    • torch.backends.cudnn.benchmark = False 和 torch.backends.cudnn.deterministic = True:禁用 CuDNN 的随机性,确保结果可重复。

    • random.seed(seed) 和 np.random.seed(seed):设置 Python 和 NumPy 的随机种子。

    • os.environ['PYTHONHASHSEED'] = str(seed):设置 Python 的哈希种子。

4.3 设置超参数
lr = 0.0001
batchsize = 8
loss = nn.CrossEntropyLoss()

bert_path = "bert-base-chinese"
num_class = 2
data_path = "jiudian.txt"
device = "cuda" if torch.cuda.is_available() else "cpu"
max_acc = 0.6

设置训练的超参数。

  • 关键点

    • lr:学习率,设置为 0.0001

    • batchsize:批量大小,设置为 8

    • loss:损失函数,使用交叉熵损失(nn.CrossEntropyLoss)。

    • bert_path:预训练 BERT 模型的路径,这里使用的是 bert-base-chinese

    • num_class:分类任务的类别数,设置为 2(二分类任务)。

    • data_path:数据文件的路径,这里是 jiudian.txt

    • device:设备选择,如果有 GPU 则使用 GPU(cuda),否则使用 CPU。

    • max_acc:模型保存的准确率阈值,初始设置为 0.6

4.4 初始化模型
model = myBertModel(bert_path, num_class, device).to(device)

初始化基于 BERT 的文本分类模型。

  • 关键点

    • 调用 myBertModel 类,传入 BERT 模型路径、类别数和设备,将模型加载到指定设备上。

4.5 初始化优化器
optimizer = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=0.00001)
  • 关键点

    • 使用 AdamW 优化器,传入模型参数、学习率和权重衰减系数。

4.6 加载数据
train_loader, val_loader = get_data_loader(data_path, batchsize)

加载训练集和验证集的数据加载器。

  • 关键点

    • 调用 get_data_loader 函数,传入数据路径和批量大小,返回训练集和验证集的 DataLoader


4.7 设置学习率调度器
scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=20, eta_min=1e-9)

设置学习率调度器。

  • 关键点

    • 使用余弦退火调度器(CosineAnnealingWarmRestarts),周期为 20,最小学习率为 1e-9

4.8 打包参数
val_epoch = 1
para = {
    "model": model,
    "train_loader": train_loader,
    "val_loader": val_loader,
    "scheduler": scheduler,
    "optimizer": optimizer,
    "loss": loss,
    "epoch": epochs,
    "device": device,
    "save_path": save_path,
    "max_acc": max_acc,
    "val_epoch": val_epoch  # 训练多少轮验证一次
}

将模型、数据加载器、优化器、损失函数等参数打包到一个字典 para 中,方便传递给训练函数。

  • 关键点

    • val_epoch:每隔多少轮进行一次验证。

4.9 启动训练
train_val(para)
  • 功能

    • 调用 train_val 函数,传入参数字典 para,开始训练和验证模型。

关键点总结
  • 超参数设置

    • 包括学习率、批量大小、损失函数、设备选择等。

  • 模型初始化

    • 使用预训练的 BERT 模型,并添加一个全连接层用于分类。

  • 数据加载

    • 使用 get_data_loader 函数加载训练集和验证集。

  • 优化器和调度器

    • 使用 AdamW 优化器和余弦退火调度器。

  • 训练启动

    • 调用 train_val 函数,开始训练和验证模型。

### BERT项目介绍 BERT (Bidirectional Encoder Representations from Transformers) 是一种用于自然语言处理(NLP)的预训练模型,能够显著改善各种下游任务的表现。该模型解决了传统单向语言模型无法捕捉上下文双向依赖关系的问题,通过采用Transformer架构中的编码器部分并对其进行改进,实现了对输入序列中每个token在其前后位置上的全面理解[^1]。 为了使BERT适应特定的任务需求,在完成大规模无标注语料库上的预训练之后还需要进行微调阶段。这一过程涉及调整最后一层或者几层网络权重以匹配目标任务的数据分布特征。例如,在分类任务中可以简单地添加一个线性变换加softmax激活函数;而在问答系统里则可能需要设计专门结构来提取答案片段的位置信息。 #### 参与方式 对于有兴趣参与到BERT相关工作的个人来说,可以从以下几个方面入手: - **研究方向探索**:关注最新的学术论文和技术博客文章,了解当前存在的挑战以及潜在的研究课题。比如如何进一步压缩模型体积而不损失太多精度、怎样增强跨语言迁移能力等问题都值得深入探讨。 - **开源社区贡献**:许多基于BERT构建的应用程序都是完全公开源码发布的,这意味着任何人都有机会为其添砖加瓦。可以通过修复bug、编写文档教程或是提交新的功能请求等方式来进行实质性帮助。特别是像Hugging Face这样的平台提供了大量实用工具包和支持服务,非常适合初学者入门实践[^4]。 - **实际案例分析**:尝试利用现有的框架搭建属于自己的解决方案,并分享经验心得给其他爱好者们参考借鉴。这不仅有助于巩固所学知识体系,还能建立起良好的人脉圈子以便未来合作交流。 ```bash pip install torch pip install transformers ``` 上述命令可以帮助安装必要的软件环境,从而顺利开展实验工作[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值