定义:在进行卷积计算的时候,将图片划分为一个个的区域进行计算/考虑;
由于越是接近的像素点之间的关联性越强, 反之则越弱. 所以我们选择先进行局部感知, 然后在更高层(FC层)将这些局部信息综合起来得到全局信息的方式.【来自深度学习之卷积神经网络(Convolutional Neural Networks, CNN) - 知乎 (zhihu.com)】
感知的大小只有滑动窗口那么大。如下图所示:

可以看到,每次卷积核都是针对某一局部的数据窗口进行卷积,这就是所谓的CNN中的局部感知机制。
定义:在卷积神经网络中,同一个卷积核在整个输入数据的不同位置共享相同的权重参数。这意味着无论卷积核移动到哪个位置,它所使用的权重参数都是一样的。
以上文参考卷积神经网络(CNN)的相关概念 - 掘金 (juejin.cn)
如下图所示,移动的蓝框是局部感知,在计算第一个output时,W0不改变,这就是权值共享

池化,也称子采样、降采样或汇聚,它的工作是取区域平均或最大,其目的是为了减少特征图,减少特征的数量,从而降低模型的计算量和参数量。
其中池化运算有以下几种:

图来自【CS231n Convolutional Neural Networks for Visual Recognition】
上图是一个2*2且步长为2的最大池化。
文来自【卷积神经网络(CNN)的相关概念 - 掘金 (juejin.cn)】

上图可以看到池化操作具有特征不变性,可以保留主要特征,实现降维。
好处:
1)降维:池化减小图片的尺寸,主要用来降维。
2)减少计算量:通过池化操作,可以大幅降低后续层的计算复杂度,从而提高模型的训练速度。
3)保留主要特征:池化操作通常会采用最大池化或平均池化等方式,保留主要特征,降低了特征的冗余性。
4)减少过拟合:通过减少特征图的尺寸,池化层有助于减少模型对于训练数据中噪声和微小变化的敏感度,从而有助于防止过拟合。
图文来自【图像识别(七)| 池化层是什么?有什么作用? - 知乎 (zhihu.com)】
坏处:
池化层由于保留的是主要特征,会丧失一些细节信息,特别是在一些对细节敏感的任务中,可能需要谨慎使用或者考虑使用合适的池化方式。【文来自CNN中池化层的作用?-CSDN博客】
定义:全卷积网络(FCN)是从抽象的特征中恢复出每个像素所属的类别。即从图像级别的分类进一步延伸到像素级别的分类。
文来自【机器学习基础系列笔记7—全卷积网络FCN&U-Net结构 - 知乎 (zhihu.com)】
相比于CNN:FCN相较于CNN来说,其将CNN最后几个用于输出概率的全连接层都改成了卷积层,这样网络的输出将是热力图而非类别;同时,为解决卷积和池化导致图像尺寸的变小,使用上采样方式对图像尺寸进行恢复。

它的基本思想是:1.不含全连接层(fc)的全卷积网络。可适应任意尺寸输入。 2.增大数据尺寸的反卷积(deconv)层。能够输出精细的结果。 3.结合不同深度层结果的跳级(skip)结构。同时确保鲁棒性和精确性。
文来自【卷积神经网络( CNN)与全卷积神经网络(FCN) (xjx100.cn)】
FCN网络结构:全卷积部分和反卷积部分。其中全卷积部分为一些经典的CNN网络(如VGG,ResNet等),用于提取特征;反卷积部分则是通过上采样得到原尺寸的语义分割图像。FCN的输入可以为任意尺寸的彩色图像,输出与输入尺寸相同,通道数为n(目标类别数)+1(背景)。

图文来自【FCN(全卷积神经网络)详解-菜鸟笔记 (coonote.com)】
网络中靠前的部分提取的是初级特征:例如边缘特征,是直接从原数据集中提取到的。
网络中段提取的是中级特征,中级特征一定程度上是初级特征的再组合体现,比如纹理特征等。
而网络后端提取的是高级特征,高级特征可以看作中级特征的再组合,也更加抽象。
文来自【分层特征提取Hierarchical Feature Extraction (baidu.com)】
还是这个图:

