
我的名字叫Eduard Tyantov,我领导Mail.ru Group的计算机视觉团队。 在存在的几年中,我们的团队已经解决了许多计算机视觉问题,今天我将向您介绍成功创建适用于各种任务的机器学习模型的方法。 我将分享一些技巧,这些技巧可以在所有阶段加速模型:设置任务,准备数据,培训和在生产中部署。
Mail.ru的计算机视觉
首先,Mail.ru中的计算机视觉是什么,以及我们从事哪些项目。 我们在产品中提供解决方案,例如Mail,Mail.ru Cloud(用于存储照片和视频的应用程序),Vision(基于计算机视觉的B2B解决方案)等。 我会举几个例子。
云(这是我们的第一个也是主要的客户)拥有600亿张照片。 我们基于机器学习为其智能处理开发了各种功能,例如面部识别和观光(
有单独的相关文章 )。 所有用户照片均通过识别模型运行,该模型可让您按人员,标签,访问过的城市和国家/地区进行搜索和分组。


对于Mail,我们进行了OCR-从图片中识别文本。 今天,我将告诉您更多有关他的信息。
对于B2B产品,我们会识别并计算队列中的人数。 例如,滑雪缆车有一个队列,您需要计算其中有多少人。 首先,为了测试技术并发挥作用,我们在办公室的餐厅中部署了原型。 有多个收银台,因此有几个队列,我们使用几个摄像头(每个队列一个),使用模型,我们计算队列中有多少人,每个队列中还剩下大约几分钟。 这样,我们可以更好地平衡饭厅中的线条。

问题陈述
让我们从任何任务的关键部分开始-它的制定。 几乎所有的ML开发都至少需要一个月的时间(最好是在您知道该怎么做的情况下),并且在大多数情况下要花费几个月的时间。 如果任务不正确或不正确,那么在工作结束时很有可能会听到产品经理的精神:“一切都错了。 这不好。 我想要其他东西。” 为防止这种情况发生,您需要采取一些措施。 基于ML的产品有什么特别之处? 与开发站点的任务不同,机器学习的任务不能仅靠文本来形式化。 而且,通常,对于一个没有准备的人来说,一切似乎已经很明显了,只需要“美丽”地做所有事情即可。 但是任务经理可能甚至不知道,那里有什么小细节,他们从来没有想过,也不会想,直到他们看到最终产品并说:“您做了什么?”
问题所在
让我们以示例的方式了解可能会出现的问题。 假设您有一个人脸识别任务。 您收到了,很高兴并打电话给您的母亲:“万岁,一项有趣的任务!” 但是可以直接分解并开始做吗? 如果您这样做,那么最后您可能会感到意外:
- 有不同的国籍。 例如,数据集中没有亚洲人或其他任何人。 因此,您的模型根本不知道如何识别它们,因此产品需要它。 反之亦然,您在修订版上花费了额外的三个月时间,并且该产品仅包含高加索人,这不是必需的。
- 有孩子。 对于像我这样没有孩子的父亲,所有的孩子都呆在一起。 我完全同意该模型,当她将所有孩子都送到一个群集中时-真的不清楚大多数孩子之间的区别! ;)但是有孩子的人有完全不同的看法。 通常他们也是您的领导者。 或者,如果成功地将孩子的头部与肘部或秃头的头部进行比较,则仍然存在有趣的识别错误(真实故事)。
- 通常不知道如何处理绘制的字符。 我是否需要识别它们?
任务的这些方面在开始时就非常重要。 因此,您需要从一开始就在“数据上”与经理进行沟通。 不能接受口头解释。 有必要查看数据。 从模型将在其上运行的相同分布中是可取的。
理想情况下,在讨论过程中,将获得一些测试数据集,您可以最终在该数据集上运行该模型并检查其是否按经理的要求工作。 最好将测试数据集的一部分交给经理本人,这样您就无权访问它。 因为您可以轻松地在此测试集上进行再培训,所以您是ML开发人员!
在ML中设置任务是ML的产品经理和专家之间的一项持续工作。 即使一开始您设置好任务,然后随着模型的发展,也会出现越来越多的新问题,新的功能将使您了解数据。 所有这些都需要与经理进行不断讨论。 优秀的经理总是向其ML团队广播,他们需要承担责任并帮助经理制定任务。
为什么这样 机器学习是一个相当新的领域。 经理没有(或几乎没有)管理此类任务的经验。 人们多久学会解决一次新问题? 关于错误。 如果您不希望自己喜欢的项目成为一个错误,那么您需要介入并承担责任,教产品经理正确设置任务,制定清单和政策; 所有这些都很有帮助。 每当我完成一项新的有趣任务时(或者我同事中的某个人将我拉开),我们就会去做。 我刚才告诉你的一切,我自己都忘记了。 因此,拥有某种检查表来检查自己很重要。
资料
数据在ML中非常重要。 对于深度学习,您提供的模型数据越多越好。 蓝色图表明,通常,深度学习模型在添加数据后会大大改善。
而且从某些角度来看,“旧的”(经典)算法无法再进行改进。
通常在ML中,数据集很脏。 他们的特征是总是撒谎的人。 评估人员通常不专心,并且会犯很多错误。 我们使用这种技术:我们获取所拥有的数据,在其上训练模型,然后借助该模型清除数据并再次重复该循环。
让我们仔细看一下相同面部识别的示例。 假设我们下载了VKontakte用户头像。 例如,我们有一个包含4个头像的用户个人资料。 我们检测所有4张图像上的面部,并通过面部识别模型运行。 因此,我们得到了人的嵌入,在他们的帮助下,他们可以将相似的人“粘合”成组(集群)。 接下来,假设用户的头像主要包含他的面部,我们选择最大的群集。 因此,我们可以用这种方法清除所有其他面孔(即噪音)。 之后,我们可以再次重复该循环:在清除的数据上训练模型并使用它来清除数据。 您可以重复几次。
对于此类群集,几乎总是使用CLink算法。 这是一种分层聚类算法,其中设置“粘合”相似对象的阈值非常方便(这正是清洁所需的阈值)。 CLink生成球形簇。 这很重要,因为我们经常学习这些嵌入的度量空间。 该算法的复杂度为O(n
2 ),原则上大约为0。
有时,数据很难获取或标记,以至于您一开始生成数据就无所事事。 生成方法使您可以生成大量数据。 但是为此,您需要编程一些东西。 最简单的示例是OCR,即图像上的文本识别。 用于此任务的文本标记非常昂贵且嘈杂:您需要突出显示每一行和每个单词,对文本进行签名等等。 评估员(标记人员)将在非常长的时间内占用一百页的文本,并且培训还需要更多内容。 显然,您可以以某种方式生成文本并以某种方式“移动”文本,以便模型从中学习。
我们已经为自己发现最好,最方便的工具包是PIL,OpenCV和Numpy的组合。 他们拥有处理文本的所有功能。 您可以通过任何方式使图像与文本复杂化,以使网络不会因简单的示例而重新训练。

