【23-24 秋学期】NNDL 作业7 基于CNN的XO识别

一、用自己的语言解释以下概念


(一)、局部感知、权值共享

(1)局部感知

定义:在进行卷积计算的时候,将图片划分为一个个的区域进行计算/考虑;

由于越是接近的像素点之间的关联性越强, 反之则越弱. 所以我们选择先进行局部感知, 然后在更高层(FC层)将这些局部信息综合起来得到全局信息的方式.【来自深度学习之卷积神经网络(Convolutional Neural Networks, CNN) - 知乎 (zhihu.com)】

感知的大小只有滑动窗口那么大。如下图所示:

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第1张图片

可以看到,每次卷积核都是针对某一局部的数据窗口进行卷积,这就是所谓的CNN中的局部感知机制。

(2)权值共享

定义:在卷积神经网络中,同一个卷积核在整个输入数据的不同位置共享相同的权重参数。这意味着无论卷积核移动到哪个位置,它所使用的权重参数都是一样的。

以上文参考卷积神经网络(CNN)的相关概念 - 掘金 (juejin.cn)

如下图所示,移动的蓝框是局部感知,在计算第一个output时,W0不改变,这就是权值共享

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第2张图片

【图来自https://cs231n.github.io/convolutional-networks/】

(二)、池化(子采样、降采样、汇聚)。会带来那些好处和坏处?

(1)池化对应定义

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

其中池化运算有以下几种:

  • 最大池化(Max Pooling)。取4个点的最大值。这是最常用的池化方法。
  • 均值池化(Mean Pooling)。取4个点的均值。
  • 高斯池化。借鉴高斯模糊的方法。不常用。
  • 可训练池化。训练函数 ff ,接受4个点为输入,出入1个点。不常用。

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第3张图片

图来自【CS231n Convolutional Neural Networks for Visual Recognition】 

 上图是一个2*2且步长为2的最大池化。

文来自【卷积神经网络(CNN)的相关概念 - 掘金 (juejin.cn)】

(2)好处、坏处

上图可以看到池化操作具有特征不变性,可以保留主要特征,实现降维。

好处:

1)降维:池化减小图片的尺寸,主要用来降维。

2)减少计算量:通过池化操作,可以大幅降低后续层的计算复杂度,从而提高模型的训练速度。

3)保留主要特征:池化操作通常会采用最大池化或平均池化等方式,保留主要特征,降低了特征的冗余性。

4)减少过拟合:通过减少特征图的尺寸,池化层有助于减少模型对于训练数据中噪声和微小变化的敏感度,从而有助于防止过拟合。

图文来自【图像识别(七)| 池化层是什么?有什么作用? - 知乎 (zhihu.com)】

坏处:

池化层由于保留的是主要特征,会丧失一些细节信息,特别是在一些对细节敏感的任务中,可能需要谨慎使用或者考虑使用合适的池化方式。【文来自CNN中池化层的作用?-CSDN博客】

(三)、全卷积网络

定义全卷积网络(FCN)是从抽象的特征中恢复出每个像素所属的类别。即从图像级别的分类进一步延伸到像素级别的分类。

文来自【机器学习基础系列笔记7—全卷积网络FCN&U-Net结构 - 知乎 (zhihu.com)】

相比于CNNFCN相较于CNN来说,其将CNN最后几个用于输出概率的全连接层都改成了卷积层,这样网络的输出将是热力图而非类别;同时,为解决卷积和池化导致图像尺寸的变小,使用上采样方式对图像尺寸进行恢复。

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第4张图片

它的基本思想是:1.不含全连接层(fc)的全卷积网络。可适应任意尺寸输入。 2.增大数据尺寸的反卷积(deconv)层。能够输出精细的结果。 3.结合不同深度层结果的跳级(skip)结构。同时确保鲁棒性和精确性。

文来自【卷积神经网络( CNN)与全卷积神经网络(FCN) (xjx100.cn)】

FCN网络结构:全卷积部分和反卷积部分。其中全卷积部分为一些经典的CNN网络(如VGG,ResNet等),用于提取特征;反卷积部分则是通过上采样得到原尺寸的语义分割图像。FCN的输入可以为任意尺寸的彩色图像,输出与输入尺寸相同,通道数为n(目标类别数)+1(背景)。

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第5张图片

图文来自FCN(全卷积神经网络)详解-菜鸟笔记 (coonote.com) 

(四)、低级特征、中级特征、高级特征

网络中靠前的部分提取的是初级特征:例如边缘特征,是直接从原数据集中提取到的

网络中段提取的是中级特征,中级特征一定程度上是初级特征的再组合体现,比如纹理特征等。

而网络后端提取的是高级特征,高级特征可以看作中级特征的再组合,也更加抽象

文来自【分层特征提取Hierarchical Feature Extraction (baidu.com)】

还是这个图:

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第6张图片

