整个夏天,我一直在致力于改进 Flux 的计算机视觉能力。我的具体工作是 **将新的模型添加到 Flux 模型库中**,**实现一些新功能**,以及 **提高之前层的计算速度**。具体来说,我将 **卷积** 的速度提高了 **18 倍**,**批归一化** 的速度提高了 **3 倍** 左右。
我列出了我在这个项目期间完成的所有重要 PR。其中一些已被合并,一些尚未合并,一些甚至还在进行中。我们只讨论主要的 PR,省略了错误修复和小的补丁。以下是 PR 列表:
在该项目过程中,开发了以下新包:
让我们逐一了解这些更改。
Flux 目前缺乏专门用于批归一化的 GPU 内核。批归一化是神经网络中最重要的一层,它通过处理内部均值协方差偏移来加速训练。到目前为止,我们一直在使用 Flux 的 CPU 代码进行批归一化(这显然很慢)。因此,这个 PR 旨在通过包装 CUDNN 批归一化层并将其与 Flux 的自动微分集成来解决这个问题。速度(和内存消耗)改进的一些亮点是:1.860 s (1367 allocations: 50.92 KiB)
-> 2.782 ms (276 allocations: 10.38 KiB)
。我正在对 **BatchNorm(100)** 的 **总时间(前向 + 反向)** 进行基准测试,该时间针对大小为 **224 * 224 * 100 * 10** 的数组。这个 PR 尚未合并。它需要更新到 Julia 1.0(Flux 主分支支持)才能合并。
我对 Flux 和 Pytorch 之间进行了基准测试(继续阅读以了解更多信息)。我们对神经网络进行了分析,并在 Flux 卷积层中发现了一些问题。主要瓶颈在于我们执行的 广播偏差添加
。因此,我们没有使用 广播偏差添加
,而是对 7.1 之前的 CUDNN 版本使用了 cudnnAddTensor
。对于 7.1 以上的版本,我们转向使用 cudnnConvolutionBiasActivationForward
,其中激活始终为 identity
,最后根据其他激活进行调度。使用此更新对速度进行的主要改进反映在 DeepLearningBenchmarks
存储库中。此外,此 PR 依赖于 CuArrays 的 PR,因此它无法在 CuArrays 合并之前合并。此外,它还需要更新才能适应 Julia 1.0。
深度可分离卷积对于深度神经网络的移动应用至关重要。MobileNets 和 Xception Net 直接使用这种形式的卷积。因此,对于深度学习库来说,开箱即用地支持此类卷积至关重要。首先,这涉及在 NNlib 中实现代码的 CPU 版本。然后,我们只需要将深度可分离卷积连接到 Flux 的自动微分中。开箱即用的支持还允许一些即将在 **Metalhead.jl** 和 **model-zoo** 中添加的模型轻松定义。作为对该主题的未来工作的部分,需要为该算法提供 CUDNN 绑定。
周围有各种各样的卷积算法。所有这些都利用了 输入张量
和 滤波器张量
的属性,并且为高效卷积开发了非常专门的例程。值得庆幸的是,CUDNN 将这些专门开发的卷积例程内置到其中。因此,我们需要将其直接集成到 CuArrays
中,并公开其 API 以供其他包(如 Flux
)使用。用于简单卷积运算的包装器已在 CuArrays 中预先编写。因此,我们只需要为 工作空间分配
创建包装器即可。此 PR 添加了必要的包装器,并更改了卷积函数定义以公开 算法更改
的 API。因此,对于最终用户来说,唯一的变化是更改关键字参数 algo
。
在对 Flux 卷积代码进行基准测试时,我们发现了一些主要瓶颈,这些瓶颈来自 卷积偏差的反向传播
。因此,自然的选择是包装 CUDNN 函数,它可以有效地计算偏差的梯度。此外,我们能够包装一个函数,以便同时应用 激活
和 添加偏差
。为了使用此函数 cudnnConvolutionBiasActivationFunction
,我们需要包装激活的前向和反向传播函数。现在让我们看看我们通过此更新获得了什么速度改进。
最近一些模型(例如 GoogleNet 和 Resnet)被添加到 Metalhead 中(特别感谢 **Ayush Shridhar [@ayush1999]** 对 **ONNX.jl** 的工作)。但是,此代码是 **自动生成的**,并不一定可读。此外,我们对这些模型所能做的唯一事情就是进行预测。我们不能将它用于特征提取之类的事情。因此,我们将模型库中的部分模型移植过来,并手动将权重加载到其中。有关 Metalhead 的更详细用法,请访问 此处。
加载到 Flux 中的现有模型的准确性非常糟糕。我们已经尝试过各种预处理步骤,但大多没有用。经过一些反复试验后,我们终于找到了主要原因。我们正在使用 **互相关运算** 的权重代替 **卷积运算** 的权重。目前,这是通过在加载权重之前手动翻转权重来解决的。作为一项长期的解决方案,我们正在 NNlib
(并最终在 Flux
中)公开参数,以在 **卷积** 和 **互相关** 之间进行选择。
项目的这一部分仍处于起步阶段。这项工作的大部分都已完成(但主要分散在模型库中)。模型库本质上是针对允许用户将其代码中的所有模型导入而设计的。这些模型可能未经训练(当前大多数模型都是如此)。因此,主要动机是,如果我们想训练一个 ResNeXt
模型,我们就不必重新定义别人已经做过的东西。我们应该能够毫不费力地加载模型。
model = VGG19() # This fetches an untrained VGG19 model
model_trained = trained(VGG19)
# Get the trained VGG19 model. This is the same as previously calling VGG19()
trained(VGG11)
# We get an error as we don't currently have a trained VGG11 model but VGG11() works fine
此包提供了一个简单的 API,用于在所需图像上生成 梦境。您需要提供图像,选择您想要的梦境类型以及要使用的模型。此包依赖于 Flux 和 Metalhead 的训练模型。
上面的图像使用 引导深度梦境
生成的。
多年来,已经开发出几种可视化算法来了解神经网络的功能。此包旨在实现此类算法。其中大多数将开箱即用地适用于 Metalhead。目前,这是一个正在开发中的包,但大部分内容已经完成。
这是一个小型的包演示:
这是论文 **用于实时风格迁移和超分辨率的感知损失** 的实现。与论文有一些明显的偏差。我们使用了目前 Flux 中可用的最佳层实现。至于确切的架构,它仍在开发中。我们在此包中提供了三个预训练模型。API 已尽可能保持简单。
下面是 MonaLisa 风格迁移的小例子:
如您从上面的 PR 说明中看到的那样,我的许多工作都围绕着对 Flux 模型进行基准测试并在可能的情况下进行加速。我工作的最初部分是向 Flux 模型库添加一些新的计算机视觉模型。因此,我们在 Flux 模型库中添加了 VGGNets
、ResNets
、DenseNets
等模型。此外,我们还能够将部分模型移植到 Metalhead
包中,该包专门设计用于解决计算机视觉问题。在与 JuliaLang 社区的一些成员进行大量实验和帮助后,我们能够解决遇到的一些准确性问题。接下来,我们开始开发一个包来执行 FastStyleTransfer。它允许用户轻松地 训练
他们的模型,以及 风格化
图像,非常容易。我们还能够训练一些 densenet 模型,并重现 MURA 论文的结果。
接下来是对 Flux 中当前实现进行基准测试,并在可能的情况下解决瓶颈。因此,我们编写了 Flux 和 Pytorch 的基准测试脚本,并在两者之间进行了直接比较。结果发现,Pytorch 比 Flux 快得多。但是,我们能够找到导致速度慢的原因。事实证明,这是因为缺乏用于广播添加及其反向传播的专用内核。因此,直接的解决方案是 包装一些 CUDNN 函数
并将其与 Flux 集成。这样做实际上大大减少了这些层所花费的时间。目前,我们在每个单独层的计算时间方面与 Pytorch 齐平。
我有幸参加了 2018 年在伦敦举行的 JuliaCon。感谢 **Julia 项目** 和 **NumFOCUS** 为这次旅行提供资金。我有机会展示了我在 GSoC 期间完成的工作的 海报。这是我参加的第一个会议,所以确实是一次独特的体验。我能够与其他人分享我的工作,甚至获得了一些关于它的宝贵建议。此外,我还发现了一些未来想要贡献的新酷开源项目。最后,与我在 Slack 中互动的人见面总是很愉快。
有一篇关于 Julia 如何作为机器学习语言发挥作用的 精彩文章。这篇文章总结了从机器学习领域经验丰富的人的角度来看的原因。在这里,我将从一个外行的角度介绍这些原因。
试想一下,在一个流行的框架中实现一个标准的计算机视觉模型,比如 Pytorch 或 Tensorflow。很简单,对吧?只需使用它们的 API 调用必要的层,就完成了。现在想象一下,必须定义一些不在它们标准库中的东西。您需要首先用 C++ 编写自定义层(如果您想知道,包括前向和反向传递),如果这还不够难,您需要用 CUDA C 为该代码定义 GPU 内核。现在,您根据它们特定的 API 将此层(显然用 Python)与 Pytorch 或 Tensorflow 集成。祝您好运调试您遇到的 SegFaults。
现在让我们看看如何在 Flux 中做到这一点。您首先用 Julia 编写层,并使用 **CUDAnative** 编写其 CUDA GPU 版本(感谢 **Tim Besand [@maleadt]** 杰出的工作)。至于集成到 Flux AD 中,您只需使用 @grad
宏。就这么简单!
您可能会有一个抱怨,就是很多训练好的模型不可用。但是,由于 **ONNX.jl** 和 **Keras.jl**,这个问题或多或少得到了解决。这两者都是 **Ayush Shridhar** 的作品。使用这些,您可以使用 Pytorch 或 CNTK 训练的模型,只要它们存储在 ONNX 格式中。此外,现在您还有广泛的强化学习模型,例如 **AlphaGo.jl**(由 **Tejan Karmali** 编写)使用 Flux 编写,除了 **model-zoo** 和 **Metalhead.jl** 中的计算机视觉模型外。
这个项目已经偏离了我最初提出的内容,但这主要是一件好事。作为这个项目的一部分实现的东西肯定会帮助更快速地训练 Flux 中的深度神经网络,也有助于使用 Flux 创建更复杂的模型。话虽如此,这个项目未来的一个令人兴奋的事情是完成在 Metalhead 中添加 **对象分类模型**,正如这个 问题中提出的那样。另一个有趣的事情是在一个地方构建一些使用 Flux 构建的 **对象检测** 模型。此外,我们应该继续解决当前需要解决的瓶颈。我们应该继续将基准添加到 DeepLearningBenchmarks 中,这对于识别瓶颈至关重要。
首先,我要感谢 **Google** 组织了 Google Summer of Code,它给了我这个与开源社区合作的绝佳机会。此外,我感谢 **NumFOCUS** 和 **JuliaLang** 选择我参与这个项目。接下来,我要感谢我的导师 **Viral Shah** 和 **Mine Innes** 的不断支持,他们指导我完成了我的项目。最后,我要感谢杰出的 **JuliaLang 社区** 为我解答疑难问题,并成为我学习的优秀资源。