Pytorch实战笔记

参考:https://zhuanlan.zhihu.com/p/29024978

文件组织架构

  • 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.ModuleBasicModule类,主要提供saveload两个方法

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
2
module.save()
module.load(opt.load_path)
  • 一般模型继承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]),
              win=unicode(name),
              opts=dict(title=name),
              update=None if x == 0 else 'append',
              **kwargs
             )
    
    self.index[name] = x + 1
    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 DefaultConfig

    opt = 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.1

      1
      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=VALUE2
      1
      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()
  • 因此可以通过:*python main.py <function> --args=xx执行训练测试等等

训练

步骤:

  1. 定义网络
  2. 定义数据
  3. 定义criterion(loss func)和optimizer
  4. 计算重要指标
  5. 开始训练
    1. 训练网络
    2. 可视化指标
    3. 计算验证集上的指标

训练函数

根据命令行更新参数配置

1
2
opt.parse(kwargs)
vis = Visualizer(opt.env)

处理模型

  1. 从Models导入模型包
  2. (optional)从本地加载模型文件
  3. (optional)使用GPU

处理数据

  1. 加载训练集和测试集
  2. 设置两个dataloader

目标函数和优化器

统计指标

  1. 平滑处理之后的损失

    1
    loss_meter = meter.AverageValueMeter()
  2. 混淆矩阵

    1
    confusion_matrix = meter.ConfusionMeter(2)
  3. previous_loss用于存储上次的损失

训练

  1. 每个epoch清空上次的平滑损失和混淆矩阵

    1
    2
    loss_meter.reset()
    confusion_matrix.reset()
  2. 遍历训练集

    1
    for ii,(data,label) in enumerate(train_dataloader):
    1. 加载Batch的input和label

      1
      2
      input = Variable(data)
      target = Variable(label)
    2. 优化器清零

      1
      optimizer.zero_grad()
    3. 过模型,计算结果

      1
      score = model(input)
    4. 计算损失函数

      1
      loss = criterion(score,target)
    5. 反向传播

      1
      loss.backward()
    6. 更新

      1
      optimizer.step()
    7. 可视化结果保存

      1. 将loss[0]加入loss_meter

        1
        loss_meter.add(loss.data[0])
      2. 混淆矩阵加入(score.data, target.data)

        1
        confusion_matrix.add(score.data, target.data)
  3. 训练到一定程度进行绘图

    1
    2
    if ii%opt.print_freq==opt.print_freq-1:
    vis.plot('loss', loss_meter.value()[0])
  4. 每个epoch保存模型

    1
    model.save()
  5. 计算验证集的表现

    1
    val_cm,val_accuracy = val(model,val_dataloader)
    • 并绘图

      1
      2
      3
      4
      5
      6
      7
      8
      vis.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))
  6. 调整学习率

    如果损失不下降,降低学习率

    1
    2
    3
    4
    5
    6
    if 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. 将模型设置为验证模式

    1
    model.eval()
  2. 初始化混淆矩阵

    1
    confusion_matrix = meter.ConfusionMeter(2)
  3. 进行遍历数据集并存储相关指标

    1
    2
    3
    4
    5
    6
    7
    8
    9
    for 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())
  4. 恢复模型为训练模式

    1
    model.train()
  5. 计算整个验证集上的表现

    1
    2
    3
    cm_value = confusion_matrix.value()
    accuracy = 100. * (cm_value[0][0] + cm_value[1][1]) /\
    (cm_value.sum())

测试

  • 这里计算每个样本属于狗的概率,将结果保存为csv
  1. 处理输入参数

    1
    opt.parse(kwargs)
  2. 加载模型

    1
    model = getattr(models, opt.model)().eval()
  3. 构建数据集和数据集加载器

    1
    2
    3
    4
    5
    train_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)
  4. 编辑测试集计算结果并保存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    results = [] # 结果数组
    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
  5. 写文件

    1
    write_csv(results,opt.result_file)

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 训练模型
python main.py train
--train-data-root=data/train/
--load-model-path='checkpoints/resnet34_16:53:00.pth'
--lr=0.005
--batch-size=32
--model='ResNet34'
--max-epoch = 20

# 测试模型
python main.py test
--test-data-root=data/test1
--load-model-path='checkpoints/resnet34_00:23:05.pth'
--batch-size=128
--model='ResNet34'
--num-workers=12

# 打印帮助信息
python main.py help