有时我们需要一些现实世界的对象。 例如,货架上的商品。 这些图片之一是自动生成的。 您认为是左还是右?

实际上,两者都是生成的。 如果您不看这些小细节,那么您将不会注意到与现实的差异。 我们使用Blender(3dmax的模拟)进行此操作。

主要的重要优点是它是开源的。 它具有出色的Python API,可让您直接在代码中放置对象,配置和随机化过程,最后获得多样化的数据集。
为了进行渲染,使用了光线跟踪。 这是一个相当昂贵的过程,但它可以产生高质量的结果。 最重要的问题:在哪里获取对象模型? 通常,必须购买它们。 但是,如果您是一个贫穷的学生,并且想尝试一些东西,总会有许多麻烦的。 显然,对于生产,您需要从某人处购买或订购渲染模型。
这就是数据。 让我们继续学习。
公制学习
度量学习的目标是训练网络,以便它将相似的对象转换为嵌入度量空间中的相似区域。 我将再次举例说明景点,这是不寻常的,因为从本质上讲这是一项分类任务,但要进行数万个课程。 看来,为什么在这里,度量学习通常适合诸如面部识别之类的任务? 让我们尝试找出答案。
如果在训练分类问题(例如Softmax)时使用标准损失,则度量空间中的类可以很好地分离,但是在嵌入空间中,不同类的点可以彼此靠近...

这会在泛化期间产生潜在的错误,因为 源数据中的细微差异可能会改变分类结果。 我们真的希望这些要点更加紧凑。 为此,使用了各种度量学习技术。 例如,“中心损失”的概念非常简单:我们只需将点汇总到每个班级的学习中心,最终将变得更加紧凑。

