如何从科学论文中实现一个算法

时间:2021-09-07 19:17:54

原文:http://codecapsule.com/2012/01/18/how-to-implement-a-paper/

作者:Emmanuel Goossaert


本文是从科学论文中实现算法的简短指南。我从书籍和科学出版物中实现了许多复杂的算法,本文总结了我在搜索,阅读,编码和调试方面所学到的知识。这显然仅限于与计算机科学领域有关的领域的出版物。然而,您应该能够将以下提供的指导方针和良好做法应用于任何类型的文件或实施。

1 - 在你进入之前

在您阅读技术论文并实施之前,您应该查看几点。每当你准备开始这样一个项目时,请确保你仔细地盖住他们。

1.1 - 找到一个开源实现,以避免编码

除非你想为了更多地了解这个领域来执行论文,否则你无需实现。事实上,你想要的不是对纸张进行编码,而只是实现纸张的代码。所以在你开始任何事情之前,你应该花几天的时间试图在互联网上找到一个开源的实现。只要考虑一下:你宁愿失去两天的时间寻找代码,还是浪费两个月来实现一个已经可用的算法?

1.2 - 找到更简单的方法来实现你的目标

问问自己想要做什么,如果更简单的解决方案可以满足您的需要。您可以使用其他技术 -即使结果只有您想要的80% -不需要实现该文档,并且您可以在随后的两天内运行可用的开源库?有关更多信息,请参阅我的文章20/80生产力规则

1.3 - 小心软件专利

如果您在美国,请注意软件专利。有些论文获得专利,您可能会遇到在商业应用中使用它们的麻烦。

1.4 - 了解有关该领域的更多信息

如果您正在阅读关于在计算神经科学的背景下使用支持向量机(SVM)的文章,那么您应该阅读机器学习的简短介绍以及可以替代SVM的不同类型的分类器,您应该阅读关于计算神经科学的一般文章,以了解现在研究中正在做什么。

1.5 - 保持动力

如果你从未执行过纸张和/或如果您是新的纸张领域,那么阅读可能非常困难。无论发生什么,不要让数学方程的数量和复杂性阻止你。此外,速度不是一个问题:即使你觉得你理解纸张比你想要的慢,只要继续工作,你会看到你会慢慢地,稳定地了解论文中提出的概念,并通过所有困难一个接一个。

2 - 三种论文

选择随机纸并立即开始执行是不错的选择。那里有很多论文,这意味着有很多垃圾。所有出版物可以分为三类:

2.1 - 开创性的论文

一些真正有趣的,写得好的和原创的研究。大多数这些论文都是从*大学出来的,或者是在处理这个问题大约六到十年的小型大学的研究团队中出来的。后来很容易发现:他们在论文中引用了自己的出版物,表明他们已经在这个问题上一段时间了,而且他们的新工作基于出版物的成功记录。此外,这些开创性的论文一般都是在现场最好的期刊上发表的。

2.2 - 模板纸

一些研究小组正在跟随开创性团队的工作,提出改进建议,并发布其改进成果。这些论文中有许多缺乏正确的统计分析,错误地得出结论,改进真的在打破原始算法。大多数时候,他们真的没有带来任何东西,除了不必要的额外的复杂性。但并不是所有的模仿者都是坏的。有些是好的,但很少见。

2.3 - 垃圾纸

一些研究人员真的不知道他们在做什么和/或是邪恶的。他们只是试图在他们所教授的学术机构中保持自己的地位和特权。所以他们需要资金,因此他们需要发布,什么东西。诚实的人会在结论中告诉你,他们失败了,结果只有N%的时间是准确的(N是坏的价值)。但有些邪恶的人会说谎,他们的研究取得了很大的成功。经过一段时间阅读出版物后,很容易发现垃圾纸并将其弄脏。

3 - 如何阅读科学论文

很多已经写了这个话题,所以我不会写太多。一个好的起点是:如何阅读 SrinivasanKeshav 的论文。以下是我在阅读科学出版物时发现有用的几点。

