LeNet - Python中的卷积神经网络

时间:2021-04-03 19:35:05

本教程将  主要面向代码,  旨在帮助您 深入学习和卷积神经网络。由于这个意图,我  不会花很多时间讨论激活功能,池层或密集/完全连接的层
- 将来会有  很多教程在PyImageSearch博客上将覆盖  每个层类型/概念  在很多细节。

再次,本教程是您  第一个端到端的例子,您可以训练一个现实的CNN(并在实际中看到它)。我们将在本系列帖子中稍后介绍激活功能,汇集层和完全连接层的细节(尽管您应该已经知道卷积运算的基本知识); 但是在此期间,只需跟随,享受教训,并  学习如何使用Python和Keras实现您的第一个卷积神经网络。

MNIST数据集

LeNet - Python中的卷积神经网络

图1: MNIST数字识别数据集。

您可能已经看过MNIST数据集,无论是在PyImageSearch博客上还是在您研究的其他地方。在任何一种情况下,我将继续查看数据集,以确保您  准确了解我们正在使用的数据。

MNIST数据集可以说是计算机视觉和机器学习文献中研究最多,数据最多的数据集,它是您在深入学习之旅中使用的出色的“第一个数据集”。

注意:正如我们将会发现的那样,即使在CPU上,在这个数据集上获得>
98%的分类精度也很容易,训练时间最短。

该数据集的目标是对手写数字0-9进行分类。我们共获得了7万张图像,通常有6万张图像用于培训,10,000张用于评估; 但是,我们可以*分割这些数据,因为我们认为合适。共同分拆包括标准60,000
/ 10,000,75%/ 25%和66.6%/ 33.3%。我将在博客文章中使用2/3的数据进行培训和1/3的数据进行测试。

每个数字表示为  28 x 28  灰度图像(来自MNIST数据集的示例可以在上图中看到)。这些灰度像素强度是无符号整数,像素值落在[0,255]的范围内  。 所有数字都放置在  黑色背景上 ,具有浅色前景(即,数字本身)为  白色和  各种灰色。

值得注意的是,许多库(如scikit-learn)都有内置的帮助方法来下载MNIST数据集,将其缓存到磁盘上,然后加载它。这些帮助方法通常将每个图像表示为  784-d向量。

784号来自哪里?

简单。这只是  平坦的  28
x 28 = 784的形象。

要从784-d矢量恢复我们的原始图像,我们简单地将阵列重塑为  28×28 图像。

在本博客的上下文中,我们的目标是培训LeNet,以便我们最大限度地提高我们的测试集的准确性。

LeNet架构

LeNet - Python中的卷积神经网络

图2: LeNet架构由两组卷积,激活和合并层组成,后面是完全连接的层,激活,另一个完全连接,最后是一个softmax分类器(图像源)。

LeNet架构是卷积神经网络的一个很好的“第一架构”(特别是在MNIST数据集上进行了培训时,手写数字识别的图像数据集)。

LeNet很小,易于理解 - 但足够大,可以提供有趣的结果。此外,LeNet
+ MNIST的组合能够在CPU上运行,使初学者能够轻松地在深度学习和卷积神经网络中迈出第一步。

在许多方面,LeNet
+ MNIST是“Hello,World”等同于Deep
Learning的图像分类。

LeNet架构由以下层组成:

LeNet
- Convolutional Neural Network in Python

Shell

1
INPUT => CONV => RELU => POOL => CONV => RELU => POOL => FC => RELU => FC

而不是解释每层的卷积过滤器数量,过滤器本身的大小以及现在完全连接节点的数量,我将保存这个讨论,直到我们的  “使用Python和Keras实现LeNet”  部分博客文章,其中的源代码将作为辅助解除。

同时,我们来看看我们的项目结构 - 一个结构,我们将  在以后的PyImageSearch博客文章中多次重用。

注意:原来的LeNet架构使用 TANH   激活功能而不是 RELU  。我们  在这里使用RELU的原因是因为它有更好的分类精度,因为一些很好的,理想的属性(我将在未来的博客文章中讨论)。如果您在LeNet中进行任何其他讨论,您可能会看到他们使用 TANH,   而不是再想一想。

