[Trainer类封装训练过程]和[手动实现训练循环]的区别

代码举例

代码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

特点:

  1. 高抽象层封装:

    • Trainer类高度封装了训练过程,包括数据加载、模型前向传播、损失计算、梯度更新等操作。
    • 用户只需提供数据集、模型和相关的训练参数,Trainer会自动完成大部分工作。
  2. 少量代码实现复杂任务:

    • 训练、评估和推理都由Trainer封装。
    • 例如,评估指标由compute_metrics函数提供,Trainer会在每次评估时自动调用并记录结果。
  3. 集成式训练:

    • 支持多GPU分布式训练、混合精度训练和自动保存最佳模型。
    • 设置参数(如TrainingArguments中的evaluation_strategysave_strategy)即可控制训练和评估行为。
  4. 使用datasets库:

    • 数据加载和预处理使用了datasets库,便于快速操作标准数据集。

优缺点:

  • 优点:
    • 代码简洁,适合快速实现任务。
    • 默认提供优化的训练配置,省去很多低级别细节。
  • 缺点:
    • 封装度高,对训练过程的细粒度控制较难。

代码2:自定义训练循环

特点:

  1. 手动实现训练流程:

    • 使用PyTorch原生的训练流程,包括手动定义数据加载器、损失函数、优化器等。
    • 每个训练步骤都需要显式地编写代码,包括前向传播、损失计算、反向传播、梯度更新等。
  2. 更灵活的控制:

    • 用户可以完全掌控训练过程,便于实现自定义操作,如动态调整学习率、自定义损失函数等。
    • 需要明确处理设备分配(to(device))和数据类型转换。
  3. 评估和推理:

    • 模型评估也是手动实现,包括预测输出、计算准确率等。
    • 使用torch.no_grad()显式关闭梯度计算,以节省资源。
  4. 无自动化功能:

    • 没有自动模型保存、早停机制等功能,需手动实现。

优缺点:

  • 优点:
    • 灵活性高,适合复杂和自定义需求。
    • 可以更深入理解深度学习训练的底层机制。
  • 缺点:
    • 代码冗长,容易引入错误。
    • 实现效率低,需重复书写很多标准训练代码。

语法上的主要区别:

特性 代码1 代码2
训练框架 使用Trainer类封装 手动实现训练循环
数据加载 使用datasets库自动处理 使用DataLoader手动加载
训练步骤 内部封装完成,无需显式编写 每步显式编写,包括前向传播、损失计算等
损失函数 内置于模型 手动定义criterion
优化器 自动初始化 手动定义optim.Adam
设备分配 自动处理 需显式调用to(device)
评估方式 通过compute_metrics函数定义 手动计算准确率和其他指标
日志与检查点 自动记录与保存 需手动实现
适合场景 快速开发标准任务 深入理解与自定义训练流程

总结:

  • 代码1适合标准化的任务开发,比如快速实验和验证模型效果。
  • 代码2适合需要高度自定义的任务,比如非标准的模型架构或训练过程。

如果目标是快速完成任务,可以选择代码1的方式。如果需要深入理解训练过程或实现定制化功能,可以选择代码2。

相关推荐
boooo_hhh1 小时前
深度学习笔记16-VGG-16算法-Pytorch实现人脸识别
pytorch·深度学习·机器学习
AnnyYoung1 小时前
华为云deepseek大模型平台:deepseek满血版
人工智能·ai·华为云
INDEMIND2 小时前
INDEMIND:AI视觉赋能服务机器人,“零”碰撞避障技术实现全天候安全
人工智能·视觉导航·服务机器人·商用机器人
慕容木木2 小时前
【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体的替代品,可本地部署+知识库,注册即可有750w的token使用
人工智能·火山引擎·deepseek·deepseek r1
南 阳2 小时前
百度搜索全面接入DeepSeek-R1满血版:AI与搜索的全新融合
人工智能·chatgpt
企鹅侠客2 小时前
开源免费文档翻译工具 可支持pdf、word、excel、ppt
人工智能·pdf·word·excel·自动翻译
冰淇淋百宝箱3 小时前
AI 安全时代:SDL与大模型结合的“王炸组合”——技术落地与实战指南
人工智能·安全
Elastic 中国社区官方博客3 小时前
Elasticsearch Open Inference API 增加了对 Jina AI 嵌入和 Rerank 模型的支持
大数据·人工智能·elasticsearch·搜索引擎·ai·全文检索·jina
美狐美颜sdk3 小时前
直播美颜工具架构设计与性能优化实战:美颜SDK集成与实时处理
深度学习·美颜sdk·第三方美颜sdk·视频美颜sdk·美颜api
AWS官方合作商4 小时前
Amazon Lex:AI对话引擎重构企业服务新范式
人工智能·ai·机器人·aws