Pytorch常用工具总结

数据处理

数据加载

  • 自定义数据集
    1. 继承Dataset,实现python方法:
      • __getitem__: 返回一条数据/一个样本
      • __len__: 返回样本数量

以猫狗识别为例:

  • 文件结构

    所有文件放在一个文件夹,根据前缀判断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    data/dogcat/
    |-- cat.12484.jpg
    |-- cat.12485.jpg
    |-- cat.12486.jpg
    |-- cat.12487.jpg
    |-- dog.12496.jpg
    |-- dog.12497.jpg
    |-- dog.12498.jpg
    `-- dog.12499.jpg
  • 在构造函数中获取图片路径

    1
    2
    3
    4
    5
    def __init__(self, root):
    # 图片文件夹路径
    imgs = os.listdir(root)
    # 遍历文件夹下所有文件 + 文件姐路径=> 构成绝对路径
    self.imgs = [os.path.join(root, img) for img in imgs]
  • 定义__getitem__,这一步中真正读取图片,并根据文件名生成label

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def __getitem__(self, index):
    img_path = self.imgs[index]
    # 根据文件名生成Label
    label = 1 if 'dog' in img_path.split('/')[-1] else 0
    # 加载图片
    pil_img = Image.open(img_path)
    # 图片转为numpy
    array = np.asarray(pil_img)
    # numpy转为tensor
    data = t.from_numpy(array)
    return data, bale
  • 还需要定义__len__,返回imgs数组的长度

  • 调用数据加载类

    • ```python
      dataset = DogCat(FILEPATH)

      访问某个元素

      dataset[INDEX]

      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

      ### transforms

      - 常见操作

      - | 操作 | 说明 |
      | ------------------------------------------------ | ------------------------------------------------ |
      | `Scale` | 调整图片尺寸,长宽比 |
      | `CenterCrop`, ``RandomCrop`, `RandomResizedCrop` | 裁剪 |
      | `pad` | 填充 |
      | `ToTensor` | 将PIL Image对象转成Tensor,将[0, 255]归一化[0,1] |

      - 对Tensor的操作

      - Normalize: 标准化
      - `ToPILImage`: Tensor转PIL Image
      - `Compose`: 拼接

      - 实现

      - ```python
      transform = T.Compose([
      T.Resize(224), # 缩放,保持长宽比,短边224
      T.CenterCrop(224), # 中间切224*224
      T.ToTensor(), # 归一化到[0, 1]
      T.Normalize(mean = [.5, .5, .5], std = [.5, .5, .5]) # 表转化到[-1, 1]
      ])
    • 在数据集的类中定义transform,并在__getitem__方法中返回transforms处理过的data(图像数据).

  • 自定义转换

    • e.g. trans=T.Lambda(lambda img: img.rotate(random()*360))

ImageFolder

  • 假设文件按文件夹保存,每个文件夹存储同类别,文件夹名为类名。

    1
    ImageFolder(root, transform=None, target_transform=None, loader=default_loader)
    • root: 路径
    • transform: 对图片的转换操作
    • target_transform: 对label转换
    • loader: 读取格式
  • 属性

    • class_to_idx - 类名和类下标的对应关系

图片保存:Channel x Height x Width

DataLoader

  • ```python
    DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, num_workers=0, collate_fn=default_collate, pin_memory=False, drop_last=False)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    - `dataset` - Dataset对象
    - `shuffle` - 打乱
    - `sampler`
    - `num_workers` - 多进程加载的进程数
    - `collate_fn` - 多个样本数据拼成一个batch的方法,一般使用默认
    - `pin_memory` - 是否将数据保存在`pin_memory`区,转到GPU快
    - `drop_last` -师傅 将最后不足`batch_size`个数据丢弃

    #### 可迭代

    - 可以使用for循环

    - ```python
    for batch_datas, batch_labels in dataloader:
  • 可以使用迭代器

    • ```python
      dataiter = iter(dataloader)
      batch_datas, batch_labels = next(dataiter)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18

      ### 剔除出错样本

      #### 返回None,并在拼合为batch的时候筛选掉

      - 使用`try`, `except`

      - 异常情况:`return None, None`

      > 这里需要配合实现`Dataloader`的`collate_fn`,将None过滤

      ```python
      from torch.utils.data.dataloader import default_collate # 导入默认的拼接方式
      def my_collate_fn(batch):
      # 过滤为None的数据
      batch = list(filter(lambda x:x[0] is not None, batch))
      if len(batch) == 0: return t.Tensor()
      return default_collate(batch) # 用默认方式拼接过滤后的batch数据