中心损失实际上是用Python在10行中编程的,它可以非常快速地工作,最重要的是,它可以提高分类质量,因为 紧密度导致更好的泛化能力。
角软最大
我们尝试了许多不同的度量学习方法,得出的结论是Angular Softmax产生最佳结果。 在研究社区中,他也被认为是最先进的。
让我们看一个面部识别的例子。 这里有两个人。 如果您使用标准的Softmax,则将基于两个权重向量在它们之间绘制一个划分平面。 如果我们嵌入范数1,则这些点将位于圆上,即 在n维情况下的球体上(右图)。
然后,您会看到它们之间的角度已经引起了类的分离,并且可以对其进行优化。 但这还不够。 如果我们只是继续优化角度,那么任务实际上不会改变,因为 我们只是简单地用其他方式重新定义了它。 我记得我们的目标是使群集更紧凑。
有必要以某种方式要求各类之间的角度更大-使神经网络的任务复杂化。 例如,以这样一种方式,她认为一个类的点之间的角度比现实中的要大,因此她尝试越来越多地压缩它们。 这可以通过引入参数m来实现,该参数控制角度余弦的差异。

Angular Softmax有多个选项。 他们都玩这个角度乘以m或相加,或相乘并相加。 最新技术-ArcFace。
实际上,该代码很容易集成到管道分类中。

让我们看一下杰克·尼科尔森的例子。 在学习过程中,我们通过网格运行他的照片。 我们进行嵌入,遍历线性层进行分类,并在输出处获得反映类成员资格程度的分数。 在这种情况下,Nicholson的照片速度最高为20。 此外,根据ArcFace的公式,我们将速度从20降低到13(仅对groundtruth类执行),这使神经网络的任务复杂化。 然后,我们像往常一样执行所有操作:Softmax +交叉熵。
总的来说,通常的线性层被ArcFace层替代,该层不是以10行而是以20行编写的,但是具有出色的结果,并且实现开销最小。 结果,ArcFace在大多数任务上比大多数其他方法要好。 它与分类任务完美集成,并提高了质量。
转移学习
我想谈的第二件事是转移学习-在类似任务上使用预先训练的网络对新任务进行再培训。 因此,知识从一项任务转移到另一项任务。
我们在图像上进行搜索。 任务的本质是从图像(查询)的数据库中产生语义相似的内容。

顺理成章的做法是,使用已经研究过大量图像的网络-在ImageNet或OpenImages数据集中,其中包含数百万张图片,并训练我们的数据。

我们根据图片和用户点击的相似性为该任务收集了数据,并获得了20万个类别。 经过ArFace的培训,我们得到了以下结果。

在上图中,我们看到对于要求的鹈鹕,麻雀也成问题了。 即 嵌入在语义上是正确的-它是鸟,但在种族上不忠。 最令人讨厌的是,我们重新训练的原始模型知道这些类别并完美地区分了它们。 在这里,我们看到了所有神经网络共有的效应,称为灾难性遗忘。 也就是说,在再培训期间,网络有时甚至完全忘记了先前的任务。 这正是阻止此任务获得更高质量的原因。
知识升华
当一个网络教另一个网络并“将其知识传递给它”时,使用称为知识蒸馏的技术对此进行处理。 外观(下图中的完整培训管道)。

我们已经有了Arcface熟悉的分类管道。 回想一下,我们有一个假装的网络。 我们将其冻结,然后简单地在学习网络的所有照片中计算其嵌入,并获得OpenImages类的类:鹈鹕,麻雀,汽车,人等。。。我们从原始训练过的神经网络中汲取教训,并为该类学习另一种嵌入OpenImages,产生相似的分数。 使用BCE,我们使网络产生这些分数的类似分布。 因此,一方面,我们正在学习一个新任务(在图片的顶部),但同时也使网络不要忘记其根源(在图片的底部)-记住它过去知道的类。 如果您以50/50的条件比例正确地平衡了渐变,那么这将使所有鹈鹕都位于顶部,并从那里丢弃所有麻雀。

当我们应用它时,我们在mAP中获得了全部百分比。 这是很多。
因此,如果您的网络忘记了先前的任务,请使用知识提炼进行处理-可以正常工作。
额外的头
基本思想很简单。 再次以人脸识别为例。 我们在数据集中有一组人。 但是在数据集中通常也有面部的其他特征。 例如,几岁,什么眼睛的颜色等等。 所有这些都可以添加为另外一个。 信号:教导各个负责人预测此数据。 因此,我们的网络接收到更多种信号,因此,学习主要任务可能会更好。

另一个例子:队列检测。

通常在人的数据集中,除了身体之外,头部的位置还有单独的标记,显然可以使用。 因此,我们将人的边界框的预测和头部的边界框的预测添加到网络中,从而使准确度(mAP)提高了0.5%,非常不错。 最重要的是-在性能方面是免费的,因为 在生产中,多余的头是“断开的”。

光学字符识别
一个更复杂和有趣的情况是上面已经提到的OCR。 标准管道就是这样。

