[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。

相关推荐
蚝油菜花35 分钟前
FlowiseAI:Star!集成多种模型和100+组件的 LLM 应用低代码开发平台,拖拽组件轻松构建程序
人工智能·开源
CM莫问39 分钟前
python实战(十四)——Bert-BiLSTM-CRF命名实体识别
人工智能·python·深度学习·算法·bert·实体识别·crf
ZZZXXE42 分钟前
GLM: General Language Model Pretraining with Autoregressive Blank Infilling论文解读
人工智能·语言模型·自然语言处理
martian6651 小时前
深入详解人工智能语音识别之声学模型与语言模型:掌握HMM、CTC等方法
人工智能·语言模型·语音识别·声学模型
2401_899104111 小时前
适合与简约设计搭配的复古符号推荐
人工智能·数码相机
Crazy learner2 小时前
从语音识别到语音合成:一步步构建智能语音交互系统
人工智能·交互·语音识别
985小水博一枚呀2 小时前
【大厂面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据...本篇介绍训练网络的时候如何判断过拟合和欠拟合?
人工智能·深度学习·神经网络·自然语言处理·面试·cnn
道友老李2 小时前
【机器学习】实战:天池工业蒸汽量项目(三)模型预测
人工智能·机器学习
科研服务器mike_leeso2 小时前
RTX 5090 加持,科研服务器如何颠覆 AI 深度学习构架?
服务器·人工智能·深度学习