python深度学习--卷积神经网络可视化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SunChao3555/article/details/88409470
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import pylab
from pandas import DataFrame, Series
from keras import models, layers, optimizers, losses, metrics
from keras.utils.np_utils import to_categorical
from keras.models import load_model

'''
    人们常说,深度学习模型是“黑盒”,即模型学到的表示很难用人
类可以理解的方式来提取和呈现。
    虽然对于某些类型的深度学习模型来说,这种说法部分正确。
    卷积神经网络学到的表示非常适合可视化,很大程度上是因为它们
是视觉概念的表示
'''
#可视化中间激活:
'''
    #对于给定输入,展示网络中各个卷积层和池化层输出的特征图(
层的输出通常被称为该层的激活,即激活函数的输出)
    #这让我们可以看到输入如何被分解为网络学到的不同过滤器。我
们希望在三个维度对特征图进行可视化:宽度、高度和深度(通道)。
    每个通道都对应相对独立的特征,所以将这些特征图可视化的正确方
法是---将每个通道的内容分别绘制成二维图像
'''
#加载之前保存的模型(small_datasets_cat_dog.py)
model=load_model('cats_and_dogs_small_2.h5')
print(model.summary())
#加载一张输入图像,从之前下载的测试集中选取一张猫的图像
img_path='F:/dogs-vs-cats/cats_and_dogs_small/test/cats/cat.1700.jpg'
from keras.preprocessing import image
img=image.load_img(img_path,target_size=(150,150))#读取图像并转换为150*150大小
img_tensor=image.img_to_array(img)#图像信息转换为(150,150,3)数组
print(img_tensor.shape)
# img_tensor=np.expand_dims(img_tensor,axis=0)#转换为4D张量,
img_tensor=img_tensor.reshape((1,)+img_tensor.shape)#与上面一行作用相同
img_tensor /=255.#归一化到[0,1]
print(img_tensor.shape)#(1, 150, 150, 3)

#显示测试图像
plt.imshow(img_tensor[0])
plt.show()


# 为了要提取要查看的特征图,需要创建一个keras模型,以图像批量作为输入,并输出所有卷积层和池化层的激活。
# 使用keras的Model类(允许模型有多个输出)

#用一个输入张量和一个输出张量列表将模型实例化
layer_outputs=[layer.output for layer in model.layers[:8]]
#提取卷积层(这里为前8层)的输出
activation_model=models.Model(inputs=model.input,outputs=layer_outputs)#创建一个模型,给定模型输入,返回这些输出
#该模型有一个输入和 8 个输出,即每层激活对应一个输出

#以预测模式运行模型
activations=activation_model.predict(img_tensor)
first_layer_activation=activations[0]#第一层激活
print(first_layer_activation.shape)
#将第五个通道可视化
plt.matshow(first_layer_activation[0,:,:,4],cmap='viridis')
plt.show()#这个通道似乎是对角边缘检测器

#将第八个通道可视化
plt.matshow(first_layer_activation[0,:,:,7],cmap='viridis')
plt.show()#通道看起来像是“鲜绿色圆点”检测器,对寻找猫眼睛很有用。


def deprocess_image(x):#张量转换为有效图像
    x-=x.mean()
    x/=(x.std()+1e-5)
    x*=0.1#对张量做标准化,均值为0,标准差为0.1

    x+=0.5
    x=np.clip(x,0,1)#将x裁剪到[0,1]区间

    x*=255
    x=np.clip(x,0,255).astype('uint8')#将x转换为RGB数组
    return x

