我正在为通过Keras创建的卷积神经网络生成热图,如这里所描述。当我为普通的VGG16
网络运行该算法时,热图看起来很好:
然后我基于那个VGG16
网络创建了自己的自定义模型,但顶层是自定义的:
input_layer = layers.Input(shape=(img_size, img_size, 3), name="model_input")vgg16_base = VGG16(weights="imagenet", include_top=False, input_tensor=input_layer)temp_model = vgg16_base.outputtemp_model = layers.Flatten()(temp_model)temp_model = layers.Dense(256, activation="relu")(temp_model)temp_model = layers.Dense(1, activation="sigmoid")(temp_model)custom = models.Model(inputs=input_layer, outputs=temp_model)
然而,当我为我自己的自定义网络的同一层(即来自VGG16
基础的最后一个卷积层,是我新网络的一部分)生成热图时,使用完全相同的代码/函数,热图看起来不对:
我的自定义网络的验证/测试准确率在97-98%,所以我认为它运行良好。那么为什么激活/热图如此不准确呢?还是我遗漏了其他东西?
PS:供您参考,热图是通过这里列出的函数创建的。它的调用方式如下:
# 从磁盘加载图像并通过Keras工具进行预处理img_path = "/path/to/image.jpg"img = image.load_img(img_path, target_size=(224, 224))img_tensor = image.img_to_array(img)img_tensor = np.expand_dims(img_tensor, axis=0)img_tensor = preprocess_input(img_tensor)# 在这一点上,我要么直接加载VGG16模型(热图工作),# 要么创建我自己的基于VGG16的自定义模型(热图不工作)# 然后模型本身存储在变量“model”中preds = model.predict(img_tensor)model_prediction = model.output[:, np.argmax(preds[0])]# 然后我调用上述引用的自定义函数input_layer = model.get_layer("model_input")conv_layer = model.get_layer("block5_conv3")plot_conv_heat_map(model_prediction, input_layer, conv_layer, img_tensor, img_path)
回答:
简短回答: 由于您在训练阶段将狗标记为1,猫标记为0,您需要将model_prediction
替换为1 - model_prediction
以找到与猫相关的区域:
plot_conv_heat_map(1 - model_prediction, ...)
详细回答: 当您使用原始的VGG模型时,最后一层有1000个神经元(假设您使用的是预训练的ImageNet模型),每个神经元对应1000个不同类别中的一个:
# VGG模型中的最后一层x = layers.Dense(classes, activation='softmax', name='predictions')(x)
这些神经元的输出值从零到一(输出总和必须为1)。所以最活跃的神经元(即输出值最高的神经元)对应于预测的类别。因此,您可以这样找到它:
model_prediction = model.output[:, np.argmax(preds[0])] \ \___ 找到输出值最大的神经元的索引
然后您将其传递给可视化函数,以计算相对于所选卷积层的梯度,并可视化热图:
plot_conv_heat_map(model_prediction, ...)
到目前为止,一切顺利。然而,在您的自定义模型中,您将问题从多类分类任务转换为二元分类任务,即狗与猫。您使用一个带有单个单元的sigmoid层作为最后一层,并将神经元的活跃状态(即接近1的输出)视为狗,将不活跃状态(即接近0的输出)视为猫。因此,您的网络本质上是一个狗检测器,如果没有狗,我们就假设图像中有猫。
好的,您可能会问“这有什么问题吗?”答案是就模型的训练而言没有问题,正如您所建议的,您得到了很好的训练准确率。但是,请记住可视化函数背后的假设:它以输出最高的神经元作为输入,这对应于图像中检测到的类别。因此,当您给自定义模型一个猫的图像时,最后一层的输出将是一个非常低的数字,比如0.01。所以这个数字的一个解释是,这张图像是狗的概率为0.01。那么当您直接将其传递给您的可视化函数时会发生什么呢?是的,您猜对了:它会找到图像中与狗最相关的区域!您可能还会反对说“但我给它的是猫的图像!!!”这没关系,因为那个神经元在有狗时会激活,因此当您相对于卷积层取其梯度时,与狗最相关的区域将在热图中得到高度展示。然而,如果您给模型一个狗的图像,可视化将是正确的。
“那么当我们想要可视化与猫最相关的区域时,我们应该怎么做呢?”很简单:只需让那个神经元成为猫的检测器。“怎么做?”只需创建它的补码:1 - model_prediction
。这给您的是图像中存在猫的概率。您可以像这样轻松地使用它来绘制与猫相关的区域:
plot_conv_heat_map(1 - model_prediction, ...)
或者,您可以更改模型的最后一层,让它有两个带有softmax
激活的神经元,然后重新训练它:
temp_model = layers.Dense(2, activation="softmax")(temp_model)
这样,每个类别,即狗和猫,都将有自己的神经元,因此在可视化激活热图时不会出现问题。