NNDL 作业7 基于CNN的XO识别

一、用自己的语言解释以下概念
局部感知、权值共享

局部感知指的是在卷积神经网络的卷积层中,每个神经元只与输入数据的局部区域相连。这种设计方式使得神经网络能够更有效地捕捉输入数据中的局部特征。以图像处理为例,对于一个图像,局部感知意味着每个神经元只关注输入图像中的一个小的局部区域,而不是整个图像。通过这种方式,网络能够从不同的局部特征中提取出更多有用的信息。

权值共享是指在卷积神经网络中,多个神经元使用相同的权重参数来处理输入数据的不同部分。具体来说,在卷积层中,每个神经元都使用相同的卷积核进行计算。这样做的好处是减少了网络的参数数量,降低了模型的复杂度,并且可以更好地处理平移不变性的特征。通过权值共享,网络能够共享参数并学习到更具有泛化能力的特征表示,从而提高模型的性能和效率。


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

池化(Pooling)是卷积神经网络中常用的一种操作,也被称为子采样、降采样或汇聚。它通过对输入数据进行降维处理,以便减少数据的复杂性和计算量。池化操作通常在卷积层之后应用,在提取特征之后用于压缩数据和保留重要信息。

池化操作的基本思想是通过在输入数据的局部区域内进行统计汇聚,从而得到该区域的代表性特征。最常见的池化操作包括最大池化(Max Pooling)和平均池化(Average Pooling)。在最大池化中,池化窗口内的最大值被选取作为代表性特征;而在平均池化中,则是计算池化窗口内数值的平均值。这些操作可以帮助网络学习到对位置变化不敏感的特征,并且加速了模型的训练和推理过程。

通过池化操作,可以实现以下几个目标:

  1. 降维:池化操作可以减少输入数据的尺寸,从而减少模型的参数数量和计算复杂度,同时加快了计算速度。

  2. 参数共享:池化操作可以减少特征图的尺寸,进而减少模型的参数数量,有助于减小过拟合的风险。

  3. 平移不变性:池化操作可以使特征对位置的敏感程度降低,使得模型对于目标在图像中的具体位置不那么敏感。

池化操作的好处主要:

  1. 降低数据维度:池化操作可以减少输入数据的尺寸,从而降低整个模型的参数数量和计算复杂度。这有助于提高模型的训练和推理效率。

  2. 提取主要特征:池化操作通过在输入数据上滑动一个固定大小的窗口,并从窗口内提取一个值来代表该区域的特征。这样的操作可以提取出最显著的特征,有助于增强模型对输入数据的鲁棒性和泛化能力。

  3. 减轻过拟合:池化操作有助于减轻模型的过拟合风险。通过降低数据维度和提取主要特征,池化操作可以减少模型对噪声和细节的敏感性,从而提高模型的泛化能力。

池化操作可能带来的问题:

  1. 信息丢失:池化操作会降低输入数据的分辨率,导致部分细节信息的丢失。在某些任务中,这些细节信息可能对模型的性能有一定影响,因此需要在使用池化操作时进行权衡。

  2. 空间不变性:池化操作会丢失特征图中的位置信息,从而降低了模型对空间位置的感知能力。在一些需要精确位置信息的任务中,这可能会对模型的性能产生影响。

全卷积网络(课上讲的这个概念不准确,同学们查资料纠正一下)

全卷积网络(Fully Convolutional Networks,FCN)将传统CNN后面的全连接层换成了卷积层,这样网络的输出将是热力图而非类别。同时,为解决卷积和池化导致图像尺寸的变小,使用上采样方式对图像尺寸进行恢复。

低级特征、中级特征、高级特征

低级特征通常指一些基本的图像特征,比如边缘、角点、纹理等。这些特征通常可以直接从图像的原始像素值中提取出来,是对图像局部信息的简单描述,例如某个位置上的颜色、亮度变化等。

中级特征则是在低级特征的基础上更进一步进行组合和抽象得到的特征,比如形状、轮廓、纹理布局等。中级特征对应于更加抽象的图像描述,它们可能需要对低级特征进行一定程度的组合和解释,才能够描述图像中的相对复杂的结构信息。

高级特征则是在中级特征的基础上进一步进行抽象和泛化得到的特征,比如物体、场景、语义信息等。高级特征通常需要对整个图像或图像的大片区域进行分析和理解,能够描述图像中更加抽象和语义化的信息。

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

