week5 8月3日

批量归一化

为什么需要归一化?

每层网络变换 之后的输出分布都不一样,由于输入是前一层的输出,层层叠加,所以每层的输出分布更奇奇怪怪

归一化:在每层网络之后,将每个样本归一化到均值为0,方差为1的分布中。

但是并不希望每一层的样本分布完全相同,所以每一层都有一个γ和β的线性变化,进行轻微的偏移。γ和β随着训练过程自动调整,所以几乎不完全相同。

分布归一化和非线性激活顺序可以调换。第一个可以是其他的变换,卷积之类的 

代码:

引入第三方库

import torch
from torch import nn
from d2l import torch as d2l

归一化处理函数 

def batch_norm(x,gamma,beta,moving_mean,moving_var,eps,momentum):
    #gamma和beta是可学习参数,moving_mean和moving_var是全局变量,是整个数据集上的均值和方差
    #momentum用来更新mean或者var通常取0.9,eps也一样,是固定的值
    if not torch.is_grad_enabled():#如果不算梯度,是预测模式
        x_hat=(x-moving_mean)/torch.sqrt(moving_var+eps)
        #
    else:#算梯度,是训练模式
        assert len(x.shape) in (2,4)#x的形状是2或者4
        #下面求均值和方差
        if len(x.shape)==2:#全连接层
            mean=x.mean(dim=0)#均值
            var=((x-mean)**2).mean(dim=0)#方差也是一个行向量
        else:#4是卷积层
            mean=x.mean(dim=(0,2,3),keepdim=True)
            #把x的第0维,第2维和第3维求均值
            #最终是1*n*1*1的矩阵
            var=((x-mean)**2).mean(dim=(0,2,3),keepdim=True)
        #得到x的初步位置,没有算上拉伸和偏移(可学习参数)
        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
    #得到x的该层的最终估计值
    y=gamma*x_hat+beta
    return y,moving_mean.data,moving_var.data

定义一个batch norm类

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):
        if self.moving_mean.device!=x.device:
            # 如果X不在内存上,将moving_mean和moving_var复制到X所在显存上
            self.moving_mean=self.moving_mean.to(x.device)
            self.moving_var=self.moving_var.to(x.device)
        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

定义网络

