NNDL 作业7 基于CNN的XO识别

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

  1. 局部感知、权值共享
  2. 池化(子采样、降采样、汇聚)。会带来那些好处和坏处?
  3. 全卷积网络(课上讲的这个概念不准确,同学们查资料纠正一下)
  4. 低级特征、中级特征、高级特征
  5. 多通道。N输入,M输出是如何实现的?
  6. 1×1的卷积核有什么作用

1.深度学习的两大核心特征:局部感知和权值共享

黄色部分是滤波器

上面是局部感知的示例图,也就是局部连接,传统的神经网络是全连接的,权值很多,计算量很大。如果输入一张图片,局部连接,每个神经元都在卷积层进行局部感知,然后几层卷积池化后最后综合起来。减少复杂性和参数量。

数据量:局部连接数据量=感受野*参数个数

为什么要进行局部感知?

在图像中,像素之间的相关性与其距离有关。距离较近的像素间相关性较强,而距离较远的像素间相关性较弱。因此,每个神经元无需对全局图像进行感知,只需要对局部进行感知,然后在更高层将局部的信息综合起来就得到了全局的信息。引入了多核卷积,获取更全面的特征。

权值共享:滤波器在滑动的过程中,输入在变化,但中间滤波器(filter)的权重(即每个神经元连接数据窗口的权重)是固定不变的,这个权重不变即所谓的CNN中的权重(参数)共享机制。

用老师给的动图更生动形象理解局部感知和权值共享

2.池化(子采样、降采样、汇聚),好处和坏处

池化,简言之,即取区域平均或最大,其目的是为了减少特征图,尺寸一般为(2,2),常见的池化操作有maxpooling和mean pooling。Maxpooling是指对卷积层的输出结果进行分组取最大值,mean pooling是指取分组中所有元素的平均值。这两种池化方式都能够将特征图进行下采样,从而减少计算量。

    def __init__(self, size=(2, 2), mode='max', stride=1):
        super(Pool2D, self).__init__()
        # 汇聚方式
        self.mode = mode
        self.h, self.w = size
        self.stride = stride

mode汇聚方式最大或者平均

子采样:在卷积神经网络中用于降维,减少网络学习的参数数量,防止过拟合,扩大感知野,或者实现平移不变性,旋转不变性,尺度不变性。

降采样:采样点减少。这样是为了使得图像符合显示区域的大小,生成对应图像的缩略图。

汇聚:对提取特征进行综合,目的是为了降低卷积层对位置的敏感性,同时降低对空间降采样表示的敏感性。

好处:

1. 降低特征图的维度,减少网络中参数的数量,避免过拟合现象的发生

2. 提高模型的计算速度和运行效率。

3.通过调整池化参数优化模型

坏处:降低模型的预测精度,因为池化过程中会丢失一些细节

全卷积网络: 

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

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

第一个从端到端训练的卷积神经网络

图片转自全卷积神经网络(Fully Convolutional Neural Network,FCN) - 知乎 (zhihu.com)

低级特征:包含图像底层信息,对于图像的平移、旋转和缩放具有较强的不变性。

中级特征 :包括对象信息,形状和空间信息,通常是在网络的中间层次进行提取的特征,比低级特征具有更高层次的抽象和语义含义。将低级特征组合起来。

高级特征:包括对象内在的语义信息,在神经网络的较深层次中提取的抽象和语义丰富的特征表示。

多通道,N输入,M输出?

 多通道,N输入,M输出是指一个具有多个输入通道和多个输出通道,在进行基础卷积计算后,初始化一个空列表存储最后的卷积的结果,对p个(w,b),计算特征图,循环计算出每个输入特征图对应的卷积结果,将所有卷积结果相加,最后使用torch.stack()对所有Zp进行堆叠。

下面展示多通道卷积算子的代码

class Conv2D_2(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0, weight_attr=[], bias_attr=[]):
        super(Conv2D_2, self).__init__()
        self.weight = torch.nn.Parameter(weight_attr)
        self.bias = torch.nn.Parameter(bias_attr)
        self.stride = stride
        self.padding = padding
        # 输入通道数
        self.in_channels = in_channels
        # 输出通道数
        self.out_channels = out_channels

    # 基础卷积运算
    def single_forward(self, X, weight):
        # 零填充
        new_X = torch.zeros([X.shape[0], X.shape[1] + 2 * self.padding, X.shape[2] + 2 * self.padding])
        new_X[:, self.padding:X.shape[1] + self.padding, self.padding:X.shape[2] + self.padding] = X
        u, v = weight.shape
        output_w = (new_X.shape[1] - u) // self.stride + 1
        output_h = (new_X.shape[2] - v) // self.stride + 1
        output = torch.zeros([X.shape[0], output_w, output_h])
        for i in range(0, output.shape[1]):
            for j in range(0, output.shape[2]):
                output[:, i, j] = torch.sum(
                    new_X[:, self.stride * i:self.stride * i + u, self.stride * j:self.stride * j + v] * weight,
                    dim=[1, 2])
        return output

    def forward(self, inputs):
        feature_maps = []
        # 进行多次多输入通道卷积运算
        p = 0
        for w, b in zip(self.weight, self.bias):  # P个(w,b),每次计算一个特征图Zp
            multi_outs = []
            # 循环计算每个输入特征图对应的卷积结果
            for i in range(self.in_channels):
                single = self.single_forward(inputs[:, i, :, :], w[i])
                multi_outs.append(single)
                # print("Conv2D in_channels:",self.in_channels,"i:",i,"single:",single.shape)
            # 将所有卷积结果相加
            feature_map = torch.sum(torch.stack(multi_outs), dim=0) + b  # Zp
            feature_maps.append(feature_map)
            # print("Conv2D out_channels:",self.out_channels, "p:",p,"feature_map:",feature_map.shape)
            p += 1
        # 将所有Zp进行堆叠
        out = torch.stack(feature_maps, 1)
        return out

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

