第57步 深度学习图像识别:CNN可视化(Pytorch)

基于WIN10的64位系统演示

一、写在前面

由于不少模型使用的是Pytorch,因此这一期补上基于Pytorch实现CNN可视化的教程和代码,以SqueezeNet模型为例。

二、CNN可视化实战

继续使用胸片的数据集:肺结核病人和健康人的胸片的识别。其中,肺结核病人700张,健康人900张,分别存入单独的文件夹中。

(a)SqueezeNet建模

######################################导入包###################################
# 导入必要的包
import copy
import torch
import torchvision
import torchvision.transforms as transforms
from torchvision import models
from torch.utils.data import DataLoader
from torch import optim, nn
from torch.optim import lr_scheduler
import os
import matplotlib.pyplot as plt
import warnings
import numpy as np

warnings.filterwarnings("ignore")
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 设置GPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")


################################导入数据集#####################################
import torch
from torchvision import datasets, transforms
import os

# 数据集路径
data_dir = "./MTB"

# 图像的大小
img_height = 100
img_width = 100

# 数据预处理
data_transforms = {
    'train': transforms.Compose([
        transforms.RandomResizedCrop(img_height),
        transforms.RandomHorizontalFlip(),
        transforms.RandomVerticalFlip(),
        transforms.RandomRotation(0.2),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
    'val': transforms.Compose([
        transforms.Resize((img_height, img_width)),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
}

# 加载数据集
full_dataset = datasets.ImageFolder(data_dir)

# 获取数据集的大小
full_size = len(full_dataset)
train_size = int(0.7 * full_size)  # 假设训练集占80%
val_size = full_size - train_size  # 验证集的大小

# 随机分割数据集
torch.manual_seed(0)  # 设置随机种子以确保结果可重复
train_dataset, val_dataset = torch.utils.data.random_split(full_dataset, [train_size, val_size])

# 将数据增强应用到训练集
train_dataset.dataset.transform = data_transforms['train']

# 创建数据加载器
batch_size = 32
train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=4)
val_dataloader = torch.utils.data.DataLoader(val_dataset, batch_size=batch_size, shuffle=True, num_workers=4)

dataloaders = {'train': train_dataloader, 'val': val_dataloader}
dataset_sizes = {'train': len(train_dataset), 'val': len(val_dataset)}
class_names = full_dataset.classes


###############################定义ShuffleNet模型################################
# 定义SqueezeNet模型
model = models.squeezenet1_1(pretrained=True)  # 这里以SqueezeNet 1.1版本为例
num_ftrs = model.classifier[1].in_channels

# 根据分类任务修改最后一层
model.classifier[1] = nn.Conv2d(num_ftrs, len(class_names), kernel_size=(1,1))

# 修改模型最后的输出层为我们需要的类别数
model.num_classes = len(class_names)

model = model.to(device)

# 打印模型摘要
print(model)

#############################编译模型#########################################
# 定义损失函数
criterion = nn.CrossEntropyLoss()

# 定义优化器
optimizer = optim.Adam(model.parameters())

# 定义学习率调度器
exp_lr_scheduler = lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1)

# 开始训练模型
num_epochs = 20

# 初始化记录器
train_loss_history = []
train_acc_history = []
val_loss_history = []
val_acc_history = []

for epoch in range(num_epochs):
    print('Epoch {}/{}'.format(epoch, num_epochs - 1))
    print('-' * 10)

    # 每个epoch都有一个训练和验证阶段
    for phase in ['train', 'val']:
        if phase == 'train':
            model.train()  # 设置模型为训练模式
        else:
            model.eval()   # 设置模型为评估模式

        running_loss = 0.0
        running_corrects = 0

        # 遍历数据
        for inputs, labels in dataloaders[phase]:
            inputs = inputs.to(device)
            labels = labels.to(device)

            # 零参数梯度
            optimizer.zero_grad()

            # 前向
            with torch.set_grad_enabled(phase == 'train'):
                outputs = model(inputs)
                _, preds = torch.max(outputs, 1)
                loss = criterion(outputs, labels)

                # 只在训练模式下进行反向和优化
                if phase == 'train':
                    loss.backward()
                    optimizer.step()

            # 统计
            running_loss += loss.item() * inputs.size(0)
            running_corrects += torch.sum(preds == labels.data)

        epoch_loss = running_loss / dataset_sizes[phase]
        epoch_acc = (running_corrects.double() / dataset_sizes[phase]).item()

        # 记录每个epoch的loss和accuracy
        if phase == 'train':
            train_loss_history.append(epoch_loss)
            train_acc_history.append(epoch_acc)
        else:
            val_loss_history.append(epoch_loss)
            val_acc_history.append(epoch_acc)

        print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))

    print()

