昨天摆了一天,今天也没学很多东西,但是都是以前没接触过的方法,也算是学有所成吧。为了更有针对性和效率一点,尝试把第二天的目标写出来,分为基础目标和进阶目标,基础目标是至少要完成的,进阶目标是比较难完成的。当然这都只是估算,不一定切合实际,但基础目标也仍要尽力完成。明天的基础目标:把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)
热力图
注意力情况