正在美国纽约举行的国际机器大会(ICML)上,我们很难忽略 Facebook 研究科学家们的身影——他们呈现三篇论文、主导四场研讨会、并主讲两场教程。其中包括 FB 研究科学家 Ronan Collobert、Armand Joulin 和 Laurens van der Maaten合作的论文《Torch:机器学习研究的开源平台》。
Torch 是进行深度机器学习研究的主要框架之一,不过研究人员必须重复实验逻辑,Facebook 推出的 Torchnet 由于鼓励模块化编程和代码重用,未来可以推动机器视觉、自然语言处理和语音处理等多个机器学习领域的发展,并且,整个研究社区都能通过插件参与贡献。
摘要
Torch 7 是一个支持 CPU 和 GPU 计算的科学计算平台,在简单的脚本语言中带有轻量的包装,可以快速执行常见的代数运算。它已成为(深度)机器学习研究的主要框架之一。不过,Torch 不提供机器学习实验的抽象和样板代码。因此,研究人员一次次重复完成那些并非彼此协作的实验逻辑。我们在此介绍 Torchnet:一个为机器学习提供抽象和样板代码的开源框架。它鼓励模块化编程和代码重用,这减少了漏洞出现的几率,并且让不同步的数据载入和高效的多GPU计算,使用起来简单明了。Torchnet 是用 Lua 语言编写的,因此用 Torch 装置在任何架构上都很容易安装。我们认为未来 Torch 会成为一个平台,外界社区可以通过插件参与贡献。
1. 简介
Torch 7 是一个针对科学计算框架的灵活计算库,包含 CPU (通过 OpenMP / SSE)和 GPU (通过 CUDA)上、针对主要代数运算的低等级高效执行,以及使用 Lua 脚本语言的非常轻量的包装(Collobert等人,2011)。Torch 有非常活跃的开发者社区,已经开发了许多程序包,包括优化、流行学习、度量学习和神经网络等等。Torch 神经网络程序包目前是一个非常流行的深度学习框架,因为它融合了灵活性和计算高效性。
Torch 神经网络程序包使得建立模型、评估模型输出、以及基于其参数或输入来计算模型输出的导数变得很容易。但是,要实施一个完整的学习实验仍然需要大量开发工作,而 torch / nn 无法为之提供支持:研究人员们需要开发一个高效的数据加载器,将可用的数据区分为训练组和测试组,用所选的优化器连接模型(梯度)进行评估,进行性能测量来监控训练、评估终模型的质量,并且设立日志。此类样板代码的开发经常包含大量代码重复,并且容易出错,这可能会导致不正确的研究结果。尤其是缺少预定义的抽象和参考执行,让研究人员很难写出容易为他人所采用和重用的代码。
这篇论文中我们呈现 Torch:一个新的开源框架,推动(深度)机器学习实验快速前进。Torchnet 提供了一系列关键抽象、样板代码和参考执行,目标是让代码可以重用,并且实现高效。Torchnet 特别鼓励一种模块化设计,明确区分数据库、数据载入过程、模型、优化和性能测量。不同的组成部分在一个 Engine 中互相联接,它执行模型训练和评估。模块化设计使其很容易重用代码、并开发一系列实验:例如,在不同的数据库上运行同样的实验,相当于插入一个不同的数据载入器;改变评估标准,相当于插入一个不同的性能表。Torch 也不牺牲效率:它提供不同步数据载入的创新支持,并在多GPU上支持训练。
我们预见,Torchnet 未来会成为一个平台,研究社区可以通过机器学习实验或工具的插件来参与贡献。这会让验证实验设置的细节(和正确度)、复制结果和重用代码都变得更加容易。
表1:Torchnet 中所有实现的 Dataset 概况。
2. 抽象
Torchnet 从有所相似、但是问世更早的 Lush 框架中获得启发,执行五种主要类型的抽象:(1) Dataset,(2) DatasetIterator,(3) Engine,(4) Meter 和(5) Log。下面,我们分别呈现这五种主要的抽象:
2.1 Dataset
Dataset 抽象只提供两种函数:(1) size( ) 函数,返回数据库中样本的数量,以及(2) get ( idx ) 函数,返回数据库中第 idx 个样本。创建复杂的数据载入器可以通过将一个数据库插入另一个数据库,后者执行各种操作,例如数据联接、数据库分割、批量数据、数据重新取样、数据过滤和样本转换,这与 Torchnet 对模块化编程的重视一致。表1展现了通过使用 Torchnet 在数据库上可以运行的所有操作。模块化方法的主要优势是,它在少数几行代码中帮助打造复杂的数据载入器:当你想在一个新数据库上训练模型时,只要执行一个返回数据库中样本数量的函数、以及一个返回某个具体样本的函数就行。接下来,表1中的数据库可以基于某个特定的分布用于各项操作,包括重新平衡类别、打造小批量进行训练、将数据分割为训练数据和测试数据等等。而且,实施模型训练或评估的引擎,对于其用来训练的数据库基本上是不可知的:例如,当使用 Imagenet(Deng等人,2009)、MS COCO(Lin等人,2014b) 数据库等数据载入器的时候,在 MS COO 数据库上重新训练(或测试)一个 Imagenet 卷积神经网络(He等人,2016),相当于只要将 MS COCO 数据库的核心数载入器插入现有代码中就行了。
2.2 数据库迭代器
当运行训练或测试时,必须在数据库中所有样本上进行迭代,并进行参数更新或者性能测量积累(分别是 Engine 和 Meter 函数的功能)等操作。类似这样的数据库迭代器简单的形式是一个简单的 for 循环,从1运行至数据库大小,调用 get()函数,用循环值作为输入;DatasetIterator 执行的就是这个迭代器,带有一个可选的依赖数据的过滤器(可以通过 filter ( ) 闭包来实现)。在实际情况中效率非常重要,宁可多线不同步地执行数据载入。ParallelDatasetIterator 提供这项功能:其中线的数量是预定义的,都从其中的数据库中载入数据,当迭代器中的样本遇到请求,会返回首个可用的样本。如果有足够多线,数据迭代器永远都会有可供即刻返回的可用样本,这样我们可以将整个针对训练或测试的数据载入和预处理都隐藏起来。当数据在进入模型前执行复杂转换时,这变得尤为重要,例如,在训练计算机视觉模型时经常应用在图像上的仿射变换和色彩变换(Howard,2013)。
2.3 Engine
当利用不同的模型和数据库进行实验时,经常是基于相同的训练过程。Engine 抽象提供模型训练和测试必需的样本逻辑。特别重要的是,它完成模型(默认为 nn.Module)、DatasetIterator 和损失函数(默认为 nn.Criterion)之间的交互。举个例子,一个 Engine 执行两个说明这类互动的函数:(1)一个 train( ) 函数从数据中取样、在模型中传输该数据、计算损失值、在模型中传输损失梯度并执行参数更新;(2)一个 test( ) 函数从数据中取样、在模型中传输数据并测量终预测的质量。
Engine 提供了一系列钩子,让用户不用编辑 Engine 的核心逻辑,就能插入针对某个具体实验的代码,例如性能 Meter。这鼓励了代码重用,并可能预防漏洞,同时仍然为编写训练和测试代码提供了完全灵活性。钩子有一个特别好的特点,就是它们被打造为闭包,这样,要在训练模型使用的代码和测试模型使用的代码之间分享逻辑(例如复制数据样本到 GPU)就很方便。
表2:Torchnet 中实现的所有 Meter 的概况。
目前的 Torchnet 代码包含两个 Engine 操作:(1)一个 SGDEngine,通过 SGD 执行模型的训练;(2)一个 OptimEngine,通过 torch / optim 程序包中的任何优化器——包括 AdaGrad(Duchi等人,2011)、Adam(Kingma & Ba,2015)、共轭梯度法以及 L-BFGS——执行模型的训练。
2.4. Meter
通常在学习模型的训练和测试中,我们希望测量一些特性,例如执行训练阶段所需要的时间、在所有样本上平均的损失函数值、二元分类器的 ROC 曲线下的面积、多类分类器的分类错误、检索模型的精度和召回、或者排名算法的归一化衰减累积增量。Torchnet 提供了各种 Meter,能防止研究人员一遍遍重新执行此类性能测量(此过程中还可能引入漏洞)。表2 是所有目前 Torchnet 中实现的所有 Meter 的概况。。大部分性能表(除了 TimeMeter 和AverageValueMeter 之外)执行两个主要函数:(1)add(output,target)函数,将模型输出和一或多个样本相应目标的值加入性能表;(2)value()函数,返回目前性能表的值。
2.5 日志
Torchnet 为记录实验提供两种 Log:一种简单的 Log,还有一种 RemoteLog。两者都可以以原始文本(输入到一个文档或者 stdout)和 JSON 的形式输出日志信息。
3. 例子
在这个部分,我们将呈现一个简单、可用的例子,说明如何在 MNIST 数据库上使用 Torchnet 训练一个逻辑回归量。代码首先包括所需的相关性:
然后,我们定义一个函数,在 MNIST 训练或测试库中打造一个不同步的数据库迭代器。数据库迭代器收到一个闭包作为输入,后者打造 Torchnet 数据库目标。这里,数据库是一个 ListDataset,仅仅从张量返回包含图像和目标的行;实践中,你会用自己的数据库定义代替这个 ListDataset。核心的 Dataset 被包裹在一个 BatchDataset 中,用于打造大小为128的迷你批次。
然后,我们设立了一个简单的线性模型:
接下来,我们初始化 Torchnet 引擎,实施钩子来重设、更新和打印平均损失和平均分类错误。调用更新平均损失和分类错误的钩子,是在 forward () 调用以下的训练 criterion 之后:
之后,我们使用 SGD 将逻辑损失小化:
模型训练后,我们在测试组上测量平均损失和分类错误:
大部分高级例子可能会在 engine 中实施额外的 hooks。例如,如果你想在每一次训练后测量测试错误,这可以在 engine.hooks.onEndEpoch 钩子中实施。让同一个样本运行 GPU 需要为代码做一些简单的增补,特别是将模型和数据都复制到 GPU。要复制数据样本到 GPU 的一个缓冲器上,可以通过实施一个钩子来实现,钩子在可用的样本出现后执行:
4. 与其他框架相比较
Torchnet 与其他深度学习框架都非常不同,例如 Caffe(Jia等人,2014)、Chainer、TensorFlow(Abadi等人,2016)和 Theano(Bergstra等人,2011),因为 Torchnet 不专注于在深度网络中执行有效的推理和梯度计算,而是在一个深度学习框架之上(在我们的例子中,也就是 torch / nn)提供一个框架 ,通过提供样板代码以及鼓励模块化设计——后者让研究人员可以恨简单地进行代码重用——这让快速实验更加简单。Torchnet 对于其学习框架不做什么假设:Torchnet 抽象可以为例如 Caffe 和 TensorFlow 进行直接实施。
Torchnet 与 Blocks 和 Theano 的 Fuel 类似。特别是 Torchnet 的 Dataset 类似于 Fuel 的 Transformer,但是 Torchnet 的 Dataset 更加灵活,因为他们还完成数据批量、分离和重新取样。Torchnet 的 DatasetIterator 类似于 Fuel 的 DataStream,但是 Torchnet 对不同步、多线数据载入有更好的支持: Fuel 提供的 ServerDataStream 运行单独的数据载入过程,通过 TCP 插座与训练器联络——多线数据载入必须手动完成。与之相比,Torchnet 提供的插件 ParallelDatasetIterator 让不同步的、多线数据载入变得非常简单。Fuel 的 ServerDataStream 与 Torchnet 的 ParallelDatasetInterator 相比有一个潜在优势,即是数据载入过程可以在与训练代码不同的机器上运行。在 Blocks 里,Brick 类似于 torch / nn 的 Module,Blocks 里的 MainLoop 类似于 Torchnet 的 Engine。Blocks 可以实现一些基本的性能测试,例如分类错误,但是目前 Blocks 不提供 Torchnet 的 Meter 所提供的那种丰富的测量库。
5. 展望
我们预见 Torchnet 未来会成为一个由社区拥有的平台,在 Torchment 的核心完成之后,与 Torch 一样提供一系列子程序包。我们认为,重要的子程序包未来将实施与许多机器学习问题相关的样板代码,例如计算机视觉(vision)、自然语言处理(text)和语音处理(speech)。不过,其他子程序包可能更小,专注于更加特定的问题、甚至特定的数据库。举个例子,我们认为未来会有小的子程序包,将各种数据库包装入 Torchnet 的 Dataset,例如 Imagenet 和 COCO 等视觉数据库(Deng等人,2009;Lin等人,2014a)、TIMIT 和 LibriSpeech 等语音数据库(Garofalo等人,1993;Panayotov等人,2015)、以及“十亿单词基准”和 WMT-14 等文本数据库(Chelba等人,2013)。