我们的CNN项目结构

在我们潜入任何代码之前,我们先来看看我们的项目结构:

LeNet - Convolutional Neural Network in Python

Shell

1
2
3
4
5
6
7
8
9
|---
output
|---
pyimagesearch
|    |---
__init__.py
|    |---
cnn
|    |    |---
__init__.py
|    |    |---
networks
|    |    |    |---
__init__.py
|    |    |    |---
lenet.py
|---
lenet_mnist.py

为了保持代码的组织,我们将定义一个名为pyimagesearch的包  。在 pyimagesearch  
模块中,我们将创建一个 cnn  
子模块 - 这是我们将存储卷积神经网络实现的地方,以及与CNN相关的任何帮助实用程序。

看看cnn里面  ,你会看到 网络  
子模块:这是  网络实现本身将被存储的地方。顾名思义,这一点。py  
文件将定义一个名为LeNet的类  ,这是我们在Python
+ Keras中实际的LeNet实现。

该 lenet_mnist 。py  
脚本将是我们的驱动程序,用于实例化LeNet网络架构,训练模型(或加载模型,如果我们的网络是预先训练的),然后评估MNIST数据集上的网络性能。

最后, 输出  
目录将在我们的LeNet  
模型训练完成后存储 ,从而允许我们在后续调用lenet_mnist  时对数字进行分类 。py,而不必重新训练网络。

过去一年,我个人一直在使用这个项目结构(或项目结构非常相似)。 我发现它很有条理,易于扩展
- 随着我们用更多的网络架构和帮助功能添加到这个库中,这将在未来的博客文章中变得更加明显。

用Python和Keras实现LeNet

首先,我会假设你已经有Keras,scikit学习,和OpenCV的系统上安装(和可选,启用GPU支持)。

否则,打开 礼物。py  
文件并插入以下代码:

LeNet
- Convolutional Neural Network in Python

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
# import the necessary packages
from keras.models import Sequential
from keras.layers.convolutional import Convolution2D
from keras.layers.convolutional import MaxPooling2D
from keras.layers.core import Activation
from keras.layers.core import Flatten
from keras.layers.core import Dense
 
class LeNet:
@staticmethod
def build(width, height, depth, classes, weightsPath=None):
# initialize the model
model = Sequential()

第2-7行处理从keras   库导入所需的函数/类 。

所述 LeNet   类被定义在  第9行,其次是 构建   于方法  11号线。每当我定义一个新的网络架构,我  总是将它放在自己的类中(主要用于命名空间和组织目的),然后创建一个  静态 构建   函数。

该 构建   方法,顾名思义,需要提供的任何参数,其在  最低限度 包括:

  • 输入图像的  宽度。
  • 输入图像的  高度。
  • 输入图像的  深度(即通道数)。
  • 和数量  班在我们的数据集(即类标签的唯一的号码)。

我通常还包括一个  可用于加载预训练模型的 权值路径。给定这些参数, 构建   函数负责构建网络架构。

谈到构建LeNet架构时,  第13行将实例化一个 Sequential   类,我们将用它来构建网络。

现在模型已初始化,我们可以开始添加图层:

LeNet - Convolutional Neural Network in Python

Python

15
16
17
18
19
#
first set of CONV => RELU => POOL
model.add(Convolution2D(20,
5,
5,
border_mode="same",
input_shape=(depth,
height,
width)))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2,
2),
strides=(2,
2)))

在  第15-19行,我们创建了第一组 CONV = > RELU = > POOL  
图层集。

我们的 CONV  
层将学习20个卷积滤波器,每个滤波器的大小为  5
x 5。该值的输入尺寸与输入图像的宽度,高度和深度相同(在本例中为MNIST数据集),所以我们将有  28
x 28个输入,单个通道用于深度(灰度)。

然后,我们将在x和y方向上应用ReLU激活功能,然后  在x和  y方向上移动2
x 2的 最大值池  (假设一个2
x 2的滑动窗口,通过激活体积“滑动”,进行最大运算,同时在水平和垂直方向上采取2像素的步骤)。