3.1 - 找到正确的文件

你想要实现的是一个原始的论文,一个开始整个领域的论文。有时候,如果你觉得它给一个好的但不成熟的开创性论文带来了真正的改进和一致性,那么你可以选择一张模板纸。

所以让我们说你有一张纸作为你的起点。您需要在其周围进行一些研究。为此,该策略是寻找相关出版物,以及本文末尾“参考”部分列出的出版物。去Google学术搜索,搜索标题和作者。你发现的任何一篇论文比原来的论文做得更好吗?如果是的话,那就先把你正在看的纸扔掉,然后保持你发现的新的纸张。GoogleScholar的另一个很酷的功能是您可以找到引用给定论文的论文。这真的很棒,因为你所要做的就是按照一篇论文到下一篇文章的引用链,你会发现最近的论文。从起点寻找好的纸张是所有关于寻找的论文被引用由当前的纸张,以及论文引用当前的纸张。及时移动,您应该找到高质量,符合您需求的纸张。

重要提示:在这个简单的探索和推算阶段,您不应该阅读并充分了解论文。这个搜索正确的文章应该只是通过抄写文件,并用你的本能来检测垃圾(这带有经验)。

3.2 - 不要在屏幕上阅读

在硬纸上打印出版物,并阅读论文版本。另外,不要减小大小,以便在每个页面上打印更多。是的,你会保存三张纸,但你会失去时间,因为你会很快读这些小字符。良好的阅读字体大小在11到13分之间。

3.3 - 良好的时间和位置

半夜不要看纸,在大脑仍然新鲜的一天的时刻做。另外,找到一个安静的区域,并使用良好的照明。当我阅读时,我有一个台灯直接指向文件。

3.4 - 标记和笔记

用标记突出显示重要信息,并在阅读时在头部弹出的任何想法的边缘记笔记。

3.5 - 了解所有条款的定义

当你习惯于阅读大多数新闻文章和小说时,你的大脑训练有素,通过使用上下文作为扣除设备来填充你不知道的单词的意思。阅读科学出版物是一个不同的练习,最大的错误之一是假设一个词的错误含义。例如在这句话中“这种分割方法的结果仍然遭受模糊人造物”的困扰。这两个词,“分割”和“文物”,具有英文的一般含义,但在计算机视觉领域也具有特定的意义。如果您不知道这些词在本文中具有特定的意义,那么在阅读时不注意,您的大脑将填写一般含义,您可能会丢失一些非常重要的信息。因此,您必须(i)避免对字词的假设,并且每当有疑问的时候,在出版物的领域的上下文中查找这个词,并且(ii)在一张纸上写上一个关于您之前不了解的出版物特有的所有概念和词汇的词汇表。如果您首次遇到诸如“逼真点”和“分段仿射变换”等概念,那么您应该查找其精确的定义并将其写入您的词汇表。概念是支持语言的大脑快捷方式,可以让您更快地了解作者的意图。如果您首次遇到诸如“逼真点”和“分段仿射变换”等概念,那么您应该查找其精确的定义并将其写入您的词汇表。概念是支持语言的大脑快捷方式,可以让您更快地了解作者的意图。如果您首次遇到诸如“逼真点”和“分段仿射变换”等概念,那么您应该查找其精确的定义并将其写入您的词汇表。概念是支持语言的大脑快捷方式,可以让您更快地了解作者的意图。

3.6 - 在结论中寻找统计分析

如果作者仅从其算法中提供一条曲线,并从另一种算法中提取一条曲线,并说“看,它的准确度高20%”,那么你知道你正在阅读垃圾。您想要阅读的内容是:“在N个实例的一组测试中,我们的算法显示了使用双样本t检验的p值为5%的显着改进。”使用统计分析显示,作者,并且是一个很好的证明,结果可以信任泛化(除非作者谎称使他们的结果看起来更性感,这可以永远发生)。

