Pytorch实战笔记
文件组织架构
- checkpoints/: 保存训练好模型
- data/: 数据相关
- models/: 模型定义
- utils/: 工具函数
- config.py: 可配置变量
- main.py: 程序主入口,通过不同命令指定不同参数和操作
__init__.py
- 每个文件夹包含 => 其他程序从目录导入函数/模块
from FOLDERNAME.PACKAGENAME import MODULENAME
- 在其中加入:
from .PACKAGENAME import MODULENAME
-> 从外界访问:from FOLDERNAME import MODULENAME
数据加载
- 划出验证集,将图像随机排列,前70%作为训练集,后30%作为验证集
训练集 | 验证集/测试集 | |
---|---|---|
图像增强 | √ | |
返回Label | 分类 | 图片id |
模型定义
对于nn.Module进行简易封装
- 封装
nn.Module
为BasicModule
类,主要提供save
和load
两个方法
load
将模型路径作为参数
```python
self.load_state_dict(t.load(path))1
2
3
4
5
6
7
8
9
10
#### save
- 使用模型 + 时间作为命名,传入参数,并放入`checkpoints/`文件夹
- ```python
if name is None:
prefix = 'checkpoints/' + self.model_name + '_'
name = time.strftime(prefix + '%m%d_%H:%M:%S.pth')
t.save(self.state_dict(), name)
实际使用:
1 | module.save() |
- 一般模型继承BasicModule并加以实现
数据集导入
在models/__init__.py
实现:```python
from .DATASETNAME import DATASETNAME
… #多个模型的添加以此类推1
2
3
4
5
6
- 使得主函数中可以写为
- ```python
import models
model = getattr(models, 'DATASETNAME')()直接修改字符串可以选择具体模型
工具函数
Visualizer的封装
- 构造函数
visdom.Visdom(...)
,传入env和其他参数
属性
index
- 字典,记录plot的名称和是第几个绘制的图片- key - name, value - 下标
- 用于确认是需要在已有串口上进行绘制还是新窗口
log_text
- 输出的日志
绘图
- ```python
x = self.index.get(name, 0)在窗口上绘制多个点
self.vis.line(Y=np.array([y]), X=np.array([x]),
self.index[name] = x + 1win=unicode(name), opts=dict(title=name), update=None if x == 0 else 'append', **kwargs )
1
2
3
4
5
6
7
8
9
#### 输出图像
- ```python
self.vis.images(img_.cpu().numpy(),
win=unicode(name),
opts=dict(title=name),
**kwargs
)
输出文本
```python
self.log_text += (‘[{time}] {info}
‘.format(time=time.strftime('%m%d_%H%M%S'),\ info=info))
self.vis.text(self.log_text, win)
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
-
## 配置文件
- 将所有可配置项放入`config.py`,包括模型定义、数据处理和训练的变量默认值 => 方便调试修改代码
- 主要用到的变量如下:
- ```python
class DefaultConfig(object):
# visdom参数
env = 'default' # visdom 环境
model = 'AlexNet' # 使用的模型,名字必须与models/__init__.py中的名字一致
# 数据集参数
train_data_root = './data/train/' # 训练集存放路径
test_data_root = './data/test1' # 测试集存放路径
load_model_path = 'checkpoints/model.pth' # 加载预训练的模型的路径,为None代表不加载
# 模型参数
batch_size = 128 # batch size
use_gpu = True # use GPU or not
num_workers = 4 # how many workers for loading data
print_freq = 20 # print info every N batch
debug_file = '/tmp/debug' # if os.path.exists(debug_file): enter ipdb
result_file = 'result.csv'
# 训练参数
max_epoch = 10
lr = 0.1 # initial learning rate
lr_decay = 0.95 # when val_loss increase, lr = lr*lr_decay
weight_decay = 1e-4 # 损失函数在程序中使用
config.py
中的参数```python
import models
from config import DefaultConfigopt = DefaultConfig()
lr = opt.lr
model = getattr(models, opt.model)
dataset = DogCat(opt.train_data_root)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 通过命令行传入需要参数并覆盖默认配置
```python
def parse(self, kwargs):
'''
根据字典kwargs 更新 config参数
'''
# 更新配置参数
for k, v in kwargs.iteritems():
if not hasattr(self, k):
# 警告还是报错,取决于你个人的喜好
warnings.warn("Warning: opt has not attribut %s" %k)
setattr(self, k, v)
# 打印配置信息
print('user config:')
for k, v in self.__class__.__dict__.iteritems():
if not k.startswith('__'):
print(k, getattr(self, k))使用方法:
```python
opt = DefaultConfig()
new_config = {‘lr’:0.1,’use_gpu’:False}
opt.parse(new_config)
opt.lr == 0.11
2
3
4
5
6
7
8
9
10
11
12
13
14
## main.py
### fire
- 假设main.py如下:
- ```python
import fire
# def FUNC(para1, ...)
# ... 各种定义函数
if __name__ == '__main__':
fire.Fire()
在命令行可以直接调用文件内的函数
- ```shell
python main.py FUNC –para1=VALUE1 –para2=VALUE21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
### 主程序函数分析
- 四个函数:
- `train` - 训练
- `val` - 辅助训练
- `test` - 测试
- `help` - 打印帮助信息
- `__main__`部分如下:
- ```python
if __name__=='__main__':
import fire
fire.Fire()
- ```shell
因此可以通过:
*python main.py <function> --args=xx
执行训练测试等等
训练
步骤:
- 定义网络
- 定义数据
- 定义criterion(loss func)和optimizer
- 计算重要指标
- 开始训练
- 训练网络
- 可视化指标
- 计算验证集上的指标
训练函数
根据命令行更新参数配置
1 | opt.parse(kwargs) |
处理模型
- 从Models导入模型包
- (optional)从本地加载模型文件
- (optional)使用GPU
处理数据
- 加载训练集和测试集
- 设置两个dataloader
目标函数和优化器
统计指标
平滑处理之后的损失
1
loss_meter = meter.AverageValueMeter()
混淆矩阵
1
confusion_matrix = meter.ConfusionMeter(2)
previous_loss用于存储上次的损失
训练
每个epoch清空上次的平滑损失和混淆矩阵
1
2loss_meter.reset()
confusion_matrix.reset()遍历训练集
1
for ii,(data,label) in enumerate(train_dataloader):
加载Batch的input和label
1
2input = Variable(data)
target = Variable(label)优化器清零
1
optimizer.zero_grad()
过模型,计算结果
1
score = model(input)
计算损失函数
1
loss = criterion(score,target)
反向传播
1
loss.backward()
更新
1
optimizer.step()
可视化结果保存
将loss[0]加入loss_meter
1
loss_meter.add(loss.data[0])
混淆矩阵加入(score.data, target.data)
1
confusion_matrix.add(score.data, target.data)
训练到一定程度进行绘图
1
2if ii%opt.print_freq==opt.print_freq-1:
vis.plot('loss', loss_meter.value()[0])每个epoch保存模型
1
model.save()
计算验证集的表现
1
val_cm,val_accuracy = val(model,val_dataloader)
并绘图
1
2
3
4
5
6
7
8vis.plot('val_accuracy',val_accuracy)
vis.log("epoch:{epoch},lr:{lr},loss:{loss},train_cm:{train_cm},val_cm:{val_cm}"
.format(
epoch = epoch,
loss = loss_meter.value()[0],
val_cm = str(val_cm.value()),
train_cm=str(confusion_matrix.value()),
lr=lr))
调整学习率
如果损失不下降,降低学习率
1
2
3
4
5
6if loss_meter.value()[0] > previous_loss:
lr = lr * opt.lr_decay
for param_group in optimizer.param_groups:
param_group['lr'] = lr
# 这里记录上一步的损失
previous_loss = loss_meter.value()[0]
meter工具
- 快速统计训练工程中的指标
AverageValueMeter
计算所有数的平均值和标准差- 统计epoch损失的平均值
confusionmeter
- 统计分类情况
验证
注意将模型置于验证模式,验证完后调整回训练模式
将模型设置为验证模式
1
model.eval()
初始化混淆矩阵
1
confusion_matrix = meter.ConfusionMeter(2)
进行遍历数据集并存储相关指标
1
2
3
4
5
6
7
8
9for ii, data in enumerate(dataloader):
input, label = data
val_input = Variable(input, volatile=True)
val_label = Variable(label.long(), volatile=True)
if opt.use_gpu:
val_input = val_input.cuda()
val_label = val_label.cuda()
score = model(val_input)
confusion_matrix.add(score.data.squeeze(), label.long())恢复模型为训练模式
1
model.train()
计算整个验证集上的表现
1
2
3cm_value = confusion_matrix.value()
accuracy = 100. * (cm_value[0][0] + cm_value[1][1]) /\
(cm_value.sum())
测试
- 这里计算每个样本属于狗的概率,将结果保存为csv
处理输入参数
1
opt.parse(kwargs)
加载模型
1
model = getattr(models, opt.model)().eval()
构建数据集和数据集加载器
1
2
3
4
5train_data = DogCat(opt.test_data_root,test=True)
test_dataloader = DataLoader(train_data,\
batch_size=opt.batch_size,\
shuffle=False,\
num_workers=opt.num_workers)编辑测试集计算结果并保存
1
2
3
4
5
6
7
8
9results = [] # 结果数组
for ii,(data,path) in enumerate(test_dataloader):
input = t.autograd.Variable(data,volatile = True)
if opt.use_gpu: input = input.cuda()
score = model(input)
# 计算概率
probability = t.nn.functional.softmax(score)[:,1].data.tolist()
batch_results = [(path_,probability_) for path_,probability_ in zip(path,probability) ]
results += batch_results写文件
1
write_csv(results,opt.result_file)
使用
1 | 训练模型 |