批量归一化与残差网络、凸优化、梯度下降法

批量归一化

对于浅层模型:
对输入进行标准化处理,使得各个特征的分布相近,任意一个特征在数据集中所有样本上的均值为0、标准差为1

对于深度模型:
提出批量归一化,利用小批量的均值和标准差,不断调整网络中间输出,使得网络各层的输出数值更稳定

1. 对全连接层做BatchNormalization:

x = W u + b o u t p u t = ϕ ( x ) \boldsymbol{x} = \boldsymbol{W\boldsymbol{u} + \boldsymbol{b}} \\ output =\phi(\boldsymbol{x}) x=Wu+boutput=ϕ(x)
输入u的大小是batch size 乘以 输入神经元的个数,输出x的大小是batch size 乘以 输出神经元的个数,经过激活函数之后output的size是与x相同. 而我们要做的批量归一化就是在两个式子中间,即全连接层中的仿射变换和激活函数之间就是对x做标准化
o u t p u t = ϕ ( BN ( x ) ) output=\phi(\text{BN}(\boldsymbol{x})) output=ϕ(BN(x))
y ( i ) = BN ( x ( i ) ) \boldsymbol{y}^{(i)} = \text{BN}(\boldsymbol{x}^{(i)}) y(i)=BN(x(i))

μ B ← 1 m ∑ i = 1 m x ( i ) , \boldsymbol{\mu}_\mathcal{B} \leftarrow \frac{1}{m}\sum_{i = 1}^{m} \boldsymbol{x}^{(i)}, μBm1i=1mx(i),
σ B 2 ← 1 m ∑ i = 1 m ( x ( i ) − μ B ) 2 , \boldsymbol{\sigma}_\mathcal{B}^2 \leftarrow \frac{1}{m} \sum_{i=1}^{m}(\boldsymbol{x}^{(i)} - \boldsymbol{\mu}_\mathcal{B})^2, σB2m1i=1m(x(i)μB)2,
x ^ ( i ) ← x ( i ) − μ B σ B 2 + ϵ , \hat{\boldsymbol{x}}^{(i)} \leftarrow \frac{\boldsymbol{x}^{(i)} - \boldsymbol{\mu}_\mathcal{B}}{\sqrt{\boldsymbol{\sigma}_\mathcal{B}^2 + \epsilon}}, x^(i)σB2+ϵ x(i)μB,

首先求出对应的每个 x ( i ) x^(i) x(i)的均值 μ \mu μ,m是batch size,而方差是 σ 2 \sigma^2 σ2,最后使用标准化公式更新出 x ^ ( i ) \hat{\boldsymbol{x}}^{(i)} x^(i). 这是Z-score Normalization公式,不过这里加多了一个 ϵ \epsilon ϵ,是个很小的常数,保证分母大于0.
y ( i ) ← γ ⊙ x ^ ( i ) + β . {\boldsymbol{y}}^{(i)} \leftarrow \boldsymbol{\gamma} \odot \hat{\boldsymbol{x}}^{(i)} + \boldsymbol{\beta}. y(i)γx^(i)+β.
我们还引入了两个新的学习参数,拉伸参数γ和偏移参数β。在训练中,这两个参数会进行学习,若 γ = σ B 2 + ϵ \boldsymbol{\gamma} = \sqrt{\boldsymbol{\sigma}_\mathcal{B}^2 + \epsilon} γ=σB2+ϵ β = μ B \boldsymbol{\beta} = \boldsymbol{\mu}_\mathcal{B} β=μB,批量归一化无效。
它们的作用是,如果批量归一化之后反而效果不好,可以通过学习的这两个参数进行归一化的无效化

2. 对卷积层做批量归一化

位置:卷积计算之后、应用激活函数之前。
卷积层输出的维度:样本数x通道数x卷积后的高x卷积后的宽 = mxcxpxq
如果卷积计算输出多个通道,我们需要对这些通道的输出分别做批量归一化,且每个通道都拥有独立的拉伸和偏移参数
计算:对单通道,batchsize=m,卷积计算输出=pxq
对该通道中m×p×q个元素同时做批量归一化,使用相同的均值和方差。

3. 预测时的批量归一化

训练:以batch为单位,对每个batch计算均值和方差。
预测:用移动平均估算整个训练数据集的样本均值和方差。
因为预测的时候是没有均值和方差做参考的,只能使用移动平均法来估算

  • 使用传入的移动平均:

       X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
    
