【深度学习实验】网络优化与正则化(六):逐层归一化方法——批量归一化、层归一化、权重归一化、局部响应归一化

news2024/11/27 4:17:59

文章目录

  • 一、实验介绍
  • 二、实验环境
    • 1. 配置虚拟环境
    • 2. 库版本介绍
  • 三、优化算法
    • 0. 导入必要的库
    • 1. 随机梯度下降SGD算法
      • a. PyTorch中的SGD优化器
      • b. 使用SGD优化器的前馈神经网络
    • 2.随机梯度下降的改进方法
      • a. 学习率调整
      • b. 梯度估计修正
    • 3. 梯度估计修正:动量法Momentum
    • 4. 自适应学习率
    • 5. Adam算法
  • 四、参数初始化
  • 五、数据预处理
  • 六、逐层归一化
    • 1. 批量归一化
      • a. 理论基础
      • b. 代码实现
      • c. 测试
    • 2. 层归一化
      • a. 理论基础
      • b. 代码实现
      • c. 测试
    • 3. 权重归一化
    • 4. 局部响应归一化
    • 5. 代码整合

一、实验介绍

  深度神经网络在机器学习中应用时面临两类主要问题:优化问题和泛化问题。

  • 优化问题:深度神经网络的优化具有挑战性。

    • 神经网络的损失函数通常是非凸函数,因此找到全局最优解往往困难。
    • 深度神经网络的参数通常非常多,而训练数据也很大,因此使用计算代价较高的二阶优化方法不太可行,而一阶优化方法的训练效率通常较低。
    • 深度神经网络存在梯度消失梯度爆炸问题,导致基于梯度的优化方法经常失效。
  • 泛化问题:由于深度神经网络的复杂度较高且具有强大的拟合能力,很容易在训练集上产生过拟合现象。因此,在训练深度神经网络时需要采用一定的正则化方法来提高网络的泛化能力。

  目前,研究人员通过大量实践总结了一些经验方法,以在神经网络的表示能力、复杂度、学习效率和泛化能力之间取得良好的平衡,从而得到良好的网络模型。本系列文章将从网络优化和网络正则化两个方面来介绍如下方法:

  • 在网络优化方面,常用的方法包括优化算法的选择参数初始化方法数据预处理方法逐层归一化方法超参数优化方法
  • 在网络正则化方面,一些提高网络泛化能力的方法包括ℓ1和ℓ2正则化权重衰减提前停止丢弃法数据增强标签平滑等。

  本文将介绍神经网络优化的逐层归一化方法,包括批量归一化、层归一化、权重归一化(略)、局部响应归一化(略)等

二、实验环境

  本系列实验使用了PyTorch深度学习框架,相关操作如下:

1. 配置虚拟环境

conda create -n DL python=3.7 
conda activate DL
pip install torch==1.8.1+cu102 torchvision==0.9.1+cu102 torchaudio==0.8.1 -f https://download.pytorch.org/whl/torch_stable.html
conda install matplotlib
 conda install scikit-learn

2. 库版本介绍

软件包本实验版本目前最新版
matplotlib3.5.33.8.0
numpy1.21.61.26.0
python3.7.16
scikit-learn0.22.11.3.0
torch1.8.1+cu1022.0.1
torchaudio0.8.12.0.2
torchvision0.9.1+cu1020.15.2

三、优化算法

  神经网络的参数学习是一个非凸优化问题.当使用梯度下降法来进行优化网络参数时,参数初始值的选取十分关键,关系到网络的优化效率和泛化能力.参数初始化的方式通常有以下三种:

0. 导入必要的库

from torch import nn

1. 随机梯度下降SGD算法

  随机梯度下降(Stochastic Gradient Descent,SGD)是一种常用的优化算法,用于训练深度神经网络。在每次迭代中,SGD通过随机均匀采样一个数据样本的索引,并计算该样本的梯度来更新网络参数。具体而言,SGD的更新步骤如下:

  1. 从训练数据中随机选择一个样本的索引。
  2. 使用选择的样本计算损失函数对于网络参数的梯度。
  3. 根据计算得到的梯度更新网络参数。
  4. 重复以上步骤,直到达到停止条件(如达到固定的迭代次数或损失函数收敛)。