随机选取图片代替

  • 优点:保证每个batch的数目仍是batch_size

注意事项

  • 高负载的操作放在__getitem__,如加载图片 => 为实现并行加速
  • dataset包含只读,避免修改 => 使用多进程加载,修改可能产生冲突

sampler

  • 对数据采样,loader里面shuffle为True,调用随机采样器RandomSampler,打乱数据;默认使用SequentialSampler,顺序采样
WeightedRandomSampler
  • 根据每个样本的权重选取数据,比例不均衡问题中进行重采样
  • 构建时提供每个样本的权重weights和选取样本总数num_samples,可选replacement
    • 权重越大被选中概率越大
    • replacement决定是否可以重复选取某一样本

在指定了sampler的情况下,shuffle不再生效;一个epoch返回的图片总数取决于sampler.num_samples

计算机视觉工具包 torchvision

  • models:提供网络结构以及预训练好的模型
  • datasets:提供常用的数据集加载
  • transforms:提供常用的数据预处理

Visdom

  • env: 不同环境的可视化结果相互隔离,默认main
  • pane: 窗格

安装和使用

  • ```shell
    $ pip install visdom
    $ python -m visdom.server # 启动visdom服务
    $ nohup python -m visdom.server & # 将服务放至后台运行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    > **注意:**
    >
    > 手动指定保存`env`,在web上或者程序里save,否则visdom服务重启后,信息会丢失。

    #### 常用操作

    - 新建连接客户端

    - ```python
    vis = visdom.Visdom(env=u'test1',use_incoming_socket=False)
    • 可以指定host, port..
  • 画图函数: line, image, text, histogram, scatter, bar, pie…

支持tensor和narray的数据结构

常见参数
  • win: 指定Pane的名字,两次操作指定的win相同,则覆盖

    需要更新数值且不覆盖,指定参数update='append'

    也可以使用vis.updateTrace更新图:

    1. 增加新trace

      1
      2
      3
      x = t.arange(0, 9, 0.1)
      y = (x ** 2) / 9
      vis.line(X=x, Y=y, win='polynomial', name='this is a new Trace',update='new')

      在原trace上追加

      1
      2
      3
      4
      5
      for ii in range(0, 10):
      # y = x
      x = t.Tensor([ii])
      y = x
      vis.line(X=x, Y=y, win='polynomial', update='append' if ii>0 else None)
  • opts: 选项,接收字典,用于pane的显示格式

text
  • 支持html

使用GPU加速 cuda

注意事项

  • 损失函数定义后也应该调用criterion.cuda转移到GPU

  • 推荐方法设置环境变量CUDA_VISIBLE_DEVICES

    • 在运行py的命令行实现

    • 在程序中:

      • ```python
        import os
        os.environ[“CUDA_VISIBLE_DEVICES”] = “2”
        1
        2
        3
        4
        5

        - 在Jupyter notebook中:

        - ```shell
        %env CUDA_VISIBLE_DEVICES=1,2

持久化

  • 可持久化的对象:
    • Tensor
    • Variable
    • nn.Module
    • Optimizer
  • 都保存成Tensor,使用t.save(OBJ, FILENAME)t.load(FILENAME)可完成
    • load的时候可以指定加载的存储位置,设置map_location参数
  • 建议module和optimizer保存为state_dict
module
  • ```python

    保存

    t.save(model.state_dict(), FILENAME)

    加载

    model.load_state_dict(t.load(FILENAME))
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    - optimizer同样

    #### 一起保存加载

    - ```python
    all_data = dict(
    optimizer = optimizer.state_dict(),
    model = model.state_dict(),
    info = u'模型和优化器的所有参数'
    )
    t.save(all_data, 'all.pth')

    all_data = t.load('all.pth')