1.灵活控制特征图的深度,主要用于调节通道数,对不同的通道上的像素点进行线性组合,然后进行非线性化操作,可以完成升维和降维的功能

2.减少参数,在特征图的通道数上进行卷积,压缩特征图,二次提取特征,使得新特征图的特征表达更佳。

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

3.实现跨通道信息组合,增加了非线性特征。

 二、使用CNN进行XO识别

 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
 
#可视化数据集
import matplotlib.pyplot as plt
a=0
plt.figure()
index=0
 
for i in labels:
    if i == 0 and a<5:
        plt.subplot(151+a)
        plt.imshow(images[index].data.squeeze().numpy(),cmap='gray')
        plt.title('circle '+str(a+1))
        a+=1
    if a==5:
        break
    index+=1
 
plt.show()
a=0
plt.figure()
index=0
for i in labels:
    if i == 1 and a<5:
        plt.subplot(151+a)
        plt.imshow(images[index].data.squeeze().numpy(),cmap='gray')
        plt.title('crosses '+str(a+1))
        a+=1
    if a==5:
        break
    index+=1
 
plt.show()

可视化

结果显示torch.size([64,1,116,116])

它描述了一个四维张量的尺寸。这个张量有64个一维的切片(slice),每个切片有1个二维的切片,每个二维切片有116个一维的切片,每个一维切片有116个元素。

64张图片,一个通道数,图片高度和宽度,也就是代码中的图片的形状

构建模型 

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

模型训练 

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, 'model_name.pth')  # 保存的是模型, 不止是w和b权重值

 

测试训练好的模型/计算准确率

model = Net()
model.load_state_dict(torch.load('model_name1.pth', map_location='cpu'))  # 导入网络的参数

# model_load = torch.load('model_name1.pth')
# https://blog.csdn.net/qq_41360787/article/details/104332706

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

查看训练好的模型特征图

# 看看每层的 卷积核 长相,特征图 长相
# 获取网络结构的特征矩阵并可视化
import torch
import matplotlib.pyplot as plt
import numpy as np
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'train_data'
#使用ImageFolder从指定路径加载图像数据,对数据进行预处理
data_train = datasets.ImageFolder(path, transform=transforms)
#创建DataLoader,用于批量加载数据,并打乱数据顺序
data_loader = DataLoader(data_train, batch_size=64, shuffle=True)
for i, data in enumerate(data_loader):
    images, labels = data
    print('images.shape:',images.shape)
    print('labels.shape',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) #保存经过卷积层1后的输出
        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
model1.load_state_dict(torch.load(model_weight_path))
 
# 打印出模型的结构
print('模型结构:','\n',model1)
 
x = images[0] #选取输入的第一张图象
out_put = model1(x) #进行forward正向传播过程,得到输出结果
 
#特征图可视化
for feature_map in out_put: #遍历模型的输出结果(特征图)
    # [N, C, H, W] -> [C, H, W]    维度变换
    #将pytorch转换为numpy数组,并去除额外的维度
    im = np.squeeze(feature_map.detach().numpy())
    # [C, H, W] -> [H, W, C]
    im = np.transpose(im, [1, 2, 0]) #对特征图进行维度变换
    print('im.shape:','\n',im.shape)
 
    # show 9 feature maps
    plt.figure()
    for i in range(9):
        ax = plt.subplot(3, 3, i + 1) #i+1:图的索引
        # [H, W, C]
        # 特征矩阵每一个channel对应的是一个二维的特征矩阵,就像灰度图像一样,channel=1
        # plt.imshow(im[:, :, i])
        plt.imshow(im[:, :, i], cmap='gray')
    plt.show()

查看训练好模型卷积核

