Python 现代开发学习笔记

(二十四)训练与验证共用一轮读取流程

现在把前面的批次、模型、损失和更新组合为一个函数。

# 项目文件:training_project/src/readingtorch/engine.py
import json
import math
from pathlib import Path

import torch
from torch import nn

from .checkpoint import load_checkpoint, save_checkpoint, snapshot
from .data import load_bundle, make_loader
from .models import build_model


def one_epoch(model, loader, loss_fn, device, task, y_scale=1.0, optimizer=None):
    training = optimizer is not None
    model.train(training)
    total, count, correct = 0.0, 0, 0
    for x, y in loader:
        x, y = x.to(device), y.to(device)
        # 训练建立计算图,验证直接计算;模式切换和梯度开关各自设置。
        with torch.set_grad_enabled(training):
            if training:
                optimizer.zero_grad(set_to_none=True)
            output = model(x)
            loss = loss_fn(output, y)
            if not torch.isfinite(loss):
                raise ValueError("损失出现了 NaN 或无穷值,请检查输入与学习率")
            if training:
                loss.backward()
                optimizer.step()
        total += loss.item() * len(x)  # 按样本数加权,末批较小时仍统计正确。
        count += len(x)
        if task == "images":
            correct += (output.argmax(1) == y).sum().item()
    result = {"loss": total / count}
    if task == "images":
        result["accuracy"] = correct / count
    else:
        result["rmse"] = math.sqrt(result["loss"]) * y_scale
    return result

传入优化器时做训练,不传时做验证。model.train(training) 设置层行为,set_grad_enabled(training) 决定是否为本次计算记录求导信息;两者在函数里分别写出。

loss.item() * len(x) 把本批平均损失还原为本批损失和,所有批读完再除以总样本数。例如两批大小分别为 2、1,平均损失分别为 1、9,整轮应为 (1×2+9×1)/3=11/3,直接平均两批会错算成 5。

分类准确率为“判断正确的样本数除以样本总数”。回归的损失来自标准化目标,开方得到标准化 RMSE 后,再乘训练目标标准差 y_scale,就回到页数单位。

训练批执行反向和更新,验证批只读参数;批均值先乘实际条数再累计,最后除以总条数并计算分类准确率或页数 RMSE

笔记正文与全部题解 · Ctrl / ⌘ K