TensorFlow 2 和 Keras 高级深度学习:1~5(1)https://developer.aliyun.com/article/1426943
4. 卷积神经网络(CNN)
现在,我们将进入第二个人工神经网络 CNN。 在本节中,我们将解决相同的 MNIST 数字分类问题,但这一次使用 CNN。
“图 1.4.1”显示了我们将用于 MNIST 数字分类的 CNN 模型,而其实现在“列表 1.4.1”中进行了说明。 实现 CNN 模型将需要对先前模型进行一些更改。 现在,输入张量不再具有输入向量,而具有新尺寸(height
,width
,channels
)或(image_size
,image_size
,1
)=(28
,28
,1
)用于 MNIST 灰度图像。 需要调整训练和测试图像的大小以符合此输入形状要求。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gyl7Z3Dl-1681704179650)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_01_11.png)]
图 1.4.1:用于 MNIST 数字分类的 CNN 模型
实现上图:
“列表 1.4.1”:cnn-mnist-1.4.1.py
import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Activation, Dense, Dropout from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten from tensorflow.keras.utils import to_categorical, plot_model from tensorflow.keras.datasets import mnist
# load mnist dataset (x_train, y_train), (x_test, y_test) = mnist.load_data()
# compute the number of labels num_labels = len(np.unique(y_train))
# convert to one-hot vector y_train = to_categorical(y_train) y_test = to_categorical(y_test)
# input image dimensions image_size = x_train.shape[1] # resize and normalize x_train = np.reshape(x_train,[-1, image_size, image_size, 1]) x_test = np.reshape(x_test,[-1, image_size, image_size, 1]) x_train = x_train.astype('float32') / 255 x_test = x_test.astype('float32') / 255
# network parameters # image is processed as is (square grayscale) input_shape = (image_size, image_size, 1) batch_size = 128 kernel_size = 3 pool_size = 2 filters = 64 dropout = 0.2
# model is a stack of CNN-ReLU-MaxPooling model = Sequential() model.add(Conv2D(filters=filters, kernel_size=kernel_size, activation='relu', input_shape=input_shape)) model.add(MaxPooling2D(pool_size)) model.add(Conv2D(filters=filters, kernel_size=kernel_size, activation='relu')) model.add(MaxPooling2D(pool_size)) model.add(Conv2D(filters=filters, kernel_size=kernel_size, activation='relu')) model.add(Flatten()) # dropout added as regularizer model.add(Dropout(dropout)) # output layer is 10-dim one-hot vector model.add(Dense(num_labels)) model.add(Activation('softmax')) model.summary() plot_model(model, to_file='cnn-mnist.png', show_shapes=True)
# loss function for one-hot vector # use of adam optimizer # accuracy is good metric for classification tasks model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) # train the network model.fit(x_train, y_train, epochs=10, batch_size=batch_size)
_, acc = model.evaluate(x_test, y_test, batch_size=batch_size, verbose=0) print("\nTest accuracy: %.1f%%" % (100.0 * acc))
的主要更改是Conv2D
层的使用。 ReLU
激活函数已经是Conv2D
的参数。 当模型中包含batch normalization
层时,可以将ReLU
函数作为Activation
层使用。 Batch normalization
用于深层 CNN,因此可以利用较大的学习率而不会引起训练过程中的不稳定。
卷积
如果在 MLP 模型中,单元数量表示密集层,则核表示 CNN 操作。 如图“图 1.4.2”所示,可以将核可视化为矩形补丁或窗口,该补丁或窗口从左到右,从上到下在整个图像中滑动。 此操作称为卷积。 它将输入图像转换成特征映射,该特征映射表示核从输入图像中学到的内容。 然后将特征映射转换为后续层中的另一个特征映射,依此类推。 每个Conv2D
生成的特征映射的数量由filters
参数控制。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6wqnH6rX-1681704179651)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_01_12.png)]
图 1.4.2:3×3 核与 MNIST 数字图像卷积。
在步骤t[n]
和t[n + 1]
中显示了卷积,其中核向右移动了 1 个像素 。
卷积中涉及的计算显示在“图 1.4.3”中:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a2LTbSK1-1681704179651)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_01_13.png)]
图 1.4.3:卷积运算显示如何计算特征映射的一个元素
为简单起见,显示了应用了3×3
核的3×3
输入图像(或输入特征映射)。 卷积后显示结果特征映射。 特征映射中一个元素的值被加阴影。 您会注意到,结果特征映射小于原始输入图像的,这是因为卷积仅在有效元素上执行。 核不能超出映像的边界。 如果输入的尺寸应与输出特征映射相同,则Conv2D
接受选项padding='same'
。 输入在其边界周围填充零,以在卷积后保持尺寸不变。
池化操作
最后的更改是添加了MaxPooling2D
层以及参数pool_size=2
。 MaxPooling2D
压缩每个特征映射。 每个大小为pool_size × pool_size
的补丁都减少为 1 个特征映射点。 该值等于补丁中的最大特征点值。 下图显示了MaxPooling2D
的两个补丁:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3CIYPbze-1681704179651)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_01_14.png)]
图 1.4.4:MaxPooling2D
操作。 为简单起见,输入特征映射为4×4
,结果为2×2
特征映射。
MaxPooling2D
的意义在于特征映射尺寸的减小,这转化为感受野尺寸的增加。 例如,在MaxPooling2D(2)
之后,2×2 核现在大约与4×4
补丁卷积。 CNN 学会了针对不同接收场大小的一组新的特征映射。
还有其他合并和压缩方式。 例如,要使MaxPooling2D(2)
的尺寸减少 50%,AveragePooling2D(2)
会取一个补丁的平均值而不是找到最大值。 交叉卷积Conv2D(strides=2,…)
在卷积过程中将跳过每两个像素,并且仍具有相同的 50% 缩小效果。 每种还原技术的有效性都有细微的差异。
在Conv2D
和MaxPooling2D
中,pool_size
和kernel
都可以是非正方形的。 在这些情况下,必须同时指定行和列的大小。 例如,pool_ size = (1, 2)
和kernel = (3, 5)
。
最后一个MaxPooling2D
操作的输出是一堆特征映射。 Flatten
的作用是,将特征映射的栈转换为适用于Dropout
或Dense
层的向量格式,类似于 MLP 模型输出层。
在下一部分中,我们将评估经过训练的 MNIST CNN 分类器模型的表现。
表现评估和模型摘要
如“列表 1.4.2”中所示,“列表 1.4.1”中的 CNN 模型在 80,226 处需要较少数量的参数,而使用 MLP 层时需要 269,322 个参数。 conv2d_1
层具有 640 个参数,因为每个核具有3×3 = 9
个参数,并且 64 个特征映射中的每一个都有一个核,一个偏置参数。 其他卷积层的参数数量可以类似的方式计算。
“列表 1.4.2”:CNN MNIST 数字分类器的摘要
Layer (type) Output Shape Param # ================================================================= conv2d_1 (Conv2D) (None, 26, 26, 64) 640 max_pooling2d_1 (MaxPooiling2) (None, 13, 13, 64) 0 conv2d_2 (Conv2D) (None, 11, 11, 64) 36928 max_pooling2d_2 (MaxPooiling2) (None, 5.5, 5, 64) 0 conv2d_3 (Conv2D) (None, 3.3, 3, 64) 36928 flatten_1 (Flatten) (None, 576) 0 dropout_1 (Dropout) (None, 576) 0 dense_1 (Dense) (None, 10) 5770 activation_1 (Activation) (None, 10) 0 =================================================================== Total params: 80,266 Trainable params: 80,266 Non-trainable params: 0
“图 1.4.5”:显示了 CNN MNIST 数字分类器的图形表示形式。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qa3PGsvE-1681704179651)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_01_15.png)]
图 1.4.5:CNN MNIST 数字分类器的图形描述
“表 1.4.1”显示了 99.4% 的最大测试准确率,这对于使用带有dropout=0.2
的 Adam 优化器的每层具有 64 个特征映射的 3 层网络可以实现。 CNN 比 MLP 具有更高的参数效率,并且具有更高的准确率。 同样,CNN 也适合从顺序数据,图像和视频中学习表示形式。
| 层 | 优化器 | 正则化函数 | 训练准确率(%) | 测试准确率(%) |
| — | — | — | — | — | — |
| 64-64-64 | SGD | 丢弃(0.2) | 97.76 | 98.50 |
| 64-64-64 | RMSprop | 丢弃(0.2) | 99.11 | 99.00 |
| 64-64-64 | Adam | 丢弃(0.2) | 99.75 | 99.40 |
| 64-64-64 | Adam | 丢弃(0.4) | 99.64 | 99.30 |
表 1.4.1:CNN MNIST 数字分类器的不同 CNN 网络配置和表现指标。
看了 CNN 并评估了训练好的模型之后,让我们看一下我们将在本章中讨论的最终核心网络:RNN。
5. 循环神经网络(RNN)
现在,我们来看一下三个人工神经网络中的最后一个,即 RNN。
RNN 是网络的序列,适用于学习顺序数据的表示形式,例如自然语言处理(NLP)中的文本或仪器中的传感器数据流 。 尽管每个 MNIST 数据样本本质上都不是顺序的,但不难想象每个图像都可以解释为像素行或列的序列。 因此,基于 RNN 的模型可以将每个 MNIST 图像作为 28 个元素的输入向量序列进行处理,时间步长等于 28。下面的清单在“图 1.5.1”中显示了 RNN 模型的代码:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1zTcpkLI-1681704179651)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_01_16.png)]
图 1.5.1:用于 MNIST 数字分类的 RNN 模型
“列表 1.5.1”:rnn-mnist-1.5.1.py
import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Activation, SimpleRNN from tensorflow.keras.utils import to_categorical, plot_model from tensorflow.keras.datasets import mnist
# load mnist dataset (x_train, y_train), (x_test, y_test) = mnist.load_data()
# compute the number of labels num_labels = len(np.unique(y_train))
# convert to one-hot vector y_train = to_categorical(y_train) y_test = to_categorical(y_test)
# resize and normalize image_size = x_train.shape[1] x_train = np.reshape(x_train,[-1, image_size, image_size]) x_test = np.reshape(x_test,[-1, image_size, image_size]) x_train = x_train.astype('float32') / 255 x_test = x_test.astype('float32') / 255
# network parameters input_shape = (image_size, image_size) batch_size = 128 units = 256 dropout = 0.2
# model is RNN with 256 units, input is 28-dim vector 28 timesteps model = Sequential() model.add(SimpleRNN(units=units, dropout=dropout, input_shape=input_shape)) model.add(Dense(num_labels)) model.add(Activation('softmax')) model.summary() plot_model(model, to_file='rnn-mnist.png', show_shapes=True)
# loss function for one-hot vector # use of sgd optimizer # accuracy is good metric for classification tasks model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy']) # train the network model.fit(x_train, y_train, epochs=20, batch_size=batch_size)
_, acc = model.evaluate(x_test, y_test, batch_size=batch_size, verbose=0) print("\nTest accuracy: %.1f%%" % (100.0 * acc))
RNN 分类器与之前的两个模型之间有两个主要区别。 首先是input_shape = (image_size, image_size)
,它实际上是input_ shape = (timesteps, input_dim)
或时间步长的input_dim
维向量序列。 其次是使用SimpleRNN
层以units=256
表示 RNN 单元。 units
变量代表输出单元的数量。 如果 CNN 是通过输入特征映射上的核卷积来表征的,则 RNN 输出不仅是当前输入的函数,而且是先前输出或隐藏状态的函数。 由于前一个输出也是前一个输入的函数,因此当前输出也是前一个输出和输入的函数,依此类推。 Keras 中的SimpleRNN
层是真实 RNN 的简化版本。 以下等式描述了SimpleRNN
的输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iUrYd0Dh-1681704179652)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_01_024.png)] (Equation 1.5.1)
在此等式中,b
是偏差,而W
和U
被称为循环核(先前输出的权重)和核(当前输入的权重) ), 分别。 下标t
用于指示序列中的位置。 对于具有units=256
的SimpleRNN
层,参数总数为256 + 256×256 + 256×28 = 72,960
,对应于b
,W
和个贡献。
下图显示了用于分类任务的SimpleRNN
和 RNN 的图。 使SimpleRNN
比 RNN 更简单的是缺少输出值o[t] = Vh[t] + c
在计算softmax
函数之前:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-99oEV98q-1681704179652)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_01_17.png)]
图 1.5.2:SimpleRNN
和 RNN 图
与 MLP 或 CNN 相比,RNN 最初可能较难理解。 在 MLP 中,感知器是基本单元。 一旦了解了感知器的概念,MLP 就是感知器的网络。 在 CNN 中,核是一个补丁或窗口,可在特征映射中滑动以生成另一个特征映射。 在 RNN 中,最重要的是自环的概念。 实际上只有一个单元。
出现多个单元的错觉是因为每个时间步都有一个单元,但实际上,除非网络展开,否则它只是重复使用的同一单元。 RNN 的基础神经网络在单元之间共享。
“列表 1.5.2”中的摘要指示使用SimpleRNN
需要较少数量的参数。
“列表 1.5.2”:RNN MNIST 数字分类器的摘要
Layer (type) Output Shape Param # ================================================================= simple_rnn_1 (SimpleRNN) (None, 256) 72960 dense_1 (Dense) (None, 10) 2570 activation_1 (Activation) (None, 10) 36928 ================================================================= Total params: 75,530 Trainable params: 75,530 Non-trainable params: 0
“图 1.5.3”显示了 RNN MNIST 数字分类器的图形描述。 该模型非常简洁:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Ok95HHh-1681704179652)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_01_18.png)]
图 1.5.3:RNN MNIST 数字分类器图形说明
“表 1.5.1”显示 SimpleRNN 在所呈现的网络中具有最低的准确率:
| 层 | 优化器 | 正则化函数 | 训练准确率(%) | 测试准确率(%) |
| — | — | — | — | — | — |
| 256 | SGD | 丢弃(0.2) | 97.26 | 98.00 |
| 256 | RMSprop | 丢弃(0.2) | 96.72 | 97.60 |
| 256 | Adam | 丢弃(0.2) | 96.79 | 97.40 |
| 512 | SGD | 丢弃(0.2) | 97.88 | 98.30 |
表 1.5.1:不同的SimpleRNN
网络配置和表现指标
在许多深度神经网络中,更常使用 RNN 家族的其他成员。 例如,机器翻译和问答问题都使用了长短期记忆(LSTM)。 LSTM 解决了长期依赖或记住与当前输出相关的过去信息的问题。
与 RNN 或SimpleRNN
不同,LSTM 单元的内部结构更为复杂。“图 1.5.4”显示了 LSTM 的示意图。 LSTM 不仅使用当前输入和过去的输出或隐藏状态,还引入了一个单元状态s[t]
,该状态将信息从一个单元传送到另一个单元。 单元状态之间的信息流由三个门控制f[t]
,i[t]
和q[t]
。 这三个门的作用是确定应保留或替换哪些信息,以及过去对当前单元状态或输出有贡献的信息量以及过去和当前的输入。 我们不会在本书中讨论 LSTM 单元内部结构的细节。 但是,可以在这个页面上找到 LSTM 的直观指南。
LSTM()
层可以用作SimpleRNN()
的嵌入式替代。 如果 LSTM 对于手头的任务过于苛刻,则可以使用更简单的版本,称为门控循环单元(GRU)。 GRU 通过将单元状态和隐藏状态组合在一起来简化 LSTM。 GRU 还将门数量减少了一个。 GRU()
函数也可以用作SimpleRNN()
的直接替代品。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hb6qWiFa-1681704179652)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_01_19.png)]
图 1.5.4:LSTM 图。为了清楚起见,未显示参数。
还有许多其他方法可以配置 RNN。 一种方法是制作双向 RNN 模型。 默认情况下,从当前输出仅受过去状态和当前输入影响的意义上讲,RNN 是单向的。
在双向 RNN 中,未来状态还可以通过允许信息向后流动来影响当前状态和过去状态。 根据收到的新信息,根据需要更新过去的输出。 可以通过调用包装器函数使 RNN 双向。 例如,双向 LSTM 的实现是Bidirectional(LSTM())
。
对于所有类型的 RNN,增加单元数量也将增加容量。 但是,增加容量的另一种方法是堆叠 RNN 层。 尽管应注意,但作为一般经验法则,只有在需要时才应增加模型的容量。 容量过大可能会导致过拟合,结果可能导致训练时间延长和预测期间的表现降低。
6. 总结
本章概述了三种深度学习模型(MLP,RNN,CNN),并介绍了 TensorFlow 2 tf.keras
,这是一个用于快速开发,训练和测试适合于生产环境的深度学习模型的库。 还讨论了 Keras 的顺序 API。 在下一章中,将介绍函数式 API,这将使我们能够构建更复杂的模型,专门用于高级深度神经网络。
本章还回顾了深度学习的重要概念,例如优化,正则化和损失函数。 为了便于理解,这些概念是在 MNIST 数字分类的背景下提出的。
还讨论了使用人工神经网络(特别是 MLP,CNN 和 RNN)进行 MNIST 数字分类的不同解决方案,它们是深度神经网络的重要组成部分,并讨论了它们的表现指标。
了解了深度学习概念以及如何将 Keras 用作工具之后,我们现在可以分析高级深度学习模型。 在下一章讨论了函数式 API 之后,我们将继续执行流行的深度学习模型。 随后的章节将讨论选定的高级主题,例如自回归模型(自编码器,GAN,VAE),深度强化学习,对象检测和分段以及使用互信息的无监督学习。 随附的 Keras 代码实现将在理解这些主题方面发挥重要作用。
7. 参考
Chollet, François. Keras (2015). https://github.com/keras-team/keras.
LeCun, Yann, Corinna Cortes, and C. J. Burges. MNIST handwritten digit database. AT&T Labs [Online]. Available: http://yann.lecun.com/exdb/mnist2 (2010).
二、深度神经网络
在本章中,我们将研究深度神经网络。 这些网络在更具挑战性的数据集,如 ImageNet,CIFAR10 和 CIFAR100。 为简洁起见,我们仅关注两个网络: ResNet [2] [4]和 DenseNet [5]。 尽管我们会更加详细,但重要的是花一点时间介绍这些网络。
ResNet 引入了残差学习的概念,使残障学习能够通过解决深度卷积网络中消失的梯度问题(在第 2 节中讨论)来构建非常深的网络。
DenseNet 允许每个卷积直接访问输入和较低层的特征映射,从而进一步改进了 ResNet。 通过利用瓶颈和过渡层,还可以在深层网络中将参数的数量保持为较低。
但是,为什么这些是两个模型,而不是其他? 好吧,自从引入它们以来,已经有无数的模型,例如 ResNeXt [6]和 WideResNet [7],它们受到这两个网络使用的技术的启发。 同样,在了解 ResNet 和 DenseNet 的情况下,我们将能够使用他们的设计指南来构建我们自己的模型。 通过使用迁移学习,这也将使我们能够将预训练的 ResNet 和 DenseNet 模型用于我们自己的目的,例如对象检测和分割。 仅出于这些原因,以及与 Keras 的兼容性,这两个模型非常适合探索和补充本书的高级深度学习范围。
尽管本章的重点是深度神经网络; 在本章中,我们将讨论 Keras 的重要功能,称为函数式 API。 该 API 充当在tf.keras
中构建网络的替代方法,使我们能够构建更复杂的网络,而这是顺序模型 API 无法实现的。 我们之所以专注于此 API 的原因是,它将成为构建诸如本章重点介绍的两个之类的深度网络的非常有用的工具。 建议您先完成“第 1 章”,“Keras 的高级深度学习介绍”,然后再继续本章,因为我们将参考在本章中探讨的入门级代码和概念,我们将它们带入了更高的层次。
本章的目的是介绍:
- Keras 中的函数式 API,以及探索运行该 API 的网络示例
tf.keras
中的深度残差网络(ResNet 版本 1 和 2)实现tf.keras
中密集连接卷积网络(DenseNet)的实现- 探索两种流行的深度学习模型,即 ResNet 和 DenseNet
让我们开始讨论函数式 API。
1. 函数式 API
在我们首先在“第 1 章”,“Keras 高级深度学习入门”的顺序模型 API 中,一层堆叠在另一层之上。 通常,将通过其输入和输出层访问模型。 我们还了解到,如果我们发现自己想要在网络中间添加辅助输入,或者甚至想在最后一层之前提取辅助输出,则没有简单的机制。
这种模式也有缺点。 例如,它不支持类似图的模型或行为类似于 Python 函数的模型。 此外,在两个模型之间共享层也很困难。函数式 API 解决了这些局限性,这就是为什么它对于想要使用深度学习模型的任何人来说都是至关重要的工具的原因。
函数式 API 遵循以下两个概念:
- 层是接受张量作为参数的实例。 一层的输出是另一个张量。 为了构建模型,层实例是通过输入和输出张量彼此链接的对象。 这与在顺序模型中堆叠多个层有类似的最终结果。 但是,使用层实例会使模型更容易具有辅助或多个输入和输出,因为每个层的输入/输出将易于访问。
- 模型是一个或多个输入张量和输出张量之间的函数。 在模型输入和输出之间,张量是通过层输入和输出张量彼此链接的层实例。 因此,模型是一个或多个输入层和一个或多个输出层的函数。 该模型实例将数据从输入流到输出流的形式的计算图形式化。
在完成函数式 API 模型的构建之后,训练和评估将由顺序模型中使用的相同函数执行。 为了说明,在函数式 API 中,二维卷积层Conv2D
带有 32 个过滤器,并且x
作为层输入张量,y
作为层输出张量可以写为:
y = Conv2D(32)(x)
我们也可以堆叠多层来构建模型。 例如,我们可以使用函数式 API 重写 MNIST cnn-mnist-1.4.1.py
上的卷积神经网络(CNN),如下所示:
“列表 2.1.1”:cnn-functional-2.1.1.py
import numpy as np from tensorflow.keras.layers import Dense, Dropout, Input from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten from tensorflow.keras.models import Model from tensorflow.keras.datasets import mnist from tensorflow.keras.utils import to_categorical
# load MNIST dataset (x_train, y_train), (x_test, y_test) = mnist.load_data()
# from sparse label to categorical num_labels = len(np.unique(y_train)) y_train = to_categorical(y_train) y_test = to_categorical(y_test)
# reshape and normalize input images image_size = x_train.shape[1] x_train = np.reshape(x_train,[-1, image_size, image_size, 1]) x_test = np.reshape(x_test,[-1, image_size, image_size, 1]) x_train = x_train.astype('float32') / 255 x_test = x_test.astype('float32') / 255
# network parameters input_shape = (image_size, image_size, 1) batch_size = 128 kernel_size = 3 filters = 64 dropout = 0.3
# use functional API to build cnn layers inputs = Input(shape=input_shape) y = Conv2D(filters=filters, kernel_size=kernel_size, activation='relu')(inputs) y = MaxPooling2D()(y) y = Conv2D(filters=filters, kernel_size=kernel_size, activation='relu')(y) y = MaxPooling2D()(y) y = Conv2D(filters=filters, kernel_size=kernel_size, activation='relu')(y) # image to vector before connecting to dense layer y = Flatten()(y) # dropout regularization y = Dropout(dropout)(y) outputs = Dense(num_labels, activation='softmax')(y)
# build the model by supplying inputs/outputs model = Model(inputs=inputs, outputs=outputs) # network model in text model.summary() # classifier loss, Adam optimizer, classifier accuracy model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# train the model with input images and labels model.fit(x_train, y_train, validation_data=(x_test, y_test), epochs=20, batch_size=batch_size)
# model accuracy on test dataset score = model.evaluate(x_test, y_test, batch_size=batch_size, verbose=0) print("\nTest accuracy: %.1f%%" % (100.0 * score[1]))
默认情况下,使用pool_size=2
作为参数,因此MaxPooling2D
已被删除。
在前面的清单中,每一层都是张量的函数。 每一层生成一个张量作为输出,该张量成为下一层的输入。 要创建此模型,我们可以调用Model()
并提供inputs
和outputs
张量,或者提供张量列表。 其他一切保持不变。
类似于顺序模型,也可以使用fit()
和evaluate()
函数来训练和评估相同的列表。 实际上,Sequential
类是Model
类的子类。 我们需要记住,我们在fit()
函数中插入了validation_data
参数,以查看训练期间验证准确率的进度。 在 20 个周期内,准确率范围从 99.3% 到 99.4%。
创建两输入一输出模型
现在,我们将做一些令人兴奋的事情,创建一个具有两个输入和一个输出的高级模型。 在开始之前,重要的是要知道序列模型 API 是为仅构建 1 输入和 1 输出模型而设计的。
假设发明了一种用于 MNIST 数字分类的新模型,它称为 Y 网络,如图“图 2.1.1”所示。 Y 网络在左 CNN 分支和右 CNN 分支两次使用相同的输入。 网络使用concatenate
层合并结果。 合并操作concatenate
类似于沿连接轴堆叠两个相同形状的张量以形成一个张量。 例如,沿着最后一个轴连接两个形状为(3, 3, 16)
的张量将导致一个形状为(3, 3, 32)
的张量。
concatenate
层之后的所有其他内容将与上一章的 CNN MNIST 分类器模型相同:Flatten
,然后是Dropout
,然后是Dense
:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jIixUNMr-1681704179652)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_02_01.png)]
图 2.1.1:Y 网络接受两次相同的输入,但是在卷积网络的两个分支中处理输入。 分支的输出使用连接层进行合并。最后一层的预测将类似于上一章的 CNN MNIST 分类器模型。
为了提高“列表 2.1.1”中模型的表现,我们可以提出一些更改。 首先,Y 网络的分支将过滤器数量加倍,以补偿MaxPooling2D()
之后特征映射尺寸的减半。 例如,如果第一个卷积的输出为(28, 28, 32)
,则在最大池化之后,新形状为(14, 14, 32)
。 下一个卷积的过滤器大小为 64,输出尺寸为(14, 14, 64)
。
其次,尽管两个分支的核大小相同,但右分支使用 2 的扩展率。“图 2.1.2”显示了不同的扩展率对大小为 3 的核的影响。 这个想法是,通过使用扩张率增加核的有效接受域大小,CNN 将使正确的分支能够学习不同的特征映射。 使用大于 1 的扩张速率是一种计算有效的近似方法,可以增加接收场的大小。 这是近似值,因为该核实际上不是成熟的核。 这是有效的,因为我们使用与膨胀率等于 1 相同的操作数。
要了解接受域的概念,请注意,当核计算特征映射的每个点时,其输入是前一层特征映射中的补丁,该补丁也取决于其前一层特征映射。 如果我们继续将此依赖关系一直跟踪到输入图像,则核将依赖于称为接收场的图像补丁。
我们将使用选项padding='same'
来确保使用扩张的 CNN 时不会出现负张量。 通过使用padding='same'
,我们将使输入的尺寸与输出特征映射相同。 这是通过用零填充输入以确保输出的大小相同来实现的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UbJhyeVu-1681704179653)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_02_02.png)]
图 2.1.2:通过从 1 增加膨胀率,有效的核接受域大小也增加了
“列表 2.1.2”的cnn-y-network-2.1.2.py
显示了使用函数式 API 的 Y 网络的实现。 两个分支由两个for
循环创建。 两个分支期望输入形状相同。 两个for
循环将创建两个Conv2D-Dropout-MaxPooling2D
的三层栈。 虽然我们使用concatenate
层组合了左右分支的输出,但我们还可以利用tf.keras
的其他合并函数,例如add
,dot
和multiply
。 合并函数的选择并非纯粹是任意的,而必须基于合理的模型设计决策。
在 Y 网络中,concatenate
不会丢弃特征映射的任何部分。 取而代之的是,我们让Dense
层确定如何处理连接的特征映射。
“列表 2.1.2”:cnn-y-network-2.1.2.py
import numpy as np from tensorflow.keras.layers import Dense, Dropout, Input from tensorflow.keras.layers import Conv2D, MaxPooling2D from tensorflow.keras.layers import Flatten, concatenate from tensorflow.keras.models import Model from tensorflow.keras.datasets import mnist from tensorflow.keras.utils import to_categorical from tensorflow.keras.utils import plot_model
# load MNIST dataset (x_train, y_train), (x_test, y_test) = mnist.load_data()
# from sparse label to categorical num_labels = len(np.unique(y_train)) y_train = to_categorical(y_train) y_test = to_categorical(y_test)
# reshape and normalize input images image_size = x_train.shape[1] x_train = np.reshape(x_train,[-1, image_size, image_size, 1]) x_test = np.reshape(x_test,[-1, image_size, image_size, 1]) x_train = x_train.astype('float32') / 255 x_test = x_test.astype('float32') / 255
# network parameters input_shape = (image_size, image_size, 1) batch_size = 32 kernel_size = 3 dropout = 0.4 n_filters = 32
# left branch of Y network left_inputs = Input(shape=input_shape) x = left_inputs filters = n_filters # 3 layers of Conv2D-Dropout-MaxPooling2D # number of filters doubles after each layer (32-64-128) for i in range(3): x = Conv2D(filters=filters, kernel_size=kernel_size, padding='same', activation='relu')(x) x = Dropout(dropout)(x) x = MaxPooling2D()(x) filters *= 2
# right branch of Y network right_inputs = Input(shape=input_shape) y = right_inputs filters = n_filters # 3 layers of Conv2D-Dropout-MaxPooling2Do # number of filters doubles after each layer (32-64-128) for i in range(3): y = Conv2D(filters=filters, kernel_size=kernel_size, padding='same', activation='relu', dilation_rate=2)(y) y = Dropout(dropout)(y) y = MaxPooling2D()(y) filters *= 2
# merge left and right branches outputs y = concatenate([x, y]) # feature maps to vector before connecting to Dense y = Flatten()(y) y = Dropout(dropout)(y) outputs = Dense(num_labels, activation='softmax')(y)
# build the model in functional API model = Model([left_inputs, right_inputs], outputs) # verify the model using graph plot_model(model, to_file='cnn-y-network.png', show_shapes=True) # verify the model using layer text description model.summary()
# classifier loss, Adam optimizer, classifier accuracy model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# train the model with input images and labels model.fit([x_train, x_train], y_train, validation_data=([x_test, x_test], y_test), epochs=20, batch_size=batch_size)
# model accuracy on test dataset score = model.evaluate([x_test, x_test], y_test, batch_size=batch_size, verbose=0) print("\nTest accuracy: %.1f%%" % (100.0 * score[1]))
退后一步,我们可以注意到 Y 网络期望有两个输入用于训练和验证。 输入是相同的,因此提供了[x_train, x_train]
。
在 20 个周期的过程中,Y 网络的准确率为 99.4% 至 99.5%。 与 3 叠 CNN 相比,这是一个微小的改进,CNN 的精度在 99.3% 到 99.4% 之间。 但是,这是以更高的复杂度和两倍以上的参数数量为代价的。
下图“图 2.1.3”显示了 Keras 理解并由plot_model()
函数生成的 Y 网络的架构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2QP0CwnT-1681704179653)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_02_03.png)]
图 2.1.3:清单 2.1.2 中实现的 CNN Y 网络
总结我们对函数式 API 的了解。 我们应该花时间记住本章的重点是构建深度神经网络,特别是 ResNet 和 DenseNet。 因此,我们只讨论构建它们所需的函数式 API 材料,因为涵盖整个的 API 将超出本书的范围。 话虽如此,让我们继续讨论 ResNet。
有关函数式 API 的其他信息,请阅读这里。
2. 深度残差网络(ResNet)
深度网络的一个主要优点是,它们具有从输入图和特征映射学习不同级别表示的能力。 在分类,分割,检测和许多其他计算机视觉问题中,学习不同的特征映射通常可以提高性能。
但是,您会发现训练深层网络并不容易,因为在反向传播过程中,梯度可能会随着浅层中的深度消失(或爆炸)。“图 2.2.1”说明了梯度消失的问题。 通过从输出层向所有先前层的反向传播来更新网络参数。 由于反向传播是基于链法则的,因此当梯度到达浅层时,梯度会逐渐减小。 这是由于小数的乘法,尤其是对于小损失函数和参数值。
乘法运算的数量将与网络深度成正比。 还要注意的是,如果梯度降低,则不会适当更新参数。
因此,网络将无法提高其表现。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hrRqfskI-1681704179653)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_02_04.png)]
图 2.2.1:深层网络中的一个常见问题是,在反向传播过程中,梯度在到达浅层时会消失。
为了减轻深度网络中梯度的降级,ResNet 引入了深度残差学习框架的概念。 让我们分析一个块:深度网络的一小部分。
“图 2.2.2”显示了典型 CNN 块和 ResNet 残差块之间的比较。 ResNet 的想法是,为了防止梯度降级,我们将让信息通过快捷连接流到浅层。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3xGypxwf-1681704179653)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_02_05.png)]
图 2.2.2:典型 CNN 中的块与 ResNet 中的块之间的比较。 为了防止反向传播期间梯度的降低,引入了快捷连接。
接下来,我们将在中讨论两个模块之间的差异,以了解更多详细信息。“图 2.2.3”显示了另一个常用的深层网络 VGG [3]和 ResNet 的 CNN 块的更多详细信息。 我们将层特征映射表示为x
。 层l
的特征映射为x[l]
。 在 CNN 层中的操作是 Conv2D 批量规范化(BN)- ReLU。
假设我们以H() = Conv2D-Batch Normalization(BN)-ReLU
的形式表示这组操作; 然后:
x[l-1] = H(x[l-2])
(公式 2.2.1)
x[l] = H(x[l-1])
(方程式 2.2.2)
换句话说,通过H() =Conv2D-Batch Normalization(BN)-ReLU
将l-2
层上的特征映射转换为x[l-1]
。 应用相同的操作集将x[l-1]
转换为x[l]
。 换句话说,如果我们有一个 18 层的 VGG,则在将输入图像转换为第 18 个层特征映射之前,有 18 个H()
操作。
一般而言,我们可以观察到l
层输出特征映射仅直接受先前的特征映射影响。 同时,对于 ResNet:
x[l-1] = H(x[l-2])
(公式 2.2.3)
x[l] = ReLU(F(x[l-1]) + x[l-2])
(公式 2.2.4)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SHXD1l0P-1681704179653)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_02_06.png)]
图 2.2.3:普通 CNN 块和残差块的详细层操作
F(x[l-1])
由Conv2D-BN
制成,这也被称为残差映射。 +
符号是快捷方式连接和F(x[l-1])
输出之间的张量元素加法。 快捷连接不会增加额外的参数,也不会增加计算复杂度。
可以通过add()
合并函数在tf.keras
中实现添加操作。 但是,F(x[l-1])
和x[l-2]
应该具有相同的尺寸。
如果尺寸不同,例如,当更改特征映射尺寸时,我们应该在x[l-2]
上进行线性投影以匹配尺寸F([l-1])
的含量。 在原始论文中,当特征映射的大小减半时,情况的线性投影是通过Conv2D
和 1 strides=2
核完成的。
在“第 1 章”,“Keras 高级深度学习”,我们讨论了stride > 1
等效于在卷积期间跳过像素。 例如,如果strides=2
,则在卷积过程中滑动核时,可以跳过其他每个像素。
前面的“公式 2.2.3”和“公式 2.2.4”都对 ResNet 残余块操作进行建模。 他们暗示,如果可以训练较深的层具有较少的误差,则没有理由为什么较浅的层应具有较高的误差。
知道 ResNet 的基本构建块后,我们就可以设计一个深度残差网络来进行图像分类。 但是,这一次,我们将处理更具挑战性的数据集。
在我们的示例中,我们将考虑 CIFAR10,它是原始论文所基于的数据集之一。 在此示例中,tf.keras
提供了一个 API,可以方便地访问 CIFAR10 数据集,如下所示:
from tensorflow.keras.datasets import cifar10 (x_train, y_train), (x_test, y_test) = cifar10.load_data()
与 MNIST 一样,CIFAR10 数据集也有 10 个类别。 数据集是对应于飞机,汽车,鸟,猫,鹿,狗,青蛙,马,船和卡车的小型(32×32
)RGB 真实世界图像的集合。 10 个类别中的每个类别。“图 2.2.4”显示了来自 CIFAR10 的示例图像。
在数据集中,有 50,000 个标记的训练图像和 10,000 个标记的测试图像用于验证:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ruZaUKw-1681704179654)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_02_07.png)]
图 2.2.4:来自 CIFAR10 数据集的样本图像。 完整的数据集包含 50,000 张标签的训练图像和 10,000 张标签的测试图像以进行验证。
对于 CIFAR10 数据,可以使用“表 2.2.1”中所示的不同网络架构来构建 ResNet。“表 2.2.1”表示我们有三组残差块。 每组具有对应于n
个残余块的2n
层。32×32
的额外层是输入图像的第一层。
层 | 输出大小 | 过滤器尺寸 | 操作 |
卷积 | 32 × 32 |
16 | 3 x 3 Conv2D |
残差块(1) | 32 × 32 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4jeAVsH-1681704179654)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_02_001.png)] | |
过渡层(1) | 32 × 32 |
{1 x 1 Conv2D, stride = 2} |
|
16 × 16 |
|||
残差块(2) | 16 × 16 |
32 | [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ovWocSOl-1681704179654)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_02_002.png)] |
过渡层(2) | 16 × 16 |
||
8 × 8 |
|||
残差块(3) | 8 × 8 |
64 | [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nGSHOu7m-1681704179654)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_02_003.png)] |
平均池化 | 1 × 1 |
表 2.2.1:ResNet 网络架构配置
核大小为 3,不同大小的两个特征映射之间的过渡除外,该过渡实现了线性映射。 例如,核大小为 1 的Conv2D
和strides=2
。 为了与 DenseNet 保持一致,当我们连接两个大小不同的剩余块时,我们将使用项Transition
层。
ResNet 使用kernel_initializer='he_normal'
以便在进行反向传播时帮助收敛[1]。 最后一层由AveragePooling2D-Flatten-Dense
制成。 在这一点上值得注意的是 ResNet 不使用丢弃。 似乎add
合并操作和1 x 1
卷积具有自正则化效果。“图 2.2.5”显示了 CIFAR10 数据集的 ResNet 模型架构,如“表 2.2.1”中所述。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jmBSF8zR-1681704179654)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_02_08.png)]
图 2.2.5:用于 CIFAR10 数据集分类的 ResNet 的模型架构
以下代码段显示了tf.keras
中的部分 ResNet 实现。 该代码已添加到 Keras GitHub 存储库中。 从“表 2.2.2”(稍后显示)中,我们还可以看到,通过修改n
的值,我们可以增加网络的深度。
例如,对于n = 18
,我们已经拥有 ResNet110,这是一个具有 110 层的深度网络。 要构建 ResNet20,我们使用n = 3
:
n = 3
# model version # orig paper: version = 1 (ResNet v1), # improved ResNet: version = 2 (ResNet v2) version = 1
# computed depth from supplied model parameter n if version == 1: depth = n * 6 + 2 elif version == 2: depth = n * 9 + 2
if version == 2: model = resnet_v2(input_shape=input_shape, depth=depth) else: model = resnet_v1(input_shape=input_shape, depth=depth)
resnet_v1()
方法是 ResNet 的模型构建器。 它使用工具函数resnet_layer(),
来帮助构建Conv2D-BN-ReLU
的栈。
它将称为版本 1,正如我们将在下一节中看到的那样,提出了一种改进的 ResNet,该版本称为 ResNet 版本 2 或 v2。 通过 ResNet,ResNet v2 改进了残差块设计,从而提高了表现。
以下清单显示了resnet-cifar10-2.2.1.py
的部分代码,它是 ResNet v1 的tf.keras
模型实现。
“列表 2.2.1”:resnet-cifar10-2.2.1.py
def resnet_v1(input_shape, depth, num_classes=10): """ResNet Version 1 Model builder [a]
Stacks of 2 x (3 x 3) Conv2D-BN-ReLU Last ReLU is after the shortcut connection. At the beginning of each stage, the feature map size is halved (downsampled) by a convolutional layer with strides=2, while the number of filters is doubled. Within each stage, the layers have the same number filters and the same number of filters. Features maps sizes: stage 0: 32x32, 16 stage 1: 16x16, 32 stage 2: 8x8, 64 The Number of parameters is approx the same as Table 6 of [a]: ResNet20 0.27M ResNet32 0.46M ResNet44 0.66M ResNet56 0.85M ResNet110 1.7M
Arguments: input_shape (tensor): shape of input image tensor depth (int): number of core convolutional layers num_classes (int): number of classes (CIFAR10 has 10)
Returns: model (Model): Keras model instance """ if (depth - 2) % 6 != 0: raise ValueError('depth should be 6n+2 (eg 20, 32, in [a])') # Start model definition. num_filters = 16 num_res_blocks = int((depth - 2) / 6)
inputs = Input(shape=input_shape) x = resnet_layer(inputs=inputs) # instantiate the stack of residual units for stack in range(3): for res_block in range(num_res_blocks): strides = 1 # first layer but not first stack if stack > 0 and res_block == 0: strides = 2 # downsample y = resnet_layer(inputs=x, num_filters=num_filters, strides=strides) y = resnet_layer(inputs=y, num_filters=num_filters, activation=None) # first layer but not first stack if stack > 0 and res_block == 0: # linear projection residual shortcut # connection to match changed dims x = resnet_layer(inputs=x, num_filters=num_filters, kernel_size=1, strides=strides, activation=None, batch_normalization=False) x = add([x, y]) x = Activation('relu')(x) num_filters *= 2
# add classifier on top. # v1 does not use BN after last shortcut connection-ReLU x = AveragePooling2D(pool_size=8)(x) y = Flatten()(x) outputs = Dense(num_classes, activation='softmax', kernel_initializer='he_normal')(y)
# instantiate model. model = Model(inputs=inputs, outputs=outputs) return model
TensorFlow 2 和 Keras 高级深度学习:1~5(3)https://developer.aliyun.com/article/1426946