DL基础补全计划(六)---卷积和池化
PS:要转载请注明出处,本人版权所有。
PS: 这个只是基于《我自己》的理解,
如果和你的原则及想法相冲突,请谅解,勿喷。
前置说明
本文作为本人csdn blog的主站的备份。(BlogID=110)
环境说明
- Windows 10
- VSCode
- Python 3.8.10
- Pytorch 1.8.1
- Cuda 10.2
前言
本文是此基础补全计划的最终篇,因为从我的角度来说,如果前面这些基础知识都能够了解及理解,再加上本文的这篇基础知识,那么我们算是小半只脚踏入了大门。从这个时候,其实我们就已经可以做图像上的基本的分类任务了。除了分类任务,我们还有两类重要的图像任务是目标检测和图像分割,这两项任务都和分类任务有一定的关联,可以说,分类可以说是这两类的基础。
卷积神经网络是一个专门为处理图像数据的网络。下面我们简单的来看看卷积、池化的含义和怎么计算的,然后我们通过一个LeNet5的经典网络,训练一个分类模型。
卷积
卷积是一种运算,类似加减乘除。卷积是一种运算,类似加减乘除。卷积是一种运算,类似加减乘除。重要的事情说三次。
在数学上的定义是:连续n的情况\((f*g)(x) = \int f(n)g(x-n)dn\), 离散n的情况\((f*g)(x) = \sum\limits_{n} f(n)g(x-n)\)。从这里我们可以看到,卷积就是测量函数f和函数g的翻转且平移x后的重叠。其二维离散a,b的表达是\((f*g)(x1,x2) = \sum\limits_{a}\sum\limits_{b} f(a, b)g(x1-a, x2-b)\)
卷积是一种运算,类似加减乘除。卷积是一种运算,类似加减乘除。卷积是一种运算,类似加减乘除。重要的事情再说三次。
我们再次想一想,在之前的文章中,我们普遍都建立了一种想法是,把输入数据拉成一条直线输入的,这就意味着我们在之前的任务里面只建立了相邻输入数据之间的左右关联。但是我们可以想一想,是不是所有的数据只建立左右关联就行了呢?显而易见的,并不是这样的,比如我们图片,可能上下左右4个像素加起来才是一个猫,如果我们只关联了左右,那么它可能是狗或者猫。那么我们应该通过什么样的方式来对图片像素的这种二维关联性进行描述或者计算呢?这种方法就是卷积运算。
卷积网上有许许多多的介绍,大家都做了许多详细的解答,包含信号分析、复利、概率以及图像滤波等等方面的解释。我个人认为我们可以抛开这些方面,从数据之间的关联性来看这个问题可能是最好理解的,因为我们之前只关注了数据之间左右关联,我们应该同时关注上下左右的关联才对,我们要从空间的角度来考虑数据之间的关联性。而卷积作为一种数学运算,他恰好是计算了数据的上下左右关联性,因此卷积这种数学运算很适合拿来代替之前的一条线的线性运算。
下面我们来看一下一个基本的卷积计算过程是什么样子的。
图像边缘检测实例
计算代码如下:
def corr2d(X, K): #@save
"""计算⼆维互相关运算。"""
h, w = K.shape
Y = np.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (X[i:i + h, j:j + w] * K).sum()
return Y
_X = np.ones((6, 8))
_X[0:2, 2:6] = 0
_X[3:, 2:6] = 0
print(_X)
_K = np.array([[1.0, -1.0]])
_Y = corr2d(_X, _K)
print(_Y)
_Y = corr2d(_X, _K.T)
print(_Y)
结果如图:
我们可以分别的看到,图像边缘的数值在经过我们手动构造的滤波器后,成功的检测到边缘信息。
在实际情况中,我们可能要学习边缘,角点等等特征,这个时候我们不可能手动去构造我们的滤波器,那么我们可不可以通过学习的方式把滤波器学习出来呢?下面通过实例来演示:
_X = np.ones((6, 8))
_X[0:2, 2:6] = 0
_X[3:, 2:6] = 0
print(_X)
_K = np.array([[1.0, -1.0]])
_Y = corr2d(_X, _K)
print(_Y)
# _Y = corr2d(_X, _K.T)
# print(_Y)
X = torch.from_numpy(_X)
X.requires_grad = True
X = X.to(dtype=torch.float32)
X = X.reshape(1, 1, 6, 8)
Y = torch.from_numpy(_Y)
Y.requires_grad = True
conv2d = torch.nn.Conv2d(1, 1, (1, 2), bias=False)
for i in range(20):
y_train = conv2d(X)
l = (y_train - Y)**2
conv2d.zero_grad()
# print(l.shape)
l.backward(torch.ones_like(l))
# print(conv2d.weight)
with torch.no_grad():
# print('grad = ', conv2d.weight.grad)
conv2d.weight[:] -= 0.02 * conv2d.weight.grad
# print(conv2d.weight)
# print(conv2d.weight.shape)
if (i + 1) % 2 == 0:
print(f'batch {i+1}, loss {float(l.sum()):.3f}')
print(conv2d.weight)
结果如图:
我们通过corr2d函数构造出特征Y,然后我们通过训练特征Y,我们可以看到最终卷积层的权重就是接近与1和-1,恰好等于我们构造的特殊滤波器。
这个实例说明了,我们可以通过学习的方式来学习出一个我们想要的滤波器,不需要手动构造。
此外卷积还有卷积核、步长、填充等等资料,我就不造轮子了,网上有很多大佬写的很好的,大家去看看。此外这里有个公式非常有用:N=(W-K+2P)/S+1。
池化
我们在上文知道了卷积的输出结果代表了一片上下左右数据的关联性,比如一个像素和之前的9个像素有关联,比如一个\(9*9\)的图,经过一个卷积后,假如还是\(9*9\),这个时候输出的\(9*9\)里面的每个像素我们已经和之前对应位置的一片像素建立了关联。但是某些时候,我们希望这种关联性聚合起来,通过求最大值或者平均等等,这就是池化的概念。以之前例子为例:卷积输出了\(9*9\)的像素,经过池化之后,假如变成了\(3*3\),我们可以看到池化输出的每个像素代表之前卷积输出的\(3*3\)个像素,这代表我们的信息聚集了,因为一个像素代表了上一层的多个像素。
注意池化,我们还可以从视野的角度来看待,还是和上面的例子一样,假如原图上的猫是\(9*9\)的像素,经过卷积池化之后,假如变成了\(3*3\), 这意味着我们从像素的角度来说,之前81个像素代表猫,现在9个像素就可以代表了,也就是之前的一个像素和现在的一个像素代表的原图视野不一样了,形成了视野放大的感觉。但是有一个缺点就是,这可能导致小目标丢失了,这个在目标检测里面会关注到。
一个经典神经网络LeNet5
在2017年12月份,我的这篇文章中《LeNet-5 论文及原理分析(笨鸟角度)》 ( https://blog.csdn.net/u011728480/article/details/78799672 )其实当时我为了学习一些基本知识,也对LeNet5的论文中网络结构部分做了细致的分析。
注意本文中的C3层和论文中的C3层不一样。本文的C3层是\(16*6*(5*5+1) = 2496\)个参数。论文原文是\(6*(3*5*5+1)+6*(4*5*5+1)+3*(4*5*5+1)+1* (6*5*5+1)=1516\)个参数。
训练代码如下:
import numpy as np
from numpy.lib.utils import lookfor
import torch
from torchvision.transforms import ToTensor
import os
import torch
from torch import nn
from torch.nn.modules import activation
from torch.nn.modules import linear
from torch.nn.modules.linear import Linear
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import visdom
vis = visdom.Visdom(env='main')
title = 'LeNet5 on ' + 'FashionMNIST'
legend = ['TrainLoss', 'TestLoss', 'TestAcc']
epoch_plot_window = vis.line(
X=torch.zeros((1, 3)).cpu(),
Y=torch.zeros((1, 3)).cpu(),
win='epoch_win',
opts=dict(
xlabel='Epoch',
ylabel='Loss/Acc',
title=title,
legend=legend
))
def corr2d(X, W): #@save
"""计算⼆维互相关运算。"""
h, w = W.shape
Y = np.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (X[i:i + h, j:j + w] * W).sum()
return Y
def TrainConv2d():
_X = np.ones((6, 8))
_X[0:2, 2:6] = 0
_X[3:, 2:6] = 0
print(_X)
_K = np.array([[1.0, -1.0]])
_Y = corr2d(_X, _K)
print(_Y)
# _Y = corr2d(_X, _K.T)
# print(_Y)
X = torch.from_numpy(_X)
X.requires_grad = True
X = X.to(dtype=torch.float32)
X = X.reshape(1, 1, 6, 8)
Y = torch.from_numpy(_Y)
Y.requires_grad = True
conv2d = torch.nn.Conv2d(1, 1, (1, 2), bias=False)
for i in range(20):
y_train = conv2d(X)
l = (y_train - Y)**2
conv2d.zero_grad()
# print(l.shape)
l.backward(torch.ones_like(l))
# print(conv2d.weight)
with torch.no_grad():
# print('grad = ', conv2d.weight.grad)
conv2d.weight[:] -= 0.02 * conv2d.weight.grad
# print(conv2d.weight)
# print(conv2d.weight.shape)
if (i + 1) % 2 == 0:
print(f'batch {i+1}, loss {float(l.sum()):.3f}')
print(conv2d.weight)
class NeuralNetwork(nn.Module):
def __init__(self):
super(NeuralNetwork, self).__init__()
self.lenet5 = nn.Sequential(
# 6*28*28---->6*28*28
nn.Conv2d(1, 6, (5, 5), stride=1, padding=2),
nn.Sigmoid(),
# 6*28*28----->6*14*14
nn.AvgPool2d((2, 2), stride=2, padding=0),
# 6*14*14----->16*10*10
nn.Conv2d(6, 16, (5, 5), stride=1),
nn.Sigmoid(),
# 16*10*10------>16*5*5
nn.AvgPool2d((2, 2), stride=2, padding=0),
nn.Flatten(),
nn.Linear(16*5*5, 1*120),
nn.Sigmoid(),
nn.Linear(1*120, 1*84),
nn.Sigmoid(),
nn.Linear(1*84, 1*10)
)
def forward(self, x):
logits = self.lenet5(x)
return logits
def LoadFashionMNISTByTorchApi():
# 60000*28*28
training_data = datasets.FashionMNIST(
root="..\data",
train=True,
download=True,
transform=ToTensor()
)
# 10000*28*28
test_data = datasets.FashionMNIST(
root="..\data",
train=False,
download=True,
transform=ToTensor()
)
# labels_map = {
# 0: "T-Shirt",
# 1: "Trouser",
# 2: "Pullover",
# 3: "Dress",
# 4: "Coat",
# 5: "Sandal",
# 6: "Shirt",
# 7: "Sneaker",
# 8: "Bag",
# 9: "Ankle Boot",
# }
# figure = plt.figure(figsize=(8, 8))
# cols, rows = 3, 3
# for i in range(1, cols * rows + 1):
# sample_idx = torch.randint(len(training_data), size=(1,)).item()
# img, label = training_data[sample_idx]
# figure.add_subplot(rows, cols, i)
# plt.title(labels_map[label])
# plt.axis("off")
# plt.imshow(img.squeeze(), cmap="gray")
# plt.show()
return training_data, test_data
def train_loop(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
num_batches = len(dataloader)
loss_sum = 0
for batch, (X, y) in enumerate(dataloader):
# move X, y to gpu
if torch.cuda.is_available():
X = X.to('cuda')
y = y.to('cuda')
# Compute prediction and loss
pred = model(X)
loss = loss_fn(pred, y)
# Backpropagation
optimizer.zero_grad()
loss.backward()
optimizer.step()
loss_sum += loss.item()
if batch % 100 == 0:
loss1, current = loss.item(), batch * len(X)
print(f"loss: {loss1:>7f} [{current:>5d}/{size:>5d}]")
return loss_sum/num_batches
def test_loop(dataloader, model, loss_fn):
size = len(dataloader.dataset)
num_batches = len(dataloader)
test_loss, correct = 0, 0
with torch.no_grad():
for X, y in dataloader:
# move X, y to gpu
if torch.cuda.is_available():
X = X.to('cuda')
y = y.to('cuda')
pred = model(X)
test_loss += loss_fn(pred, y).item()
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
test_loss /= num_batches
correct /= size
print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
return test_loss, correct
if __name__ == '__main__':
# TrainConv2d()
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print('Using {} device'.format(device))
def init_weights(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
nn.init.xavier_uniform_(m.weight)
model = NeuralNetwork()
model.apply(init_weights)
model = model.to(device)
print(model)
batch_size = 200
learning_rate = 0.9
training_data, test_data = LoadFashionMNISTByTorchApi()
train_dataloader = DataLoader(training_data, batch_size, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size, shuffle=True)
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
epochs = 1000
model.train()
for t in range(epochs):
print(f"Epoch {t+1}\n-------------------------------")
train_loss = train_loop(train_dataloader, model, loss_fn, optimizer)
test_loss, test_acc = test_loop(test_dataloader, model, loss_fn)
vis.line(np.array([train_loss, test_loss, test_acc]).reshape(1, 3),
np.ones((1, 3))*t,
win='epoch_win',
update=None if t == 0 else 'append',
opts=dict(
xlabel='Epoch',
ylabel='Loss/Acc',
title=title,
legend=legend
)
)
print("Done!")
# only save param
torch.save(model.state_dict(), 'lenet5.pth')
# save param and net
torch.save(model, 'lenet5-all.pth')
# export onnx
input_image = torch.zeros((1,1,28,28))
input_image = input_image.to(device)
torch.onnx.export(model, input_image, 'model.onnx')
结果如图:
我们从训练可视化界面上可以看到,我们的模型确实是收敛了,但是不幸的是准确率大概有90%左右,而且存在过拟合现象。注意这里我们这个模型,由于有Sigmoid层,导致了很容易出现梯度消失的情况,为了加快训练,所以学习率设置的很大。
后记
整理本系列的基础知识的原因是需要加深对深度学习的理解。同时跟着参考资料,重复试验,重复运行。对我个人而言,只有真实的写了代码之后,才能够理解的更加透彻。
本文也是此系列的终篇,以后更新随缘。
参考文献
- https://github.com/d2l-ai/d2l-zh/releases (V1.0.0)
- https://github.com/d2l-ai/d2l-zh/releases (V2.0.0 alpha1)
- https://blog.csdn.net/u011728480/article/details/78799672 (《LeNet-5 论文及原理分析(笨鸟角度)》)
打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)
PS: 请尊重原创,不喜勿喷。
PS: 要转载请注明出处,本人版权所有。
PS: 有问题请留言,看到后我会第一时间回复。
DL基础补全计划(六)---卷积和池化的更多相关文章
- DL基础补全计划(二)---Softmax回归及示例(Pytorch,交叉熵损失)
PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明 本文作为本人csdn blog的主站的备份.(Bl ...
- DL基础补全计划(三)---模型选择、欠拟合、过拟合
PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明 本文作为本人csdn blog的主站的备份.(Bl ...
- DL基础补全计划(一)---线性回归及示例(Pytorch,平方损失)
PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明 本文作为本人csdn blog的主站的备份.(Bl ...
- DL基础补全计划(五)---数值稳定性及参数初始化(梯度消失、梯度爆炸)
PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明 本文作为本人csdn blog的主站的备份.(Bl ...
- OSPF补全计划-0 preface
哇靠,一看日历吓了我一跳,我这一个月都没写任何东西,好吧,事情的确多了点儿,同事离职,我需要处理很多untechnical的东西,弄得我很烦,中间学的一点小东西(关于Linux的)也没往这里记,但是我 ...
- CNN中卷积层 池化层反向传播
参考:https://blog.csdn.net/kyang624823/article/details/78633897 卷积层 池化层反向传播: 1,CNN的前向传播 a)对于卷积层,卷积核与输入 ...
- tensorflow的卷积和池化层(二):记实践之cifar10
在tensorflow中的卷积和池化层(一)和各种卷积类型Convolution这两篇博客中,主要讲解了卷积神经网络的核心层,同时也结合当下流行的Caffe和tf框架做了介绍,本篇博客将接着tenso ...
- 卷积和池化的区别、图像的上采样(upsampling)与下采样(subsampled)
1.卷积 当从一个大尺寸图像中随机选取一小块,比如说 8x8 作为样本,并且从这个小块样本中学习到了一些特征,这时我们可以把从这个 8x8 样本中学习到的特征作为探测器,应用到这个图像的任意地方中去. ...
- 【DeepLearning】基本概念:卷积、池化、Backpropagation
终于有了2个月的空闲时间,给自己消化沉淀,希望别有太多的杂事打扰.在很多课程中,我都学过卷积.池化.dropout等基本内容,但目前在脑海中还都是零散的概念,缺乏整体性框架,本系列博客就希望进行一定的 ...
随机推荐
- 通过修改Host实现chrome同步
问题原因 : 中国的GW屏蔽了google服务,导致无法同步账号信息,这是一个很好的功能.可以同步书签,插件等! 跟chrome的版本号无关,设置hosts文件就能够正常运行(文件位置 : C:\Wi ...
- Ant Design Blazor 组件库的路由复用多标签页介绍
最近,在 Ant Design Blazor 组件库中实现多标签页组件的呼声日益高涨.于是,我利用周末时间,结合 Blazor 内置路由组件实现了基于 Tabs 组件的 ReuseTabs 组件. 前 ...
- PHP大文件分片上传的实现方法
一.前言 在网站开发中,经常会有上传文件的需求,有的文件size太大直接上传,经常会导致上传过程中耗时太久,大量占用带宽资源,因此有了分片上传. 分片上传主要是前端将一个较大的文件分成等分的几片,标识 ...
- WPF教程九:理解WPF中的对象资源
在WPF中,所有继承自FrameworkElement的元素都包含一个Resources属性,这个属性就是我们这篇要讲的资源. 这一篇讲解的资源是不是上一篇的程序集资源(那个是在编译过程中打包到程序集 ...
- Dapper的基本使用 [转]
Dapper是.NET下一个micro的ORM,它和Entity Framework或Nhibnate不同,属于轻量级的,并且是半自动的.也就是说实体类都要自己写.它没有复杂的配置文件,一个单文件就可 ...
- kong的管理UI选择-konga
目录 npm方式安装 1. 准备依赖环境 2. 安装konga 3. 配置 4. 环境变量(more) 5. 数据库 配置 初始化/迁移 6. 运行 Docker方式安装 关于Kong-Dashboa ...
- [zebra源码]分片语句ShardPreparedStatement执行过程
主要过程包括: 分库分表的路由定位 sql语句的 ast 抽象语法树的解析 通过自定义 SQLASTVisitor (MySQLSelectASTVisitor) 遍历sql ast,解析出逻辑表名 ...
- VMware中的虚机如何挂载U盘
1.将U盘插入到宿主机上. 2.在VM Client上,点击宿主机,右键,扫描存储设备(目的是为了发现新USB存储) 3.在需要的虚拟机上编辑配置,添加硬件,添加USB设备(如果不进行以上2个步骤,此 ...
- webpack 快速入门 系列 —— 性能
其他章节请看: webpack 快速入门 系列 性能 本篇主要介绍 webpack 中的一些常用性能,包括热模块替换.source map.oneOf.缓存.tree shaking.代码分割.懒加载 ...
- 前端-js基础
HTML三把利剑之一,浏览器具有解析js的能力 一.js基础 在HTML中可以将JavaScript/JS的代码写在head中,被script标签所包裹,当浏览器解释HTML时,遇到style标签时, ...