形象来说,首先尽可能找到与这个头像相关的各种边,这些边就是底层的特征(Low-level features),也就是低级特征;然后对这些底层特征进行组合,就可以看到鼻子、眼睛、耳朵等,它们是中间层特征(Mid-level features),也就是中级特征;最后,对鼻子、眼睛等进行组合,就可以组成各种各样的头像,也就是高层特征(High-level features)。这个时候,它就可以识别出各种人的头像了。
图文来自【白话版,聊聊“深度学习” (qq.com)】
在搜集了其他的博客后【来自【22-23 春学期】人工智能基础--AI作业8-卷积2-CSDN博客】里边对于这几个特征是这么说的:
低级特征通常指一些较为基础的、直接从原始数据中提取的特征,如颜色、纹理、边缘等,它们通常对于物体的分类或识别任务并不十分有效,但是可以作为中级特征的基础。
中级特征则是指基于低级特征构建的一些更高层次的特征,如形状、轮廓、纹理组合等,这些特征能够更好地描述物体的形态和结构,因此对于分类或识别任务的效果会更好。
高级特征则是指基于中级特征构建的更加抽象和复杂的特征,如物体的部件、结构、语义等,这些特征能够更好地描述物体的高层次语义信息,因此对于更加复杂的任务(如目标检测、语义分割等)的效果会更好。

如图所示,输入通道有两个,那么需要两个卷积核,进行卷积运算,然后累加,得到一个输出。
当输入通道有多个时,因为对各个通道的结果做了累加,所以不论输入通道数是多少,输出通道数总是为 1。
那么如何得到多个输出呢?

如上图所示,如果想要得到M个输出,那么只要准备M组卷积核,其中每一组卷积核的多少与有几个输入有关。 来自【精选】【从零开始学习深度学习】23. CNN中的多通道输入及多通道输出计算方式及1X1卷积层介绍_多通道cnn-CSDN博客
1)增加网络深度(增加非线性映射次数)
当1*1的卷积核但是是m层和n层的话,1×1卷积核可以起到一个跨通道聚合的作用。
2)升维/降维
1*1不会改变输出的尺寸,改变的是通道数。

1*1的卷积核将原本的数据量进行增加或者减少。这里看其他文章或者博客中都称之为升维、降维。但我觉得维度并没有改变,改变的只是 height × width × channels 中的 channels 这一个维度的大小而已。
上图文来自【1*1卷积核的作用-CSDN博客】

图来自【深度笔记|1x1卷积核的作用 - 知乎 (zhihu.com)】
3)跨通道的信息交互
4)减少卷积核参数(简化模型)
降维,其实也是减少了参数,因为特征图少了,参数也自然跟着就减少,相当于在特征图的通道数上进行卷积,压缩特征图,二次提取特征,使得新特征图的特征表达更佳。
上边这四个作用是相互联系的,归根到底,1*1的卷积核实现了输出的降维或者升维,才会出现其他作用。


如上图所示,构建模型:
- class Net(nn.Module):
- def __init__(self):
- super(Net, self).__init__()
- #定义卷积层
- self.conv1 = nn.Conv2d(1, 9, 3)
- #最大池化层
- self.maxpool = nn.MaxPool2d(2, 2)
- self.conv2 = nn.Conv2d(9, 5, 3)
-
- self.relu = nn.ReLU()
- self.fc1 = nn.Linear(27 * 27 * 5, 1200)
- self.fc2 = nn.Linear(1200, 64)
- self.fc3 = nn.Linear(64, 2)
-
- def forward(self, x):
- #第一次
- x = self.maxpool(self.relu(self.conv1(x)))
- #第二次
- x = self.maxpool(self.relu(self.conv2(x)))
- x = x.view(-1, 27 * 27 * 5)
- #全连接+激活
- x = self.relu(self.fc1(x))
- x = self.relu(self.fc2(x))
- x = self.fc3(x)
- return x
把数据分成了10个批次,然后进行循环,循环中嵌套的循环是计算平均损失函数:

