寒假博客日记——第九天

昨天摆了一天,今天也没学很多东西,但是都是以前没接触过的方法,也算是学有所成吧。为了更有针对性和效率一点,尝试把第二天的目标写出来,分为基础目标和进阶目标,基础目标是至少要完成的,进阶目标是比较难完成的。当然这都只是估算,不一定切合实际,但基础目标也仍要尽力完成。明天的基础目标:把RNN这一章学(其实是复习)完。进阶目标:把文本处理这一章学完transformer。

可视化卷积核

输入一张随机图像,然后利用梯度上升方法修改图像的数值,最终可以生成使得卷积核激活值最大的图像,由此就可以知道这个卷积核对什么样的输入最敏感,即:这个卷积核是检测什么物品的。

先获取一个Xception模型的中间特征输出:

import keras
import tensorflow.keras as tf_keras
import tensorflow as tf
import numpy as np
from keras.layers import *
from keras.utils.vis_utils import plot_model

model=tf_keras.applications.xception.Xception(include_top=False,input_shape=(200,200,3))

print(model(np.random.random((1,200,200,3))))

for layer in model.layers:
    if isinstance(layer,(Conv2D,SeparableConv2D)):
        print(layer.name)
# 查看每个卷积和可分离卷积的名字

# plot_model(model,'Xception.png',dpi=1000,show_shapes=True)
# # 打印模型结构

layer_name='block3_sepconv1'
layer=model.get_layer(layer_name)
feature_extractor=keras.Model(model.input,layer.output)
# 特征提取器模型,输出第三个块的第一个可分离卷积

# activation=feature_extractor(tf_keras.applications.xception.preprocess_input(img_tensor))
# 使用特征提取器的方法

然后编写自定义损失函数:滤波器输出的图像的平均值

def compute_loss(image,filter_index): # 损失函数接受图像张量和感兴趣的滤波器索引(整数)
    activation=feature_extractor(image) # 获取模型输出
    filter_activation=activation[:, 2:-2, 2:-2, filter_index]
    # 取该滤波器输出的值,并舍弃了激活边界的两个像素,这是为了避免边界伪影
    return tf.reduce_mean(filter_activation)

编写梯度上升方法:

@tf.function
def gradient_ascent_step(image,filter_index,learning_rate):
    with tf.GradientTape() as tape:
        tape.watch(image) # 因为image不是一个Variable,所以要手动监控
        loss=compute_loss(image,filter_index) # 计算损失标量

    grads=tape.gradient(loss,image)  # 求dloss/dimage,最大化损失函数
    grads=tf.math.l2_normalize(grads)  # 将梯度l2规范化,保证每轮的梯度大小适当
    image+=learning_rate*grads  # 修改图像,让其滤波器的输出更大
    return image

编写生成最终图像的函数:

img_width=200
img_height=200

def genrate_filter_pattern(filter_index):
    iterations=30 # 迭代次数
    learning_rate=10. # 学习率
    image=tf.random.uniform(minval=0.4,maxval=0.6,shape=(1,img_width,img_height,3))

    for i in range(iterations):
        image=gradient_ascent_step(image,filter_index,learning_rate)

    return image[0].numpy()

将上面生成的array类型转换为可见的图像:

def deprocess_image(image):
    # 将张量转换为有效图像

    image-=image.mean()
    image/=image.std()
    # 标准化

    image*=64
    image+=128 # 使图像服从均值为128,方差为64的分布
    image=np.clip(image,0,255).astype('uint8') # 转换为图像的像素范围
    image=image[25:-25,25:-25,:] # 避免伪影
    return image

生成一个图像:

import matplotlib.pyplot as plt

plt.axis('off')
plt.imshow(deprocess_image(genrate_filter_pattern(filter_index=2)))
plt.savefig(f'{layer_name}的第二个通道的最大相应模式',dpi=1000)

在这里插入图片描述

这个卷积核对该图像的相应最大,有点像水或者毛皮。

书上对多个深度可分离的卷积层进行了实验,得出结论:浅层的滤波器对应简单的方向边缘和颜色,深层的滤波器对应类似自然图像中的纹理,比如羽毛、眼睛、树叶等。但是我跑出来的结果没用这么成功,不贴图了。

类激活热力图的可视化

这是一个可视化模型的好方法,可以获取模型对图像的哪部分感兴趣。方法是求最大预测概率通道对最后一个卷积层的梯度,假设最后一个卷积层的尺寸为20,20,2048那么求出来的梯度也是20,20,2048。然后将梯度汇聚,即求平均值,得到一个2048的向量,让这个向量乘以最后一个卷积层的输出,该输出是20,20,2048。然后再对逐通道求平均值,得到20,20的类激活热力图。将该图归一化,然后resize后与原图相加,再归一化,然后映射到0,255的整数区间,就可以知道模型对哪一部分感兴趣了。下面这篇博客写得很好:

