持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天, 点击查看活动详情
使用 Keras 构建卷积神经网络
为了进一步加深对卷积神经网络 (
Convolutional Neural Network
,
CNN
) 的理解,我们将使用
Keras
构建基于
CNN
的体系结构,并通过使用
Keras
和
Numpy
从输入开始构建
CNN
前向传播过程获得输出,来增强我们对
CNN
的理解。
CNN 使用示例
我们首先定义一个输入和预期输出数据的简单示例来实现
CNN
。
import numpy as np
x_train = np.array([[[1,2,3,3],
[2,3,4,5],
[4,5,6,7],
[1,3,4,6]],
[[-1,2,3,-5],
[2,-2,4,5],
[-3,5,-4,9],
[-1,-3,-2,-5]]])
y_train = np.array([0, 1])
在以上代码中,我们创建了以下数据:输入全为正数得到的输出为 0,带有负值的输入则得到 1 作为输出。
缩放输入数据集:
x_train = x_train / 9
对输入数据集的形状进行整形,以使每个输入图像都以 (宽度 x 高度 x 通道数)
的格式表示:
x_train = x_train.reshape(x_train.shape[0], x_train.shape[1], x_train.shape[1], 1)
准备数据后,构建模型架构,导入相关方法后实例化模型:
from keras.models import Sequential
from keras.layers import Conv2D, Flatten, MaxPooling2D
model = Sequential()
接下来,我们将执行卷积操作:
model.add(Conv2D(1, (3, 3), input_shape=(4, 4, 1), activation='relu'))
在上述代码中,我们对输入数据执行 2D
卷积 Conv2D
,其中有 1
个大小为 3 x 3
的卷积核,由于这是实例化模型中的第一层,我们需要指定输入形状,为 (4, 4, 1)
;最后,我们在卷积的输出之上使用 ReLU
激活函数。由于我们没有对输入进行填充,因此输出的特征图大小将缩小,卷积运算输出的形状为 2 x 2 x 1
。
接下来,我们将添加一个执行最大池化操作的网络层 MaxPooling2D
,如下所示:
model.add(MaxPooling2D(pool_size=(2, 2)))
我们在上一层获得的输出之上执行最大池化 (池化核大小为 2 x 2
),这表示需要计算图像每个滑动窗口 2 x 2
部分中的最大值,得到池化运算输出的形状为 1 x 1 x 1
。
接下来,展平池化层的输出:
model.add(Flatten())
一旦执行了展平 Flatten
处理,展平层的操作十分简单——将多维的输入整形为一维数组,Flatten
操作不影响第一维的 batch_size
,在本例中,执行 Flatten
操作后,数据形状由 (batch_size, 1,1,1)
变为 (batch_size, 1)
。之后就与在标准前馈神经网络中执行的过程非常相似,先连接到若干隐藏层,最后连接到输出层。在这里,我们使用 sigmoid
激活函数,并直接将展平层的输出连接到输出层:
model.add(Dense(1, activation='sigmoid'))
查看该模型的相关信息:
model.summary()
输出的模型简要信息如下:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 2, 2, 1) 10
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 1, 1, 1) 0
_________________________________________________________________
flatten (Flatten) (None, 1) 0
_________________________________________________________________
dense (Dense) (None, 1) 2
=================================================================
Total params: 12
Trainable params: 12
Non-trainable params: 0
_________________________________________________________________
如上所示,卷积层中有 10
个参数,因为一个 3 x 3
的卷积核,其具有 9
个权重和 1
个偏置项。池化层和展平层没有任何参数,因为它们只需某个区域中提取最大值(最大池化 MaxPooling2D
),或者展平前一层的输出(展平层 Flatten
),因此不需要在其中一个权重进行修改的操作这些层。输出层具有两个参数,因为展平层只有一个输出,该输出连接到具有一个值的输出层,因此具有一个权重和一个偏置项来连接展平层和输出层。
最后,编译并拟合模型:
model.compile(loss='binary_crossentropy',optimizer='adam',metrics=['acc'])
model.fit(x_train, y_train, epochs=50)
在上述代码中,我们将损失指定为二进制交叉熵,因为输出结果是 1
或 0
。
验证 CNN 输出
在上一节中,我们已经拟合了模型,接下来,通过实现 CNN
的前向传播过程来验证从模型中获得的输出。
首先,我们提取权重和偏置的相关信息:
print(model.get_weights())
输出结果如下:
[<tf.Variable 'conv2d/kernel:0' shape=(3, 3, 1, 1) dtype=float32, numpy=
array([[[[ 0.1985341 ]],
[[ 0.27599618]],
[[ 0.20717546]]],
[[[-0.41702896]],
[[-0.21289168]],
[[ 0.12980239]]],
[[[-0.14379142]],
[[ 0.55261314]],
[[-0.49706236]]]], dtype=float32)>,
<tf.Variable 'conv2d/bias:0' shape=(1,) dtype=float32, numpy=array([0.04198299], dtype=float32)>,
<tf.Variable 'dense/kernel:0' shape=(1, 1) dtype=float32, numpy=array([[1.5805249]], dtype=float32)>,
<tf.Variable 'dense/bias:0' shape=(1,) dtype=float32, numpy=array([-0.28272304], dtype=float32)>]
可以看到,首先显示了卷积层的权重,然后是偏置,最后是输出层中的权重和偏置。卷积层中权重的形状为 (3, 3, 1, 1)
,因为卷积核的形状为 3 x 3 x 1
(形状中的前三个值),形状中的第四个值 1
用于在卷积层中指定卷积核的数量。如果我们指定 64
作为卷积中的卷积核数量,则权重的形状为 3 x 3 x 1 x 64
,而如果对具有 3
个通道的图像执行卷积运算,则每个卷积核的形状将为 3 x 3 x 3
。
使用 model
的 weights
属性提取各层的权重值:
print(model.weights)
接下来,我们使用模型计算第一个输入 x_train[0]
的输出,以便我们可以通过前向传播查看 CNN
计算结果:
print(model.predict(x_train[0].reshape(1,4,4,1)))
# 输出如下
# [[0.04299431]]
以上代码对输入数据整形,同时将其传递给预测方法,因为模型希望接受输入的形状为 (None, 4, 4, 1)
,其中 None
用于指定批大小,可以是任何数字。我们运行的模型预测输出为 0.04299431
。
接下来,我们通过模拟卷积过程进行验证,对输入数据执行卷积,输入图像的形状为 4 x 4
,而卷积核的形状为 3 x 3
,在代码中沿着行和列执行矩阵乘法(卷积):
sumprod = []
for i in range(x_train[0].shape[0]-model.get_weights()[0].shape[0]+1):
for j in range(x_train[0].shape[0]-model.get_weights()[0].shape[0]+1):
img_subset = np.array(x_train[0,i:(i+3),j:(j+3),0])
filter = model.get_weights()[0].reshape(3,3)
val = np.sum(img_subset*filter) + model.get_weights()[1]
sumprod.append(val)
在以上代码中,我们初始化一个名为 sumprod
的空列表,用于存储卷积核与输入数据的每个子矩阵卷积的输出。
整形 sumprod
的输出,以便将其传递到池化层:
sumprod = np.array(sumprod).reshape(2, 2, 1)
在卷积的输出传递到池化层之前,先对其使用激活函数:
sumprod = np.where(sumprod>0, sumprod, 0)
将卷积输出传递到池化层,根据以上模型定义,考虑到卷积的输出为 2 x 2
,根据最大池化层的定义我们取输出中的最大值:
pooling_layer_output = np.max(sumprod)
将池化层的输出连接到输出层,将池化层的输出乘以输出层中的权重,然后在输出层中加上偏置值:
intermediate_output_value = pooling_layer_output * model.get_weights()[2] + model.get_weights()[3]
计算 Sigmoid
输出:
print(1/(1+np.exp(-intermediate_output_value)))
以上操作的输出如下:
[[0.42994314]]
可以看到输出与我们使用 model.predict
方法获得的输出相同,从而加深了我们对 CNN
工作流程的了解。