a. PyTorch中的SGD优化器

   Pytorch官方教程

optimizer = torch.optim.SGD(model.parameters(), lr=0.2)

b. 使用SGD优化器的前馈神经网络

   【深度学习实验】前馈神经网络(final):自定义鸢尾花分类前馈神经网络模型并进行训练及评价

2.随机梯度下降的改进方法

  传统的SGD在某些情况下可能存在一些问题,例如学习率选择困难和梯度的不稳定性。为了改进这些问题,提出了一些随机梯度下降的改进方法,其中包括学习率的调整和梯度的优化。

a. 学习率调整

在这里插入图片描述

  • 学习率衰减(Learning Rate Decay):随着训练的进行,逐渐降低学习率。常见的学习率衰减方法有固定衰减、按照指数衰减、按照时间表衰减等。
  • Adagrad:自适应地调整学习率。Adagrad根据参数在训练过程中的历史梯度进行调整,对于稀疏梯度较大的参数,降低学习率;对于稀疏梯度较小的参数,增加学习率。这样可以在不同参数上采用不同的学习率,提高收敛速度。
  • Adadelta:与Adagrad类似,但进一步解决了Adagrad学习率递减过快的问题。Adadelta不仅考虑了历史梯度,还引入了一个累积的平方梯度的衰减平均,以动态调整学习率。
  • RMSprop:也是一种自适应学习率的方法,通过使用梯度的指数加权移动平均来调整学习率。RMSprop结合了Adagrad的思想,但使用了衰减平均来减缓学习率的累积效果,从而更加稳定。

b. 梯度估计修正

  • Momentum:使用梯度的“加权移动平均”作为参数的更新方向。Momentum方法引入了一个动量项,用于加速梯度下降的过程。通过积累之前的梯度信息,可以在更新参数时保持一定的惯性,有助于跳出局部最优解、加快收敛速度。
  • Nesterov accelerated gradient:Nesterov加速梯度(NAG)是Momentum的一种变体。与Momentum不同的是,NAG会先根据当前的梯度估计出一个未来位置,然后在该位置计算梯度。这样可以更准确地估计当前位置的梯度,并且在参数更新时更加稳定。
  • 梯度截断(Gradient Clipping):为了应对梯度爆炸或梯度消失的问题,梯度截断的方法被提出。梯度截断通过限制梯度的范围,将梯度控制在一个合理的范围内。常见的梯度截断方法有阈值截断和梯度缩放。

3. 梯度估计修正:动量法Momentum

【深度学习实验】网络优化与正则化(一):优化算法:使用动量优化的随机梯度下降算法(Stochastic Gradient Descent with Momentum)

4. 自适应学习率

【深度学习实验】网络优化与正则化(二):基于自适应学习率的优化算法详解:Adagrad、Adadelta、RMSprop

5. Adam算法

  Adam算法(Adaptive Moment Estimation Algorithm)[Kingma et al., 2015]可以看作动量法和 RMSprop 算法的结合,不但使用动量作为参数更新方向,而且可以自适应调整学习率
【深度学习实验】网络优化与正则化(三):随机梯度下降的改进——Adam算法详解(Adam≈梯度方向优化Momentum+自适应学习率RMSprop)~入选综合热榜
在这里插入图片描述

四、参数初始化

【深度学习实验】网络优化与正则化(四):参数初始化及其Pytorch实现——基于固定方差的初始化(高斯、均匀分布),基于方差缩放的初始化(Xavier、He),正交初始化

五、数据预处理

【深度学习实验】网络优化与正则化(五):数据预处理详解——标准化、归一化、白化、去除异常值、处理缺失值~入选综合热榜
在这里插入图片描述

六、逐层归一化

  逐层归一化(Layer-wise Normalization)是将传统机器学习中的数据归一化方法应用到深度神经网络中,对神经网络中隐藏层的输入进行归一化,从而使得网络更容易训练,进而获得更好的性能和训练效果。它具有:

  • 更好的尺度不变性
    • 逐层归一化可以使输入数据的尺度保持一致,从而提高模型的鲁棒性和泛化能力。通过将每一层的输入数据归一化到相似的尺度,可以减轻不同层之间数据分布差异带来的问题。
    • 内部协变量偏移:在深度神经网络中,每层的输入分布会随着网络参数的更新而发生变化,这被称为内部协变量偏移。逐层归一化可以通过标准化每一层的输入,使得每层的激活函数的输入分布更加稳定,有助于网络的训练和收敛。
  • 更平滑的优化地形
    • 逐层归一化可以使得损失函数的优化地形更加平滑,从而加速模型的训练过程。通过减少梯度的变化范围,逐层归一化可以提高梯度下降算法的效率和稳定性。

