一、用自己的语言解释以下概念
- 局部感知、权值共享
- 池化(子采样、降采样、汇聚)。会带来那些好处和坏处?
- 全卷积网络(课上讲的这个概念不准确,同学们查资料纠正一下)
- 低级特征、中级特征、高级特征
- 多通道。N输入,M输出是如何实现的?
- 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.减少参数,在特征图的通道数上进行卷积,压缩特征图,二次提取特征,使得新特征图的特征表达更佳。
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博客