注:本教程主要是基于代码的意思是你 第一次接触到实现卷积神经网络-我会去到 很多更 详细的关于卷积层,激活功能,和最大集中在未来的博客帖子层。在此期间,只需试着跟随代码。

我们现在准备应用我们的第二组 CONV = > RELU = > POOL  
层:

LeNet
- Convolutional Neural Network in Python

Python

21
22
23
24
# second set of CONV => RELU => POOL
model.add(Convolution2D(50, 5, 5, border_mode="same"))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))

这一次,我们将学习  50个卷积滤波器,而不是像上一个图层集中的  20个卷积滤波器。

通常,在网络的更深层次上,观察到的CONV   滤波器数量的 增加。

接下来,我们来到LeNet架构的完全连接的层(通常称为“密集”层):

LeNet - Convolutional Neural Network in Python

Python

26
27
28
29
30
31
32
33
#
set of FC => RELU layers
model.add(Flatten())
model.add(Dense(500))
model.add(Activation("relu"))
 
#
softmax classifier
model.add(Dense(classes))
model.add(Activation("softmax"))

在  第27行,我们取上一个MaxPooling2D  
层的输出,  并将其  平坦化为一个向量,使我们可以应用密集/完全连接的层。如果您有神经网络的任何先前经验,那么您将知道一个密集/完全连接的层是网络中的“标准”类型的层,其中上一层中的每个节点都连接到下一层的每个节点(因此,术语“完全连接”)。

我们的完全连接的层将包含500个单位(28行),我们通过另一个非线性ReLU激活。

第32行是  非常重要的,虽然它很容易忽视
- 这一行定义了另一个 Dense  
类,但接受一个变量(即,不是硬编码)的大小。这个大小是由 类  
变量表示的类标签的  数量 。在MNIST数据集的情况下,我们有10个类(我们正在尝试学习识别的十位数中的每个一个)。

最后,我们应用一个softmax分类器(多项式逻辑回归),它将返回  概率列表,一个用于10个类标签中的每一个(第33行)。具有最大概率的类标签将被选为网络的最终分类。

我们的最后一个代码块处理加载一个预先存在的 weightsPath  
(如果这样一个文件存在)并将构造的模型返回给调用函数:

LeNet
- Convolutional Neural Network in Python

Python

35
36
37
38
39
40
41
# if a weights path is supplied (inicating that the model was
# pre-trained), then load the weights
if weightsPath is not None:
model.load_weights(weightsPath)
 
# return the constructed network architecture
return model

创建LeNet驱动程序脚本

现在我们已经使用Python + Keras实现了LeNet卷积神经网络架构,现在是定义lenet_mnist的时候了 。py   驱动脚本将处理:

  1. 加载MNIST数据集。
  2. 将MNIST分成培训和  测试分组  。
  3. 加载和编译LeNet架构。
  4. 培训网络
  5. 可选地将序列化的网络权重保存到磁盘,以便可以重用(而不必重新训练网络)。
  6. 显示  网络输出的可视示例,以证明我们的实现确实正常工作。

打开你的 lenet_mnist 。py   文件并插入以下代码:

LeNet - Convolutional Neural Network in Python

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#
import the necessary packages
from
pyimagesearch.cnn.networks
import
LeNet
from
sklearn.cross_validation
import
train_test_split
from
sklearn
import
datasets
from
keras.optimizers
import
SGD
from
keras.utils
import
np_utils
import
numpy
as
np
import
argparse
import
cv2
 
