Sacred 深度学习实验管理工具

深度学习实验管理

Every experiment is sacred

Every experiment is great

If an experiment is wasted

God gets quite irate

最近研究深度学习在目标检测领域的应用,在测试调整检测框架以及炼丹调参的情况时,常有的困惑就是如何保留每一次实验的实验数据,因为每次实验小意思就三四个小时,中等意思就两三天,每次跑下来的数据都应当保存下来留作后续分析,而不是看它效果不行,就直接删除了。注意这里的实验数据并不仅仅包括代码和训练结果,还应当包括

  • 实验的参数配置
  • 实验运行的环境
    • GPU信息
    • python 依赖
    • 主机信息
  • 实验过程的各类指标变化信息
  • 实验的输出

日常使用中,要改的东西主要集中在代码[模型结构和训练流程]+参数[各类超参],对于代码改进,勤用Git进行版本管理;对于参数设置,统一管理在配置文件中;上面其他的实验数据除了养成良好的习惯进行手动保存备份之外,似乎没有更方便的方式了。

最近找到一个名为Sacred的工具,用于记录实验的配置、组织、日志和复现。我今天测试了一下,Sacred主要的工作在于将每次实验的输入-过程-输出保存到数据库中,并利用Web将历史实验数据进行了可视化,方便我们查看历史实验,并进行参数配置和实验结果的比较。

下面记录一下Sacred的安装过程和使用案例。

安装

Github 主页: Sacred

Sacred 文档:Welcome to Sacred’s documentation!

Sacred的使用有两部分:

  • Sacred + MongoDB:实验记录和保存
  • Ominiboard:可视化管理

备注:

  • Sacred的数据保存后端有多种形式:Mongo Observer、File Storage Observer、TinyDB Observer、Telegram Observer等,我选择第一种Mongo Observer。其他的方式可以查看文档。
  • Sacred的可视化前端也支持多种形式:Omniboard、Sacredboard、SacredBrowser等,我选择第一种Omniboard。其他的方式可以在Github主页上找到入口。

Sacred

1
2
3
4
5
# 主角
pip install sacred

# 用于数据库连接
pip install numpy pymongo

MongoDB

MongoDB是一个数据库管理系统,这里用作Sacred的存储后端。

在ubuntu上的MongoDB安装可以参考Install MongoDB Community Edition on Ubuntu,其他系统也可以在该网站上找到对应的安装方式。

这里重复一下安装的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. Import the public key used by the package management system.
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 9DA31620334BD75D9DCB49F368818C72E52529D4

# 2. Create a list file for MongoDB.
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list

# 3. Reload local package database.
sudo apt-get update

# 4. Install the MongoDB packages.
sudo apt-get install -y mongodb-org

# 4.1 prevent MongoDB upgrades when use apt-get
echo "mongodb-org hold" | sudo dpkg --set-selections
echo "mongodb-org-server hold" | sudo dpkg --set-selections
echo "mongodb-org-shell hold" | sudo dpkg --set-selections
echo "mongodb-org-mongos hold" | sudo dpkg --set-selections
echo "mongodb-org-tools hold" | sudo dpkg --set-selections

友情提醒:按这种方式,下载比较缓慢。

我是用梯子安装的,网络条件不好的话,可以参考清华大学开源软件镜像站的MongoDB 镜像使用帮助

Tips:MongoDB的日常使用:

1
2
3
4
5
6
7
8
9
10
11
# 启动
sudo service mongod start

# 停止
sudo service mongod stop

# 重启
sudo service mongod restart

# 进入MongoDB
mongo

创建一个名为sacred的数据库,用作sacred工具的后端存储:

1
2
3
4
5
# 进入MongoDB
mongo

# 创建sacred数据库。use命令切换数据库,没有该数据就会自动创建一个
use sacred

Omniboard

Omniboard效果图:

Omniboard

Github Repo: Omniboard

安装指导:Omniboard: Quick Start

Guide上有两种方式:NPM 和 Docker,我用第一种npm的方式。

第一步,在Ubuntu机器上安装版本≥v8的Node.js,系统默认apt 安装的版本不够,需要手动安装,以下是安装步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 0. 从官网下载编译好的node.js
wget https://nodejs.org/dist/v10.15.0/node-v10.15.0-linux-x64.tar.xz

