全连接层
全连接层(Fully Connected Layer)可以理解为神经网络的一个隐藏层,它包含权重向量W和激活函数。具体来说,对于一张32*32*3的图片(宽和高均为32个像素,有RGB三个通道,可以将其理解为一个32*32*3的矩阵),要通过全连接层,首先要将其拉伸为3072*1的向量作为神经网络隐藏层的输入,然后该向量与权重向量 W做点乘操作,再将点乘后的结果作为激活函数(如Sigmoid或tanh)的输入,最终,激活函数输出的结果便是全连接层的最终结果。
当完成激活(activation)后的结果为一维向量时,通常将该结果称为特征向量(或激活向量);当激活后的结果为二维向量时,通常称为特征层(feature map)
卷积层
卷积层(Convolution Layer)与全连接层不同,它保留了输入图像的空间特征,即对于一张32*32*3的图片而言,卷积层的输人就是32*32*3的矩阵,不需要做任何改变。
卷积核kernel
(常简称为卷积,有时也称为波器 filter)。卷积的大小可以在实际需要时自定义其长和宽(常见的卷积神经网络中通常将其设置为1*1、3*3、5*5等),其通道个数一般设置为与输入图片通道数量一致。
让卷积(核)在输入图片上依次进行滑动,滑动方向为从左到右,从上到下;每滑动一次,卷积(核)就与其滑窗位置对应的输入图片x做一次点积计算并得到一个数值。
步长(stride):
步长是指卷积在输入图片上移动时需要移动的像素数,如步长为1时,卷积每次只移动1个像素,计算过程不会跳过任何一个像素、而步长为2时,卷积每次移动2个像素。
卷积每次滑动覆盖的格子范围在图像处理中被称为“感受野”
通用卷积层计算公式
输入矩阵大小为 w 卷积核大小为 k , 步幅为 s pad为 p
卷积函数
classtorch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True) in_channels (int) :输入图片的channel out_channels (int) :输出图片(特征层)的channel kernel_size (int or tuple) :kernel的大小 stride (int or tuple, optional) :卷积的步长,默认为1 padding (int or tuple, optional) :四周pad的大小,默认为0 dilation (int or tuple, optional) :kernel元素间的距离,默认为1(dilation翻译为扩张,有时候也称为“空洞”) groups (int, optional) :将原始输入channel划分成的组数,默认为1 bias (bool, optional) :如果是Ture,则输出的bias可学,默认为 True
池化层
池化(pooling)是对图片进行压缩(降采样)的一种方法,池化的方法有很多,如 pooling、average pooling等。池化kernel 的大小为F*F、步长为S,那么经过池化后输出的图像的宽、高、channel分别为:
import torch.nn as nn import torch.nn.functional as F class Net(nn.Module): def __init__(self): #在这里定义卷积神经网络需要的元素 super(Net, self).__init__() self.conv1 = nn.Conv2d(3, 6, 5) #定义第一个卷积层 self.pool = nn.MaxPool2d(2, 2) #池化层 self.conv2 = nn.Conv2d(6, 16, 5) #定义第二个卷积层 self.fc1 = nn.Linear(16 * 5 * 5, 120) #全连接层 self.fc2 = nn.Linear(120, 84) #全连接层 self.fc3 = nn.Linear(84, 10) #最后一个全连接层用作10分类 def forward(self, x): #使用__init__中的定义,构建卷积神经网络结构 x = self.pool(F.relu(self.conv1(x))) #第一个卷积层首先要经过relu做激活,然后使用前面定义好的nn.MaxPool2d(2, 2)方法做池化 x = self.pool(F.relu(self.conv2(x))) #第二个卷积层也要经过relu做激活,然后使用前面定义好的nn.MaxPool2d(2, 2)方法做池化 x = x.view(-1, 16 * 5 * 5) #对特征层tensor维度进行变换 x = F.relu(self.fc1(x)) #卷积神经网络的特征层经过第一次全连接层操作,然后再通过relu层激活 x = F.relu(self.fc2(x)) #卷积神经网络的特征层经过第二次全连接层操作,然后再通过relu层激活 x = self.fc3(x) #卷积神经网络的特征层经过最后一次全连接层操作,得到最终要分类的结果(10类标签) return x net = Net()
常见的神经网络结构
AlexNet的网络结构相对简单但高效,主要由卷积层、池化层和全连接层组成,具体结构如下:
-
输入层:输入图像大小为227x227x3(注意,虽然原始论文中提到的输入尺寸为224x224,但实际上进行了随机裁剪,实际大小为227x227)。
-
卷积层:
- C1:使用96个11x11x3的卷积核,步长为4,无填充(padding=0),输出特征图大小为55x55x96。
- C2:使用256个5x5x96的卷积核,步长为1,填充为2(padding=2),输出特征图大小为27x27x256。
- C3:使用384个3x3x256的卷积核,步长为1,填充为1(padding=1),输出特征图大小为13x13x384。
- C4:与C3相同,再次使用384个3x3x384的卷积核,输出特征图大小仍为13x13x384。
- C5:使用256个3x3x384的卷积核,步长为1,填充为1(padding=1),输出特征图大小为13x13x256。随后进行最大池化操作,池化核大小为3x3,步长为2,输出特征图大小为6x6x256。
-
全连接层:
- FC6:通过卷积实现的全连接层,输入为6x6x256,使用4096个6x6x256的卷积核,输出为1x1x4096,使用ReLU激活函数和Dropout防止过拟合。
- FC7:与FC6类似,输入为1x1x4096,输出为1x1x4096,同样使用ReLU激活函数和Dropout。
- FC8:输出层,使用softmax激活函数进行分类,输出为1x1x1000,对应ImageNet数据集中的1000个类别。
深度结构:AlexNet引入了较深的网络结构,相比以往的浅层网络,能够提取更多层次的抽象特征,提高了图像分类的准确性。
ReLU激活函数:使用ReLU(Rectified Linear Unit)作为激活函数,相比传统的sigmoid或tanh函数,ReLU具有更快的计算速度和更好的非线性变换能力,有助于网络更快地收敛并学习到更好的特征表示。
Dropout正则化:为了避免过拟合,AlexNet引入了Dropout技术,在训练期间随机将一定比例的神经元输出置为0,从而减少神经元之间的依赖性,提高模型的泛化能力。
数据增强:通过对训练样本进行随机翻转、裁剪、旋转等数据增强操作,扩展了训练集,增加了数据的多样性和数量,有助于减轻过拟合问题并提高模型的鲁棒性。
GPU加速:AlexNet是第一个充分利用GPU并行计算能力的深度卷积神经网络模型,通过在两个GPU上进行并行计算,极大地加快了模型的训练速度
import time import torch from torch import nn, optim import torchvision device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') class AlexNet(nn.Module): def __init__(self): super(AlexNet, self).__init__() self.conv = nn.Sequential( nn.Conv2d(1, 96, 11, 4), # in_channels, out_channels, kernel_size, stride, padding nn.ReLU(), nn.MaxPool2d(3, 2), # kernel_size, stride # 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数 nn.Conv2d(96, 256, 5, 1, 2), nn.ReLU(), nn.MaxPool2d(3, 2), # 连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,进一步增大了输出通道数。 # 前两个卷积层后不使用池化层来减小输入的高和宽 nn.Conv2d(256, 384, 3, 1, 1), nn.ReLU(), nn.Conv2d(384, 384, 3, 1, 1), nn.ReLU(), nn.Conv2d(384, 256, 3, 1, 1), nn.ReLU(), nn.MaxPool2d(3, 2) ) # 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合 self.fc = nn.Sequential( nn.Linear(256*5*5, 4096), nn.ReLU(), nn.Dropout(0.5), nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5), # 输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000 nn.Linear(4096, 10), ) def forward(self, img): feature = self.conv(img) output = self.fc(feature.view(img.shape[0], -1)) return output
VGGNET
VGGNet的网络结构可以划分为5个block,每个block由多个卷积层组成,每个卷积层后面都跟着ReLU激活函数,部分卷积层后面还跟着最大池化层。卷积层主要采用3x3的卷积核,这是VGGNet的一个重要特点,因为使用较小的卷积核可以在保持感受野不变的情况下增加网络的深度,从而提升网络的性能。
VGGNet可以看成是加深版的AlexNet,把网络分成了5段,每段都把多个尺寸为3×3的卷积核串联在一起,每段卷积接一个尺寸2×2的最大池化层,最后面接3个全连接层和一个softmax层,所有隐层的激活单元都采用ReLU函数
######################################## #第1步:载入数据 ######################################## import torch import torchvision import torchvision.transforms as transforms #使用torchvision可以很方便地下载cifar10数据集,而torchvision下载的数据集为[0, 1]的PILImage格式,我们需要将张量Tensor归一化到[-1, 1] transform = transforms.Compose( [transforms.ToTensor(), #将PILImage转换为张量 transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))] #将[0, 1]归一化到[-1, 1] ) trainset = torchvision.datasets.CIFAR10(root='./book/classifier_cifar10/data', #root表示cifar10的数据存放目录,使用torchvision可直接下载cifar10数据集,也可直接在https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz这里下载(链接来自cifar10官网) train=True, download=True, transform=transform #按照上面定义的transform格式转换下载的数据 ) trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, #每个batch载入的图片数量,默认为1 shuffle=True, num_workers=2 #载入训练数据所需的子任务数 ) testset = torchvision.datasets.CIFAR10(root='./book/classifier_cifar10/data', train=False, download=True, transform=transform) testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2) cifar10_classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck') ######################################## #查看训练数据 #备注:该部分代码可以不放入主函数 ######################################## import numpy as np dataiter = iter(trainloader) #随机从训练数据中取一些数据 images, labels = dataiter.next() images.shape #(4L, 3L, 32L, 32L) #我们可以看到images的shape是4*3*32*32,原因是上面载入训练数据trainloader时一个batch里面有4张图片 torchvision.utils.save_image(images[1],"test.jpg") #我们仅随机保存images中的一张图片看看 cifar10_classes[labels[j]] #打印label ######################################## #第2步:构建卷积神经网络 ######################################## import math import torch import torch.nn as nn cfg = {'VGG16':[64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M']} class VGG(nn.Module): def __init__(self, net_name): super(VGG, self).__init__() #构建网络的卷积层和池化层,最终输出命名features,原因是通常认为经过这些操作的输出为包含图像空间信息的特征层 self.features = self._make_layers(cfg[net_name]) #构建卷积层之后的全连接层以及分类器 self.classifier = nn.Sequential( nn.Dropout(), nn.Linear(512, 512), #fc1 nn.ReLU(True), nn.Dropout(), nn.Linear(512, 512), #fc2 nn.ReLU(True), nn.Linear(512, 10), #fc3,最终cifar10的输出是10类 ) #初始化权重 for m in self.modules(): if isinstance(m, nn.Conv2d): n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels m.weight.data.normal_(0, math.sqrt(2. / n)) m.bias.data.zero_() def forward(self, x): x = self.features(x) #前向传播的时候先经过卷积层和池化层 x = x.view(x.size(0), -1) x = self.classifier(x) #再将features(得到网络输出的特征层)的结果拼接到分类器上 return x def _make_layers(self, cfg): layers = [] in_channels = 3 for v in cfg: if v == 'M': layers += [nn.MaxPool2d(kernel_size=2, stride=2)] else: #conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1) #layers += [conv2d, nn.ReLU(inplace=True)] layers += [nn.Conv2d(in_channels, v, kernel_size=3, padding=1), nn.BatchNorm2d(v), nn.ReLU(inplace=True)] in_channels = v return nn.Sequential(*layers) net = VGG('VGG16') ######################################## #第3步:定义损失函数和优化方法 ######################################## import torch.optim as optim #x = torch.randn(2,3,32,32) #y = net(x) #print(y.size()) criterion = nn.CrossEntropyLoss() #定义损失函数:交叉熵 optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9) #定义优化方法:随机梯度下降 ######################################## #第4步:卷积神经网络的训练 ######################################## for epoch in range(5): #训练数据集的迭代次数,这里cifar10数据集将迭代2次 train_loss = 0.0 for batch_idx, data in enumerate(trainloader, 0): #初始化 inputs, labels = data #获取数据 optimizer.zero_grad() #先将梯度置为0 #优化过程 outputs = net(inputs) #将数据输入到网络,得到第一轮网络前向传播的预测结果outputs loss = criterion(outputs, labels) #预测结果outputs和labels通过之前定义的交叉熵计算损失 loss.backward() #误差反向传播 optimizer.step() #随机梯度下降方法(之前定义)优化权重 #查看网络训练状态 train_loss += loss.item() if batch_idx % 2000 == 1999: #每迭代2000个batch打印看一次当前网络收敛情况 print('[%d, %5d] loss: %.3f' % (epoch + 1, batch_idx + 1, train_loss / 2000)) train_loss = 0.0 print('Saving epoch %d model ...' % (epoch + 1)) state = { 'net': net.state_dict(), 'epoch': epoch + 1, } if not os.path.isdir('checkpoint'): os.mkdir('checkpoint') torch.save(state, './checkpoint/cifar10_epoch_%d.ckpt' % (epoch + 1)) print('Finished Training') ######################################## #第5步:批量计算整个测试集预测效果 ######################################## correct = 0 total = 0 with torch.no_grad(): for data in testloader: images, labels = data outputs = net(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() #当标记的label种类和预测的种类一致时认为正确,并计数 print('Accuracy of the network on the 10000 test images: %d %%' % (100 * correct / total)) #结果打印:Accuracy of the network on the 10000 test images: 73 % ######################################## #分别查看每个类的预测效果 ######################################## class_correct = list(0. for i in range(10)) class_total = list(0. for i in range(10)) with torch.no_grad(): for data in testloader: images, labels = data outputs = net(images) _, predicted = torch.max(outputs, 1) c = (predicted == labels).squeeze() for i in range(4): label = labels[i] class_correct[label] += c[i].item() class_total[label] += 1 for i in range(10): print('Accuracy of %5s : %2d %%' % ( cifar10_classes[i], 100 * class_correct[i] / class_total[i]))
GoogLeNet
引入了名为Inception的核心子网络结构,这是其最显著的特点。Inception模块允许网络并行地学习多个不同大小的特征,通过不同尺寸的卷积核(如1x1、3x3、5x5)和池化操作(如3x3的最大池化),将稀疏矩阵聚合为较为密集的子矩阵,从而提高了计算效率和特征提取能力。
Inception模块的设计思想在于,通过并行处理不同尺度的特征,网络能够捕捉到更丰富的信息,提高模型的预测能力。
GoogLeNet在深度和宽度上都进行了扩展,通过堆叠多个Inception模块,形成了较深的网络结构。这种设计使得网络能够在多个尺度上提取特征,增强了模型的表达能力。
全局平均池化:
GoogLeNet去除了传统的全连接层,采用了全局平均池化(Global Average Pooling, GAP)来替代。全局平均池化对每个特征图进行求平均操作,将每个特征图转化为一个单一的数值,从而减少了参数量并防止了过拟合。
辅助分类器:
GoogLeNet在网络中加入了两个辅助分类器,这些分类器在训练过程中与主分类器一起工作,有助于加速收敛并防止梯度消失。在测试时,这些辅助分类器可以被移除。
ResNet
残差神经网络(Residual Neural Network)
残差块主要由两部分构成:一个或多个卷积层和一个捷径连接(直连边,Shortcut Connection)。根据输入和输出维度是否相同,残差块可以分为标准残差块和降采样残差块。
- 标准残差块:在输入和输出维度相同的情况下,输入x通过一系列卷积层进行特征变换,得到输出F(x)。与此同时,输入x直接通过直连边传递,然后与F(x)相加得到最终的输出y,即y = F(x) + x。
- 降采样残差块:在网络的某些层,为了减小特征图的尺寸或改变通道数,需要在直连边上增加一个1x1的卷积层,用于调整输入的维度,使之与输出维度匹配,以便进行相加操作。
解决了梯度消失的问题
import torch.nn as nn import torch # 定义18层网络和34层网络的残差结构 class BasicBlock(nn.Module): # expansion对应残差结构中,主分支的卷积核数有没有发生变化 # 18层和34层的网络没有变化,50层、101层和152层的网络发生变化 expansion = 1 # downsample下采样参数,用于残差分支的尺寸维度缩放 def __init__(self, in_channel, out_channel, stride=1, downsample=None): super(BasicBlock, self).__init__() self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel, kernel_size=3, stride=stride, padding=1, bias=False) # BN层 self.bn1 = nn.BatchNorm2d(out_channel) self.relu = nn.ReLU() self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel, kernel_size=3, stride=1, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(out_channel) # 下采样方法 self.downsample = downsample def forward(self, x): # 分支线上的输出 # 将x赋值给identity,捷径上不执行下采样的输出值 identity = x # 判断downsample=None,对捷径执行下采样操作并输出 if self.downsample is not None: identity = self.downsample(x) # 主支线上的输出 out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.conv2(out) out = self.bn2(out) # 主分支输出加上捷径分支输出 out += identity out = self.relu(out) return out # 定义50层网络、101层网络和152层网络的残差结构 class Bottleneck(nn.Module): # expansion对应残差结构中,主分支的卷积核数有没有发生变化 # 50层、101层和152层的网络发生变化,其残差结构中第三层的卷积核个数为前两层的四倍,例如64—64—256 expansion = 4 def __init__(self, in_channel, out_channel, stride=1, downsample=None): super(BasicBlock, self).__init__() self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel, kernel_size=1, stride=1, bias=False) self.bn1 = nn.BatchNorm2d(out_channel) # ----------------------------------------- self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel, kernel_size=3, stride=stride, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(out_channel) # ----------------------------------------- self.conv3 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel*self.expansion, kernel_size=1, stride=1, bias=False) self.bn3 = nn.BatchNorm2d(out_channel*self.expansion) self.relu = nn.ReLU(inplace=True) self.dowmsample = downsample def forward(self, x): identity = x if self.dowmsample is not None: identity = self.dowmsample(x) out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.conv2(out) out = self.bn2(out) out = self.relu(out) out = self.conv3(out) out = self.bn3(out) out += identity out = self.relu(out) return out # 定义ResNet网络 class ResNet(nn.Module): # block:残差结构 block_num(list):残差结构数量 include_top=True:方便在ResNet上搭建其他网络 def __init__(self, block, block_num, num_classes=1000, include_top=True): super(ResNet, self).__init__() self.include_top = include_top self.in_channel = 64 self.conv1 = nn.Conv2d(3, self.in_channel, kernel_size=7, stride=2, padding=3, bias=False) self.bn1 = nn.BatchNorm2d(self.in_channel) self.relu = nn.ReLU(inplace=True) self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) self.layer1 = self._make_layer(block, 64, block_num[0]) self.layer2 = self._make_layer(block, 128, block_num[1], stride=2) self.layer3 = self._make_layer(block, 256, block_num[2], stride=2) self.layer4 = self._make_layer(block, 512, block_num[3], stride=2) # 输出层+全连接层 if self.include_top: self.avepool = nn.AdaptiveAvgPool2d((1, 1)) self.fc = nn.Linear(512 * block.expansion, num_classes) # 对卷积层初始化 for m in self.modules(): if isinstance(m, nn.Conv2d): nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') # 定义生成残差结构的方法 # block:残差结构 channel:第一层卷积核的个数 block_num:残差结构数量 def _make_layer(self, block, channel, block_num, stride=1): downsample = None # 判断通道数是否发生变化,来执行下采样操作 if stride != 1 or self.in_channel != channel * block.expansion: downsample = nn.Sequential( nn.Conv2d(self.in_channel, channel * block.expansion, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(channel * block.expansion)) layers = [] # 添加第一层残差结构 layers.append(block(self.in_channel, channel, downsample=downsample, stride=stride)) # 根据expansion来生成实线和虚线的残差结构 self.in_channel = channel * block.expansion # 残差结构中除了第一层均为实线结构,将其依次添加到layers中 for _ in range(1, block_num): # 从1开始,即实线残差结构从第二层开始 layers.append(block(self.in_channel, channel)) return nn.Sequential(*layers) # 正向传播过程 def forward(self, x): x = self.conv1(x) x = self.bn1(x) x = self.relu(x) x = self.maxpool(x) x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.layer4(x) if self.include_top: x = self.avepool(x) x = torch.flatten(x, 1) x = self.fc(x) return x def resnet18(num_classes=1000, include_top=True): return ResNet(BasicBlock, [2, 2, 2, 2], num_classes=num_classes, include_top=include_top) def resnet34(num_classes=1000, include_top=True): return ResNet(BasicBlock, [3, 4, 6, 3], num_classes=num_classes, include_top=include_top) def resnet50(num_classes=1000, include_top=True): return ResNet(Bottleneck, [3, 4, 6, 3], num_classes=num_classes, include_top=include_top) def resnet101(num_classes=1000, include_top=True): return ResNet(Bottleneck, [3, 4, 23, 3], num_classes=num_classes, include_top=include_top) def resnet152(num_classes=1000, include_top=True): return ResNet(Bottleneck, [3, 8, 36, 3], num_classes=num_classes, include_top=include_top)
标签:nn,卷积,self,神经网络,num,channel,out From: https://www.cnblogs.com/candice1/p/18347680