3.7 - 确保结论证明本文正在做您所需要的

假设你想要一个可以在图片中找到任何脸的算法。本文作者在结论中说,他们的模型是使用来自80个不同人(10x 80 = 800张图片)的10个姿势进行训练的,训练组的面部检测准确率为98%,但只有70%与测试集(图片在训练期间未使用)。这是什么意思?这意味着显然,该算法具有适当的概括性问题。在训练集上使用(无用)时表现良好,在现实世界中使用时表现更差。在这一点你应该得出结论,也许这篇文章对你所需要的不够好。

3.8 - 注意作者使用的输入数据

如果要使用网络摄像头执行人脸检测,并且作者已经使用高清晰度摄像机拍摄的照片,那么在您的情况下,算法将不会像作者那样执行。确保该算法是在与您的数据相似的数据上进行测试的,否则您将最终获得在实际设置中完全无法使用的良好实现。

3.9 - 作者是人类

作者是人类,因此他们犯错误。不要以为作者是绝对正确的,如果一个方程真的很难理解或跟随,你应该问自己,作者是否在那里犯了错误。这可能只是文中的打字错误,或数学错误。无论是哪种情况,最好的方法就是自己推出方程式,并尝试验证其结果。

3.10 - 了解变量和运算符

在出版物实施过程中的主要任务是将论文中的数学方程式转化为代码和数据。这意味着在跳入代码之前,您必须了解这些方程的100%的方程和过程。例如,“C= A”。B“可能有不同的含义。A和B可以是简单的数字,“。”运算符可以简单地成为一个产品。在这种情况下,C将是两个数字A和B的乘积。但也可能是A和B是矩阵,“。”表示矩阵乘积运算符。在这种情况下,C将是矩阵A和B的乘积矩阵。另一种可能性是A和B是矩阵,“。”是术语“逐项”乘积运算符。在这种情况下,每个元素C(i,j)是A(i,j)和B(i,j)的乘积。变量和运算符的符号可以从一个数学公式转变为另一个数学公式,从一个研究组到另一个。确保你知道每个变量是什么(标量,向量,矩阵或其他东西),以及每个运算符对这些变量做什么。

3.11 - 了解数据流

一篇论文是一系列方程式。在开始编码之前,您必须知道如何将方程式N的输出插入方程式N+ 1的输入。

4 - 原型制作

一旦你阅读并理解了这篇文章,那么现在是创建一个原型的时候了。这是一个非常重要的步骤,避免它可能导致浪费时间和资源。在诸如C,C++或Java这样的语言中实现复杂的算法可能非常耗时。即使你对论文有一定的信心,认为该算法将会起作用,所以仍然有机会根本不起作用。所以你希望能够以最脏的方式尽可能快地进行编码,只是为了检查它是否正常工作。

4.1 - 原型设计解决方案

最好的解决方案是使用更高级别的通用语言或环境,如Matlab,R,Octave或SciPy/ NumPy。在C++中表示数学方程然后打印结果来手动检查它并不容易。相反,在Matlab中写方程非常简单,然后打印出来。在C++中需要两到三周的时间,您将在Matlab中花费两天时间。

4.2 - 原型开发有助于调试过程

拥有原型的一个优点是当您拥有C ++版本时,您可以通过比较Matlab原型和C++实现之间的结果进行调试。这将在下面的“调试”部分进一步发展。

4.3 - 事先清除执行问题

您一定会在您的原型中造成软件设计错误,这是一件好事,因为您将能够确定过程或数据的难点。当您编写C++版本时,您将了解如何更好地构建软件,并且您将生成比没有原型设计步骤更简洁,更稳定的代码(这是Frederick提出的“扔掉系统”的想法布鲁克斯在神话人月)。

4.4 - 验证文中提出的结果