1. 批量归一化

a. 理论基础

  • 批量归一化(Batch Normalization,BN):
    • 对神经网络中任意的中间层的单个神经元通过一个Batch数据进行标准化。
    • BN通常应用于卷积神经网络(CNN)和全连接神经网络(FCN)中。
  • 优点:
    • 提高优化效率:通过标准化每一层的输入数据,批量归一化可以缓解梯度消失和梯度爆炸问题,有助于加速优化算法的收敛过程。它可以使每一层的激活函数的输入保持在一个合适的范围内,提高了网络的稳定性和训练效率。、
    • 隐式的正则化方法:批量归一化在每个小批量样本上计算均值和方差,并将其用于标准化数据。这种标准化过程可以看作是一种正则化方法,有助于减少模型的过拟合风险。它在某种程度上充当了正则化的效果,使得模型在一定程度上具有更好的泛化能力。
  • 缺点:
    • 小批量样本的数量不能太小:批量归一化的效果受到小批量样本数量的影响。如果小批量样本数量太小,计算的均值和方差可能不准确,导致归一化效果不佳。通常建议使用较大的批量大小以获得更好的结果。
    • 无法应用到循环神经网络(RNN):批量归一化的计算是基于每个小批量样本的统计信息,而在循环神经网络中,由于神经元状态随时间变化,无法同时处理所有时间步的样本。因此,常规的批量归一化方法无法直接应用于循环神经网络。针对RNN,可以使用层归一化(Layer Normalization)来实现类似的效果。
      在这里插入图片描述

b. 代码实现

def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum):
    # 通过is_grad_enabled来判断当前模式是训练模式还是预测模式
    if not torch.is_grad_enabled():
        # 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差
        X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
    else:
        assert len(X.shape) in (2, 4)
        if len(X.shape) == 2:
            # 使用全连接层的情况,计算特征维上的均值和方差
            mean = X.mean(dim=0)
            var = ((X - mean) ** 2).mean(dim=0)
        else:
            # 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。
            # 这里我们需要保持X的形状以便后面可以做广播运算
            mean = X.mean(dim=(0, 2, 3), keepdim=True)
            var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True)
        # 训练模式下,用当前的均值和方差做标准化
        X_hat = (X - mean) / torch.sqrt(var + eps)
        # 更新移动平均的均值和方差
        moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
        moving_var = momentum * moving_var + (1.0 - momentum) * var
    Y = gamma * X_hat + beta  # 缩放和移位
    return Y, moving_mean.data, moving_var.data


class BatchNorm(nn.Module):
    # num_features:完全连接层的输出数量或卷积层的输出通道数。
    # num_dims:2表示全连接层,4表示卷积层
    def __init__(self, num_features, num_dims):
        super().__init__()
        if num_dims == 2:
            shape = (1, num_features)
        else:
            shape = (1, num_features, 1, 1)
        # 参与求梯度和迭代的拉伸和偏移参数,分别初始化成1和0
        self.gamma = nn.Parameter(torch.ones(shape))
        self.beta = nn.Parameter(torch.zeros(shape))
        # 非模型参数的变量初始化为0和1
        self.moving_mean = torch.zeros(shape)
        self.moving_var = torch.ones(shape)

    def forward(self, X):
        # 如果X不在内存上,将moving_mean和moving_var 复制到X所在显存上
        if self.moving_mean.device != X.device:
            self.moving_mean = self.moving_mean.to(X.device)
            self.moving_var = self.moving_var.to(X.device)
        # 保存更新过的moving_mean和moving_var
        Y, self.moving_mean, self.moving_var = batch_norm(
            X, self.gamma, self.beta, self.moving_mean,
            self.moving_var, eps=1e-5, momentum=0.9)
        return Y

c. 测试

