由浅入深,走进深度学习(补充篇:神经网络结构层基础)

本期内容是针对神经网络层结构的一个补充

相关内容:

由浅入深,走进深度学习(2)_卷积层-CSDN博客

由浅入深,走进深度学习(补充篇:神经网络基础)-CSDN博客

在这里我也是对自己做一个学习记录,如果不符合大家的口味,大家划走就可以啦

可能没有什么文字或者原理上的讲解,基本上都是代码,但是我还是想说,如果基础不是很好,认认真真敲一遍,会有不一样的感受!!

目录

卷积层

NiN网络

Inception块 

批量归一化

残差网络


卷积层


假如就是要看一个3 * 3的局部信息的话   卷积核就是一个 3 * 3 的矩阵   不会因为输入变得特别大导致权重变得特别大
卷积层将输入和核矩阵进行交叉相关  加上偏移后得到输出
核矩阵核偏移是可学习的参数
核矩阵的大小是超参数

# 卷积层操作  自定义
# 互相关运算
import torch
from torch import nn
from d2l import torch as d2l

def corr2d(X, K):  # X 为输入  K为核矩阵
    # X 为输入  K为核矩阵
    h, w = K.shape  # 核矩阵的行数和列数
    Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))  # X.shape[0]为输入高   
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            Y[i, j] = (X[i:i + h, j:j + w] * K).sum()  # 图片的小方块区域与卷积核做点积

    return Y

# 验证上述二维互相关运算的输出
X = torch.tensor([[0.0, 1.0, 2.0],
                  [3.0, 4.0, 5.0],
                  [6.0, 7.0, 8.0]])
K = torch.tensor([[0.0, 1.0],
                  [2.0, 3.0]])
corr2d(X, K)

# 实现二维卷积层
class Conv2D(nn.Module):
    def __init__(self, kernel_size):
        self.weight = nn.Parameter(torch.rand(kernel_size))
        self.bias = nn.Parameter(torch.zeros(1))

    def forward(self, x):
        return corr2d(x, self.weight) + self.bias

# 卷积层的一个简单应用  检测图片中不同颜色的边缘
X = torch.ones((6, 8))
X[:, 2:6] = 0  # 把中间四列设置为0
print('X:', X)  # 0 与 1 之间进行过渡   表示边缘

K = torch.tensor([[1.0, -1.0]])   # 如果左右原值相等   那么这两原值乘1和-1相加为0   则不是边缘
Y = corr2d(X, K)
print('Y:', Y)
print(corr2d(X.t(), K))   # X.t() 为X的转置   而K卷积核只能检测垂直边缘


# 学习由X生成Y的卷积核
conv2d = nn.Conv2d(1, 1, kernel_size = (1, 2), bias = False)  # 单个矩阵  输入通道为1  黑白图片通道为1  彩色图片通道为3  这里输入通道为1  输出通道为1
X = X.reshape((1, 1, 6, 8))   # 通道维:通道数  RGB图3通道  灰度图1通道  批量维就是样本维   就是样本数
Y = Y.reshape((1, 1, 6, 7))
for i in range(10):
    Y_hat = conv2d(X)
    l = (Y_hat - Y) ** 2
    conv2d.zero_grad()
    l.sum().backward()
    conv2d.weight.data[:] -= 3e-2 * conv2d.weight.grad  # 3e-2是学习率
    if(i + 1) % 2 == 0:
        print(f'batch {i + 1}, loss {l.sum():.3f}')

# 所学的卷积核的权重张量
print(conv2d.weight.data.reshape((1,2)))

卷积里的 填充和步幅
奇数卷积核更容易做padding  我们假设卷积核大小为k * k
为了让卷积后的图像大小与原图一样大  根据公式可得到padding=(k-1)/2
这里的k只有在取奇数的时候   padding才能是整数   否则padding不好进行图片填充
k为偶数时  p为浮点数  所做的操作为一个为向上取整  填充  一个为向下取整  填充
填充和步幅是卷积层的超参数
填充在输入周围添加额外的行/列  来控制输出形状的减少量
步幅是每次滑动核窗口时的行/列的步长  可以成倍的减少输出形状