让那里有一张带有企鹅的海报,上面写上文字。 使用检测模型,我们突出显示此文本。 此外,我们将此文本输入到识别模型的输入中,该模型会生成识别的文本。 假设我们的网络是错误的,而不是企鹅预测“ l”一词中的“ i”。 当网络混淆相似字符时,这实际上是OCR中非常普遍的问题。 问题是如何避免这种情况-将企鹅翻译成企鹅? 当一个人看这个例子时,对他来说很明显这是一个错误,因为 他具有语言结构的知识。 因此,有关语言中字符和单词分布的知识应嵌入模型中。
为此,我们使用了一种称为BPE(字节对编码)的东西。 这是一种压缩算法,通常在90年代发明,并非用于机器学习,但现在它非常流行,并用于深度学习。 该算法的含义是将文本中频繁出现的子序列替换为新字符。 假设我们有字符串“ aaabdaaabac”,并且我们想要为其获取BPE。 我们发现在我们的单词中,成对的字符“ aa”是最常见的。 我们用新字符“ Z”替换它,得到字符串“ ZabdZabac”。 我们重复该迭代:我们看到ab是最频繁的子序列,将其替换为“ Y”,得到字符串“ ZYdZYac”。 现在“ ZY”是最常见的子序列,我们将其替换为“ X”,得到“ XdXac”。 因此,我们对文本分布中的一些统计依赖性进行编码。 如果我们遇到一个单词,其中有非常“奇怪”的子序列(对于教学团队来说是罕见的),那么这个单词就是可疑的。
aaabdaaabac
ZabdZabac Z=aa
ZY d ZY ac Y=ab
X d X ac X=ZY
这一切如何被认可。

我们突出显示“企鹅”一词,并将其发送到卷积神经网络,该神经网络产生了空间嵌入(固定长度向量,例如512)。 该向量对空间符号信息进行编码。 接下来,我们使用递归网络(UPD:实际上,我们已经使用了Transformer模型),它给出了一些隐藏状态(绿色条),在每个状态中都缝制了概率分布-根据模型,将其描绘在特定位置上。 然后,使用CTC-Loss消除这些状态并获得整个单词的预测,但有一个错误:L代替i。

现在将BPE集成到管道中。 我们希望摆脱预测单个字符到单词的过程,因此我们从缝合有关字符信息的状态分支出来,并在其上设置另一个递归网络; 她预测BPE。 在上述错误的情况下,获得3个BPE:“ peng”,“ ul”,“ ns”。 这与企鹅一词(即pen,gu,ins)的正确顺序有很大不同。 如果从模型训练的角度来看这个问题,那么在逐字预测中,网络会在八个字母中只有一个字母出错(错误率为12.5%); 就BPE而言,她在错误地预测所有3个BPE时被误认为100%。 对于网络来说,这是一个更大的信号,表明出了点问题,您需要修复您的行为。 当我们实现此功能时,我们能够修复此类错误,并将字错误率降低了0.25%-很大。 推理时将多余的头部移除,从而在训练中发挥作用。
FP16
关于培训,我想说的最后一件事是FP16。 历史上曾经发生过这样的事情,即在GPU上以FP32精度对网络进行了训练。 但这是多余的,特别是对于推理,其中半精度(FP16)足够而不会损失质量。 但是,培训并非如此。

如果我们看梯度的分布,即在传播误差时更新权重的信息,我们将看到在零处有一个巨大的峰值。 通常,很多值都接近零。 如果仅将所有权重转移到FP16,结果是我们在零区域(从红线开始)切除了左侧。

也就是说,我们将重置大量的渐变。 FP16工作范围中的右侧部分根本没有使用。 结果,如果您在FP16上训练额头,则该过程很可能会分散(下图中的灰色图形)。

如果使用混合精度技术进行训练,则结果几乎与FP32相同。 混合精度实现了两个技巧。
首先:我们简单地将损耗乘以一个常数(例如128)。因此,我们缩放所有渐变,并将其值从零移动到FP16的工作范围。 第二:我们存储FP32天平的主版本,该主版本仅用于更新,而在计算正向和反向通过网络的操作中,仅使用FP16。
我们使用Pytorch训练网络。 NVIDIA使用所谓的APEX为该组件进行了特殊组装,该组件实现了上述逻辑。 他有两种模式。 第一个是自动混合精度。 请参阅下面的代码,以了解它的易用性。