batch_size = 20
train_data = CIFAR10Dataset('cifar10_tiny', 'trainLabels.csv')
train_iter = DataLoader(train_data, batch_size=batch_size)

num_classes = 10
# 定义模型
net = nn.Sequential(
    nn.Conv2d(3, 6, kernel_size=5), BatchNorm(6, num_dims=4), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
    nn.Linear(400, 120), BatchNorm(120, num_dims=2), nn.Sigmoid(),
    nn.Linear(120, 84), BatchNorm(84, num_dims=2), nn.Sigmoid(),
    nn.Linear(84, 10))
# 定义损失函数
loss_fn = F.cross_entropy
# 定义优化器
optimizer = torch.optim.SGD(net.parameters(), lr=0.1)

runner = Runner(net, optimizer, loss_fn, metric=None)
runner.train(train_iter, num_epochs=10, save_path='BatchNorm')

# 第一个批量规范化层中学到的拉伸参数gamma和偏移参数beta
print(net[1].gamma.reshape((-1,)), net[1].beta.reshape((-1,)))

在这里插入图片描述

2. 层归一化

a. 理论基础

  • 层归一化(Layer Normalization):
    • 对一个中间层的所有神经元进行归一化。
    • 与批量归一化不同,层归一化是在每一层的特征维度上进行归一化,而不是在批次维度上。这使得层归一化更适用于递归神经网络(RNN)等具有变长输入的模型。

在这里插入图片描述

在这里插入图片描述

b. 代码实现

class LayerNorm(nn.Module):
    def __init__(self, eps=1e-7, gamma=1.0, beta=0.0):
        super().__init__()
        self.gamma = torch.tensor(gamma)
        self.beta = torch.tensor(beta)
        self.eps = eps

    def forward(self, x):
        # x为规范化层的输入,请注意x的维度
        mean = x.mean(dim=0)
        var = x.var(dim=0)

        output = (x - mean) / torch.sqrt(var + self.eps)
        output = output * self.gamma + self.beta

        return output

c. 测试

batch_size = 20
train_data = CIFAR10Dataset('cifar10_tiny', 'trainLabels.csv')
train_iter = DataLoader(train_data, batch_size=batch_size)


num_classes = 10
# 定义模型
net = nn.Sequential(
    nn.Conv2d(3, 6, kernel_size=5),
    LayerNorm(gamma=1.0, beta=0.0),  # 使用自定义的LayerNorm类,并设置gamma和beta的初始值
    nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), LayerNorm(gamma=1.0, beta=0.0), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
    nn.Linear(400, 120),LayerNorm(), nn.Sigmoid(),
    nn.Linear(120, 84),LayerNorm(),nn.Sigmoid(),
    nn.Linear(84, 10)
)


# 定义损失函数
loss_fn = F.cross_entropy
# 定义优化器
optimizer = torch.optim.SGD(net.parameters(), lr=0.1)

runner = Runner(net, optimizer, loss_fn, metric=None)
runner.train(train_iter, num_epochs=10, save_path='LayerNorm')



3. 权重归一化

  • 权重归一化(Weight Normalization)
    • 权重归一化是通过对模型权重进行归一化,而不是对输入数据进行归一化。它可以在训练过程中动态地调整权重的尺度,以改善模型的训练效果。

4. 局部响应归一化

  • 局部响应归一化(Local Response Normalization,LRN)
    • LRN是一种在卷积神经网络中常用的归一化方法,它通过对每个神经元的输出进行归一化,以增强模型对局部输入模式的响应能力。
    • 局部响应归一化和层归一化都是对同层的神经元进行归一化.不同的是,局部响应归一化应用在激活函数之后,只是对邻近的神经元进行局部归一化,并且不减去均值。

5. 代码整合

(以批量归一化为例)

import os
import torch
from torch import nn
import torch.nn.functional as F
import matplotlib.pyplot as plt
from d2l import torch as d2l
from sklearn.datasets import load_iris
from torchvision.io import read_image
from torch.utils.data import Dataset, DataLoader