#将每个中间激活的所有通道可视化
layer_names=[layer.name for layer in model.layers[:8]]
print(layer_names)
images_cols=16#每行展示特征(通道)数目16
for layer_name,layer_activation in zip(layer_names,activations):
    n_features=layer_activation.shape[-1]#通道数
    size=layer_activation.shape[1]#尺寸(宽度/高度)
    # print(size)
    n_rows=n_features // images_cols #行数
    display_grid=np.zeros((size*n_rows,images_cols*size))#将某一通道信息对应放入grid中(比如(1,148,148,3)的某第三通道信息对应着148*148的网格)
    print(display_grid.shape)
    for row in range(n_rows):#将每个过滤器平铺到display_grid中
        for col in range(images_cols):
            channel_image=layer_activation[0,:,:,row*images_cols + col]#通道信息
            channel_image=deprocess_image(channel_image)
            # channel_image-=channel_image.mean()#标准化,使其均值为0
            # channel_image/=channel_image.std()
            # channel_image*=64
            # channel_image+=128
            # channel_image=np.clip(channel_image,0,255).astype('uint8')#将原数组裁剪为范围[0,255]的RGB格式,使其看起来更加美观
            display_grid[row*size:(row+1)*size,col*size:(col+1)*size]=channel_image
    scale=1./size
    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()

# #需要注意的是:
# '''
#     1.第一层是各种边缘探测器的集合。在这一阶段,激活几乎保留了原始图像中的所有信息。
#     2.随着层数的加深,激活变得越来越抽象,并且越来越难以直观地
# 理解。它们开始表示更 高层次的概念,比如“猫耳朵”和“猫眼睛”。
# 层数越深,其表示中关于图像视觉内容的信息就越少,而关于类别的信
# 息就越多
#     3.激活的稀疏度(sparsity)随着层数的加深而增大。在第一层
# 里,所有过滤器都被输入图 像激活,但在后面的层里,越来越多的过
# 滤器是空白的。也就是说,输入图像中找不到这些过滤器所编码的模
# 式。
#
# 深度神经网络可以有效地作为信息蒸馏管道(information distillation pipeline),输入原始数据(如RGB图像),反复对
# 其进行变换, 将无关信息过滤掉(比如图像的具体外观),并放大和
# 细化有用的信息(比如图像的类别)
#     这与人类和动物感知世界的方式类似:人类观察一个场景几秒钟
# 后,可以记住其中有哪些抽象物体(比如自行车、树),但记不住这
# 些物体的具体外观
# '''
# #-------------------------------------------------------------
# #-------------------------------------------------------------
# #-------------------------------------------------------------
# #可视化卷积神经网络的过滤器
# '''
# 想要观察卷积神经网络学到的过滤器,另一种简单的方法是显示每个
# 过滤器所响应的视觉模式。这可以通过在输入空间中进行梯度上升
# 来实现:从空白输入图像开始,将梯度下降应用于卷积神经网络输入
# 图像的值,其目的是让某个过滤器的响应最大化。得到的输入图像是
# 选定过滤器具有最大响应的图像。
#
#     我们需要构建一个损失函数,其目的是让某个卷积层的某个过滤
# 器的值最大化;然后,我们要使用随机梯度下降来调节输入图像的值,
# 以便让这个激活值最大化
# '''
# #为过滤器的可视化定义损失张亮
from keras.applications import VGG16
from keras import backend as K
model=VGG16(
    weights='imagenet',
    include_top=False
)
print(model.summary())
layer_name1='block3_conv1'

