这是我参与更文挑战的第 23 天,活动详情查看: 更文挑战
甜点
深度学习中神经网推动计算机视觉,在分类和目标检测上取得优益成绩。打了一个漂亮翻身战,再次推动机器学习向前迈出一大步。我们看到了将神经元各种方式组合就可以完成一些让人惊叹的成绩。机器是如何做到的呢?机器是如何一步一步学习到呢?今天我们来揭开卷积神经网络如何通过特征图在图像上提取特征来完成图像分类的任务的。
参考 Deep Learning with Python
我们将通过 3 个例子来一步一步逐步了解机器是如何识别图像
-
在卷积神经网中间过程,输出每层卷积输出,也就是机器每个特征图表示机器具体在不同卷积层都看到了什么,他们更专注什么样的特征
-
视觉化神经网络的特征图通道,通过输入不同图片然后查看拿个通道找到一个特征图敏感的图
-
将一张图片给神经网,通过热力图表示神经网络通过图像哪个区域识别出图像中物体是什么类别
from keras.models import load_model
我们使用 models/cats_and_dogs_small_2.h5
猫狗分类的模型,这个模型是我们之前训练好的模型,用于识别图片中出现的是狗狗还是猫猫。
model = load_model('models/cats_and_dogs_small_2.h5')
在神经网络中,需要弄清楚每一层对 tensor 形状的变化,也就是每一层输入和输出 tensor 形状,这个看似简单,要是能够完全掌握看到网络结构就能快速说出 tensor 的形状也非易事。通过 keras 提供 API 我们能够清楚了解到每一层输出图片样子
model.summary()
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_21 (Conv2D) (None, 148, 148, 32) 896
_________________________________________________________________
max_pooling2d_21 (MaxPooling (None, 74, 74, 32) 0
_________________________________________________________________
conv2d_22 (Conv2D) (None, 72, 72, 64) 18496
_________________________________________________________________
max_pooling2d_22 (MaxPooling (None, 36, 36, 64) 0
_________________________________________________________________
conv2d_23 (Conv2D) (None, 34, 34, 128) 73856
_________________________________________________________________
max_pooling2d_23 (MaxPooling (None, 17, 17, 128) 0
_________________________________________________________________
conv2d_24 (Conv2D) (None, 15, 15, 128) 147584
_________________________________________________________________
max_pooling2d_24 (MaxPooling (None, 7, 7, 128) 0
_________________________________________________________________
flatten_6 (Flatten) (None, 6272) 0
_________________________________________________________________
dropout_3 (Dropout) (None, 6272) 0
_________________________________________________________________
dense_11 (Dense) (None, 512) 3211776
_________________________________________________________________
dense_12 (Dense) (None, 1) 513
=================================================================
Total params: 3,453,121
Trainable params: 3,453,121
Non-trainable params: 0
_________________________________________________________________
我们想要知道当我们输入一张 cat 图片给机器时,机器都看到了什么,以及机器是如何通过图片进行提取各种各样特征图来进行识别的。一层一层的特征图变动越来越抽象,我们人类可能无法通过输出图来分辨出这是一只 cat 时候,机器却利用这些特征图判断图像中有一只 cat。
from keras.preprocessing import image
import numpy as np
img_path = 'data/cat_001.jpg'
img = image.load_img(img_path,target_size=(150,150))
img_tensor = image.img_to_array(img)
img_tensor = np.expand_dims(img_tensor,axis=0)
img_tensor /= 255.
使用 keras 提供的图片工具加载图片,然后将图片转换为 tensor ,用 np 的 expand_dims 添加一个表示批次的维度,最后除以 255 进行归一化,这一切工作都是图片转换为神经网络接受的格式。
print(img_tensor.shape)
(1, 150, 150, 3)
import matplotlib.pyplot as plt
plt.imshow(img_tensor[0])
plt.show()
from keras import models
加载模型之后,通过 summary 我们发现其结构前 8 层分别卷积层和池化层交替出现,所以我们要看一下图片经过这些层特征图输出的是什么图,从而就知道了机器在这一层找到 cat 图哪些特征。
layer_outputs = [layer.output for layer in model.layers[:8]]
activation_model = models.Model(inputs=model.input,outputs=layer_outputs)
创建一个模型,输入是一张图片的 tensor ,输出为模型模型 8 个层的输出,也就是输入一个 (1, 150, 150, 3) tensor 我们会的模型各个层的输出。
activations = activation_model.predict(img_tensor)
我们将事先准备好的图片输入到模型中,然后返回就得到模型 8 层输出
first_layer_activation = activations[0]
print(first_layer_activation.shape)
(1, 148, 148, 32)
然后我们将第一层也就是编号 0 的层输出拿出来观察一下。输出结果和在 summary 中第一层输出结果相同。
plt.matshow(first_layer_activation[0,:,:,3],cmap='viridis')
plt.show()
然后先第一层输出 tensor 输出来观察一下,
plt.matshow(first_layer_activation[0,:,:,16],cmap='viridis')
plt.show()
plt.matshow(first_layer_activation[0,:,:,31],cmap='viridis')
plt.show()
然后将所有层都输出一下来观察一下机器在各个层都看到了什么,也就是机器通这些特征学习到了什么。
# 搜集层名称
layer_names = []
# 遍历模型的层
for layer in model.layers[:8]:
# 获取模型层的名称
layer_names.append(layer.name)
# 每一行绘制 16 张图,注意这里 row 表示列
images_per_row = 16
# activations 为每一层输出 这里简单解释 zip ,zip 有点像拉拉链将两组数据对应位置上的数据放在一起作为一组数据
for layer_name,layer_activation in zip(layer_names,activations):
# 每一层输出层的最后一个维度 (None, 148, 148, 32) 的 32 也就是每一层通道数
n_features = layer_activation.shape[-1]
# size 就是特征图的大小
size = layer_activation.shape[1]
# 也就是计算行数 n_cols 这里表示行
n_cols = n_features // images_per_row
# 每一张图,然后给每一张图填满 0,我们以第一层为例 32 // 16 = 2 [148 * 2 ,148 * 16]
display_grid = np.zeros((size * n_cols,images_per_row * size))
# 填充像素 n_cols [0,1] rows = [0,15]
for col in range(n_cols):
for row in range(images_per_row):
channel_image = layer_activation[0,:,:,col*images_per_row + row]
# 预处理图片便于我们观察
channel_image -= channel_image.mean()
channel_image /= channel_image.std()
# 期望值是 128 标准差为 64
channel_image *= 64
channel_image += 128
# 控制图片每一个像素大小范围
channel_image = np.clip(channel_image,0,255).astype('uint8')
# 绘制图片
display_grid[col * size : (col + 1) * size,
row * size : (row + 1) * size] = channel_image
scale = 1. /size
# 这里 width height in inches
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')
plt.show()
前 2 块都是 32 通道个特征图,逐一将这些通道输出图显示出来,这里因为特征卷积基本都是一些简单纹理所以我们还可以清晰看出猫样子。
接下来 2 块是 64 通道的卷积层,与前面的经过特征图输出图片略显有一些抽象。
随后在经过一些特征层输出图片已经看不出猫样子,甚至有些位置上特征层通道已经没有输出了,这些特征通道没有找到他们想要的东西。
在神经网络前几层中,我们还可以清晰地看出猫的模样,在这些层中神经网络主要观察图片的边缘,特征层基本都是一些纹理之类。随着层数的增加神经网将猫的一些特征进行抽象,例如猫的眼睛和猫的耳朵,虽然我们似乎很难看出猫的模样,但并不是完全看不出来,这个阶段机器是在对猫的特征进行归类。我们可能已经在高层的神经网络中发现了一些空白的滤镜。这是因为这些滤镜没有找到他们想要特征。 机器在学习过程中逐渐过滤掉一些无用信息,例如毛发细节等,保留猫的耳朵、眼睛和嘴巴这些用于识别猫的特征信息。例如我们人类在识别图片,如果让我们画一个动物,我们也是通过抓住这些动物特征,而忽略一些细节,让我们一眼看出来这就是猫。