class Runner(object):
    def __init__(self, model, optimizer, loss_fn, metric=None):
        self.model = model
        self.optimizer = optimizer
        self.loss_fn = loss_fn
        # 用于计算评价指标
        self.metric = metric

        # 记录训练过程中的评价指标变化
        self.dev_scores = []
        # 记录训练过程中的损失变化
        self.train_epoch_losses = []
        self.dev_losses = []
        # 记录全局最优评价指标
        self.best_score = 0

    def train(self, train_loader, dev_loader=None, **kwargs):
        # 将模型设置为训练模式,此时模型的参数会被更新
        self.model.train()

        num_epochs = kwargs.get('num_epochs', 0)
        log_steps = kwargs.get('log_steps', 100)
        save_path = kwargs.get('save_path', 'best_model.pth')
        eval_steps = kwargs.get('eval_steps', 0)
        # 运行的step数,不等于epoch数
        global_step = 0

        if eval_steps:
            if dev_loader is None:
                raise RuntimeError('Error: dev_loader can not be None!')
            if self.metric is None:
                raise RuntimeError('Error: Metric can not be None')

        # 遍历训练的轮数
        for epoch in range(num_epochs):
            total_loss = 0
            # 遍历数据集
            for step, data in enumerate(train_loader):
                x, y = data
                logits = self.model(x.float())
                loss = self.loss_fn(logits, y.long())
                total_loss += loss
                if step % log_steps == 0:
                    print(f'loss:{loss.item():.5f}')

                loss.backward()
                self.optimizer.step()
                self.optimizer.zero_grad()
            # 每隔一定轮次进行一次验证,由eval_steps参数控制,可以采用不同的验证判断条件
            if eval_steps != 0:
                if (epoch + 1) % eval_steps == 0:

                    dev_score, dev_loss = self.evaluate(dev_loader, global_step=global_step)
                    print(f'[Evalute] dev score:{dev_score:.5f}, dev loss:{dev_loss:.5f}')

                    if dev_score > self.best_score:
                        self.save_model(f'model_{epoch + 1}.pth')

                        print(
                            f'[Evaluate]best accuracy performance has been updated: {self.best_score:.5f}-->{dev_score:.5f}')
                        self.best_score = dev_score

                    # 验证过程结束后,请记住将模型调回训练模式
                    self.model.train()

            global_step += 1
            # 保存当前轮次训练损失的累计值
            train_loss = (total_loss / len(train_loader)).item()
            self.train_epoch_losses.append((global_step, train_loss))
        self.save_model(f'{save_path}.pth')
        print('[Train] Train done')

    # 模型评价阶段
    def evaluate(self, dev_loader, **kwargs):
        assert self.metric is not None
        # 将模型设置为验证模式,此模式下,模型的参数不会更新
        self.model.eval()
        global_step = kwargs.get('global_step', -1)
        total_loss = 0
        self.metric.reset()

        for batch_id, data in enumerate(dev_loader):
            x, y = data
            logits = self.model(x.float())
            loss = self.loss_fn(logits, y.long()).item()
            total_loss += loss
            self.metric.update(logits, y)

        dev_loss = (total_loss / len(dev_loader))
        self.dev_losses.append((global_step, dev_loss))
        dev_score = self.metric.accumulate()
        self.dev_scores.append(dev_score)
        return dev_score, dev_loss

    # 模型预测阶段,
    def predict(self, x, **kwargs):
        self.model.eval()
        logits = self.model(x)
        return logits

    # 保存模型的参数
    def save_model(self, save_path):
        torch.save(self.model.state_dict(), save_path)

    # 读取模型的参数
    def load_model(self, model_path):
        self.model.load_state_dict(torch.load(model_path, map_location=torch.device('cpu')))


def plot_training_loss_acc(runner, fig_name, fig_size=(16, 6), sample_step=20, loss_legend_loc='upper right',
                           acc_legend_loc='lower right',
                           train_color='#8E004D', dev_color='#E20079', fontsize='x-large', train_linestyle='-',
                           dev_linestyle='--'):
    plt.figure(figsize=fig_size)
    plt.subplot(1, 2, 1)
    train_items = runner.train_epoch_losses[::sample_step]
    train_steps = [x[0] for x in train_items]
    train_losses = [x[1] for x in train_items]

    plt.plot(train_steps, train_losses, color=train_color, linestyle=train_linestyle, label='Train loss')
    if len(runner.dev_losses) > 0:
        dev_steps = [x[0] for x in runner.dev_losses]
        dev_losses = [x[1] for x in runner.dev_losses]
        plt.plot(dev_steps, dev_losses, color=dev_color, linestyle=dev_linestyle, label='dev loss')

    plt.ylabel('loss')
    plt.xlabel('step')
    plt.legend(loc=loss_legend_loc)
    if len(runner.dev_scores) > 0:
        plt.subplot(1, 2, 2)
        plt.plot(dev_steps, runner.dev_scores, color=dev_color, linestyle=dev_linestyle, label='dev accuracy')

        plt.ylabel('score')
        plt.xlabel('step')
        plt.legend(loc=acc_legend_loc)
    # 将绘制结果保存
    plt.savefig(fig_name)
    plt.show()