(1条消息) Grad-CAM简介_太阳花的小绿豆的博客-CSDN博客

直接贴代码:

model=tf_keras.applications.xception.Xception()
plot_model(model,'非洲象测试模型.png',True)

img_path=tf_keras.utils.get_file(fname='elephant.jpg',origin='https://img-datasets.s3.amazonaws.com/elephant.jpg')
# 下载图像,保存在img_path路径下

def get_img_array(img_path,target_size):
    img=tf_keras.utils.load_img(img_path,target_size=target_size) # 返回一张尺寸为299*299的PLT图像
    array=tf_keras.utils.img_to_array(img) # 返回一个299,299,3的float32的np数组
    array=np.expand_dims(array,axis=0) # 增加一个维度,变成1,299,299,3
    array=tf_keras.applications.xception.preprocess_input(array)
    return array

img_array=get_img_array(img_path,(299,299))

preds=model.predict(img_array)
print(tf_keras.applications.xception.decode_predictions(preds,top=3)[0])

last_conv_layer_name='block14_sepconv2_act' # 将输入图像映射到最后一个卷积层的激活值
classifier_layer_names=['avg_pool','predictions'] # 整个模型的最后两层,全局池化和预测层

last_conv_layer=model.get_layer(last_conv_layer_name)
last_conv_layer_model=keras.Model(model.inputs,last_conv_layer.output) # 从输入图像到最后一个卷积层激活值的映射模型

# 创建一个模型,将最后一个卷积层的激活值映射到最终的预测类别
classifier_input=keras.Input(shape=last_conv_layer.output_shape[1:])
x=classifier_input
for layer_name in classifier_layer_names:
    x=model.get_layer(layer_name)(x)
classifier_model=keras.Model(classifier_input,x)

# 以上将模型分成了两个部分,前面的卷积部分和后面的分类部分

with tf.GradientTape() as tape:
    last_conv_layer_output=last_conv_layer_model(img_array) # 计算最后一个卷积层的激活值
    tape.watch(last_conv_layer_output) # 让梯度带监控该激活值

    preds=classifier_model(last_conv_layer_output) # 分类器的输入是卷积层的输出
    top_pred_index=tf.argmax(preds[0]) # 获取最大概率对应的索引
    top_class_channel=preds[:,top_pred_index]
    # 检索与最大概率类别对应的激活通道,即从1000个输出通道中,检索最大概率的那个通道

grads=tape.gradient(top_class_channel,last_conv_layer_output)
# 计算最大预测类别相对于最后一个卷积层的输出特征图的梯度 d(top_class_channel)/d(last_conv_layer_output)

# 下面对梯度张量进行汇聚和重要性加权,以得到类激活热力图

pooled_grads=tf.reduce_mean(grads,axis=(0,1,2)).numpy()
# 这是一个向量,其中每个元素是某个通道的平均梯度强度,它量化了每个通道对最大预测类别的重要性
last_conv_layer_output=last_conv_layer_output.numpy()[0]

for i in range(pooled_grads.shape[-1]):
    last_conv_layer_output[:,:,i] *= pooled_grads[i]
heatmap=np.mean(last_conv_layer_output,axis=-1)


heatmap=np.maximum(heatmap,0) # 做relu处理
heatmap/=np.max(heatmap) # 归一化
plt.imshow(heatmap)
plt.show()

import matplotlib.cm as cm
img=tf_keras.utils.load_img(img_path)
img=tf_keras.utils.img_to_array(img)

heatmap=np.uint(255*heatmap)

#使用jet颜色图对热力图重新着色
jet=cm.get_cmap('jet')
jet_colors=jet(np.arange(256))[:,:3]
jet_heatmap=jet_colors[heatmap]

#创建一张图表,使其包含重新着色的热力图
jet_heatmap=tf_keras.utils.array_to_img(jet_heatmap)
jet_heatmap=jet_heatmap.resize((img.shape[1],img.shape[0]))
jet_heatmap=tf_keras.utils.img_to_array(jet_heatmap)

#热力图和原始图像叠加,热力图的不透明度为0.4
superimposed_img=jet_heatmap*0.4+img
superimposed_img=tf_keras.utils.array_to_img(superimposed_img)

save_path='elephant_cam.jpg'
superimposed_img.save(save_path)

在这里插入图片描述

热力图
在这里插入图片描述
注意力情况

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

圆大侠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值