# 在所有侧边填充1个像素
import torch
from torch import nn

def comp_conv2d(conv2d, X):  # conv2d 作为传参传进去  在内部使用
    X = X.reshape((1, 1) + X.shape)   # 在维度前面加入一个通道数和批量大小数
    Y = conv2d(X)   # 卷积处理是一个四维的矩阵
    return Y.reshape(Y.shape[2:])  # 将前面两个维度拿掉

conv2d = nn.Conv2d(1, 1, kernel_size = 3, padding = 1)  # padding=1 为左右都填充一行
X = torch.rand(size = (8, 8))
print(comp_conv2d(conv2d,X).shape)
conv2d = nn.Conv2d(1, 1, kernel_size = (5, 3), padding = (2, 1))
print(comp_conv2d(conv2d,X).shape)

# 将高度和宽度的步幅设置为2
conv2d = nn.Conv2d(1, 1, kernel_size = 3, padding = 2)
print(comp_conv2d(conv2d,X).shape)

# 一个稍微复杂的例子
conv2d = nn.Conv2d(1, 1, kernel_size = (3, 5), padding = (0, 1), stride = (3, 4))
print(comp_conv2d(conv2d,X).shape)
# 多个输入通道
# 核的通道数与输入的通道数一样
# 每个输出通道可以匹配图片里面特定的模式
# 把每个通道里面识别出来的模式组合起来  就得到组合模式识别
# 输出通道数是卷积层的超参数
# 每个输入通道有独立的二维卷积核  所有通道结果相加得到一个输出通道结果
# 每个输出通道有独立的三维卷积核

# 输入与输出   自定义
# 多输入通道互相关运算
import torch
from d2l import torch as d2l
from torch import nn

# 多通道输入运算
def corr2d_multi_in(X, K):
    return sum(d2l.corr2d(x, k) for x, k in zip(X, K)) # X,K为3通道矩阵   for使得对最外面通道进行遍历        

X = torch.tensor([[[0.0, 1.0, 2.0],
                   [3.0, 4.0, 5.0],
                   [6.0, 7.0, 8.0]],
                  [[1.0, 2.0, 3.0],
                   [4.0, 5.0, 6.0],
                   [7.0, 8.0, 9.0]]])
K = torch.tensor([[[0.0, 1.0],
                   [2.0, 3.0]],
                  [[1.0, 2.0],
                   [3.0, 4.0]]])

print(corr2d_multi_in(X, K))

# 多输出通道运算
def corr2d_multi_in_out(X,K):  # X为3通道矩阵   K为4通道矩阵   最外面维为输出通道      
    return torch.stack([corr2d_multi_in(X,k) for k in K],0) # 大k中每个小k是一个3D的Tensor   0表示stack堆叠函数里面在0这个维度堆叠           

print(K.shape)
print((K + 1).shape)
print((K + 2).shape)
print(K)
print(K + 1)
K = torch.stack((K, K + 1, K + 2),0) # K与K+1之间的区别为K的每个元素加1
print(K.shape)
print(corr2d_multi_in_out(X, K))
# 1×1 卷积
# 1×1卷积的多输入   多输出通道运算
def corr2d_multi_in_out_1x1(X, K):
    c_i, h, w = X.shape # 输入的通道数、宽、高
    c_o = K.shape[0]    # 输出的通道数
    X = X.reshape((c_i, h * w)) # 拉平操作   每一行表示一个通道的特征
    K = K.reshape((c_o, c_i)) 
    Y = torch.matmul(K, X) 
    return Y.reshape((c_o, h, w))

X = torch.normal(0, 1, (3, 3, 3))   # norm函数生成0到1之间的(3,3,3)矩阵 
K = torch.normal(0, 1, (2, 3, 1, 1)) # 输出通道是2  输入通道是3  核是1X1

Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
assert float(torch.abs(Y1 - Y2).sum()) < 1e-6
print(float(torch.abs(Y1 - Y2).sum()))