#
construct the argument parse and parse the arguments
ap
=
argparse.ArgumentParser()
ap.add_argument("-s",
"--save-model",
type=int,
default=-1,
help="(optional)
whether or not model should be saved to disk")
ap.add_argument("-l",
"--load-model",
type=int,
default=-1,
help="(optional)
whether or not pre-trained model should be loaded")
ap.add_argument("-w",
"--weights",
type=str,
help="(optional)
path to weights file")
args
=
vars(ap.parse_args())

第2-9行处理导入我们需要的Python包。注意我们如何 
从cnn  
和 pyimagesearch的网络  
子模块 导入我们的 LeNet类  。

注意:如果您跟随此博客文章并打算执行代码,  请使用此帖子底部的“下载”部分。为了保持这个短短的简洁,我已经省略了 __init__ 。py  
更新可能会抛弃新的Python开发人员。

从那里,  第12-19行解析三个可选的命令行参数,每个参数详细如下:

  • - 保存- 模型  :指示器变量,用于指定在培训LeNet后是否 将模型保存到磁盘。
  • - 负载- 模型  :另一个指示器变量,此时间指定我们是否应该 加载从磁盘预先训练的模型。
  • - 权重  :在这种情况下 - 保存- 模型  
    提供的 - 权- 路径  
    应该指向我们要 保存序列化的模型。而在这情况下 - 负载- 模型  
    提供的 - 权重  
    应该指向预先存在的权重文件我们的系统上的生活。

我们现在可以加载MNIST数据集并将其分为我们的培训和测试分裂:

LeNet
- Convolutional Neural Network in Python

Python

21
22
23
24
25
26
27
28
29
30
31
32
33
# grab the MNIST dataset (if this is your first time running this
# script, the download may take a minute -- the 55MB MNIST dataset
# will be downloaded)
print("[INFO] downloading MNIST...")
dataset = datasets.fetch_mldata("MNIST Original")
 
# reshape the MNIST dataset from a flat list of 784-dim vectors, to
# 28 x 28 pixel images, then scale the data to the range [0, 1.0]
# and construct the training and testing splits
data = dataset.data.reshape((dataset.data.shape[0], 28, 28))
data = data[:, np.newaxis, :, :]
(trainData, testData, trainLabels, testLabels) = train_test_split(
data / 255.0, dataset.target.astype("int"), test_size=0.33)

第25行从磁盘加载MNIST数据集。如果这是您首次  使用“MNIST Original”   字符串调用fetch_mldata函数 ,则需要下载MNIST数据集。MNIST数据集是一个55MB的文件,所以根据你的互联网连接,这个下载可能需要几秒到几分钟的时间。

在下载MNIST数据集之后,我们将 数据   从一组  784-d特征向量(即原始像素强度)重构为  28×28灰度图像,我们可以通过网络(30行)。

我们的 数据   矩阵现在具有形状 (70000 ,28 ,28 )  ; 然而,  存在一个问题 --Keras假定我们将为每个图像提供  至少 1个通道,因此我们需要在数据   阵列中添加一个额外的维度(第31行)。这一行执行后,新形状 数据   矩阵将是: (70000 ,1 ,28 ,28 )   - ,现在适合于通过我们的LeNet架构。

最后,  第32-33行执行训练和测试拆分,使用2/3的数据进行训练,剩下的1/3用于测试。我们也可以将图像从  [0,255]缩小至  [ 0,1.0 ],这是一种常见的缩放技术。

下一步是处理我们的标签,以便它们可以与分类交叉熵损失函数一起使用:

LeNet - Convolutional Neural Network in Python

Python

35
36
37
38
39
40
#
transform the training and testing labels into vectors in the
#
range [0, classes] -- this generates a vector for each label,
#
where the index of the label is set to `1` and all other entries
#
to `0`; in the case of MNIST, there are 10 class labels
trainLabels
=
np_utils.to_categorical(trainLabels,
10)
testLabels
=
np_utils.to_categorical(testLabels,
10)

39和40行处理我们的培训和测试标签(即,MNIST数据集中每个图像的“地面真相”标签)。

由于我们使用分类交叉熵损失函数,我们需要应用 
将整数的标签从整数转换为  向量的to_categorical函数  ,其中每个向量范围从 [ 0 ,类]  。该函数为每个类标签生成一个向量  ,其中正确标签的索引设置为  1,所有其他条目设置为  0。

在MNIST数据集的情况下,我们有10个lass标签,因此每个标签现在表示为  10-d向量。例如,考虑培训标签  “3”。应用 to_categorical  
函数后,我们的向量现在看起来像:

LeNet
- Convolutional Neural Network in Python

Shell

1
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]

注意,除了现在设置为1的第三个索引之外,向量中的所有条目都为零  。

我们现在准备建立我们的 LeNet   架构,可选择从磁盘加载任何预先训练的权重,然后训练我们的网络:

LeNet - Convolutional Neural Network in Python

Python

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#
initialize the optimizer and model
print("[INFO]
compiling model...")
opt
=
SGD(lr=0.01)
model
=
LeNet.build(width=28,
height=28,
depth=1,
classes=10,
weightsPath=args["weights"]
if
args["load_model"]
>
0
else
None)
model.compile(loss="categorical_crossentropy",
optimizer=opt,
metrics=["accuracy"])
 
#
only train and evaluate the model if we *are not* loading a
#
pre-existing model
if
args["load_model"]
<
0:
print("[INFO]
training...")
model.fit(trainData,
trainLabels,
batch_size=128,
nb_epoch=20,
verbose=1)
 
#
show the accuracy on the testing set
print("[INFO]
evaluating...")
(loss,
accuracy)
=
model.evaluate(testData,
testLabels,
batch_size=128,
verbose=1)
print("[INFO]
accuracy: {:.2f}%".format(accuracy
*
100))

我们将使用随机梯度下降(SGD)训练我们的网络 ,学习率为 0.01  。分类交叉熵将被用作我们的损失函数,这是在使用具有两个以上类标签的数据集时相当标准的选择。然后我们的模型被编译并加载到第45-48行的内存中  。

在这种情况下 - 负载- 模型  不提供,我们要培养我们的网络(52号线)。

培训我们的网络是通过打电话来完成的 。 
实例化模型的拟合方法  
(第54和55行)。我们将允许我们的网络训练  20个纪元(表明我们的网络将“看到”每个训练示例共20次,以学习每个数字类的区分过滤器)。

然后我们对测试数据进行评估(59-61行),并将结果显示给我们的终端。

接下来,我们检查一下我们是否应该将网络权重序列化为文件,以便我们运行 lenet_mnist。py  
脚本  后续时间,无需从头开始重新训练网络:

LeNet
- Convolutional Neural Network in Python

Python

63
64
65
66
# check to see if the model should be saved to file
if args["save_model"] > 0:
print("[INFO] dumping weights to file...")
model.save_weights(args["weights"], overwrite=True)

我们的最后一个代码块可以从我们的测试集中随机选择几位数字,然后通过我们训练有素的LeNet网络进行分类:

LeNet - Convolutional Neural Network in Python

Python

68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#
randomly select a few testing digits
for
i
in
np.random.choice(np.arange(0,
len(testLabels)),
size=(10,)):
#
classify the digit
probs
=
model.predict(testData[np.newaxis,
i])
prediction
=
probs.argmax(axis=1)
 
#
resize the image from a 28 x 28 image to a 96 x 96 image so we
#
can better see it
image
=
(testData[i][0]
*
255).astype("uint8")
image
=
cv2.merge([image]
*
3)
image
=
cv2.resize(image,
(96,
96),
interpolation=cv2.INTER_LINEAR)
cv2.putText(image,
str(prediction[0]),
(5,
20),
cv2.FONT_HERSHEY_SIMPLEX,
0.75,
(0,
255,
0),
2)
 
#
show the image and prediction
print("[INFO]
Predicted: {}, Actual: {}".format(prediction[0],
np.argmax(testLabels[i])))
cv2.imshow("Digit",
image)
cv2.waitKey(0)

对于每个随机选择的数字,我们使用LeNet模型(第71行)对图像进行分类。

我们网络的实际 预测是通过找到具有最大概率的类标签的索引获得的  。记住,我们的网络将通过softmax函数返回一组概率,每个类别标签一个,因此网络的实际“预测”是具有最大概率的类标签。

76-80行处理将28
x 28图像调整  到  96
x 96  像素,以便我们可以更好地可视化,然后绘制 图像  
上的 预测  。

最后,  第82-86行将结果显示在我们的屏幕上。

用Python和Keras训练LeNet

要在MNIST数据集上训练LeNet,请确保已使用 本教程底部找到的“下载”表单下载源代码  。这个 。zip  
文件包含本教程中详细介绍的所有代码 - 此外,此代码的组织方式与上面详细描述的  相同的项目结构,  确保在系统上正常运行(如果您的环境配置正确)。

下载后 。邮政编码  
存档,您可以通过执行以下命令在MNIST上训练LeNet:

LeNet
- Convolutional Neural Network in Python

Shell

1
$ python lenet_mnist.py --save-model 1 --weights output/lenet_weights.hdf5

我的机器输出结果如下:

LeNet - Python中的卷积神经网络

图3:在我的Titan X上的MNIST数据集上训练LeNet每个时期需要大约3秒钟。经过20个时代,LeNet在培训数据上达到98.90%的分类精度,测试数据的精度达到98.49%。

在我的Titan X GPU上,每个纪元需要大约3秒钟,允许  整个训练过程在大约60秒内完成。

只有20个时代,LeNet 在MNIST数据集上达到  98.49%的分类精度 -  在所有计算时间只有60秒的时间里,差不多!

注意:如果执行 lenet_mnist 。py   脚本在我们的CPU而不是GPU,期望每个时代的时间跳到70-90秒。您仍然可以在您的CPU上训练LeNet,只需要一段时间。

用Python和Keras评估LeNet

下面我列出了LeNet + MNIST实现的几个示例评估图像:

LeNet - Python中的卷积神经网络

图4:应用LeNet对MNIST数据集中的数字进行分类。

在上述图像中,我们可以正确地将数字分类为  “6”。

在这个图像中,LeNet正确地将数字识别为  “2”:

LeNet - Python中的卷积神经网络

图5:在Python和Keras中实现LeNet。

下面的图像是CNN滤波器学习的卷积滤波器的鲁棒性,区分性质的一个很好的例子:这个  “6”是相当扭曲的,在数字的圆形区域之间留下了很少的差距,但LeNet仍然能够正确分类数字:

LeNet - Python中的卷积神经网络

图6:使用LeNet和卷积神经网络正确分类特别难读的数字。

这是另一个图像,这次分类严重偏斜的  “1”:

LeNet - Python中的卷积神经网络

图7:使用卷积神经网络对偶数位进行正确分类。

最后,最后一个例子演示了分类“2”的LeNet模型  :

LeNet - Python中的卷积神经网络

图8:使用LeNet和Deep Learning对数字进行分类的最终示例。

Running the serialized LeNet model

After our lenet_mnist.py  script finishes executing the first time (provided you supplied both--save-model  and --weights ), you should now have a lenet_weights.hdf5  file in youroutput  directory.

Instead of re-training our network on subsequent runs of lenet_mnist.py , we can instead load these weights and use them to classify digits.

To load our pre-trained LeNet model, just execute the following command:

LeNet - Convolutional Neural Network in Python

Shell

1
$
python
lenet_mnist.py
--load-model
1
--weights
output/lenet_weights.hdf5

I’ve included a GIF animation of LeNet used to correctly classify handwritten digits below:

LeNet - Python中的卷积神经网络

图9: LeNet的示例动画正确分类数字。

概要

在今天的博文中,我演示了如何使用Python编程语言和Keras库实现LeNet架构,用于深入学习。

LeNet架构是一个伟大的  “你好,世界”网络,让你的脚深入学习和卷积神经网络。网络本身很简单,内存占用空间小,当应用于MNIST数据集时,可以在CPU或GPU上运行,使其成为实验和学习的理想选择,尤其是在深入学习新手时。

本教程主要以  代码为重点,因此,我需要跳过  重要的卷积神经网络概念的详细评论,例如激活层,池层和密集/完全连接的层(否则这个帖子可能  很容易被5x为长)。

在将来的博客文章中,我会详细介绍  每个这些图层类型
- 同时,您只需熟悉代码并尝试自己执行。如果您感觉  真的很大胆,请尝试调整每个卷积层的过滤器数量和过滤器尺寸,看看会发生什么!

无论如何,我希望你喜欢这篇博客文章 - 我一定会在将来做更深入的学习和图像分类工作。