仔细阅读本文的“实验”部分,并尝试通过使用与作者使用的测试数据尽可能相似的方式尽可能接近地重现实验条件。这增加了您重写作者获得的结果的机会。不使用类似的条件可能会导致您执行的行为,您可能会将其视为错误,而您只是不提供正确的数据。一旦您可以根据类似的数据重现结果,那么您可以开始在不同类型的数据上进行测试。

5 - 选择正确的语言和图书馆

在这个阶段,您必须清楚地了解出版物中提供的算法和概念,您必须拥有一个运行的原型,以说服算法实际上是处理您希望在生产中使用的输入数据。现在是进入下一步的时候,其中包括使用您希望在生产中使用的语言和框架来实施出版物。

5.1 - 预先存在的系统

许多时候,生产语言和图书馆都是由现有的系统所决定的。例如,您有一套用于图片中的照明归一化的算法,在Java编码的库中,并且您想从发布中添加新的算法。在这种情况下,显然,您不会在C++中编写这个新算法,而是在Java中编写。

5.2 - 预测未来使用的实施

在这种情况下,没有预先存在的系统强加一种语言,那么语言的选择应该基于该算法的预测使用。例如,如果您认为在四到六个月内,您的应用程序的可能端口将完成到iPhone,那么您应该选择C/ C ++ over Java,因为它将是将代码轻松集成到目标中的唯一方法-C应用程序,无需从头开始。

5.3 - 完全或部分解决算法的可用库

不同语言的可用库也可以定向生产语言的选择。我们假设您希望实现的算法使用众所周知的代数技术,如主成分分析(PCA)和奇异值分解(SVD)。那么你可以从头开始编写PCA和SVD,如果有一个错误可能会导致一个星期的调试结束,或者你可以重新使用已经实现这些技术的库,并使用惯例和Matrix编写实现代码这个图书馆的类。理想情况下,您应该能够将您的实现分解为子任务,并尝试查找尽可能实现尽可能多的这些子任务的库。如果您发现仅适用于某种语言的完美图书库,那么您应该选择该语言。也,请注意,库的选择应该是重新使用现有代码和最小化依赖关系之间的权衡。是的,为实现所需的每个子任务编写代码是很好的,但是如果需要创建20个不同的库的依赖关系,那么这可能不是很实用,甚至可能会危及实现的未来稳定性。

6 - 实施

以下是我在实施出版物方面的经验的一些提示

6.1 - 选择正确的精度

您应该仔细选择您用于计算的类型。通常使用double而不是float更好。内存使用量可以更大,但是计算的精度会大大提高,一般值得。此外,您应该了解32位和64位系统之间的区别。无论何时,创建自己的类型来封装底层类型(float或double,32位或64位),并在代码中使用此类型。这可以通过定义是C/ C ++或Java中的类来完成。

6.2 - 记录所有内容

尽管过度文档可能会大大减缓项目的真实性,但在执行复杂技术文件的情况下,您需要对所有内容进行评论。即使您是唯一从事项目工作的人员,您应该记录文件,课程和方法。选择一个常规,如Doxygen或reStructuredText,并坚持下去。在开发过程中,会有一段时间你会忘记一些类的工作原理,或者你如何实现一些方法,你会感谢你自己编写代码!

6.3 - 在代码中添加对本文的引用

对于您实施的论文中的每个方程,您需要添加一个引用文章(作者和年份)的评论以及段落号或方程数。这样,当稍后重新读取代码时,您将能够将代码直接连接到纸张中的精确位置。这些评论应该是:

// See Cootes et al. 2001 Equation 2.3
// See Matthews and Baker
2004 Section 4.1.2

6.4 - 在变量名中避免使用数学符号

假设算法中的一些数量是一个表示为A的矩阵。后来,该算法需要矩阵在两维上的梯度,表示为dA=(dA/ dx,dA/ dy)。那么变量的名称不应该是“dA_dx”和“dA_dy”,而是“gradient_x”和“gradient_y”。类似地,如果方程式系统需要收敛测试,则变量不应该是“prev_dA_dx”和“dA_dx”,而是“error_previous”和“error_current”。总是为它们所代表的物理量命名,而不是文章作者使用的任何字母符号(例如“gradient_x”而不是“dA_dx”),并且总是表示从左到右更不具体的特定(例如“gradient_x“而不是”x_gradient“)。