代码实现:
import time
import torch
from torch import nn, optim
import torch.nn.functional as F
import torchvision
import sys
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
#用于做归一化
def batch_norm(is_training, X, gamma, beta, moving_mean, moving_var, eps, momentum):
    # 判断当前模式是训练模式还是预测模式
    if not is_training:
        # 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差
        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, keepdim=True).mean(dim=2, keepdim=True).mean(dim=3, keepdim=True)
            var = ((X - mean) ** 2).mean(dim=0, keepdim=True).mean(dim=2, keepdim=True).mean(dim=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, moving_var

#做参数的初始化
class BatchNorm(nn.Module):
    def __init__(self, num_features, num_dims):
        super(BatchNorm, self).__init__()
        if num_dims == 2:
            shape = (1, num_features) #全连接层输出神经元
        else:
            shape = (1, num_features, 1, 1)  #通道数
        # 参与求梯度和迭代的拉伸和偏移参数,分别初始化成0和1
        self.gamma = nn.Parameter(torch.ones(shape))
        self.beta = nn.Parameter(torch.zeros(shape))
        # 不参与求梯度和迭代的变量,全在内存上初始化成0
        self.moving_mean = torch.zeros(shape)
        self.moving_var = torch.zeros(shape)
	#参数的更新,直接调用了上面的batch_norm函数一起使用
    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, Module实例的traning属性默认为true, 调用.eval()后设成false
        Y, self.moving_mean, self.moving_var = batch_norm(self.training, 
            X, self.gamma, self.beta, self.moving_mean,
            self.moving_var, eps=1e-5, momentum=0.9)
        return Y

应用到LeNet

net = nn.Sequential(
            nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
            BatchNorm(6, num_dims=4),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2), # kernel_size, stride
            nn.Conv2d(6, 16, 5),
            BatchNorm(16, num_dims=4),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2),
            d2l.FlattenLayer(),
            nn.Linear(16*4*4, 120),
            BatchNorm(120, num_dims=2),
            nn.Sigmoid(),
            nn.Linear(120, 84),
            BatchNorm(84, num_dims=2),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )
print(net)

Pytorch版本直接实现:
不需要自己去写函数,BatchNorm2d代表输入维度是4,BatchNorm1d代表输入维度是2

net = nn.Sequential(
            nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
            nn.BatchNorm2d(6),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2), # kernel_size, stride
            nn.Conv2d(6, 16, 5),
            nn.BatchNorm2d(16),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2),
            d2l.FlattenLayer(),
            nn.Linear(16*4*4, 120),
            nn.BatchNorm1d(120),
            nn.Sigmoid(),
            nn.Linear(120, 84),
            nn.BatchNorm1d(84),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )

optimizer = torch.optim.Adam(net.parameters(), lr=lr)
d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)

残差网络ResNet

深度学习把层数加深不是万能的,即使我们已经加入了BN方法,让数据更加稳定一点,但是层数一直加深,带来的不是性能变强,反而会招致网络收敛变得更慢,准确率也变得更差。

网络退化现象degradation

假设存在一个最优化的网络结构,例如10层,但是我们并不知道具体到哪个层次的网络才是最优的,如果我们设计了一个18层的结构,那么后面的8层都是冗余的,我们希望模型可以在训练中把多出来的层数进行恒等映射,即经过这层时的输入与输出完全相同. 但是模型是很难把多出来的8层的恒等变换参数学习正确,导致模型退化,不过这个不是过拟合产生的,训练的loss是增加的,所以这是因为网络冗余无法正确学习恒等映射参数而导致的.

从信息论的角度讲,由于DPI(数据处理不等式)的存在,在前向传输的过程中,随着层数的加深,Feature Map包含的图像信息会逐层减少,而ResNet的直接映射的加入,保证了 l + 1 l+1 l+1层的网络一定比 l l l层包含更多的图像信息。

Image Name
虚线框里面的就是残差块,里面放上什么网络都可以,例如全连接网络、CNN等,因为ResNet是在ImageNet比赛中获得成功的,所以我们多用于处理图像,所以通常也是放CNN进去. 加权运算就是对卷积核的运算。 这幅图可能让人理解还不够深刻,结合下面的解释可能会理解更深:
在这里插入图片描述
一个残差块可以用这个公式表示: x l + 1 = x l + F ( x l , W l ) x_{l+1}=x_l + F(x_l,W_l) xl+1=xl+F(xl,Wl)
左边一条直线下来的,是直接映射部分, h ( x l ) h(x_l) h(xl);右边是 F ( x l , W l ) F(x_l,W_l) F(xl,Wl)残差部分,一般由2至3个卷积操作组成
weight指的是卷积操作,addition指单位加操作.

因为卷积网络中,可能 x l x_l x

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值