# 1×1 卷积  使用框架
def comp_conv2d(conv2d, X): # conv2d 作为传参传进去,在内部使用
    X = X.reshape((1, 1) + X.shape) # 在维度前面加入一个通道数和批量大小数
    Y = conv2d(X)  # 卷积处理是一个四维的矩阵
    return Y.reshape(Y.shape[2:]) # 将前面两个维度拿掉

X = torch.rand(size = (8, 8))
conv2d = nn.Conv2d(1, 1, kernel_size = 3, padding = 1, stride = 2)  # Pytorch里面卷积函数的第一个参数为输出通道   第二个参数为输入通道   
print(comp_conv2d(conv2d, X).shape) 

conv2d = nn.Conv2d(1,1,kernel_size = (3, 5), padding = (0, 1), stride = (3, 4)) # 一个稍微复杂的例子
print(comp_conv2d(conv2d, X).shape)
# VGG使用可重复使用的卷积块来构建深度卷积神经网络
# 不同的卷积块个数和超参数可以得到不同复杂度的变种
# VGG网络  自定义
import torch
from torch import nn
from d2l import torch as d2l

def vgg_block(num_convs, in_channels, out_channels):
    layers = []
    for _ in range(num_convs):
        layers.append(nn.Conv2d(in_channels, out_channels, kernel_size = 3, padding = 1))
        layers.append(nn.ReLU())
        in_channels = out_channels
    layers.append(nn.MaxPool2d(kernel_size = 2, stride = 2))
    return nn.Sequential(*layers)

conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))

def vgg(conv_arch):
    conv_blks = []
    in_channels = 1
    for (num_convs, out_channels) in conv_arch:
        conv_blks.append(vgg_block(num_convs, in_channels, out_channels))
        in_channels = out_channels

    return nn.Sequential(*conv_blks, nn.Flatten(),
                         nn.Linear(out_channels * 7 * 7, 4096),
                         nn.ReLU(),
                         nn.Dropout(0.5),
                         nn.Linear(4096, 4096),
                         nn.ReLU(),
                         nn.Dropout(0.5),
                         nn.Linear(4096, 10))

net = vgg(conv_arch)

# 观察每个层输出的形状
X = torch.randn(size = (1, 1, 224, 224))
for blk in net:
    X = blk(X)
    print(blk.__class__.__name__,'output shape:\t', X.shape) # VGG使得高宽减半  通道数加倍
# 由于VGG-11比AlexNet计算量更大  因此构建了一个通道数较少的网络
ratio = 4
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]  # 所有输出通道除以4
net = vgg(small_conv_arch)

lr, num_epochs, batch_size = 0.05, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize = 224)
d2l.train_ch6(net,train_iter,test_iter,num_epochs,lr,d2l.try_gpu())

NiN网络


一个卷积层后跟两个全连接层
步幅1 无填充 输出形状跟卷积层输出一样
起到全连接作用

NiN架构
无全连接层

交替使用NiN块和步幅为2的最大池化层
逐步减小高宽和增大通道数
最后使用全局平均池化层得到输出
其输入通道数是类别数


在全局平均池化层(GAP)被提出之前  常用的方式是将feature map直接拉平成一维向量
但是GAP不同  是将每个通道的二维图像做平均  最后也就是每个通道对应一个均值
假设卷积层的最后输出是h × w × d 的三维特征图   具体大小为6 × 6 × 3   
经过GAP转换后  变成了大小为 1 × 1 × 3 的输出值  也就是每一层 h × w 会被平均化成一个值

NiN块使用卷积层加两个1×1卷积层   后者对每个像素增加了非线性性
NiN使用全局平均池化层来替代VGG和AlexNet中的全连接层  不容易过拟合 更少的参数个数
 

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

def nin_block(in_channels, out_channels, kernel_size, strides, padding):
    return nn.Sequential(nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding),
                         nn.ReLU(), 
                         nn.Conv2d(out_channels, out_channels, kernel_size = 1),
                         nn.ReLU(), 
                         nn.Conv2d(out_channels, out_channels, kernel_size = 1),
                         nn.ReLU())

