用Keras可视化卷积神经网络中的激活

用Keras可视化卷积神经网络中的激活

在本文中,我们将使用 Keras和Python 训练一个简单的卷积神经网络,用于分类任务。为此,我们将使用一组非常小而简单的图像,其中包括100幅圆形图画,100幅正方形图像和100张三角形图像,数据集:https://www.kaggle.com/dawgwelder/keras-cnn-build/data。这些将被分成训练和测试集并馈送到网络。

本文重点是了解我们的层结构如何处理数据,以可视化的方式处理每一个中间激活,这包括显示由卷积和网络中的池化层输出的特征映射。

这意味着我们将可视化每个激活层的结果。

让我们先导入所有必需的Python库:

%matplotlib inline
​
import glob
import matplotlib
from matplotlib import pyplot as plt
import matplotlib.image as mpimg
import numpy as np
import imageio as im
from keras import models
from keras.models import Sequential
from keras.layers import Conv2D
from keras.layers import MaxPooling2D
from keras.layers import Flatten
from keras.layers import Dense
from keras.layers import Dropout
from keras.preprocessing import image
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import ModelCheckpoint

用Keras可视化卷积神经网络中的激活

数据集

images = []
for img_path in glob.glob('training_set/circles/*.png'):
 images.append(mpimg.imread(img_path))
​
plt.figure(figsize=(20,10))
columns = 5
for i, image in enumerate(images):
 plt.subplot(len(images) / columns + 1, columns, i + 1)
 plt.imshow(image)

用Keras可视化卷积神经网络中的激活

用Keras可视化卷积神经网络中的激活

正方形

images = []
for img_path in glob.glob('training_set/squares/*.png'):
 images.append(mpimg.imread(img_path))
​
plt.figure(figsize=(20,10))
columns = 5
for i, image in enumerate(images):
 plt.subplot(len(images) / columns + 1, columns, i + 1)
 plt.imshow(image)

用Keras可视化卷积神经网络中的激活

用Keras可视化卷积神经网络中的激活

三角形

images = []
for img_path in glob.glob('training_set/triangles/*.png'):
 images.append(mpimg.imread(img_path))
​
plt.figure(figsize=(20,10))
columns = 5
for i, image in enumerate(images):
 plt.subplot(len(images) / columns + 1, columns, i + 1)
 plt.imshow(image)

用Keras可视化卷积神经网络中的激活

用Keras可视化卷积神经网络中的激活

在RGB下,图像的形状是28像素×28像素。

神经网络模型

现在让我们继续我们的卷积神经网络构造。通常,我们使用以下方式启动模型Sequential():

# Initialising the CNN
classifier = Sequential()

我们指定了卷积层,并将MaxPooling添加到downsample和dropout中,以防止过拟合。我们指定softmax为最后一个激活函数,用于多类分类,每个类一个(圆[0],正方形[1],三角形[1])。Python实现如下:

# Step 1 - Convolution
classifier.add(Conv2D(32, (3, 3), padding='same', input_shape = (28, 28, 3), activation = 'relu'))
classifier.add(Conv2D(32, (3, 3), activation='relu'))
classifier.add(MaxPooling2D(pool_size=(2, 2)))
classifier.add(Dropout(0.5)) # antes era 0.25
# Adding a second convolutional layer
classifier.add(Conv2D(64, (3, 3), padding='same', activation = 'relu'))
classifier.add(Conv2D(64, (3, 3), activation='relu'))
classifier.add(MaxPooling2D(pool_size=(2, 2)))
classifier.add(Dropout(0.5)) # antes era 0.25
# Adding a third convolutional layer
classifier.add(Conv2D(64, (3, 3), padding='same', activation = 'relu'))
classifier.add(Conv2D(64, (3, 3), activation='relu'))
classifier.add(MaxPooling2D(pool_size=(2, 2)))
classifier.add(Dropout(0.5)) # antes era 0.25
# Step 3 - Flattening
classifier.add(Flatten())
# Step 4 - Full connection
classifier.add(Dense(units = 512, activation = 'relu'))
classifier.add(Dropout(0.5)) 
classifier.add(Dense(units = 3, activation = 'softmax'))

用Keras可视化卷积神经网络中的激活