# 1. 解压到 /usr/local/lib/nodejs, 注意VERSION是版本号,此处是v10.15.0
VERSION=v10.15.0
DISTRO=linux-x64
sudo mkdir /usr/local/lib/nodejs
sudo tar -xJvf node-$VERSION-$DISTRO.tar.xz -C /usr/local/lib/nodejs
sudo mv /usr/local/lib/nodejs/node-$VERSION-$DISTRO /usr/local/lib/nodejs/node-$VERSION

# 2. 设置环境变量,打开 ~/.profile, 追加如下信息
# Nodejs
export NODEJS_HOME=/usr/local/lib/nodejs/node-$VERSION/bin
export PATH=$NODEJS_HOME:$PATH

# 3. 刷新
. ~/.profile

# 4. 测试安装版本信息
node -v
npm version
npx -v

# 创建sudo链接
sudo ln -s /usr/local/lib/nodejs/node-$VERSION/bin/node /usr/bin/node
sudo ln -s /usr/local/lib/nodejs/node-$VERSION/bin/npm /usr/bin/npm
sudo ln -s /usr/local/lib/nodejs/node-$VERSION/bin/npx /usr/bin/npx

第二步,npm 安装omniboard

1
npm install -g omniboard

第三步,开启omniboard服务。平时也是用该命令开启omniboard可视化前端

1
2
3
4
5
# 开启用法
omniboard -m hostname:port:database

# 默认情况下如下,其中27017是MongoDB的端口
omniboard -m localhost:27017:sacred

第四步,打开 http://localhost:9000 来查看前端,并进行管理。

使用案例

使用yunjey的一个pytorch教程作为演示,代码是演示用pytorch实现基于CNN的MINIST手写数字识别。

根据Sacred文档稍作修改,就可以演示如何进行实验的记录。

更多用法请去看Sacred 文档:Welcome to Sacred’s documentation!。内容超丰富,功能超级多。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from sacred import Experiment
from sacred.observers import MongoObserver
from sacred.utils import apply_backspaces_and_linefeeds

import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms


ex = Experiment("mnist_cnn")
ex.observers.append(MongoObserver.create(url='localhost:27017',
db_name='sacred'))
ex.captured_out_filter = apply_backspaces_and_linefeeds


# 超参数设置
@ex.config
def myconfig():
# Device configuration
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

# Hyper parameters
num_epochs = 5
num_classes = 10
batch_size = 100
learning_rate = 0.001


# Convolutional neural network (two convolutional layers)
class ConvNet(nn.Module):
def __init__(self, num_classes=10):
super(ConvNet, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(16),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2))
self.layer2 = nn.Sequential(
nn.Conv2d(16, 32, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2))
self.fc = nn.Linear(7 * 7 * 32, num_classes)

def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = out.reshape(out.size(0), -1)
out = self.fc(out)
return out


# notice how we can access the message here by taking it as an argument
@ex.automain
def main(device,num_epochs,num_classes,batch_size,learning_rate ):
# MNIST dataset
train_dataset = torchvision.datasets.MNIST(root='/home/ubuntu/Datasets/MINIST/',
train=True,
transform=transforms.ToTensor(),
download=True)

test_dataset = torchvision.datasets.MNIST(root='/home/ubuntu/Datasets/MINIST/',
train=False,
transform=transforms.ToTensor())

# Data loader
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=batch_size,
shuffle=True)

test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
batch_size=batch_size,
shuffle=False)

model = ConvNet(num_classes).to(device)

# Loss and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

# Train the model
total_step = len(train_loader)
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
images = images.to(device)
labels = labels.to(device)

# Forward pass
outputs = model(images)
loss = criterion(outputs, labels)

# Backward and optimize
optimizer.zero_grad()
loss.backward()
optimizer.step()

if (i + 1) % 100 == 0:
print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'
.format(epoch + 1, num_epochs, i + 1, total_step, loss.item()))

# Test the model
model.eval() # eval mode (batchnorm uses moving mean/variance instead of mini-batch mean/variance)
with torch.no_grad():
correct = 0
total = 0
for images, labels in test_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()

print('Test Accuracy of the model on the 10000 test images: {} %'.format(100 * correct / total))

# Save the model checkpoint
torch.save(model.state_dict(), 'model.ckpt')

执行完该程序后,可以打开omniboard前端 http://localhost:9000 ,效果如图:

sacred

sacred_output

sacred_details

sacred_config

有啥问题欢迎讨论啊。

参考资源

Version your machine learning models with Sacred

pytorch-tutorial: convolutional neural network