今天我们来聊一聊数据可视化的问题,在这里向大家推荐一个大佬开源的教程,我的可视化的工作也是从他的教程借鉴而来的,再次表达感谢!
开源教程地址:tensor-yu/PyTorch_Tutorialgithub.com
Pytorch框架也有自己的可视化软件--Visdom,但是我用着不太习惯,感觉它的API也不太方便,参数设置过于复杂,而且可视化的功能性并不是太强,所以有人就写个库用来将Pytorch中的参数放到tensorboard上面进行可视化,十分方便!
tensorboadX
这相当于一个辅助工具,可以把Pytorch中的参数传递到Tensorboad上面,那么如何进行安装呢?分为三个步骤:pip install tensorboardX
pip install tensorboard
pip install tensorflow
注意numpy的版本要对应,否则会报错,如果不匹配,那就进行更新或者新建虚拟环境了!
网络训练(Cifar10)
首先,我使用了非官方的代码对Cifar10进行训练,类似于ResNet, 由于Cifar10中的图片尺寸都很小,大约32x32,所以我们对传统的resnet进行了修改,其网络结构如下:
参考于官方的ResNet18并做如下修改:由于像素太小,修改第一个卷积核步长为1,不进行下采样
修改通道,让通道变小些
删除layer4,不用再继续降采样了
# ResNet
class ResNet(nn.Module):
def __init__(self, block, layers, num_classes = 10): # block为残差模块
super(ResNet, self).__init__()
self.in_channels = 16
self.conv = conv3x3(3, 16)
self.bn = nn.BatchNorm2d(16)
self.relu = nn.ReLU(inplace=True)
self.layer1 = self.make_layer(block, 16, layers[0], 1) # 残差模块
self.layer2 = self.make_layer(block, 32, layers[1], 2)
self.layer3 = self.make_layer(block, 64, layers[2], 2)
self.avg_pool = nn.AvgPool2d(8) # kernel_size = 8
self.fc = nn.Linear(64, num_classes)
def make_layer(self, block, out_channels, blocks, stride=1):
downsample = None
if (stride != 1) or (self.in_channels != out_channels):
downsample = nn.Sequential(
conv3x3(self.in_channels, out_channels, stride=stride),
nn.BatchNorm2d(out_channels)
)
layers = []
layers.append(block(self.in_channels, out_channels, stride, downsample))
self.in_channels = out_channels
for i in range(1, blocks):
layers.append(block(out_channels, out_channels))
return nn.Sequential(*layers) # 把一个列表变成一个层的函数
def forward(self, x):
out = self.conv(x)
out = self.bn(out)
out = self.relu(out)
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.avg_pool(out)
out = out.reshape(out.size(0), -1) # 1*1*64 features
out = self.fc(out)
return out
然后进行模型训练,训练代码如下:
# 训练整个网络
total_step = len(train_loader)
curr_lr = learning_rate
for epoch in range(num_epoches):
for i, (images, labels) in enumerate(train_loader):
images = images.to(device)
labels = labels.to(device)
# 正向传播
outputs = model(images)
loss = criterion(outputs, labels)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (i+1) % 100 == 0:
print(f'Epoch {epoch+1}/{num_epoches}, Step {i+1}/{total_step}, {loss.item()}') # 不要忘了item()
torch.save(model.state_dict(), 'ResnetCifar10.pt')
最后我们得到模型,精度为:0.78左右,没有一直进行训练,这无所谓!!
TensorBoardX可视化
参考了一开始的那个代码库,我把可视化的工作分成了如下几个:卷积核的可视化
feature map的可视化
模型图的可视化
标量变化情况的可视化
权重的直方图的可视化(可以知道权重的数据分布)
那么,话不多说,我们来看看如何进行可视化的把,首先把tensorboardX导入并进行初始化:
from tensorboardX import SummaryWriter
# 定义Summary_Writer
writer = SummaryWriter('./Result') # 数据存放在这个文件夹
第一个任务:标量可视化与权重直方图add_scalar(tag, scalar_value, global_step=None, walltime=None) 记录标量的变化
add_scalars(main_tag, tag_scalar_dict, global_step=None, walltime=None) 记录多个标量的值
add_histogram(tag, values, global_step=None, bins='tensorflow', walltime=None) 绘制直方图
这个权重直方图,非常的关键,特别是寻找模型训练loss出现的各种问题,由于我是在测试时画的图,所以只有一幅,但一般我们在训练阶段使用,用于寻找模型的问题~~
任务代码:
# 显示每个layer的权重
loss = 10 # 第0层
for i, (name, param) in enumerate(model.named_parameters()):
if 'bn' not in name:
writer.add_histogram(name, param, 0)
writer.add_scalar('loss', loss, i)
loss = loss*0.5
效果图:
第二个任务:feature map的可视化和卷积核的可视化add_image(tag, img_tensor, global_step=None, walltime=None) 绘制图片
torchvision.utils.make_grid(tensor, nrow=8, padding=2, normalize=False, ra nge=None, scale_each=False, pad_value=0) 制作网格用于显示
我们把每个卷积层后的feature map来进行可视化,一共有4个卷积层,所以我们将要绘制4个阶段的feature map,由于我们的模型已经训练好了,所以可视化出来的feature map可以很好地解释我们训练出来的模型到底是什么,每个卷积层提取的特征是什么?当然只能部分解释~
任务代码:
img_grid = vutils.make_grid(x, normalize=True, scale_each=True, nrow=2)
# 绘制原始图像
writer.add_image('raw img', img_grid, global_step=666) # j 表示feature map数
print(x.size())
model.eval()
for name, layer in model._modules.items():
# 为fc层预处理x
x = x.view(x.size(0), -1) if "fc" in name else x
print(x.size())
x = layer(x)
print(f'{name}')
# 第一个卷积没有进行relu,要记得加上
x = F.relu(x) if 'conv' in name else x
if 'layer' in name or 'conv' in name:
x1 = x.transpose(0, 1) # C,B, H, W ---> B,C, H, W
img_grid = vutils.make_grid(x1, normalize=True, scale_each=True, nrow=4) # normalize进行归一化处理
writer.add_image(f'{name}_feature_maps', img_grid, global_step=0)
效果图:Cifar 10里面的一张原图conv/layer 1/layer 2/layer 3四个层提取的feature map
我们可以清晰的看到,在网络的底层,我们可以看到船的边缘特征,由于归一化了,所以色彩特征不太明显,当然也可以不进行归一化,但是layer3这种高层特征我们就能以解释了,它代表更多的是语义特征,很抽象!
卷积核的可视化与这个类似,其实质也就是权重可视化,代码如下:
# 可视化卷积核
# in model.named_parameters():
if 'conv' in name and 'weight' in name:
in_channels = param.size()[1]
out_channels = param.size()[0] # 输出通道,表示卷积核的个数
k_w, k_h = param.size()[3], param.size()[2] # 卷积核的尺寸
kernel_all = param.view(-1, 1, k_w, k_h) # 每个通道的卷积核
kernel_grid = vutils.make_grid(kernel_all, normalize=True, scale_each=True, nrow=in_channels)
writer.add_image(f'{name}_all', kernel_grid, global_step=0)
效果图(conv层和layer 1层):
第三个任务:feature map的可视化和卷积核的可视化
这个不知道为什么,我使用Pytorch自带的网络模型就没有问题,但是自己定义的网络进行模型图显示就会出现一些莫名其妙的问题,有大佬知道的请告知原因啊!这里面我们使用ResNet18的结构来进行可视化,很简单,就一句话:
model = torchvision.models.resnet18(False)
writer.add_graph(model, torch.rand([1,3,224,224])) # 自己定义的网络有时显示错误
效果图:
总结
再次感谢首页大佬的开源教程,让我学到了很多,通过这些可视化的工作,我们就可以更好的去观测和调控这个模型的训练过程了!这个工程代码过段时间整理后也会和其他的一同开源~供大家参考学习~模型图可视化的问题,有人知道还请告知啊!