对于这种类型的图像,我可能会构建一种过于复杂的结构,而一旦我们考虑了该特征图,将会很明显,但是,对本文而言,它有助于我准确地展示每一层所做的工作。我相信我们可以用更少的层次和更少的复杂性得到相同或更好的结果。

我们来看看我们的神教网络模型摘要:

classifier.summary()

用Keras可视化卷积神经网络中的激活

我们使用rmsprop优化器编译模型,categorical_crossentropy作为我们的损失函数,并指定我们想要跟踪的度量accuracy:

# Compiling the CNN
classifier.compile(optimizer = 'rmsprop',
 loss = 'categorical_crossentropy', 
 metrics = ['accuracy'])

用Keras可视化卷积神经网络中的激活

此时,我们需要将图片转换为模型能够接受的形状。为此,我们使用ImageDataGenerator。我们初始化它并使用.flow_from_directory来提供图像。工作目录中有两个主文件夹,称为training_set和test_set。每个文件夹都有3个子文件夹,分别是circles, squares和triangles。我向training_set发送70张每种形状的图片,向test_set发送30张。

train_datagen = ImageDataGenerator(rescale = 1./255)
test_datagen = ImageDataGenerator(rescale = 1./255)
training_set = train_datagen.flow_from_directory('training_set',
 target_size = (28,
 28),
 batch_size = 16,
 class_mode =
 'categorical')
test_set = test_datagen.flow_from_directory('test_set',
 target_size = (28, 28),
 batch_size = 16,
 class_mode =
 'categorical')

用Keras可视化卷积神经网络中的激活

该模型将训练30个epochs,但我们将用ModelCheckpoint存储表现最佳时期的权重。我们将指定val_acc用作定义最佳模型的度量标准。

checkpointer = ModelCheckpoint(filepath="best_weights.hdf5", 
 monitor = 'val_acc',
 verbose=1, 
 save_best_only=True)

用Keras可视化卷积神经网络中的激活

训练神经网络模型

现在是时候训练模型,Python代码如下:

history = classifier.fit_generator(training_set,
 steps_per_epoch = 100,
 epochs = 20,
 callbacks=[checkpointer],
 validation_data = test_set,
 validation_steps = 50)

用Keras可视化卷积神经网络中的激活

这个神经网络模型训练了20个epochs,在第10个epoch达到了最佳表现。我们得到以下信息:

“Epoch 00010: val_acc improved from 0.93333 to 0.95556, saving model to best_weights.hdf5”

在那之后,模型对于下一个epoch没有改进,所以epoch 10的权重是存储的,这意味着我们现在有了一个hdf5文件,它存储了特定epoch的权重,测试集的准确率是95.6%。

我们将确保我们的分类器加载了最佳权重

classifier.load_weights('best_weights.hdf5')

最后,让我们保存最终模型以供日后使用:

classifier.save('shapes_cnn.h5')

在训练期间显示损失和准确度的曲线

现在让我们来看看我们的模型在30个epochs中的表现:

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()

用Keras可视化卷积神经网络中的激活

用Keras可视化卷积神经网络中的激活

我们可以看到,在第10 epoch之后,模型开始过度拟合。

  • circles: 0
  • squres: 1
  • triangles: 2

预测图像

有了我们的模型训练和存储,我们可以从我们的测试集加载一图像,看看它是如何分类的:

img_path = 'test_set/triangles/drawing(2).png'
img = image.load_img(img_path, target_size=(28, 28))
img_tensor = image.img_to_array(img)
img_tensor = np.expand_dims(img_tensor, axis=0)
img_tensor /= 255.
plt.imshow(img_tensor[0])
plt.show()
print(img_tensor.shape)

用Keras可视化卷积神经网络中的激活

用Keras可视化卷积神经网络中的激活

# predicting images
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
images = np.vstack([x])
classes = classifier.predict_classes(images, batch_size=10)
print("Predicted class is:",classes)
> Predicted class is: [2]

用Keras可视化卷积神经网络中的激活

预测是类[2],它是一个三角形。

到现在为止还挺好。我们现在进入本文最重要的部分

可视化intermediate activations

intermediate activations“对于理解连续的卷积网层如何转换其输入以及了解各个卷积filters的含义是有用的。”