#生成过滤器可视化的函数
def generate_pattern(layer_name,filter_index,size=150):
    layer_output=model.get_layer(layer_name).output
    loss=K.mean(layer_output[:,:,:,filter_index])
    # 为了得到损失相对于模型输入的梯度以实现梯度下降。
    # 这里使用Keras的backend模块内置的gradient函数
    # 获取损失相对于输入的梯度
    grads=K.gradients(loss,model.input)[0]#计算这个损失相对于输入图像的梯度,#返回的是一个张量列表(本例中列表长度为1)。因此,只保留第一个元素,它是一个张量
    grads/=(K.sqrt(K.mean(K.square(grads)))+1e-5)#梯度标准化
    #梯度张量除以其 L2 范数(张量中所有值的平方的平均值的平方根)
    #做除法前加上1e–5,以防不小心除以0
    iterate=K.function([model.input],[loss,grads])#返回给定输入图像的损失和梯度
    '''
    ##给定输入图像,它能够计算损失张量和梯度张量的值
    定义一个 Keras 后端函数来实现此方法:
    iterate是一个函数,它将一个 Numpy 张量(表示为长度为 1
    的张量列表)转换为两个 Numpy 张量组成的列表,这两个张量分
    别是损失值和梯度值
    '''
    #通过SGD让损失最大化
    input_img_data=np.random.random((1,size,size,3))*20 +128.#从附加噪声的随机图像开始
    step=1.#步长/学习率
    for i in range(40):  # 执行40次梯度上升,损失最大化
        loss_value, grads_value = iterate([input_img_data])
        input_img_data += grads_value * step
    img=input_img_data[0]
    '''
    #得到的图像张量是形状为(1, 150, 150, 3)的浮点数张量,其取值可能不是[0,255]区间内的整数。
    # 因此,需要调用前面的deprocess_image()函数对这个张量进行后处理,将其转换为可显示的图像
    '''
    return deprocess_image(img)
plt.imshow(generate_pattern(layer_name1,0))
plt.show()#block3_conv1 层第 0 个过滤器响应的是波尔卡点(polka-dot)图案

#生成某一层中所有过滤器响应模式组成的网格
layer_name2='block1_conv1'
size=64#过滤器图像尺寸
margin=5#图像留出的黑色边缘
results = np.zeros((8 * size + 7 * margin, 8 * size + 7 * margin, 3),dtype='uint8')#空图像(全黑色), 用于保存结果
for i in range(8):
    for j in range(8):#8行8列(64个过滤器)
        filter_img=generate_pattern(layer_name2,i+(j*8),size=size)#纵向填充results
        horizontal_start=i*size+i*margin#水平方向开始位置
        horizontal_end=size+horizontal_start
        vertical_start=j*size+j*margin#垂直方向开始位置
        vertical_end=vertical_start+size
        results[horizontal_start:horizontal_end,vertical_start:vertical_end,:]=filter_img#填充
plt.figure(figsize=(20,20))
plt.imshow(results)
plt.show()#类似地可以得到所有层的过滤器可视化图像

# 注意:若出现空白格的问题
'''
#Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers)
#np.zer生成数组的时候应该指定类型为整数。
#上面写的deprocess_image()函数是专门将浮点数张量处理为0-255的。
# results = np.zeros((8 * size + 7 * margin, 8 * size + 7 * margin, 3), dtype='uint8')才可以正常出结果
'''

'''
过滤器可视化--包含卷积神经网络的层如何观察世界的很多信息:
    cnn每一层都学习一组过滤器,以便将其输入表示为过滤器的组合。
    这类似于傅里叶变换将信号分解为一组余弦函数的过程。随着层数的加深,卷积神经网络中的过滤器变得越来越复杂,越来越精细。
    ###模型第一层(block1_conv1)的过滤器对应简单的方向边缘和颜色(还有一些是彩色边缘)
    ###block2_conv1 层的过滤器对应边缘和颜色组合而成的简单纹理
    ###更高层的过滤器类似于自然图像中的纹理:羽毛、眼睛、树叶等
'''

#可视化类激活(CAM,class activation map)的热力图
'''
#有助于了解一张图像的哪一部分让卷积神经网络做出了最终的分类决
策。这有助于对卷积神经网络的决策过程进行调试,特别是出现分类
错误的情况下。 这种方法还可以定位图像中的特定目标。

    类激活热力图是与特定输出类别相关的二维分数网格,对任何输
入图像的每个位置都要进行计算,它表示每个位置对该类别的重要程度
    对于输入到猫狗分 类卷积神经网络的一张图像,CAM可视化可以
生成类别“猫”的热力图,表示图像的各个部分与“猫”的相似程度
CAM可视化也会生成类别“狗”的热力图,表示图像的各个部分与“狗”
的相似程度
    Ramprasaath R. Selvaraju 等人 2017 年发表“Grad-CAM: 
visual explanations from deep networks via gradient- based 
localization”这篇论文中描述的方法:给定一张输入图像,对于一
个卷积层的输出特征图,用类别相对于通道的梯度对这个特征图中的
每个通道进行加权。
    [用“每个通道对类别的重要程度”对“输入图像对不同通道的激
活强度”的空间图进行加权,从而得到了“输入图像对类别的激活强
度”的空间图]
'''