def read_csv_labels(fname):
    """读取fname来给标签字典返回一个文件名"""
    with open(fname, 'r') as f:
        # 跳过文件头行(列名)
        lines = f.readlines()[1:]
    tokens = [l.rstrip().split(',') for l in lines]
    return dict(((name, label) for name, label in tokens))


class CIFAR10Dataset(Dataset):
    def __init__(self, folder_path, fname):
        self.labels = read_csv_labels(os.path.join(folder_path, fname))
        self.folder_path = os.path.join(folder_path, 'train')

    def __len__(self):
        return len(self.labels)

    def __getitem__(self, idx):
        img = read_image(self.folder_path + '/' + str(idx + 1) + '.png')
        label = self.labels[str(idx + 1)]

        return img, torch.tensor(int(label))


def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum):
    # 通过is_grad_enabled来判断当前模式是训练模式还是预测模式
    if not torch.is_grad_enabled():
        # 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差
        X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
    else:
        assert len(X.shape) in (2, 4)
        if len(X.shape) == 2:
            # 使用全连接层的情况,计算特征维上的均值和方差
            mean = X.mean(dim=0)
            var = ((X - mean) ** 2).mean(dim=0)
        else:
            # 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。
            # 这里我们需要保持X的形状以便后面可以做广播运算
            mean = X.mean(dim=(0, 2, 3), keepdim=True)
            var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True)
        # 训练模式下,用当前的均值和方差做标准化
        X_hat = (X - mean) / torch.sqrt(var + eps)
        # 更新移动平均的均值和方差
        moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
        moving_var = momentum * moving_var + (1.0 - momentum) * var
    Y = gamma * X_hat + beta  # 缩放和移位
    return Y, moving_mean.data, moving_var.data


class BatchNorm(nn.Module):
    # num_features:完全连接层的输出数量或卷积层的输出通道数。
    # num_dims:2表示全连接层,4表示卷积层
    def __init__(self, num_features, num_dims):
        super().__init__()
        if num_dims == 2:
            shape = (1, num_features)
        else:
            shape = (1, num_features, 1, 1)
        # 参与求梯度和迭代的拉伸和偏移参数,分别初始化成1和0
        self.gamma = nn.Parameter(torch.ones(shape))
        self.beta = nn.Parameter(torch.zeros(shape))
        # 非模型参数的变量初始化为0和1
        self.moving_mean = torch.zeros(shape)
        self.moving_var = torch.ones(shape)

    def forward(self, X):
        # 如果X不在内存上,将moving_mean和moving_var 复制到X所在显存上
        if self.moving_mean.device != X.device:
            self.moving_mean = self.moving_mean.to(X.device)
            self.moving_var = self.moving_var.to(X.device)
        # 保存更新过的moving_mean和moving_var
        Y, self.moving_mean, self.moving_var = batch_norm(
            X, self.gamma, self.beta, self.moving_mean,
            self.moving_var, eps=1e-5, momentum=0.9)
        return Y


batch_size = 20
# cifar10_tiny是卷积神经网络那节课的数据集的文件夹
train_data = CIFAR10Dataset('cifar10_tiny', 'trainLabels.csv')
train_iter = DataLoader(train_data, batch_size=batch_size)

num_classes = 10
# 定义模型
net = nn.Sequential(
    nn.Conv2d(3, 6, kernel_size=5), BatchNorm(6, num_dims=4), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
    nn.Linear(400, 120), BatchNorm(120, num_dims=2), nn.Sigmoid(),
    nn.Linear(120, 84), BatchNorm(84, num_dims=2), nn.Sigmoid(),
    nn.Linear(84, 10))
