首先来解决一下上一篇文章遗留的问题NNDL 作业6:
Conv2d:
在上一篇文章中,自己调的函数咋也画不出来自己想要的结果,原来是自己Conv2d的参数搞错了,正确的先写出来:
一、用自己的语言解释以下概念
1、局部感知、权值共享
局部感知也可以叫局部连接,卷积层中的每个神经元都只和前一层中某个局部窗口内的神经元相连,如下图,卷积层(右)里每个神经元不用像全连接层(左)似的和前一层的神经元全部连接。对局部进行感知,不要怕,综合起来就是整体信息了。
权值共享:
当对图像提取某个特征时,使用同一卷积核,同一卷积核对应同一权重。所以作为参数的卷积核w对于第l层的所有的神经元都是相同的
2、池化(子采样、降采样、汇聚)。会带来那些好处和坏处?
池化:对特征图进行约减,实现下采样,同时保留特征图中主要信息。就是抽取形成缩略图,还能保留主要信息。也可以叫做子采样、降采样、汇聚。
好处:降低特征数量,从而减少参数数量又能保留保留特征图中主要信息。还可以使得网络对一些小的局部形态改变保持不变性, 并拥有更大的感受野。
坏处:过大的采样区域会急剧减少神经元的数量,也会造成过多的信息损失
3、全卷积网络(课上讲的这个概念不准确,同学们查资料纠正一下)
全卷积网络:网络中原先的全连接层也由卷积层作为替代。注意不是池化层,全卷积网络中也有池化层。由下图可以看到:
最后的全连接层换成了全卷积层。将最后一个全卷积层得到的特征图反卷积,然后拼接,反卷积,拼接……直至成原图尺寸
全卷积网络(FCN)是从抽象的特征中恢复出每个像素所属的类别。即从图像级别的分类进一步延伸到像素级别的分类。
当年FCN的出现,是语义分割的里程碑式的跨越。通俗来讲,语义分割是把一张图片的每种不同物体逐个用不同颜色标注。
4、低级特征、中级特征、高级特征(具体原因已经放在了上一篇博客里了上一篇)
5、多通道。N输入,M输出是如何实现的?
通过卷积核来实现,N输入,输入图象是N个通道,然后经过M组卷积核,每组卷积核的厚度是N,最后输出即为M输出。具体内容在开篇一列举,这一问题正好是上一篇遗留的问题。
6、1×1的卷积核有什么作用
(1)、增加网络深度(增加非线性映射次数)1x1卷积核,可以在保持feature map尺度不变的(即不损失分辨率)的前提下大幅增加非线性特性,通过加入非线性激活函数,可以增加网络的非线性,使得网络可以表达更复杂的特征
(2)、1x1卷积核能够使用更少的权重参数数量,单纯的去提升或者降低特征图的通道。
(3) 跨通道的信息交互
(4)减少卷积核参数,因为在这个过程中能够使特征图的通道数减少。
终于等到你~我就想看看中间过程是啥样的,咋优化的。
二、使用CNN进行XO识别
1.复现参考资料中的代码
1)数据集准备
从qq群里下载老师提供的数据集,首先把那些图片分为两部分分别放到测试集和训练集里,圆和叉各选150个放进测试集,剩下的放进训练集里。
这样就可以了。
接下来把它转换为用于深度学习的数据格式
from torch.utils.data import DataLoader
from torchvision import transforms, datasets
#可视化数据集
import matplotlib.pyplot as plt
#建立数据集
transforms = transforms.Compose([#创建一个转换的组合,为了将其转化为可用于深度学习的数据格式
transforms.ToTensor(), # 把图片进行归一化,并把数据转换成Tensor类型
transforms.Grayscale(1) # 把图片 转为灰度图,参数1表示转换过程中保持原图的大小不变。
])
data_train = datasets.ImageFolder('training_data', transforms)
data_test = datasets.ImageFolder('test_data', transforms)
#使用ImageFolder函数来创建一个数据集,该数据集从'training_data'文件夹中读取图像,并且应用前面定义的转换。ImageFolder函数会根据图像的文件名自动分类。
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
输出了第一批的形状:
#可视化前五个标签是0的(圆)
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()
#可视化前五个标签是1的(叉)
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()
首先感受一下展平
上网络:
import torch.nn as nn
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.conv1 = nn.Conv2d(1, 9, 3)
self.maxpool = nn.MaxPool2d(2, 2)#2x2的最大池化层
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
针对模型的理解:注意nn.Conv2d和nn.Linear这些个函数的输入可以是批量数据。输入一张图片时得unsqueeze(0)。
看到这些代码开始有点懵,不是批量吗,怎么在定义网络里看不出来批量呢,后来发现在实例化这些nn.Conv2d和nn.Linear时,输入的参数(批量大小*特征数量)包含着批量大小呢,输入进去了,他自会求批量。一个是实例化输入的参数,一个是定义网络的参数,注意区分。
模型训练:
#模型训练
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()#损失函数对模型参数的偏导数。这些梯度会存储在每个参数的.grad属性中。
#从输出节点开始,依次向输入节点回溯,对于每一个节点,它会计算这个节点的梯度(对于损失的偏导数),并将结果存储在每个参数的.grad属性中。
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权重值
torch.save(model.state_dict(), 'model_name1.pth') # 保存的是w和b权重值
这个代码一定要强调一下!
torch.save(model.state_dict(), 'model_name1.pth') # 保存的是w和b权重值得加上,方便之后显示特征图的时候用(因为我没加,报错不会改,看了同学的博客才明白了)
模型测试:
#模型测试
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)#第二个参数1指定在哪个维度上查找最大值,获取张量中每个元素的最大值,并返回一个包含最大值的张量和对应的索引。
print("labels[0] predict:\t", predicted.indices)
img = images[0].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
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'
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 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组卷积核。
第二组一共9个大图,5个小图,9对应着卷积核的厚度,5对应着卷积核的组数。
2.重新设计网络结构
至少增加一个卷积层,卷积层达到三层以上(下次一定用画图软件画~)
class CNN1(nn.Module):
def __init__(self):
super(CNN1, self).__init__()
self.conv1 = nn.Conv2d(1, 9, 3)
self.maxpool = nn.MaxPool2d(2, 2)#2x2的最大池化层
self.conv2 = nn.Conv2d(9, 5, 3)
self.conv3 = nn.Conv2d(5, 5, 3)
self.relu = nn.ReLU()
self.fc1 = nn.Linear(12 * 12 * 5, 480)
self.fc2 = nn.Linear(480, 320)
self.fc3 = nn.Linear(320, 2)
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
试了两次
第一次:Accuracy of the network on the test images: 54.333333 %
第二次:Accuracy of the network on the test images: 50.000000 %
显然效果不好。
根据之前前馈神经网络的经验,增加轮数可能会好一些,改成了20试一下:
Accuracy of the network on the test images: 99.666667 %
好很多,但是也加重了过拟合的风险
去掉池化层,对比“有无池化”的效果
class CNN2(nn.Module):
def __init__(self):
super(CNN2, 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
能够明显感受到,最后训练效果和之前差不多,但他的速度很慢!可以感受到池化真的很有必要,对特征图进行约减,实现下采样,同时保留特征图中主要信息。
修改“通道数”等超参数,观察变化
class CNN3(nn.Module):
def __init__(self):
super(CNN3, self).__init__()
self.conv1 = nn.Conv2d(1, 18, 3)
self.maxpool = nn.MaxPool2d(2, 2)#2x2的最大池化层
self.conv2 = nn.Conv2d(18, 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
将通道数从9改成了18,看不出来啥区别,理论上应该更慢一些,但是不明显。
3.可视化
选择自己的最优模型
可视化部分卷积核和特征图
探索低级特征、中级特征、高级特征
为了探索低级特征、中级特征、高级特征 ,使用的是三个卷积层的,为了方便,我使用了之前定义的那个,还是有些瑕疵的,他的训练效果不太好,这个留个伏笔,希望之后抓时间去找最优模型。
# 看看每层的 卷积核 长相,特征图 长相
# 获取网络结构的特征矩阵并可视化
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'
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)
self.maxpool = nn.MaxPool2d(2, 2) # 2x2的最大池化层
self.conv2 = nn.Conv2d(9, 5, 3)
self.conv3 = nn.Conv2d(5, 5, 3)
self.relu = nn.ReLU()
self.fc1 = nn.Linear(12 * 12 * 5, 480)
self.fc2 = nn.Linear(480, 320)
self.fc3 = nn.Linear(320, 2)
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 = self.maxpool(self.relu(self.conv3(x)))
outputs.append(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 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(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()
可视化特征图:
可能是这个模型选的不好,所以高级特征显示的不是很明显。
收获:
定义nn.Conv2d的参数:输入通道数,输出通道数,卷积核的大小
nn.Conv2d输入参数:通常是一个四维张量,形状为(batch_size, channels, height, width)
,表示批量大小、通道数、高度和宽度。
weight:表示卷积层的卷积核参数,通常也是一个四维张量(out_channels, in_channels/groups, kernel_height, kernel_width),其中out_channels表示卷积后的通道数,in_channels/groups表示输入数据的通道数或者分组数量,kernel_height和kernel_width表示卷积核的高度和宽度。
定义nn.Linear的参数:输入特征的个数,输出特征的个数
nn.Linear输入参数:输入的是一个二维张量,其中每个维度分别代表批量大小和特征数量。
心得体会:写博客真的会督促自己在回顾补充上课的知识。
注意:还没找到最优模型。
参考:
https://www.coonote.com/note/fcn.html
【2021-2022 春学期】人工智能-作业6:CNN实现XO识别_x = self.conv2(x)#请问经过conv2(x)之后,x的维度是多少-CSDN博客
【精选】NNDL 作业6:基于CNN的XO识别-CSDN博客
https://blog.csdn.net/zhenjiteng/article/details/127465666?spm=1001.2014.3001.5502