卷积神经网络构建,新图片预测与类激活图——提高CNN模型的可解释性
文章目录
前言
卷积神经网络(CNN)作为最基础的图像分类与识别的深度学习模型,在很多领域都有着应用,例如人脸识别,医学图像分类等。本文从CNN出发,介绍基本CNN模型的构建,逐渐深入到复杂CNN变体VGG16的搭建。同时为了提高CNN这类模型的可解释性,使用已保存的CNN和VGG16模型预测批量的新图片,借助类激活图(CAM, class activation map) 对分类结果进行解释。最后绘制受试者工作特性曲线(ROC),对整体预测结果进行分析。
一、卷积神经网络CNN与VGG16 介绍
这部分只做卷积神经网络(CNN)和VGG16的简单介绍,使读者有个基本的了解,具体详细原理及参数请参考其他资料。CNN模型主要结构包括输入层、卷积层、池化层、全连接层和输出层。其中卷积层和池化层是CNN模型关键的功能层。卷积层的作用是对图片进行卷积,从中提取特征。卷积层由不同卷积核组成,常用3 * 3大小卷积核,不同卷积层提取不同特征,例如有的提取边界线条、有的提取整个色块。形象的说,卷积核就像扫描仪,对图片各部分进行扫描,但是这个扫描仪只对它感兴趣的形状重视,不重视的形状重视程度降低。而池化层则是为了降低卷积层提取的特征维度,常见池化的方式包括最大池化和平均池化,池化常用的窗口大小为2 * 2,移动步幅为2。全连接层则是为了将得到的特征展平,方便使用激活函数进行分类。VGG16则是一个16层的卷积神经网络(不包括最大池化层与softmax层),是牛津大学视觉几何团队开发的,目前也常被用于图像分类的任务中。
二、搭建CNN模型
本次代码在python3.7版本的tensroflow框架下搭建,使用了Jupyter notebook来运行。主要任务是对实现猫、狗、兔图片进行多分类,其中训练集数据各有200张,验证集各50张左右,测试集新图片各10张。需要说明的是,本文只是为了说明模型搭建和结果分析,固未使用更多数据,也没有进行更多的调优。读者可根据实际情况进行数据量的扩充和模型的调优。
1.输入数据与图片增强
代码如下():
import os #输入数据
from glob import glob
train_dir = 'D:/AM Learning/data/kaggle/3categories/train' #训练集的保存路径
test_dir = 'D:/AM Learning/data/kaggle/3categories/validation' #验证集的保存路径
nb_train_samples = 600 #训练集的图片总数量
nb_validation_samples = 161 验证集的图片数量
epochs = 25
batch_size = 20
from keras.preprocessing.image import ImageDataGenerator #对图片进行处理
#(通过数据增强对图片进行变换)
train_datagen = ImageDataGenerator(rescale=1./255,rotation_range =40, #(训练集图片增强)
width_shift_range=0.2, height_shift_range=0.2,
shear_range=0.2,zoom_range=0.2,horizontal_flip=True)
test_datagen = ImageDataGenerator(rescale=1./255) #(验证集图片像素标准化)
train_generator = train_datagen.flow_from_directory(train_dir,
target_size=(150, 150), #(图片大小)
batch_size=batch_size,
class_mode='categorical') #(分类类别,二分类对应binary,多分类对应categorical)
validation_generator = test_datagen.flow_from_directory(test_dir,
target_size=(150, 150),
batch_size=batch_size,
class_mode='categorical')
2.搭建CNN模型与编译
其中模型的卷积层层数、卷积核数等可根据读者需求调整,代码如下:
from keras import models
from keras import layers
from keras import optimizers
model = models.Sequential()
model.add(layers.Conv2D(16,(3,3),activation='relu',input_shape=(150,150,3)))#16个卷积核,每个卷积核大小为3*3
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(32, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
#model.add(layers.Dropout(0.5))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(3, activation='softmax')) #数字3表示图片的类别,用于分类的激活函数二分类使用sigmoid,多分类单标签使用softmax
#编译模型
model.compile(optimizer=optimizers.RMSprop(lr=1e-5), #设置激活函数和学习率,可以根据实际需要调整
loss='categorical_crossentropy', #误差,二分类对应binary_crossentorpy
metrics=['acc'])
#查看模型结构及内部参数量
model.summary()
#打印不同类别对应的标签
print(train_generator.class_indices)
3.训练模型并保存
#训练模型
history = model.fit_generator(train_generator, #利用批量生成器拟合模型
steps_per_epoch=nb_train_samples/batch_size,
epochs=25,
validation_data=validation_generator,
validation_steps=nb_validation_samples/batch_size)
#以“3categories”的名字保存模型
model.save('3categories.h5')
4.输出训练结果
#打印训练集结果与验证集结果
import matplotlib.pyplot as plt
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, 'ro', label = 'Training loss')
plt.plot(epochs, val_loss, 'r', label = 'Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
训练集和验证及结果如图:
四、批量新图片的分类及类激活图
在训练好CNN模型之后,需要在新的图片上验证其泛化能力,即新数据上分类或预测的准确率。本次还借助类激活图对CNN模型的结果进行了分析。类激活图是指对输入图像生成类激活的热力图,具体方法是对输入图像的一层卷积层提取的特征图,用类别相对于通道的梯度对这个特征图中的每个通道进行加权。直观来说,就是用“每个通道对类别的重要程度”对“输入图像对不同的通道的激活强度”的空间进行加权,从而得到了“输入图像对类别的激活强度”的空间图。
1.导入模型和要分类的图片
import keras
from tensorflow.keras.applications import VGG16
#导入已保存地CNN模型
model = keras.models.load_model('3categories.h5')
# 导入新图片(使用目标图像的本地路径)
img_path = 'D:/AM Learning/data/kaggle/3categories/test/1/dog.19.jpg'
2.实现分类并输出结果
from tensorflow.keras.preprocessing import image
from keras.preprocessing.image import img_to_array
from keras.applications.vgg16 import preprocess_input
import numpy as np
import cv2
# (大小为 150×150 的Python图像库(PIL,Python imaging library)图像)
img = cv2.imread(img_path)
# x形 状 为 (150, 150, 3) 的float32 格式的 Numpy 数组 ,保持和训练时用的图片尺寸大小相同
img_arr = cv2.resize(img,(150,150))
ima_arr = img_to_array(img_arr)/255.0
img_arr = np.expand_dims(img_arr,axis=0)
img_arr=preprocess_input(img_arr)
#预测目标分类
preds = model.predict(img_arr)
proba = np.max(preds)
print('Predicted:',preds) #不同类别的可能性
print('prob:',proba) #最终预测的图片类别对应的可能性大小
3.绘制类映射图
from keras import backend as K
index=np.argmax(preds[0])
cat_output = model.output[:, index]
# conv2d_4层的输出特征图,它是模型的最后一个卷积层,读者根据本身实际情况调整卷积层的名字
last_conv_layer = model.get_layer('conv2d_4')
# “图片”类别相对于 conv2d_4输出特征图的梯度
grads = K.gradients(cat_output, last_conv_layer.output)[0]
# 形状为 (512,) 的向量,每个元素是特定特征图通道的梯度平均大小
pooled_grads = K.mean(grads, axis=(0, 1, 2))
# 访问刚刚定义的量:对于给定的样本图像,pooled_grads 和 block5_conv3 层的输出特征图
iterate = K.function([model.input], [pooled_grads, last_conv_layer.output[0]])
# 对于样本图像, 这两个量都是 Numpy 数组
pooled_grads_value, conv_layer_output_value = iterate([img_arr])
# 将特征图数组的每个通道乘以“这个通道 对类别的重要程度”
for i in range(conv_layer_output_value.shape[-1]):
conv_layer_output_value[:, :, i] *= pooled_grads_value[i]
# 得到的特征图的逐通道平均值即为类激活的热力图
heatmap = np.mean(conv_layer_output_value, axis=-1)
import matplotlib.pyplot as plt
max_heat = np.max(heatmap)
if max_heat == 0:
max_heat = 1e-10
heatmap /= max_heat
plt.matshow(heatmap)
plt.show()
选择了两张新图片测试,结果如下图。可以看见“狗”这个类别分类时更关注“黑色”和和“嘴”这两个特征,而兔子更关注”身体“和“白色”这两个特征。当然不同的训练模型会有不同的结果,这里只说原理,不详细讨论结果。具体问题具体分析。
结论
本文阐述了卷积神经网络CNN的搭建过程,并对新的图片进行预测。同时还阐述了类激活图的搭建预分析。但是还存在以下问题:(1)进行新图片预测时,只能单张的预测,效率较低,应该考虑批量图片的预测;(2)图片数据量较少时,预测准确率较低,应考虑较复杂的模型。以上这两个问题将会在后面进行讨论。
本文参考了《python深度学习》与部分网友的思路,若侵权请联系本人。