# 定义损失函数
loss_fn = F.cross_entropy
# 定义优化器
optimizer = torch.optim.SGD(net.parameters(), lr=0.1)

runner = Runner(net, optimizer, loss_fn, metric=None)
runner.train(train_iter, num_epochs=10, save_path='BatchNorm')

# 第一个批量规范化层中学到的拉伸参数gamma和偏移参数beta
print(net[1].gamma.reshape((-1,)), net[1].beta.reshape((-1,)))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1216739.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux进程间通信之管道通信

Linux 进程间通信机制分三类:数据交互,同步,信号。理解了这些机制才能灵活运用操作系统提供的 IPC 工具。 一.管道 管道是一种文件形式,是内核的一块缓冲区。匿名管道只能用于具有亲缘关系的进程间通信,命名管道可以用…

C语言对10个数进行排序,使用快速排序算法

完整代码&#xff1a; // 对10个数进行排序&#xff0c;使用快速排序算法 #include<stdio.h>//用第一个元素将待排序序列划分成左右两个部分&#xff0c;返回排序后low的位置&#xff0c;即枢轴的位置 int partition(int arr[],int low,int high){//让待排序序列中的第一…

操作系统OS/存储管理/内存管理/内存管理的主要功能_基本原理_要求

基本概念 内存管理的主要功能/基本原理/要求 **内存管理的主要功能&#xff1a; ** 内存空间的分配与回收。由操作系统完成主存储器空间的分配和管理&#xff0c;使程序员摆脱存储分配的麻烦&#xff0c;提高编程效率。地址转换。在多道程序环境下&#xff0c;程序中的逻辑地…

【Git 全功能解析: 探索版本控制的强大工具】

文章目录 概要集中式版本管理和分布式版本管理Git 基础命令基本流程开发管理 概要 “Git 的历史与现状” Git是Linux的创始人Linus Torvalds的又一力作。在2002年&#xff0c;他在Linux内核的版本控制中使用Bitkeeper&#xff0c;但由于Bitkeeper是一款受版权保护的软件&…

什么是脏读、不可重复读、幻读讲解

数据库隔离级别是数据库管理系统中一个重要的概念&#xff0c;它定义了事务之间的可见性和影响。在多用户并发访问数据库时&#xff0c;隔离级别能够确保事务之间的相互独立性&#xff0c;避免数据不一致的问题。本文将深入探讨三种常见的并发问题&#xff1a;脏读、不可重复读…

TikTok运营干货——养号篇

随着国内抖音红利的进一步释放&#xff0c;越来越多人涌入了TikTok国内外市场。而TikTok作为海外新兴的社媒平台&#xff0c;也在迅速的发展着&#xff0c;吸引了大批的跨境电商玩家入驻。然而&#xff0c;TikTok运营的一大难点就是养号&#xff0c;许多人还没开始转化号就被封…

vue3别名配置(vite)

1、配置别名的优点&#xff1a; 在VUE项目中import导入文件时&#xff0c;可以写相对路径. 2、在vite.config.js中配置 a. 首先引入path import path from "path"/* */ b.在resolve添加别名&#xff0c;例如&#xff1a; alias:{"~":path.resolve(__di…

零基础快速上手FFmpeg!一篇就够啦~

在这个自媒体盛行的时代&#xff0c;音视频&#xff08;电影、音乐&#xff09;对于我们来说是再熟悉不过了吧。那么对于一个音视频文件&#xff0c;都有哪些属性呢&#xff1f;以视频为例&#xff0c;我们可以通过如下命令查看其信息。 > ffmpeg -i .\demo.mp4 ffmpeg ver…

探索arkui(2)--- 布局(列表)--- 2(支持分组/实现响应滚动位置)

前端开发布局是指前端开发人员宣布他们开发的新网站或应用程序正式上线的活动。在前端开发布局中&#xff0c;开发人员通常会展示新网站或应用程序的设计、功能和用户体验&#xff0c;并向公众宣传新产品的特点和优势。前端开发布局通常是前端开发领域的重要事件&#xff0c;吸…

通义灵码,阿里巴巴的编程辅助工具