多通道是指在神经网络中同时处理多个输入或输出的数据通道的概念。它可以理解为一种数据的分组方式,其中每个组被称为一个通道。每个通道可以包含不同的信息或特征。例如,在图像处理中,一幅彩色图像可以被分成红、绿、蓝三个通道,每个通道代表了不同的颜色信息。在神经网络中,多通道的输入可以提供更多的特征信息,帮助网络更好地理解和处理输入数据。同样地,多通道的输出表示网络可以产生多个输出结果,每个结果对应一个通道,这些通道可以代表不同的类别、特征或属性。通过多通道的设计,神经网络可以更加灵活地处理和表达复杂的数据,从而提高模型的性能和效果。

对于N输入,假设我们有一个N通道的输入张量,每个通道包含不同的特征信息。在卷积神经网络中,我们会使用N个大小相同的卷积核(filter),每个卷积核与输入张量的对应通道进行卷积操作。这样就可以从每个通道中提取特征,并将这些特征进行组合,得到卷积操作的结果。

对于M输出,假设我们希望得到一个M通道的输出张量,每个通道代表不同的特征表示。通过调整卷积核的数量和设置,我们可以在卷积操作中得到M个不同的卷积结果,即M个输出通道。这样就能够获取到M种不同的特征表示,而且这些特征表示是由输入数据中不同通道的信息经过卷积运算得到的。

1×1的卷积核有什么作用

  1. 降维和增加非线性:应用1×1的卷积核可以将输入数据的通道数进行降维或增加非线性。通过调整卷积核的权重,可以对输入数据的通道进行线性组合,从而减少通道的数量或引入非线性关系。这有助于降低模型的复杂度和计算成本,并提高网络的表达能力。

  2. 特征融合和交互:1×1的卷积核可以用于在不同通道之间进行特征融合和交互。通过在通道维度上进行卷积操作,可以将不同通道的特征信息相互影响和融合,从而生成更加丰富和综合的特征表示。这有助于提高网络对输入数据的理解和区分能力。

二、使用CNN进行XO识别
 1.复现参考资料中的代码

                1.1 数据集

from torch.utils.data import DataLoader
from torchvision import transforms, datasets
 
transforms = transforms.Compose([
    transforms.ToTensor(),  # 把图片进行归一化,并把数据转换成Tensor类型
    transforms.Grayscale(1)  # 把图片 转为灰度图
])
 
data_train = datasets.ImageFolder('train_data', transforms)
data_test = datasets.ImageFolder('test_data', transforms)
 
train_loader = DataLoader(data_train, batch_size=64, shuffle=True)
test_loader = DataLoader(data_test, batch_size=64, shuffle=True)
for i, data in enumerate(train_loader):
    images, labels = data
    print(images.shape)
    print(labels.shape)
    break
 
for i, data in enumerate(test_loader):
    images, labels = data
    print(images.shape)
    print(labels.shape)
    break

                1.2 模型构建

import torch.nn as nn
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Conv2d(1, 9, 3)
        self.pool = 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.pool(self.relu(self.conv1(x)))
        x = self.pool(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

                1.3 模型训练

model = CNN()
 
loss = nn.CrossEntropyLoss()
opti = torch.optim.SGD(model.parameters(), lr=0.1)
 
epochs = 10
 
for epoch in range(epochs):
    total_loss = 0
    for i, data in enumerate(train_loader):
        images, labels = data
        out = model(images)
        one_loss = loss(out, labels)
        opti.zero_grad()
        one_loss.backward()
        opti.step()
        total_loss += one_loss
        if (i + 1) % 10 == 0:
            print('[%d  %5d]   loss: %.3f' % (epoch + 1, i + 1, total_loss / 100))
            total_loss = 0.0
 
print('finished train')
 
# 保存模型
torch.save(model, 'model.pth')  # 保存的是模型, 不止是w和b权重值

                1.4 模型测试

import matplotlib.pyplot as plt
 
# 读取模型
model_load = torch.load('model.pth')
# 读取一张图片 images[0],测试
print("labels[0] truth:\t", labels[0])
x = images[0].unsqueeze(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()

                1.5 计算模型的准确率

# 读取模型
model_load = torch.load('model.pth')
 
correct = 0
total = 0
with torch.no_grad():  # 进行评测的时候网络不更新梯度
    for data in test_loader:  # 读取测试集
        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))

                1.6 查看训练好的模型的特征图

