【Transformers】英文文本分类

本文展示了如何在GPU环境下利用PyTorch和transformers库训练BERT模型,包括数据预处理、模型构建、训练过程以及模型评估。代码涵盖了从CSV文件加载数据、构建BERT输入格式、分词、固定序列长度、数据集划分、模型训练和验证,以及学习率调度等关键步骤。
摘要由CSDN通过智能技术生成
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import io
from sklearn.model_selection import train_test_split
from tensorflow.keras.preprocessing.sequence import pad_sequences

import torch
from torch.utils.data import DataLoader, TensorDataset, RandomSampler, SequentialSampler
from torch.optim.lr_scheduler import ExponentialLR

from transformers import BertTokenizer, BertConfig, BertModel
from transformers import AdamW, BertForSequenceClassification, get_linear_schedule_with_warmup

from tqdm import tqdm, trange

import warnings
warnings.filterwarnings("ignore")

使用 GPU。

# 检查本机GPU是否可用
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# GPU型号
torch.cuda.get_device_name()

我电脑真跑不了,果断用了 Colab,真的好用,感谢 Google。学生一般用不起这样的 GPU 吧🐶,推荐大家使用,省时又省心。

加载数据集。

# 读取数据

df = pd.read_csv("./train.tsv", delimiter='\t', header=None, names=['label', 'sentence'])
df.head()

构建 BERT 的输入语料格式。

# 语料
sentences = df.sentence.values

# BERT的输入语料格式: [CLS] + sentence + [SEP]
sentences = ["[CLS] " + sen + " [SEP]" for sen in sentences]

sentences[:5]
# 标签
labels = df.label.values

获取 BERT 的分词器 tokenizer 进行分词和转换。

# 1 从 hugging face 获取预训练分词模型
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased", do_lower_case=True) # 全部转换为小写

# 2 对语料进行分词
sentence_tokens = [tokenizer.tokenize(sen) for sen in sentences]

# 3 将token转换为id,并固定每个语句的长度
max_len = 128
sentence_ids = [tokenizer.convert_tokens_to_ids(sen) for sen in sentence_tokens]

# 4 将所有语句的长度固定到 max_len
sentence_ids = pad_sequences(sentence_ids, maxlen=max_len, dtype='long', truncating='post', padding='post')

print(sentence_ids[0])
# 5 根据 sentence_ids 创建 attention mask
attention_mask = [[1 if id > 0 else 0 for id in sen] for sen in sentence_ids]
print(attention_mask[0])
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 0]

数据集进行分离:train 和 eval。

X_train, X_eval, y_train, y_eval = train_test_split(sentence_ids, labels, test_size=0.2, random_state=666)

train_masks, eval_masks, _, _ = train_test_split(attention_mask, sentence_ids, test_size=0.2, random_state=666)

第二段代码,train_test_split 返回了四个变量,分别是train_maskseval_masks__。其中 train_maskseval_masks 是训练集和验证集的 attention_mask,用于指示输入序列的哪些部分是真正的输入。__ 是被忽略的变量,因为在这里我们不需要使用它们。

数据类型转换,全部转换为 tensor。

X_train = torch.tensor(X_train)
X_eval = torch.tensor(X_eval)
y_train = torch.tensor(y_train)
y_eval = torch.tensor(y_eval)
train_masks = torch.tensor(train_masks)
eval_masks = torch.tensor(eval_masks)

数据打包,便于进行批量训练(PyTorch的基本操作)。

batch_size = 32

# 1 训练集

# 数据打包
train_dataset = TensorDataset(X_train, train_masks, y_train)

# 一批一批地读取
train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)


#2 验证集

# 数据打包
eval_dataset = TensorDataset(X_eval, eval_masks, y_eval)

# 一批一批地读取
eval_dataloader = DataLoader(eval_dataset, batch_size=batch_size, shuffle=True)

模型训练。

model = BertForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=2)
model.cuda()

model.cuda() 是将 PyTorch 模型移动到 GPU 设备上进行加速计算的方法之一。在 PyTorch 中,所有的计算都是在 Tensor 上进行的。通过将 Tensor 移动到 GPU 上,可以利用 GPU 并行计算的能力,从而大幅提高计算速度。在使用 PyTorch 模型进行训练和推理时,通常需要将输入数据和模型参数移动到 GPU 上,并使用 model.cuda() 将模型移动到 GPU 上进行计算。如果想将模型从 GPU 移回 CPU,可以使用 model.cpu()

# 训练轮次

EPOCHS = 10

# 优化器

optimizer = AdamW(model.parameters(), lr=2e-5, eps=1e-8)

# 学习率

scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=0, num_training_steps=len(train_dataloader)*EPOCHS)

使用 get_linear_schedule_with_warmup() 函数为神经网络优化器设置学习率,用于创建一个学习率调度器。optimizer 参数是要调整学习率的优化器对象。

num_warmup_steps 参数确定在训练开始时要使用多少个步骤进行热身。在热身期间,学习率会逐渐从0增加到优化器设置的初始学习率。在这段代码中,num_warmup_steps的值为0,这意味着没有热身期。

num_training_steps 参数指定模型的总训练步骤数,这是通过将一个epoch中的批次数(len(train_dataloader))乘以epoch数(EPOCHS)来计算的。在热身期间,学习率会线性增加,然后在训练结束时线性减少到0。

总体而言,使用学习率调度器的目的是在训练过程中调整模型的学习率,以提高其收敛和泛化性能。get_linear_schedule_with_warmup()函数是在自然语言处理(NLP)任务中常用的学习率调度方法之一。