# 保存模型
torch.save(model.state_dict(), 'squeezenet.pth')

(b)可视化卷积神经网络的中间输出

import torch
from torchvision import models, transforms
import matplotlib.pyplot as plt
from torch import nn
from PIL import Image

# 定义图像的大小
img_height = 100
img_width = 100

# 1. 加载模型
model = models.squeezenet1_1(pretrained=False) 
num_ftrs = model.classifier[1].in_channels
num_classes = 2 
model.classifier[1] = nn.Conv2d(num_ftrs, num_classes, kernel_size=(1,1))
model.num_classes = num_classes
model.load_state_dict(torch.load('squeezenet.pth')) 
model.eval()
model = model.to('cuda' if torch.cuda.is_available() else 'cpu')

# 2. 加载图片并进行预处理
img = Image.open('./MTB/Tuberculosis/Tuberculosis-203.png')
transform = transforms.Compose([
    transforms.Resize((img_height, img_width)),
    transforms.Grayscale(num_output_channels=3),  
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])
img_tensor = transform(img).unsqueeze(0)
img_tensor = img_tensor.to('cuda' if torch.cuda.is_available() else 'cpu')

# 3. 提取前N层的输出
N = 25
activations = []
x = img_tensor
for i, layer in enumerate(model.features):
    x = layer(x)
    if i < N:
        activations.append(x)

# 4. 将每个中间激活的所有通道可视化,但最多只显示前9个通道
max_channels_to_show = 9
for i, activation in enumerate(activations):
    num_channels = min(max_channels_to_show, activation.shape[1])
    fig, axs = plt.subplots(1, num_channels, figsize=(num_channels*2, 2))
    for j in range(num_channels):
        axs[j].imshow(activation[0, j].detach().cpu().numpy(), cmap='viridis')
        axs[j].axis('off')
    plt.tight_layout()
    plt.show()

# 清空当前图像
plt.clf()

结果输出如下:

 由于SqueezeNet只有13层,所以即使我们在代码中要求输出25层,那也只能输出13层。从第一层到最后一层,可以看到逐渐抽象化。

(c)可视化过滤器

import matplotlib.pyplot as plt

def visualize_filters(model):
    # 获取第一个卷积层的权重
    first_conv_layer = model.features[0]
    weights = first_conv_layer.weight.data.cpu().numpy()
    
    # 取绝对值以便于观察所有权重
    weights = np.abs(weights)
    
    # 归一化权重
    weights -= weights.min()
    weights /= weights.max()
    
    # 计算子图网格大小
    num_filters = weights.shape[0]
    num_cols = 12
    num_rows = num_filters // num_cols
    if num_filters % num_cols != 0:
        num_rows += 1
    
    # 创建子图
    fig, axs = plt.subplots(num_rows, num_cols, figsize=(num_cols*2, num_rows*2))
    
    # 绘制过滤器
    for filter_index, ax in enumerate(axs.flat):
        if filter_index < num_filters:
            ax.imshow(weights[filter_index].transpose(1, 2, 0))
        ax.axis('off')
    
    plt.tight_layout()
    plt.show()

# 调用函数来显示过滤器
visualize_filters(model)

这个更加抽象:

(d)Grad-CAM绘制特征热力图

