pytorch简单代码实现deep dream图(即CNN特征可视化 features visualization)

本文通过代码演示如何利用梯度上升法实现CNN特征的可视化,特别是针对ResNet18模型。通过最大化特定层、通道或元素的响应,生成对应的输入图像,揭示网络内部特征学习的过程。从单个元素到通道再到层的可视化,展示了网络从低级纹理到高级概念的抽象能力。此外,还比较了不同网络的风格差异。
摘要由CSDN通过智能技术生成

  本文给出完整代码实现CNN特征的可视化输入图像,也就是简单的deep dream图,有助于更好的理解CNN工作原理,并掌握用梯度上升法生成满足要求输入图像的技术。更清晰美观的deep dream图需要加入一些其他技巧,可以参考我另一篇文章

1,原理

  深度网络的常规工作过程是,给定输入样本及标签,前向处理后的输出结果和标签计算出某种损失函数,再由损失函数反向传播求网络各参数的梯度,根据此梯度更新参数,以使损失函数减小,逐渐训练得到一个逼近标签的网络。
  有趣的是,我们也可以反向思维,固定网络的参数不变,而是优化输入图像。根据某项指标求得输入图像的梯度,再根据此梯度优化输入,就可以得到满足要求的输入图像。由于往往需要最大化某种指标,与通常最小化损失函数的梯度下降法不同,所以这种方法也被称为梯度上升法。在程序实现上,为了利用SGD,Adam等成熟的梯度下降优化算法,我们只需要给指标加个负号,也就变成和梯度下降一样了。
  如果我们把特征图的某个部分的均值作为最大化的指标,此时就可以得到使指定特征图部分最大响应的输入图,也就能够直观的看出指定部分的特征图到底处理的是什么类型的特征。这个指定的特征图部分可以是某个层,也可以是某个层的某个通道,甚至也可以是某个通道上某个元素。我们先来看单个元素的情况:

2,指定特征图单个元素的最大响应输入图像可视化

import torch
import torchvision.models as models
import cv2
import time
t0 = time.time()

model = models.resnet18(pretrained=True).cuda()
batch_size = 1

for params in model.parameters():
    params.requires_grad = False
model.eval()

def hook(module,inp,out):
    global features
    features = out

data = torch.rand(batch_size,3,224,224).cuda()
data.requires_grad=True

mu = torch.Tensor([0.485, 0.456, 0.406]).unsqueeze(-1).unsqueeze(-1).cuda()
std = torch.Tensor([0.229, 0.224, 0.225]).unsqueeze(-1).unsqueeze(-1).cuda()
unnormalize = lambda x: x*std + mu
normalize = lambda x: (x-mu)/std

#optimizer = torch.optim.SGD([data], lr=1, momentum=0.99) #参数需根据实际情况再调
optimizer = torch.optim.Adam([data], lr=0.1, weight_decay=1e-6)
myhook = model.layer2.register_forward_hook(hook)
n,h,w = 0,3,8
for i in range(4001):
    x = (data - data.min()) / (data.max() - data.min())
    x = normalize(x)
    _ = model(x)
    loss =  - features[:,n,h,w].mean() #指定元素
    #loss =  - features[:,n,:,:].mean() #指定通道
    #loss =  - features.mean() #指定层
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    if i%100==0:
        print('data.abs().mean():',data.abs().mean().item())
        print('loss:',loss.item())
        print('time: %.2f'%(time.time()-t0))
myhook.remove()
data_i = data[0]
data_i = (data_i - data_i.min()) / (data_i.max() - data_i.min())
data_i = data_i.permute(1,2,0).data.cpu().numpy()*255
data_i = data_i[...,::-1].astype('uint8')  #注意cv2使用BGR顺序
cv2.imwrite('./feature_visual/layer2_filter%d.png'%n,data_i)

完整代码如上,我们以torchvision中的预训练resnet18为例,按照大的模块划分,resnet18中有4个layer,画出每个layer中某个元素对应的输入特征图如下:
在这里插入图片描述