形象来说,首先尽可能找到与这个头像相关的各种边,这些边就是底层的特征(Low-level features),也就是低级特征;然后对这些底层特征进行组合,就可以看到鼻子、眼睛、耳朵等,它们是中间层特征(Mid-level features),也就是中级特征;最后,对鼻子、眼睛等进行组合,就可以组成各种各样的头像,也就是高层特征(High-level features)。这个时候,它就可以识别出各种人的头像了。

图文来自【白话版,聊聊“深度学习” (qq.com)】

在搜集了其他的博客后【来自【22-23 春学期】人工智能基础--AI作业8-卷积2-CSDN博客】里边对于这几个特征是这么说的:

低级特征通常指一些较为基础的、直接从原始数据中提取的特征,如颜色、纹理、边缘等,它们通常对于物体的分类或识别任务并不十分有效,但是可以作为中级特征的基础。

中级特征则是指基于低级特征构建的一些更高层次的特征,如形状、轮廓、纹理组合等,这些特征能够更好地描述物体的形态和结构,因此对于分类或识别任务的效果会更好。

高级特征则是指基于中级特征构建的更加抽象和复杂的特征,如物体的部件、结构、语义等,这些特征能够更好地描述物体的高层次语义信息,因此对于更加复杂的任务(如目标检测、语义分割等)的效果会更好。

(五)、多通道。N输入,M输出是如何实现的?

1)多输入、单输出

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第7张图片

如图所示,输入通道有两个,那么需要两个卷积核,进行卷积运算,然后累加,得到一个输出。

当输入通道有多个时,因为对各个通道的结果做了累加,所以不论输入通道数是多少,输出通道数总是为 1。

那么如何得到多个输出呢? 

2)多输入、多输出

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第8张图片

如上图所示,如果想要得到M个输出,那么只要准备M组卷积核,其中每一组卷积核的多少与有几个输入有关。 来自【精选】【从零开始学习深度学习】23. CNN中的多通道输入及多通道输出计算方式及1X1卷积层介绍_多通道cnn-CSDN博客

(六)、1×1的卷积核有什么作用

1)增加网络深度(增加非线性映射次数)

当1*1的卷积核但是是m层和n层的话,1×1卷积核可以起到一个跨通道聚合的作用。

2)升维/降维

1*1不会改变输出的尺寸,改变的是通道数。

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第9张图片

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

上图文来自【1*1卷积核的作用-CSDN博客】

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第10张图片

 图来自【深度笔记|1x1卷积核的作用 - 知乎 (zhihu.com)】

3)跨通道的信息交互

4)减少卷积核参数(简化模型)

降维,其实也是减少了参数,因为特征图少了,参数也自然跟着就减少,相当于在特征图的通道数上进行卷积,压缩特征图,二次提取特征,使得新特征图的特征表达更佳。

上边这四个作用是相互联系的,归根到底,1*1的卷积核实现了输出的降维或者升维,才会出现其他作用。

二、使用CNN进行XO识别


(一)、复现参考资料中的代码

(1)数据集

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第11张图片

(2)模型构建

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第12张图片

如上图所示,构建模型:

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

 (3)训练模型

把数据分成了10个批次,然后进行循环,循环中嵌套的循环是计算平均损失函数:

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第13张图片

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

(4)测试模型

读取一张图片进行测试:

# 读取模型
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()

如下图所示,预测正确:

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第14张图片

(5)计算准确率 

 “预测正确的数目除以总的数目”,得到准确率:

# 读取模型
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))

得到准确率:

(6)查看训练好模型的特征图

# 看看每层的 卷积核 长相,特征图 长相
# 获取网络结构的特征矩阵并可视化
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)
)

 【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第15张图片【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第16张图片

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第17张图片

(7)查看卷积核

# 看看每层的 卷积核 长相,特征图 长相
# 获取网络结构的特征矩阵并可视化
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()

 输出:

卷积核

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第18张图片

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第19张图片 【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第20张图片【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第21张图片

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第22张图片 【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第23张图片【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第24张图片

 【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第25张图片【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第26张图片【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第27张图片

(二)、重新设计网络结构

(1)至少增加一个卷积层,卷积层达到三层以上

由于我在加入一层卷积层后,计算特征图尺寸一一直不对,然后舍友茜借了我一段代码:

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


(2)去掉池化层,对比“有无池化”的效果

得到的结果:

[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轮次有较好的效果。但是速度比较慢,还有池化层的效率更高。 

(3)修改“通道数”等超参数,观察变化

修改了通道数,改成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 %

(4)可视化低级特征、中级特征、高级特征

注重一下可视化的过程

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()

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第28张图片

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第29张图片

【23-24 秋学期】NNDL 作业7 基于CNN的XO识别_第30张图片

可以看到,在最开始的图像中还有明确的⚪,到后来就没了,变成了一个个像素组的,因为初级特征关注的是直接从原数据集中提取到的。而在后期,也就是高级特征阶段,关注的更加抽象。

这一部分是参考的【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博客

你可能感兴趣的:(深度学习,cnn,人工智能,神经网络)