目前正在复现一篇paper的代码,工作还没有完成,这里作为自己的经验总结。
首先必须得说,复现他人的程序实在是迫不得已的事情。要么源码无法要到,要么就是不符合自己的编程习惯或者输入输出不能够对应得上。通过paper去复现代码虽然相对可靠,但是也难免有作者并没有提及的细节处理地方,所以预先要做好充分的心理准备。前路漫漫~~~
精读paper,梳理算法流程及输入输出
准备开始动手前,一定要精读paper,至少是要精度算法设计部分。这里如果作者阐述很详细,采用伪码表或者算法流程图来表达,那么就会方便很多。
我目前的习惯是,先把整篇论文的算法流程搞清楚,看每个子函数的时候,第一遍至少将子函数的输入输出搞懂(因为paper上的思路有时并不是阅读一遍就能够搞懂),这样一遍流程下来,就可以把整篇论文的框架梳理出来。
根据整体的算法流程图,就可以把子函数纳入其中,有点纲举目张的感觉。
在这里插一句,我其实也是算法编程的小白,经验仅作参考
设计数据结构,并详细记录
在第一步完成之后,我梳理了程序中常用的参数的数据结构(当然,在这里应该反观第一步,需要把重要的数据及其使用记录下来)。有的数据类型比较复杂,根据不同的编程语言需要设计不同的结构,比如C++中的vector/struct,Matlab中的cell等等。
我个人感觉,数据结构的定义非常重要,因为后序程序的运行会经常调用需要用到的数据,也会将结果,存储在对应的位置。而且,数据结构的定义也需要有清晰的文字记录。之前在阅读他人程序的时候,因为数据的定义不清楚,只能采用猜测+调试的方法最终搞明白,确实花费了太多的时间。在做项目的过程中,软件设计也需要一份清晰的输入输出、以及中间数据的定义,这也是从中学习到的方法。
所以这次我对数据进行了一个有记录的定义,至少保证我自己能够看懂,而且遇到问题的时候我可以查询、检验。
不过在算法实现的过程中,我发现,数据的定义在预先设想和程序具体实现时是有差异的。所以需要至少2个版本,预先设计的版本和实际程序实现的版本,在程序实现的过程中,发现需要调整数据格式的,需要及时进行修改,并详细记录下来。在本次实现中,cell数据最多有4层,最大的单独数据存储量达到了1.53GB。
程序实现,从前至后,由简入难,测试校验,细致莫烦
然后就是程序的具体实现过程了,现在工作还在进行中,只能记录一些零散的经验……
1)按照程序的流程来编写代码,整体的框架列出来,然后先把数据输入的部分整理好。这样做的好处在于,后面的程序可以有正确格式的输入,就可以在每写完一个函数之后进行详细的测试;
2)每一段函数 / 每个子函数 写完之后都需要进行详细测试!因为程序中除了逻辑错误,还会出现书写错误,如果书写错误没有导致逻辑问题,也是将数据导向了另一个位置,那么后序调试起来真的会崩溃的!!!所以应该在写完一段程序之后,单步运行查看数据的结果是否正确,写完一个函数之后,需要对函数的功能进行测试,同时考虑可能出现的意外情况,先在注释中记录下来。Matlab实现程序时,可以单独建一个测试脚本。
2020-07-04
------未完待续------
程序复现的工作在继续开展,碰到一个较难的点,还没有想清楚怎么实现,所以也就先来再继续总结一下经验。
关于为什么要先阅读paper的算法框架,再梳理输入输出及Local数据的格式,今天又有了一点新的认识。
数据的结构如何组织,与程序具体实现的方式也有关系。例如:for循环的判据是什么,就会直接对数据的组织结构产生影响。所以在熟悉程序框架的时候,不仅要看到有哪些数据要进行结构的梳理,还需要尽量根据程序实现的方式,构想出更为合理的数据结构。
如上图所示,一个子函数的程序实现其实有不同的方式,这里的一个结构就比较复杂,第一轮循环中采用 target → task → position的三重循环,来进行判断,随后又对计算结果按照orbit 来进行组织,这对数据的结构就提出了比较高的要求,同时,复现的时候也容易被数据的格式搞得心烦意乱。但是数据的格式一旦出错,程序运行就肯定是错误的!!
而数据的格式一旦设计好之后,后序的程序的代码实现以及测试都会方便很多,我个人的体会:前期数据格式没有确定的时候,感觉复现的工作很难推进,畏难情绪也很大,等数据格式确定之后,程序的实现以及测试在2-3天内就取得了飞速的进展,至少完成到70%的过程中困难都不大。可见这项工作的进展也并不是按照线性模式来前进和完成的。
一些小的Tips
对于要复现代码的paper,我的习惯是把文章打印出来。采用知云PDF阅读器可以实现快速翻译,局部放大也可以查看得很清晰,不过对于算法前后功能对照查看的时候就显得有些捉襟见肘,即使水平拆分也不能获得整个纸质版paper拿在手中,能够掌握全局的感觉。
另外,尤其是对于算法中比较复杂和困难的部分,这些地方一般与前后环节有不少的交集,同时本身理解和实现起来难度较大,对于这些部分的反复研读、分析、做笔记和思考,有纸质版的论文确实会方便很多。
在代码实现过程中,通过Git托管代码可以方便地记录自己每天工作的内容和进度,既可以方便、安全地保存自己的工作,也可以使每一步工作可以溯源,最后回过头来还像保存的棋谱一样可以复盘,从中总结经验,是一个很不错的工具。
磨刀不误砍柴工,谋定而后动,做的过程中阶段性地总结经验教训,这样才能逐渐进步。
2020-07-05
------未完待续(下一次记录:如何解决一部分较难的算法)------
面对复杂算法设计,应当怎么办?一些经验和体会……
好久没有更新了!因为前一段时间在进行代码实现的时候遇到了各种Bug,是我始料未及的。也让我心情焦虑郁闷了比较长的一段时间,也没有心思来记录这个过程。现在情况稍稍有些缓解,所以先腾出手来进行一下总结。
(1)中间有一次和师兄交流,原paper的一些程序流程问题没有搞清楚,但当时我已经开始复现那部分代码了,师兄指出我这种行为的错误之处:在程序没有完全弄清楚之前,不要动手去复现,即使写出来了,还很可能是错误的,后面甚至需要花费更长的时间进行修改!!!所以一定要清楚理解:磨刀不误砍柴工!
(2)关于阅读代码时应当注意的问题(这一点体会太深刻了):
因为阅读伪码时不够细致(没有逐行进行分析+思考),自己存在很多的疑问,列出了一系列的不能理解的问题,和程序实现过程中面临的问题,导致自己对其代码理解不正确,还以为是对方写的有问题(对于正式发表的paper,以后这种念头真的是可以打消了)!
这个时候,能有人进行沟通真的很关键!这倒并不完全是能力的问题,很大程度上心态会产生影响,孤军奋战的感觉其实相当不好受,自己身后没有依靠,心中的力量耗尽的时候,就容易想到放弃,或者一段时间的怠惰——对于像我这种自身动力不是很足的人来说,真的是这样。
姜还是老的辣,通过逐行地对代码进行分析解读,之前产生的不理解的问题就迎刃而解了,完全是理解上的误区造成的。
(3)有问题要及时沟通。这次复现代码的过程中,还很有幸地与论文原作者进行了一次简单的十几分钟的沟通。虽然时间很短,但是就能够将自己对于算法的种种问题全部解决掉。
在请教之前,一定要充分准备!探讨的过程可以让自己对内容认识得更加清晰准确,另外也许会有科研方法上的启发。
(4)一个惨痛的教训!!!对于sophisticated算法部分,一定要在精神状态极佳的时候去实现,不能拖着自己满满不情愿的心情来“完成任务”!
因为一些原因,这个比较困难的算法的主要部分反而是在精神状态不太好、注意力不够集中的情况下完成的,这给后续的测试造成了极大的影响!
因为在困顿的时候,所能考虑的仅是算法流程的实现,但是对于算法性能的完备方面,却根本没有精力去思考了。
具体来说,复杂的算法牵涉的变量和子函数尤其多,大量的精力需要用来考虑变量引用格式的准确性、函数接口输入输出的正确性、函数流程实现的正确性。因为这是核心算法,甚至可能整个算法的framework都是为了这一步的操作而设计的。
所以这个部分对精力的集中程度、思维的缜密程度、考虑问题的全面程度的要求是最高的!而我,就败在了对问题考虑的全面性上了!
在程序实现的过程中,有的参数设定为某值,但可能为0,也可能为空,针对不同的数据类型,索引的范围可能溢出,以及一些逻辑判断可能是不合逻辑的……这些问题在后面的测试过程中层出不穷,而我在写程序的时候根本就没有考虑到这种情况,我把这种问题称之为算法的完备性不好——在此场景下可以解决问题,换一个场景就会出现Bug。(当然,这个问题肯定有非常多的人都曾经遇到过,以后还需要多与其他人沟通探讨)
(5)其实无论做什么事情,心态都会起到非常重要的作用,好的心态起正面作用,坏的心态则会让事情变得很糟糕。
盲目的对于算法实现的自信是千万要不得的。没有得到结果之前,谁也无法预料求解的结果会是怎样的。对于科研工作者而言,一定要对最终实现的结果有一个客观的面对心态,以及冷静的处理方式。否则,学习和培养的过程就不能称之为合格了。
(待续)
2020年07月14日