【深入浅出PyTorch】--7.2.PyTorch可视化2
目录
3.TensorBoard--可视化训练过程
3.1.TensorBoard--基本逻辑
3.2.TensorBoard-配置与启动
3.3.TensorBoard--模型结构可视化
3.4.TensorBoard--图像可视化
3.5.TensorBoard--连续变量可视化
3.6.TensorBoard--参数分布可视化
3.7.服务器端使用TensorBoard
3.8.总结
4.wandb--可视化训练过程
4.1.wandb的安装
4.2.wandb的使用
4.3.wandb的demo演示
3.TensorBoard--可视化训练过程
训练过程的可视化在深度学习模型训练中扮演着重要的角色。学习的过程是一个优化的过程,我们需要找到最优的点作为训练过程的输出产物。一般来说,我们会结合训练集的损失函数和验证集的损失函数,绘制两条损失函数的曲线来确定训练的终点,找到对应的模型用于测试。那么除了记录训练中每个epoch的loss值,能否实时观察损失函数曲线的变化,及时捕捉模型的变化呢?
此外,我们也希望可视化其他内容,如输入数据(尤其是图片)、模型结构、参数分布等,这些对于我们在debug中查找问题来源非常重要(比如输入数据和我们想象的是否一致)。
TensorBoard作为一款可视化工具能够满足上面提到的各种需求。TensorBoard由TensorFlow团队开发,最早和TensorFlow配合使用,后来广泛应用于各种深度学习框架的可视化中来。本节我们探索TensorBoard的强大功能,希望帮助读者“从入门到精通”。
经过本节的学习,你将收获:
-
安装TensorBoard工具
-
了解TensorBoard可视化的基本逻辑
-
掌握利用TensorBoard实现训练过程可视化
-
掌握利用TensorBoard完成其他内容的可视化
3.1.TensorBoard--基本逻辑
我们可以将TensorBoard看做一个记录员,它可以记录我们指定的数据,包括模型每一层的feature map,权重,以及训练loss等等。TensorBoard将记录下来的内容保存在一个用户指定的文件夹里,程序不断运行中TensorBoard会不断记录。记录下的内容可以通过网页的形式加以可视化。
3.2.TensorBoard-配置与启动
在使用TensorBoard前,我们需要先指定一个文件夹供TensorBoard保存记录下来的数据。然后调用tensorboard中的SummaryWriter作为上述“记录员”
from tensorboardX import SummaryWriterwriter = SummaryWriter('./runs')
上面的操作实例化SummaryWritter为变量writer,并指定writer的输出目录为当前目录下的"runs"目录。也就是说,之后tensorboard记录下来的内容都会保存在runs。
如果使用PyTorch自带的tensorboard,则采用如下方式import:
from torch.utils.tensorboard import SummaryWriter
启动tensorboard也很简单,在命令行中输入
tensorboard --logdir=/path/to/logs/ --port=xxxx
- “path/to/logs/"是指定的保存tensorboard记录结果的文件路径(等价于上面的“./runs",
- port是外部访问TensorBoard的端口号,可以通过访问ip:port访问tensorboard,这一操作和jupyter notebook的使用类似。如果不是在服务器远程使用的话则不需要配置port。
有时,为了tensorboard能够不断地在后台运行,也可以使用nohup命令或者tmux工具来运行tensorboard。大家可以自行搜索,这里不展开讨论了。
下面,我们将模拟深度学习模型训练过程,来介绍如何利用TensorBoard可视化其中的各个部分。
3.3.TensorBoard--模型结构可视化
首先定义模型:
import torch.nn as nnclass Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 = nn.Conv2d(in_channels=3,out_channels=32,kernel_size = 3)self.pool = nn.MaxPool2d(kernel_size = 2,stride = 2)self.conv2 = nn.Conv2d(in_channels=32,out_channels=64,kernel_size = 5)self.adaptive_pool = nn.AdaptiveMaxPool2d((1,1))self.flatten = nn.Flatten()self.linear1 = nn.Linear(64,32)self.relu = nn.ReLU()self.linear2 = nn.Linear(32,1)self.sigmoid = nn.Sigmoid()def forward(self,x):x = self.conv1(x)x = self.pool(x)x = self.conv2(x)x = self.pool(x)x = self.adaptive_pool(x)x = self.flatten(x)x = self.linear1(x)x = self.relu(x)x = self.linear2(x)y = self.sigmoid(x)return ymodel = Net()
print(model)
可视化模型的思路和7.1中介绍的方法一样,都是给定一个输入数据,前向传播后得到模型的结构,再通过TensorBoard进行可视化,使用add_graph:
import torchwriter.add_graph(model, input_to_model = torch.rand(1, 3, 224, 224))
writer.close()
把生成的文件拖到英文路径下:改名为11,
路径不要带中文字符否者报错,加载的是绝对路径
python -m tensorboard.main --logdir=./test --host=0.0.0.0 --port=6006
http://0.0.0.0:6006/
3.4.TensorBoard--图像可视化
当我们做图像相关的任务时,可以方便地将所处理的图片在tensorboard中进行可视化展示。
-
对于单张图片的显示使用add_image
-
对于多张图片的显示使用add_images
-
有时需要使用torchvision.utils.make_grid将多张图片拼成一张图片后,用writer.add_image显示
这里我们使用torchvision的CIFAR10数据集为例:
import torchvision
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
from tensorboardX import SummaryWritertransform_train = transforms.Compose([transforms.ToTensor()])
transform_test = transforms.Compose([transforms.ToTensor()])train_data = datasets.CIFAR10(".", train=True, download=True, transform=transform_train)
test_data = datasets.CIFAR10(".", train=False, download=True, transform=transform_test)
train_loader = DataLoader(train_data, batch_size=64, shuffle=True)
test_loader = DataLoader(test_data, batch_size=64)images, labels = next(iter(train_loader))# 仅查看一张图片
writer = SummaryWriter('./pytorch_tb')
writer.add_image('images[0]', images[0])
writer.close()# 将多张图片拼接成一张图片,中间用黑色网格分割
# create grid of images
# writer = SummaryWriter('./pytorch_tb')
# img_grid = torchvision.utils.make_grid(images)
# writer.add_image('image_grid', img_grid)
# writer.close()
#
# # 将多张图片直接写入
# writer = SummaryWriter('./pytorch_tb')
# writer.add_images("images",images,global_step = 0)
# writer.close()
python -m tensorboard.main --logdir=./pytorch_tb --host=0.0.0.0 --port=6006
另外注意上方menu部分,刚刚只有“GRAPHS"栏对应模型的可视化,现在则多出了”IMAGES“栏对应图像的可视化。左侧的滑动按钮可以调整图像的亮度和对比度。
此外,除了可视化原始图像,TensorBoard提供的可视化方案自然也适用于我们在Python中用matplotlib等工具绘制的其他图像,用于展示分析结果等内容。
3.5.TensorBoard--连续变量可视化
TensorBoard可以用来可视化连续变量(或时序变量)的变化过程,通过add_scalar实现:
writer = SummaryWriter('./pytorch_tb')
for i in range(500):x = iy = x**2writer.add_scalar("x", x, i) #日志中记录x在第step i 的值writer.add_scalar("y", y, i) #日志中记录y在第step i 的值
writer.close()
可视化结果如下:
如果想在同一张图中显示多个曲线,则需要分别建立存放子路径(使用SummaryWriter指定路径即可自动创建,但需要在tensorboard运行目录下),同时在add_scalar中修改曲线的标签使其一致即可:
writer1 = SummaryWriter('./pytorch_tb/x')
writer2 = SummaryWriter('./pytorch_tb/y')
for i in range(500):x = iy = x*2writer1.add_scalar("same", x, i) #日志中记录x在第step i 的值writer2.add_scalar("same", y, i) #日志中记录y在第step i 的值
writer1.close()
writer2.close()
这里也可以用一个writer,但for循环中不断创建SummaryWriter不是一个好选项。此时左下角的Runs部分出现了勾选项,我们可以选择我们想要可视化的曲线。曲线名称对应存放子路径的名称(这里是x和y)。
这部分功能非常适合损失函数的可视化,可以帮助我们更加直观地了解模型的训练情况,从而确定最佳的checkpoint。左侧的Smoothing滑动按钮可以调整曲线的平滑度,当损失函数震荡较大时,将Smoothing调大有助于观察loss的整体变化趋势。
3.6.TensorBoard--参数分布可视化
当我们需要对参数(或向量)的变化,或者对其分布进行研究时,可以方便地用TensorBoard来进行可视化,通过add_histogram实现。下面给出一个例子:
import torch
import numpy as np
from torch.utils.tensorboard import SummaryWriter # 导入 TensorBoard 可视化工具# 定义一个函数,用于生成服从正态分布的张量(模拟神经网络参数)
def norm(mean, std):"""生成一个形状为 (100, 20) 的张量,其元素服从均值为 mean、标准差为 std 的正态分布。这可以用来模拟某一层神经网络的参数(如权重)。参数:mean (float): 正态分布的均值std (float): 正态分布的标准差返回:torch.Tensor: 形状为 (100, 20) 的张量"""t = std * torch.randn((100, 20)) + mean # torch.randn 生成标准正态分布(均值0,标准差1)return t# 创建一个 SummaryWriter 实例,将日志写入 './pytorch_tb/' 目录
# 后续可通过 `tensorboard --logdir=./pytorch_tb/` 在浏览器中查看可视化结果
writer = SummaryWriter('./pytorch_tb/')# 遍历均值从 -10 到 9(步长为 1),共 20 个 step
for step, mean in enumerate(range(-10, 10, 1)):# 生成当前均值下的参数张量(标准差固定为 1)w = norm(mean, 1)writer.add_histogram("w", w, step)# 立即将数据写入磁盘(确保日志及时保存,便于实时查看)writer.flush()# 关闭 SummaryWriter,释放资源并确保所有数据写入完成
writer.close()
3.7.服务器端使用TensorBoard
一般情况下,我们会连接远程的服务器来对模型进行训练,但由于服务器端是没有浏览器的(纯命令模式),因此,我们需要进行相应的配置,才可以在本地浏览器,使用tensorboard查看服务器运行的训练过程。 本文提供以下几种方式进行,其中前两种方法都是建立SSH隧道,实现远程端口到本机端口的转发,最后一种方法适用于没有下载Xshell等SSH连接工具的用户
-
MobaXterm
-
在MobaXterm点击Tunneling
-
选择New SSH tunnel,我们会出现以下界面。
-
对新建的SSH通道做以下设置,第一栏我们选择
Local port forwarding
,< Remote Server>
我们填写localhost,< Remote port>
填写6006,tensorboard默认会在6006端口进行显示,我们也可以根据 tensorboard --logdir=/path/to/logs/ --port=xxxx的命令中的port进行修改,< SSH server>
填写我们连接服务器的ip地址,<SSH login>
填写我们连接的服务器的用户名,<SSH port>
填写端口号(通常为22),< forwarded port>
填写的是本地的一个端口号,以便我们后面可以对其进行访问。 -
设定好之后,点击Save,然后Start。在启动tensorboard,这样我们就可以在本地的浏览器输入
http://localhost:6006/
对其进行访问了
-
-
Xshell
-
Xshell的连接方法与MobaXterm的连接方式本质上是一样的,具体操作如下:
-
连接上服务器后,打开当前会话属性,会出现下图,我们选择隧道,点击添加
-
按照下方图进行选择,其中目标主机代表的是服务器,源主机代表的是本地,端口的选择根据实际情况而定。
-
启动tensorboard,在本地127.0.0.1:6006 或者 localhost:6006进行访问。
-
-
SSH
-
该方法是将服务器的6006端口重定向到自己机器上来,我们可以在本地的终端里输入以下代码:其中16006代表映射到本地的端口,6006代表的是服务器上的端口。
ssh -L 16006:127.0.0.1:6006 username@remote_server_ip
-
在服务上使用默认的6006端口正常启动tensorboard
tensorboard --logdir=xxx --port=6006
-
在本地的浏览器输入地址
127.0.0.1:16006 或者 localhost:16006
-
3.8.总结
对于TensorBoard来说,它的功能是很强大的,可以记录的东西不只限于本节所介绍的范围。
主要的实现方案是构建一个SummaryWriter,然后通过add_XXX()
函数来实现。
其实TensorBoard的逻辑还是很简单的,它的基本逻辑就是文件的读写逻辑,写入想要可视化的数据,然后TensorBoard自己会读出来。
4.wandb--可视化训练过程
我们使用了Tensorboard可视化训练过程,但是Tensorboard对数据的保存仅限于本地,也很难分析超参数不同对实验的影响。
wandb的出现很好的解决了这些问题,因此在本章节中,我们将对wandb进行简要介绍。 wandb是Weights & Biases(权重和偏差)的缩写,它能够自动记录模型训练过程中的超参数和输出指标,然后可视化和比较结果,并快速与其他人共享结果。目前它能够和Jupyter、TensorFlow、Pytorch、Keras、Scikit、fast.ai、LightGBM、XGBoost一起结合使用。
经过本节的学习,你将收获:
-
wandb的安装
-
wandb的使用
-
demo演示
4.1.wandb的安装
wandb的安装非常简单,我们只需要使用pip安装即可。
pip install wandb
安装完成后,我们需要在官网注册一个账号并复制下自己的API keys,然后在本地使用下面的命令登录。https://wandb.ai/site
需要连接外网。call me
wandb login
这时,我们会看到下面的界面,只需要粘贴你的API keys即可。
4.2.wandb的使用
wandb的使用也非常简单,只需要在代码中添加几行代码即可。
import wandb
wandb.init(project='my-project', entity='my-name')
这里的project和entity是你在wandb上创建的项目名称和用户名,如果你还没有创建项目,可以参考官方文档。
#api:#api接口:5db56bbfd6a1c2feb98f219ffb789d9660afa761import wandb
wandb.init(project='my-project', entity='2898694631-gyp')
4.3.wandb的demo演示
下面我们使用一个CIFAR10的图像分类demo来演示wandb的使用。
import random # to set the python random seed
import numpy # to set the numpy random seed
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
from torchvision.models import resnet18
import warnings
warnings.filterwarnings('ignore')#忽略警告warning
使用wandb的第一步是初始化wandb,这里我们使用wandb.init()函数来初始化wandb,其中project是你在wandb上创建的项目名称,name是你的实验名称。
# 初始化wandb
import wandb
wandb.init(project="my-project",name="wandb_demo",)
使用wandb的第二步是设置超参数,这里我们使用wandb.config来设置超参数,这样我们就可以在wandb的界面上看到超参数的变化。wandb.config的使用方法和字典类似,我们可以使用config.key的方式来设置超参数。
# 超参数设置
config = wandb.config # config的初始化
config.batch_size = 64
config.test_batch_size = 10
config.epochs = 5
config.lr = 0.01
config.momentum = 0.1
config.use_cuda = True
config.seed = 2043
config.log_interval = 10 # 设置随机数
def set_seed(seed):random.seed(config.seed) torch.manual_seed(config.seed) numpy.random.seed(config.seed)
第三步是构建训练和测试的pipeline,这里我们使用pytorch的CIFAR10数据集和resnet18来构建训练和测试的pipeline。
# 定义训练函数:对模型进行一个 epoch 的训练
def train(model, device, train_loader, optimizer):model.train() # 将模型设为训练模式(启用梯度、BatchNorm 等)for batch_id, (data, target) in enumerate(train_loader):data, target = data.to(device), target.to(device) # 数据迁移到指定设备(CPU/GPU)optimizer.zero_grad() # 清空优化器中的梯度output = model(data) # 前向传播,得到模型输出criterion = nn.CrossEntropyLoss() # 定义交叉熵损失函数loss = criterion(output, target) # 计算损失loss.backward() # 反向传播,计算梯度optimizer.step() # 更新模型参数# 定义测试函数:评估模型性能,并通过 wandb 记录指标和示例图像
def test(model, device, test_loader, classes):model.eval() # 将模型设为评估模式(关闭 Dropout、冻结 BatchNorm)test_loss = 0correct = 0example_images = [] # 用于保存预测示例图像with torch.no_grad(): # 禁用梯度计算,加速推理并节省显存for data, target in test_loader:data, target = data.to(device), target.to(device)output = model(data)criterion = nn.CrossEntropyLoss()test_loss += criterion(output, target).item() # 累加损失值pred = output.max(1, keepdim=True)[1] # 获取预测类别(最大 logit 的索引)correct += pred.eq(target.view_as(pred)).sum().item() # 统计正确预测数量# 将第一个样本的图像及预测/真实标签保存为 wandb.Image 对象example_images.append(wandb.Image(data[0], caption="Pred:{} Truth:{}".format(classes[pred[0].item()], classes[target[0]])))# 使用 wandb.log 记录测试阶段的关键指标和可视化示例wandb.log({"Examples": example_images, # 可视化预测样例"Test Accuracy": 100. * correct / len(test_loader.dataset), # 测试准确率(%)"Test Loss": test_loss # 测试总损失(未平均)})# 防止 wandb.watch 被重复调用(兼容性处理)
wandb.watch_called = False# 主函数:配置环境、加载数据、初始化模型并开始训练
def main():use_cuda = config.use_cuda and torch.cuda.is_available()device = torch.device("cuda:0" if use_cuda else "cpu")kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {}# 设置随机种子,保证实验可复现set_seed(config.seed)torch.backends.cudnn.deterministic = True# 定义数据预处理流程:转为 Tensor 并标准化(CIFAR-10 常用均值/标准差)transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])# 加载 CIFAR-10 训练数据集train_loader = DataLoader(datasets.CIFAR10(root='dataset',train=True,download=True,transform=transform), batch_size=config.batch_size, shuffle=True, **kwargs)# 加载 CIFAR-10 测试数据集test_loader = DataLoader(datasets.CIFAR10(root='dataset',train=False,download=True,transform=transform), batch_size=config.batch_size, shuffle=False, **kwargs)# CIFAR-10 数据集的 10 个类别名称classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')# 加载预训练的 ResNet18 模型,并移动到指定设备model = resnet18(pretrained=True).to(device)# 注意:此处未修改最后的分类层,若用于 CIFAR-10 可能存在类别数不匹配问题(但代码未改)# 定义优化器:使用 SGD,传入学习率和动量参数optimizer = optim.SGD(model.parameters(), lr=config.lr, momentum=config.momentum)# 使用 wandb 监控模型(记录参数、梯度、计算图等)wandb.watch(model, log="all")# 开始训练循环:逐 epoch 训练并测试for epoch in range(1, config.epochs + 1):train(model, device, train_loader, optimizer)test(model, device, test_loader, classes)# 保存训练好的模型到本地文件torch.save(model.state_dict(), 'model.pth')# 同时将模型文件同步到 wandb 云端wandb.save('model.pth')# 程序入口
if __name__ == '__main__':main()
哎,中国大陆要断开,真麻烦,用一下别人的实验结果
我们可以发现,使用wandb可以很方便的记录我们的训练结果,除此之外,wandb还为我们提供了很多的功能,比如:模型的超参数搜索,模型的版本控制,模型的部署等等。这些功能都可以帮助我们更好的管理我们的模型,更好的进行模型的迭代和优化。这些功能我们在后面的更新中会进行介绍。