一文开启深度学习之旅
Posted 盼小辉丶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文开启深度学习之旅相关的知识,希望对你有一定的参考价值。
1. 深度学习引言
近年来,深度学习 (Deep Learning
, DL
) 在多个领域中都取得了突破性进展,尤其是在图像识别、目标检测以及自然语言处理等领域。深度学习的相关内容并非一篇或几篇博客能够详尽的介绍完整,本文的目的也并非介绍所有深度学习概念与模型。本文的主要目的是通过介绍深度学习存在的必要性以及相关概念,来快速入门深度学习,感受深度学习的强大魅力。
在本文中,我们将学习经典神经网络架构以及网络内部使用的网络层类型。我们还将构建一个基于全连接网络的线性回归模型,并学习如何使用全连接神经网络构建图像分类模型。
同时,我们还将了解一类重要的网络架构——卷积神经网络 (Convolutional Neural Network
, CNN
),并使用 CNN
构建图像分类模型。图像分类模型有很多应用,但本质上它只是计算机辨别图像中物体的过程。例如,可以构建一个模型来确定图片中的动物是猫还是狗。
除了 CNN
外,还有其他许多深度学习模型,包括经常用于文本处理的循环神经网络、长短时记忆网络,用于处理图数据的图神经网络等。限于篇幅,本文主要介绍 CNN
。
2. 深度神经网络基础
我们首先简单回顾下神经网络的工作原理,神经网络由具有权重和偏置的人工神经元组成,这些权重和偏置会在模型训练过程中进行调整,以得到一个性能优异的学习模型。每个神经元可以接收一组输入,以某种方式对其进行处理后,输出一个值。关于神经网络更详细的介绍,可以参考《神经网络基础》。
如果我们通过堆叠多层的神经网络,它就被称为深度神经网络,处理这些深度神经网络的人工智能分支称为深度学习
。
2.1 从传统神经网络到卷积神经网络
传统全连接神经网络的主要缺点之一是它们忽略了输入数据的结构,所有数据在输入网络之前都被转换为一维数组。这对于简单的数字数据而言,可能并没有什么问题,但当我们处理图像数据时,全连接网络就表现出不足之处。以灰度图像为例,这些图像是二维结构,同时像素的空间排列包含很多隐藏信息。如果我们忽略这些信息,而将图片转换为一维结构,我们将失去很多潜在信息。而这也正是卷积神经网络 (Convolutional Neural Network
, CNN
) 的优势所在,CNN
在处理图像时会考虑图像的 2D 结构。
CNN
也是由权重和偏差组成的神经元组成,这些神经元接受输入数据,进行处理后,输出处理后的值。网络的目标是从输入层的原始图像数据到得到输出层的正确正确结果,不同任务中,网络的目标并不相同:在图像分类中,网络的目标是得到图片类别;在目标检测中,网络的目标是定位目标的位置。普通全连接神经网络和 CNN
之间的区别在于使用的神经网络层类型以及我们如何处理输入数据,假设 CNN
的输入是图像,那么可以使用 CNN
提取特图像的特征。除此之外,CNN
的输入并不仅限于图像,也可以为文本等数据。这使得 CNN 在处理图像方面更加高效。关于 CNN
内部的计算细节,在此不再展开,感兴趣的话,可以参考《卷积神经网络详解与实现》。了解了 CNN
的基础知识后,我们继续学习如何构建 CNN
。
2.2 卷积神经网络架构
当我们使用全连接神经网络时,我们需要将输入数据转换为单个向量作为神经网络的输入,然后通过神经网络中的各个层。在这些层中,每个神经元都连接到前一层中的所有神经元,而每一层内的神经元相互间并无连接,它们只连接到相邻层的神经元。网络中的最后一层是输出层,它表示最终的输出。
如果我们将这种结构用于图像,其中的参数量将迅速增长。例如,考虑一个尺寸为 256×256
的 RGB
图像数据集,由于 RGB
图像具有 3
个通道,因此输入层的单个神经元就有
256
×
256
×
3
=
196608
256 \\times 256 \\times 3 = 196608
256×256×3=196608 个参数。由于每一层都会有多个神经元,因此参数的数量也会迅速增加,这将导致在训练过程中会调整大量参数,训练过程将变得十分复杂和耗时。这种将每个神经元连接到相邻层中的每个神经元的全连接网络,显然在处理大规模图像数据集时是不可行的。
而 CNN
在处理数据时会明确考虑图像的结构,CNN
中的神经元具有 3
个维度——宽度、高度和深度。当前层中的每个神经元都连接到前一层输出的一小部分,这就像在输入图像上叠加一个 NxN
的滤波器,或者称为卷积核
。这大大的减少了权重的数量,以尺寸为
3
×
3
3\\times 3
3×3 的卷积核为例,当图像通道为 3
时每个卷积核仅包含
3
×
3
×
3
=
27
3\\times3\\times3=27
3×3×3=27 个参数。
由于单个卷积核无法捕获图像的所有细微差别,因此我们可以使用 M
个卷积核提取图像特征。如果可视化这些卷积核的输出,我们可以看到它们 CNN
靠近输入的卷积核提取了边缘、纹理等特征。随着我们通过网络层的加深,后面的卷积层将提取图片中更高级别的特征。
CNN
是一种经典的深度学习网络,它通常用于图像识别等任务。与任何其他神经网络一样,为图像中的元素分配权重和偏置,并能够将这些元素彼此区分开来。与其他分类模型相比,CNN
中所需使用的数据预处理较少。
CNN
架构的基本形式可以比作人脑中的神经元和树突,它的灵感来自视觉皮层。单个神经元只对视野受限区域的刺激作出反应,这个视野区域被称为感受野 (Receptive Field
),这些感受野相互重叠后,覆盖了整个视野范围。
2.3 全连接神经网络与卷积神经网络对比
图像是像素值的矩阵,传统全连接网络通常需要在输入网络前,将图像展平。例如,将 10x10
的图像展平为 100x1
的矢量,然后使用这个展平后的图像作为全连接神经网络的输入。当使用简单的二值(黑白)图像作为输入时,此方法可能会在执行类别预测时有一定的效果,但在涉及像素间依赖性较强的复杂图像时此类模型基本失效。
为什么将输图像展平会导致模型性能不佳?让我们分析以下简单示例,假设存在一个包含人物面部图像,我们的大脑可以瞬间处理图像并意识到它包含人物面部信息:
但,如果我们把该图像展平,我们是否还能轻易识别图像内容?
这一任务将变得不再容易,尽管如此它们包含相同的信息。同样,当使用全连接神经网络而不是 CNN
时,也面临类似的问题,像素在空间中的联系信息由于展平操作而丢失了,CNN
可以通过应用卷积核来捕获图像中的空间和时间依赖性。由于使用 CNN
可以大幅减少参数数量和且权重可以被重用,因此,CNN
架构在大型数据集上的性能表现优异,得到了快速发展。
2.4 经典卷积神经网络组成
CNN
中通常包含以下类型的神经网络层:
- 输入层——获取原始图像数据
- 卷积层——计算卷积核和输入图像的卷积结果,卷积层可以理解为计算权重和前一层输出中的一个小块之间的点积,更详细的计算细节,可以参考《卷积神经网络详解与实现》
- 激活层——将激活函数应用于前一层的输出,确切而言,它并不算独立的一个网络层,使用激活函数来为网络添加非线性,以便它可以拟合任何类型的函数,有许多不同类型的激活函数,可以参考《深度学习中常用激活函数详解》
- 池化层——该层对前一层的输出进行下采样,从而形成一个尺寸更小的特征图,随着网络中的加深,池化用于只保留显著特征,池化层经常使用最大池化——在给定的
KxK
窗口中选择保留最大值 - 全连接层——该层计算最后一层的输出,在图像识别中,其结果输出的大小为
1x1xC
,其中C
是训练数据集中的类别数
图像分类任务中,图像从网络中的输入层到输出层的过程中,输入图像从像素值转换为最终的类概率分数。由于 CNN
研究的活跃性,有许多不同的 CNN
架构被提出,包括 ResNet
、DenseNet
,InceptionNet
等等。卷积神经网络模型的准确性和鲁棒性取决于许多因素——网络层的类型、网络的深度、网络内各种层的排列方式、为每一层的使用的激活函数、训练数据等等,可以参考《神经网络性能优化技术详解》,了解不同超参数对模型性能的影响。
3. 使用神经网络实现线性回归
在构建 CNN
之前,我们使用一个更基本的模型来搭建了解神经网络,以深入了解 CNN
如何改进全连接网络模型性能。在本节中,我们将学习如何使用全连接网络构建线性回归模型。我们可以在《监督学习》中更详细的了解线性回归,本节将使用神经网络方法构建线性回归模型。
我们将在本节中使用 Keras
构建神经网络模型,Keras
是一种流行的深度学习框架,该库提供了很多实用工具,可以简化构建复杂神经网络的过程。在本节中,我们将熟悉它的工作原理。安装方法可以参考《使用 Keras 构建神经网络》
安装成功后,导入以下所需包:
import numpy as np
import matplotlib.pyplot as plt
import keras
from keras.layers import Dense
我们将生成一些数据样本,同时在数据中添加噪声,并学习使用这些数据样本训练模型。首先,定义要生成的数据样本的数量:
num_points = 1200
定义将用于生成数据的参数,使用直线模型: y = a x + b y = ax + b y=ax+b:
data = []
a = 0.2
b = 0.5
for i in range(num_points):
x = np.random.normal(0.0, 0.8)
在数据中添加噪声,以允许数据中包含一些异常值:
noise = np.random.normal(0.0, 0.04)
根据以上公式计算 y y y 的值:
y = a*x + b + noise
data.append([x, y])
迭代生成数据样本后,将数据分成输入和输出变量:
x_data = [d[0] for d in data]
y_data = [d[1] for d in data]
绘制数据,以观察数据:
plt.plot(x_data, y_data, 'o')
plt.title('Input data')
plt.show()
实例化一个可以顺序计算的神经网络模型,可以在其中堆叠添加多个网络层,计算过程按网络层的堆叠顺序进行。Sequential
方法能够构建顺序计算模型:
model = keras.models.Sequential()
向模型添加一个 Dense
层(全连接层)作为输出层,也就是说,我们使用的网络仅包含一个输入层和一个输出层,且输出层中只包含一个神经元:
model.add(Dense(1, input_shape=(1,)))
在使用前面的代码初始化的 Dense
层中,需要确保为模型提供输入形状(由于这是第一个全连接层,因此需要指定模型期望的接受的数据形状),输出层中包含一个神经元。
可以将模型概要信息 (model summary
) 可视化输出:
model.summary()
可以看到模型概要信息如下所示:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 1) 2
=================================================================
Total params: 2
Trainable params: 2
Non-trainable params: 0
_________________________________________________________________
从模型概要信息可以看到,网络中共有 2
个参数(一个权重和一个偏置项)。
编译模型。首先,需要定义损失函数和优化器,以及优化器相对应的学习率:
from keras.optimizers import SGD
sgd = SGD(lr=0.01)
上述代码指定优化器是随机梯度下降,学习率为 0.01
。将预定义的优化器及其相应的学习率、损失函数作为参数传递给 compile
方法编译模型:
model.compile(optimizer=sgd,loss='mean_squared_error')
- 拟合模型。更新权重,以优化模型:
for i in range(20):
history = model.fit(x_data, y_data, epochs=1, batch_size = 50, verbose=1)
fit
方法需要接收一个输入 x
和相应的实际值 y
,epochs
代表训练数据集的次数,batch_size
代表每次更新权重的迭代中训练的数据量大小,verbose
指定训练过程中的输出信息,可以包含有关训练和测试数据集上损失值以及模型训练的进度等信息。在以上代码中,我们指定在每次迭代中,训练模型一次 epochs=1
。
在每次迭代中,打印模型训练过程中的相关信息:
history_dict = history.history
print('\\nITERATION', i+1)
print('W =', model.get_weights()[0][0])
print('b =', model.get_weights()[1])
print('loss =', history_dict['loss'])
可视化数据集,并显示每次训练过程得到的预测回归线:
plt.plot(x_data, y_data, 'o')
plt.plot(x_data, model.get_weights()[0][0] * x_data + model.get_weights()[1])
plt.xlabel('Dimension 0')
plt.ylabel('Dimension 1')
plt.title('Iteration ' + str(i+1) + ' of ' + str(20))
plt.show()
观察训练过程中生成的图片,可以看到,在第一个 epoch
时,生成的回归线效果较差:
模型继续训练,随着训练的增加,可以看到得到的回归线越来越接近实际情况,下图是训练 8
个 epoch
时得到的结果:
当模型训练完成后,得到的最终回归线如下所示:
完成训练后,可以得到类似以下输入的训练过程内容:
ITERATION 1
W = [0.5017756]
b = [0.18768263]
loss = [0.23922152817249298]
24/24 [==============================] - 0s 862us/step - loss: 0.1069
ITERATION 2
W = [0.42391354]
b = [0.30439818]
loss = [0.10692004859447479]
24/24 [==============================] - 0s 797us/step - loss: 0.0497
...
ITERATION 8
W = [0.23639818]
b = [0.48727533]
loss = [0.0030727821867913008]
24/24 [==============================] - 0s 807us/step - loss: 0.0024
...
ITERATION 12
W = [0.2106886]
b = [0.49783146]
loss = [0.0017689659725874662]
24/24 [==============================] - 0s 861us/step - loss: 0.0017
...
ITERATION 19
W = [0.20126395]
b = [0.49975297]
loss = [0.0016625870484858751]
24/24 [==============================] - 0s 860us/step - loss: 0.0017
ITERATION 20
W = [0.20092124]
b = [0.49968114]
loss = [0.0016619376838207245]
可以看到随着训练的增加
w
w
w 和
b
b
b 的值是不断得到调整的,还可以看到损失值在不断减少,最后的
w
w
w 和
b
b
b 值与我们设定的 a
和 b
值十分接近 。
4. 使用全连接神经网络识别手写数字
我们已经学习了神经网络的基础概念,同时也了解了如何使用 keras
库构建神经网络模型,本节我们将更进一步,通过实现一个实用模型来一窥神经网络的强大性能。
我们将训练全连接神经网络模型预测 MNIST
数据集中的数字标签,MNIST
数据集是十分常用的数据集,数据集由来自 250
个不同人手写的数字构成,其中训练集包含 60000
张图片,测试集包含 10000
张图片,每个图片都有其标签,图片大小为 28*28
。
导入相关的包和数据集,并可视化数据集以了解数据情况:
from keras.datasets import mnist
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.utils import np_utils
import matplotlib.pyplot as plt
(x_train, y_train), (x_test, y_test) = mnist.load_data()
在前面的代码中,导入相关的 Keras
方法和 MNIST
数据集。MNIST
数据集中图像的形状为 28 x 28
,绘制数据集中的一些图像,以更好的了解数据集:
plt.subplot(221)
plt.imshow(x_train[0], cmap='gray')
plt.subplot(222)
plt.imshow(x_train[1], cmap='gray')
plt.subplot(223)
plt.imshow(x_test[0], cmap='gray')
plt.subplot(224)
plt.imshow(x_test[1], cmap='gray')
plt.show()
下图显示了以上代码的输出:
展平 28 x 28
图像,以便将输入变换为一维的 784
个像素值,并将其馈送至 Dense
层中。此外,需要将标签变换为独热编码。此步骤是数据集准备过程中的关键:
num_pixels = x_train.shape[1] * x_train.shape[2]
x_train = x_train.reshape(-1, num_pixels).astype('float32')
x_test = x_test.reshape(-1, num_pixels).astype('float32')
在上示代码中,使用 reshape
方法对输入数据集进行形状变换,np.reshape()
将给定形状的数组转换为不同的形状。在此示例中,x_train
数组具有 x_train.shape[0]
个数据点(图像),每个图像中都有 x_train.shape[1]
行和 x_train.shape[2]
列, 我们将其形状变换为具有 x_train.shape[0]
个数据,每个数据具有 x_train.shape [1] * x_train.shape[2]
个值的数组。
接下来,我们将标签数据编码为独热向量:
y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]
我们简单了解下独热编码的工作原理。假设有一数据集的可能标签为 apple, orange, banana, lemon, pear
,如果我们将相应的标签转换为独热编码,则如下所示:
类别 | 索引0 | 索引1 | 索引2 | 索引3 | 索引4 |
---|---|---|---|---|---|
apple | 1 | 0 | 0 | 0 | 0 |
orange | 0 | 1 | 0 | 0 | 0 |
banana | 0 | 0 | 1 | 0 | 0 |
lemon | 0 | 0 | 0 | 1 | 0 |
pear | 0 | 0 | 0 | 0 | 1 |
每个独热向量含有
n
n
n 个数值,其中
n
n
n 为可能的标签数,且仅有标签对应的索引处的值为 1 外,其他所有值均为 0
。如上所示,apple
的独热编码可以表示为 [1, 0, 0, 0, 0]
。在 Keras
中,使用 to_categorical
方法执行标签的独热编码,该方法找出数据集中唯一标签的数量,然后将标签转换为独热向量。
用具有 1000
个节点的隐藏层构建神经网络:
model = Sequential()
model.add(Dense(1000, input_dim=num_pixels, activation='relu'))
model.add(Dense(num_classes, activation='softmax'))
输入具有 28×28=784
个值,这些值与隐藏层中的 1000
个节点单元相连,指定激活函数为 ReLU
。最后,隐藏层连接到具有 num_classes=10
个值的输出 (有十个可能的图像标签,因此 to_categorical
方法创建的独热向量有 10
列),在输出的之前使用 softmax
激活函数,以便获得图像的类别概率。上述模型架构信息可视化如下所示:
model.summary()
架构信息输出如下:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 1000) 785000
_________________________________________________________________
dense_1 (Dense) (None, 10) 10010
=================================================================
Total params: 795,010
Trainable params: 795,010
Non-trainable params: 0
_________________________________________________________________
在上述体系结构中,第一层的参数数量为 785000
,因为 784
个输入单元连接到 1000
个隐藏层单元,因此在隐藏层中包括 784 * 1000
权重值加 1000
个偏置值,总共 785000
个参数。类似地,输出层有 10
个输出,分别连接到 1000
个隐藏层,从而产生 1000 * 10
个权重和 10
个偏置(总共 10010
个参数)。输出层有 10
个节点单位,因为输出中有 10
个可能的标签,输出层为我们提供了给定输入图像的属于每个类别的概率值,例如第一节点单元表示图像属于 0
的概率,第二个单元表示图像属于 1
的概率,以此类推。
编译模型如下:
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])
因为目标值是包含多个类别的独热编码矢量,所以损失函数是多分类交叉熵损失。此外,我们使用 Adam
优化器来最小化损失函数,在训练模型时,监测准确率 (accuracy
,可以简写为 acc
) 指标。
拟合模型,如下所示:
history = model.fit(x_train, y_train,
validation_data=(x_test, y_test),
epochs=50,
batch_size=64,
verbose=1)
上述代码中,我们指定了模型要拟合的输入 (x_train
) 和输出 (y_train
);指定测试数据集的输入和输出,模型将不会使用测试数据集来训练权重,但是,它可以用于观察训练数据集和测试数据集之间的损失值和准确率有何不同。
以上是关于一文开启深度学习之旅的主要内容,如果未能解决你的问题,请参考以下文章
一文带你玩转深度学习:神经网络基础知识环境配置theanoTensorFlow