如果看不懂这一块可以移步深度学习板块先去了解基础。
尽可能使用比较好的硬件,
模块介绍
首先理解代码中使用的模块,可以更快的上手,省去了查询的时间。
transformers
BertTokenizer
BertTokenizer
是用于将文本转换为BERT模型可以接受的输入格式的工具类。它负责将原始文本转换成BERT模型所需的token IDs,并进行必要的预处理,如分词、添加特殊标记等。
BertTokenizer
的作用类似于自然语言处理任务中的数据预处理步骤,将原始文本转换为模型可以处理的数值表示形式。
BertForSequenceClassification
BertForSequenceClassification
是一个预训练的BERT模型,在BERT的基础上添加了用于序列分类任务的额外输出层。它可以用于文本分类任务,例如情感分析、文本分类等。
与普通的BERT模型相比,
BertForSequenceClassification
在最后一层输出的基础上,增加了一个用于分类的线性层(通常是一个全连接层)。在训练过程中,
BertForSequenceClassification
会根据任务的标签,利用监督学习的方法来调整模型的参数,使其适应特定的分类任务。
AdamW
这就是我们深度学习中所提到的权重,这个可以帮助我们自动的调整权重。点我去学习
AdamW
是一个优化器,是Adam优化器的一个变体,添加了权重衰减(weight decay)的功能。权重衰减通常用于减少模型的过拟合程度,通过对权重进行惩罚来防止它们过大。
AdamW
在BERT等预训练模型的微调过程中经常被使用。
get_linear_schedule_with_warmup
这是我们深度学习中提到的学习率,它可以动态的调整学习率。点我去学习
get_linear_schedule_with_warmup
是一个学习率调度器,用于动态调整学习率。在训练过程中,学习率通常会随着训练的进行而逐渐衰减,以帮助模型更好地收敛。
get_linear_schedule_with_warmup
会在训练初期使用一个较高的学习率,并在预定的预热期间(warm-up period)内逐渐将学习率线性地减小到预定的最小值。
torch.utils.data
Dataset
Dataset
是PyTorch中的一个抽象类,用于表示数据集。所有自定义数据集都应该继承自
Dataset
类,并实现__len__
和__getitem__
方法。__len__
方法返回数据集的长度(即样本数量),__getitem__
方法根据给定的索引返回对应的数据样本。
DataLoader
DataLoader
是PyTorch中用于批量加载数据的类。它接受一个数据集(通常是
Dataset
的子类)作为输入,并允许使用多线程和进程加载数据,以加快训练过程。DataLoader
可以根据指定的批次大小自动将数据划分成小批量,并在训练过程中迭代地提供数据。
TensorDataset
TensorDataset
是Dataset
的一个实现,用于将张量(tensor)组合成一个数据集。在实际应用中,经常将输入数据张量和标签张量组合成一个
TensorDataset
,以便用于模型的训练和验证。TensorDataset
的一个常见用法是将训练数据和对应的标签组合成一个数据集,然后通过DataLoader
进行批量加载。
任务
我们需要根据头条标题对文章进行分类。
项目地址:点我前往
注释版本代码及项目中使用的数据下载:点我下载
代码解析
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
import codecs
from transformers import BertTokenizer
from transformers import BertForSequenceClassification, AdamW, get_linear_schedule_with_warmup
# 标签
news_label = [int(x.split('_!_')[1])-100
for x in codecs.open('toutiao_cat_data.txt',encoding='utf-8')]
# print(news_label)
# 文本
news_text = [x.strip().split('_!_')[-1] if x.strip()[-3:] != '_!_' else x.strip().split('_!_')[-2]
for x in codecs.open('toutiao_cat_data.txt',encoding='utf-8')]
# print(news_text)
# news_text -- 特征集
# train_target -- 样本结果
# test_size -- 样本占比,如果是整数的话就是样本的数量
# stratify=news_label[:] 保持训练集关于标签的比例与测试集相同
# 分割训练集
x_train, x_test, train_label, test_label = train_test_split(news_text[:],
news_label[:], test_size=0.2, stratify=news_label[:])
# 将训练集和测试集输入模型
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
# 参数1:集合
# 参数2:是否删除超出长度部分
# 参数3:是否填充不足长度的部分
# 参数4:最大长度
train_encoding = tokenizer(x_train, truncation=True, padding=True, max_length=64)
test_encoding = tokenizer(x_test, truncation=True, padding=True, max_length=64)
# 数据集类
class NewsDataset(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)
# 创建训练数据集
train_dataset = NewsDataset(train_encoding, train_label)
# 创建测试数据集
test_dataset = NewsDataset(test_encoding, test_label)
# 始化了一个 BERT 模型,用于序列分类任务。它加载了一个在中文文本数据上预训练过的 BERT 模型(bert-base-chinese),并将其配置为具有17个标签的分类任务。100-116
model = BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=17)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
# 单个读取到批量读取
train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=16, shuffle=True)
# batch_size 每个批次样本含量
# shuffle是否随机
# 优化方法
# PyTorch 的 BERT 微调教程 https://xungejiang.com/2020/06/06/BERT/
optim = AdamW(model.parameters(), lr=2e-5)
# AdamW是一个优化器,用于更新模型的参数以减小损失函数。
# 在这里,model.parameters()指定了需要优化的模型参数,即神经网络中的权重和偏置。
# lr=2e-5指定了学习率,即每次参数更新的步长。在这里,学习率被设置为2e-5。
total_steps = len(train_loader) * 1 #总批次的数量*1
scheduler = get_linear_schedule_with_warmup(optim,
num_warmup_steps = 0, # Default value in run_glue.py
num_training_steps = total_steps)
# get_linear_schedule_with_warmup是一个学习率调度器,用于动态调整学习率。
# optim参数指定了要调度学习率的优化器。
# num_warmup_steps=0表示预热期间的步数,即在训练初期学习率逐渐增加的步数。在这里,预热期间的步数被设置为0,意味着不使用预热。
# num_training_steps=total_steps指定了总的训练步数,即模型将进行多少步的参数更新。在这里,总的训练步数被设置为train_loader中的总批次数乘以1。
# 训练函数
def train():
# 将bert模型设置为训练模式
model.train()
# 设置损失率为0
total_train_loss = 0
# 当前批次
iter_num = 0
# 样本总数量
total_iter = len(train_loader)
for batch in train_loader:
# 正向传播
# 清除优化器中之前批次的梯度信息。
optim.zero_grad()
# 从当前批次中获取输入数据(input_ids、attention_mask)和标签(labels),并将它们移到指定的设备上。
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['labels'].to(device)
# 通过BERT模型进行前向传播,得到模型的输出。
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)
# 参数更新
optim.step()
scheduler.step()
iter_num += 1
if(iter_num % 1==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()
# 初始化总评估准确率和总评估损失为0
total_eval_accuracy = 0
total_eval_loss = 0
# 对验证数据集进行迭代,每次迭代一个批次的数据
for batch in test_dataloader:
# 在评估过程中不需要计算梯度,所以在这个上下文中的所有操作都不会被记录梯度,节省了内存和计算资源。
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(test_dataloader)
print("Accuracy: %.4f" % (avg_val_accuracy))
print("Average testing loss: %.4f"%(total_eval_loss/len(test_dataloader)))
print("-------------------------------")
for epoch in range(4):
print("------------Epoch: %d ----------------" % epoch)
train()
validation()