卷积是寻找特征 池化是压缩数据 激活是加强特征
一、卷积
Feature Map
在卷积神经网络中,特征图(feature map)是经过卷积核提取后保留下来的关键信息的空间表示,反映了输入数据中的某些特征模式(如边缘、纹理或高层语义信息)。它通过逐层处理,从简单到复杂逐步提炼特征,是网络理解和学习数据的核心输出。
dCNN于通过感受野和权值共享减少了神经网络需要训练的参数的个数。
上图左:如果我们有1000x1000像素的图像,有1百万个隐层神经元,那么他们全连接的话,就会输出10^12个权值参数,图像的空间联系是局部的,它不需要全局遍历来提取每一点的特征。由感受野我们可以采用上图右的方式进行局部链接,每个神经元只感受特定的区域,在更高层综合起来。
weight sharing
通过权值共享,模型可以在处理类似任务或者特征的情况下复用已学到的知识,对于如卷积神经网络(CNN)中的卷积层尤其常见,它们的滤波器(权重矩阵)可以沿时间或空间维度共享,有效地提取图像或序列数据的局部特征。
1D Convolution
2D Convolution
Convolution in Computer vision
通过卷积核对图像进行一系列操作。
Sharpen Blur Edge Detect
二、卷积神经网络
单通道的 Kernel size
三个通道1个核
一个经典的图,随着左边数据窗口的平移滑动,滤波器Filter w0 / Filter w1对不同的局部数据进行卷积计算「即分别先后以两个不同滤波器filter为轴,滑动数组进行卷积计算」,得到两组不同的结果。
卷积神经网络各个层级如下图所示
三、VGG网络结构
VGGNet是牛津大学视觉几何组(Visual Geometry Group)提出的模型,多层3×3卷积核取代大卷积核网络深度至16,19层,然后逐步加宽。减少所需的训练的参数
AlexNet比LeNet更深更大来带来更好的精度。能不能更深更大,更多全连接层?太贵。更多的卷积层?VGG用卷积块。
LeNet :2卷积+池化层
AlexNet:更大更深,ReLu,Dropout,数据增强
VGG:更大更深的AlexNet
AlexNet网络不规则,VGG提出块的想法。VGG使用可重复的卷积块来构建深度卷积神经网络,不同的卷积块个数和超参数可以得到不同复杂度的计算
下面是学习链接:4.1 VGG网络详解及感受野的计算_哔哩哔哩_bilibili l
空间池化由5个最大池化层执行,最大池化在2 × 2像素窗口上执行,步长为2
一堆卷积层(在不同的架构中具有不同的深度)后面是三个全连接(FC)层:前两个层各有 4096 个通道,第三个执行 1000 路 ILSVRC 分类,因此包含 1000 个通道。 最后一层是soft-max层。 所有网络中全连接层的配置都是相同的。
每个模型的参数数量,虽然我们的模型小而深但是参数权重仍然小于等于浅而宽,大感受野的网络。
论文提到,可以通过堆叠3*3卷积核来代替5*5的卷积核,那么来计算所需参数。
7*7*C*C=49C^2
3*3*C*C+3*3*C*C+3*3*C*C=27C^2
可以看到计算量、权重参数量下降,层数越深,非线性表达能力越强。
论文中的训练:
训练是通过使用带有动量的小批量梯度下降,优化多项逻辑回归目标来进行的。 批量大小设置为 256,动量设置为 0.9。训练通过权重衰减(L2 惩罚乘数设置为 5·10−4)和前两个全连接层的 dropout 正则化(dropout 比率设置为 0.5)进行正则化。 学习率最初设置为 10−2。然后当验证集准确性停止提高时降低 10 倍。
网络权重的初始化很重要,我们从训练配置 A(表 1)开始,该配置足够浅,可以通过随机初始化进行训练。 然后,在训练更深层次的架构时,我们用网络 A 的层初始化前四个卷积层和最后三个全连接层(中间层随机初始化)。
为了获得固定大小的 224×224ConvNet 输入图像,它们是从重新缩放的训练图像中随机裁剪的。 为了进一步增强训练集,对作物进行了随机水平翻转和随机 RGB 颜色偏移。
可以看到最深的E模型效果最好,深度有利于分类精度。
冻结预训练模型的特定层: 如果主要目标是利用预训练模型的特征提取能力,可以冻结部分卷积层参数,减少训练时间。
四、实战
建议从五、六开始看。
本次实战结合李沐老师教程及B站up主,下面是链接
25 使用块的网络 VGG【动手学深度学习v2】_哔哩哔哩_bilibili
7.2. 使用块的网络(VGG) — 动手学深度学习 2.0.0 documentation (d2l.ai)
4.2 使用pytorch搭建VGG网络_哔哩哔哩_bilibili
pytorch实战7:手把手教你基于pytorch实现VGG16_vgg16 pytorch-CSDN博客
经典卷积神经网络的基本组成部分是下面的这个序列:
-
带填充以保持分辨率的卷积层;
-
非线性激活函数,如ReLU;
-
汇聚层,如最大汇聚层。
而一个VGG块与之类似,由一系列卷积层组成,后面再加上用于空间下采样的最大汇聚层。在最初的VGG论文中 (Simonyan and Zisserman, 2014),作者使用了带有3×3卷积核、填充为1(保持高度和宽度)的卷积层,和带有2×2汇聚窗口、步幅为2(每个块后的分辨率减半)的最大汇聚层。
对VGG网络框架结构的配置。M是池化层,64表示64个卷积核。
cfgs = {
'vgg11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
'vgg13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
'vgg16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
'vgg19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
}
def make_features(cfg: list):
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(True)]
in_channels = v
return nn.Sequential(*layers)
生成提取特征网络函数。定义空列表然后定义channels,因为RGB通道数是3,紧接这通过循环遍历配置列表,若是池化层,则创建配置一个最大池化下采样层。若是卷积核,创建卷积操作。stride默认为1.每一个卷积层采用ReLU激活函数。然后通过非关键字参数传递到函数。函数Sequential(*layers)。
论文中也提到参数初始化的重要性,论文是在正态分布随机选取,传统的固定方差的高斯分布初始化方法,在网络变深的时候会使得模型很难收敛。我们选用He 初始化
参数初始化
if init_weight: # 如果进行参数初始化
for m in self.modules(): # 对于模型的每一层
if isinstance(m, nn.Conv2d): # 如果是卷积层
# 使用kaiming初始化
nn.init.kaiming_normal_(m.weight, mode="fan_out", nonlinearity="relu")
# 如果bias不为空,固定为0
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):# 如果是线性层
# 正态初始化
nn.init.normal_(m.weight, 0, 0.01)
# bias则固定为0
nn.init.constant_(m.bias, 0)
正向传播过程,x是输入图像数据,将图像数据通过特征提取结构得到输出,然后进行展平输出,再输出到分类结构。
再构建DataSet,获取到我们的**图像路径和相应标签,
class My_Dataset(Dataset):
def __init__(self,filename,transform=None):
self.filename = filename # 文件路径
self.transform = transform # 是否对图片进行变化
self.image_name,self.label_image = self.operate_file()
def __len__(self):
return len(self.image_name)
def __getitem__(self,idx):
# 由路径打开图片
image = Image.open(self.image_name[idx])
# 下采样: 因为图片大小不同,需要下采样为224*224
trans = transforms.RandomResizedCrop(224)
image = trans(image)
# 获取标签值
label = self.label_image[idx]
# 是否需要处理
if self.transform:
image = self.transform(image)
# 转为tensor对象
label = torch.from_numpy(np.array(label))
return image,label
def operate_file(self):
# 获取所有的文件夹路径 '../data/net_train_images'的文件夹
dir_list = os.listdir(self.filename)
# 拼凑出图片完整路径 '../data/net_train_images' + '/' + 'xxx.jpg'
full_path = [self.filename+'/'+name for name in dir_list]
# 获取里面的图片名字
name_list = []
for i,v in enumerate(full_path):
temp = os.listdir(v)
temp_list = [v+'/'+j for j in temp]
name_list.extend(temp_list)
# 由于一个文件夹的所有标签都是同一个值,而字符值必须转为数字值,因此我们使用数字0-4代替标签值
label_list = []
temp_list = np.array([0,1,2,3,4],dtype=np.int64) # 用数字代表不同类别
# 将标签每个复制200个
for j in range(5):
for i in range(200):
label_list.append(temp_list[j])
return name_list,label_list
调整学习率,在论文中,明确提及了学习率自动调整,即前期使用大学习率,当误差收敛或在某值跳动时,降低学习率。添加一个函数,输入参数为损失值,记录当前的损失值变化情况如何,当变化波动很小时,便通过函数改变学习率的大小即可。
loss_save = []
flag = 0
lr = 0.0002
def adjust_lr(loss):
global flag,lr
loss_save.append(loss)
if len(loss_save) >= 2:
# 如果已经训练了2次,可以判断是否收敛或波动
if abs(loss_save[-1] - loss_save[-2]) <= 0.0005:
# 如果变化范围小于0.0005,说明可能收敛了
flag += 1
if loss_save[-1] - loss_save[-2] >= 0:
# 如果损失值增加,也记一次
flag += 1
if flag >= 3:
# 如果出现3次这样的情况,需要调整学习率
lr /= 10
print('学习率已改变,变为了%s' % (lr))
# 并将flag清为0
flag = 0
训练阶段
def main():
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print("using {} device.".format(device))
data_transform = {
"train": transforms.Compose([transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),
"val": transforms.Compose([transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}
对训练集和验证集预处理,随机裁剪,随机水平翻转,标准化。【123.68,116.78,103.94】
CUDA 内存不足(OutOfMemoryError
)。在使用 GPU 进行训练时,PyTorch 会在 GPU 上分配显存。
--------------------------------------------------------------------------- OutOfMemoryError Traceback (most recent call last) Cell In[6], line 2 1 if __name__ == '__main__': ----> 2 train() Cell In[5], line 24, in train() 22 optimizer.zero_grad() 23 # 模型训练 ---> 24 prediction = model(batch_data) 25 # 损失值 26 loss = loss_func(prediction,batch_label) OutOfMemoryError: CUDA out of memory. Tried to allocate 98.00 MiB. GPU 0 has a total capacity of 4.00 GiB of which 0 bytes is free. Of the allocated memory 3.21 GiB is allocated by PyTorch, and 195.02 MiB is reserved by PyTorch but unallocated. If reserved but unallocated memory is large try setting PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True to avoid fragmentation. See documentation for Memory Management (CUDA semantics — PyTorch 2.5 documentation)
我尝试修改了优化器,改为了SGD优化器并且降低了batch_size大小(由原来的32改为了10,5):都显示不出来
加载官方预训练的VGG16模型:方便效率高
def load_pretrained():
path = 'F:/官方_预训练模型/vgg16-397923af.pth'
model = vgg16() # 来自 from torchvision.models import vgg16
model.load_state_dict(torch.load(path))
return model
训练过程
def train():
batch_size = 10 # 批量训练大小
model = My_VGG16() # 创建模型
# 加载预训练vgg
# model = load_pretrained()
# 定义优化器
optimizer = optim.SGD(params=model.parameters(), lr=lr)
# 将模型放入GPU中
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device)
# 定义损失函数
loss_func = nn.CrossEntropyLoss()
# 加载数据
train_set = My_Dataset('../data/net_train_images',transform=transforms.ToTensor())
train_loader = DataLoader(train_set, batch_size, shuffle=True)
# 训练20次
for i in range(20):
loss_temp = 0 # 临时变量
for j,(batch_data,batch_label) in enumerate(train_loader):
# 数据放入GPU中
batch_data,batch_label = batch_data.cuda(),batch_label.cuda()
# 梯度清零
optimizer.zero_grad()
# 模型训练
prediction = model(batch_data)
# 损失值
loss = loss_func(prediction,batch_label)
loss_temp += loss.item()
# 反向传播
loss.backward()
# 梯度更新
optimizer.step()
# 每25个批次打印一次损失值
print('[%d] loss: %.4f' % (i+1,loss_temp/len(train_loader)))
# 是否调整学习率,如果调整的话,需要把优化器也移动到循环内部
# adjust_lr(loss_temp/len(train_loader))
# torch.save(model,'VGG16.pkl')
test(model)
第一次训练结果,并且发现实际是CPU在运行。
[1] loss: 1.7897 [2] loss: 1.5357 [3] loss: 1.4489 [4] loss: 1.4368 [5] loss: 1.3647 [6] loss: 1.3497 [7] loss: 1.3319 [8] loss: 1.3207 [9] loss: 1.2675 [10] loss: 1.2986 [11] loss: 1.3009 [12] loss: 1.2588 [13] loss: 1.2684 [14] loss: 1.2596 [15] loss: 1.2429 [16] loss: 1.2235 [17] loss: 1.2308 [18] loss: 1.2023 [19] loss: 1.2057 [20] loss: 1.1677 准确率: 44.20 %
发现学习率不尽人意。可能是轮数不够,对模型继续进行50次epoch
[1] loss: 1.8411 [2] loss: 1.5187 [3] loss: 1.4280 [4] loss: 1.3844 [5] loss: 1.3788 [6] loss: 1.3334 [7] loss: 1.3388 [8] loss: 1.3224 [9] loss: 1.2985 [10] loss: 1.2744 [11] loss: 1.2775 [12] loss: 1.2773 [13] loss: 1.2299 [14] loss: 1.2595 [15] loss: 1.2658 [16] loss: 1.2100 [17] loss: 1.2255 [18] loss: 1.2158 [19] loss: 1.2001 [20] loss: 1.2082 [21] loss: 1.1808 [22] loss: 1.1976 [23] loss: 1.1582 [24] loss: 1.1657 [25] loss: 1.1708 [26] loss: 1.1532 [27] loss: 1.1554 [28] loss: 1.1391 [29] loss: 1.1411 [30] loss: 1.1361 [31] loss: 1.1171 [32] loss: 1.1275 [33] loss: 1.1184 [34] loss: 1.1189 [35] loss: 1.0958 [36] loss: 1.1425 [37] loss: 1.1013 [38] loss: 1.0890 [39] loss: 1.0719 [40] loss: 1.0879 [41] loss: 1.1126 [42] loss: 1.1011 [43] loss: 1.0875 [44] loss: 1.0569 [45] loss: 1.0913 [46] loss: 1.0769 [47] loss: 1.0445 [48] loss: 1.0713 [49] loss: 1.0756 [50] loss: 1.0871 准确率: 51.80 % 结果有些许进步,是不是官方预训练模型未加载。(发现确实未加载)加载之后发现显存不够,后续考虑对Google Colab进行学习。
五、VGG11
与他们一样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)
实现VGG块
sconv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))这是VGG11组成。
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)
调用vgg_block将卷积层构建出来,然后展平,
构建一个高,宽为224的单通道数据样本
X = torch.randn(size=(1, 1, 224, 224))
for blk in net:
X = blk(X)
print(blk.__class__.__name__,'output shape:\t',X.shape)
得到结果。
Sequential output shape: torch.Size([1, 64, 112, 112]) Sequential output shape: torch.Size([1, 128, 56, 56]) Sequential output shape: torch.Size([1, 256, 28, 28]) Sequential output shape: torch.Size([1, 512, 14, 14]) Sequential output shape: torch.Size([1, 512, 7, 7]) Flatten output shape: torch.Size([1, 25088]) Linear output shape: torch.Size([1, 4096]) ReLU output shape: torch.Size([1, 4096]) Dropout output shape: torch.Size([1, 4096]) Linear output shape: torch.Size([1, 4096]) ReLU output shape: torch.Size([1, 4096]) Dropout output shape: torch.Size([1, 4096]) Linear output shape: torch.Size([1, 10])
我们构建了一个通道数较少的网络,足够用于训练Fashion-MNIST数据集。这里选择batch_size=128,每次更新会更平稳,训练更加稳定。
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())
结果模型在训练和测试集上表现优秀,训练准确率为93.7%,测试准确率为91.7%,并且在GPU上运行速度较快(每秒处理483.4个样本)。
运行了30min
loss 0.170, train acc 0.937, test acc 0.917 483.4 examples/sec on cuda:0
将Fashion-MNIST数据集图像的高度和宽度从224改为96
使用 96x96
作为输入尺寸时,我们需要确保第一个全连接层的输入维度与卷积和池化操作后的输出尺寸匹配。512*3*3=4068
时间25min
96x96的图像尺寸在每个训练周期(epoch)处理的图像数量(examples/sec)显著高于224x224。具体来说,96x96的训练速度大约是224x224的4.35倍。
输入图像尺寸从224x224降到96x96会导致模型的计算量显著减少。卷积层的计算量和后续全连接层的输入特征维度都会变小,进而减少了训练过程中的计算负担和内存消耗。
特征图尺寸变小,全连接层的输入节点数也会减少 ,全连接层的计算量也会减少。从25088下降到4608。
六、VGG16
改变结构
conv_arch = ((2, 64), (2, 128), (3, 256), (3, 512), (3, 512),其余保持不变,
训练时长50min
1. 整体趋势分析
- 两张图都展示了随着训练轮数(epoch)的增加,训练损失(train loss)、训练准确率(train acc)和测试准确率(test acc)的变化情况。
- 在这两张图中,训练损失都呈现出下降的趋势,而训练准确率和测试准确率都呈现出上升的趋势。这是典型的模型在训练过程中逐渐收敛的表现。
2. VGG11与VGG16的对比
- VGG11(第一张图)
- 损失(loss):最终的训练损失约为0.178,这个值相对较低,表明模型在训练集上的拟合效果较好。
- 准确率(acc):训练准确率达到0.935,测试准确率达到0.920。这表明模型在训练集和测试集上都有较高的准确率,且训练集和测试集的准确率较为接近,说明模型没有明显的过拟合现象。
- 训练速度:每秒处理246.7个样本,这个速度可以用来评估模型的训练效率。
- VGG16(第二张图)
- 损失(loss):最终的训练损失约为0.203,略高于VGG11的训练损失。
- 准确率(acc):训练准确率达到0.926,测试准确率达到0.917。虽然准确率仍然较高,但略低于VGG11的准确率。
- 训练速度:每秒处理239.8个样本,略低于VGG11的训练速度。
- VGG16由于其更深的网络结构和更多的参数,在初始化时可能会导致损失函数(loss)的初始值较高。
对于Fashion - MNIST数据集,VGG11在训练损失、准确率和训练速度上都表现得比VGG16更好。