- model = Net()
-
- criterion = torch.nn.CrossEntropyLoss() # 损失函数: 交叉熵损失函数
- optimizer = optim.SGD(model.parameters(), lr=0.1) # 优化函数:随机梯度下降
-
- epochs = 10
- for epoch in range(epochs):
- running_loss = 0.0#计算平均损失值【累加,每10次一输出一清零】
- for i, data in enumerate(data_loader):
- images, label = data
- out = model(images)
- loss = criterion(out, label)#计算损失
-
- optimizer.zero_grad()#清空之前的梯度
- loss.backward()
- optimizer.step()
-
- running_loss += loss.item()
- if (i + 1) % 10 == 0:
- print('[%d %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 100))
- running_loss = 0.0
-
- print('finished train')
-
- # 保存模型
- torch.save(model, 'model_name.pth') # 保存的是模型, 不止是w和b权重值
然后保存模型。
得到的输出为:
- [1 10] loss: 0.069
- [1 20] loss: 0.069
- [2 10] loss: 0.068
- [2 20] loss: 0.066
- [3 10] loss: 0.058
- [3 20] loss: 0.037
- [4 10] loss: 0.023
- [4 20] loss: 0.008
- [5 10] loss: 0.004
- [5 20] loss: 0.004
- [6 10] loss: 0.003
- [6 20] loss: 0.002
- [7 10] loss: 0.001
- [7 20] loss: 0.001
- [8 10] loss: 0.001
- [8 20] loss: 0.000
- [9 10] loss: 0.001
- [9 20] loss: 0.000
- [10 10] loss: 0.001
- [10 20] loss: 0.000
- finished train
读取一张图片进行测试:
- # 读取模型
- model_load = torch.load('model_name.pth')
- # 读取一张图片 images[0],测试
- print("labels[0] truth:\t", labels[0])
- x = images[0]
- predicted = torch.max(model_load(x), 1)
- print("labels[0] predict:\t", predicted.indices)
-
- img = images[0].data.squeeze().numpy() # 将输出转换为图片的格式
- plt.imshow(img, cmap='gray')
- plt.show()
如下图所示,预测正确:

“预测正确的数目除以总的数目”,得到准确率:
- # 读取模型
- model_load = torch.load('model_name.pth')
-
- correct = 0
- total = 0
- with torch.no_grad(): # 进行评测的时候网络不更新梯度
- for data in data_loader_test: # 读取测试集
- images, labels = data
- outputs = model_load(images)
- _, predicted = torch.max(outputs.data, 1) # 取出 最大值的索引 作为 分类结果
- total += labels.size(0) # labels 的长度
- correct += (predicted == labels).sum().item() # 预测正确的数目
- print('Accuracy of the network on the test images: %f %%' % (100. * correct / total))
得到准确率:

- # 看看每层的 卷积核 长相,特征图 长相
- # 获取网络结构的特征矩阵并可视化
- import torch
- import matplotlib.pyplot as plt
- import numpy as np
- from PIL import Image
- from torchvision import transforms, datasets
- import torch.nn as nn
- from torch.utils.data import DataLoader
-
- # 定义图像预处理过程(要与网络模型训练过程中的预处理过程一致)
-
- transforms = transforms.Compose([
- transforms.ToTensor(), # 把图片进行归一化,并把数据转换成Tensor类型
- transforms.Grayscale(1) # 把图片 转为灰度图
- ])
- path = r'training_data_sm'
- data_train = datasets.ImageFolder(path, transform=transforms)
- data_loader = DataLoader(data_train, batch_size=64, shuffle=True)
- for i, data in enumerate(data_loader):
- images, labels = data
- print(images.shape)
- print(labels.shape)
- break
-
-
- class Net(nn.Module):
- def __init__(self):
- super(Net, self).__init__()
- self.conv1 = nn.Conv2d(1, 9, 3) # in_channel , out_channel , kennel_size , stride
- self.maxpool = nn.MaxPool2d(2, 2)
- self.conv2 = nn.Conv2d(9, 5, 3) # in_channel , out_channel , kennel_size , stride
-
- self.relu = nn.ReLU()
- self.fc1 = nn.Linear(27 * 27 * 5, 1200) # full connect 1
- self.fc2 = nn.Linear(1200, 64) # full connect 2
- self.fc3 = nn.Linear(64, 2) # full connect 3
-
- def forward(self, x):
- outputs = []
- x = self.conv1(x)
- outputs.append(x)
- x = self.relu(x)
- outputs.append(x)
- x = self.maxpool(x)
- outputs.append(x)
- x = self.conv2(x)
-
- x = self.relu(x)
-
- x = self.maxpool(x)
-
- x = x.view(-1, 27 * 27 * 5)
- x = self.relu(self.fc1(x))
- x = self.relu(self.fc2(x))
- x = self.fc3(x)
- return outputs
-
-
- # create model
- model1 = Net()
-
- # load model weights加载预训练权重
- # model_weight_path ="./AlexNet.pth"
- model_weight_path = "model_name1.pth"
- model1.load_state_dict(torch.load(model_weight_path))
-
- # 打印出模型的结构
- print(model1)
-
- x = images[0]
-
- # forward正向传播过程
- out_put = model1(x)
-
- for feature_map in out_put:
- # [N, C, H, W] -> [C, H, W] 维度变换
- im = np.squeeze(feature_map.detach().numpy())
- # [C, H, W] -> [H, W, C]
- im = np.transpose(im, [1, 2, 0])
- print(im.shape)
-
- # show 9 feature maps
- plt.figure()
- for i in range(9):
- ax = plt.subplot(3, 3, i + 1) # 参数意义:3:图片绘制行数,5:绘制图片列数,i+1:图的索引
- # [H, W, C]
- # 特征矩阵每一个channel对应的是一个二维的特征矩阵,就像灰度图像一样,channel=1
- # plt.imshow(im[:, :, i])
- plt.imshow(im[:, :, i], cmap='gray')
- plt.show()
输出:
实例化情况
- torch.Size([64, 1, 116, 116])
- torch.Size([64])
- Net(
- (conv1): Conv2d(1, 9, kernel_size=(3, 3), stride=(1, 1))
- (maxpool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
- (conv2): Conv2d(9, 5, kernel_size=(3, 3), stride=(1, 1))
- (relu): ReLU()
- (fc1): Linear(in_features=3645, out_features=1200, bias=True)
- (fc2): Linear(in_features=1200, out_features=64, bias=True)
- (fc3): Linear(in_features=64, out_features=2, bias=True)
- )



- # 看看每层的 卷积核 长相,特征图 长相
- # 获取网络结构的特征矩阵并可视化
- import torch
- import matplotlib.pyplot as plt
- import numpy as np
- from PIL import Image
- from torchvision import transforms, datasets
- import torch.nn as nn
- from torch.utils.data import DataLoader
-
- plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
- plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号 #有中文出现的情况,需要u'内容
- # 定义图像预处理过程(要与网络模型训练过程中的预处理过程一致)
- transforms = transforms.Compose([
- transforms.ToTensor(), # 把图片进行归一化,并把数据转换成Tensor类型
- transforms.Grayscale(1) # 把图片 转为灰度图
- ])
- path = r'training_data_sm'
- data_train = datasets.ImageFolder(path, transform=transforms)
- data_loader = DataLoader(data_train, batch_size=64, shuffle=True)
- for i, data in enumerate(data_loader):
- images, labels = data
- # print(images.shape)
- # print(labels.shape)
- break
-
-
- class Net(nn.Module):
- def __init__(self):
- super(Net, self).__init__()
- self.conv1 = nn.Conv2d(1, 9, 3) # in_channel , out_channel , kennel_size , stride
- self.maxpool = nn.MaxPool2d(2, 2)
- self.conv2 = nn.Conv2d(9, 5, 3) # in_channel , out_channel , kennel_size , stride
-
- self.relu = nn.ReLU()
- self.fc1 = nn.Linear(27 * 27 * 5, 1200) # full connect 1
- self.fc2 = nn.Linear(1200, 64) # full connect 2
- self.fc3 = nn.Linear(64, 2) # full connect 3
-
- def forward(self, x):
- outputs = []
- x = self.maxpool(self.relu(self.conv1(x)))
- # outputs.append(x)
- x = self.maxpool(self.relu(self.conv2(x)))
- outputs.append(x)
- x = x.view(-1, 27 * 27 * 5)
- x = self.relu(self.fc1(x))
- x = self.relu(self.fc2(x))
- x = self.fc3(x)
- return outputs
-
-
- # create model
- model1 = Net()
-
- # load model weights加载预训练权重
- model_weight_path = "model_name1.pth"
- model1.load_state_dict(torch.load(model_weight_path))
-
- x = images[0]
-
- # forward正向传播过程
- out_put = model1(x)
-
- weights_keys = model1.state_dict().keys()
- for key in weights_keys:
- print("key :", key)
- # 卷积核通道排列顺序 [kernel_number, kernel_channel, kernel_height, kernel_width]
- if key == "conv1.weight":
- weight_t = model1.state_dict()[key].numpy()
- print("weight_t.shape", weight_t.shape)
- k = weight_t[:, 0, :, :] # 获取第一个卷积核的信息参数
- # show 9 kernel ,1 channel
- plt.figure()
-
- for i in range(9):
- ax = plt.subplot(3, 3, i + 1) # 参数意义:3:图片绘制行数,5:绘制图片列数,i+1:图的索引
- plt.imshow(k[i, :, :], cmap='gray')
- title_name = 'kernel' + str(i) + ',channel1'
- plt.title(title_name)
- plt.show()
-
- if key == "conv2.weight":
- weight_t = model1.state_dict()[key].numpy()
- print("weight_t.shape", weight_t.shape)
- k = weight_t[:, :, :, :] # 获取第一个卷积核的信息参数
- print(k.shape)
- print(k)
-
- plt.figure()
- for c in range(9):
- channel = k[:, c, :, :]
- for i in range(5):
- ax = plt.subplot(2, 3, i + 1) # 参数意义:3:图片绘制行数,5:绘制图片列数,i+1:图的索引
- plt.imshow(channel[i, :, :], cmap='gray')
- title_name = 'kernel' + str(i) + ',channel' + str(c)
- plt.title(title_name)
- plt.show()
输出:
卷积核








由于我在加入一层卷积层后,计算特征图尺寸一一直不对,然后舍友茜借了我一段代码:
- import torch
- import torch.nn as nn
-
- # 定义卷积层和池化层
- conv1 = nn.Conv2d(1, 9, 3)
- maxpool = nn.MaxPool2d(2, 2)
- conv2 = nn.Conv2d(9, 5, 3)
- conv3 = nn.Conv2d(5, 3, 3)
- # 假设输入图像尺寸为 64x64
- W_in, H_in = 116, 116
-
- # 计算特征图尺寸
- x = torch.rand(1, 1, W_in, H_in) # 构造一个输入张量
- x = maxpool(nn.ReLU()(conv1(x)))
- x = maxpool(nn.ReLU()(conv2(x)))
- x = maxpool(nn.ReLU()(conv3(x)))
-
- output_size = x.size()[2:] # 获取特征图的尺寸
- print("Output feature map size:", output_size)
可以得到对应的尺寸:
Output feature map size: torch.Size([12, 12])
在后边就不放代码了,只有构建模型部分进行了一点点修改:
- import torch
- import torch.nn as nn
-
- # 定义卷积层和池化层
- conv1 = nn.Conv2d(1, 9, 3)
- maxpool = nn.MaxPool2d(2, 2)
- conv2 = nn.Conv2d(9, 5, 3)
- conv3 = nn.Conv2d(5, 3, 3)
- # 假设输入图像尺寸为 64x64
- W_in, H_in = 116, 116
-
- # 计算特征图尺寸
- x = torch.rand(1, 1, W_in, H_in) # 构造一个输入张量
- x = maxpool(nn.ReLU()(conv1(x)))
- x = maxpool(nn.ReLU()(conv2(x)))
- x = maxpool(nn.ReLU()(conv3(x)))
-
- output_size = x.size()[2:] # 获取特征图的尺寸
- print("Output feature map size:", output_size)
然后训练模型得到的损失:
- [1 10] loss: 0.051
- [1 20] loss: 0.045
- [1 30] loss: 0.039
- [2 10] loss: 0.045
- [2 20] loss: 0.041
- [2 30] loss: 0.044
- [3 10] loss: 0.042
- [3 20] loss: 0.046
- [3 30] loss: 0.042
- [4 10] loss: 0.040
- [4 20] loss: 0.045
- [4 30] loss: 0.043
- [5 10] loss: 0.040
- [5 20] loss: 0.044
- [5 30] loss: 0.045
- [6 10] loss: 0.045
- [6 20] loss: 0.041
- [6 30] loss: 0.044
- [7 10] loss: 0.043
- [7 20] loss: 0.047
- [7 30] loss: 0.037
- [8 10] loss: 0.037
- [8 20] loss: 0.046
- [8 30] loss: 0.044
- [9 10] loss: 0.042
- [9 20] loss: 0.046
- [9 30] loss: 0.042
- [10 10] loss: 0.041
- [10 20] loss: 0.045
- [10 30] loss: 0.041
- finished train
效果不太好。
然后参考了一下美女学霸NNDL 作业7 相关语言解释+基于CNN的XO识别代码复现-CSDN博客的,多增加了10轮次,效果很好:
- [1 10] loss: 0.069
- [1 20] loss: 0.069
- [2 10] loss: 0.069
- [2 20] loss: 0.068
- [3 10] loss: 0.056
- [3 20] loss: 0.034
- [4 10] loss: 0.012
- [4 20] loss: 0.006
- [5 10] loss: 0.005
- [5 20] loss: 0.001
- [6 10] loss: 0.002
- [6 20] loss: 0.001
- [7 10] loss: 0.001
- [7 20] loss: 0.001
- [8 10] loss: 0.001
- [8 20] loss: 0.000
- [9 10] loss: 0.000
- [9 20] loss: 0.000
- [10 10] loss: 0.000
- [10 20] loss: 0.000
- [11 10] loss: 0.000
- [11 20] loss: 0.000
- [12 10] loss: 0.000
- [12 20] loss: 0.000
- [13 10] loss: 0.000
- [13 20] loss: 0.000
- [14 10] loss: 0.000
- [14 20] loss: 0.000
- [15 10] loss: 0.000
- [15 20] loss: 0.000
- [16 10] loss: 0.000
- [16 20] loss: 0.000
- [17 10] loss: 0.000
- [17 20] loss: 0.000
- [18 10] loss: 0.000
- [18 20] loss: 0.000
- [19 10] loss: 0.000
- [19 20] loss: 0.000
- [20 10] loss: 0.000
- [20 20] loss: 0.000
- finished train
得到的结果:
- [1 10] loss: 0.062
- [1 20] loss: 0.051
- [1 30] loss: 0.044
- [2 10] loss: 0.041
- [2 20] loss: 0.045
- [2 30] loss: 0.042
- [3 10] loss: 0.044
- [3 20] loss: 0.043
- [3 30] loss: 0.041
- [4 10] loss: 0.043
- [4 20] loss: 0.041
- [4 30] loss: 0.044
- [5 10] loss: 0.042
- [5 20] loss: 0.042
- [5 30] loss: 0.044
- [6 10] loss: 0.041
- [6 20] loss: 0.045
- [6 30] loss: 0.042
- [7 10] loss: 0.043
- [7 20] loss: 0.043
- [7 30] loss: 0.039
- [8 10] loss: 0.046
- [8 20] loss: 0.039
- [8 30] loss: 0.043
- [9 10] loss: 0.039
- [9 20] loss: 0.044
- [9 30] loss: 0.043
- [10 10] loss: 0.037
- [10 20] loss: 0.043
- [10 30] loss: 0.044
- [11 10] loss: 0.040
- [11 20] loss: 0.042
- [11 30] loss: 0.041
- [12 10] loss: 0.039
- [12 20] loss: 0.040
- [12 30] loss: 0.042
- [13 10] loss: 0.039
- [13 20] loss: 0.041
- [13 30] loss: 0.037
- [14 10] loss: 0.038
- [14 20] loss: 0.036
- [14 30] loss: 0.035
- [15 10] loss: 0.034
- [15 20] loss: 0.033
- [15 30] loss: 0.031
- [16 10] loss: 0.030
- [16 20] loss: 0.029
- [16 30] loss: 0.029
- [17 10] loss: 0.025
- [17 20] loss: 0.024
- [17 30] loss: 0.024
- [18 10] loss: 0.014
- [18 20] loss: 0.019
- [18 30] loss: 0.021
- [19 10] loss: 0.011
- [19 20] loss: 0.009
- [19 30] loss: 0.017
- [20 10] loss: 0.006
- [20 20] loss: 0.006
- [20 30] loss: 0.008
- finished train
可以看到,20轮次有较好的效果。但是速度比较慢,还有池化层的效率更高。
修改了通道数,改成11的:
- [1 10] loss: 0.070
- [1 20] loss: 0.069
- [2 10] loss: 0.069
- [2 20] loss: 0.067
- [3 10] loss: 0.050
- [3 20] loss: 0.038
- [4 10] loss: 0.022
- [4 20] loss: 0.010
- [5 10] loss: 0.004
- [5 20] loss: 0.003
- [6 10] loss: 0.002
- [6 20] loss: 0.002
- [7 10] loss: 0.001
- [7 20] loss: 0.002
- [8 10] loss: 0.001
- [8 20] loss: 0.001
- [9 10] loss: 0.001
- [9 20] loss: 0.000
- [10 10] loss: 0.001
- [10 20] loss: 0.000
- finished train
Accuracy of the network on the test images: 99.666667 %
改为15,效果不好:
- [1 10] loss: 0.070
- [1 20] loss: 0.069
- [2 10] loss: 0.069
- [2 20] loss: 0.069
- [3 10] loss: 0.069
- [3 20] loss: 0.069
- [4 10] loss: 0.069
- [4 20] loss: 0.069
- [5 10] loss: 0.069
- [5 20] loss: 0.069
- [6 10] loss: 0.069
- [6 20] loss: 0.069
- [7 10] loss: 0.069
- [7 20] loss: 0.069
- [8 10] loss: 0.069
- [8 20] loss: 0.069
- [9 10] loss: 0.069
- [9 20] loss: 0.068
- [10 10] loss: 0.065
- [10 20] loss: 0.061
- finished train
Accuracy of the network on the test images: 80.000000 %
注重一下可视化的过程:
1.通过模型将输入数据`x`进行正向传播,得到输出的特征图`out_put`。
2.但是得到的特征图不能使用plt直接显示出来,需要对他们进行一些操作实现可视化【循环:`np.squeeze`函数将特征图的维度从`[N, C, H, W]`变换为`[C, H, W]`,其中`N`表示批次大小,`C`表示通道数,`H`和`W`表示特征图的高度和宽度;np.transpose`函数将特征图的维度从`[C, H, W]`变换为`[H, W, C]`;然后输出特征图形状】
3.创建图形窗口,循环使用`plt.imshow`函数绘制特征图的每个通道,其中`im[:, :, i]`表示特征图的第`i`个通道。
4.`plt.show`函数展示绘制好的特征图。
- import torch
- from torchvision import transforms
- from torch.utils.data import DataLoader
- from torchvision import datasets, transforms
- import torch.nn as nn
-
- # 数据预处理
- transforms = transforms.Compose([
- transforms.ToTensor(), # 把图片进行归一化,并把数据转换成Tensor类型
- transforms.Grayscale(), # 把图片转为灰度图
- ])
-
- path = r'train_data'
- data_train = datasets.ImageFolder(path, transform=transforms)
- data_loader = DataLoader(data_train, batch_size=64, shuffle=True)
-
- for i, data in enumerate(data_loader):
- images, labels = data
- print(images.shape) # 输出:(batch_size, 1, height, width) 或者 (batch_size, num_channels, height, width)取决于你的图像数据
- print(labels.shape) # 输出:(batch_size,)
- break
-
- class Net(nn.Module):
- def __init__(self):
- super(Net, self).__init__()
-
- self.conv1 = nn.Conv2d(1, 9, 3) # input: (batch_size, 1, height, width), output: (batch_size, 9, height-2, width-2)
- self.maxpool = nn.MaxPool2d(2, 2) # 2x2的最大池化层,输出:(batch_size, 9, height/2-1, width/2-1)
- self.conv2 = nn.Conv2d(9, 5, 3) # input: (batch_size, 9, height/2-1, width/2-1), output: (batch_size, 5, height/2-2, width/2-2)
- self.conv3 = nn.Conv2d(5, 5, 3) # input: (batch_size, 5, height/2-2, width/2-2), output: (batch_size, 5, height/2-3, width/2-3)
- self.relu = nn.ReLU()
- self.fc1 = nn.Linear(5 * 5 * 5, 480) # input: (batch_size, 5*5*5), output: (batch_size, 480)
- self.fc2 = nn.Linear(480, 320) # input: (batch_size, 480), output: (batch_size, 320)
- self.fc3 = nn.Linear(320, 2) # input: (batch_size, 320), output: (batch_size, 2)
-
- def forward(self, x):
- x = self.maxpool(self.relu(self.conv1(x))) # output: (batch_size, 9, height/2-1, width/2-1)
- x = self.maxpool(self.relu(self.conv2(x))) # output: (batch_size, 5, height/4-1, width/4-1) or (batch_size, 5*num_channels ..., height/4-1, width/4-1) depending on the num_channels of your input data
- x = self.maxpool(self.relu(self.conv3(x))) # output: (batch_size, 5, height/8-1, width/8-1) or (batch_size,
-
- # 打印出模型的结构
- print(model1)
-
- x = images[0]
-
- # forward正向传播过程
- out_put = model1(x)
-
- for feature_map in out_put:
- # [N, C, H, W] -> [C, H, W] 维度变换
- im = np.squeeze(feature_map.detach().numpy())
- # [C, H, W] -> [H, W, C]
- im = np.transpose(im, [1, 2, 0])
- print(im.shape)
-
- # show 9 feature maps
- plt.figure()
- for i in range(5):
- ax = plt.subplot(3, 3, i + 1) # 参数意义:3:图片绘制行数,5:绘制图片列数,i+1:图的索引
- # [H, W, C]
- # 特征矩阵每一个channel对应的是一个二维的特征矩阵,就像灰度图像一样,channel=1
- # plt.imshow(im[:, :, i])
- plt.imshow(im[:, :, i], cmap='gray')
- plt.show()



可以看到,在最开始的图像中还有明确的⚪,到后来就没了,变成了一个个像素组的,因为初级特征关注的是直接从原数据集中提取到的。而在后期,也就是高级特征阶段,关注的更加抽象。
这一部分是参考的【NNDL 作业7 相关语言解释+基于CNN的XO识别代码复现-CSDN博客】
1.首先是修改卷积层:在这一环节中,我多次尝试【10轮次时】,然后最后的准确率都在50%-60%左右,后来修改到20轮次,发现效果好了很多,可是在第7轮是就没有损失了,感觉很神奇。
2.关于提取这几个特征图,很难弄,在网上也没有搜到相关资料,一直卡着,后来看了美女学霸的【指路:NNDL 作业7 相关语言解释+基于CNN的XO识别代码复现-CSDN博客】,发现“out_put = model1(x)”这一步,也就是正向传播得到特征图,豁然开朗,后边我就懂了。
3.在最后可视化特征这部分,我用了三个卷积层+三个线性层【想让高级特征更明显点,但是好像不太行】
深层网络的感受野更大,大感受野下才存在一定的高阶语义。深层网络所积累的特征空间更大。
上边那句话我的理解是,神经网络越深,那么他的高级特征更抽象。
【来自(46 封私信 / 82 条消息) 为什么越深层的特征图具备更丰富的语义信息? - 知乎 (zhihu.com)】
我感觉我的高级特征图还不是很抽象,可能卷积层再多一点,网络再深一点效果会更明显。
XO识别参考:
【23-24 秋学期】NNDL 作业7 基于CNN的XO识别-CSDN博客
【2021-2022 春学期】人工智能-作业6:CNN实现XO识别_x = self.conv2(x)#请问经过conv2(x)之后,x的维度是多少-CSDN博客