机器学习从入门到放弃:卷积神经网络CNN(二)
一、前言
通过上一篇文章,我们大概了解了卷积是什么,并且分析了为什么卷积能在图像识别上起到巨大的作用。接下来,废话不多话,我们自己尝试动手搭建一个简易的CNN网络。
二、准备工作
在开始的时候,我们首先概括一下卷积所需要进行的工作:
- 定义一个卷积核:卷积核是一个小的矩阵(例如3x3或5x5),包含一些数字。这个卷积核的作用是在图像中识别特定类型的特征,例如边缘、线条等,也可能是难以描述的抽象特征。
- 卷积核滑过图像:卷积操作开始时,卷积核会被放置在图像的左上角。然后,它会按照一定的步长(stride)在图像上滑动,可以是从左到右,也可以是从上到下。步长定义了卷积核每次移动的距离。
- 计算点积:在卷积核每个位置,都会计算卷积核和图像对应部分的点积。这就是将卷积核中的每个元素与图像中对应位置的像素值相乘,然后将所有乘积相加。
- 生成新的特征图:每次计算的点积结果被用来构建一个新的图像,也称为特征图或卷积图。
- 重复以上过程:通常在一个 CNN 中,我们会有多个不同的卷积核同时进行卷积操作。这意味着我们会得到多个特征图,每个特征图捕捉了原始图像中的不同特征。
上图中间的矩阵就是所谓的卷积核,又称为滤波器。这个滤波器可以帮助我们观测到一定区域大小的像素信息,并且通过卷积计算,变成”低频“的信息特征,比如上面我们提到的一些图像的边缘,纹理等等。当权重系数(卷积核)的参数改变时,它可以提取的特征类型也会改变。所以训练卷积神经网络时,实质上训练的是卷积核的参数。如下图所示,是一次卷积计算的过程:
一个内核从图像的左上角开始滑动,将核所覆盖的像素值与相应的核值相乘,并对乘积求和。结果被放置在新图像中与核的中心相对应的点处。上面的235其实就是计算所得出的灰度值,当一个内核走完整个图片之后,得出的结果大概是这样子:
三、CNN架构
当只有一层CNN结构时,一般都会有如下几个层级,来帮助我们进行一次卷积,训练对应的特征。
- 输入层(Input Layer):
输入层负责接收原始数据,例如图像。每个节点对应输入数据的一个特征。
- 卷积层(Convolutional Layer):
卷积层是CNN的核心。它通过应用卷积操作来提取图像中的特征。每个卷积层包含多个卷积核(也称为滤波器),每个卷积核负责检测输入中的不同特征。卷积操作通过滑动卷积核在输入上进行计算,并生成特征图。
- 激活函数层(Activation Layer):
在卷积层之后,一般会添加激活函数,例如ReLU(Rectified Linear Unit),用于引入非线性性。这有助于模型学习更复杂的模式和特征。
- 池化层(Pooling Layer):
池化层用于减小特征图的空间维度,降低计算复杂度,并减少过拟合风险。常见的池化操作包括最大池化和平均池化。
- 全连接层(Fully Connected Layer):
全连接层将前面层的所有节点与当前层的所有节点连接。这一层通常用于整合前面层提取的特征,并生成最终的输出。在分类问题中,全连接层通常输出类别的概率分布。
- 输出层(Output Layer):
输出层给出网络的最终输出,例如分类的概率分布。通常使用softmax函数来生成概率分布。
输入层就不用解释了,毕竟在全连接网络中我们已经对它有了一定的了解,我们首先看看在卷积层中,具体是怎么实现的。在我们接下来要做的 MNIST 手写数字识别的数据集中,我们用其中的图片来举例,例如 8 这个图片:
在卷积层中左边黑色为原图,中间为卷积层,右边为卷积后的输出。我们可以注意到,当我们是不用的卷积核时,所对应的结果是不同的。
上面第一种卷积核所有元素值相同,所以它可以计算输入图像在卷积核覆盖区域内的平均灰度值。这种卷积核可以平滑图像,消除噪声,但会使图像变得模糊。第二种卷积核可以检测图像中的边缘,可以看到输入的8的边缘部分颜色更深一些,在更大的图片中这种边缘检测的效果会更明显。
需要注意的是,虽然上边说道不同的卷积核有着不同的作用,但是在卷积神经网络中,卷积核并不是手动设计出来的,而是通过数据驱动的方式学习得到的。这就是说,我们并不需要人工设计出特定的卷积核来检测边缘、纹理等特定的特征,而是让模型自己从训练数据中学习这些特征,即模型可以自动从复杂数据中学习到抽象和复杂的特征,这些特征可能人工设计难以达到。
在卷积过程中需要注意的两个参数是:步长和零填充。如果两者优化得当,可以让CNN的效果更加好。
1. 步长 - Strade
在卷积神经网络(CNN)中,"步长"(stride)是一个重要的概念。步长描述的是在进行卷积操作时,卷积核在输入数据上移动的距离。在两维图像中,步长通常是一个二元组,分别代表卷积核在垂直方向(高度)和水平方向(宽度)移动的单元格数。例如,步长为1意味着卷积核在每次移动时,都只移动一个单元格,这就意味着卷积核会遍历输入数据的每一个位置;同理,如果步长为2,那么卷积核每次会移动两个单元格。如下图,就是 strade=2 时,卷积后所得的结果:
步长的选择会影响卷积操作的输出尺寸。更大的步长会产生更小的输出尺寸,反之同理。
之所以设置步长,主要考虑以下几点:
- 降低计算复杂性:当步长大于1时,卷积核在滑动过程中会"跳过"一些位置,这将减少输出的尺寸并降低后续层的计算负担。
- 模型的可扩展性:增大步长可以有效地降低网络层次的尺寸,使得模型能处理更大尺寸的输入图片。
- 控制过拟合:过拟合是指模型过于复杂,以至于开始"记住"训练数据,而不是"理解"数据中的模式。通过减少模型的复杂性,我们可以降低过拟合的风险。
- 减少存储需求:更大的步长将产生更小的特征映射,因此需要更少的存储空间。
2. 零填充 - Zero Padding
注意上面的图,我们发现底部输入图片的中间十字部分,被输入了两次。并且输入图像与卷积核进行卷积后的结果中损失了部分值,输入图像的边缘被“修剪”掉了(边缘处只检测了部分像素点,丢失了图片边界处的众多信息)。这是因为边缘上的像素永远不会位于卷积核中心,而卷积核也没法扩展到边缘区域以外。这个结果我们是不能接受的,有时我们还希望输入和输出的大小应该保持一致。为解决这个问题,可以在进行卷积操作前,对原矩阵进行边界填充(Padding),也就是在矩阵的边界上填充一些值,以增加矩阵的大小,通常都用”空“来进行填充。
如果定义输入层的边长是M,卷积核的边长是K,填充的圈数为P,以及步长的长度为S,我们可以推导出卷积之后输出的矩阵大小为:
激活层就不解释了,一般来说引入激活层只是引入非线性,官方指导中也说明,一般使用 Relu 即可。而池化层则其实是为了让模型具有泛化的能力,其实说白了就是为了避免模型的 overfit,使用池化层让模型忘记一些之前学过的特征,这里的做法其实在后面的很多模型中都能看到。
池化层主要采用最大池化(Max Pooling)、平均池化(Average Pooling)等方式,对特征图进行操作。以最常见的最大池化为例,我们选择一个窗口(比如 2x2)在特征图上滑动,每次选取窗口中的最大值作为输出,如下图这就是最大池化的工作方式:
大致可以看出,经过池化计算后的图像,基本就是左侧特征图的“低像素版”结果。也就是说池化运算能够保留最强烈的特征,并大大降低数据体量。
四、手写数字CNN设计
接下来,我们可以使用上面提到的 CNN 的各个层,给我们自己的手写数字识别设计网络架构。其实就是让 卷积层 -> Relu -> 池化层 叠加 N 层,然后最后最后再加入全连接层,进行分类。
下图就是具体的网络架构图
用代码实现如下:
import torch
from torch import nn
from torch.nn import functional as F
from torch import optim
import torchvision
from matplotlib import pyplot as plt
from utils import plot_curve, plot_image, one_hot, predict_plot_image # step 1 : load dataset
batch_size = 512
# https://blog.csdn.net/weixin_44211968/article/details/123739994
# DataLoader 和 dataset 数据集的应用
train_loader = torch.utils.data.DataLoader(
torchvision.datasets.MNIST('./data', train=True, download=True,
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(
(0.1307,), (0.3081,)
)
])),
batch_size=batch_size, shuffle=True
) test_loader = torch.utils.data.DataLoader(
torchvision.datasets.MNIST('./data', train=True, download=True,
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(
(0.1307,), (0.3081,)
)
])),
batch_size=batch_size, shuffle=False
) # step 2 : CNN网络
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
# 图片是灰度图片,只有一个通道
# 第一层
self.conv1 = nn.Conv2d(in_channels=1, out_channels=16,
kernel_size=5, stride=1, padding=2)
self.relu1 = nn.ReLU()
self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2) # 第二层
self.conv2 = nn.Conv2d(in_channels=16, out_channels=32,
kernel_size=5, stride=1, padding=2)
self.relu2 = nn.ReLU()
self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2) # 全连接层
self.fc1 = nn.Linear(in_features=7 * 7 * 32, out_features=256)
self.relufc = nn.ReLU()
self.fc2 = nn.Linear(in_features=256, out_features=10) # 定义前向传播过程的计算函数
def forward(self, x):
# 第一层卷积、激活函数和池化
x = self.conv1(x)
x = self.relu1(x)
x = self.pool1(x)
# 第二层卷积、激活函数和池化
x = self.conv2(x)
x = self.relu2(x)
x = self.pool2(x)
# 将数据平展成一维
x = x.view(-1, 7 * 7 * 32)
# 第一层全连接层
x = self.fc1(x)
x = self.relufc(x)
# 第二层全连接层
x = self.fc2(x)
return x # step3: 定义损失函数和优化函数
# 学习率
learning_rate = 0.005
# 定义损失函数,计算模型的输出与目标标签之间的交叉熵损失
criterion = nn.CrossEntropyLoss()
model = CNN()
optimizer = optim.SGD(model.parameters(), lr=learning_rate, momentum=0.9) # step4: 模型训练
train_loss = []
# 定义迭代次数
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# 将神经网络模型 net 移动到指定的设备上。
model = model.to(device)
# 为了和全连接网路的训练次数一致,我们采用相同的迭代次数
total_step = len(train_loader)
num_epochs = 3
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
images = images.to(device)
labels = labels.to(device)
optimizer.zero_grad() # 清空上一个batch的梯度信息
# 将输入数据 inputs 喂入神经网络模型 net 中进行前向计算,得到模型的输出结果 outputs。
outputs = model(images)
# 使用交叉熵损失函数
loss = criterion(outputs, labels)
# 使用反向传播算法计算模型参数的梯度信息
loss.backward()
# 更新梯度
optimizer.step() train_loss.append(loss.item())
# 输出训练结果
if (i + 1) % 100 == 0:
print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'.format(epoch + 1, num_epochs, i + 1, total_step,
loss.item())) print('Finished Training')
# 打印 loss 损失图
plot_curve(train_loss) # step 5 : 准确度测试
# 测试CNN模型
with torch.no_grad(): # 进行评测的时候网络不更新梯度
correct = 0
total = 0
for images, labels in test_loader:
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('Accuracy of the network on the 10000 test images: {} %'.format(100 * correct / total))
损失可以看到经过两层的卷积层之后,虽然没有全连接层的Loss那么低,但是下降的还是非常快的,最后趋于平稳。
经过三个epoch的训练,结果如下:
Epoch [1/3], Step [100/118], Loss: 0.4683
Epoch [2/3], Step [100/118], Loss: 0.2781
Epoch [3/3], Step [100/118], Loss: 0.1155
Finished Training
Accuracy of the network on the 10000 test images: 96.47 %
我们可以惊奇的发现,这里test数据中预测的准确率竟然高达 96.47%。要知道我们使用好几层的全连接网络,最后虽然 Loss 值非常低,但是在 test 数据中的准确度都只是徘徊在 90% 左右。而我们在使用相同层数,同等训练 epoch 的情况下,我们就把识别准确率提高了7%左右,这真是令人兴奋的进步!!!
以上就是CNN的全部实践过程了,这个系列的一小节就到此结束了,随着学习深入,我发现机器学习中比较能做出成果的是深度学习,所以这几篇都是关于深度学习的内容。后面会更多的更新机器学习相关知识,我只是知识的搬运工,下期再见~
Reference
[1] https://zhuanlan.zhihu.com/p/635438713
[2] https://mlnotebook.github.io/post/CNN1/
[3] https://poloclub.github.io/cnn-explainer/#article-convolution
[4] https://blog.csdn.net/weixin_41258131/article/details/133013757
[5] https://alexlenail.me/NN-SVG/LeNet.html
机器学习从入门到放弃:卷积神经网络CNN(二)的更多相关文章
- 写给程序员的机器学习入门 (八) - 卷积神经网络 (CNN) - 图片分类和验证码识别
这一篇将会介绍卷积神经网络 (CNN),CNN 模型非常适合用来进行图片相关的学习,例如图片分类和验证码识别,也可以配合其他模型实现 OCR. 使用 Python 处理图片 在具体介绍 CNN 之前, ...
- 卷积神经网络(CNN)学习笔记1:基础入门
卷积神经网络(CNN)学习笔记1:基础入门 Posted on 2016-03-01 | In Machine Learning | 9 Comments | 14935 Vie ...
- python机器学习卷积神经网络(CNN)
卷积神经网络(CNN) 关注公众号"轻松学编程"了解更多. 一.简介 卷积神经网络(Convolutional Neural Network,CNN)是一种前馈神经网络,它的人 ...
- 卷积神经网络CNN总结
从神经网络到卷积神经网络(CNN)我们知道神经网络的结构是这样的: 那卷积神经网络跟它是什么关系呢?其实卷积神经网络依旧是层级网络,只是层的功能和形式做了变化,可以说是传统神经网络的一个改进.比如下图 ...
- 【深度学习系列】手写数字识别卷积神经--卷积神经网络CNN原理详解(一)
上篇文章我们给出了用paddlepaddle来做手写数字识别的示例,并对网络结构进行到了调整,提高了识别的精度.有的同学表示不是很理解原理,为什么传统的机器学习算法,简单的神经网络(如多层感知机)都可 ...
- 【深度学习系列】卷积神经网络CNN原理详解(一)——基本原理
上篇文章我们给出了用paddlepaddle来做手写数字识别的示例,并对网络结构进行到了调整,提高了识别的精度.有的同学表示不是很理解原理,为什么传统的机器学习算法,简单的神经网络(如多层感知机)都可 ...
- 深度学习之卷积神经网络CNN及tensorflow代码实现示例
深度学习之卷积神经网络CNN及tensorflow代码实现示例 2017年05月01日 13:28:21 cxmscb 阅读数 151413更多 分类专栏: 机器学习 深度学习 机器学习 版权声明 ...
- TensorFlow 2.0 深度学习实战 —— 浅谈卷积神经网络 CNN
前言 上一章为大家介绍过深度学习的基础和多层感知机 MLP 的应用,本章开始将深入讲解卷积神经网络的实用场景.卷积神经网络 CNN(Convolutional Neural Networks,Conv ...
- 卷积神经网络(CNN)前向传播算法
在卷积神经网络(CNN)模型结构中,我们对CNN的模型结构做了总结,这里我们就在CNN的模型基础上,看看CNN的前向传播算法是什么样子的.重点会和传统的DNN比较讨论. 1. 回顾CNN的结构 在上一 ...
- 卷积神经网络(CNN)反向传播算法
在卷积神经网络(CNN)前向传播算法中,我们对CNN的前向传播算法做了总结,基于CNN前向传播算法的基础,我们下面就对CNN的反向传播算法做一个总结.在阅读本文前,建议先研究DNN的反向传播算法:深度 ...
随机推荐
- Windows10磁盘占用100%和内存占用高
前言 公司配备了两台电脑,两台电脑都是安装的win10系统,一台是磁盘占用高,另一台是内存可用低. 具体情况如下: 一台外网机 8g内存,安装win10 专业版,开机一天后经常出现内存不够用,但其实都 ...
- 微信小程序-页面跳转navigator组件
官方文档地址:https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/route.html 在官方文档当中有提到一 ...
- 【主流技术】15 分钟掌握 Redis 的安装部署和基本特性
目录 前言 一.Redis 概述 1.1Redis 是什么? 1.2Redis 能做什么? 1.3基础知识 二.Redis 安装与基本命令 2.1Windows 安装 方式一 方式二 2.2Linux ...
- 验证码识别服务2Captcha框架
2Captcha是一个自动验证码识别服务,主要用于解决各种互联网服务中的验证码问题.在许多网站注册账户或进行敏感操作时,为了验证用户是真实的而不是自动化程序,会出现验证码.用户必须正确输入验证码,才能 ...
- Python 使用Scapy构造特殊数据包
Scapy是一款Python库,可用于构建.发送.接收和解析网络数据包.除了实现端口扫描外,它还可以用于实现各种网络安全工具,例如SynFlood攻击,Sockstress攻击,DNS查询攻击,ARP ...
- 2.5 PE结构:导入表详细解析
导入表(Import Table)是Windows可执行文件中的一部分,它记录了程序所需调用的外部函数(或API)的名称,以及这些函数在哪些动态链接库(DLL)中可以找到.在Win32编程中我们会经常 ...
- Python 潮流周刊第 38 期(摘要)+赠书5本
本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...
- 20.2 显示的链接到导出符号--《Windows核心编程》
FAPPROC GetProcAddress(HMOUDLE hInstDll,PCSTR pszSymbolName); 1.根据名称 FARPROC FunctionAddress = (ULON ...
- Windows-Xshell对多个终端同时执行命令(发送命令到多个会话)
方法1:使用查看中的撰(zhuàn)写栏 (1).查看→撰写→撰写栏. (2).底部可以看到"撰写栏",选择全部会话. (3).在撰写栏输入命令,回车后就会发送到所有会话窗口. 方 ...
- RAPTOR:递归摘要与树形检索的结合,提升RAG检索性能
RAPTOR:递归摘要与树形检索的结合,提升RAG检索性能 来源:ICLR'24 https://arxiv.org/pdf/2401.18059.pdf 随着 LLM 技术的发展,RAG 的价值也来 ...