一、用自己的语言解释以下概念
1. 局部感知、权值共享
局部感知:
重要性:若每一个神经元都像全连接一样,则需要的参数太多了,因此采用局部感知的方法。
每一个神经元只需要感知图像中的局部信息,然后在更高层次进行信息组合就可以得到全局信息。我认为这里可以与感受野联系起来,深层网络的感受野更大,大感受野下才存在一定的高阶语义,深层网络所积累的特征空间更大。所以可以用局部感知来减少参数。(感受野部分借鉴了NNDL 作业6 卷积神经网络的相关知识点-CSDN博客)
权值共享:(【精选】理解卷积神经网络中的权值共享_神经网络共享层-CSDN博客)
权值共享就是说,给一张输入图片,用一个卷积核去扫这张图,卷积核里面的数就叫权重,权值共享就意味着,并不是说每滑动一次窗口,卷积核里面的值要变一下,而是卷积核里面的值是固定的,这张图每个位置是被同样的卷积核扫的,所以权重是一样的,也就是共享。在卷积过程中不会变,只有反向传播时参数优化才会变。权值共享如下图:
权值不共享 :
全连接网络(fully connect),即每个元素单元与隐层的神经原进行全连接,网络结构如下所示。
可以看出权值共享的参数量最少,权值不共享和全连接参数量巨大,可能会导致无法训练。
2. 池化(子采样、降采样、汇聚)。会带来那些好处和坏处?
池化流程:(鱼书中的图)
好处:
减少计算量:池化可以减小特征图的尺寸,能够有更低的维度,从而减少后续层级的计算量,提高计算效率。
参数共享:参数过多容易发生过拟合,由于池化不含可学习参数,因此池化操作并不会增加模型的参数数量,有利于减少过拟合的风险。
平移不变性:池化可以使特征对于位置的变化更加鲁棒,即使目标在图像中稍微移动,仍然可以检测到相同的特征。
坏处:
信息丢失:池化会舍弃部分细节信息,可能导致丢失对分类或者定位任务重要的细节。
分辨率下降:池化会导致特征图的尺寸减小,这可能会限制网络对输入数据细微变化的感知能力。
过度采样:在一些情况下,过度的池化操作可能会导致信息丢失过多,影响了网络对输入数据的有效表征
3. 全卷积网络(课上讲的这个概念不准确,同学们查资料纠正一下)
全卷积网络(Fully Convolutional Networks,FCN)将传统CNN后面的全连接层换成了卷积层,并非去掉了池化,而是全连接。这样网络的输出将是热力图而非类别;同时,为解决卷积和池化导致图像尺寸的变小,使用上采样方式对图像尺寸进行恢复。
核心思想
- 不含全连接层的全卷积网络,可适应任意尺寸输入;
- 反卷积层增大图像尺寸,输出精细结果;
- 结合不同深度层结果的跳级结构,确保鲁棒性和精确性。
FCN的不足
- 得到的结果还不够精细,对细节不够敏感;
- 未考虑像素与像素之间的关系,缺乏空间一致性等。
4. 低级特征、中级特征、高级特征
低级特征指的是在神经网络的浅层或较早的卷积层中提取到的原始、局部的图像特征。这些特征通常包含一些基本的形状、边缘、纹理和颜色等低层次的信息。
中级特征是在网络的中间层次进行提取的特征,它们具有比低级特征更高层次的抽象和语义含义。中级特征可以将低级特征组合起来,形成更具有代表性和抽象性的特征表示。
高级特征是在神经网络的较深层次中提取的抽象和语义丰富的特征表示。它们通过对中级特征的进一步组合和整合,能够对整体图像的语义信息和高层次结构进行建模。
5. 多通道。N输入,M输出是如何实现的?
多通道输入:
当输入数据含多个通道时,需要构造一个输入通道数与输入数据的通道数相同的卷积核,从而能够与含多通道的输入数据做互相关运算。
多个输出通道
在最流行的神经网络架构中,随着神经网络层数的加深,我们常会增加输出通道的维数,通过减少空间分辨率以获得更大的通道深度。
6. 1×1的卷积核有什么作用
下图是老师给出的:
1×1卷积层看起来似乎没有多大意义,它并没有提取相邻像素间的相关特征,但却是很受欢迎的,因为它虽然不识别空间模式,但起到了一个融合通道的作用
GoogleNet中的1 × 1 1\times 11×1卷积:降低网络复杂性
(【精选】【动手深度学习-笔记】多通道卷积核、1X1卷积_wonder-wall的博客-CSDN博客这里的解释更加详细)
二、使用CNN进行XO识别
1.复现参考资料中的代码
数据集:
代码:(这里我对代码进行了注释)
训练集:
#训练集
# 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'
# 使用PyTorch的datasets.ImageFolder函数从指定的路径读取图像,并根据图像文件的名字自动为图像生成标签,transform参数指定了应用于图像的转换操作
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_train/data_test中加载数据,每次加载batch_size数量的数据。shuffle参数指定是否需要在每次加载数据时打乱数据的顺序。
data_loader = DataLoader(data_train, batch_size=64, shuffle=True)
data_loader_test = DataLoader(data_test, batch_size=64, shuffle=True)
# 通过循环遍历data_loader,每次取出其中的一对数据(图像和标签)并打印其形状。
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, 9, 3) # in_channel , out_channel , kennel_size , stride
self.maxpool = nn.MaxPool2d(2, 2) # kernel_size,stride
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):#进行最大池化和前馈神经网络的部分
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()#调用backward()函数实现反向传播
optimizer.step()#更新模型参数
running_loss += loss.item()
if (i + 1) % 10 == 0:#每10次训练进行一次输出
print('[%d %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 100))
running_loss = 0.0#为计算下一轮10个batch的平均损失做准备
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')
测试集:
#测试集
# 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)
print(len(data_loader))
print(len(data_loader_test))
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)# kernel_size,stride
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):
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()
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))
# "_," 的解释 https://blog.csdn.net/weixin_48249563/article/details/111387501
运行结果:
2.重新设计网络结构
- 至少增加一个卷积层,卷积层达到三层以上
- 去掉池化层,对比“有无池化”的效果
- 修改“通道数”等超参数,观察变化
增加一个卷积层,卷积层达到三层
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) # kernel_size,stride
self.conv2 = nn.Conv2d(9, 5, 3) # in_channel , out_channel , kennel_size , stride
self.conv3=nn.Conv2d(5,5,3)
self.relu = nn.ReLU()
self.fc1 = nn.Linear(12 * 12 * 5, 480) # full connect 1
self.fc2 = nn.Linear(480, 320) # full connect 2
self.fc3 = nn.Linear(320, 2) # full connect 3
def forward(self, x):#进行最大池化和前馈神经网络的部分
x = self.maxpool(self.relu(self.conv1(x)))
x = self.maxpool(self.relu(self.conv2(x)))
x=self.maxpool(self.relu(self.conv3(x)))
x = x.view(-1, 12 * 12 * 5)
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
x = self.fc3(x)
return x
可以看出,增加一层卷积层后误差变大,准确率下降,效果不是很好。
参考了舍友【23-24 秋学期】NNDL 作业7 基于CNN的XO识别-CSDN博客的博客发现增加训练次数可以使效果更好。博客中是改到了20轮,我增大训练轮数到30先试一试
训练30轮:
我发现将训练轮数调到30准确率降到了50%,发现不行,应该是出现了过拟合现象。
于是我改成了20轮:
[1 10] loss: 0.069
[1 20] loss: 0.069
[2 10] loss: 0.068
[2 20] loss: 0.066
[3 10] loss: 0.055
[3 20] loss: 0.041
[4 10] loss: 0.016
[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.002
[7 20] loss: 0.001
[8 10] loss: 0.000
[8 20] loss: 0.001
[9 10] loss: 0.000
[9 20] loss: 0.001
[10 10] loss: 0.001
[10 20] loss: 0.000
[11 10] loss: 0.000
[11 20] loss: 0.001
[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
Accuracy of the network on the test images: 100.000000 %
这里训练20轮误差为0,准确率为100%已经达到了最好。
去掉池化层,对比“有无池化”的效果
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 9, 3)
self.conv2 = nn.Conv2d(9, 5, 3)
self.relu = nn.ReLU()
self.fc1 = nn.Linear(112 * 112 * 5, 4000)
self.fc2 = nn.Linear(4000, 2400)
self.fc3 = nn.Linear(2400, 2)
def forward(self, x):
x = self.relu(self.conv1(x))
x = self.relu(self.conv2(x))
x = x.view(-1, 112 * 112 * 5)
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
x = self.fc3(x)
return x
去掉池化层,训练20轮结果:
[1 10] loss: 0.069
[1 20] loss: 0.069
[2 10] loss: 0.069
[2 20] loss: 0.069
[3 10] loss: 0.068
[3 20] loss: 0.066
[4 10] loss: 0.084
[4 20] loss: 0.070
[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.069
[10 10] loss: 0.069
[10 20] loss: 0.069
[11 10] loss: 0.069
[11 20] loss: 0.069
[12 10] loss: 0.069
[12 20] loss: 0.069
[13 10] loss: 0.069
[13 20] loss: 0.069
[14 10] loss: 0.066
[14 20] loss: 0.054
[15 10] loss: 0.033
[15 20] loss: 0.013
[16 10] loss: 0.003
[16 20] loss: 0.002
[17 10] loss: 0.001
[17 20] loss: 0.001
[18 10] loss: 0.000
[18 20] loss: 0.001
[19 10] loss: 0.000
[19 20] loss: 0.000
[20 10] loss: 0.000
[20 20] loss: 0.000
Accuracy of the network on the test images: 98.666667 %
运行速度明显变慢了,并且准确率也降低了。而且误差的收敛速度也变慢了。
搜索资料发现:(CNN中卷积层和池化的作用和理解_池化层作用-CSDN博客)
- 如果只有卷积层会怎么样?
1)过于执着局部特征学习,忽视全局
经过3层conv-layer 3X3filter后,相当于只用了1层conv-layer7X7filter来扫描原图(28-7+1=22);对原图整体状态信息学习很少;从底层细小局部简单的特征到高层复杂全局性更高的特征,推进速度太慢。
2)计算量仍然很大
- 为什么conv-layer之后需要加pooling_layer?
卷积层【局部连接和权重共享】虽然可以显著减少网络中连接的数量,但特征映射组中的神经元个数并没有显著减少。如果后面接一个分类器,分类器的输入维数依然很高,很容易过拟合。为了解决这个问题,可以再卷积层之后加上一个pooling layer,从而降低特征维数,避免过拟合。
修改“通道数”等超参数,观察变化
修改通道数为12:
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 12, 3) # in_channel , out_channel , kennel_size , stride
self.maxpool = nn.MaxPool2d(2, 2)# kernel_size,stride
self.conv2 = nn.Conv2d(12, 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):
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
实验结果:
[1 10] loss: 0.069
[1 20] loss: 0.069
[2 10] loss: 0.065
[2 20] loss: 0.060
[3 10] loss: 0.049
[3 20] loss: 0.068
[4 10] loss: 0.018
[4 20] loss: 0.009
[5 10] loss: 0.005
[5 20] loss: 0.004
[6 10] loss: 0.002
[6 20] loss: 0.001
[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.000
[10 20] loss: 0.001
Accuracy of the network on the test images: 99.666667 %
从结果可以看出通道数增加后,训练的效果并不是很好。
3.可视化
- 选择自己的最优模型
- 可视化部分卷积核和特征图
- 探索低级特征、中级特征、高级特征
此处参考了【精选】NNDL 作业6:基于CNN的XO识别-CSDN博客
查看训练好的模型的特征图
import matplotlib.pyplot as plt
# 读取模型
model = torch.load('model.pth')
print(model)
x = images[0].unsqueeze(0)
# forward正向传播过程
out_put = model(x)
weights_keys = model.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 = model.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 = model.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()
前三个图分别是高级特征,中级特征和低级特征
仔细观察特征图不难发现:
第一个特征图有九个输出,对应着第一层卷积层的输出是9
后面的特征图是五个输出,对应着第二层卷积层输出是5,一共九组,对应着第二层的输入是9,这里的9又与第一层卷积层相对应
附完整代码:
from torch.utils.data import DataLoader
from torchvision import transforms, datasets
import torch
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<8:
plt.subplot(241+a)
plt.imshow(images[index].data.squeeze().numpy(),cmap='gray')
plt.title('circle '+str(a+1))
plt.suptitle('Circle Images')
a+=1
if a==8:
break
index+=1
plt.show()
a=0
plt.figure()
index=0
for i in labels:
if i == 1 and a<8:
plt.subplot(241+a)
plt.imshow(images[index].data.squeeze().numpy(),cmap='gray')
plt.title('crosses '+str(a+1))
plt.suptitle('Cross Images')
a+=1
if a==8:
break
index+=1
plt.show()
#构建模型
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
#训练模型
model = CNN()
loss = nn.CrossEntropyLoss()
opti = torch.optim.SGD(model.parameters(), lr=0.1)
epochs = 20
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权重值
#模型测试
import matplotlib.pyplot as plt
# 读取模型
model_load = torch.load('model.pth')
# 读取一张图片 images[0],测试vc
print("labels[4] truth:\t", labels[4])
x = images[4].unsqueeze(0)
predicted = torch.max(model_load(x), 1)
print("labels[0] predict:\t", predicted.indices)
img = images[4].data.squeeze().numpy() # 将输出转换为图片的格式
plt.imshow(img, cmap='gray')
plt.show()
#计算模型准确率
# 读取模型
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))
#查看训练好的模型特征图
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 matplotlib.pyplot as plt
# 读取模型
model = torch.load('model.pth')
print(model)
x = images[0].unsqueeze(0)
# forward正向传播过程
out_put = model(x)
weights_keys = model.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 = model.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 = model.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()
总结:
1. 对于CNN中的各层之间参数的对应关系我不是很清楚,于是我在官网上搜索各个层的函数的用法并总结。
这里我对每个函数进行解析,看看参数都是什么,如何将参数联系起来:
torch.nn.Conv2d():(Conv2d — PyTorch 2.1 documentation)
我在官网上搜索了函数功能:
主要参数:
in_channels(int):输入图像中的通道数
out_channels(int):卷积产生的通道数
kernel_size(int或tuple):卷积内核的大小
torch.nn.MaxPool2d():(MaxPool2d — PyTorch 2.1 documentation)
主要参数:
kernel_size(Union[int,Tuple[int,int]])-窗口的最大值
torch.nn.Linear()(Linear — PyTorch 2.1 documentation)
更加详细的解析nn.Linear()函数参考:超级详细的torch.nn.Linear()函数讲解-CSDN博客
可以看出卷积层的第一层的对应着第二层的输入,第一层的输入通道数为1,输出通道为9,卷积核的尺寸为3*3.
2. 通过这次实验我发现老师给出的模型在训练20轮时达到最优,误差为0,准确率达到了100%。增加卷积层,去掉池化层都会降低准确率。
参考链接:
【23-24 秋学期】NNDL 作业7 基于CNN的XO识别-CSDN博客
其它参考链接均在文中给出。