import torch
from torchvision import models, transforms
from torch.nn import functional as F
from torch.autograd import Variable
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
import matplotlib.cm as cm

# 模型路径
model_path = 'squeezenet.pth'

# 图像路径
image_path = './MTB/Tuberculosis/Tuberculosis-203.png'

# 加载模型
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 创建一个SqueezeNet模型
model = models.squeezenet1_1(pretrained=False)

# 修改最后一层为二分类
model.classifier[1] = torch.nn.Conv2d(512, 2, kernel_size=(1,1), stride=(1,1))

# 这行代码用于在模型全连接层输出后添加一个softmax函数,使得模型输出可以解释为概率
model.num_classes = 2

model = model.to(device)
model.load_state_dict(torch.load(model_path))
model.eval()

class GradCAM:
    def __init__(self, model, target_layer):
        self.model = model
        self.target_layer = target_layer
        self.feature = None
        self.gradient = None

        # 定义钩子
        self.hooks = self.target_layer.register_forward_hook(self.save_feature_map)
        self.hooks = self.target_layer.register_backward_hook(self.save_gradient)

    # 保存feature
    def save_feature_map(self, module, input, output):
        self.feature = output

    # 保存梯度
    def save_gradient(self, module, grad_in, grad_out):
        self.gradient = grad_out[0]

    # 计算权重
    def compute_weight(self):
        return F.adaptive_avg_pool2d(self.gradient, 1)

    def remove_hooks(self):
        self.hooks.remove()

    def __call__(self, inputs, index=None):
        self.model.zero_grad()

        output = self.model(inputs)
        if index == None:
            index = np.argmax(output.cpu().data.numpy())

        target = output[0][index]
        target.backward()

        weight = self.compute_weight()
        cam = weight * self.feature
        cam = cam.cpu().data.numpy()
        cam = np.sum(cam, axis=1)
        cam = np.maximum(cam, 0)

        # 归一化处理
        cam -= np.min(cam)
        cam /= np.max(cam)

        self.remove_hooks()

        return cam

# 图像处理
img = Image.open(image_path).convert("RGB")
img_transforms = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
img_tensor = img_transforms(img)
img_tensor = img_tensor.unsqueeze(0).to(device)

# 获取预测类别
outputs = model(img_tensor)
_, pred = torch.max(outputs, 1)
pred_class = pred.item()

# 获取最后一个卷积层
target_layer = model.features[12]

# Grad-CAM
grad_cam = GradCAM(model=model, target_layer=target_layer)
# 获取输入图像的Grad-CAM图像
cam_img = grad_cam(img_tensor, index=pred_class)

# 重新调整尺寸以匹配原始图像
cam_img = Image.fromarray(np.uint8(255 * cam_img[0]))
cam_img = cam_img.resize((img.width, img.height), Image.BICUBIC)

# 将CAM图像转换为Heatmap
cmap = cm.get_cmap('jet')
cam_img = cmap(np.float32(cam_img))

# 将RGBA图像转换为RGB
cam_img = Image.fromarray(np.uint8(cam_img[:, :, :3] * 255))
cam_img = Image.blend(img, cam_img, alpha=0.5)

# 显示图像
plt.imshow(cam_img)
plt.axis('off')  # 不显示坐标轴
plt.show()

print(pred_class)

输出如下,分别是第一、二、六、十二层的卷积层的输出:

结果解读:

在Grad-CAM热图中,颜色的深浅表示了模型在做出预测时,对输入图像中的哪些部分赋予了更多的重要性。红色区域代表了模型认为最重要的部分,这些区域在模型做出其预测时起到了主要的决定性作用。而蓝色区域则是对预测贡献较少的部分。

具体来说:

红色区域:这些是模型在进行预测时,权重较高的部分。也就是说,这些区域对模型的预测结果影响最大。在理想情况下,这些区域应该对应于图像中的目标对象或者是对象的重要特征。

蓝色区域:这些是模型在进行预测时,权重较低的部分。也就是说,这些区域对模型的预测结果影响较小。在理想情况下,这些区域通常对应于图像的背景或无关信息。