import torch.optim
from torch.utils.data import DataLoader
from torchvision import transforms, datasets
 
transforms = transforms.Compose([
    transforms.ToTensor(),  # 把图片进行归一化,并把数据转换成Tensor类型
    transforms.Grayscale(1)  # 把图片 转为灰度图
])
 
data_train = datasets.ImageFolder('train_data', transforms)
data_test = datasets.ImageFolder('test_data', transforms)
 
train_loader = DataLoader(data_train, batch_size=64, shuffle=True)
test_loader = DataLoader(data_test, batch_size=64, shuffle=True)
for i, data in enumerate(train_loader):
    images, labels = data
    print(images.shape)
    print(labels.shape)
    break
 
for i, data in enumerate(test_loader):
    images, labels = data
    print(images.shape)
    print(labels.shape)
    break
 
import torch.nn as nn
 
 
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Conv2d(1, 9, 3)
        self.pool = 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):
        outputs = []
        x = self.conv1(x)
        outputs.append(x)
        x = self.relu(x)
        outputs.append(x)
        x = self.pool(x)
        outputs.append(x)
        x = self.conv2(x)
 
        x = self.relu(x)
 
        x = self.pool(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
 
 
 
 
 
import matplotlib.pyplot as plt
import numpy as np
# 读取模型
model = torch.load('model.pth')
print(model)
x = images[0].unsqueeze(0)
 
# forward正向传播过程
out_put = model(x)
for feature_map in out_put:
    # [N, C, H, W] -> [C, H, W]    维度变换
    im = np.squeeze(feature_map.detach().numpy())
    print(im.shape)
    # [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()

                1.7 查看训练好的模型的卷积核

import torch.optim
from torch.utils.data import DataLoader
from torchvision import transforms, datasets
 
transforms = transforms.Compose([
    transforms.ToTensor(),  # 把图片进行归一化,并把数据转换成Tensor类型
    transforms.Grayscale(1)  # 把图片 转为灰度图
])
 
data_train = datasets.ImageFolder('train_data', transforms)
data_test = datasets.ImageFolder('test_data', transforms)
 
train_loader = DataLoader(data_train, batch_size=64, shuffle=True)
test_loader = DataLoader(data_test, batch_size=64, shuffle=True)
for i, data in enumerate(train_loader):
    images, labels = data
    print(images.shape)
    print(labels.shape)
    break
 
for i, data in enumerate(test_loader):
    images, labels = data
    print(images.shape)
    print(labels.shape)
    break
 
import torch.nn as nn
 
 
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Conv2d(1, 9, 3)
        self.pool = 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):
        outputs = []
        x = self.conv1(x)
        outputs.append(x)
        x = self.relu(x)
        outputs.append(x)
        x = self.pool(x)
        outputs.append(x)
        x = self.conv2(x)
 
        x = self.relu(x)
 
        x = self.pool(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
 
 
 
 
 
import matplotlib.pyplot as plt
import numpy as np
# 读取模型
model = torch.load('model.pth')
print(model)
x = images[0].unsqueeze(0)
 
# forward正向传播过程
out_put = model(x)
for feature_map in out_put:
    # [N, C, H, W] -> [C, H, W]    维度变换
    im = np.squeeze(feature_map.detach().numpy())
    print(im.shape)
    # [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()

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

import torch.nn as nn
import torch
 
from torch.utils.data import DataLoader
from torchvision import transforms, datasets
 
# 将输入的图片先转换为灰度图,然后将其转换为 Tensor,并进行归一化处理。这样处理后的数据可以直接作为模型的输入。
transforms = transforms.Compose([
    transforms.ToTensor(),  # 把图片进行归一化,并把数据转换成Tensor类型
    transforms.Grayscale(1)  # 把图片 转为灰度图
])
 
# dataset.ImageFolder 将数据集封装为dataset类型
# root (string): 数据集的根目录路径。在这个根目录下,每个类别的图像应该被存放在一个单独的子文件夹中。每个子文件夹的名称将被视为一个类别,并且其中的图像将被标记为该类别。
# transform (callable, optional): 一个对图像进行变换的函数或转换操作。可以使用 transforms 模块中的函数来对图像进行预处理、数据增强等操作,例如将图像转换为 Tensor 类型、调整大小、裁剪等。
data_train = datasets.ImageFolder('train_data', transforms)
data_test = datasets.ImageFolder('test_data', transforms)
 
train_loader = DataLoader(data_train, batch_size=64, shuffle=True)
test_loader = DataLoader(data_test, batch_size=64, shuffle=True)
 
for i, data in enumerate(train_loader):
    images, labels = data
    print(images.shape)
    print(labels.shape)
    break
 
for i, data in enumerate(test_loader):
    images, labels = data
    print(images.shape)
    print(labels.shape)
    break
import torch.nn as nn
 
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Conv2d(1, 9, 3)
        self.conv2 = nn.Conv2d(9, 9, 3)
        self.conv3 = nn.Conv2d(9, 7, 3)  # 更改第三个卷积层的输出通道数为7
        self.pool = nn.MaxPool2d(2, 2)
        self.relu = nn.ReLU()
 
        self.fc1 = nn.Linear(12 * 12 * 7, 1200)  # 根据新的维度更新全连接层的输入
        self.fc2 = nn.Linear(1200, 64)
        self.fc3 = nn.Linear(64, 2)
 
    def forward(self, x):
        x = self.pool(self.relu(self.conv1(x)))
        x = self.pool(self.relu(self.conv2(x)))
        x = self.pool(self.relu(self.conv3(x)))  # 添加对第三个卷积层的处理
        x = x.view(-1, 12 * 12 * 7)  # 根据新的维度进行reshape
        x = self.relu(self.fc1(x))
        x = self.relu(self.fc2(x))
        x = self.fc3(x)
        return x
model = CNN()
loss = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
epochs = 30
 
for epoch in range(epochs):
    total_loss = 0.0
    for i, data in enumerate(train_loader):
        images, labels = data
        out = model(images)
        one_loss = loss(out, labels)
        optimizer.zero_grad()
        one_loss.backward()
        optimizer.step()
        total_loss += one_loss
        if (i + 1) % 10 == 0:
            print('[%d  %5d]   loss: %.3f' % (epoch + 1, i + 1, total_loss / 100))
            total_loss = 0.0
 
print('train finish')
 
torch.save(model, 'model.pth')  # 保存的是模型, 不止是w和b权重值
torch.save(model.state_dict(), 'model_name1.pth')  # 保存的是w和b权重值
 
correct = 0
total = 0
with torch.no_grad():  # 进行评测的时候网络不更新梯度
    for data in test_loader:  # 读取测试集
        images, labels = data
        outputs = model(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.nn as nn
import torch
 
from torch.utils.data import DataLoader
from torchvision import transforms, datasets
 
# 将输入的图片先转换为灰度图,然后将其转换为 Tensor,并进行归一化处理。这样处理后的数据可以直接作为模型的输入。
transforms = transforms.Compose([
    transforms.ToTensor(),  # 把图片进行归一化,并把数据转换成Tensor类型
    transforms.Grayscale(1)  # 把图片 转为灰度图
])
 
# dataset.ImageFolder 将数据集封装为dataset类型
# root (string): 数据集的根目录路径。在这个根目录下,每个类别的图像应该被存放在一个单独的子文件夹中。每个子文件夹的名称将被视为一个类别,并且其中的图像将被标记为该类别。
# transform (callable, optional): 一个对图像进行变换的函数或转换操作。可以使用 transforms 模块中的函数来对图像进行预处理、数据增强等操作,例如将图像转换为 Tensor 类型、调整大小、裁剪等。
data_train = datasets.ImageFolder('train_data', transforms)
data_test = datasets.ImageFolder('test_data', transforms)
 
train_loader = DataLoader(data_train, batch_size=64, shuffle=True)
test_loader = DataLoader(data_test, batch_size=64, shuffle=True)
 
for i, data in enumerate(train_loader):
    images, labels = data
    print(images.shape)
    print(labels.shape)
    break
 
for i, data in enumerate(test_loader):
    images, labels = data
    print(images.shape)
    print(labels.shape)
    break
import torch.nn as nn
import torch
 
from torch.utils.data import DataLoader
from torchvision import transforms, datasets
 
# 将输入的图片先转换为灰度图,然后将其转换为 Tensor,并进行归一化处理。这样处理后的数据可以直接作为模型的输入。
transforms = transforms.Compose([
    transforms.ToTensor(),  # 把图片进行归一化,并把数据转换成Tensor类型
    transforms.Grayscale(1)  # 把图片 转为灰度图
])
 
# dataset.ImageFolder 将数据集封装为dataset类型
# root (string): 数据集的根目录路径。在这个根目录下,每个类别的图像应该被存放在一个单独的子文件夹中。每个子文件夹的名称将被视为一个类别,并且其中的图像将被标记为该类别。
# transform (callable, optional): 一个对图像进行变换的函数或转换操作。可以使用 transforms 模块中的函数来对图像进行预处理、数据增强等操作,例如将图像转换为 Tensor 类型、调整大小、裁剪等。
data_train = datasets.ImageFolder('train_data', transforms)
data_test = datasets.ImageFolder('test_data', transforms)
 
train_loader = DataLoader(data_train, batch_size=64, shuffle=True)
test_loader = DataLoader(data_test, batch_size=64, shuffle=True)
 
for i, data in enumerate(train_loader):
    images, labels = data
    print(images.shape)
    print(labels.shape)
    break
 
for i, data in enumerate(test_loader):
    images, labels = data
    print(images.shape)
    print(labels.shape)
    break
 
 
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=1, out_channels=9, kernel_size=3)
        self.conv2 = nn.Conv2d(in_channels=9, out_channels=5, kernel_size=3)
        self.relu = nn.ReLU()
 
        self.fc1 = nn.Linear(in_features=112 * 112 * 5, out_features=1200)
        self.fc2 = nn.Linear(in_features=1200, out_features=64)
        self.fc3 = nn.Linear(in_features=64, out_features=2)
 
    def forward(self, input):
        output = self.relu(self.conv1(input))
        output = self.relu(self.conv2(output))
        output = output.view(-1, 112 * 112 * 5)
        output = self.relu(self.fc1(output))
        output = self.relu(self.fc2(output))
        output = self.fc3(output)
        return output
 
 
model = CNN()
loss = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
epochs = 10
 
for epoch in range(epochs):
    total_loss = 0.0
    for i, data in enumerate(train_loader):
        images, labels = data
        out = model(images)
        one_loss = loss(out, labels)
        optimizer.zero_grad()
        one_loss.backward()
        optimizer.step()
        total_loss += one_loss
        if (i + 1) % 10 == 0:
            print('[%d  %5d]   loss: %.3f' % (epoch + 1, i + 1, total_loss / 100))
            total_loss = 0.0
 
print('train finish')
 
torch.save(model, 'model.pth')  # 保存的是模型, 不止是w和b权重值
torch.save(model.state_dict(), 'model_name1.pth')  # 保存的是w和b权重值
 
correct = 0
total = 0
with torch.no_grad():  # 进行评测的时候网络不更新梯度
    for data in test_loader:  # 读取测试集
        images, labels = data
        outputs = model(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))
 
