pytorch深度学习和入门实战(六)pytorch像keras一样使用封装接口

    科技2026-06-21  4

    目录

    1. earlystop1.1简介1.2 如何使用早停法1.2.1、停止标准简介1.2.2、停止标准选择规则 1.3 pytorch举例说明 2. lr_schedule3. summary可视化4. 接口封装

    1. earlystop

    1.1简介

    当我们训练深度学习神经网络的时候通常希望能获得最好的泛化性能(generalization performance,即可以很好地拟合数据)。但是所有的标准深度学习神经网络结构如全连接多层感知机都很容易过拟合:当网络在训练集上表现越来越好,错误率越来越低的时候,实际上在某一刻,它在测试集的表现已经开始变差。

    图1、理想中的训练集误差和验证集的误差

    模型的泛化能力通常使用模型在验证数据集(validation set)上的表现来评估。随着网络的优化,我们期望的理想中的泛化错误如图1所示。即当模型在训练集上的误差降低的时候,其在验证集上的误差表现不会变差。反之,当模型在训练集上表现很好,在验证集上表现很差的时候,我们认为模型出现了过拟合(overfitting)的情况。

    解决过拟合问题有两个方向降低参数空间的维度或者降低每个维度上的有效规模(effective size)

    降低参数数量的方法包括greedy constructive learning、剪枝和权重共享等。 降低每个参数维度的有效规模的方法主要是正则化,如权重衰变(weight decay)和早停法(early stopping)等。

    早停法是一种被广泛使用的方法,在很多案例上都比正则化的方法要好。图1是我们经常看到论文中出现的图,也是使用早停法出现的一个结果。其基本含义是在训练中计算模型在验证集上的表现,当模型在验证集上的表现开始下降的时候,停止训练,这样就能避免继续训练导致过拟合的问题。其主要步骤如下:

    1. 将原始的训练数据集划分成训练集和验证集 2. 只在训练集上进行训练,并每个一个周期计算模型在验证集上的误差,例如,每15次epoch(mini batch训练中的一个周期) 3. 当模型在验证集上的误差比上一次训练结果差的时候停止训练 4. 使用上一次迭代结果中的参数作为模型的最终参数

    然而,在现实中,模型在验证集上的误差不会像上图那样平滑,而是像下图一样:

    图2、真实的验证集误差变化曲线

    也就是说,模型在验证集上的表现可能咱短暂的变差之后有可能继续变好。上图在训练集迭代到400次的时候出现了16个局部最低。其中有4个最低值是它们所在位置出现的时候的最低点。其中全局最优大约出现在第205次迭代中。首次出现最低点是第45次迭代。相比较第45次迭代停止,到第400次迭代停止的时候找出的最低误差比第45次提高了1.1%,但是训练时间大约是前者的7倍。

    但是,并不是所有的误差曲线都像上图一样,有可能在出现第一次最低点之后,后面再也没有比当前最低点更低的情况了。所以我们看到,早停法主要是训练时间和泛化错误之间的权衡。尽管如此,也有某些停止标准也可以帮助我们寻找更好的权衡。

    1.2 如何使用早停法

    我们需要一个停止的标准来实施早停法,因此,我们希望它可以产生最低的繁华错误,同时也可以有最好的性价比,即给定泛化错误下的最小训练时间

    1.2.1、停止标准简介

    停止标准有很多,也很灵活,大约有三种。在给出早停法的具体标准之前,我们先确定一下符号。假设我们使用EE作为训练算法的误差函数,那么E_{tr}(t)E​tr​​(t)是训练数据上的误差,E_{te}(t)E​te​​(t)是测试集上的误差。实际情况下我们并不能知道泛化误差,因此我们使用验证集误差来估计它。

    第一类停止标准

    假设E_{opt}(t)E​opt​​(t)是在迭代次数tt时取得最好的验证集误差:

    E_{opt}(t) := \text{min}{t’\leq t}E{va}(t’)E​opt​​(t):=min​t​′​​≤t​​E​va​​(t​′​​)

    我们定义一个新变量叫泛化损失(generalization loss),它描述的是在当前迭代周期t中,泛化误差相比较目前的最低的误差的一个增长率:

    GL(t) = 100 \cdot \big( \frac{E_{va}(t)}{E_{opt}(t)} - 1 \big)GL(t)=100⋅(​E​opt​​(t)​​E​va​​(t)​​−1)

    较高的泛化损失显然是停止训练的一个候选标准,因为它直接表明了过拟合。这就是第一类的停止标准,即当泛化损失超过一定阈值的时候,停止训练。我们用GL_{\alpha}GL​α​​来定义,即当GL_{\alpha}GL​α​​大于一定值\alphaα的时候,停止训练。

    第二类停止标准

    然而,当训练的速度很快的时候,我们可能希望模型继续训练。因为如果训练错误依然下降很快,那么泛化损失有很大概率被修复。我们通常会假设过拟合只会在训练错误降低很慢的时候出现。在这里,我们定义一个kk周期,以及基于周期的一个新变量度量进展(measure progress):

    P_k(t) = 1000 \cdot \big( \frac{ \sum_{t’ = t-k+1}^t E_{tr}(t’) }{ k \cdot min_{t’ = t-k+1}^t E_{tr}(t’) } -1 \big)P​k​​(t)=1000⋅(​k⋅min​t​′​​=t−k+1​t​​E​tr​​(t​′​​)​​∑​t​′​​=t−k+1​t​​E​tr​​(t​′​​)​​−1)

    它表达的含义是,当前的指定迭代周期内的平均训练错误比该期间最小的训练错误大多少。注意,当训练过程不稳定的时候,这个measure progress结果可能很大,其中训练错误会变大而不是变小。实际中,很多算法都由于选择了不适当的较大的步长而导致这样的抖动。除非全局都不稳定,否则在较长的训练之后,measure progress结果趋向于0(其实这个就是度量训练集错误在某段时间内的平均下降情况)。由此,我们引入了第二个停止标准,即泛化损失和进展的商PQ_{\alpha}PQ​α​​大于指定值的时候停止,即\frac{GL(t)}{P_k(t)} \gt \alpha​P​k​​(t)​​GL(t)​​>α

    第三类停止标准 第三类停止标准则完全依赖于泛化错误的变化,即当泛化错误在连续ss个周期内增长的时候停止(UPUP)。

    当验证集错误在连续ss个周期内出现增长的时候,我们假设这样的现象表明了过拟合,它与错误增长了多大独立。这个停止标准可以度量局部的变化,因此可以用在剪枝算法中,即在训练阶段,允许误差可以比前面最小值高很多时候保留。

    1.2.2、停止标准选择规则

    一般情况下,“较慢”的标准会相对而言在平均水平上表现略好,可以提高泛化能力。然而,这些标准需要较长的训练时间。其实,总体而言,这些标准在系统性的区别很小。主要选择规则包括:

    除非较小的提升也有很大价值,负责选择较快的停止标准 为了最大可能找到一个好的方案,使用GL标准 为了最大化平均解决方案的质量,如果网络只是过拟合了一点点,可以使用PQ标准,否则使用UP标准 注意,目前并没有理论上可以证明那种停止标准较好,所以都是实验的数据。

    1.3 pytorch举例说明

    本博客使用了这个 g i t h u b 仓库中提供的 p y t o r c h _ t o o l s pytorch_toolspytorch_tools,而且该仓库中还有这个工具的使用案例,建议读者前往查看。在此感谢作者的代码分享。

    (1)需要写一个util文件,包括以下earlystop的类:

    class EarlyStopping: """Early stops the training if validation loss doesn't improve after a given patience.""" def __init__(self, patience=7, verbose=False, delta=0, path='./models/best_loss.pt', trace_func=print): """ Args: patience (int): How long to wait after last time validation loss improved. Default: 7 verbose (bool): If True, prints a message for each validation loss improvement. Default: False delta (float): Minimum change in the monitored quantity to qualify as an improvement. Default: 0 path (str): Path for the checkpoint to be saved to. Default: 'checkpoint.pt' trace_func (function): trace print function. Default: print """ self.patience = patience self.verbose = verbose self.counter = 0 self.best_score = None self.early_stop = False self.val_loss_min = np.Inf self.delta = delta self.path = path self.trace_func = trace_func def __call__(self, val_loss, model): score = -val_loss if self.best_score is None: self.best_score = score self.save_checkpoint(val_loss, model) elif score < self.best_score + self.delta: self.counter += 1 self.trace_func(f'EarlyStopping counter: {self.counter} out of {self.patience}') if self.counter >= self.patience: self.early_stop = True else: self.best_score = score self.save_checkpoint(val_loss, model) self.counter = 0 def save_checkpoint(self, val_loss, model): '''Saves model when validation loss decrease.''' if self.verbose: self.trace_func(f'Validation loss decreased ({self.val_loss_min:.6f} --> {val_loss:.6f}). Saving model ...') torch.save(model.state_dict(), self.path) self.val_loss_min = val_loss

    参数说明:

    patience=7, : 在经过了多少次step后,loss还不能小于已记录的 min loss,就停止退出。这个patience就是这个次数。verbose=False, 如果是真的话,每一条loss计算都打印; 否则只是在出现best loss时候打印delta=0, 偏差值。就是当 loss - best_loss < dellta的时候,都算是达标,不进行stop;path=’./models/best_loss.pt’, 最佳模型的保存位置,视情况而定,自行修改

    (2)基本用法

    #Train the Model using Early Stopping def train_model(model, batch_size, patience, n_epochs): # to track the training loss as the model trains train_losses = [] # to track the validation loss as the model trains valid_losses = [] # to track the average training loss per epoch as the model trains avg_train_losses = [] # to track the average validation loss per epoch as the model trains avg_valid_losses = [] # initialize the early_stopping object early_stopping = EarlyStopping(patience=patience, verbose=True) for epoch in range(1, n_epochs + 1): ################### # train the model # ################### model.train() # prep model for training for batch, (data, target) in enumerate(train_loader, 1): # clear the gradients of all optimized variables optimizer.zero_grad() # forward pass: compute predicted outputs by passing inputs to the model output = model(data) # calculate the loss loss = criterion(output, target) # backward pass: compute gradient of the loss with respect to model parameters loss.backward() # perform a single optimization step (parameter update) optimizer.step() # record training loss train_losses.append(loss.item()) ###################### # validate the model # ###################### model.eval() # prep model for evaluation for data, target in valid_loader: # forward pass: compute predicted outputs by passing inputs to the model output = model(data) # calculate the loss loss = criterion(output, target) # record validation loss valid_losses.append(loss.item()) # print training/validation statistics # calculate average loss over an epoch train_loss = np.average(train_losses) valid_loss = np.average(valid_losses) avg_train_losses.append(train_loss) avg_valid_losses.append(valid_loss) epoch_len = len(str(n_epochs)) print_msg = (f'[{epoch:>{epoch_len}}/{n_epochs:>{epoch_len}}] ' + f'train_loss: {train_loss:.5f} ' + f'valid_loss: {valid_loss:.5f}') print(print_msg) # clear lists to track next epoch train_losses = [] valid_losses = [] # early_stopping needs the validation loss to check if it has decresed, # and if it has, it will make a checkpoint of the current model early_stopping(valid_loss, model) if early_stopping.early_stop: print("Early stopping") break # load the last checkpoint with the best model model.load_state_dict(torch.load('checkpoint.pt')) return model, avg_train_losses, avg_valid_losses

    2. lr_schedule

    具体参数不做详解,可以参考我前面文章 pytorch深度学习和入门实战(四)神经网络的构建和训练

    主要是使用ReduceLROnPlateau

    class torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode=min, factor=0.1, patience=10, verbose=False, threshold=0.0001, threshold_mode=‘rel’, cooldown=0, min_lr=0, eps=1e-08)

    当某指标不再变化(下降或升高),调整学习率,这是非常实用的学习率调整策略。例如,当验证集的loss不再下降时,进行学习率调整;或者监测验证集的accuracy,当accuracy不再上升时,则调整学习率。

    具体用法,可以参见官方文档实例,写的比较清楚: 1.scheduler使用在optim.step 之后(Learning rate scheduling should be applied after optimizer’s update) 2 使用在epoch之后,而不是mini-batchsize之中。 3 注意监测的是valid loss, 所以应该也在valid之后使用(# Note that step should be called after validate())

    3. summary可视化

    像kears使用summary显示每个模型的参数和layer情况,pytorch也有第三方提供类似功能。

    安装: pip install torchsummary 使用 from torchsummary import summary print("[INFO] Model Layer: ", summary(new_model, (3, 224, 224))) 显示

    4. 接口封装

    使用过keras的朋友都会觉得非常简单,很多接口封装的非常好,都是模块化的,直接调用填到里面就可以,非常方便。改用pytorch之后,多少有一些不习惯。所以,参考网站一些教程,自己分装一下接口。这样,只是在最外面改动一下参数就可以比较随意训练,还是比较方便。

    Processed: 0.008, SQL: 9