net = nn.Sequential(nn.Conv2d(1, 6, kernel_size=5),
                    BatchNorm(6, num_dims=4),#6个输入通道、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(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))

训练

lr, num_epochs, batch_size = 1.0, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

结果

简洁版的批量归一化网络

netjianming = nn.Sequential(nn.Conv2d(1, 6, kernel_size=5),
                    nn.BatchNorm2d(6),
                    #直接调用学习框架定义的batchnorm,不需要手动定义函数和类
                    nn.Sigmoid(),
                    nn.AvgPool2d(kernel_size=2, stride=2),
                    nn.Conv2d(6, 16, kernel_size=5),
                    nn.BatchNorm2d(16),
                    nn.Sigmoid(),
                    nn.AvgPool2d(kernel_size=2, stride=2),
                    nn.Flatten(),
                    nn.Linear(256, 120),
                    nn.BatchNorm1d(120),
                    nn.Sigmoid(),
                    nn.Linear(120, 84),
                    nn.BatchNorm1d(84),
                    nn.Sigmoid(),
                    nn.Linear(84, 10))
d2l.train_ch6(netjianming, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

残差网络 ResNet

在深度学习中,为了增强模型的学习能力,网络层会变得越来越深,但是随着深度的增加,也带来了比较一些问题,主要包括:

  • 模型复杂度上升,网络训练困难;
  • 梯度消失/梯度爆炸
  • 网络退化,也就是说模型的学习能力达到了饱和,增加网络层数并不能提升精度了。

 为此提出了残差网络

因为残差块保留了原始输入的信息,所以网络有如下优势:

  • 随着深度的增加,可以获取更高的精度,因为其学习的残差越准确;
  • 网络优化比较简单;
  • 比较通用;

       假如新加的这些层的学习效果非常差,那我们就可以通过一条残差边将这一部分直接“跳过”。实现这一目的很简单,将这些层的权重参数设置为0就行了。这样一来,不管网络中有多少层,效果好的层我们保留,效果不好的我们可以跳过。总之,添加的新网络层至少不会使效果比原来差,就可以较为稳定地通过加深层数来提高模型的效果了。

残差网络为什么可以避免梯度消失其实也很好理解:

        在没有残差边的时候,假如网络层数很深的话,要想更新底层的(靠近输入数据部分)的网络权重,首先对其求梯度,根据链式法则需要一直向前累乘,只要其中的任何一个因数过小就会导致求出来的梯度很小很小,这个小梯度就算乘以再大的学习率也是无济于事;

       而当我们有了残差边时,求梯度可以直接经过“高速公路”直达我们想要求梯度的对象,此时不管通过链式法则走正常路线得到的梯度多么小,两条路线相加的结果都不会小,就可以很有效地进行梯度更新了

细节

 由于需要x+g(x),所以要求x输入通道数与g(x)的输出通道数相同。

如果想要改变通道数,需要引入一个1*1conv来改变x的输入通道数

残差块 由两个3*3卷积层组成,每个卷积层后跟着一个批量归一化和激活函数。而x的输入直接加在第2个卷积层之后

至于加在卷积之后、归一化之激活之后,都可以。效果都差不多捏。

 

 具体代码如下:

import d2l.torch
import torch
from torch.nn import functional as F
from torch import nn

#定义残差类
class Residual(nn.Module): #@save
    def __init__(self,input_channels,num_channels,use_1x1conv=False,strides=1):
        super().__init__()#继承Module模块的初始化函数
        self.conv1=
        nn.Conv2d(input_channels,num_channels,kernel_size=3,padding=1,stride=strides)
        self.conv2=n
        n.Conv2d(num_channels,num_channels,kernel_size=3,padding=1)
        if use_1x1conv:
            self.conv3=
            nn.Conv2d(input_channels,num_channels,kernel_size=1,stride=strides)
        else:
            self.conv3=None
        self.bn1=nn.BatchNorm2d(num_channels)
        self.bn2=nn.BatchNorm2d(num_channels)
    def forward(self,x):
        y=F.relu(self.bn1(self.conv1(x)))#卷积、归一化、激活
        y=self.bn2(self.conv2(y))#卷积、归一化
        if self.conv3:#看看要不要让x卷积再相加
            x=self.conv3(x)
        y+=x
        return F.relu(y)  


#定义ResNet网络的各个模块
#ResNet第一个模块跟GoogleNet第一个模块相同
b1 = nn.Sequential(nn.Conv2d(
                   in_channels=1,out_channels=64,kernel_size=7,padding=3,stride=2),
                   nn.BatchNorm2d(64),
                   nn.ReLU(),
                   nn.MaxPool2d(kernel_size=3,padding=1,stride=2))
                   #conv2d尺寸减半,池化也减半,所以第一个模块后,图像尺寸缩小4倍

#开始定义其他模块,这些模块才是真正的残差
#定义一个ResNet块,通常包含两个残差块Residul块(也即是包含两个残差网络层)
#一个ResNet块通常通道数加倍,尺寸形状高和宽减半,
#对应到由第一个残差块输出通道是输入通道两倍,尺寸大小减半
#第二个残差块输入输出通道数相同,输入输出尺寸形状大小不变
#但除开第2个ResNet块,因为第一个ResNet块将输入尺寸形状大小降低了4倍
def resnet_block(input_channels,num_channels,num_residuls,first_block=False):
    block = []
    for i in range(num_residuls):
        if i==0 and not first_block:
            #如果该块是两组块中的第一个块,且不是全局第2个块
            block.append(
                #要加一个1*1的卷积
                Residual(input_channels,num_channels,use_1x1conv=True,strides=2))
                #调用残差类
        else:
            block.append(
                #直接加x就可以,不用卷积
                Residual(num_channels,num_channels))
    return block

#剩下的直接调用上述的残差函数
b2 = nn.Sequential(*resnet_block(64,64,2,True))
#*是把列表展开
#第二个ResNet块,输入输出通道数不变,
#因为true,不经过卷积,直接加上x,所以输入输出尺寸形状大小不变
b3 = nn.Sequential(*resnet_block(64,128,2,False))
#第三个ResNet块,输出通道数是输入通道数2倍,
#经过步幅为2的卷积,输出尺寸形状是输入尺寸形状高和宽的1/2
b4 = nn.Sequential(*resnet_block(128,256,2,False))
#第二个ResNet块,输出通道数是输入通道数2倍,则输出尺寸形状是输入尺寸形状高和宽的1/2
b5 = nn.Sequential(*resnet_block(256,512,2,False))
#第二个ResNet块,输出通道数是输入通道数2倍,则输出尺寸形状是输入尺寸形状高和宽的1/2

#把这些模块连接起来
resnet = nn.Sequential(b1,b2,b3,b4,b5,
                       nn.AdaptiveAvgPool2d((1,1)),
                       nn.Flatten(),
                       nn.Linear(in_features=512,out_features=10))
#查看每一层输出的通道数和形状尺寸大小
X = torch.randn(size=(1,1,224,224))
for layer in resnet:
    X = layer(X)
    print(layer.__class__.__name__," output shape :\t",X.shape)


 训练模块

lr,num_epochs,batch_size = 0.05,10,256
train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size,resize=96)
d2l.train_ch6(resnet,train_iter,test_iter,num_epochs,lr,device=d2l.try_gpu())


数据增强

只在训练的时候做增强,测试的时候不做。

算是一个正则项 

上下翻转取决于数据集的样子

 数据增强,通过变形数据来获取多样性而使得模型泛化性更好

%matplotlib inline
import torch
import torchvision
from torch import nn
from d2l import torch as d2l

#获取图片
d2l.set_figsize()
img=d2l.Image.open("C:/Users/13930/Pictures/Saved Pictures/摩天轮.jpg")
d2l.plt.imshow(img);

#图像增广函数
def apply(img,aug,num_rows=2,num_cols=4,scale=1.5):
    y=[aug(img) for _ in range(num_rows*num_cols)]
    d2l.show_images(y,num_rows,num_cols,scale=scale)

#随机水平翻转
apply(img,torchvision.transforms.RandomHorizontalFlip())

#随机上下翻转
apply(img,torchvision.transforms.RandomVerticalFlip())

#随机裁剪
shape_aug = torchvision.transforms.RandomResizedCrop(size=(200,200),scale=(0.1,1),ratio=(0.5,2))
#大小200*200,裁剪出来的图片要保存多大的原始图片(10%到100%都可以),高宽比要在0.5和2之间
apply(img,shape_aug)

#随机更改图像的亮度,随机值为原始图像的50%( 1−0.5 )到150%( 1+0.5 )之间(brightness=0.5)。
bright_aug = torchvision.transforms.ColorJitter(brightness=0.5,contrast=0,saturation=0,hue=0)
apply(img,bright_aug)

#改变色调
hue_aug = torchvision.transforms.ColorJitter(brightness=0,contrast=0,saturation=0,hue=0.5)
apply(img,hue_aug)

#亮度、饱和度、色调一起改
colors_aug = torchvision.transforms.ColorJitter(brightness=0.5,contrast=0.5,saturation=0.5,hue=0.5)
apply(img,colors_aug)

#结合多种图像增广的方法
augs = torchvision.transforms.Compose([torchvision.transforms.RandomHorizontalFlip(),colors_aug,shape_aug])
apply(img,augs)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值