先简要总结下论文复现挑战赛的步骤:
1 选论文任务,一般就是选看着顺眼的论文。或者先预选几个,简单的看下论文/代码,哪个有把握就选哪一个(当然水平高的,哪个论文性价比高选哪个)
2 代码实现,并调参以达到论文效果。这步是大头,又可以分为:网络结构代码部分(代码转换/权重转换/生成tensor验证模型正确性)和组网训练(loss对齐和精度复现)。
3 分析/总结 即使最终失败了,通过分析和总结,也能提高自己的技术水平,那么离成功就不远了。
这次论文复现营,勉强在最后一天,做到了生成tensor验证模型正确性这一步,也就是俗话说的前向对齐。但即使到这一步,也是碰到很多坑的,真是纸上得来终觉浅,绝知此事要躬行!
几个比较耗时的情况如下:
1 torch的 sequencal里面,可以使用OrderedDict数据类型,而飞桨里面是tuple数据类型,并且torch的OrderedDict里可以再嵌套OrderedDict,OrderedDict里还可以嵌入列表表达式。我刚开始直接手工把OrderedDict改成tuple,结果导致里面的网络层数都对不上。后来在论文复现营课堂上学到一招,使用*来解包。如下分别是torch代码和转换后的飞桨代码:
#torch代码:
self.body = nn.Sequential(OrderedDict([
('block1', nn.Sequential(OrderedDict(
[('unit01', PreActBottleneck(cin=64*wf, cout=256*wf, cmid=64*wf))] +
[(f'unit{i:02d}', PreActBottleneck(cin=256*wf, cout=256*wf, cmid=64*wf)) for i in range(2, block_units[0] + 1)],
))),
#飞桨代码:
self.body = nn.Sequential(
('block1', nn.Sequential(
*[('unit01', PreActBottleneck(cin=64 * wf, cout=256 *
wf, cmid=64 * wf))] + [(f'unit{i:02d}', PreActBottleneck(cin=\
256 * wf, cout=256 * wf, cmid=64 * wf)) for i in range(2,
block_units[0] + 1)])),
2 前向对齐中,通过添加断点输出,观察是哪一步没有对齐。最终,发现是head层没有对齐,且定位到head层的conv算子。这步花费的时间非常多,首先分步输出对比数值是个体力活,最主要的是,定位到conv算子这块,一度手足无措,找不到头绪。后来是先比较torch和飞桨的conv处代码,理解代码思路,最终发现是引用的一处x2paddle代码有bug,一个参数置0的初始化函数没有起作用,导致跟torch的初始化参数不一致。具体用到的是paddle.assign这个函数,代码块为:
def zeros_init_(param):
replaced_param = paddle.create_parameter(
shape=param.shape,
dtype=param.dtype,
default_initializer=paddle.nn.initializer.Assign(
paddle.zeros(param.shape, param.dtype)))
paddle.assign(param, replaced_param)
最后一句正确的写法应该是:paddle.assign( replaced_param, param)
今天2021.6.15日,突然发现https://github.com/PaddleEdu/Transformer-CV-models/tree/main/docs 这里有参数全0初始化的工具代码,哎,晚了 。
大佬牛批了
马上有新的复现赛,欢迎参加,再接再厉
重在参与,复现的过程,就是学习提高的过程!