目录
一、用自己的语言解释以下概念
- 局部感知、权值共享
- 池化(子采样、降采样、汇聚)。会带来那些好处和坏处?
- 全卷积网络(课上讲的这个概念不准确,同学们查资料纠正一下)
- 低级特征、中级特征、高级特征
- 多通道。N输入,M输出是如何实现的?
- 1×1的卷积核有什么作用
-
1. 局部感知、权值共享
局部感知:是指在卷积神经网络中,每个神经元不需要对整个图像的数据都进行感知,只需要对局部数据进行感知,(如图所示:感知的数据大小只有卷积核那么大)找出局部特征即可,随后在比当前层更高的层将局部特征综合起来就可以了。所谓感知,是指神经元对输入数据的处理和响应,具体来讲就是神经元通过卷积核对输入数据进行加权求和,并通过激活函数激活等一系列操作得到输出的过程。
权值共享:在卷积神经网络中,要共享的权值是卷积核中的数。卷积核在进行卷积操作时,是用相同的卷积核对整个图像的全局数据进行扫描,这就是权值共享。相对的,如果某种情况下设定卷积核都有不同的权值,就是权值独立,但是这种情况会带来很大的计算量。
-
2. 池化(子采样、降采样、汇聚)。会带来那些好处和坏处?
池化:图像进行卷积后,对输出的特征图进行约减,缩小图像(称为下采样或降采样),同时保留特征图中的主要特征。一般有平均汇聚和最大汇聚:即用采样区间的最大值或平均值来代替整个区间,以达到缩小数据量的效果。
池化的好处:
- 降低计算量:池化操作可以减小特征图的尺寸和数量,从而降低后续层的计算量,加快模型的训练速度。
- 减少过拟合:池化操作可以减少特征图的维度,降低模型的参数数量,有助于减少过拟合的风险,提高模型的泛化能力。
- 提取主要特征:最大池化可以保留最显著的特征信息,有助于提取图像中最重要的特征,提高模型的表达能力。
池化的坏处:
池化操作会丢失一部分特征信息,可能会损失一些细节信息,导致模型在某些任务上的性能下降。过多的池化操作会导致特征图的尺寸变得很小,可能会导致分辨率下降,影响模型对细节的捕捉能力。
-
3. 全卷积网络:参考 全卷积神经网络详解
核心:
- 不含全连接层的全卷积网络,可适应任意尺寸输入;
- 反卷积层增大图像尺寸,输出精细结果;
- 结合不同深度层结果的跳级结构:即下文中通过跳级结构将最后一层的预测(富有全局信息)和更浅层(富有局部信息)的预测结合起来,在遵守全局预测的同时进行局部预测,确保鲁棒性和精确性。
全卷积神经网络一般步骤:
阶段1:以经典的分类网络为初始化,最后两级为全连接(红色),参数弃去不用。
阶段2:FCN-32s 网络—从特征小图预测分割小图,之后直接升采样为大图。
阶段3:FCN-16s 网络—上采样分为两次完成。在第二次升采样前,把第4个pooling层的预测结果融合进来,使用跳级结构提升精确性。
阶段4:FCN-8s 网络—升采样分为三次完成。 进一步融合了第3个pooling层的预测结果
-
4. 低级特征、中级特征、高级特征
-
低级特征: 低级特征通常指的是由网络的底层卷积层提取的特征,这些特征包括边缘、纹理、颜色等局部的基本特征。低级特征具有较小的感受野,能够捕捉到图像中的细微细节,但缺乏全局的语义信息。
-
中级特征: 中级特征是在网络的中间层提取的特征,它们能够捕捉到更加抽象的图像特征,例如简单的形状、纹理组合等。中级特征具有较大的感受野,能够捕捉到一定程度的全局信息,但还不具备高级语义信息。
-
高级特征:高级特征能够捕捉到图像的全局语义信息, 是由网络的顶层卷积层或全连接层提取的特征,它们能够表征图像中的抽象概念和语义信息,如物体的类别、位置等。
-
5. 多通道。N输入,M输出是如何实现的?
N输入是指输入N通道的图像,经过M个卷积核,卷积操作后得到M个通道的特征图。(对于卷积核来说:一共有M个卷积核,每个卷积核都有N个通道。)
如图所示:N通道的输入图像是要和每个卷积核的通道相对应的,N通道的输入图像和N通道的卷积核进行卷积,然后将特征图上对应位置的值相加,得到单通道的特征图,然后M个卷积核的结果集合而成的就是M通道的特征图。
-
6. 1×1的卷积核有什么作用
1. 增加网络深度(增加非线性映射次数):1x1卷积核的卷积过程相当于全连接的计算过程,不改变感受野和图像大小,但是内含的非线性激活函数被逐层引入,增加网络的非线性,增强神经网络的表达能力,使得网络可以表达更复杂的特征。
卷积核越大,它生成的特征图对应的感受野就越大,随着网络深度的增加,越靠后的特征图上的节点感受野也越大。因此特征也越来越抽象。但有的时候,我们想在不增加感受野的情况下,让网络加深,只是为了引入更多的非线性。1x1卷积核正好可以解决这个问题。
2. 升维/降维
可以通过改变1x1卷积核的个数,改变该层卷积输出特征图通道的个数,以达到升维降维的效果。虽然其他的卷积核也有这个作用,但是如果只想单纯的去提升或者降低特征图的通道,选用1x1卷积核最为合适, 1x1卷积核会使用更少的权重参数数量。
3. 跨通道的信息交互
1x1卷积核只有一个参数,当它作用在多通道的特征图上时,相当于不同通道上的一个线性组合,实际上就是加起来再乘以一个系数,但是这样输出的特征图就是多个通道的整合信息了,能够使网络提取的特征更加丰富。
比如:在尺寸3x3,64通道个数的卷积核后面添加一个尺寸1x1,28通道个数的卷积核,就变成了尺寸3x3,28尺寸的卷积核。 原来的64个通道就可以理解为跨通道线性组合变成了28通道(只是通道处的线性组合),这就是通道间的信息交互。
4. 减少卷积核参数(简化模型)
在该卷积过程中,通过再增加一层1x1的卷积核,先将输入通道减少,再进行非1x1卷积核的卷积操作,大大的降低了计算量。在GoogLeNet的3a模块和ResNet网络中都使用了1x1的卷积核来减少参数数目,降低计算量。
二、使用CNN进行XO识别
1.复现参考资料中的代码
2.重新设计网络结构
- 至少增加一个卷积层,卷积层达到三层以上
- 去掉池化层,对比“有无池化”的效果
- 修改“通道数”等超参数,观察变化
3.可视化
- 选择自己的最优模型
- 可视化部分卷积核和特征图
- 探索低级特征、中级特征、高级特征
1.复现参考资料中的代码
训练模型
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, 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):
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.state_dict(), 'model_name1.pth') # 保存的是模型, 不止是w和b权重值
# 读取模型
model = torch.load('model_name1.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()
测试模型(计算模型在测试集上的准确率)
import torch
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'
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)
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')
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))
测试结果:
以上是对老师HBU_David 代码的复现。
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)
self.conv2 = nn.Conv2d(9, 5, 3) # in_channel , out_channel , kennel_size , stride
self.conv3 = nn.Conv2d(5, 3, 3)
self.relu = nn.ReLU()
self.fc1 = nn.Linear(25*25*3, 500) # full connect 1
self.fc2 = nn.Linear(500, 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 = self.relu(self.conv3(x))
x = x.view(-1, 25*25*3)
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
x = self.fc3(x)
return x
以上是增加一层卷积层,当增加再多卷积层,就会出现,损失不收敛的情况。可能对于该数据集模型过于复杂,导致过拟合或者训练困难,从而导致损失不收敛或者收敛缓慢。
也可能是在深度卷积神经网络中梯度消失。当信号通过多个卷积层传播时,梯度可能会逐渐变小,最终导致梯度消失。这是因为在反向传播过程中,梯度会通过多个层进行传递,每一层都会乘以权重矩阵的转置,导致梯度逐渐变小。导致训练过程中的梯度消失问题,使得模型难以收敛。
所以在原先模型的基础上就只增加了一个卷积层,并且调整了一些线性层的参数。
-
去掉池化层,对比“有无池化”的效果
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(112 * 112 * 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.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
对于该数据集而言,可以看出去掉池化层的训练过程中, 损失相对收敛的快一些,但是由于去掉池化后数据量增多,训练速度较慢,训练时间增长。
-
修改“通道数”等超参数,观察变化
通道数增多:
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 15, 3) # in_channel , out_channel , kennel_size , stride
self.maxpool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(15, 8, 3) # in_channel , out_channel , kennel_size , stride
self.relu = nn.ReLU()
self.fc1 = nn.Linear(27*27 * 8, 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 * 8)
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
x = self.fc3(x)
return x
通道数减少:
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 5, 3) # in_channel , out_channel , kennel_size , stride
self.maxpool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(5, 3, 3) # in_channel , out_channel , kennel_size , stride
self.relu = nn.ReLU()
self.fc1 = nn.Linear(27*27 * 3, 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 * 3)
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
x = self.fc3(x)
return x
卷积层通道数调至更少:
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 3, 3) # in_channel , out_channel , kennel_size , stride
self.maxpool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(3, 1, 3) # in_channel , out_channel , kennel_size , stride
self.relu = nn.ReLU()
self.fc1 = nn.Linear(27*27 * 1, 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 * 1)
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
x = self.fc3(x)
return x
根据训练实验结果可以看出修改通道数的参数,当增多或减少一些训练效果差不多,只有调整到第一个卷积层有三个通道,第二个有一个通道,它的收敛速度是最快的。在第五次训练就达到了0.006,最后也收敛到了0.000。
3.可视化
-
选择自己的最优模型
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 15, 3) # in_channel , out_channel , kennel_size , stride
self.maxpool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(15, 9, 3) # in_channel , out_channel , kennel_size , stride
self.conv3 = nn.Conv2d(9, 5, 3)
self.relu = nn.ReLU()
self.fc1 = nn.Linear(25*25*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 = self.relu(self.conv3(x))
x = x.view(-1, 25*25*5)
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
x = self.fc3(x)
return x
-
可视化特征图
# 看看每层的 卷积核 长相,特征图 长相
# 获取网络结构的特征矩阵并可视化
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'
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()
![]() |
![]() |
![]() |
查看训练好的模型的卷积核
# 看看每层的 卷积核 长相,特征图 长相
# 获取网络结构的特征矩阵并可视化
import torch
import matplotlib.pyplot as plt
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'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)
# 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()
运行结果:
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
-
探索低级特征、中级特征、高级特征
# 输出第一个卷积层的特征图
conv1_feature = model.conv1(images)
plt.figure()
for i in range(9):
ax = plt.subplot(3, 3, i + 1)
plt.imshow(conv1_feature[0, i, :, :].detach().numpy(), cmap='gray')
plt.show()
# 输出第二个卷积层的特征图
conv2_feature = model.conv2(model.maxpool(model.relu(model.conv1(images))))
plt.figure()
for i in range(5):
ax = plt.subplot(1, 5, i + 1)
plt.imshow(conv2_feature[0, i, :, :].detach().numpy(), cmap='gray')
plt.show()
# 输出全连接层的特征图
x =model.maxpool( model.conv2(model.maxpool(model.relu(model.conv1(images)))))
x = x.view(-1, 27 * 27 * 5)
fc_feature = model.fc3(model.relu(model.fc2(model.relu(model.fc1(x))))).detach().numpy()
plt.figure()
plt.bar(range(2), fc_feature[0])
plt.xticks(range(2), ['class 0', 'class 1'])
plt.show()
![]() |
![]() |
线性层的输出:那个种类输出的概率大选哪个。
![]() | ![]() | ![]() |
对于这个实验来说,随着层数的增加,低级特征,中级特征,高级特征的特征图表现变化不是很明显,可以说对于整个图像来说,直接跳到了高级特征,可能是图片相对简单。但是图中物体的轮廓相对逐层增加了,在线性层后可以看出输出的就是分类结果了。
计算卷积、池化层后的特征图尺寸:
import torch
import torch.nn as nn
# 定义卷积层和池化层
conv1 = nn.Conv2d(1,9, 3)
maxpool = nn.MaxPool2d(2, 2)
conv2 = nn.Conv2d(9, 5, 3)
conv3 = nn.Conv2d(5, 3, 3)
# 假设输入图像尺寸为 64x64
W_in, H_in = 116, 116
# 计算特征图尺寸
x = torch.rand(1, 1, W_in, H_in) # 构造一个输入张量
# x = maxpool(nn.ReLU()(conv1(x)))
# x = maxpool(nn.ReLU()(conv2(x)))
x = nn.ReLU()(conv1(x))
x = nn.ReLU()(conv2(x))
x = nn.ReLU()(conv3(x))
output_size = x.size()[2:] # 获取特征图的尺寸
print("Output feature map size:", output_size)
画出训练过程损失和准确率变化的图像:
import matplotlib.pyplot as plt
# 可视化模型的训练过程
losses = [] # 用于存储每个epoch的损失值
accuracies = [] # 用于存储每个epoch的准确率
epochs = 10
for epoch in range(epochs):
running_loss = 0.0
correct = 0
total = 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()
_, predicted = torch.max(out, 1)
total += label.size(0)
correct += (predicted == label).sum().item()
epoch_loss = running_loss / len(data_loader)
accuracy = correct / total
losses.append(epoch_loss)
accuracies.append(accuracy)
print(f'Epoch [{epoch + 1}/{epochs}], Loss: {epoch_loss:.4f}, Accuracy: {accuracy * 100:.2f}%')
# 绘制损失值和准确率随着epoch变化的曲线
plt.figure()
plt.plot(range(1, epochs + 1), losses, label='Training Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.title('Training Loss over epochs')
plt.show()
plt.figure()
plt.plot(range(1, epochs + 1), accuracies, label='Training Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.title('Training Accuracy over epochs')
plt.show()
![]() | ![]() |
总结:
在这个作业中,主要就是对卷积神经网络的相关概念进行总结,然后进行了应用,利用CNN进行XO识别,在XO识别的过程中:
- 首先就是要对数据进行处理,导入数据,划分训练集和测试集。
- 然后构建模型,调用函数定义卷积层和池化层,并且调好参数,然后前向传播。
- 定义损失函数和优化器。
- 进行训练,加上反向传播。
要实现这些过程首要的就是了解卷积和池化层函数参数的调用。注意要会查看torch官网的解释最权威:torch函数官网
这次主要使用的这两个:
- torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
- torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
这次实验主要就是了解卷积神经网络的过程,并且熟悉这两个函数,会调参,并解决出现的各种问题。