# 损失降低的很快,但是最后的时候收敛很慢
model = CNN()
loss = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
epochs = 30
 
for epoch in range(epochs):
    total_loss = 0.0
    for i, data in enumerate(train_loader):
        images, labels = data
        out = model(images)
        one_loss = loss(out, labels)
        optimizer.zero_grad()
        one_loss.backward()
        optimizer.step()
        total_loss += one_loss
        if (i + 1) % 10 == 0:
            print('[%d  %5d]   loss: %.3f' % (epoch + 1, i + 1, total_loss / 100))
            total_loss = 0.0
 
print('train finish')
 
torch.save(model, 'model.pth')  # 保存的是模型, 不止是w和b权重值
torch.save(model.state_dict(), 'model_name1.pth')  # 保存的是w和b权重值
 
correct = 0
total = 0
with torch.no_grad():  # 进行评测的时候网络不更新梯度
    for data in test_loader:  # 读取测试集
        images, labels = data
        outputs = model(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))

去掉池化层后,训练时间明显变长,但训练效果并未得到提升,说明池化后,图像信息并未丢失到会使训练效果变差的程度。


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

# https://blog.csdn.net/qq_53345829/article/details/124308515
import torch
from torchvision import transforms, datasets
import torch.nn as nn
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
import torch.optim as optim
 
