赞
踩
一、写在前面
(1)CNN可视化
在理解和解释卷积神经网络(CNN)的行为方面,可视化工具起着重要的作用。以下是一些可以用于可视化的内容:
(a)激活映射(Activation maps):可以显示模型在训练过程中的激活情况,这可以帮助我们理解每一层(或每个过滤器)在识别图像的哪个部分。
(b)过滤器(Filter):可以将卷积核或过滤器的权重可视化,以了解模型在寻找什么样的特征。
(c)类激活映射(Class Activation Mapping,CAM):这是一种可视化技术,可以用于理解网络为什么会将图像分类到特定类别中。这通过生成一个热图来实现,该热图高亮显示网络认为对分类决策最重要的区域。
(d)混淆矩阵(Confusion Matrix):在分类问题中,混淆矩阵可以帮助我们理解模型在各个类别上的性能,显示模型何时正确分类,何时出现错误。
(e)学习曲线(Learning Curves):显示训练和验证误差随着训练周期(或时间)的变化,可以帮助我们理解模型是否过拟合,是否需要更多的训练等。
(2)激活映射和过滤器
由于混淆矩阵和学习曲线之前都已经给了代码,这一期主要介绍激活映射和过滤器(卷积层)。用的模型是Mobilenet_v2,以为够快!!
二、CNN可视化实战
继续使用胸片的数据集:肺结核病人和健康人的胸片的识别。其中,肺结核病人700张,健康人900张,分别存入单独的文件夹中。
(a)Mobilenet_v2建模
- ######################################导入包###################################
- from tensorflow import keras
- import tensorflow as tf
- from tensorflow.python.keras.layers import Dense, Flatten, Conv2D, MaxPool2D, Dropout, Activation, Reshape, Softmax, GlobalAveragePooling2D, BatchNormalization
- from tensorflow.python.keras.layers.convolutional import Convolution2D, MaxPooling2D
- from tensorflow.python.keras import Sequential
- from tensorflow.python.keras import Model
- from tensorflow.python.keras.optimizers import adam_v2
- import numpy as np
- import matplotlib.pyplot as plt
- from tensorflow.python.keras.preprocessing.image import ImageDataGenerator, image_dataset_from_directory
- from tensorflow.python.keras.layers.preprocessing.image_preprocessing import RandomFlip, RandomRotation, RandomContrast, RandomZoom, RandomTranslation
- import os,PIL,pathlib
- import warnings
- #设置GPU
- gpus = tf.config.list_physical_devices("GPU")
-
- if gpus:
- gpu0 = gpus[0] #如果有多个GPU,仅使用第0个GPU
- tf.config.experimental.set_memory_growth(gpu0, True) #设置GPU显存用量按需使用
- tf.config.set_visible_devices([gpu0],"GPU")
-
- warnings.filterwarnings("ignore") #忽略警告信息
- plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
- plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
-
- ################################导入数据集#####################################
- #1.导入数据
- data_dir = "./MTB"
- data_dir = pathlib.Path(data_dir)
- image_count = len(list(data_dir.glob('*/*')))
- print("图片总数为:",image_count)
-
- batch_size = 32
- img_height = 100
- img_width = 100
-
- train_ds = image_dataset_from_directory(
- data_dir,
- validation_split=0.2,
- subset="training",
- seed=12,
- image_size=(img_height, img_width),
- batch_size=batch_size)
-
- val_ds = image_dataset_from_directory(
- data_dir,
- validation_split=0.2,
- subset="validation",
- seed=12,
- image_size=(img_height, img_width),
- batch_size=batch_size)
-
- class_names = train_ds.class_names
- print(class_names)
- print(train_ds)
-
-
- #2.检查数据
- for image_batch, labels_batch in train_ds:
- print(image_batch.shape)
- print(labels_batch.shape)
- break
-
- #3.配置数据
- AUTOTUNE = tf.data.AUTOTUNE
-
- def train_preprocessing(image,label):
- return (image/255.0,label)
-
- train_ds = (
- train_ds.cache()
- .shuffle(800)
- .map(train_preprocessing)
- .prefetch(buffer_size=AUTOTUNE)
- )
-
- val_ds = (
- val_ds.cache()
- .map(train_preprocessing)
- .prefetch(buffer_size=AUTOTUNE)
- )
-
- #4. 数据可视化
- plt.figure(figsize=(10, 8)) # 图形的宽为10高为5
- plt.suptitle("数据展示")
-
- class_names = ["Tuberculosis","Normal"]
-
- for images, labels in train_ds.take(1):
- for i in range(15):
- plt.subplot(4, 5, i + 1)
- plt.xticks([])
- plt.yticks([])
- plt.grid(False)
-
- # 显示图片
- plt.imshow(images[i])
- # 显示标签
- plt.xlabel(class_names[labels[i]-1])
-
- plt.show()
-
- ######################################数据增强函数################################
-
- data_augmentation = Sequential([
- RandomFlip("horizontal_and_vertical"),
- RandomRotation(0.2),
- RandomContrast(1.0),
- RandomZoom(0.5,0.2),
- RandomTranslation(0.3,0.5),
- ])
-
- def prepare(ds):
- ds = ds.map(lambda x, y: (data_augmentation(x, training=True), y), num_parallel_calls=AUTOTUNE)
- return ds
- train_ds = prepare(train_ds)
-
- ################################导入mobilenet_v2################################
- #获取预训练模型对输入的预处理方法
- from tensorflow.python.keras.applications import mobilenet_v2
- from tensorflow.python.keras import Input, regularizers
- IMG_SIZE = (img_height, img_width, 3)
-
- # 创建输入张量
- inputs = Input(shape=IMG_SIZE)
- # 定义基础模型,并将 inputs 传入
- base_model = mobilenet_v2.MobileNetV2(input_tensor=inputs,
- include_top=False,
- weights='imagenet')
-
- #从基础模型中获取输出
- x = base_model.output
- #全局池化
- x = GlobalAveragePooling2D()(x)
- #BatchNormalization
- x = BatchNormalization()(x)
- #Dropout
- x = Dropout(0.8)(x)
- #Dense
- x = Dense(128, kernel_regularizer=regularizers.l2(0.1))(x) # 全连接层减少到128,添加 L2 正则化
- #BatchNormalization
- x = BatchNormalization()(x)
- #激活函数
- x = Activation('relu')(x)
- #输出层
- outputs = Dense(2, kernel_regularizer=regularizers.l2(0.1))(x) # 添加 L2 正则化
- #BatchNormalization
- outputs = BatchNormalization()(outputs)
- #激活函数
- outputs = Activation('sigmoid')(outputs)
- #整体封装
- model = Model(inputs, outputs)
- #打印模型结构
- print(model.summary())
-
- #############################编译模型#########################################
- #定义优化器
- from tensorflow.python.keras.optimizers import adam_v2, rmsprop_v2
- optimizer = adam_v2.Adam()
-
-
- #编译模型
- model.compile(optimizer=optimizer,
- loss='sparse_categorical_crossentropy',
- metrics=['accuracy'])
-
- #训练模型
- from tensorflow.python.keras.callbacks import ModelCheckpoint, Callback, EarlyStopping, ReduceLROnPlateau, LearningRateScheduler
-
- NO_EPOCHS = 50
- PATIENCE = 10
- VERBOSE = 1
-
- # 设置动态学习率
- annealer = LearningRateScheduler(lambda x: 1e-5 * 0.99 ** (x+NO_EPOCHS))
-
- # 设置早停
- earlystopper = EarlyStopping(monitor='loss', patience=PATIENCE, verbose=VERBOSE)
-
- #
- checkpointer = ModelCheckpoint('mtb_jet_best_model_mobilenetv3samll.h5',
- monitor='val_accuracy',
- verbose=VERBOSE,
- save_best_only=True,
- save_weights_only=True)
-
- train_model = model.fit(train_ds,
- epochs=NO_EPOCHS,
- verbose=1,
- validation_data=val_ds,
- callbacks=[earlystopper, checkpointer, annealer])
(b)将前M层每个中间激活的所有通道可视化
- from tensorflow.python.keras.preprocessing.image import ImageDataGenerator
- from tensorflow.python.keras import Model
- import matplotlib.pyplot as plt
- from tensorflow.python.keras.models import load_model
- from tensorflow.python.keras.preprocessing import image
- from tensorflow.python.keras.preprocessing.image import img_to_array
- from PIL import Image
-
- if __name__=='__main__':
- #加载保存的模型
- model=load_model('mtb_jet_best_model_mobilenet.h5')
- model.summary()
-
- #加载一张猫的图像
- img=image.load_img(path='./MTB/Tuberculosis/Tuberculosis-203.png',target_size=(img_width,img_height))
- img_tensor=img_to_array(img)
- img_tensor=img_tensor.reshape((1,)+img_tensor.shape)
- img_tensor/=255.
- plt.imshow(img_tensor[0])
- plt.show()
-
- N=25#提取前N层的输出
- layer_outputs=[layer.output for layer in model.layers[:N]]
- activation_model=Model(inputs=model.input,outputs=layer_outputs)
-
- #以预测模式运行模型 activations包含卷积层的N个输出
- activations=activation_model.predict(img_tensor)
- print(activations[0].shape)
-
- first_layer_activation = activations[0]
- plt.matshow(first_layer_activation[0, :, :, 1], cmap='viridis')
- plt.show()
- #清空当前图像
- #plt.clf()
-
- #将每个中间激活的所有通道可视化
- layer_names = []
- for layer in model.layers[:N]:
- layer_names.append(layer.name)
- images_per_row = 16
- for layer_name, layer_activation in zip(layer_names, activations):
- n_features = layer_activation.shape[-1]
- size = layer_activation.shape[1]
- n_cols = n_features // images_per_row
- display_grid = np.zeros((size * n_cols, images_per_row * size))
- for col in range(n_cols):
- for row in range(images_per_row):
- channel_image = layer_activation[0,:, :,col * images_per_row + row]
- channel_image -= channel_image.mean()
- channel_image /= channel_image.std()
- channel_image *= 64
- channel_image += 128
- channel_image = np.clip(channel_image, 0, 255).astype('uint8')
- display_grid[col * size : (col + 1) * size,
- row * size : (row + 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()
这个代码输出了模型的前25层的所有中间的激活图层。
首先我们来看看模型结构,非常长的一个表格:
注意哈,第一层是输入层,所以后面是不会输出的。只会输出后24个图层:
先来看前六个图层的处理:
(1)Conv1 (Conv2D): 这是一个卷积层,它对输入图像进行卷积操作以提取图像的局部特征。在这个例子中,这个层级的输出形状为(50, 50, 32),表示该层使用了32个卷积核,生成了32个特征图(feature map),每个特征图的尺寸是50x50。
(2)bn_Conv1 (BatchNormalization): 这是一个批标准化层,它将卷积层的输出进行标准化处理,使得结果的均值接近0,标准差接近1。这个操作可以提高模型的训练速度和模型性能。
(3)Conv1_relu (ReLU): 这是一个ReLU激活函数,它对上一层的输出应用非线性变换(将所有负值变为0)。这样可以增加模型对复杂模式的学习能力。
(4)expanded_conv_depthwise (DepthwiseConv2D): 这是一个深度可分离卷积层,它首先对每个输入通道分别进行卷积操作,然后再通过1x1的卷积将这些结果进行组合。这种操作相比传统的卷积操作可以减少模型的参数和计算量。
(5-6)expanded_conv_depthwise_BN (BatchNormalization) 和 expanded_conv_depthwise_relu (ReLU): 这两个层和上面的bn_Conv1、Conv1_relu层的操作是类似的,也是分别对深度卷积层的结果进行标准化和ReLU非线性变换。
接着,往后看:
继续解读:
(7)expanded_conv_project (Conv2D): 这是一个卷积层,它对上一层的深度卷积层的输出进行处理,输出形状为(50, 50, 16)。
(8)expanded_conv_project_BN (BatchNormalization): 这是一个批标准化层,对卷积层的输出进行标准化处理。
(9-10)block_1_expand (Conv2D) 和 block_1_expand_BN (BatchNormalization): 这是另一个卷积层和批标准化层,它们对上一层的输出进行卷积操作和标准化操作,输出形状为(50, 50, 96)。
(11)block_1_expand_relu (ReLU): 这是一个ReLU激活函数,它对上一层的输出应用非线性变换(将所有负值变为0)。
(12)block_1_pad (ZeroPadding2D): 这是一个零填充层,它在输入的周围填充零以保持特征图的空间尺寸。
(13-14)block_1_depthwise (DepthwiseConv2D) 和 block_1_depthwise_BN (BatchNormalization): 这是深度可分离卷积层和批标准化层,它们对上一层的输出进行深度卷积操作和标准化操作,输出形状为(25, 25, 96)。
(15)block_1_depthwise_relu (ReLU): 这又是一个ReLU激活函数,它对上一层的输出应用非线性变换。
(16-17)block_1_project (Conv2D) 和 block_1_project_BN (BatchNormalization): 这是卷积层和批标准化层,它们对上一层的输出进行卷积操作和标准化操作,输出形状为(25, 25, 24)。
(18-24)接下来的block_2_expand (Conv2D) 到 block_2_project (Conv2D) 层:是另一个与block_1相似的模块,只不过输入形状改变了。它们进行的操作和上述的操作大致相同,也是包含卷积、批标准化和ReLU激活等操作。
注意看:以上每个层级都在处理和转化上一层的输出,以便提取和学习图像的特征。这样的架构可以帮助模型捕捉到更复杂的模式和特征。通俗来说,就是随着层数增加,就越能看到细节!其中黄色的部分是模型重点关注的区域!
(b)显示第M层输出的第L个通道图像
- ##加载保存的模型
- from tensorflow.python.keras.models import load_model
- from tensorflow.python.keras.preprocessing import image
- from tensorflow.python.keras.preprocessing.image import img_to_array
- from PIL import Image
- if __name__=='__main__':
- model=load_model('mtb_jet_best_model_mobilenet.h5')
- model.summary()
-
- #加载一张图像
- img=image.load_img(path='./MTB/Tuberculosis/Tuberculosis-203.png',target_size=(img_width,img_height))
- img_tensor=img_to_array(img)
- img_tensor=img_tensor.reshape((1,)+img_tensor.shape)
- img_tensor/=255.
- plt.imshow(img_tensor[0])
- plt.show()
-
- M=25#提取前M层的输出
- layer_outputs=[layer.output for layer in model.layers[:M]]
- activation_model=Model(inputs=model.input,outputs=layer_outputs)
-
- #以预测模式运行模型 activations包含卷积层的M个输出
- activations=activation_model.predict(img_tensor)
- print(activations[M-1].shape)
-
- first_layer_activation = activations[M-1]
- L=3 # 显示第L个通道的图像
- plt.matshow(first_layer_activation[0, :, :, L-1], cmap='viridis')
- plt.show()
输出如下:
第24层输出的图像,黄色的部分是模型重点关注的区域!
我们再看看更深层次的图像,比如第80层:
太过于抽象,哈哈哈哈!
(c)可视化过滤器(卷积核)
- # 获取基础模型的第一层,即Conv2D层的权重
- for layer in base_model.layers:
- if isinstance(layer, Conv2D):
- weights = layer.get_weights()[0]
- break
-
- # 将权重缩放到0-1
- weights_min = weights.min()
- weights_max = weights.max()
- weights = (weights - weights_min) / (weights_max - weights_min)
-
- # 获取过滤器数量
- num_filters = weights.shape[-1]
-
- # 循环遍历所有过滤器
- for i in range(num_filters):
- # 获取第i个过滤器
- filter = weights[:, :, :, i]
-
- # 为过滤器创建一个子图
- plt.subplot(num_filters//4 + 1, 4, i+1)
-
- # 显示过滤器
- plt.imshow(filter)
-
- # 删除坐标轴
- plt.axis('off')
-
- # 显示整个图
- plt.show()
这个更加抽象哈:
三、写在最后
激活映射是每个卷积层通过卷积操作与激活函数处理后的输出结果,它展示了网络在特定层级对输入图像的响应,可视化这些激活映射有助于我们理解模型学习到的特征与决策过程。通过上面的例子,相信大家对于CNN的原理有更直观的认识吧。
四、数据
链接:https://pan.baidu.com/s/15vSVhz1rQBtqNkNp2GQyVw?pwd=x3jf
提取码:x3jf
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。