#加载带有预训练权重的VGG16网络
model=VGG16(weights='imagenet')#包含密集连接分类器
#图像预处理为VGG16模式能够读取的模式:大小调整为 224×224, 然后将其转换为 float32 格式的 Numpy 张量
# 训练图像都根据 keras.applications.vgg16.preprocess_ input函数中内置的规则进行预处理

from keras.applications.vgg16 import preprocess_input,decode_predictions
img_path='datasets/elephants.jpg'
img=image.load_img(img_path,target_size=(224,224))
x=image.img_to_array(img)
x=np.expand_dims(x,axis=0)#添加一个维度,将数组转换为 (1, 224, 224, 3)形状的批量
x=preprocess_input(x)
preds=model.predict(x)
print('Predicted:',decode_predictions(preds,top=3)[0])
#Predicted: [('n02504458', 'African_elephant', 0.91007733), ('n01871265', 'tusker', 0.08247791), ('n02504013', 'Indian_elephant', 0.005972492)]
#预测的前三个类别,African_elephant的概率最大为91%左右

print(np.argmax(preds[0]))#预测向量中被最大激活的元素是对应“非洲象” 类别的元素,索引编号为 386

#为了展示图像中哪些部分最像非洲象,应用Grad-CAM算法
african_elephant_output=model.output[:,386]#预测向量中的非洲象元素

last_conv_layer=model.get_layer('block5_conv3')#VGG16最后一个卷积层

grads=K.gradients(african_elephant_output,last_conv_layer.output)[0]#“非洲象”类别相对于block5_conv3输出特征图的梯度
print(grads.shape)
pooled_grads=K.mean(grads,axis=(0,1,2))#平均梯度
# print(pooled_grads.shape)#形状为(512,)的向量
iterate=K.function([model.input],[pooled_grads,last_conv_layer.output[0]])#返回两个numpy数组
pooled_grads_value,conv_layer_output_value=iterate([x])

for i in range(512):
    conv_layer_output_value[:,:,i]*=pooled_grads_value[i]#将特征图数组的每个通道乘以“这个通道 对‘大象’类别的重要程度”
heatmap=np.mean(conv_layer_output_value,axis=-1)
heatmap=np.maximum(heatmap,0)
heatmap/=np.max(heatmap)#将热力图标准化到 0~1 范围内
plt.matshow(heatmap)
plt.show()
import cv2
######pip install opencv-python==3.3.1.11#####
#利用OpenCV生成一张图像,将热力图与原始图像叠加
img=cv2.imread(img_path)#加载原始图像
heatmap=cv2.resize(heatmap,(img.shape[1],img.shape[0]))#热力图大小调整至与原图像相同
heatmap=np.uint8(255*heatmap)#将热力图转换为RGB格式
heatmap=cv2.applyColorMap(heatmap,cv2.COLORMAP_JET)#将热力图应用于原始图像
superimposed_img=heatmap*0.4+img#0.4 是热力图强度因子
cv2.imwrite('datasets/elephant_cam.jpg',superimposed_img)#保存图像到指定位置

'''
值得注意的是,小象耳朵的激活强度很大,这可能是网络找到的非洲象和印度象的不同之处
'''

 测试图像的“非洲象”类激活热力图

 

类激活热力图叠加到原始图像

 

猜你喜欢

转载自blog.csdn.net/SunChao3555/article/details/88409470