transforms = transforms.Compose([
    transforms.ToTensor(),  # 把图片进行归一化,并把数据转换成Tensor类型
    transforms.Grayscale(1)  # 把图片 转为灰度图
])
 
path = r'train_data'
path_test = r'test_data'
 
data_train = datasets.ImageFolder(path, transform=transforms)
data_test = datasets.ImageFolder(path_test, transform=transforms)
 
print("size of train_data:", len(data_train))
print("size of test_data:", len(data_test))
 
data_loader = DataLoader(data_train, batch_size=64, shuffle=True)
data_loader_test = DataLoader(data_test, batch_size=64, shuffle=True)
 
for i, data in enumerate(data_loader):
    images, labels = data
    print(images.shape)
    print(labels.shape)
    break
 
for i, data in enumerate(data_loader_test):
    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, 16, 3)
        self.maxpool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(16, 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
 
 
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
    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.state_dict(), model_path)
torch.save(model, 'model_name6.pth')  # 保存的是模型, 不止是w和b权重值
 
# 读取模型
model = torch.load('model_name6.pth')
 
correct = 0
total = 0
with torch.no_grad():  # 进行评测的时候网络不更新梯度
    for data in data_loader_test:  # 读取测试集
        images, labels = data
        outputs = model(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))

增加卷积层的通道数有助于提高准确率,因为这样可以提供更丰富的信息,使模型能够更详细地理解输入数据。每个通道对应于输入数据的一种特定特征,增加通道数意味着模型可以检测到更多的特征,从而提高模型的表达能力。然而,当通道数增加到一定程度时,准确率可能会开始下降,这可能是由于过拟合问题。增加通道数可能会增加模型的复杂性,从而使模型更容易过拟合。此外,增加通道数也会增加模型的参数数量,这可能会导致模型难以优化。更多的参数意味着需要更多的数据来训练模型,否则模型可能会出现欠拟合问题。

