BUCT-Vision / weekly-review

Weekly-review of BUCT Lab-614
https://github.com/DIYer22/weekly-review/issues
29 stars 4 forks source link

Review-2018.05.13-杨磊 #138

Open DIYer22 opened 6 years ago

DIYer22 commented 6 years ago

Review

一. 在 Furniture 数据集上实验一种共生模型

1. 实验简介

实验一种共生模型,大致思想如下:

共生模型分为宿主模型:host 寄生模型:parasitic, 而 parasitic 能补足 host 的不足之处。

大致结构如下图: Venom net
每个节点代表一张 feature map,每层代表一个 Block

首先,选一个 base model 作为 host,host 先单独训练。

host 训练完成后,固定住 host 的参数,开始构建 parasitic,parasitic 的 block 数目和 host 相同

对于每一层 block,parasitic 的 Input 要 concat 上一层的 host 和 parasitic 的 Output .

因此,parasitic block 的 Input Channel 为 host 的 1.5 倍数 Output Channel 为 host 的 0.5 倍数,

以此,构建出寄生于 host 的 parasitic 主干网络

接下来移植 TreeSegNet 中的构建树状网络的方法,根据 host 在 validation set 上的性能,构建一颗树状网络:Tree net

将 Tree net, 接在 parasitic 主干网络后面, 使得整个 parasitic 能够学到 host 的不足之处

接下来训练 parasitic 网络,此时 host 的参数是固定的。为避免多余的计算,传给 parasitic 的 feature 要 detach

2. 实现过程 (踩坑记)

通过看 pytorch-summary 的源码,学会了通过 model.apply(regist_hook) 注册 hook 函数来探索网络内部结构,这个操作非常 Hack,但坑也特别多,下文会说。

使用 model.apply(regist_hook) 实现了自动检测 host 的网络结构及每个 block 的内部信息,并自动构建出对应的 parasitic 主干网络

接下来就是训练过程,一开始 我的方案是在 parasitic 内的 self.host=host 使 host 成为 parasitic 的子网络。

parasitic.foward(x) 内部, 先用 self.host.eval() 阻止计算 host 的 grad,再通过 self.host.apply(regist_hook) 注册提取 feature 的钩子函数, 在 self.host(x) 后, 便能通过钩子函数 提取出 host 的 feature。

这样下来发觉计算很慢,一个 epoch 比以前多花几倍的时间。经排查,在host.eval()模式下,host 网络参数的 grad 本应该为 None ,而运行后该参数的 grad 为 0矩阵。这说明 self.host.eval() 失效,整个 host 计算了 grad,哪怕 detach 了,也只是把 detach 部分的导数设置为 0,继续向后计算 grad。这个问题和之前提出来的 不在计算图中仍会计算 grad 的 issue 很相似

为避免 host.grad 的无效计算,不能把 host 作为 parasitic 的子网络。于是,我直接把 host 放到 parasitic 外部 作为全局变量,在 parasitic.foward(x) 内部再调用 host(x) 提取 feature。
这种方案在单卡上表现完美。但在多卡情况下,系统复杂性上升了一个数量集,各种错误。

(下都以双卡为例)
在多卡中,module 要被封装成为 torch.nn.DataParallel
通过查看文档和源码,DataParallel 的原理是:

  1. 把 module 复制两份 分别放在 0, 1 卡上即 module_0, module_1 (此时 整个Pyhton环境 有3个一样的 module,但是 id 不同)
  2. forward 时,把 input:x 在 batch_size 维度 分成两块 分别放到 0, 1 卡上即 x_0, x_1
  3. 多线程的形式 同时对每张卡上的数据和模型并行运行 y_i = module_i.forward(x_i) (多线程中,全局变量是可以共享的)
  4. 最后 将两张卡上 return 的结果 y_i 传输到 output_device (默认为 cuda(0)),合并成原始 batch_size 大小的 y , 再 return y

因此,parasitic.foward(x) 内部的 x 是被 DataParallel 分离出来的 半个 batch_size 的 x_i 了, 而且 x_i 已经是分配到了特定卡上的变量, 只有也在特定卡上的 module 才能处理

我还试过把 feature 打包为 list,作为 host 的输出,再传入 parasitic,但实验发现 DataParallel 只允许 return tensor,遂作罢。

经过不断的实验,最后多卡提取 feature 的方案是:在运行 parasitic.foward(x) 前,先运行 host(x) 并通过钩子函数将 host 的 feature 存储到全局变量 shareDic。钩子函数会根据 feature 所在的 device.id 把 feature 分开存储。在 parasitic.foward(x) 时,会根据 x 所在的 device.id 提取 shareDic 中对应 device 上的 feature ,然后前向计算 parasitic 的输出。

实验表面 上述方法可在 多卡中正常运行,不会计算 host 的 grad,单个 epoch 的时间也下降到了正常水平。

在上述实验中:

接下来还要把 TreeSegNet 中的 tree net 接上, 再跑实验

二. 完善工具库 Box-X

  1. 重构了多进程和多线程的 map 接口 mapmpmapmt
  2. 写了 setup.py 打包到了PyPI, 可通过 pip install boxx 安装
  3. 完善了 linux/win/osx 的兼容性,以及 $DISPLAY 为空的情况
  4. 添加了 通过代码自动log的工具 叫 logc (log code)
    >>> from boxx import logc
    >>> a, b = 1, 2
    >>> logc("c = a + b")
    Code: c = a + b
    └──—— 3 = 1 + 2

三. 看了两集台大李宏毅的 Machine Learning and having it deep and structured

Next

  1. Furniture 实验尽快收尾
  2. 复盘人脸生成有关的GAN,起草毕业论文
  3. Box-X 写个简明文档
ilydouble commented 6 years ago

需要约个事件当面讨论