net = nn.Sequential(nin_block(1, 96, kernel_size = 11,strides = 4, padding = 0),
                   nn.MaxPool2d(3, stride = 2),
                   nin_block(96, 256, kernel_size = 5, strides = 1, padding = 2),
                   nn.MaxPool2d(3, stride = 2),
                   nin_block(256, 384, kernel_size = 3, strides = 1, padding = 1),
                   nn.MaxPool2d(3, stride = 2), nn.Dropout(0.5),
                   nin_block(384, 10, kernel_size = 3, strides = 1, padding = 1),
                   nn.AdaptiveAvgPool2d((1,1)),
                   nn.Flatten())

# 查看每个块的输出形状
X = torch.rand(size = (1, 1, 224, 224))
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__, 'output shape:\t', X.shape)

Inception块 


Inception块使用了大量1X1卷积层  使得参数相对单3X3、5X5卷积层更少
跟单3×3或5×5卷积层比  Inception块更少的参数个数和计算复杂度

批量归一化

固定小批量中的均值和方差  然后学习出适合的偏移和缩放
可以加速收敛速度  但一般不改变模型精度

残差网络


残差块使得很深的网络更加容易训练  甚至可以训练一千层的网络
残差网络对随后的深层神经网络设计产生了深远影响  无论是卷积类网络还是全连接网络

# ReaNet网络  自定义
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l

class Residual (nn.Module):
    def __init__(self, input_channels, num_channels, use_1x1conv = False, strides = 1): # num_channels为输出channel数  
        super().__init__()
        self.conv1 = nn.Conv2d(input_channels, num_channels, kernel_size = 3, padding = 1, stride = strides) # 可以使用传入进来的strides 
        self.conv2 = nn.Conv2d(num_channels, num_channels, kernel_size = 3, padding = 1)   # 使用nn.Conv2d默认的strides=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)
        self.relu = nn.ReLU(inplace = True) # inplace原地操作  不创建新变量  对原变量操作  节约内存

    def forward(self, X):
        Y = F.relu(self.bn1(self.conv1(X)))
        Y = self.bn2(self.conv2(Y))
        if self.conv3:
            X = self.conv3(X)
        Y += X
        return F.relu(Y)

# 输入和输出形状一致
blk = Residual(3, 3) # 输入三通道  输出三通道
X = torch.rand(4, 3, 6, 6) 
Y = blk(X) # stride用的默认的1  所以宽高没有变化  如果strides用2  则宽高减半
print(Y.shape)

# 增加输出通道数的同时  减半输出的高和宽
blk1 = Residual(3, 6, use_1x1conv = True, strides = 2)  # 由3变为6  通道数加倍
print(blk1(X).shape)

# ResNet的第一个stage
b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size = 7, stride = 2, padding = 3),
                  nn.BatchNorm2d(64),nn.ReLU(),
                  nn.MaxPool2d(kernel_size = 3, stride = 2, padding = 1))

# class Residual为小block   resnet_block 为大block   为Resnet网络的一个stage
def resnet_block(input_channels, num_channels, num_residuals, first_block = False):
    blk = []
    for i in range(num_residuals):
        if i == 0 and not first_block: # stage中不是第一个block则高宽减半
            blk.append(Residual(input_channels, num_channels, use_1x1conv = True, strides = 2))   
        else:
            blk.append(Residual(num_channels, num_channels))
    return blk

b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block = True)) # 因为b1做了两次宽高减半  nn.Conv2d、nn.MaxPool2d  所以b2中的首次就不减半了      
b3 = nn.Sequential(*resnet_block(64, 128, 2)) # b3、b4、b5的首次卷积层都减半
b4 = nn.Sequential(*resnet_block(128, 256, 2))
b5 = nn.Sequential(*resnet_block(256, 512, 2))

net = nn.Sequential(b1, b2, b3, b4, b5, nn.AdaptiveAvgPool2d((1,1)), nn.Flatten(), nn.Linear(512,10))    

# 观察一下ReNet中不同模块的输入形状是如何变化的
X = torch.rand(size = (1, 1, 224, 224))
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__,'output shape:\t',X.shape) # 通道数翻倍、模型减半

注:上述内容参考b站up主“我是土堆”的视频,参考吴恩达深度学习,机器学习内容,参考李沐动手学深度学习!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今天不想Debug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值