图1.resnet18不同层指定元素的最大响应输入图像

从图中还可以看出,每个特征元素在输入图像上的感受野有多大,显然越靠后层元素的感受野越大。

3,指定通道

在这里插入图片描述

图2.resnet18不同层指定通道最大响应输入图像

可以看出,前序层的输入响应图都是一些均匀的纹理图,显然是图像的更基础的组成要素;随着网络越深,输入响应图则逐渐呈现出更加全局性的一些图像概念,以及更加高级和接近实物的一些图像概念,有些似乎是某种实物的形状特征。
我们还可以看出,单个通道的响应中可以看到多个类别的图像的“影子”,所以通道并不是类别依赖的。

4,指定层

在这里插入图片描述

图3.resnet18不同层最大响应输入图像

5,其他一些网络

在这里插入图片描述

图4.其他网络最末卷积层不同通道的最大响应输入图像

可以看出,不同网络的风格还是很不相同的,其中densenet不像别的那么恶心,还挺好看。

好的,以下是一个使用PyTorch实现CNN逐层可视化的代码示例。其中包含了TSNE的实现,以及如何获取CNN模型不同层的特征。 ```python import torch import torchvision import matplotlib.pyplot as plt from sklearn.manifold import TSNE # 加载数据集 transform = torchvision.transforms.Compose([ torchvision.transforms.ToTensor(), torchvision.transforms.Normalize((0.5,), (0.5,)) ]) trainset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True) # 加载CNN模型 class Net(torch.nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = torch.nn.Conv2d(1, 6, 5) self.pool = torch.nn.MaxPool2d(2, 2) self.conv2 = torch.nn.Conv2d(6, 16, 5) self.fc1 = torch.nn.Linear(16 * 4 * 4, 120) self.fc2 = torch.nn.Linear(120, 84) self.fc3 = torch.nn.Linear(84, 10) def forward(self, x): x = self.pool(torch.nn.functional.relu(self.conv1(x))) x = self.pool(torch.nn.functional.relu(self.conv2(x))) x = x.view(-1, 16 * 4 * 4) x = torch.nn.functional.relu(self.fc1(x)) x = torch.nn.functional.relu(self.fc2(x)) x = self.fc3(x) return x net = Net() net.load_state_dict(torch.load('mnist_cnn.pt')) # 获取CNN模型不同层的特征 def get_feature_maps(x, net): feature_maps = [] x = net.conv1(x) feature_maps.append(x) x = net.pool(torch.nn.functional.relu(x)) x = net.conv2(x) feature_maps.append(x) x = net.pool(torch.nn.functional.relu(x)) x = x.view(-1, 16 * 4 * 4) x = net.fc1(x) feature_maps.append(x) x = net.fc2(torch.nn.functional.relu(x)) feature_maps.append(x) x = net.fc3(torch.nn.functional.relu(x)) feature_maps.append(x) return feature_maps # 使用TSNE进行可视化 def tsne_visualization(feature_maps): tsne = TSNE(n_components=2, random_state=0) feature_maps = feature_maps.reshape(feature_maps.shape[0], -1) feature_maps_tsne = tsne.fit_transform(feature_maps) plt.scatter(feature_maps_tsne[:, 0], feature_maps_tsne[:, 1]) plt.show() # 可视化不同层的特征 for i, (images, labels) in enumerate(trainloader, 0): feature_maps = get_feature_maps(images, net) for j in range(len(feature_maps)): tsne_visualization(feature_maps[j].detach().numpy()) break ``` 上述代码中,我们首先加载了MNIST数据集和CNN模型,然后定义了一个函数`get_feature_maps`来获取CNN模型不同层的特征,接着定义了一个函数`tsne_visualization`来对这些特征进行TSNE可视化。最后,在训练集中选择了一批像,并对其不同层的特征进行了可视化。 需要注意的是,由于TSNE的计算复杂度较高,因此对于大规模的数据集或者特征,计算时间会比较长。此外,由于TSNE是一种随机算法,因此每次运行的结果可能会有所不同。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值