# 看看每层的 卷积核 长相,特征图 长相
# 获取网络结构的特征矩阵并可视化
import torch
import matplotlib.pyplot as plt
import numpy as np
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'train_data'
# 使用ImageFolder从指定路径加载图像数据,对数据进行预处理
data_train = datasets.ImageFolder(path, transform=transforms)
# 创建DataLoader,用于批量加载数据,并打乱数据顺序
data_loader = DataLoader(data_train, batch_size=64, shuffle=True)
for i, data in enumerate(data_loader):
    images, labels = data
    print('images.shape:', images.shape)
    print('labels.shape', 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)  # 保存经过卷积层1后的输出
        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
model1.load_state_dict(torch.load(model_weight_path))

# 打印出模型的结构
print('模型结构:', '\n', model1)

x = images[0]  # 选取输入的第一张图象
out_put = model1(x)  # 进行forward正向传播过程,得到输出结果

# 获取模型所有权重键key
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()  # 获取卷积层的权重,并将Tensor转换为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()

等等,共有 channel8,8通道

 

 2.重新设计网络结构

  • 至少增加一个卷积层,卷积层达到三层以上
  • 去掉池化层,对比“有无池化”的效果
  • 修改“通道数”等超参数,观察变化

增加卷积层

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

池化层可以防止过拟合,结果一样,说明我们去掉后没有过拟合 

但是参数数量增加了,运行速度明显减慢

修改通道数

将第一层输出改为11

import torch
from torchvision import transforms, datasets
import torch.nn as nn
from torch.utils.data import DataLoader
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__()
        # 这是2D卷积层,输入通道为1,输出通道为9. 卷积核大小为3
        self.conv1 = nn.Conv2d(1, 11, 3)

        # 这是一个2D最大池化层,使用2*2的窗口进行最大值操作,将卷积层的输出进行池化。
        self.maxpool = nn.MaxPool2d(2, 2)

        # 卷积层2.输入通道为9(上一个卷积层的输出通道数),输出通道为5,卷积核大小为3
        self.conv2 = nn.Conv2d(11, 5, 3)

        # 非线性激活函数。可以将负值变为0,正直保持不变
        self.relu = nn.ReLU()

        # 这是全连接层,输入节点数为27*27*5,输出节点数为1200
        self.fc1 = nn.Linear(27 * 27 * 5, 1200)
        self.fc2 = nn.Linear(1200, 64)  # 全连接层2 输入节点数为上一个连接层输出的节点数
        self.fc3 = nn.Linear(64, 2)

    # 前向传播
    def forward(self, x):
        # x经过2个卷积层和2个全连接层 返回输出结果x
        x = self.maxpool(self.relu(self.conv1(x)))  # 卷积-ReLu激活-最大池化
        x = self.maxpool(self.relu(self.conv2(x)))  # 卷积-ReLu激活-最大池化

        # Flatten 扁平化处理
        x = x.view(-1, 27 * 27 * 5)  # 修改x的形状,返回新的视图,不会改变原有的数据

        # 全连接层
        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)  # 优化函数:随机梯度下降,学习率为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.state_dict(), 'model_name1.pth')  # 保存的是模型, 不止是w和b权重值

# 读取模型
model = torch.load('model_name1.pth')

后续我又改了一些数字,发现通道少会运行结果不佳,过多又会导致过拟合

三、可视化 

  • 选择自己的最优模型
  • 可视化部分卷积核和特征图
  • 探索低级特征、中级特征、高级特征 

前面两个已经在前面说过了

探索低级,中级,高级特征。

import matplotlib.pyplot as plt
import torch
from PIL import Image
import numpy as np
import torch.nn as nn


def conv2d(input, Kernel):
    plt.figure()

    # 创建卷积层,输入通道为1,输出通道为1,卷积核大小为3*3
    Conv = nn.Conv2d(1, 1, 3, bias=False)
    Conv.weight = torch.nn.Parameter(Kernel)
    Conv_output = Conv(input)
    Conv_output_picture = Conv_output.squeeze().cpu().detach().numpy()
    plt.imshow(Conv_output_picture, cmap='gray')
    plt.show()
    return Conv_output


# 创建3*3卷积核
Kernel = torch.tensor([[1.0, -1.0, -1.0],
                       [2.0, 1.0, -2.0],
                       [1.0, -1.0, -1.0]], dtype=torch.float32).view([1, 1, 3, 3])

# 读取图片
path = r'C:\\Users\\dell\\Desktop\\duola.jpg'  # 读取图片路径
img = Image.open(path).convert('L')  # 转为灰度图
print(img.size)

# 将图像转换为NumPy数组
img = img.resize((300, 300))
img_array = np.array(img)
x = torch.tensor(img_array, dtype=torch.float32).view([1, 1, 300, 300])

x_1 = conv2d(x, Kernel)
x_2 = conv2d(x_1, Kernel)
x_3 = conv2d(x_2, Kernel)

参考文献

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

局部感知与权值共享_局部感受野和权值共享_Le0v1n的博客-CSDN博客

[23-24 ]NNDL作业7-基于CNN的XO识别-CSDN博客

NNDL 作业7 基于CNN的XO识别-CSDN博客 

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值