参加了百度飞桨的论文复现,GAN论文的实现是基于pytorch的,这样就有pytorch迁移的到paddle过程。 中途有不小的坑,现在刚好总结一下:
1. 主体思路
2. tensor 包装
3. api 包装
4. pretrain 模型转换
5. 还没搞清楚的坑
1. 主体思路
我的思路就是重新写一个torch的库(用paddle实现torch的接口),我命名为paddle_torch, 实现了之后我们复现pytorch 代码,就是只需要 把 import torch 改为 import paddle_torch 就可以了。 最后结果当然还没有这么完美,但也节省了我90%的时间在复现第二篇论文的时候。
上面就是我写的各个paddle_torch 模块,基本上就是我一路复现一路加上去的。里面的test 包括了一些简单unit test,来确保实现的功能跟原来的torch 一致 。下面的叙述是针对paddle的1.8 版本, 其他更新的版本可能不一样。
2. Tensor 包装
pytorch 中的最基本数据类型就是torch.Tensor, 这个跟paddle的Tensor不一样,反而跟VarBase类似。 而paddle的VarBase的已经实现的功能被torch.Tensor要少很多, 所以复现第一件事情就是去做一个Tensor的wrapper,来封装VarBase. 下面是构造函数,Tensor和VarBase都可以接受numpy或者python 数组的输入,所以这种情况(下图的else )就直接用VarBase 的构造函数。 如果输入是另一个Tensor/VarBase (下图的if), 其目的就是类似Tensor.clone/copy 创建另一个变量包含同样的value 和gradient。 就要用VarBase 的另一个构造函数先创建一个类似place holder的壳,再用assign 赋值. 第二种情况是的处理我是尝试了不同方法后才发现只有这个方法可以在training的时候gradient 顺利的pass 下去。
后面就算实现了一堆torch.Tensor的函数,希望尽量做到后面的复现不用改原来pytorch的代码:
有些函数例如cpu(), to(), cuda() 之类的在paddle根本就不需要,因为一开始就定义了device的scope。 所以这些函数就直接返回object本身。
3. api 包装
当时课程上有一个名为“Paddle1.8-Pytorch-API对照表-论文复现.xlsx”,这个挺有用的,但也有不少错误。 这个也是我要吐槽的地方,paddle的api一直变化很大, 导致文档也跟不上,而且很多设计的地方看出来是为了兼容之前静态图的,所以没有pytorch一开始就是动态图的设计自然和顺手。
跟tensor类似, 我先对最基本的torch.nn.Module 封装,利用的是paddle的Layer 类, 他们两个功能基本一样,所以接口方面要重新实现的不多。
如果paddle本身就有例如Conv2D和Linear我就直接用函数来包装一下interface,我还根据复现的文章实现一些其他pytorch 有而不在paddle的模块,例如InstanceNorm2d. 类似地我也封装了torch.nn.functional 的函数,就是尽量做到原来pytorch代码不改的情况下在paddle环境运行。
这里要注意的是class ModuleList(dygraph.LayerList) 里面的insert 方法。 在paddle的LayerList类,如果一开始的list为空,insert是会出错的(而pytorch是允许的,insert空的list作用就跟append一样),这里要特别handle下。 还有些比较难的实现代码是参考小伙伴在paddle github issue的解决方案。
4. pretrain 模型转换
这一块是要非常小心的,因为很容易运行通过但出来的结果不一样。这个体现了做unit test的重要性,一个大的复现项目先确保每一个小模块的输入和输出是一致的。 特别注意的是FC的出来,paddle的weight 是要transpose pytorch的weight才可以对上。 但有时候如果weight square matrix ,这样不transpose也可以运行通过,不过结果就是错误的。 要测试转换后的paddle model是否跟原来的pytorch 一致, 产生几个随机的数据输入然后看看输出是否小数点后6位都一样就可以。
这里也体现了我之前的复现思路的好处,就是不用改原来pytorch的代码,这样连变量的名字和state dict的变量顺序都跟pytorch完全一致,不用做复杂的变量对齐。
5. 还没搞清楚的坑
最后说下我提了issue但还没有满意的答复的地方
1. pytorch是可以直接指定Tensor的gradient的值, paddle要用hook的方法,这样无法避开改原来pytorch的代码
2. 数组索引, pytorch可以用LongTensor的数组做索引,而VarBase不可以, 这里涉及C++的native operation所以也没办法重载
3. 没有找到paddle up to date 的写cuda 扩展layer的方法。