在过去几年的机器学习领域,我一直想打造真正的机器学习产品。
几个月前,在参加了精彩的 Fast.AI 深度学习课程后,似乎一切皆有可能,我有机会:深度学习技术的进步使许多以前不可能实现的事情成为可能,而且开发了新工具,使部署过程比以往任何时候都更容易。
在上述课程中,我遇到了经验丰富的 Web 开发人员 Alon Burg,我们合作追求这一目标。我们共同设定了以下目标:
- 提高我们的深度学习技能
- 提高我们的 AI 产品部署技能
- 制造一款有用的产品,满足市场需求
- 享受乐趣(对我们和我们的用户而言)
- 分享我们的经验
考虑到上述情况,我们正在探索以下想法:
- 尚未完成(或尚未正确完成)
- 计划和实施不会太难——我们的计划是 2-3 个月的工作,每周工作 1 天。
- 将拥有一个简单且吸引人的用户界面——我们希望开发一款人们会使用的产品,而不仅仅是用于演示目的。
- 将随时提供训练数据——正如任何机器学习从业者都知道的那样,有时数据比算法更昂贵。
- 将使用尖端的深度学习技术(谷歌、亚马逊和朋友的云平台尚未将其商品化),但不会太过尖端(因此我们将能够在网上找到一些示例)
- 将有可能实现“生产就绪”的结果。
我们最初的想法是承担一些医疗项目,因为这个领域与我们息息相关,我们觉得(现在仍然觉得)在医疗领域,深度学习有大量唾手可得的成果。然而,我们意识到我们将遇到数据收集问题,也许还有合法性和监管问题,这与我们保持简单的意愿相矛盾。我们的第二个选择是背景去除产品。
背景去除是一项相当容易手动或半手动完成的任务(Photoshop,甚至 Power Point 都有这样的工具),如果你使用某种“标记”和边缘检测,请参见此处的示例。然而,全自动背景去除是一项相当具有挑战性的任务,据我们所知,尽管有些产品确实尝试过,但仍然没有产品能取得令人满意的效果。
我们要去除什么背景?这是一个重要的问题,因为模型在对象、角度等方面越具体,分离的质量就越高。在开始我们的工作时,我们想得很远:一个通用的背景去除器,可以自动识别每种类型图像中的前景和背景。但在训练了我们的第一个模型后,我们明白最好将精力集中在一组特定的图像上。因此,我们决定专注于自拍和人物肖像。
NSDT工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割
自拍照是一张具有突出且聚焦的前景(一个或多个“人”)的图像,它保证了我们将物体(脸部+上身)与背景很好地分开,同时保持相当恒定的角度,并且始终是同一个物体(人)。
考虑到这些假设,我们开始了研究、实施和数小时的培训之旅,以创建一键式易于使用的背景去除服务。
我们工作的主要部分是训练模型,但我们不能低估正确部署的重要性。 良好的分割模型仍然不像分类模型(例如 SqueezeNet)那样紧凑,我们积极研究了服务器和浏览器部署选项。
1、语义分割
当研究与我们的任务相似的深度学习和计算机视觉任务时,很容易看出我们的最佳选择是语义分割任务。
其他策略,如通过深度检测进行分离,也存在,但似乎还不够成熟,无法满足我们的目的。
语义分割是一个众所周知的计算机视觉任务,与分类和对象检测并列为前三大任务。分割实际上是一个分类任务,即将每个像素分类到一个类。与图像分类或检测不同,分割模型确实显示了对图像的一些“理解”,不仅可以说“这张图片中有一只猫”,还可以在像素级别指出猫在哪里以及是什么。
那么分割是如何工作的呢?为了更好地理解,我们必须研究该领域的一些早期作品。
最早的想法是采用一些早期的分类网络,如 VGG 和 Alexnet。 VGG 是 2014 年图像分类领域最先进的模型,由于其架构简单直接,如今非常有用。在检查 VGG 早期层时,可能会注意到待分类项目周围有较高的激活值。较深的层具有更强的激活值,但由于重复池化操作,它们的性质较粗糙。考虑到这些理解,我们假设分类训练也可以通过一些调整来查找/分割对象。
语义分割的早期结果与分类算法一起出现。在这篇文章中,你可以看到使用 VGG 得到的一些粗略分割结果:
后期层结果:
巴士图像分割,浅紫色(29)为校车类
双线性上采样后:
这些结果仅仅来自于将全连接层转换(或保持)为原始形状,保持其空间特征,得到一个全卷积网络。在上面的例子中,我们将 7681024 的图像输入到 VGG,得到一个 24321000 的层。2432 是图像的池化版本(乘以 32),1000 是图像网络类别计数,我们可以从中得出上面的分割。
为了平滑预测,研究人员使用了一个简单的双线性上采样层。
在 FCN 论文中,研究人员改进了上述想法。他们一路连接了一些层,以提供更丰富的解释,根据上采样率,这些层被命名为 FCN-32、FCN-16 和 FCN-8:
在层之间添加一些跳跃连接可以让预测从原始图像中编码出更精细的细节。进一步的训练进一步改善了结果。
这种技术并没有人们想象的那么糟糕,并证明了深度学习在语义分割方面确实具有潜力。
论文中的 FCN 结果
FCN 解锁了分割的概念,研究人员尝试了不同的架构来完成这项任务。主要思想保持不变:使用已知架构、上采样和使用跳跃连接在较新的模型中仍然很突出。
你可以在几篇好文章中阅读有关该领域的进展:这里、这里和这里。你还可以看到大多数架构都保留了编码器-解码器架构。
2、回到我们的项目
经过一番研究,我们确定了三个可用的模型:FCN、Unet 和 Tiramisu — 非常深的编码器-解码器架构。我们也对 mask-RCNN 有一些想法,但实现它似乎超出了我们项目的范围。
FCN 似乎并不相关,因为它的结果不如我们所希望的那么好(即使作为起点),但我们提到的另外两个模型显示的结果还不错:CamVid 数据集上的 tiramisu 和 Unet 的主要优势是它的紧凑性和速度。在实现方面,Unet 非常容易实现(我们使用了 keras),Tiramisu 也很容易实现。为了入门,我们在 Jeremy Howard 的深度学习课程的最后一节课中使用了 Tiramisu 的良好实现。
有了这两个模型,我们继续对一些数据集进行训练。我必须说,在我们第一次尝试 Tiramisu 之后,我们发现它的结果对我们来说更有潜力,因为它能够捕捉图像中的锐利边缘。另一方面,unet 似乎不够精细,结果看起来有点模糊。
Unet 斑点
3、数据
在确定了模型的总体方向后,我们开始寻找合适的数据集。分割数据并不像分类或检测那样常见。此外,手动标记实际上是不可能的。最常见的分割数据集是 COCO 数据集,其中包括约 80K 张图像和 90 个类别,VOC pascal 数据集包含 11K 张图像和 20 个类别,以及较新的 ADE20K 数据集。
我们选择使用 COCO 数据集,因为它包含更多属于“人”类别的图像,而这正是我们感兴趣的类别。
考虑到我们的任务,我们考虑是否只使用与我们高度相关的图像,还是使用更通用的数据集。一方面,使用包含更多图像和类别的更通用的数据集将使模型能够处理更多的场景和挑战。另一方面,一夜之间,我们就训练了超过约 150K 张图像。如果我们使用整个 COCO 数据集引入模型,最终模型将看到每幅图像两次(平均而言),因此稍微修剪一下会很有帮助。此外,这将使我们的模型更专注于我们的目的。
还有一件事值得一提——Tiramisu 模型最初是在 CamVid 数据集上训练的,该数据集存在一些缺陷,但最重要的是它的图像非常单调:所有图像都是汽车的道路照片。你可以轻松理解,从这样的数据集中学习(即使其中包含人物)对我们的任务没有任何好处,因此经过短暂的尝试后,我们继续前进。
来自 CamVid 数据集的图像
COCO 数据集附带了相当直观的 API,让我们能够准确地知道每幅图像中的对象是什么(根据 90 个预定义类别)
经过一些实验,我们决定稀释数据集:首先,我们只过滤其中有人的图像,剩下 40K 幅图像。然后,我们删除所有有很多人的图像,只留下 1 或 2 幅,因为这是我们的产品应该找到的。最后,我们只留下 20%-70% 的图像被标记为人的图像,删除背景中有一个非常小的人或某种怪异怪物的图像(不幸的是,不是全部)。我们的最终数据集由 11K 幅图像组成,我们认为在这个阶段已经足够了。
左:图像合适 中:人太多 右:目标太小
4、Tiramisu 模型
如前所述,我们在 Jeremy Howard 的课程中介绍了 Tiramisu 模型。虽然它的全名“100 层 Tiramisu”意味着一个巨大的模型,但它实际上非常经济,只有 9M 个参数。相比之下,VGG16 有超过 130M 个参数。
Tiramisu 模型基于 DensNet,这是一种最新的图像分类模型,其中所有层都是互连的。此外,Tiramisu 为上采样层添加了跳跃连接,就像 Unet 一样。
如果你还记得的话,这种架构与 FCN 中提出的想法是一致的:使用分类架构、上采样并添加跳跃连接以进行细化。
Tiramisu架构
DenseNet 模型可以看作是 Resnet 模型的自然演化,但 DenseNet 不会“记住”每一层直到下一层,而是记住整个模型中的所有层。这些连接称为高速公路连接。它会导致滤波器数量膨胀,这被定义为“增长率”。Tiramisu的增长率为 16,因此我们每增加一层就增加 16 个新滤波器,直到达到 1072 个滤波器的层数。你可能期望有 1600 层,因为它是 100 层Tiramisu,但是,上采样层会丢弃一些滤波器。
Densenet 模型草图——早期的过滤器堆叠在整个模型中
5、训练
我们按照原始论文中描述的时间表训练我们的模型:标准交叉熵损失、RMSProp 优化器,学习率为 1e-3,衰减较小。我们将 11K 幅图像分为 70% 训练、20% 验证和 10% 测试。以下所有图像均取自我们的测试集。
为了使我们的训练时间表与原始论文保持一致,我们将 epoch 大小设置为 500 幅图像。这也使我们能够在每次结果改进时定期保存模型,因为我们在更多数据上对其进行了训练(本文中使用的 CamVid 数据集包含不到 1K 幅图像)
此外,我们只在 2 个类别上对其进行了训练:背景和人物,而论文中有 12 个类别。我们首先尝试在 coco 的一些类别上进行训练,但我们发现这对我们的训练没有太大帮助。
6、数据问题
一些数据集缺陷阻碍了我们的得分:
- 动物——我们的模型有时会分割动物。这当然会导致 IOU 较低。将动物添加到我们的任务中,放在同一个主类中或另一个类中,可能会删除我们的结果
- 身体部位——因为我们以编程方式过滤数据集,所以我们无法判断人物类实际上是人还是某个身体部位,比如手或脚。这些图像不在我们的范围内,但仍然到处出现。
动物、身体部位、手持物体
- 手持物体 - 数据集中的许多图像与运动有关。棒球棒、网球拍和滑雪板随处可见。我们的模型对如何对它们进行分割感到困惑。与动物的情况一样,我们认为将它们添加为主类的一部分或单独的类将有助于提高模型的性能。
带有物体的运动图像
- 粗略的基准真值 ——coco 数据集不是逐像素注释的,而是用多边形注释的。有时这已经足够好了,但有时基准真值非常粗糙,这可能会阻碍模型学习细微之处
图像和(非常)粗略的基准真值
7、结果
我们的结果令人满意,但并不完美:我们的测试集上的 IoU 达到了 84.6,而目前最先进的水平是 85。不过这个数字有点棘手:它在不同的数据集和类别中会波动。有些类别本质上更容易分割,例如房屋、道路,大多数模型很容易达到 90 IoU 的结果。其他更具挑战性的类别是树木和人类,大多数模型在这些类别上达到约 60 IoU 的结果。为了衡量这种难度,我们帮助我们的网络专注于单一类别和有限类型的照片。
我们仍然觉得我们的工作还没有像我们希望的那样“准备好投入生产”,但我们认为现在是停下来讨论我们的结果的好时机,因为大约 50% 的照片会产生良好的效果。
以下是一些很好的例子,可以让你感受一下应用程序的功能:
图像、基准真值、我们的结果
8、调试和记录
调试是训练神经网络的一个非常重要的部分。当我们开始工作时,我们很想直接开始,抓取数据和网络,开始训练,然后看看结果如何。然而,我们发现跟踪每一个动作,为自己制作工具以便能够检查每一步的结果,这一点非常重要。
以下是常见的挑战,以及我们为它们所做的工作:
- 早期问题——模型可能没有训练。这可能是因为一些固有的问题,或者因为某种预处理错误,比如忘记对一些数据块进行规范化。无论如何,简单的结果可视化可能会非常有帮助。这里有一篇关于这个主题的好文章。
- 调试网络本身——在确保没有关键问题后,训练开始,使用预定义的损失和指标。在分割中,主要测量是 IoU——相交除以并集。我们花了一些时间才开始使用 IoU 作为我们模型的主要测量(而不是交叉熵损失)。另一个有用的做法是展示我们模型在每个时期的一些预测。这是一篇关于调试机器学习模型的好文章。请注意,IoU 不是 keras 中的标准指标/损失,但你可以在网上轻松找到它,例如这里。我们还使用这个要点绘制了每个时期的损失和一些预测。
- 机器学习版本控制——在训练模型时,有许多参数,其中一些参数很难理解。我必须说,我们还没有找到完美的方法,除了热切地编写我们的配置(并使用 keras 回调自动保存最佳模型,见下文)。
- 调试工具——完成上述所有操作后,我们可以在每一步检查我们的工作,但不是无缝的。因此,最重要的步骤是将上述步骤结合在一起,并创建一个 Jupyter 笔记本,它允许我们无缝加载每个模型和每个图像,并快速检查其结果。这样我们就可以轻松地看到模型之间的差异、陷阱和其他问题。
以下是我们模型改进的示例,包括参数调整和额外训练:
用于保存迄今为止具有最佳验证 IoU 的模型,Keras 提供了非常好的回调以使这些事情变得更容易:
callbacks = [keras.callbacks.ModelCheckpoint(hist_model, verbose=1,save_best_only =True, monitor= ’val_IOU_calc_loss’), plot_losses]
除了常规调试可能的代码错误之外,我们还注意到模型错误是“可预测的”,例如“切割”似乎超出一般身体部位的身体部位、大段“咬伤”、不必要地继续延伸身体部位、光线不足、质量差以及许多细节。其中一些警告在添加来自不同数据集的特定图像时得到了处理,但其他警告仍然是需要处理的挑战。为了改善下一版本的结果,我们将专门针对模型的“困难”图像使用增强。
我们已经在上面提到了这个问题,以及数据集问题。现在让我们看看我们的一些模型难题:
- 衣服——非常暗或非常浅的衣服有时往往会被解释为背景
- “咬伤”——否则结果很好,但有一些咬伤
- 照明 - 光线不足和模糊在图像中很常见,但在 COCO 数据集中却并非如此。因此,除了处理这些问题的标准模型难度之外,我们的模型甚至还没有为更难的图像做好准备。这可以通过获取更多数据来改进,此外,还可以通过数据增强来改进。同时,最好不要在晚上尝试我们的应用程序 :)
9、未来计划
进一步训练
我们的生产结果是在对训练数据进行约 300 次训练后得出的。在此之后,模型开始过度拟合。我们在发布前非常接近地达到了这些结果,因此我们还没有机会应用数据增强的基本实践。
我们在将图像大小调整为 224X224 后训练了模型。使用更多数据和更大图像(COCO 图像的原始大小约为 600X1000)进行进一步训练也有望改善结果。
CRF 和其他增强功能
在某些阶段,我们发现我们的结果在边缘处有点嘈杂。可以改进这一点的模型是 CRF。在这篇博文中,作者展示了使用 CRF 的简单示例。
然而,它对我们的工作不是很有用,也许是因为它通常在结果较粗糙时有帮助。
抠图
即使使用我们目前的结果,分割也不完美。头发、精致的衣服、树枝和其他精细物体永远无法完美分割,即使基准真值分割不包含这些细微之处。分离这种精细分割的任务称为抠图(matting),并定义了不同的挑战。这是最先进的抠图示例,于今年早些时候在 NVIDIA 会议上发布:
抠图示例——输入也包括三元图
抠图任务与其他图像相关任务不同,因为它的输入不仅包括图像,还包括三分图——图像边缘的轮廓,这使它成为一个“半监督”问题。
我们对抠图进行了一些实验,使用我们的分割作为三分图,但是没有得到显著的结果。
另一个问题是缺乏合适的数据集进行训练。
10、结束语
正如开头所说,我们的目标是构建一个重要的深度学习产品。正如你在 Alon 的帖子中看到的那样,部署变得越来越容易和快速。另一方面,训练模型是棘手的——训练,尤其是一夜之间进行的训练,需要仔细规划、调试和记录结果。
在研究和尝试新事物与平凡的训练和改进之间取得平衡也不容易。由于我们使用深度学习,我们总是觉得最好的模型,或者我们需要的确切模型就在眼前,另一个谷歌搜索或文章会引导我们找到它。但在实践中,我们真正的改进仅仅来自于从原始模型中“榨干”更多东西。正如上面所说,我们仍然觉得还有更多东西可以深入。
原文链接:图像背景剔除AI模型 - BimAnt