第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 npwarnings.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.0running_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和accuracyif 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 pltdef 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 = 12num_rows = num_filters // num_colsif 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 = 2model = model.to(device)
model.load_state_dict(torch.load(model_path))
model.eval()class GradCAM:def __init__(self, model, target_layer):self.model = modelself.target_layer = target_layerself.feature = Noneself.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)# 保存featuredef 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.featurecam = 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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

问DAO成都丨CyberDAO共识会议在成都圆满落幕

过往匆匆&#xff0c;唯有共识绵延&#xff1b;未来已来&#xff0c;愿与智者同谋。2023年8月9日至8月10日&#xff0c;CyberDAO共识会议在成都市大邑县顺利召开&#xff0c;吸引了上百名Web3.0与元宇宙爱好者参与本次会议。CyberDAO大中华区运营团队合伙人JR、漫威、安祈、可乐…

【0.1】lubancat鲁班猫4刷入debian网络ping 域名不通问题

目录 1. 环境2. 操作步骤 1. 环境 lubancat4鲁班猫4 (4G0)不带emmc系统镜像lubancat-rk3588-debian11-gnome-20230807_update.img官方资料地址https://doc.embedfire.com/products/link/zh/latest/linux/ebf_lubancat.html 2. 操作步骤 从官网给的百度网盘下载linux系统全部…

10、杂项:遍历指定目录计算文件的md5并输出到文件

目录 &#x1f345;点击这里查看所有博文 随着自己工作的进行&#xff0c;接触到的技术栈也越来越多。给我一个很直观的感受就是&#xff0c;某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了&#xff0c;只有经常会用到的东西才有可能真正记…

【Rust】Rust学习 第十一章编写自动化测试

Rust 是一个相当注重正确性的编程语言&#xff0c;不过正确性是一个难以证明的复杂主题。Rust 的类型系统在此问题上下了很大的功夫&#xff0c;不过它不可能捕获所有种类的错误。为此&#xff0c;Rust 也在语言本身包含了编写软件测试的支持。 编写一个叫做 add_two 的将传递…

[C++ 网络协议编程] TCP/IP协议

目录 1. TCP/IP协议栈 2. TCP原理 2.1 TCP套接字中的I/O缓冲 2.2 TCP工作原理 2.2.1 三次握手&#xff08;连接&#xff09; 2.2.2 与对方主机的数据交换 2.2.3 四次握手&#xff08;断开与套接字的连接&#xff09; TCP&#xff08;Transmission Control Protocol传输控…

无涯教程-Perl - ref函数

描述 如果EXPR为引用,则此函数返回真值&#xff1b;如果未提供EXPR,则为$_。返回的实际值还定义了引用所引用的实体的类型。 内置类型为- REFSCALARARRAYHASHCODEGLOBLVALUEIO::Handle 如果使用bless()函数为变量设置了祝福,则将返回新的数据类型。新的数据类型通常将是一个…

比较编程语言C和Go

使用一个简单的计数程序来比较古老的C语言和现代的Go语言。Go是一种现代的编程语言&#xff0c;它在很大程度上源自C语言。因此&#xff0c;对于任何使用C语言编写程序的人来说&#xff0c;Go可能会感觉很熟悉。Go使得编写新程序变得容易&#xff0c;同时又让C程序员感到熟悉&a…

大数据-玩转数据-Flink 自定义Sink(Mysql)

一、说明 如果Flink没有提供给我们可以直接使用的连接器&#xff0c;那我们如果想将数据存储到我们自己的存储设备中&#xff0c;mysql 的安装使用请参考 mysql-玩转数据-centos7下mysql的安装 创建表 CREATE TABLE sensor (id int(10) ) ENGINEInnoDB DEFAULT CHARSETutf8二…

二 根据用户行为数据创建ALS模型并召回商品

二 根据用户行为数据创建ALS模型并召回商品 2.0 用户行为数据拆分 方便练习可以对数据做拆分处理 pandas的数据分批读取 chunk 厚厚的一块 相当大的数量或部分 import pandas as pd reader pd.read_csv(behavior_log.csv,chunksize100,iteratorTrue) count 0; for chunk in …