定义 accuracy。

# 计算 真值labels 与 预测值preds 的 accuracy

def accuracy(labels, preds):
    preds = np.argmax(preds, axis=1).flatten() # shape = (1, :)
    labels = labels.flatten()
    acc = np.sum(preds == labels) / len(preds) # 准确率
    return acc
# 模型训练


train_loss = []


for i in tqdm(range(EPOCHS), desc='Epoch'):
    
    model.train()  # 定义训练模式
    
    tr_loss = 0
    tr_examples = 0  # 样本量
    tr_steps = 0
    
    for i, batch_data in enumerate(train_dataloader):
        # 一批一批的读取数据
        batch_data = tuple(data.to(device) for data in batch_data) # 部署到gpu
        # 解析,元组分包,一一顺序对应 
        inputs_ids, inputs_masks, inputs_labels = batch_data
        # 梯度置零
        optimizer.zero_grad()
        # 前向传播,模型的输出有两个结果
        outputs = model(inputs_ids, token_type_ids=None, attention_mask=inputs_masks, labels=inputs_labels)
        #print("keys : ", outputs.keys())  # ['loss', 'logits']
        # 获取loss
        loss = outputs['loss']
        # 保存loss,记录loss的变化过程
        train_loss.append(loss.item())
        # 累计loss,方便计算平均 loss
        tr_loss += loss.item()
        # 累计样本量
        tr_examples += inputs_ids.size(0)
        # 多少批次batch
        tr_steps += 1
        # 反向传播
        loss.backward()
        # 更新优化器参数
        optimizer.step()
        # 更新学习率
        scheduler.step()
        
    
    print("Training loss : {}".format(tr_loss / tr_steps))
    # 计算平均损失
    
    # 定义模型验证模式
    model.eval()
    eval_acc = 0.0
    eval_steps, eval_examples = 0.0, 0.0  # 轮数和样本数
    
    for batch in eval_dataloader:
        # 部署到GPU
        batch = tuple(data.to(device) for data in batch_data)
        # 解析
        inputs_ids, inputs_masks, inputs_labels = batch
        # 验证阶段不需要计算梯度
        with torch.no_grad():
            preds = model(inputs_ids, token_type_ids=None, attention_mask=inputs_masks)
        # 将 preds 和 labels 部署到cpu计算
        preds = preds['logits'].detach().to('cpu').numpy() # 将preds从计算图剥离,不计算梯度
        labels = inputs_labels.to('cpu').numpy()
        # 计算 accuracy,累加,计算平均准确率
        eval_acc += accuracy(labels, preds)
        
        eval_steps += 1
    
    print("Eval Accuracy : {}".format(eval_acc / eval_steps))
    
    print("\n\n")

训练了10轮,train 了24分钟,验证集准确率100%。

# 可视化 train loss
plt.figure(figsize=(12,10))
plt.title("Training Loss")
plt.xlabel("Batch")
plt.ylabel("Loss")
plt.plot(train_loss)
plt.show()

模型测试(首先是和训练数据集相同的数据预处理)。

# 加载测试集
df = pd.read_csv("./test.tsv", delimiter='\t', header=None, names=['label', 'sentence'])

sentences = df.sentence.values 
labels = df.label.values 

# 构造输入格式
sentences = ["[CLS] " + sen + " [SEP]" for sen in sentences]

# 分词
sentences_tokens = [tokenizer.tokenize(sen) for sen in sentences]

# token --> id
sentence_ids = [tokenizer.convert_tokens_to_ids(sen) for sen in sentences_tokens]

# 将所有语句的长度固定到 max_len
sentence_ids = pad_sequences(sentence_ids, maxlen=max_len, dtype='long', truncating='post', padding='post')

# 根据 sentence_ids 创建 attention mask
attention_mask = [[1 if id > 0 else 0 for id in sen] for sen in sentence_ids]

# 数据类型转换,全部转换为 tensor
sentence_ids = torch.tensor(sentence_ids)
attention_mask = torch.tensor(attention_mask)
labels = torch.tensor(labels)

# 数据打包
test_dataset = TensorDataset(sentence_ids, attention_mask, labels)

# 一批一批地读取数据
test_dataloader = DataLoader(test_dataset, batch_size, shuffle=True)

定义模型测试模式。

# 定义模型测试模式

model.eval()

test_loss, test_acc = 0.0, 0.0
steps = 0
num = 0

for batch in test_dataloader:
    # 部署到 GPU
    batch = tuple(data.to(device) for data in batch)
    # 数据解包
    inputs_ids, inputs_masks, inputs_labels = batch
    # 验证阶段不需要计算梯度
    with torch.no_grad():
        preds = model(inputs_ids, token_type_ids=None, attention_mask=inputs_masks) # 模型预测
    # 将preds从计算图剥离,不计算梯度
    preds = preds['logits'].detach().to('cpu').numpy()
    inputs_labels = inputs_labels.to('cpu').numpy()
    # 计算 acc
    acc = accuracy(inputs_labels, preds)
    # 累计 acc
    test_acc += acc
    # 累计轮次
    steps += 1
    # 累计 test 样本数量
    num += len(inputs_ids)
    
print("steps = ", steps)
print("test number = ", num)
print("test acc : {}".format(test_acc / steps))

效果一般,可能是数据比较小,发挥不了 BERT 的真实实力。





种一棵树最好的时间是十年前,其次是现在。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沉淀体育生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值