3.可视化

选择自己的最优模型,可视化部分卷积核和特征图,这两个都在上面。


探索低级特征、中级特征、高级特征 

import torch
import torch.nn as nn
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
from PIL import Image

# 加载图片并转为灰度图
img = Image.open('2233.png').convert('L')

# 定义转换器
transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
])

# 对图片进行转换
img_tensor = transform(img)

# 定义模型
model = nn.Sequential(
    nn.Conv2d(1, 8, kernel_size=3, stride=1, padding=1),
    nn.ReLU(),
    nn.MaxPool2d(kernel_size=2, stride=2),
    nn.Conv2d(8, 16, kernel_size=3, stride=1, padding=1),
    nn.ReLU(),
    nn.MaxPool2d(kernel_size=2, stride=2),
    nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1),
    nn.ReLU(),
    nn.MaxPool2d(kernel_size=2, stride=2),
)

# 提取初级特征
with torch.no_grad():
    x = img_tensor.unsqueeze(0)
    x = model[:4](x)
    x = x.squeeze(0)
    for i in range(8):
        plt.subplot(2, 4, i + 1)
        plt.imshow(x[i], cmap='gray')
        plt.axis('off')
    plt.show()

# 提取中级特征
with torch.no_grad():
    x = img_tensor.unsqueeze(0)
    x = model[:8](x)
    x = x.squeeze(0)
    for i in range(16):
        plt.subplot(4, 4, i + 1)
        plt.imshow(x[i], cmap='gray')
        plt.axis('off')
    plt.show()

# 提取高级特征
with torch.no_grad():
    x = img_tensor.unsqueeze(0)
    x = model[:12](x)
    x = x.squeeze(0)
    for i in range(32):
        plt.subplot(8, 4, i + 1)
        plt.imshow(x[i], cmap='gray')
        plt.axis('off')
    plt.show()

实验感悟:

本次实验的主要目标是使用卷积神经网络对xo数据集进行分类。在实验过程中,我了解了卷积神经网络的整体流程,并重点学习了卷积层和池化层的功能。卷积层通过使用卷积核提取图像特征,并将这些特征传递给下一层网络,起到了关键的作用。而池化层则可以提高训练的效率。此外,我们还进行了不同卷积参数和网络结构的实验,以观察它们对分类效果的影响。通过实验结果得出不同参数和结构变化对分类效果的差异。

参考文献:

NNDL 作业6:基于CNN的XO识别

卷积神经网络中的批归一化(Batch Normalization)技术解析

系统学习Pytorch笔记三:Pytorch数据读取机制(DataLoader)与图像预处理模块(transforms)
 

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

DL Homework 7

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值