从字面上看,在训练代码中增加了两行,用于包装模型和优化器的损失以及初始化过程。 AMP做什么? 他猴子修补了所有功能。 到底是怎么回事? 例如,他看到有卷积函数,并且她从FP16中获利。 然后,他用自己的替换它,后者首先转换为FP16,然后执行卷积运算。 因此,AMP会执行网络上可以使用的所有功能。 对于某些人而言,并非如此。 不会有加速度。 对于大多数任务,此方法是合适的。
第二种选择:FP16优化器,用于风扇的完全控制。 如果您想指定FP16中的哪些层以及FP32中的哪些层,则适用。 但是它有许多限制和困难。 它不是以半踢开始的(至少我们必须冒汗才能开始)。 FP_optimizer也仅适用于Adam,甚至仅适用于APEX中的Adam(是的,他们在存储库中拥有自己的Adam,其接口与Paytorch完全不同)。
我们在学习Tesla T4卡时进行了比较。
在推论中,我们有两次预期的加速度。 在培训中,我们看到Apex框架通过相对简单的FP16提供20%的加速。 结果,我们得到的锻炼速度快了一倍,消耗的内存少了2倍,并且训练质量丝毫不受影响。 免费赠品。
推论
因为 由于我们使用PyTorch,因此迫切需要解决的问题是如何在生产中部署它。
共有3种选择方法(我们都使用了所有这些方法)。
- ONNX-> Caffe2
- ONNX-> TensorRT
- 最近,Pytorch C ++
让我们看看它们中的每一个。
ONNX和Caffe2
ONNX出现于1.5年前。 这是一个用于在不同框架之间转换模型的特殊框架。 Caffe2是与Pytorch相邻的框架,两者都在Facebook上开发。 从历史上看,Pytorch的开发速度比Caffe2快得多。 Caffe2在功能上落后于Pytorch,因此并非您在Pytorch中训练的每个模型都可以转换为Caffe2。 通常,您必须与其他层重新学习。 例如,在Caffe2中,没有诸如使用最近邻居插值进行上采样的标准操作。 结果,我们得出的结论是,对于每个模型,我们都有一个特殊的泊坞窗映像,其中用钉子钉住了框架版本,以避免在将来的更新中出现差异,因此,当再次更新其中一个版本时,我们不会浪费时间在其兼容性上。 所有这些都不是很方便,并且会延长部署过程。
张量rt
还有Tensor RT,这是一个NVIDIA框架,可以优化网络体系结构以加快推理速度。 我们进行了测量(在Tesla T4地图上)。
如果看一下图表,您会发现从FP32到FP16的过渡在Pytorch上提供了2倍的加速度,而TensorRT在同一时间提供了4倍的加速度。 差异非常明显。 我们在Tesla T4上进行了测试,Tesla T4具有非常好利用FP16计算的张量内核,这在TensorRT中显然非常出色。 因此,如果在数十张图形卡上运行了一个高负载模型,那么就有动力去尝试Tensor RT。
但是,与TensorRT一起使用时,比Caffe2还要痛苦得多:对其的支持甚至更少。 不幸的是,每次使用此框架时,我们都必须付出一点代价来转换模型。 但对于负载较重的模型,则必须执行此操作。 ;)我注意到在没有张量内核的地图上没有观察到如此大的增加。
Pytorch C ++
最后一个是Pytorch C ++。 六个月前,Pytorch开发人员意识到了使用其框架的人们的痛苦,并发布了
TorchScript教程 ,该
教程可让您在无需不必要的手势(JIT)的情况下将Python模型跟踪并序列化为静态图形。 它于2018年12月发布,我们立即开始使用它,立即发现了一些性能错误,并等待数月从
Chintala进行修复 。 但是现在它是一种相当稳定的技术,我们正在所有模型中积极使用它。 唯一的不足是缺少文档,正在积极地进行补充。 当然,您始终可以查看* .h文件,但是对于不了解优点的人来说,这很困难。 但是,Python确实有完全相同的工作。 在C ++中,j代码在最小的Python解释器上运行,这实际上保证了C ++与Python的身份。
结论
- 问题的陈述非常重要。 您必须就数据与产品经理沟通。 在开始执行此任务之前,建议先准备一个现成的测试集,然后在实施阶段对它进行最终度量。
- 我们在群集的帮助下自行清理数据。 我们在源数据上获得模型,使用CLink群集清理数据,然后重复该过程直到收敛。
- 公制学习:甚至分类也有帮助。 最新技术-ArcFace,可轻松集成到学习过程中。
- 如果您确实从预先训练的网络中转移学习,那么为了使网络不会忘记旧任务,请使用知识提炼。
- 使用几个网络头来利用来自数据的不同信号来改善主要任务也很有用。
- 对于FP16,您需要使用NVIDIA Pytorch的Apex组件。
- 并且推断可以方便地使用Pytorch C ++。