代码举例
代码1
py
import numpy as np
import torch
from datasets import load_dataset
from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments
from sklearn.metrics import accuracy_score
# 加载数据集
dataset = load_dataset("stanfordnlp/imdb", cache_dir="./dataset/IMDB")
# 加载预训练的 RoBERTa 分词器
tokenizer = AutoTokenizer.from_pretrained("roberta-base")
# 数据预处理
def tokenize_function(examples):
return tokenizer(examples["text"], padding="max_length", truncation=True, max_length=512)
# Tokenize 数据集
tokenized_dataset = dataset.map(tokenize_function, batched=True)
# 转换数据格式为 PyTorch 格式
tokenized_dataset = tokenized_dataset.map(lambda x: {"labels": x["label"]}, batched=True)
tokenized_dataset.set_format(type="torch", columns=["input_ids", "attention_mask", "labels"])
# 划分训练集和测试集
train_dataset = tokenized_dataset["train"]
test_dataset = tokenized_dataset["test"]
# 加载预训练的 RoBERTa 模型
model = AutoModelForSequenceClassification.from_pretrained("roberta-base", num_labels=2)
# 定义评估指标
def compute_metrics(eval_pred):
logits, labels = eval_pred
predictions = np.argmax(logits, axis=-1)
acc = accuracy_score(labels, predictions)
return {"accuracy": acc}
# 设置训练参数
training_args = TrainingArguments(
output_dir="./results",
evaluation_strategy="epoch", # 每个epoch进行一次评估
save_strategy="epoch", # 每个epoch保存模型
learning_rate=2e-5,
per_device_train_batch_size=16,
per_device_eval_batch_size=16,
num_train_epochs=1,
weight_decay=0.01,
logging_dir="./logs",
logging_steps=10,
save_total_limit=2,
load_best_model_at_end=True, # 训练结束时加载最佳模型
metric_for_best_model="accuracy",
greater_is_better=True,
report_to="none", # 避免与其他工具自动集成
)
# 创建 Trainer 实例
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=test_dataset,
tokenizer=tokenizer,
compute_metrics=compute_metrics,
)
# 开始训练
trainer.train()
# 测试集评估
results = trainer.evaluate()
print(f"Test Accuracy: {results['eval_accuracy']}")
# 推理函数
def predict_sentiment(text):
inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True, max_length=512)
outputs = model(**inputs)
predictions = torch.argmax(outputs.logits, dim=-1)
return "Positive" if predictions == 1 else "Negative"
# 测试推理
example_text = "This movie is fantastic! Highly recommended."
print(f"Prediction: {predict_sentiment(example_text)}")
代码2
py
# 加载IMDB数据集和BERT分词器,并进行一些初始化操作
def load_and_preprocess_data():
dataset = load_dataset("stanfordnlp/imdb", cache_dir="./dataset/IMDB")
print("dataset['train'][:5]:", dataset['train'][:5])
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
# # 文本编码函数
# def encode_batch(batch):
# return tokenizer(batch['text'], padding=True, truncation=True, max_length=128)
#
# # 编码数据集
# encoded_dataset = dataset.map(encode_batch, batched=True)
# encoded_dataset.set_format(type="torch", columns=["input_ids", "attention_mask", "label"])
# print("encoded_dataset['train'][:5]:", encoded_dataset['train'][:5])
# # 保存encoded_dataset
# with open('encoded_dataset.pkl', 'wb') as f:
# pickle.dump(encoded_dataset, f)
with open('encoded_dataset.pkl', 'rb') as f:
encoded_dataset = pickle.load(f)
# 划分训练集和测试集
# train_dataset, test_dataset = random_split(encoded_dataset['train'], [20000, 5000])
# 使用数据集中自带的train部分作为训练集
train_dataset = encoded_dataset['train']
test_dataset = encoded_dataset['test']
test_loader = DataLoader(test_dataset, batch_size=16)
return train_dataset, test_dataset, test_loader
# 单GPU训练
def train_single_gpu(model, train_dataset, epochs=1):
print('单GPU训练开始,epochs=', epochs)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = model.to(device)
train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)
optimizer = optim.Adam(model.parameters(), lr=1e-5) # 优化器用于根据计算得到的损失值来更新模型的参数
# 以使得模型能够朝着损失值不断减小的方向进行优化,进而提高模型对数据的拟合能力和预测准确性。
# 自适应矩估计(Adaptive Moment Estimation)优化算法
# model.parameters() 获取模型中所有需要被优化的参数
start_time = time.time()
print('单GPU start_time', get_beijing_time_now())
model.train()
for epoch in range(epochs):
print('epoch', epoch)
for batch in train_loader:
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['label'].to(device)
optimizer.zero_grad()
outputs = model(input_ids, attention_mask=attention_mask)
loss = criterion(outputs.logits, labels)
loss.backward()
optimizer.step()
end_time = time.time()
print('单GPU end_time', get_beijing_time_now())
print(f"Single GPU training time: {end_time - start_time:.2f} seconds")
# 评估模型
model.eval()
all_preds = []
all_labels = []
with torch.no_grad(): # 不进行梯度计算
for batch in test_loader:
input_ids = batch['input_ids'].to(device)
attention_mask = batch['attention_mask'].to(device)
labels = batch['label'].to(device)
outputs = model(input_ids, attention_mask=attention_mask)
preds = torch.argmax(outputs.logits, dim=1)
all_preds.extend(preds.cpu().numpy())
all_labels.extend(labels.cpu().numpy())
print('单GPU评估:\n', classification_report(all_labels, all_preds))
抛开模型种类不同,我不理解为什么代码1的模型训练过程和代码2的模型训练过程这么不同,但是好像都达到了模型训练的效果。这两种代码的模型训练语法有什么不同?
代码1和代码2在模型训练过程中的差异主要源于使用了不同的训练框架和方法,尽管它们最终都实现了模型训练。以下是两者在语法和方法上的主要区别:
代码1:使用transformers
库的Trainer
类
特点:
-
高抽象层封装:
Trainer
类高度封装了训练过程,包括数据加载、模型前向传播、损失计算、梯度更新等操作。- 用户只需提供数据集、模型和相关的训练参数,
Trainer
会自动完成大部分工作。
-
少量代码实现复杂任务:
- 训练、评估和推理都由
Trainer
封装。 - 例如,评估指标由
compute_metrics
函数提供,Trainer
会在每次评估时自动调用并记录结果。
- 训练、评估和推理都由
-
集成式训练:
- 支持多GPU分布式训练、混合精度训练和自动保存最佳模型。
- 设置参数(如
TrainingArguments
中的evaluation_strategy
和save_strategy
)即可控制训练和评估行为。
-
使用
datasets
库:- 数据加载和预处理使用了
datasets
库,便于快速操作标准数据集。
- 数据加载和预处理使用了
优缺点:
- 优点:
- 代码简洁,适合快速实现任务。
- 默认提供优化的训练配置,省去很多低级别细节。
- 缺点:
- 封装度高,对训练过程的细粒度控制较难。
代码2:自定义训练循环
特点:
-
手动实现训练流程:
- 使用PyTorch原生的训练流程,包括手动定义数据加载器、损失函数、优化器等。
- 每个训练步骤都需要显式地编写代码,包括前向传播、损失计算、反向传播、梯度更新等。
-
更灵活的控制:
- 用户可以完全掌控训练过程,便于实现自定义操作,如动态调整学习率、自定义损失函数等。
- 需要明确处理设备分配(
to(device)
)和数据类型转换。
-
评估和推理:
- 模型评估也是手动实现,包括预测输出、计算准确率等。
- 使用
torch.no_grad()
显式关闭梯度计算,以节省资源。
-
无自动化功能:
- 没有自动模型保存、早停机制等功能,需手动实现。
优缺点:
- 优点:
- 灵活性高,适合复杂和自定义需求。
- 可以更深入理解深度学习训练的底层机制。
- 缺点:
- 代码冗长,容易引入错误。
- 实现效率低,需重复书写很多标准训练代码。
语法上的主要区别:
特性 | 代码1 | 代码2 |
---|---|---|
训练框架 | 使用Trainer 类封装 |
手动实现训练循环 |
数据加载 | 使用datasets 库自动处理 |
使用DataLoader 手动加载 |
训练步骤 | 内部封装完成,无需显式编写 | 每步显式编写,包括前向传播、损失计算等 |
损失函数 | 内置于模型 | 手动定义criterion |
优化器 | 自动初始化 | 手动定义optim.Adam |
设备分配 | 自动处理 | 需显式调用to(device) |
评估方式 | 通过compute_metrics 函数定义 |
手动计算准确率和其他指标 |
日志与检查点 | 自动记录与保存 | 需手动实现 |
适合场景 | 快速开发标准任务 | 深入理解与自定义训练流程 |
总结:
- 代码1适合标准化的任务开发,比如快速实验和验证模型效果。
- 代码2适合需要高度自定义的任务,比如非标准的模型架构或训练过程。
如果目标是快速完成任务,可以选择代码1的方式。如果需要深入理解训练过程或实现定制化功能,可以选择代码2。