convnets学习的表示方式非常适合于可视化,这在很大程度上是因为它们是视觉概念的表示方式。可视化中间激活包括显示各种卷积和池化层在网络中输出的特征映射,给定一定的输入(一个层的输出通常称为激活,激活函数的输出)。这提供了一个关于输入如何分解成网络学习的不同过滤器的视图。每个通道编码相对独立的特征,因此,可视化这些特征图的正确方法是将每个通道的内容独立绘制成二维图像。

接下来,我们将获得一个输入图像 - 一个三角形的图片,而不是网络训练过的图像的一部分。

“为了提取我们想要查看的特征图,我们将创建一个Keras模型,该模型将批量图像作为输入,并输出所有卷积和池化层的激活。为此,我们将使用Keras的Model类。使用两个参数来实例化模型:输入张量(或输入张量列表)和输出张量(或输出张量列表)。生成的类是Keras模型,就像Sequential模型一样,将指定的输入映射到指定的输出。Model类与众不同之处在于,与Sequential不同,它允许具有多个输出的模型。“

从输入张量和输出张量列表中实例化模型

layer_outputs = [layer.output for layer in classifier.layers[:12]] 
# Extracts the outputs of the top 12 layers
# Creates a model that will return these outputs, given the model input
activation_model = models.Model(inputs=classifier.input, outputs=layer_outputs)

用Keras可视化卷积神经网络中的激活

输入图像输入时,此模型返回原始模型中层激活的值。

在预测模式下运行模型

activations = activation_model.predict(img_tensor)

# Returns a list of five Numpy arrays: one array per layer activation

例如,这是图像输入的第一个卷积层的激活:

first_layer_activation = activations[0]
print(first_layer_activation.shape)
(1, 28, 28, 32)

用Keras可视化卷积神经网络中的激活

它是一个28×28的32个通道的特征映射。让我们尝试绘制原始模型第一层激活的第四个通道

plt.matshow(first_layer_activation[0, :, :, 4], cmap='viridis')

用Keras可视化卷积神经网络中的激活

甚至在我们尝试解释这种激活之前,让我们在每一层上绘制同一图像的所有激活

在每个intermediate activation中可视化每个通道

layer_names = []
for layer in classifier.layers[:12]:
 layer_names.append(layer.name) # Names of the layers, so you can have them as part of your plot
 
images_per_row = 16
​
for layer_name, layer_activation in zip(layer_names, activations): # Displays the feature maps
 n_features = layer_activation.shape[-1] # Number of features in the feature map
 size = layer_activation.shape[1] #The feature map has shape (1, size, size, n_features).
 n_cols = n_features // images_per_row # Tiles the activation channels in this matrix
 display_grid = np.zeros((size * n_cols, images_per_row * size))
 for col in range(n_cols): # Tiles each filter into a big horizontal grid
 for row in range(images_per_row):
 channel_image = layer_activation[0,
 :, :,
 col * images_per_row + row]
 channel_image -= channel_image.mean() # Post-processes the feature to make it visually palatable
 channel_image /= channel_image.std()
 channel_image *= 64
 channel_image += 128
 channel_image = np.clip(channel_image, 0, 255).astype('uint8')
 display_grid[col * size : (col + 1) * size, # Displays the grid
 row * size : (row + 1) * size] = channel_image
 scale = 1. / size
 plt.figure(figsize=(scale * display_grid.shape[1],
 scale * display_grid.shape[0]))
 plt.title(layer_name)
 plt.grid(False)
 plt.imshow(display_grid, aspect='auto', cmap='viridis')

用Keras可视化卷积神经网络中的激活

用Keras可视化卷积神经网络中的激活

用Keras可视化卷积神经网络中的激活

用Keras可视化卷积神经网络中的激活

让我们试着解释发生了什么:

  • 第一层可以保留三角形的完整形状,尽管有几个过滤器未被激活并留空。在那个阶段,激活几乎保留了初始图片中的所有信息。
  • 随着我们在各个层面的深入,激活变得越来越抽象,在视觉上也不易解释。他们开始编码更高级别的概念,如单边框,角落和角度。较高的呈现携带关于图像的视觉内容的信息越来越少,并且越来越多的信息与图像的类别相关。
  • 如上所述,模型结构过于复杂,以至于我们可以看到我们的最后一层实际上根本没有激活,在这一点上没有什么需要学习的。

相关推荐