这种可视化方法可以帮助我们理解卷积神经网络模型是如何看待图像的,也能提供一种评估模型是否正确关注到图像中重要部分的方法。

三、数据

链接:https://pan.baidu.com/s/15vSVhz1rQBtqNkNp2GQyVw?pwd=x3jf

提取码:x3jf

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/72924.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CSS变形与动画(一):transform变形 与 transition过渡动画 详解(用法 + 代码 + 例子 + 效果)

文章目录 变形与动画transform 变形translate 位移scale 缩放rotate 旋转skew 倾斜多种变形设置变形中心点 transition 过渡动画多种属性变化 变形与动画 transform 变形 包括&#xff1a;位移、旋转、缩放、倾斜。 下面的方法都是transform里的&#xff0c;记得加上。 展示效…

pconsc4 安装

Pconsc4 安装遇到的问题 Pconsc4-github 按照红框给的一行命令&#xff0c;一行毁所有。 1 gcc and g not found # 1 Start by updating the packages list:sudo apt update# 2 Install the build-essential package by typing:sudo apt install build-essential## The comm…

在 Windows 中恢复数据的 5 种方法

发生数据丢失的原因有多种。无论是因为文件被意外删除、文件系统或操作系统损坏&#xff0c;还是由于软件或硬件级别的存储故障&#xff0c;数据都会在您最意想不到的时候丢失。今天我们重点介绍五种数据恢复方法&#xff0c;以应对意外情况的发生。 1.从另一台机器启动硬盘 如…

MyBatis-Plus学习笔记(尚硅谷)

一、MyBatis-Plus 1.简介 MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个 MyBatis (opens new window)的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 我们的愿景是成为 MyBatis 最好的搭档&…

深入了解 Rancher Desktop 设置

Rancher Desktop 设置的全面概述 Rancher Desktop 拥有方便、强大的功能&#xff0c;是最佳的开发者工具之一&#xff0c;也是在本地构建和部署 Kubernetes 的最快捷方式。 本文将介绍 Rancher Desktop 的功能和特性&#xff0c;以及 Rancher Desktop 作为容器管理平台和本地…

Modbus TCP转Profibus DP网关modbus tcp报文解析

捷米JM-DPM-TCP网关。在Profibus总线侧作为主站&#xff0c;在以太网侧作为ModbusTcp服务器功能&#xff0c; 下面是介绍捷米JM-DPM-TCP主站网关组态工具的配置方法 2, Profibus主站组态工具安装 执行资料光盘中的安装文件setup64.exe或setup.exe安装组态工具。安装过程中一直…

Pyinstaller 打包 django 项目如何将命令行参数加入?

起因 Pyinstaller 打包 django 项目&#xff0c;打包成 manage.exe 后用命令行 cmd manage.exe runserver 0.0.0.0:8001 --noreload 来运行感觉很不方便。 希望能够直接把命令行参数也打包进去&#xff0c;直接运行 exe 。我走了些弯路&#xff0c;但最终实现了。 弯路 我看…

每天一道leetcode:300. 最长递增子序列(动态规划中等)

今日份题目&#xff1a; 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组 [0,3,1,6,2,2,7] …

多种求组合数算法

目录 求组合数Ⅰ&#xff08;递推&#xff09;核心理论理论推导典型例题代码实现 求组合数Ⅱ&#xff08;预处理&#xff09;核心理论典型例题代码实现 求组合数Ⅲ&#xff08;Lucas定理&#xff09;核心理论Lucas定理的证明1.证明Lucas定理的第一形式2.证明Lucas定理的第二形式…

小米平板6Max14即将发布:自研G1 电池管理芯片,支持33W反向快充

明天晚上7点&#xff08;8 月 14 日&#xff09;&#xff0c;雷军将进行年度演讲&#xff0c;重点探讨“成长”主题。与此同时&#xff0c;小米将推出一系列全新产品&#xff0c;其中包括备受瞩目的小米MIX Fold 3折叠屏手机和小米平板6 Max 14。近期&#xff0c;小米官方一直在…

