您的位置:首页 > 科技 > 能源 > 由浅入深,走进深度学习(补充篇:神经网络结构层基础)

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

2024/12/23 9:54:12 来源:https://blog.csdn.net/weixin_45325693/article/details/139991673  浏览:    关键词:由浅入深,走进深度学习(补充篇:神经网络结构层基础)

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

相关内容:

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

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

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

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

目录

卷积层

NiN网络

Inception块 

批量归一化

残差网络


卷积层


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

# 卷积层操作  自定义
# 互相关运算
import torch
from torch import nn
from d2l import torch as d2ldef 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) ** 2conv2d.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 nndef 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  核是1X1Y1 = 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 d2ldef 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_channelslayers.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 = 1for (num_convs, out_channels) in conv_arch:conv_blks.append(vgg_block(num_convs, in_channels, out_channels))in_channels = out_channelsreturn 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 d2ldef 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 d2lclass 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=1if use_1x1conv:self.conv3 = nn.Conv2d(input_channels, num_channels, kernel_size = 1, stride = strides)   else:self.conv3 = Noneself.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 += Xreturn 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 blkb2 = 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主“我是土堆”的视频,参考吴恩达深度学习,机器学习内容,参考李沐动手学深度学习!!!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com