一、官网 通义灵码_智能编码助手_AI编程_人工智能-阿里云 二、安装VSCode 如何下载安装VSCode 三、VSCode安装通义灵码 1.访问扩展详情界面 方式1 访问通义灵码安装教程页面 方法2 访问VSCode市场中的TONGYI Lingma 点击 Install 按钮访问扩展详情界面 2.打开VSCode …

udp多点通信-广播-组播

单播 每次只有两个实体相互通信&#xff0c;发送端和接收端都是唯一确定的。 广播 主机之间的一对多的通信所有的主机都可以接收到广播消息(不管你是否需要)广播禁止穿过路由器&#xff08;只能做局域网通信&#xff09;只有UDP可以广播广播地址 有效网络号全是1的主机号 192.1…

《成为一名优秀的架构师:从基础到实践》

文章目录 前言《高并发架构实战&#xff1a;从需求分析到系统设计》《架构师的自我修炼&#xff1a;技术、架构和未来》《中台架构与实现&#xff1a;基于DDD和微服务》《分布式系统架构&#xff1a;架构策略与难题求解》《流程自动化实战&#xff1a;系统架构和软件开发视角 》…

人工智能-深度学习之序列模型

想象一下有人正在看网飞&#xff08;Netflix&#xff0c;一个国外的视频网站&#xff09;上的电影。 一名忠实的用户会对每一部电影都给出评价&#xff0c; 毕竟一部好电影需要更多的支持和认可。 然而事实证明&#xff0c;事情并不那么简单。 随着时间的推移&#xff0c;人们对…

使用VC++实现分段线性变换,直方图均衡化、锐化处理(使用拉普拉斯算子)

图像锐化1 实验要求 5.1实验目的、要求 实验目的&#xff1a; &#xff08;1&#xff09;掌握图像增强的原理与相关方法。 &#xff08;2&#xff09;能使用VC实现图像增强的一些相关功能。 实验要求&#xff1a; A部分&#xff1a; &#xff08;1&#xff09;对一幅256级灰度…

Linux | 进程间通信

目录 前言 一、进程间通信的基本概念 二、管道 1、管道的基本概念 2、匿名管道 &#xff08;1&#xff09;原理 &#xff08;2&#xff09;测试代码 &#xff08;3&#xff09;读写控制相关问题 a、读端关闭 b、写端关闭 c、读快写慢 d、读慢些快 &#xff08;4&a…

线程的面试八股

Callable接口 Callable是一个interface,相当于给线程封装了一个返回值,方便程序猿借助多线程的方式计算结果. 代码示例: 使用 Callable 版本,创建线程计算 1 2 3 ... 1000, 1. 创建一个匿名内部类, 实现 Callable 接口. Callable 带有泛型参数. 泛型参数表示返回值的类型…

高级数据结构——树状数组

树状数组&#xff08;Binary Index Tree, BIT&#xff09;&#xff0c;是一种一般用来处理单点修改和区间求和操作类型的题目的数据结构&#xff0c;时间复杂度为O(log n)。 对于普通数组来说&#xff0c;单点修改的时间复杂度是 O(1)&#xff0c;但区间求和的时间复杂度是 O(n…

【备忘】websocket学习之挖坑埋自己

背景故事 以前没有好好学习过websocket&#xff0c;只知道它有什么用途&#xff0c;也知道是个好东西&#xff0c;平时在工作中没用过&#xff0c;所以对它并不知所以然。如今要做个自己的项目&#xff0c;要在付款的时候实时播报声音。自己是个开发者&#xff0c;也不想用别人…

类加载中的执行顺序

结论&#xff1a; 先静态再实例 实例化一个子类(这个颜色主要是实例化会执行的部分)&#xff1a; 父类静态属性&#xff0d;&#xff1e;父类静态代码块&#xff0d;&#xff1e;子类静态属性&#xff0d;&#xff1e;子类静态代码块&#xff0d;&#xff1e;父类代码块&…

jQuery【jQuery树遍历、jQuery动画(一)、jQuery动画(二)】(四)-全面详解(学习总结---从入门到深化)

目录 jQuery树遍历 jQuery动画(一) jQuery动画(二) jQuery树遍历 1、 .children() 获得子元素&#xff0c;可以传递一个选择器参数 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-…