【算法——双指针】LeetCode 1089 复写零

千万不要被这道题标注着“简单”迷惑了&#xff0c;实际上需要注意的细节很多。 题目描述&#xff1a; 解题思路&#xff1a; 正序遍历&#xff0c;确定结果数组的最后一个元素所在的位置&#xff1b;知道最后一个元素的位置后倒序进行填充。 先找到最后一个需要复写的数 先…

CHATGPT源码简介与使用指南

CHATGPT源码的基本介绍 CHATGPT源码备受关注&#xff0c;它是一款基于人工智能的聊天机器人&#xff0c;旨在帮助开发者快速搭建自己的聊天机器人&#xff0c;无需编写代码。下面是对CHATGPT搭建源码的详细介绍。 CHATGPT源码的构建和功能 CHATGPT源码是基于Google的自然语言…

汽配企业WMS系统:提升作业效率与过程管控

随着汽配企业竞争的加剧和业务模式的复杂化&#xff0c;许多企业意识到提高仓库作业效率和成本控制能力是企业成功的关键。因此&#xff0c;越来越多的企业选择引入WMS仓储管理系统。然而&#xff0c;汽配企业产品复杂&#xff0c;且从业的人员大部分是老一辈人员&#xff0c;内…

CSS实现左侧固定,右侧自适应(5种方法)

<div class"father"><!-- 左右div不能调换顺序来写 --><div class"left">固定宽度区</div><div class"right">自适应区</div> </div> 一、利用左侧浮动float右侧margin-left /* 利用浮动floatmargin…

Vue电商项目--组件通信

组件通信6种方式 第一种&#xff1a;props 适用于的场景&#xff1a;父子组件通信 注意事项&#xff1a; 如果父组件给子组件传递数据&#xff08;函数&#xff09;&#xff1a;本质其实是子组件给父组件传递数据 如果父组件给子组件传递的数据&#xff08;非函数&#xf…

时序预测 | MATLAB实现基于GRU门控循环单元的时间序列预测-递归预测未来(多指标评价)

时序预测 | MATLAB实现基于GRU门控循环单元的时间序列预测-递归预测未来(多指标评价) 目录 时序预测 | MATLAB实现基于GRU门控循环单元的时间序列预测-递归预测未来(多指标评价)预测结果基本介绍程序设计参考资料 预测结果 基本介绍 1.Matlab实现GRU门控循环单元时间序列预测未…

EMQX Enterprise 5.1 正式发布:生产环境就绪的 MQTT over QUIC、基于 MQTT 的文件传输支持

近日&#xff0c;企业级 MQTT 物联网接入平台 EMQX Enterprise 5.1 正式发布。该版本为用户提供了更强大、更灵活的物联网解决方案&#xff0c;通过简化功能操作与管理流程&#xff0c;帮助用户快速构建所需的业务。 新版本提供了更大规模且更具伸缩性的全新集群架构&#xff…

腾讯云香港服务器租用价格_CN2线路延迟速度测试

腾讯云香港服务器&#xff0c;目前中国香港地域轻量应用服务器可选配置2核2G20M、2核2G30M、2核4G30M&#xff0c;操作系统可选Windows和Linux&#xff0c;不只是香港云服务器&#xff0c;新加坡、硅谷、法兰克福和东京服务器均有活动&#xff0c;腾讯云服务器网分享腾讯云境外…

【MFC】08.MFC消息,自定义消息,常用控件(MFC菜单创建大总结),工具栏,状态栏-笔记

本专栏上几篇文章讲解了MFC几大机制&#xff0c;今天带领大家学习MFC自定义消息以及常用控件&#xff0c;最常用的控件请查看本专栏第一二篇文章&#xff0c;今天这篇文章介绍工具栏&#xff0c;菜单和状态栏&#xff0c;以及菜单创建大总结。 文章目录 MFC消息分类&#xff1…

力扣:62. 不同路径(Python3)

题目&#xff1a; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&…