6.5 - 在第一次通过期间不要优化

留下所有优化以备以后。由于您绝对不能确定代码中哪一部分需要优化。每次看到可能的优化时,添加一个注释并解释如何实现优化,如:

// OPTIMIZE HERE:computing the matrix one column at a time
// and multiplying them directly could save memory

这样一来,您可以在代码中找到可以进行优化的所有位置,并获得有关如何进行优化的新提示。一旦您的实施完成,您将可以通过运行Profiler(如Valgrind)或您使用的编程语言中的任何可用性来找到要优化的位置。

6.6 - 规划创建API?

如果您计划使用当前代码作为随时间增长的API的基础,那么您应该了解创建实际可用接口的技术。为此,我将推荐“编译图书馆”技术,由JoshuaBloch在他的演讲中总结,何设计一个好的API以及它为什么重要

7 - 调试

实现一个新的算法就像烹饪你从未吃过的菜。即使它味道好,你永远不会知道这是什么味道。现在我们很幸运,因为与烹饪不同,软件开发有一些有用的技巧来增加我们在实现中的信心。

7.1 - 将结果与其他实现进行比较

清除错误的一个好方法是将代码的结果与同一算法的现有实现的结果进行比较。因为我假设你正确地完成了上面提到的“但是在跳跃之前”的所有任务,你没有找到任何可用的算法实现(否则你会使用它而不是实现这个论文!)。因此,您在此阶段唯一的其他实现方式是您之前编程的原型。

因此,该思想是在算法的每个步骤中比较原型和生产实现的结果。如果结果不同,那么两个实现之一就会出错,你必须找到哪些和为什么。精度可以改变(原型可以给你x= 1.8966和生产代码x= 1.8965),比较当然应该考虑到这一点。

7.2 - 与阅读论文的人交谈

一旦两个实现(原型和生产)的所有步骤都得到完全相同的结果,您可以获得一些信心,您的代码是无bug的。然而,您仍然有一个风险,您错误地理解了该论文。在这种情况下,这两个实现将为每个步骤提供相同的结果,您将认为您的实现是好的,而这只是证明两个实现同样是错误的。不幸的是,我没有办法知道这种问题。您最好的选择是找到已经阅读论文的人,并询问有关您不确定的算法部分的问题。你甚至可以试图询问作者,但是你得到答案的机会很低。

7.3 - 可视化您的变量

在开发过程中,始终要注意算法使用的变量的内容。我不是在说只是打印矩阵和数据中的所有值,而是发现可视化技巧适应于实现中的任何变量。例如,如果假设矩阵表示图像的渐变,则在编码和调试期间,您应该有一个窗口弹出并显示渐变图像,而不仅仅是图像矩阵中的数字值。这样,您将将实际的图像与您正在处理的数据相关联,您将能够检测何时出现与其中一个变量有关的问题,这反过来又会指示可能的错误。发明的可视化技巧包括图像,散点图,图形或任何不仅仅是愚蠢的列表1,

7.4 - 测试数据集

生成数据来实验您的实现可能非常耗时。无论何时,都可以尝试查找数据库(面部数据库,文本提取数据库等)或用于生成此类数据的工具。如果没有,则不要手动生成1000个样本。在20行中编写一个快速数据生成器,并完成它。

结论

在本文中,我提出了实施科学出版物的良好做法。记住,这些只是基于我的个人经验,而且不应该盲目追随。阅读和编写时,请务必注意,并用您的判断力确定上述指导方针适合您的项目。也许一些做法会伤害你的项目超过它会帮助它,这取决于你找出来。现在去实现一些很酷的算法!