DNS协议及其工作原理

DNS是域名系统&#xff08;Domain Name System&#xff09;的缩写&#xff0c;它是一种用于将域名转换为IP地址的分布式数据库系统。它是因特网的基石&#xff0c;能够使人们通过域名方便地访问互联网&#xff0c;而无需记住复杂的IP地址。 DNS的历史可以追溯到1983年&#xf…

4个简化IT服务台任务的ChatGPT功能

最近几个月&#xff0c;ChatGPT 风靡全球&#xff0c;这是一个 AI 聊天机器人&#xff0c;使用户能够生成脚本、文章、锻炼图表等。这项技术在各行各业都有无穷无尽的应用&#xff0c;在本文中&#xff0c;我们将研究这种现代技术如何帮助服务台团队增强服务交付和客户体验。 什…

最佳实践:如何优雅地提交一个 Amazon EMR Serverless 作业?

《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书由博主历时三年精心创作&#xff0c;现已通过知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详…

章节7:XSS检测和利用

章节7&#xff1a;XSS检测和利用 测试payload <script>alert(XSS)</script> <script>alert(document.cookie)</script> ><script>alert(document.cookie)</script> ><script>alert(document.cookie)</script> &qu…

元宇宙之经济(02)理解NFT

1 NFT是什么&#xff1f; 想象一下&#xff0c;你小时候曾经在操场上集齐过各种不同的贴纸&#xff0c;然后和朋友们交换&#xff0c;这些贴纸有着独特的图案和价值。NFT的概念与此类似&#xff0c;但在数字世界中运作。NFT是一种基于区块链技术的数字资产&#xff0c;每个NFT…

golang—面试题大全

目录标题 sliceslice和array的区别slice扩容机制slice是否线程安全slice分配到栈上还是堆上扩容过程中是否重新写入go深拷贝发生在什么情况下&#xff1f;切片的深拷贝是怎么做的copy和左值进行初始化区别slice和map的区别 mapmap介绍map的key的类型map对象如何比较map的底层原…

《Java极简设计模式》第03章:工厂方法模式(FactoryMethod)

作者&#xff1a;冰河 星球&#xff1a;http://m6z.cn/6aeFbs 博客&#xff1a;https://binghe.gitcode.host 文章汇总&#xff1a;https://binghe.gitcode.host/md/all/all.html 源码地址&#xff1a;https://github.com/binghe001/java-simple-design-patterns/tree/master/j…

无法正确识别车牌(Python、OpenCv、Tesseract)

我正在尝试识别车牌&#xff0c;但出现了错误&#xff0c;例如错误/未读取字符 以下是每个步骤的可视化&#xff1a; 从颜色阈值变形关闭获得遮罩 以绿色突出显示的车牌轮廓过滤器 将板轮廓粘贴到空白遮罩上 Tesseract OCR的预期结果 BP 1309 GD 但我得到的结果是 BP 1309…

腾讯云标准型CVM云服务器详细介绍

腾讯云CVM服务器标准型实例的各项性能参数平衡&#xff0c;标准型云服务器适用于大多数常规业务&#xff0c;例如&#xff1a;web网站及中间件等&#xff0c;常见的标准型云服务器有CVM标准型S5、S6、SA3、SR1、S5se等规格&#xff0c;腾讯云服务器网来详细说下云服务器CVM标准…

NAS搭建指南一——服务器的选择与搭建

一、服务器的选择 有自己的本地的公网 IP 的请跳过此篇文章按需求选择一个云服务器&#xff0c;目的就是为了进行 frp 的搭建&#xff0c;完成内网穿透我选择的是腾讯云服务器&#xff0c;我的配置如下&#xff0c;仅供参考&#xff1a; 4. 腾讯云服务器官网地址 二、服务器…

docker 镜像的导出与导入 save 与 load

一、镜像导出 docker save 导出 将系统中的镜像保存为压缩包&#xff0c;进行文件传输。使用 docker save --help 查看命令各参数&#xff0c;或者去docker官网查看.以 hello-world镜像为例。 A&#xff1a;将镜像保存为tar包 docker save image > package.tar docker sa…