⚠️本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
✨专栏介绍: 经过几个月的精心筹备,本作者推出全新系列《深入浅出OCR》专栏,对标最全OCR教程,具体章节如导图所示,将分别从OCR技术发展、方向、概念、算法、论文、数据集等各种角度展开详细介绍。
??面向对象: 本篇前言知识主要介绍深度学习基础,方便小白或AI爱好者学习基础知识。
?友情提醒: 本文内容可能未能含概深度学习所有知识点,其他内容可以访问本人主页其他文章或个人博客,同时因本人水平有限,文中如有错误恳请指出,欢迎互相学习交流!
?个人主页: GoAI |? 公众号: GoAI的学习小屋 | ?交流群: 704932595 |?个人简介 : 掘金签约作者、百度飞桨PPDE、领航团团长、开源特训营导师、CSDN、阿里云社区人工智能领域博客专家、新星计划计算机视觉方向导师等,专注大数据与人工智能知识分享。
?文章目录
《深入浅出OCR》前言知识:机器学习基础总结 (✨本篇,文末有机器学习总结导图福利!)
《深入浅出OCR》前言知识:深度学习基础总结 (✨本篇,文末有深度学习总结导图福利!)
《深入浅出OCR》前言知识:深度学习基础总结
一、深度学习介绍
在介绍深度学习概念前,我们需要弄清楚机器学习与深度学习的区别。这里作者参考一些其他资料进行整理给出如下回答:
机器学习与深度学习区别
在数据准备和预处理(清洗、归一化等操作)方面,机器学习与深度学习是很相似的,但最大的区别在于对特征提取的过程。
传统机器学习的特征提取主要依赖人工,针对特定简单任务的时候人工提取特征会简单有效,但是并不能通用。
深度学习的特征提取并不依靠人工,而是机器自动提取的。这也是为什么大家都说深度学习的可解释性很差,因为有时候深度学习虽然能有好的表现,但是我们并不知道他的原理是什么。
针对上述分析,我们给出对应深度学的总结概述:
机器学习:利用计算机、概率论、统计学等知识,输入数据,让计算机学会新知识。机器学习的过程,就是训练数据去优化目标函数。
深度学习:是一种特殊的机器学习,具有强大的能力和灵活性。它通过学习将世界表示为嵌套的层次结构,每个表示都与更简单的特征相关,而抽象的表示则用于计算更抽象的表示。
具体地,深度学习的概念源于人工神经网络的研究,由Hinton等人于2006年提出。深度学习通过组合低层特征形成更加抽象的高层表示属性类别或特征,以发现数据的分布式特征表示。含多隐层的多层感知器就是一种深度学习结构。
机器学习与深度学习、人工智能的关系:
深度学习的优缺点:
深度学习优点:
- 学习能力强,无需人工提特征
- 覆盖范围广,适应性好
- 数据驱动,上限高
- 可移植性好
深度学习缺点:
- 计算量大,便携性差
- 硬件需求高
- 模型设计复杂
二、 深度学习常见名词总结
当提到深度学习时,我们经常会提到很多专有名词,给初学者学习带来一定困扰。因此,在学习深度学习前,首先我们需要明确以下几个概念。
2.1 主要术语
损失(Loss)
一种衡量指标,用于衡量模型的预测偏离其标签程度。要确定此值,模型需要定义损失函数。例如:线性回归模型参与均方误差损失函数,分类模型采用交叉熵损失函数。
在深度学习中,超参数是在开始学习过程之前设置值的参数,而不是通过训练得到的参数数据。通常情况下,需要对超参数进行优化,给学习机选择一组最优超参数,以提高学习的性能和效果。
超参数具体来讲比如算法中的学习率(learning rate)、梯度下降法迭代的数量(iterations)、隐藏层数目(hidden layers)、隐藏层单元数目、激活函数( activation function)都需要根据实际情况来设置,这些数字实际上控制了最后的参数和的值,所以它们被称作超参数。
在训练模型时用于梯度下降的一个标量。在每次迭代期间,梯度下降法都会将学习速率与梯度相乘;得出的乘积称为梯度步长。
激活函数(Activation Function)
激活函数可以引入非线性因素,为了使神经网络能够学习复杂的决策边界,我们在其一些层应用一个非线性激活函数。最常用的函数包括sigmoid、tanh、ReLU以及这些函数的变体。
梯度消失、爆炸(Vanishing Gradient、Exploding Gradient )
梯度消失问题出现在使用梯度很小(在 0 到 1 的范围内)的激活函数的很深的神经网络中。由于这些小梯度会在反向传播中相乘,往往在传播时消失,从而让网络无法学习长程依赖。而梯度爆炸问题则是在深度神经网络中梯度可能会在反向传播过程中变得很大,导致溢出。
微调(Fine-Tuning)
Fine-Tuning 这种技术是指使用来自另一个任务(例如一个无监督训练网络)的参数初始化网络,然后再基于当前任务更新这些参数。
过拟合(overfitting)
创建的模型与训练数据过于匹配,以至于模型无法根据新数据做出正确的预测,使得模型的泛化能力较低。
欠拟合
是指模型拟合程度不高,数据距离拟合曲线较远,或指模型没有很好地捕捉到数据特征,不能很好地拟合数据。换言之,模型在学习的过程中没有很好地掌握它该掌握的知识,模型学习的偏差较大。
一种正则化,根据权重的绝对值的总和,来惩罚权重。在以来稀疏特征的模型中,L1正则化有助于使不相关或几乎不相关的特征的权重正好为0,从而将这些特征从模型中移除。与L2正则化相对。
一种正则化,根据权重的平方和,来惩罚权重。L2正则化有助于使离群值(具有较大正值或较小负责)权重接近于0,但又不正好为0。在线性模型中,L2正则化始终可以进行泛化。
其他术语请参考本人这篇: 机器学习与深度学习基础概念
三、深度学习经典算法:
下面主要是对深度学习算法方向进行简单总结,分为四大类:
3.1 卷积神经网络CNN
3.1.1 神经网络与感知机
人工神经网络(Artificial Neural Networks,简写为ANNs)是一种模仿动物神经网络行为特征,进行分布式并行信息处理的算法数学模型。这种网络依靠系统的复杂程度,通过调整内部大量节点之间相互连接的关系,从而达到处理信息的目的,并具有自学习和自适应的能力。神经网络类型众多,其中最为重要的是多层感知机。为了详细地描述神经网络,我们先从最简单的神经网络说起。
感知机是1957年,由Rosenblatt提出会,是神经网络和支持向量机的基础。感知机是有生物学上的一个启发,他的参照对象和理论依据可以参照下图:(我们的大脑可以认为是一个神经网络,是一个生物的神经网络,在这个生物的神经网络里边呢,他的最小单元我们可以认为是一个神经元,一个neuron,这些很多个神经元连接起来形成一个错综复杂的网络,我们把它称之为神经网络。
简单的感知机通过设置合适的x和b,当输入为0,1 时,感知机输出为0×(−2)+1×(−2)+3=1 。
多层感知机则是由感知机推广而来,最主要的特点是有多个神经元层,因此也叫深度神经网络。相比于单独的感知机,多层感知机的第i层的每个神经元和第i-1层的每个神经元都有连接。
3.1.2卷积神经网络CNN介绍
卷积神经网络(Convolutional Neural Networks, CNN)是一类包含卷积计算且具有深度结构的前馈神经网络(Feedforward Neural Networks),是深度学习(deep learning)的代表算法之一 。目前 CNN 已经得到了广泛的应用,比如:人脸识别、自动驾驶、美图秀秀、安防等很多领域。
卷积神经网络 CNN 最擅长的就是图片的处理。它受到人类视觉神经系统的启发。
CNN 特点:
- 能够有效的将大数据量的图片降维成小数据量。
- 能够有效的保留图片特征,符合图片处理的原则。
CNN核心:
卷积层最主要的两个特征就是局部连接和权值共享,有些地方又叫做稀疏连接和参数共享。
1.局部连接:
- 一般认为图像的空间联系是局部的像素联系比较密切,而距离较远的像素相关性较弱,因此,每个神经元没必要对全局图像进行感知,只要对局部进行感知,然后在更高层将局部的信息综合起来得到全局信息。利用卷积层实现:(特征映射,每个特征映射是一个神经元阵列):从上一层通过局部卷积滤波器提取局部特征。卷积层紧跟着一个用来求局部平均与二次提取的计算层,这种二次特征提取结构减少了特征分辨率。
2.参数共享:
- 在局部连接中,每个神经元的参数都是一样的,即:同一个卷积核在图像中都是共享的。(理解:卷积操作实际是在提取一个个局部信息,而局部信息的一些统计特性和其他部分是一样的,也就意味着这部分学到的特征也可以用到另一部分上。所以对图像上的所有位置,都能使用同样的学习特征。)卷积核共享有个问题:提取特征不充分,可以通过增加多个卷积核来弥补,可以学习多种特征。
- 比如对一个100×100像素的图像来说,如果我们用一个神经元来对图像进行操作,这个神经元大小就是100×100=10000,单如果我们使用10×10的卷积核,我们虽然需要计算多次,但我们需要的参数只有10×10=100个,加上一个偏向b,一共只需要101个参数。最终我们取得图像大小还是100×100。
3.3 CNN网络介绍
下面我们主要介绍:卷积层、池化层、全连接层
- 卷积层 – 主要作用是保留图片的特征
- 池化层 – 主要作用是把数据降维,可以有效的避免过拟合
- 全连接层 – 根据不同任务输出我们想要的结果
1. 卷积层
卷积是一种有效提取图片特征的方法 。 一般用一个正方形卷积核,遍历图片上的每一个像素点。图片与卷积核重合区域内相对应的每一个像素值,乘卷积核内相对应点的权重,然后求和, 再加上偏置后,最后得到输出图片中的一个像素值。
2. 池化层
1.使卷积神经网络抽取特征是保证特征局部不变性。
2.池化操作能降低维度,减少参数数量。
3..池化操作优化比较简单。
在卷积层中,可以通过调节步长参数 s 实现特征图的高宽成倍缩小,从而降低了网络的参数量。实际上,除了通过设置步长,还有一种专门的网络层可以实现尺寸缩减功能,它就是我们要介绍的池化层(Pooling layer)。
池化层同样基于局部相关性的思想,通过从局部相关的一组元素中进行采样或信息聚合,从而得到新的元素值。通常我们用到两种池化进行下采样:
(1)最大池化(Max Pooling) ,从局部相关元素集中选取最大的一个元素值。
(2)平均池化(Average Pooling) ,从局部相关元素集中计算平均值并返回。
CNN常用骨干网络:
CNN导图总览:
这里本作者GoAI帮大家整理了骨干网络对应发展过程的思维导图及论文下载地址、翻译总结:
CNN骨干网络发展:
骨干网络论文总结及翻译:
- AlexNet ImageNet Classification with Deep Convolutional Neural Networks 中文版 中英文对照
- VGG Very Deep Convolutional Networks for Large-Scale Image Recognition 中文版 中英文对照
- ResNet Deep Residual Learning for Image Recognition 中文版 中英文对照
- GoogLeNet Going Deeper With Convolutions 中文版 中英文对照
- BN-GoogLeNet Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift 中文版 中英文对照
- Inception-v3 Rethinking the Inception Architecture for Computer Vision 中文版 中英文对照
- SENet Squeeze-and-Excitation Networks 中文版 中英文对照
CNN网络具体介绍
1.LeNet-5
LeNet-5是由LeCun提出的一种用于识别手写数字和机器印刷字符的卷积神经网络(Convolutional Neural Network,CNN) ,LeNet-5阐述了图像中像素特征之间的相关性能够由参数共享的卷积操作所提取,同时使用卷积、下采样(池化)和非线性映射这样的组合结构,是当前流行的大多数深度图像识别网络的基础。
网络结构非常简单,一共有五层(仅包含有参数的层,无参数的池化层不算在网络模型之
中):
- 输入尺寸:32×32
- 卷积层:2个
- 池化层:2个
- 全连接层:2个
- 输出层:1个,大小为10×1
参数计算:
神经元个数=卷积核数量X输出特征图宽度X输出特征图高度
卷积层可训练参数数量=卷积核数量X(卷积核宽度X卷积核高度+1)(1表示偏置)
汇聚层可训练参数数量=卷积核数量X(1+1)(两个1分别表示相加后的系数和偏置,有的汇聚层无参数)
连接数=卷积核数量X(卷积核宽度X卷积核高度+1)X输出特征图宽度X输出特征图高度(1表示偏置)
全连接层连接数=卷积核数量X(输入特征图数量X卷积核宽度X卷积核高度+1)(输出特征图尺寸为1X1)
import time
import torch
from torch import nn, optim
import matplotlib.pyplot as plt
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
"""经典神经网络LeNet模型"""
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 6, 5), # in_channels, out_channels,kernel_size
nn.Sigmoid(),
nn.MaxPool2d(2, 2), # kernel_size, stride
nn.Conv2d(6, 16, 5),#卷积层
nn.Sigmoid(),
nn.MaxPool2d(2, 2)
)
self.fc = nn.Sequential(
nn.Linear(16*4*4, 120),
nn.Sigmoid(),
nn.Linear(120, 84),
nn.Sigmoid(),
nn.Linear(84, 10)
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1))
return output
#查看每个层的形状
net=LeNet()
print(net)
"""获取数据和训练模型"""
#使⽤Fashion-MNIST作为训练数据集。
batch_size = 256
train_iter, test_iter =d2l.load_data_fashion_mnist(batch_size=batch_size)
def evaluate_accuracy(data_iter, net,device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')):
acc_sum, n = 0.0, 0
with torch.no_grad():
for X, y in data_iter:
if isinstance(net, torch.nn.Module):
net.eval() # 评估模式, 这会关闭dropout
acc_sum += (net(X.to(device)).argmax(dim=1) ==
y.to(device)).float().sum().cpu().item()
net.train() #改回训练模型
else: # ⾃定义的模型, 3.13节之后不会⽤到, 不考虑GPU
if ('is_training' in net.__code__.co_varnames): # 如果有is_training这个参数
# 将is_training设置成False
acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item()
else:
acc_sum += (net(X).argmax(dim=1) ==y).float().sum().item()
n += y.shape[0]
return acc_sum / n
def train_ch5(net, train_iter, test_iter, batch_size, optimizer,device, num_epochs):
net = net.to(device)
print("training on ", device)
loss = torch.nn.CrossEntropyLoss()
batch_count = 0
for epoch in range(num_epochs):
train_l_sum, train_acc_sum, n, start = 0.0, 0.0, 0,time.time()
for X, y in train_iter:
X = X.to(device)
y = y.to(device)
y_hat = net(X)
l = loss(y_hat, y)
optimizer.zero_grad()
l.backward()
optimizer.step()
train_l_sum += l.cpu().item()
train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()
n += y.shape[0]
batch_count += 1
test_acc = evaluate_accuracy(test_iter, net)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f,time % .1fsec'% (epoch + 1, train_l_sum / batch_count,train_acc_sum / n, test_acc, time.time() - start))
#学习率采⽤0.001,训练算法使⽤Adam算法,损失函数使⽤交叉熵损失函数。
lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
train_ch5(net, train_iter, test_iter, batch_size, optimizer, device,num_epochs)
plt.show()
2.AlexNet
AlexNet是一系列的卷积池化操作最后接上全连接层,整体结构跟LeNet类似。
该网络一共有8层(不包括池化)。
·输入尺寸:227×227×3
·卷积层:5个
·池化层:3个
·全连接层:2个
·输出层:1个,大小为1000×1
Alexnet代码实现:
import time
import matplotlib.pyplot as plt
import torch
from torch import nn, optim
import torchvision
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
"""实现稍微简化过的AlexNet """
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 96, 11, 4), # in_channels, out_channels,kernel_size, stride, padding
nn.ReLU(),
nn.MaxPool2d(3, 2), # kernel_size, stride
# 减⼩卷积窗⼝,使⽤填充为2来使得输⼊与输出的⾼和宽⼀致,且增⼤输出通道数
nn.Conv2d(96, 256, 5, 1, 2),
nn.ReLU(),
nn.MaxPool2d(3, 2),
# 连续3个卷积层,且使⽤更⼩的卷积窗⼝。除了最后的卷积层外,进⼀步增⼤了输出通道数。
# 前两个卷积层后不使⽤池化层来减⼩输⼊的⾼和宽
nn.Conv2d(256, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 256, 3, 1, 1),
nn.ReLU(),
nn.MaxPool2d(3, 2)
)# 这⾥全连接层的输出个数⽐LeNet中的⼤数倍。使⽤丢弃层来缓解过拟合
self.fc = nn.Sequential(
nn.Linear(256 * 5 * 5, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(0.5),
# 输出层。由于这⾥使⽤Fashion-MNIST,所以⽤类别数为10,⽽⾮论⽂中的1000
nn.Linear(4096, 10),
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1))
return output
#打印看网络结构
net = AlexNet()
print(net)
"""读取数据"""
def load_data_fashion_mnist(batch_size, resize=None,root='~/Datasets/FashionMNIST'):
trans = []
if resize:
trans.append(torchvision.transforms.Resize(size=resize))
trans.append(torchvision.transforms.ToTensor())
transform = torchvision.transforms.Compose(trans)
mnist_train = torchvision.datasets.FashionMNIST(root=root,
train=True, download=True, transform=transform)
mnist_test = torchvision.datasets.FashionMNIST(root=root,
train=False, download=True, transform=transform)
train_iter = torch.utils.data.DataLoader(mnist_train,
batch_size=batch_size, shuffle=True, num_workers=4)
test_iter = torch.utils.data.DataLoader(mnist_test,
batch_size=batch_size, shuffle=False, num_workers=4)
return train_iter, test_iter
batch_size = 128
# 如出现“out of memory”的报错信息,可减⼩batch_size或resize
train_iter, test_iter = load_data_fashion_mnist(batch_size,resize=224)
"""训练"""
lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer,device, num_epochs)
plt.show()
3.Inception网络
Inception的原始模型是相当于MLP卷积层更为稀疏,它采用了MLP卷积层的思想,将中间的全连接层换成了多通道卷积层。Inception与MLP卷积在网络中的作用一样,把封装好的Inception作为一个卷积单元,堆积起来形成了原始的GoogleNet网络。 其结构是将1×1、3×3、5×5的卷积核对应的卷积操作和3×3的滤波器对应的池化操作堆叠在一起,一方面增加了网络的宽度,另一方面增加了网络对尺度的适应性。增加了网络对不同尺度的适应性。
GoogleNet模型详细解读:
去除全连接层,使得模型训练更快并且减轻了过拟合。
Inception的核心思想是通过增加网络深度和宽度的同时减少参数的方法来解决问题。Inception v1由22层,比AlexNet的8层或VGGNet的19层更深。但其计算量只有15亿次浮点运算,同时只有500万的参数量,仅为AlexNet的1/12,却有着更高的准确率。
Inception的前身为MLP卷积层。
MLP卷积层的思想是将CNN高维度特征转成低维度特征,将神经网络的思想融合在具体的卷积操作当中。直白的理解就是在网络中再放一个网络,即使每个卷积的通道中包含一个微型的多层网络,用一个网络来代替原来具体的卷积运算过程(卷积核的每个值与样本对应的像素点相乘,再将相乘后的所有结果加在一起生成新的像素点的过程
Inception v1模型在原有的Inception模型基础上做了一些改进,原因是由于Inception的原始模型是将所有的卷积核都在上一层的所有输出上来做,那么5×5的卷积核所需的计算量就比较大,造成了特征图厚度很大。为了避免这一现象,在3×3前、5×5前、最大池化层后分别加上了1×1的卷积核,起到了降低特征图厚度的作用(其中1×1卷积主要用来降维)
Inception v2模型在v1模型基础上应用当时的主流技术,在卷积后加入BN层,使每一层的输出都归一化处理,减少了内变协变量的移动问题;同时还使用了梯度截断技术,增加了训练的稳定性。另外,Inception学习了VGG,用2个3×3的conv替代5×5,这既降低了参数数量,也提升了计算速度。
Inception v3没有再加入其他的技术,只是将原来的结构进行了调整,其最重要的一个改进是分解。
Inception v4结合残差连接技术的特点进行结构的优化调整。
总结: GoogLeNet相比于以前的卷积神经网络结构,除了在深度上进行了延伸,还对网络的宽度进行
了扩展,整个网络由许多块状子网络的堆叠而成,这个子网络构成了Inception结构。
Inceptionv1在同一层中采用不同的卷积核,并对卷积结果进行合并;Inceptionv2组合不同
卷积核的堆叠形式,并对卷积结果进行合并;Inception3则在v2基础上进行深度组合的尝试;
Inceptionv4结构相比于前面的版本更加复杂,子网络中嵌套着子网络。
4.ResNet残差网络
(1)Resnet概念
ResNet又名残差神经网络,指的是在传统卷积神经网络中加入残差学习(residual learning)的思想,解决了深层网络中梯度弥散和精度下降(训练集)的问题,使网络能够越来越深,既保证了精度,又控制了速度。
残差网络详细解读:
该框架能够大大简化模型网络的训练时间,使得再可接受时间内,模型能更深。所谓的残差连接就是在标准的前馈卷积网络上加一个跳跃,从而绕过一些层的连接方式。解决梯度消失的问题
在ResNet中,输入层与Addition之间存在着两个连接,左侧的连接是输入层通过若干神经层之后连接到Addition,右侧的连接是输入层直接传到Addition,在反向传播的过程中误差传到Input时会得到两个误差的相加和,一个是左侧一堆网络的误差,一个右侧直接的原始误差。左侧的误差会随着层数变深而梯度越来越小,右侧则是由Addition直接连接到Input,所以还会保留着Addition的梯度。这样Input得到的相加和后的梯度就没有那么小了,可以保证接着将误差往下传。而在论文Resnet中,提到了一个名词叫“Shortcut Connection”,实际上它指的就是identity mapping,这里先解释一下,免的大家后面会confuse。针对不同深度的ResNet,其中残差一般包括2-3层,分别是以下两种Residual Block:
对上图做如下说明:
-
左图为基本的residual block,residual mapping为两个64通道的3×3卷积,输入输出均为64通道,可直接相加。该block主要使用在相对浅层网络,比如ResNet-34;
-
右图为针对深层网络提出的block,称为“bottleneck” block,主要目的就是为了降维。首先通过一个1×1卷积将256维通道(channel)降到64通道,最后通过一个256通道的1×1卷积恢复。
论文中提出5种ResNet网络,网络参数统计表如下:
import torch
from torch import nn
from torch.nn import functional as F
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision import transforms
from torch import nn, optim
# from torchvision.models import resnet18
#定义残差模块
class ResBlk(nn.Module):
"""
resnet block
"""
def __init__(self, ch_in, ch_out):
"""
:param ch_in:
:param ch_out:
"""
super(ResBlk, self).__init__()
self.conv1 = nn.Conv2d(ch_in, ch_out, kernel_size=3, stride=1, padding=1)
self.bn1 = nn.BatchNorm2d(ch_out)
self.conv2 = nn.Conv2d(ch_out, ch_out, kernel_size=3, stride=1, padding=1)
self.bn2 = nn.BatchNorm2d(ch_out)
self.extra = nn.Sequential()
if ch_out != ch_in:
# [b, ch_in, h, w] => [b, ch_out, h, w]
self.extra = nn.Sequential(
nn.Conv2d(ch_in, ch_out, kernel_size=1, stride=1),
nn.BatchNorm2d(ch_out)
)
#定义反向传播
def forward(self, x):
"""
:param x: [b, ch, h, w]
:return:
"""
out = F.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
# short cut.
# extra module: [b, ch_in, h, w] => [b, ch_out, h, w]
# element-wise add:
out = self.extra(x) + out
return out
#构建ResNet18网络
class ResNet18(nn.Module):
def __init__(self):
super(ResNet18, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(16)
)
# followed 4 blocks
# [b, 64, h, w] => [b, 128, h ,w]
self.blk1 = ResBlk(16, 16)
# [b, 128, h, w] => [b, 256, h, w]
self.blk2 = ResBlk(16, 32)
# # [b, 256, h, w] => [b, 512, h, w]
# self.blk3 = ResBlk(128, 256)
# # [b, 512, h, w] => [b, 1024, h, w]
# self.blk4 = ResBlk(256, 512)
self.outlayer = nn.Linear(32*32*32, 10)
def forward(self, x):
"""
:param x:
:return:
"""
x = F.relu(self.conv1(x))
# [b, 64, h, w] => [b, 1024, h, w]
x = self.blk1(x)
x = self.blk2(x)
# x = self.blk3(x)
# x = self.blk4(x)
# print(x.shape)
x = x.view(x.size(0), -1)
x = self.outlayer(x)
return x
#定义主函数,以CIFAR10数据集为例
def main():
batchsz = 32
cifar_train = datasets.CIFAR10('cifar', True, transform=transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor()
]), download=True)
cifar_train = DataLoader(cifar_train, batch_size=batchsz, shuffle=True)
cifar_test = datasets.CIFAR10('cifar', False, transform=transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor()
]), download=True)
cifar_test = DataLoader(cifar_test, batch_size=batchsz, shuffle=True)
x, label = iter(cifar_train).next()
print('x:', x.shape, 'label:', label.shape)
device = torch.device('cuda')
# model = Lenet5().to(device)
model = ResNet18().to(device)
criteon = nn.CrossEntropyLoss().to(device)
optimizer = optim.Adam(model.parameters(), lr=1e-3)
print(model)
for epoch in range(1000):
model.train()
for batchidx, (x, label) in enumerate(cifar_train):
# [b, 3, 32, 32]
# [b]
x, label = x.to(device), label.to(device)
logits = model(x)
# logits: [b, 10]
# label: [b]
# loss: tensor scalar
loss = criteon(logits, label)
# backprop
optimizer.zero_grad()
loss.backward()
optimizer.step()
#
print(epoch, 'loss:', loss.item())
model.eval()
with torch.no_grad():
# test
total_correct = 0
total_num = 0
for x, label in cifar_test:
# [b, 3, 32, 32]
# [b]
x, label = x.to(device), label.to(device)
# [b, 10]
logits = model(x)
# [b]
pred = logits.argmax(dim=1)
# [b] vs [b] => scalar tensor
correct = torch.eq(pred, label).float().sum().item()
total_correct += correct
total_num += x.size(0)
# print(correct)
acc = total_correct / total_num
print(epoch, 'acc:', acc)
if __name__ == '__main__':
main()
3.3 循环神经网络RNN
RNN(Recurrent Neural Network), 称作循环神经网络, 它一般以序列数据为输入, 通过网络内部的结构设计有效捕捉序列之间的关系特征, 一般也是以序列形式进行输出。因为RNN结构能够很好利用序列之间的关系,广泛应用于NLP领域的各项任务, 如文本分类, 情感分析, 机器翻译等.但RNN在处理长期依赖(时间序列上距离较远的节点)时会遇到巨大的困难,会造成梯度消失或者梯度膨胀的现象。
RNN变体:LSTM、GRU
LSTM算法是一种重要的目前使用最多的时间序列算法,是一种特殊的RNN(Recurrent Neural Network,循环神经网络),能够学习长期的依赖关系。主要是为了解决上述长序列训练过程中的梯度消失和梯度爆炸问题。
** RNN和LSTM的区别**
所有 RNN 都具有一种重复神经网络模块的链式的形式。在标准的 RNN 中,这个重复的模块只有一个非常简单的结构,例如一个 tanh 层,如下图所示:
LSTM 同样是这样的结构,但是重复的模块拥有一个不同的结构。不同于单一神经网络层,这里是有四个,以一种非常特殊的方式进行交互。
注:上图图标具体含义如下所示:
上图中,每一条黑线传输着一整个向量,从一个节点的输出到其他节点的输入。粉色的圈代表 pointwise 的操作,诸如向量的和,而黄色的矩阵就是学习到的神经网络层。合在一起的线表示向量的连接,分开的线表示内容被复制,然后分发到不同的位置。
3 LSTM核心
LSTM 有通称作为“门”的结构来去除或者增加信息到细胞状态的能力。门是一种让信息选择式通过的方法。他们包含一个 sigmoid 神经网络层和一个 pointwise 乘法操作。示意图如下:
LSTM 拥有三个门,分别是遗忘门,输入层门和输出层门,来保护和控制细胞状态。
忘记层门
作用对象:细胞状态 。
作用:将细胞状态中的信息选择性的遗忘。
操作步骤:该门会读取ht−1和xt,输出一个在 0 到 1 之间的数值给每个在细胞状态Ct−1中的数字。1 表示“完全保留”,0 表示“完全舍弃”。示意图如下:
输入层门
作用对象:细胞状态
作用:将新的信息选择性的记录到细胞状态中。
操作步骤:
步骤一,sigmoid 层称 “输入门层” 决定什么值我们将要更新。
步骤二,tanh 层创建一个新的候选值向量C~t加入到状态中。其示意图如下:
步骤三:将ct−1更新为ct。将旧状态与ft相乘,丢弃掉我们确定需要丢弃的信息。接着加上it∗C~t得到新的候选值,根据我们决定更新每个状态的程度进行变化。其示意图如下:
输出层门
作用对象:隐层ht
作用:确定输出什么值。
操作步骤:
步骤一:通过sigmoid 层来确定细胞状态的哪个部分将输出。
步骤二:把细胞状态通过 tanh 进行处理,并将它和 sigmoid 门的输出相乘,最终我们仅仅会输出我们确定输出的那部分。其示意图如下所示:
3.4 生成对抗网络 – GANs
GAN网络结构是由生成器和判别器组成,训练过程中,生成器G不断的生成赝品,判别器D这识别生成器G生成的结果是真品还是赝品,两个网络相互对抗,生成器G努力生成出欺骗过判别器D的赝品,而判别器D努力识别出生成器G生成的赝品,往复循环,从而训练彼此。
3.5 深度强化学习 – RL
强化学习是机器学习的一个重要分支,是多学科多领域交叉的一个产物,它的本质是解决 decision making 问题,即自动进行决策,并且可以做连续决策。
它主要包含四个元素,agent,环境状态,行动,奖励, 强化学习的目标就是获得最多的累计奖励。
我们列举几个形象的例子:
小孩想要走路,但在这之前,他需要先站起来,站起来之后还要保持平衡,接下来还要先迈出一条腿,是左腿还是右腿,迈出一步后还要迈出下一步。
小孩就是 agent,他试图通过采取行动(即行走)来操纵环境(行走的表面),并且从一个状态转变到另一个状态(即他走的每一步),当他完成任务的子任务(即走了几步)时,孩子得到奖励(给巧克力吃),并且当他不能走路时,就不会给巧克力。
常见优化方法总结:
其他方法:
一种通过计算梯度,并且将损失将至最低的技术,它以训练数据位条件,来计算损失相对于模型参数的梯度。梯度下降法以迭代方式调整参数,逐渐找到权重和偏差的最佳组合,从而将损失降至最低。
梯度下降法在大数据集,会出现费时、价值不高等情况。如果我们可以通过更少的计算量得出正确的平均梯度,效果更好。通过从数据集中随机选择样本,来估算出较大的平均值。
随机梯度下降原理: 它每次迭代只使用一个样本(批量大小为1)。如果进行足够的迭代,SGD也可以发挥作用,但过程会非常杂乱。“随机”这一术语表示构成各个批量的一个样本都是随机选择的。
它是介于全批量迭代与随机选择一个迭代的折中方案。全批量迭代(梯度下降法);随机选择一个迭代(随机梯度下降)。
批量梯度下降原理: 它从数据集 随机选取一部分样本,形成小批量样本,进行迭代。小批量通常包含10-1000个随机选择的样本。BGD可以减少SGD中的杂乱样本数量,但仍然波全批量更高效。
常见激活函数总结:
对应图像如下:
四、 深度学习实战:
4.1 深度学习步骤
深度学习流程通常包括以下几个主要的步骤:
4.2 手写数字识别实战
本实战使用多层感知器训练模型,预测手写数字图片具体数字。
Step1:准备数据。
(1)数据集介绍
MNIST数据集包含60000个训练集和10000测试数据集。分为图片和标签,图片是28*28的像素矩阵,标签为0~9共10个数字。
(2)train_reader和test_reader
paddle.dataset.mnist.train()和test()分别用于获取mnist训练集和测试集
paddle.reader.shuffle()表示每次缓存BUF_SIZE个数据项,并进行打乱
paddle.batch()表示每BATCH_SIZE组成一个batch
BUF_SIZE=512
BATCH_SIZE=128
#用于训练的数据提供器,每次从缓存中随机读取批次大小的数据
train_reader = paddle.batch(
paddle.reader.shuffle(paddle.dataset.mnist.train(),
buf_size=BUF_SIZE),
batch_size=BATCH_SIZE)
#用于训练的数据提供器,每次从缓存中随机读取批次大小的数据
test_reader = paddle.batch(
paddle.reader.shuffle(paddle.dataset.mnist.test(),
buf_size=BUF_SIZE),
batch_size=BATCH_SIZE)
#用于打印,查看mnist数据
train_data=paddle.dataset.mnist.train();
sampledata=next(train_data())
print(sampledata)
img = np.resize(sampledata[0], (28, 28))
plt.imshow(img)
Step2.网络配置
以下的代码判断就是定义一个简单的多层感知器,一共有三层,两个大小为100的隐层和一个大小为10的输出层,因为MNIST数据集是手写0到9的灰度图像,类别有10个,所以最后的输出大小是10。最后输出层的激活函数是Softmax,所以最后的输出层相当于一个分类器。加上一个输入层的话,多层感知器的结构是:输入层–>>隐层–>>隐层–>>输出层。
# 定义全连接神经网络
def multilayer_perceptron(img):
# 第一个全连接层,激活函数为ReLU
hidden1 = fluid.layers.fc(input=img, size=100, act='relu')
# 第二个全连接层,激活函数为ReLU
hidden2 = fluid.layers.fc(input=hidden1, size=100, act='relu')
# 以softmax为激活函数的全连接输出层,输出层的大小必须为数字的个数10
prediction = fluid.layers.fc(input=hidden2, size=10, act='softmax')
return prediction
# 定义卷积神经网络
def convolution_network(img):
# 第一个卷积-池化层
conv_pool_1 = fluid.nets.simple_img_conv_pool(
input=img, # 输入图像
filter_size=5, # 滤波器的大小
num_filters=20, # filter 的数量。它与输出的通道相同
pool_size=2, # 池化核大小2*2
pool_stride=2, # 池化步长
act="relu") # 激活类型
conv_pool_1 = fluid.layers.batch_norm(conv_pool_1)
# 第二个卷积-池化层
conv_pool_2 = fluid.nets.simple_img_conv_pool(
input=conv_pool_1,
filter_size=5,
num_filters=50,
pool_size=2,
pool_stride=2,
act="relu")
# 以softmax为激活函数的全连接输出层,10类数据输出10个数字
prediction = fluid.layers.fc(input=conv_pool_2, size=10, act='softmax')
return prediction
Step3.模型训练
(1)创建训练的Executor
首先定义运算场所 fluid.CPUPlace()和 fluid.CUDAPlace(0)分别表示运算场所为CPU和GPU
Executor:接收传入的program,通过run()方法运行program。
# 定义使用CPU还是GPU,使用CPU时use_cuda = False,使用GPU时use_cuda = True
use_cuda = False
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
# 获取测试程序
test_program = fluid.default_main_program().clone(for_test=True)
exe = fluid.Executor(place)
exe.run(fluid.default_startup_program())
绘制训练变化图像:
all_train_iter=0
all_train_iters=[]
all_train_costs=[]
all_train_accs=[]
def draw_train_process(title,iters,costs,accs,label_cost,lable_acc):
plt.title(title, fontsize=24)
plt.xlabel("iter", fontsize=20)
plt.ylabel("cost/acc", fontsize=20)
plt.plot(iters, costs,color='red',label=label_cost)
plt.plot(iters, accs,color='green',label=lable_acc)
plt.legend()
plt.grid()
plt.show()
Step4.模型评估
训练并保存模型
训练需要有一个训练程序和一些必要参数,并构建了一个获取训练过程中测试误差的函数。必要参数有executor,program,reader,feeder,fetch_list。
executor表示之前创建的执行器
program表示执行器所执行的program,是之前创建的program,如果该项参数没有给定的话则默认使用defalut_main_program
EPOCH_NUM=2
model_save_dir = "/home/aistudio/work/hand.inference.model"
for pass_id in range(EPOCH_NUM):
# 进行训练
for batch_id, data in enumerate(train_reader()): #遍历train_reader
train_cost, train_acc = exe.run(program=fluid.default_main_program(),#运行主程序
feed=feeder.feed(data), #给模型喂入数据
fetch_list=[avg_cost, acc]) #fetch 误差、准确率
all_train_iter=all_train_iter+BATCH_SIZE
all_train_iters.append(all_train_iter)
all_train_costs.append(train_cost[0])
all_train_accs.append(train_acc[0])
# 每200个batch打印一次信息 误差、准确率
if batch_id % 200 == 0:
print('Pass:%d, Batch:%d, Cost:%0.5f, Accuracy:%0.5f' %
(pass_id, batch_id, train_cost[0], train_acc[0]))
# 进行测试
test_accs = []
test_costs = []
#每训练一轮 进行一次测试
for batch_id, data in enumerate(test_reader()): #遍历test_reader
test_cost, test_acc = exe.run(program=test_program, #执行训练程序
feed=feeder.feed(data), #喂入数据
fetch_list=[avg_cost, acc]) #fetch 误差、准确率
test_accs.append(test_acc[0]) #每个batch的准确率
test_costs.append(test_cost[0]) #每个batch的误差
# 求测试结果的平均值
test_cost = (sum(test_costs) / len(test_costs)) #每轮的平均误差
test_acc = (sum(test_accs) / len(test_accs)) #每轮的平均准确率
print('Test:%d, Cost:%0.5f, Accuracy:%0.5f' % (pass_id, test_cost, test_acc))
#保存模型
# 如果保存路径不存在就创建
if not os.path.exists(model_save_dir):
os.makedirs(model_save_dir)
print ('save models to %s' % (model_save_dir))
fluid.io.save_inference_model(model_save_dir, #保存推理model的路径
['image'], #推理(inference)需要 feed 的数据
[predict], #保存推理(inference)结果的 Variables
exe) #executor 保存 inference model
print('训练模型保存完成!')
draw_train_process("training",all_train_iters,all_train_costs,all_train_accs,"trainning cost","trainning acc")
Pass:0, Batch:0, Cost:4.67661, Accuracy:0.07812
Pass:0, Batch:200, Cost:0.13896, Accuracy:0.95312
Pass:0, Batch:400, Cost:0.09557, Accuracy:0.97656
Test:0, Cost:0.08205, Accuracy:0.97350
Pass:1, Batch:0, Cost:0.10392, Accuracy:0.96875
Pass:1, Batch:200, Cost:0.08495, Accuracy:0.97656
Pass:1, Batch:400, Cost:0.05935, Accuracy:0.98438
Test:1, Cost:0.06121, Accuracy:0.97973
save models to /home/aistudio/work/hand.inference.model
训练模型保存完成!
最终模型训练图像:
Step5.模型预测
图像预处理:
def load_image(file):
im = Image.open(file).convert('L') #将RGB转化为灰度图像,L代表灰度图像,像素值在0~255之间
im = im.resize((28, 28), Image.ANTIALIAS) #resize image with high-quality 图像大小为28*28
im = np.array(im).reshape(1, 1, 28, 28).astype(np.float32)#返回新形状的数组,把它变成一个 numpy 数组以匹配数据馈送格式。
# print(im)
im = im / 255.0 * 2.0 - 1.0 #归一化到【-1~1】之间
return im
推理:
infer_exe = fluid.Executor(place)
inference_scope = fluid.core.Scope()
预测:
# 加载数据并开始预测
with fluid.scope_guard(inference_scope):
#获取训练好的模型
#从指定目录中加载 推理model(inference model)
[inference_program, #推理Program
feed_target_names, #是一个str列表,它包含需要在推理 Program 中提供数据的变量的名称。
fetch_targets] = fluid.io.load_inference_model(model_save_dir,#fetch_targets:是一个 Variable 列表,从中我们可以得到推断结果。model_save_dir:模型保存的路径
infer_exe) #infer_exe: 运行 inference model的 executor
img = load_image(infer_path)
results = infer_exe.run(program=inference_program, #运行推测程序
feed={feed_target_names[0]: img}, #喂入要预测的img
fetch_list=fetch_targets) #得到推测结果,
# 获取概率最大的label
lab = np.argsort(results) #argsort函数返回的是result数组值从小到大的索引值
#print(lab)
print("该图片的预测结果的label为: %d" % lab[0][0][-1]) #-1代表读取数组中倒数第一列
预测结果:该图片的预测结果的label为: 0
五、深度学习导图总结分享
以下是作者自己整理的深度学习笔记思维导图,这里免费分享供大家学习,目前导图和笔记是初版,后续会继续更新。
链接:app.yinxiang.com/fx/010adf6f…
本文参考及优秀资料推荐: