对象检测和人脸识别算法
Photo by Skitterphoto downloaded from Pexels
卷积神经网络.第 2 部分:实现目标探测和人脸识别算法的详细卷积结构
卷积神经网络广泛用于解决基于图像的问题,例如对象/字符检测和人脸识别。在本文中,我们将重点介绍从 LeNet 到 Siamese networks 的最著名架构,其中大多数架构如下:
Image by Author
如果你没有任何关于卷积神经网络的知识,我建议你阅读这篇文章的第一部分 t,讨论 CNN 的基础知识。
注意:因为 Medium 不支持 LaTeX,所以数学表达式是作为图像插入的。因此,为了更好的阅读体验,我建议你关闭黑暗模式。
目录
1。交叉熵
2。图像分类
3。物体检测— YOLOv3
4。人脸识别—暹罗网络
1-交叉熵
当对图像进行分类时,我们通常在最后一层使用大小为( C ,1)的softmax
函数,其中 C 是所讨论的类的数量。
向量的第I行是输入图像属于类别 i 的概率。predicted class
被设置为与highest probability.
相对应的那个
网络通过使用反向传播进行学习,并优化定义如下的cross entropy
:
在哪里
- p ( x ,类)是参考概率,如果对象确实属于所填写的类,则等于 1,否则等于 0
- q ( x ,类)是网络通过 softmax 获知的对象 x 属于该类的概率
对于输入 x ∈ class_j :
因此,我们把loss function
设定为:
我们平均损失,其中 m 是训练集的大小。
2-图像分类
LeNet —数字识别
LeNet 是由 Yann Lecun 开发的一个架构,它的目标是检测输入中出现的数字。
给定从 0 到 9 的hand-written
位数的gray-scale
幅图像,卷积神经网络预测图像的位数。
训练集被称为MNIST
,它是一个数据集,包含超过 70k 个具有 28x28x1 像素的图像。神经网络具有以下计算超过 60k 个参数的架构:
Official paper of LeNet
更多详情,建议你看官方论文。
AlexNet
AlexNet 是一个著名的建筑,它赢得了 2012 年的 ImageNet 竞赛。它类似于 LeNet,但有更多的层次,辍学和重新激活功能的大部分时间。
Official paper of AlexNet
训练集是ImageNet database
的子集,T3 是 1500 万张标记的图像,具有高分辨率,代表超过 22k 个类别。
AlexNet 在训练集中使用了超过 120 万张图片,在验证集中使用了 50k,在测试集中使用了 150k,这些图片都被调整到了 227x227x3。该架构有多个60 million parameters
,因此在2 GPUs
上被训练,它输出一个大小为 (1000,1) 的softmax vector
。
要了解更多信息,我建议你阅读官方文件。
VGG-16
VGG-16 在一个卷积神经网络上进行图像分类,在同一个数据集ImageNet
上训练,并且已经在多个138 million parameters
GPU 上训练。
架构如下:
Official paper of VGG-16
它比 AlexNet 更准确、更深入,因为它用连续的 3x3 内核取代了大型内核 11x11x5 和 5x5。更多细节,请查看 VGG 项目的官方文件。
三物体检测— YOLO
目标检测是在图像中检测多个目标的任务,包括目标定位和目标分类。第一种粗略的方法是滑动具有可定制尺寸的窗口,并使用在裁剪图像上训练的网络来预测每次内容的类别。这个过程有很高的计算成本,幸运的是可以使用卷积来实现自动化。
YOLO 代表YouOonlyLookOnce,其基本思想是在图像上放置一个网格(通常为 19x19 ),其中:
只有一个单元,即包含对象中心/中点的单元,负责检测该对象
网格( i , j )的每个单元格标记如下:
Image by Author
因此,对于每个图像,目标输出的大小为:
IOU & NMS
为了评估对象定位,我们使用IintersectionOverUnion 来测量两个边界框之间的overlap
:
Image by Author
当预测给定网格单元中给定对象的边界框时,可能会给出许多输出,Non-Max Suppression
帮助您检测对象only once
。它取最高的概率,并抑制具有高重叠(IOU)的其他框。
对于网格的每个单元,算法如下:
Image by Author
锚箱
在大多数情况下,一个网格单元可能包含multiple objects
,锚盒允许检测所有这些。在2 anchor boxes
的情况下,网格的每个单元格标记如下:
Image by Author
更一般地,输出目标的大小为:
其中 N 为班数 M 为锚箱数。
YOLOv3 算法
YOLO 接受了 coco 数据集的训练,这是一个大规模的对象检测、分割和字幕数据库,包含 80 个对象类别。YOLOv3 有一个Darknet-53
架构作为特征提取器,也称为backbone
。
通过使用梯度方法最小化loss function
来进行训练。
是combined
的:
- p_c 上的逻辑回归损失
- b_i 的平方误差损失
- 概率 c_i 的软最大损失(交叉熵)
在每个时期,在每个单元中,我们生成输出 y_(i,j) 和evaluate
损失函数。
进行预测时,我们检查 p_c 是否足够高,对于每个网格单元,我们去除低概率预测,并对每个类使用非最大抑制来生成最终输出。
想了解更多信息,我建议你阅读官方论文。
4-人脸识别-连体网络
暹罗网络是神经网络,通常是卷积的,它允许计算两个输入(在我们的情况下是图像)之间的相似度,如下所示:
Image by Author
CNN 模块的目的是在另一个空间中表示图像上的信息,这要归功于一个函数,叫做embedding space
。然后,我们使用某个距离比较两个嵌入。暹罗网络中的学习是通过最小化一个目标函数来完成的,该目标函数由一个叫做triplet
的损失函数组成。
triplet
函数以 3 个向量变量作为输入:一个锚点 A ,一个正的 P (类似于 A )和一个负的 N (不同于 A )。因此,我们希望:
其中∨x∨=<x, x >为给定的标量积。
为了防止学习函数 f 为空,我们定义裕量 0 < α ≤1,从而:
因此,我们将loss
函数定义如下:
从大小为 n 的学习数据库开始,要最小化的目标函数是:
在训练架构时,对于每个时期,我们固定三元组的数量,并且对于每个三元组:
- 我们随机选择同一类别的两个图像(锚和积极的)
- 我们从另一个类中随机选取一张图片(负面)
一个三联体( 一个 , N , P )可以是:
- 易负,当∩f(A)f(P)∩+α∩f(A)f(【T75
- 半硬负,当∩f(A)f(P)∩+α>∩f(A)f(N)∩>∩
- 硬负,当∩f(A)—f(N)∩<∩f(A)f(P)∩
我们通常选择聚焦在半硬底片上来训练神经网络。
应用:人脸识别
暹罗网络可以用来开发一个能够识别人脸的系统。给定相机拍摄的图像,该架构将其与数据库中的所有图像进行比较。由于我们的数据库中不能有同一个人的多个图像,我们通常在足够丰富的开源图像集上训练暹罗网络来创建三胞胎。
Image by Author
卷积神经网络学习相似性函数 f ,这是图像的嵌入。
给定一张相机照片,我们将其与数据库中的每张图像进行比较,从而:
- 如果 d ( f ( 图像,图像 _ j)≤τ,两幅图像代表同一个人
- 如果 d ( f ( 图像,图像 _ j)>τ,图像是两个不同的人
我们选择距离 d 最接近图像的人脸 image_j 。阈值 τ 以这样一种方式选择,即例如F1-得分最高。
结论
CNN 是图像处理中广泛使用的架构,它们能够实现更好更快的结果。最近,它们也被用于文本处理,其中网络的输入是标记的嵌入,而不是图像的像素。
不要犹豫,检查我以前的文章处理:
参考
原载于 2019 年 2 月 15 日【https://www.ismailmebsout.com。
基于 RetinaNet 的航空影像目标检测
ESRI 数据科学挑战赛 2019 第三名解决方案
(Left) the original image. (Right) Car detections using RetinaNet, marked in green boxes
Detecting cars and swimming pools using RetinaNet
介绍
为了进行纳税评估,通常需要在实地人工进行调查。这些调查对计算房产的真实价值很重要。例如,拥有一个游泳池可以提高房地产价格。类似地,一个街区或商店周围的汽车数量可以表明那个地方的经济活动水平。能够通过航空图像和人工智能实现这一点,可以通过消除低效率以及人类所需的高成本和时间来大大帮助这些过程。
为了解决这个问题,我们将尝试在 224x224 像素的航空图像的 RGB 芯片中检测汽车和游泳池。训练数据集具有 3748 个图像,具有 PASCAL VOC 格式的边界框注释和标签。
这个问题和数据集一起由 ESRI 在黑客地球上发布为 ESRI 数据科学挑战赛 2019 。我参加了比赛,并在公共排行榜中获得第三名,在IoU = 0.3
时,我使用最先进的 RetinaNet 模型获得了 77.99 的地图(平均精度)。在下面的帖子中,我将解释我是如何尝试这个问题的。
RetinaNet
RetinaNet 是通过对现有的单级对象检测模型(如 YOLO 和 SSD)进行两项改进而形成的:
特征金字塔网络
金字塔网络通常用于识别不同尺度的物体。特征金字塔网络(FPN)利用深层 CNN 固有的多尺度金字塔等级来创建特征金字塔。
一级 RetinaNet 网络架构在前馈 ResNet 架构(a)之上使用特征金字塔网络(FPN)主干来生成丰富的多尺度卷积特征金字塔(b)。RetinaNet 将两个子网络连接到该主干,一个用于分类锚盒(c ),一个用于从锚盒回归到地面真实对象盒(d)。网络设计非常简单,这使得这项工作能够专注于一种新的焦点损失函数,该函数消除了我们的一级检测器与最先进的两级检测器(如带 FPN 的更快 R-CNN)之间的精度差距,同时运行速度更快。
焦点损失
焦点损失是对交叉熵损失的改进,有助于减少分类良好的示例的相对损失,并将更多的注意力放在困难的、错误分类的示例上。
焦点损失使得能够在存在大量简单背景示例的情况下训练高度精确的密集物体检测器。
Focal Loss Function
如果你对这个模型的细节更感兴趣,我建议你阅读原始论文和这个非常有用的描述性博客“retina net背后的直觉”。
现在,让我们开始实际的实现并开始编码。这里有一个 Github 库,你可以跟随它:
[## kapil-varshney/esri_retinanet
通过在 GitHub 上创建帐户,为 kapil-varshney/esri_retinanet 的开发做出贡献。
github.com](https://github.com/kapil-varshney/esri_retinanet)
安装 Retinanet
我们将使用由 Fizyr 开发的 RetinaNet 的令人敬畏的 Keras 实现。我假设你有你的深度学习机器设置。如果没有,跟随我的指南这里。另外,我建议使用虚拟环境。以下脚本将安装 RetinaNet 和其他必需的包。
或者,您可以在 AWS 上使用一个 GPU 实例(p2.xlarge)和“使用 python 的计算机视觉深度学习”AMI。这个 AMI 预装了 keras-retinanet 和其他必需的包。通过workon retinanet
命令激活 RetinaNet 虚拟环境后,就可以开始使用模型了。
注意:Retinanet 计算量很大。一批 4 张(224x224)图像至少需要 7–8gb 的 GPU 内存。
一旦安装了 RetinaNet,就为这个项目创建以下目录结构。
我将详细解释其中的每一个,但这里有一个概述:
build_dataset.py
—创建训练/测试集
config/esri_retinanet_config.py
的 Python 脚本—构建脚本使用的配置文件。
dataset/annotations
—保存所有图像注释的目录
dataset/images
—保存所有图像的目录
dataset/submission_test_data_images
—Esri 数据科学挑战赛的提交测试目录。如果您正在处理自己的数据集和不同的项目,则可以忽略这一点。
snapshots
—每个历元
models
后保存所有训练快照的目录—为评估和测试转换快照的目录。将被保存
tensorboard
—保存训练日志的目录,供 tensorboard
predict.py
—脚本对提交的测试文件进行预测
构建数据集
首先,我们需要编写一个配置文件,它将保存图像、注释、输出 CSV——训练、测试和类,以及测试训练分割值的路径。有了这样的配置文件,代码就可以通用于不同的数据集。
在这个配置文件中,TRAIN_TEST_SPLIT = 0.75
。标准做法是在原始数据集的训练数据集和测试数据集之间进行 75–25 或 70–30 甚至 80–20 的拆分。但是,为了这次比赛的目的,我没有制作测试数据集,而是使用完整的数据集进行训练。这样做是因为只提供了 3748 幅图像的小数据集。此外,还提供了一个由 2703 幅图像组成的测试数据集(没有注释),在此基础上,可以通过在线提交预测来测试该模型。
接下来,让我们编写一个 Python 脚本,该脚本将读取所有图像路径和注释,并输出训练和评估模型时所需的三个 CSV:
- train . CSV——该文件将保存所有用于训练的注释,格式如下:
<path/to/image>,<xmin>,<ymin>,<xmax>,<ymax>,<label>
每一行代表一个边界框,因此,一幅图像可以出现在多行中,这取决于该图像中有多少对象被注释。 - test.csv 格式类似于 train.csv,该文件将保存用于测试模型的所有注释。
- classes.csv 包含数据集中所有唯一类标签的文件,带有索引分配(从 0 开始,忽略背景)
让我们从创建一个build_dataset.py
文件并导入所需的包开始。注意,我们导入之前在 config 目录中创建的esri_retinanet_config.py
文件,并给它一个别名config
。
在上面的代码中,我们创建了一个参数解析器,可选地接收图像和注释路径、输出 CSV 路径和训练测试分割。是的,我知道我们已经在配置文件中定义了这些参数。但是,我也意识到,有时候我想为一个实验创建一个图像子样本,或者有一个不同的训练测试分割,等等。那时,在执行脚本时选择传递这些参数,而不改变配置文件,速度会更快。您可以看到,我已经为配置文件本身的每个参数提供了默认值。因此,除非您愿意,否则不要求您提供这些参数。解析参数后,为每个参数分配简单的变量名。
在前面的代码中,我们将图像路径读入一个列表,随机化该列表,将其分为训练集和测试集,并以(<dataset_type>, <list_of_paths>, <outpuCSV>)
的格式将它们存储在另一个列表dataset
中。我们还将初始化CLASS
集合来保存数据集中所有唯一的类标签。
接下来,我们遍历每个数据集(训练和测试)并打开要写入的输出 CSV 文件。对于每个数据集,我们在每个图像路径上循环。对于每个图像,提取文件名并构建相应的注释路径。这是因为,通常,图像和注释文件具有相同的名称,但扩展名不同。例如,dataset/images/0000001.jpg
在dataset/annotations/0000001.xml
中有其注释。如果数据集遵循不同的命名约定,请修改此部分。使用BeautifulSoup
解析注释(XML)文件。然后,我们可以从解析的 XML 中找到“宽度”和“高度”以及“对象”。
对于每幅图像,找到所有的对象,并对每个对象进行迭代。然后,为注释中的每个对象找到边界框(xmin,ymin,xmax,ymax)和类标签(name)。通过截断图像边界外的任何边界框坐标来进行清理。此外,如果任何最小值错误地大于最大值,则进行健全性检查,反之亦然。如果我们找到这样的值,我们将忽略这些对象并继续下一个。
现在,我们已经有了所有的信息,我们可以继续写入输出 CSV,一次一行。此外,继续向CLASSES
集合添加标签。这将最终拥有所有唯一的类标签。
构建所需格式的数据集的最后一件事是将带有各自索引的类标签写入 CSV。在 ESRI 数据集中,只有两个类-汽车(标签:’ 1 ‘,索引:1)和游泳池(标签:’ 2 ',索引:0)。这就是classes.csv
查找 Esri 数据集的方式。
2,0
1,1
训练和评估模型
现在,数据集已经准备好,RetinaNet 已经安装,让我们继续在数据集上训练模型。
# For a list of all arguments
$ retinanet-train --help
为了训练模型,我使用了以下命令:
$ retinanet-train --weights resnet50_coco_best_v2.1.0.h5 \
--batch-size 4 --steps 4001 --epochs 20 \
--snapshot-path snapshots --tensorboard-dir tensorboard \
csv dataset/train.csv dataset/classes.csv
建议加载预训练的模型或权重文件,而不是从头开始训练,以加快训练速度(损失会更早开始收敛)。我在 COCO 数据集上使用了来自带有 ResNet50 主干的预训练模型的权重。使用以下链接下载该文件。
[https://github.com/fizyr/keras-retinanet/releases/download/0.5.0/resnet50_coco_best_v2.1.0.h5](https://github.com/fizyr/keras-retinanet/releases/download/0.5.0/resnet50_coco_best_v2.1.0.h5)
batch-size
和steps
将取决于你的系统配置(主要是 GPU)和数据集。我通常从batch-size = 8
开始,然后根据模型训练是否成功开始,增加或减少 2 倍。如果训练成功开始,我将终止训练(CTRL+C ),并以较大的批量开始,否则以较小的批量开始。
一旦决定了批量大小,您就需要计算在每个时期覆盖整个数据集所需的步骤。下面的命令将给出先前在dataset
目录中创建的train.csv
中的行数。
$ wc -l datatset/train.csv
步长的计算很简单:steps = count of rows in train.csv / batch-size
。接下来,设置epochs
的数量。根据我的经验,RetinaNet 收敛得很快,所以通常用较少的历元就可以完成这项工作。如果不是,你可以随时从上一个时代开始训练,并进一步训练你的模型。因此,我们将提供一个snapshot-path
,在每个时期后模型将被保存在这里。
我们还将提供一个tensorflow-dir
,所有的日志将被保存,tensorboard 可以运行,以可视化的培训进行。要启动 tensorboard,打开一个新的终端窗口并运行下面提到的命令。在运行 tensorboard 之前,请确保您已经安装了它。
# To launch tensorboard
$ tensorboard --logdir <path/to/logs/dir>
最后,提供带有训练数据集和类标签的csv
文件。并执行训练命令。现在,当你的模型训练的时候,去做一个钢铁侠或者睡觉什么的。在 AWS p2.xlarge 实例上的 K80 Tesla GPU 上,每个包含 3748 张(224x224)图像的历元花费了 2 个多小时。
一旦模型训练到您满意的程度,就将模型转换成可用于评估和预测的格式。
# To convert the model
$ retinanet-convert-model <path/to/desired/snapshot.h5> <path/to/output/model.h5># To evaluate the model
$ retinanet-evaluate <path/to/output/model.h5> csv <path/to/train.csv> <path/to/classes.csv># Sample evaluation
95 instances of class 2 with average precision: 0.8874
494 instances of class 1 with average precision: 0.7200
mAP: 0.8037
在对 125 幅测试图像的样本评估中,该模型能够利用 18 个时期的 375 幅图像训练实现 80.37%的 mAP(平均精度)。对于如此小的数据集来说,这是一个很好的结果。
预言
构建一个脚本predict.py
,它将使用训练好的模型,对提交的图像进行预测,并将其写在磁盘上。
在将图像输入模型进行预测之前,需要使用 keras_retinanet 实用程序中的一些方法对图像进行预处理。此外,导入我们之前创建的配置文件,用于加载几个路径。
构造参数解析器以在执行脚本时接受参数,然后解析参数。参数model
将接受训练模型文件的路径,该文件将用于进行预测。对于类标签和预测输出目录,默认值取自配置文件。因此,这些不是必需的参数。参数input
将接受包含图像的目录的路径来进行预测。此外,confidence
参数可用于过滤弱预测。
接下来,从类标签 CSV 加载类标签映射,并将其创建到字典中。加载要用于预测的模型。使用input
参数中提供的目录路径获取并列出所有图像路径。
迭代每个图像路径,以便我们可以对提供的数据集中的每个图像进行预测。上面代码中的第 6–9 行从图像路径中提取图像文件名,然后构造并打开一个输出文本文件路径,在该路径中保存对该图像的预测。在第 11–15 行中,我们加载图像,对其进行预处理,调整其大小,然后在将其传递给模型之前扩展其尺寸。在第 18 行,我们将预处理的图像传递给模型,它返回预测的框(边界框坐标)、每个框的概率得分以及相关联的标签。在上面块的最后一行,根据原始图像大小重新调整边界框坐标。
接下来,迭代模型预测的每个检测。跳过得分低于提供的置信度值的项目。虽然,如果你想计算地图(平均平均精度)保持所有的预测。为此,将参数confidence
的值作为0.0
传递。包围盒坐标将是float
值,因此将其转换为int
。按照所需的格式:<classname> <confidence> <ymin> <xmin> <ymax> <xmax>
为每个预测构造一行,并将其写入文件。一旦该图像的所有检测结果都已写入相应的文件,请关闭该文件。
$ python predict.py --model models/output.h5 --input dataset/submission_test_data_images --confidence 0.0
运行上面的命令来执行predict.py
脚本。请根据您的数据集和项目随意更改参数。
实验和结果
最初,我只使用 18 个时期 10%的数据(375 张图像)来训练模型。该模型在测试图像上具有置信度值为 0.5 的 71 的 mAP。我继续在另外 10 个时期的 3748 幅图像的完整数据集上训练该模型,以产生 74 幅增加的地图。我决定对模型进行一点工程设计,并对锚盒进行修改。数据集只有方形的边界框,我将这些框的长宽比从[0.5, 1, 2]
改为[1]
。这似乎是一个很好的尝试,但我意识到这不是因为锚盒的比例会随着图像的增加而改变。随着网络规模的减小,使用总数据集进行网络训练的速度比以前快得多。预测的准确性也有所提高,但随后开始下降。我决定使用置信值为 0.0 的第二代结果来包含所有预测。这就产生了77.99
的地图,使我在挑战中获得了第三名。我还尝试了一些其他的实验,将图像的比例用于 FPN 和数据增强参数,但都没有成功,但最终提交时还是坚持使用早期的结果。
摘要
在这篇文章中,我们谈到了最先进的 RetinaNet 模型,以及我如何在 2019 年 Esri 数据科学挑战赛中使用它来检测 224x224 航拍图像中的汽车和游泳池。我们从构建项目目录开始。接下来,我们构建了模型要使用的训练/测试数据集。用适当的参数对模型进行训练,然后对训练好的模型进行转换以进行评估和预测。我们创建了另一个脚本来对提交测试图像进行检测,并将预测写到磁盘上。最后简单描述一下我尝试的实验和取得的成果。
参考
[## 密集物体探测的聚焦损失
迄今为止最高精度的物体探测器是基于一个由 R-CNN 推广的两阶段方法,其中…
arxiv.org](https://arxiv.org/abs/1708.02002) [## 用于目标检测的特征金字塔网络
特征金字塔是识别系统中的基本组件,用于检测不同尺度的对象。但是最近…
arxiv.org](https://arxiv.org/abs/1612.03144) [## 用 Python 进行计算机视觉的深度学习:用我的新书掌握深度学习
计算机视觉深度学习入门吃力?我的新书会教你所有你需要知道的。
www.pyimagesearch.com](https://www.pyimagesearch.com/deep-learning-computer-vision-python-book/)
感谢你阅读这篇文章。希望对你有帮助。欢迎留言,提出意见和建议。也可以在 LinkedIn 上和我联系。下面是 GitHub 存储库和代码:
[## kapil-varshney/esri_retinanet
通过在 GitHub 上创建帐户,为 kapil-varshney/esri_retinanet 的开发做出贡献。
github.com](https://github.com/kapil-varshney/esri_retinanet)
使用 YOLO 框架进行目标检测的综合指南—第一部分
YOLO 背后的理论,网络架构和更多
Cover Image (Source: Author)
目录:
- 介绍
- 为什么是 YOLO?
- 它是如何工作的?
- 并集上的交集
- 非最大抑制
- 网络体系结构
- 培养
- YOLO 的局限性
- 结论
简介:
你只看一次(YOLO)是一种新的和更快的对象检测方法。传统系统重新利用分类器来执行检测。基本上,为了检测任何对象,系统采用该对象的分类器,然后对其在图像中不同位置的存在进行分类。其他系统使用区域提议方法在图像中生成潜在的边界框,然后对这些潜在的框运行分类器。这产生了一种稍微有效的方法。分类后,使用后处理来细化边界框,消除重复检测等。由于这些复杂性,系统变得缓慢且难以优化,因为每个组件都必须单独训练。
Object Detection with Confidence Score
为什么是 YOLO?
基本模型可以以每秒 45 帧的速度实时处理图像。作为网络的一个较小版本,快速 YOLO 可以每秒 155 帧的速度处理图像,同时达到其他实时检测器的两倍。它优于其他检测方法,包括 DPM(可变形部分模型)和 R-CNN。
它是如何工作的?
YOLO 将物体检测重新定义为一个单一的回归问题,而不是一个分类问题。该系统只需查看图像一次,就能检测出哪些物体存在以及它们的位置,因此得名 YOLO。
系统将图像分成一个 S×S 的网格。这些网格单元中的每一个预测 B 边界框和这些框的置信度得分。置信度得分表明模型对盒子包含对象的确信程度,以及它认为盒子预测的准确性。可以使用以下公式计算置信度得分:
C = Pr(object) * IoU
IoU:预测框和实际情况之间的交集。
如果单元格中不存在任何对象,则其置信度得分应该为零。
Bounding Box Predictions (Source: Author)
每个边界框由五个预测组成: x,y,w,h 和置信度其中,
(x,y): 代表盒子中心的坐标。这些坐标是相对于网格单元的边界计算的。
w: 边框的宽度。
h: 边框的高度。
每个网格单元还预测 C 个条件类概率 Pr(Classi|Object) 。它只预测每个网格单元的一组类别概率,而不考虑盒子 b 的数量。在测试期间,这些条件类别概率乘以单个盒子置信度预测,该预测给出每个盒子的特定类别置信度得分。这些分数显示了该类别的概率以及该框与对象的匹配程度。
*Pr(I 类|对象)Pr(对象)IoU = Pr(I 类) IoU。
最终的预测被编码为一个 S x S x (B*5 + C)张量。
交集超过并集(欠条):
IoU 用于评估对象检测算法。它是基础真实和预测边界框之间的重叠,即它计算预测框相对于基础真实有多相似。
Demonstration of IoU (Edited by Author)
通常,IoU 的阈值保持在 0.5 以上。尽管许多研究人员采用更严格的阈值,如 0.6 或 0.7。如果边界框的 IoU 小于指定阈值,则不考虑该边界框。
非最大抑制:
该算法可以找到同一物体的多次检测。非最大值抑制是一种算法仅检测一次对象的技术。考虑一个例子,其中算法检测到同一对象的三个边界框。下图显示了具有相应概率的方框。
Multiple Bounding Boxes Of the Same Object (Edited by Author)
盒子的概率分别是 0.7、0.9 和 0.6。为了消除重复,我们首先要选择概率最高的盒子,并将其作为预测输出。然后用预测输出消除任何 IoU > 0.5(或任何阈值)的边界框。结果将是:
Bounding Box Selected After Non-Max Suppression (Edited by Author)
网络架构:
基本模型有 24 个卷积层,后面是 2 个全连接层。它使用 1 x 1 缩减层,然后是 3 x 3 卷积层。快速 YOLO 使用的神经网络有 9 个卷积层,这些层中的过滤器较少。完整的网络如图所示。
Network Architecture (Source)
注:
- 该架构设计用于 Pascal VOC 数据集,其中 S = 7,B = 2,C = 20。这就是为什么最终的特征图是 7×7,并且输出张量的形状也是(7×7×2 * 5+20)的原因。要将此网络用于不同数量的类或不同的格网大小,您可能需要调整图层尺寸。
- 最后一层使用线性激活函数。其余的使用泄漏的 ReLU。
培训:
- 在 ImageNet 1000 级竞争数据集上预训练前 20 个卷积层,然后是平均池层和全连接层。
- 由于检测需要更好的视觉信息,请将输入分辨率从 224 x 224 提高到 448 x 448。
- 训练网络 135 个纪元。在整个训练过程中,使用批量 64,动量 0.9,衰减 0.0005。
- 学习率:对于第一个时期,将学习率从 10–3 提高到 10–2,否则模型会由于不稳定的梯度而发散。继续以 10–2 训练 75 个周期,然后 10–3 训练 30 个周期,然后 10–4 训练 30 个周期。
- 为了避免过度拟合,请使用剔除和数据扩充。
YOLO 的局限性:
- 边界框预测的空间约束,因为每个格网单元只能预测两个框,并且只能有一个类。
- 很难检测成群出现的小物体。
- 当模型学习从数据本身预测边界框时,它很难以新的或不寻常的纵横比来概括对象。
结论:
这是对研究论文的简要解释,以及从各种其他来源获得的细节。我希望我让你更容易理解这个概念。
虽然如果真的要检查自己的理解,最好的方法还是实现算法。在下一节中,我们将会这样做。许多细节无法通过文本解释,只能在实现时理解。
感谢您的阅读。点击这里进入下一部分。
利用 YOLO 框架进行目标探测的综合指南——第二部分
使用 Python 实现
Cover Image (Source: Author)
在最后一部分,我们了解了 YOLO 是什么以及它是如何工作的。在本节中,让我们了解如何使用预先训练的权重应用它并获得结果。这篇文章受到了吴恩达深度学习专业化课程的极大启发。我还试图从各种其他文章/资源中收集信息,以使这个概念更容易理解。
现在是时候使用 Python 实现我们所理解的内容了。您可以借助 Jupyter 笔记本电脑(或您选择的任何其他 IDE)来完成这项工作。YOLO 的实现取自吴恩达的 Github 库。您还必须下载这个 zip 文件,其中包含预训练的权重和包来实现 YOLO。这里有一个链接到我的 GitHub 库,你可以在那里找到 Jupyter 笔记本。
为了更好地理解,我已经尝试对尽可能多的代码行进行注释。
导入库:
让我们首先导入所有需要的库。
import os
import imageio
import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow
import scipy.io
import scipy.misc
import numpy as np
import pandas as pd
import PIL
import tensorflow as tf
from skimage.transform import resize
from keras import backend as K
from keras.layers import Input, Lambda, Conv2D
from keras.models import load_model, Model
from yolo_utils import read_classes, read_anchors, generate_colors, preprocess_image,draw_boxes, scale_boxes
from yad2k.models.keras_yolo import yolo_head, yolo_boxes_to_corners, preprocess_true_boxes, yolo_loss, yolo_body
%matplotlib inline
应用过滤器:
首先,我们将应用阈值过滤器。我们可以通过去掉那些分数低于所选阈值的盒子来做到这一点。
该模型包含 80 个不同的检测类别。它总共给出了 19x19x5x85 个数字,其中:
19x19:网格的形状
5:锚箱数量
85:每盒包含 85 个号码(Pc,bx,by,bh,bw,c1,c2……c80)
def yolo_filter_boxes(box_confidence, boxes, box_class_probs, threshold = .6):'''
box confidence: tensor of shape (19,19,5,1) containing Pc
boxes: tensor of shape (19,19,5,4)
box_class_probs: tensor of shape (19,19,5,80)
threshold: if Pc<threshold, get rid of that box
'''
#Computing box scores
box_scores = box_confidence*box_class_probs #Finding the index of the class with maximum box score
box_classes = K.argmax(box_scores, -1) #Getting the corresponding box score
box_class_scores = K.max(box_scores,-1) #Creating a filtering mask. The mask will be true for all the boxes we intend to keep (pc >= threshold) and false for the rest
filtering_mask = box_class_scores>threshold #Applying the mask to scores, boxes and classes
scores = tf.boolean_mask(box_class_scores, filtering_mask)
boxes = tf.boolean_mask(boxes, filtering_mask)
classes = tf.boolean_mask(box_classes, filtering_mask)'''
scores: contains class probability score for the selected boxes
boxes: contains (bx,by,bh,bw) coordinates of selected boxes
classes: contains the index of class detected by the selected boxes
'''
return scores, boxes, classes
实现并集上的交集(IoU):
现在我们要实现 IoU。这将用于评估边界框。
Intersection over Union (Edited by Author)
我们将使用它的两个角(左上角和右下角)来定义一个盒子。坐标可以命名为(x1,y1,x2,y2)。
我们还必须找出两个盒子的交点坐标。
xi1: 两个框的 x1 坐标的最大值。
yi1: 两个框的 y1 坐标的最大值。
xi2: 两个框的 x2 坐标的最小值。
yi2: 两个框的 y2 坐标的最小值。
相交后形成的矩形的面积可以使用公式计算:(xi2-xi1)*(yi2-yi1)
计算借据的公式是:
(Intersection area)/(Union area)
现在让我们定义一个函数来计算 IoU。
def iou(box1, box2): #Calculating (xi1,yi1,xi2,yi2) of the intersection of box1 and box2
xi1 = max(box1[0], box2[0])
yi1 = max(box1[1], box2[1])
xi2 = min(box1[2], box2[2])
yi2 = min(box1[3], box2[3])
#Calculating the area of intersection
inter_area = (yi2-yi1)*(xi2-xi1) #Calculating the areas of box1 and box2 using the same formula
box1_area = (box1[3] - box1[1])*(box1[2] - box1[0])
box2_area = (box2[3] - box2[1])*(box2[2] - box2[0])
#Calculating the union area by using the formula: union(A,B) = A+B-Inter(A,B)
union_area = box1_area + box2_area - inter_area #Calculating iou
iou = inter_area/union_area
return iou
实施非最大抑制:
接下来,我们将实现非最大抑制来移除同一对象的所有重复边界框。涉及的步骤有:
- 选择得分最高的方框。
- 计算其与所有其他盒子的 IoU,并移除 IoU 大于所述阈值的那些盒子。
- 重复此操作,直到不再有分数低于所选框的框。
让我们来定义这个函数
def yolo_non_max_suppression(scores, boxes, classes, max_boxes = 10, iou_threshold = 0.5): #tensor used in tf.image.non_max_suppression()of size 'max_boxes'
max_boxes_tensor = K.variable(max_boxes, dtype = 'int32') #initiating the tensor
K.get_session().run(tf.variables_initializer([max_boxes_tensor])) #Using the tensorflow function tf.image.non_max_suppression to get the indices of boxes kept
nms_indices = tf.image.non_max_suppression(boxes, scores, max_boxes, iou_threshold) #Using K.gather to individually access scores, boxes and classes from nms_indices
scores = K.gather(scores, nms_indices)
boxes = K.gather(boxes, nms_indices)
classes = K.gather(classes, nms_indices)
return scores, boxes, classes
调用上面定义的函数:
现在是实现一个函数的时候了,该函数获取深度 CNN 的输出,然后使用上述函数过滤盒子。
注意,有几种方法可以表示一个边界框,即通过它们的角或中点和高度/宽度。YOLO 在几种格式之间进行转换,其中有一个名为“yolo _ boxes _ to _ corners”的函数。
此外,YOLO 还接受了 608 x 608 尺寸图像的训练。如果我们提供的图像的尺寸大于或小于原始尺寸(YOLO 在其上被训练),那么我们将不得不相应地重新缩放边界框以适合图像。为此,我们将使用一个名为“scale _ boxes”的函数。
def yolo_eval(yolo_outputs, image_shape = (720., 1280.), max_boxes = 10, score_threshold = .6, iou_threshold = .5): '''
yolo_outputs contains:
box_confidence, box_xy, box_wh, box_class_probs
''' #Retrieving output
box_confidence, box_xy, box_wh, box_class_probs = yolo_outputs #Converting the boxes for filtering functions
boxes = yolo_boxes_to_corners(box_xy, box_wh) #Using the function defined before to remove boxes with less confidence score
scores, boxes, classes = yolo_filter_boxes(box_confidence, boxes, box_class_probs, threshold = score_threshold) #Scaling the boxes
boxes = scale_boxes(boxes, image_shape) #Using the function defined before for non-max suppression
scores, boxes, classes = yolo_non_max_suppression(scores, boxes, classes, max_boxes, iou_threshold)
return scores, boxes, classes
加载预训练模型:
现在,我们将在图像上测试 YOLO 预训练模型。为此,我们必须创建一个会话。此外,请记住,我们正在尝试检测 80 个类别,并使用 5 个锚盒。我们在“coco_classes.txt”和“yolo_anchors.txt”中有所有的课程信息,这些信息必须存在于您之前在“model_data”文件夹中下载的 zip 文件中。
YOLO 模型的训练需要很长时间,尤其是如果你没有一个高规格的系统。因此,我们将加载存储在“yolo.h5”中的现有预训练的 Keras YOLO 模型。这些是 YOLOv2 模型的预训练重量。
让我们创建一个会话并加载这些文件。
sess = K.get_session()
class_names = read_classes("model_data/coco_classes.txt")
anchors = read_anchors("model_data/yolo_anchors.txt")
yolo_model = load_model("model_data/yolo.h5")
注意: 在某些情况下,加载砝码时会弹出警告。如果是这种情况,就忽略警告。
#Converting the output of model into usable bounding box tensors
yolo_outputs = yolo_head(yolo_model.output, anchors, len(class_names))
#Filtering the boxes
scores, boxes, classes = yolo_eval(yolo_outputs, image_shape)
到目前为止,我们已经创建了一个会话图,它被提供给 yolo_model 来计算输出,由 yolo_head 处理,并经过过滤函数 yolo_eval。
在图像上应用 YOLO:
现在我们必须实现一个运行图形的函数来测试图像上的 YOLO。
def predict(sess, image_file): #Preprocessing the image
image, image_data = preprocess_image("images/"+image_file, model_image_size = (608,608)) #Running the session and feeding the input to it
out_scores, out_boxes, out_classes = sess.run([scores, boxes, classes],feed_dict = {yolo_model.input: image_data, K.learning_phase(): 0}) #Prints the predicted information
print('Found {} boxes for {}'.format(len(out_boxes), image_file)) #Generates color for drawing bounding boxes
colors = generate_colors(class_names) #Draws bounding boxes on the image file
draw_boxes(image, out_scores, out_boxes, out_classes, class_names, colors) #Saving the predicted bounding box on the image
image.save(os.path.join("out", image_file), quality = 150) #Displaying the results in notebook
output_image = imageio.imread(os.path.join("out", image_file))
plt.figure(figsize=(12,12))
imshow(output_image) return out_scores, out_boxes, out_classes
在您的测试图像上运行以下单元格以查看结果。
#Loading the image
img = plt.imread('images/traffic.jpeg')#Calculating the size of image and passing it as a parameter to yolo_eval
image_shape = float(img.shape[0]),float(img.shape[1])
scores, boxes, classes = yolo_eval(yolo_outputs, image_shape)#Predicts the output
out_scores, out_boxes, out_classes = predict(sess, "traffic.jpeg")
输出是:
Output after feeding image to the model.
结论:
如果你已经走了这么远,非常感谢。请注意,如果使用相同的图像进行检测,结果可能相同,也可能不同。您可以进一步自定义每个图像的最大边界框数、阈值等。以获得更好的结果。
如果你有任何建议让这个博客更好,请在评论中提出。我会努力做出改变。
参考资料:
对象检测:简化
让我们来看看计算机视觉中最著名的问题陈述之一
什么是物体检测?
目标检测是一个常见的计算机视觉问题,它处理在图像中识别和定位某些类别的目标。可以用各种方式解释对象定位,包括在对象周围创建边界框或标记图像中包含对象的每个像素(称为分割)。
Object Detection using bounding boxes
Object Segmentation by predicting pixel-level masks
回到过去…
甚至在 CNN 在计算机视觉中大受欢迎之前,就已经研究了对象检测。虽然 CNN 能够自动提取更复杂和更好的特征,但看一眼传统方法在最坏的情况下可能是一个小弯路,在最好的情况下是一个灵感。
深度学习之前的对象检测是一个分几个步骤的过程,首先是使用 SIFT、HOG 等技术进行边缘检测和特征提取。然后将这些图像与现有的物体模板进行比较,通常是在多尺度水平上,以检测和定位图像中存在的物体。
了解指标
并集上的交集(IoU) : 不能期望边界框预测在像素级上是精确的,因此需要为 2 个边界框之间的重叠范围定义度量。
Union 上的交集确实如它所说的那样。它取所涉及的两个边界框的交集的面积,并用它们的并集的面积除之。这提供了介于 0 和 1 之间的分数,表示两个框之间的重叠质量。
**平均精确度和平均召回率:**精确度考虑我们的预测有多准确,而召回率则考虑我们是否能够检测到图像中存在的所有对象。平均精度(AP)和平均召回率(AR)是用于对象检测的两个常用度量。
两步目标检测
既然我们已经享用了这道汤,那就让我们直接进入主菜吧!!两步目标检测包括首先识别可能包含目标的边界框,然后分别对每个边界进行分类的算法。
第一步需要一个*区域提议网络,*提供多个区域,然后将这些区域传递给基于通用 DL 的分类架构。从 rcnn 中的分层分组算法(非常慢)到在快速 rcnn 中使用 CNN 和 ROI 池以及在更快 rcnn 中使用锚点(从而加速流水线和端到端训练),已经向这些区域提议网络(rpn)提供了许多不同的方法和变体。
已知这些算法比它们的一步目标检测对应物执行得更好,但是相比之下更慢。随着多年来提出的各种改进,两步目标检测网络的延迟的当前瓶颈是 RPN 步骤。关于基于 RPN 的物体检测的更多细节,你可以参考下面这个不错的博客。
物体探测领域最有影响力的论文详解
towardsdatascience.com](/object-detection-using-deep-learning-approaches-an-end-to-end-theoretical-perspective-4ca27eee8a9a)
一步目标检测
随着实时目标检测的需要,许多一步目标检测架构被提出,如 YOLO,YOLOv2,YOLOv3,SSD,RetinaNet 等。其试图将检测和分类步骤结合起来。
这些算法的主要成就之一是引入了“回归”边界框预测的思想。当每个边界框很容易用几个值(例如,xmin、xmax、ymin 和 ymax)来表示时,将检测和分类步骤结合起来就变得更容易,从而大大加快流水线的速度。
例如,YOLO 将整个图像分成更小的网格框。对于每个网格单元,它预测通过该网格单元的每个边界框的类概率和 x 和 y 坐标。有点像基于图像的验证码,你可以选择所有包含物体的小格子!!!
这些修改允许单步检测器运行得更快,且还可以在全局水平上工作。然而,由于它们不能单独作用于每个边界框,这可能导致它们在较小的对象或附近的类似对象的情况下表现更差。已经引入了多个新的体系结构来给予较低级别的特性更多的重视,从而试图提供一种平衡。
基于热图的对象检测
在某种意义上,基于热图的对象检测可以被认为是基于单镜头的对象检测的扩展。基于单镜头的对象检测算法试图直接回归边界框坐标(或偏移),而基于热图的对象检测提供边界框角/中心的概率分布。
基于热图中这些角/中心峰值的定位,预测得到的边界框。由于可以为每个类别创建不同的热图,因此该方法还结合了检测和分类。虽然基于热图的对象检测目前正在引领新的研究,但它仍然没有传统的一次性对象检测算法快。这是因为这些算法需要更复杂的主干架构(CNN)来获得可观的精度。
下一步是什么?
虽然对象检测是一个不断发展的领域,这些年来已经有了各种改进,但是这个问题显然还没有完全解决。对象检测的方法多种多样,各有利弊,人们总是可以选择最适合自己要求的方法,因此目前还没有哪种算法主宰这个领域。
这个博客是为机器学习领域创建简化介绍的努力的一部分。点击此处的完整系列
在你一头扎进去之前就知道了
towardsdatascience.com](/machine-learning-simplified-1fe22fec0fac)
或者干脆阅读系列的下一篇博客
窥探师生网络的世界
towardsdatascience.com](/knowledge-distillation-simplified-dd4973dbc764)
参考
[1]林,宗毅,等.“微软 coco:情境中的公共对象”欧洲计算机视觉会议。施普林格,查姆,2014 年。
【2】基于 HOG 的高能效硬件实现,1080HD 60 fps,多尺度支持
【3】任,等,“更快的 r-cnn:面向区域建议网络的实时对象检测”神经信息处理系统进展。2015.
【4】Redmon,Joseph 等《你只看一次:统一的,实时的物体检测》。IEEE 计算机视觉和模式识别会议录。2016.
[5]刘,魏,等.“Ssd:单次多盒探测器”欧洲计算机视觉会议。施普林格,查姆,2016。
[6]周、邢毅、贾成卓、Philipp Krahenbuhl。"通过组合极值点和中心点的自下而上的物体检测."IEEE 计算机视觉和模式识别会议录。2019.
在定制数据集上使用掩模 R-CNN 的对象检测
在这篇文章中,我们将实现掩模 R-CNN,用于从自定义数据集 中检测对象
先决条件:
计算机视觉:从 CNN 到面具 R-CC 和 YOLO 的旅程第一部分
计算机视觉:从 CNN 到面具之旅 R-CNN 和 YOLO 第二部分
数据集
屏蔽 R-CNN
Mask R-CNN 是用于实例分割的深度神经网络。该模型分为两部分
- 区域提议网络(RPN)提出候选对象包围盒。
- 二进制掩码分类器,为每个类别生成掩码
掩模 R-CNN 具有用于分类和包围盒回归的分支。它使用
- ResNet101 架构从图像中提取特征。
- 区域提议网络(RPN)生成感兴趣区域(RoI)
keras 中使用掩码 R-CNN 码的迁移学习
为此我们使用**matter port Mask R-CNN**。****
步骤 1:克隆屏蔽 R-CNN 库
git clone [https://github.com/matterport/Mask_RCNN.git](https://github.com/matterport/Mask_RCNN.git)
cd Mask_RCNN
$ python setup.py install
第二步:从matter port下载 COCO 模型的预训练权重。
将文件放在名为“mask_rcnn_coco.h5”的 Mask_RCNN 文件夹中
第三步:导入所需的库
from mrcnn.config import Config
from mrcnn import model as modellib
from mrcnn import visualize
import mrcnn
from mrcnn.utils import Dataset
from mrcnn.model import MaskRCNNimport numpy as np
from numpy import zeros
from numpy import asarray
import colorsys
import argparse
import imutils
import random
import cv2
import os
import timefrom matplotlib import pyplot
from matplotlib.patches import Rectangle
from keras.models import load_model%matplotlib inlinefrom os import listdir
from xml.etree import ElementTree
步骤 4: 我们创建一个myMaskRCNNConfig类,用于在袋鼠数据集上进行训练。它是从基础Mask R-CNN Config类派生出来的,覆盖了一些值。
***class myMaskRCNNConfig(Config):**
# give the configuration a recognizable name
**NAME = "MaskRCNN_config"**
# set the number of GPUs to use along with the number of images
# per GPU
**GPU_COUNT = 1
IMAGES_PER_GPU = 1**
# number of classes (we would normally add +1 for the background)
# kangaroo + BG
**NUM_CLASSES = 1+1**
# Number of training steps per epoch
**STEPS_PER_EPOCH = 131**
# Learning rate
**LEARNING_RATE=0.006**
# Skip detections with < 90% confidence
** DETECTION_MIN_CONFIDENCE = 0.9**
# setting Max ground truth instances
**MAX_GT_INSTANCES=10***
步骤 5:创建一个 myMaskRCNNConfig 类的实例
***config = myMaskRCNNConfig()***
让我们显示所有的配置值。
***config.display()***
第六步:建立定制的袋鼠数据集。
数据集类提供了一种一致的方式来处理任何数据集。我们将为袋鼠数据集创建新的数据集进行训练,而无需更改模型的代码。
数据集类还支持同时加载多个数据集。当您想要检测不同的对象,而它们都不在一个数据集中时,这非常有用。
在 load_dataset 方法中,我们使用 add_class 和 add_image 方法遍历 image 和 annotations 文件夹中的所有文件来添加类、图像和注释以创建数据集。
extract _ boxes方法从注释文件中提取每个边界框。注释文件是使用 pascal VOC 格式的 xml 文件。它返回盒子,它的高度和宽度
load_mask 方法为图像中的每个对象生成遮罩。它为每个实例和类 id 返回一个掩码,这是实例掩码的类 id 的 1D 数组
image _ reference方法返回图像的路径
***class KangarooDataset(Dataset)**:
# load the dataset definitions
**def load_dataset(self, dataset_dir, is_train=True)**:
# Add classes. We have only one class to add.
**self.add_class("dataset", 1, "kangaroo")**
# define data locations for images and annotations
images_dir = dataset_dir + '\\images\\'
annotations_dir = dataset_dir + '\\annots\\'
# Iterate through all files in the folder to
#add class, images and annotaions
**for filename in listdir(images_dir):**
# extract image id
image_id = filename[:-4]
# skip bad images
if image_id in ['00090']:
continue
# skip all images after 150 if we are building the train set
if is_train and int(image_id) >= 150:
continue
# skip all images before 150 if we are building the test/val set
if not is_train and int(image_id) < 150:
continue
# setting image file
**img_path = images_dir + filename**
# setting annotations file
** ann_path = annotations_dir + image_id + '.xml'**
# adding images and annotations to dataset
**self.add_image('dataset', image_id=image_id, path=img_path, annotation=ann_path)**# extract bounding boxes from an annotation file
**def extract_boxes(self, filename):**
# load and parse the file
tree = ElementTree.parse(filename)
# get the root of the document
root = tree.getroot()
** # extract each bounding box**
boxes = list()
**for box in root.findall('.//bndbox'):
xmin = int(box.find('xmin').text)
ymin = int(box.find('ymin').text)
xmax = int(box.find('xmax').text)
ymax = int(box.find('ymax').text)
coors = [xmin, ymin, xmax, ymax]
boxes.append(coors)**
# extract image dimensions
** width = int(root.find('.//size/width').text)
height = int(root.find('.//size/height').text)
return boxes, width, height**# load the masks for an image
"""Generate instance masks for an image.
Returns:
masks: A bool array of shape [height, width, instance count] with
one mask per instance.
class_ids: a 1D array of class IDs of the instance masks.
"""
**def load_mask(self, image_id):**
# get details of image
**info = self.image_info[image_id]**
# define anntation file location
**path = info['annotation']**
# load XML
**boxes, w, h = self.extract_boxes(path)**
# create one array for all masks, each on a different channel
**masks = zeros([h, w, len(boxes)], dtype='uint8')**
# create masks
class_ids = list()
**for i in range(len(boxes)):
box = boxes[i]
row_s, row_e = box[1], box[3]
col_s, col_e = box[0], box[2]
masks[row_s:row_e, col_s:col_e, i] = 1
class_ids.append(self.class_names.index('kangaroo'))
return masks, asarray(class_ids, dtype='int32')**# load an image reference
"""Return the path of the image."""
**def image_reference(self, image_id):**
**info = self.image_info[image_id]**
print(info)
**return info['path']***
第七步:准备列车和测试装置
*# prepare train set
**train_set = KangarooDataset()
train_set.load_dataset(‘..\\Kangaroo\\kangaroo-master\\kangaroo-master’, is_train=True)
train_set.prepare()**
print(‘Train: %d’ % len(train_set.image_ids))# prepare test/val set
**test_set = KangarooDataset()
test_set.load_dataset(‘..\\Kangaroo\\kangaroo-master\\kangaroo-master’, is_train=False)
test_set.prepare()**
print(‘Test: %d’ % len(test_set.image_ids))*
步骤 8:使用我们创建的配置实例初始化“训练”的屏蔽 R-CNN 模型
*print("Loading Mask R-CNN model...")
**model = modellib.MaskRCNN(mode="training", config=config, model_dir='./')***
步骤 9:从 COCO 数据集中加载掩模 R-CNN 的预训练权重,不包括最后几层
我们从 ResNet101 的训练中排除了最后几层。排除最后的层是为了匹配新数据集中的类的数量。
*#load the weights for COCO
model.load_weights('.\\Mask_RCNN\\mask_rcnn_coco.h5',
by_name=True,
exclude=["mrcnn_class_logits", "mrcnn_bbox_fc", "mrcnn_bbox", "mrcnn_mask"])*
第十步:训练学习率较高的头部,加快学习速度
我们可以通过提高学习速率来提高头层的学习速度
此外,我们可以将历元增加到 100–500 之间的任何值,并查看对象检测准确性的差异。我只用了 5 个纪元,因为我在 CPU 上训练它。
*## train heads with higher lr to speedup the learning
model.train(train_set, test_set, learning_rate=2*config.LEARNING_RATE, epochs=5, layers=’heads’)history = model.keras_model.history.history*
步骤 11:保存自定义数据集的训练权重
*import time**model_path = '..\\Kangaroo\\kangaroo-master\\kangaroo-master\\mask_rcnn_' + '.' + str(time.time()) + '.h5'****model.keras_model.save_weights(model_path)***
步骤 12:利用来自训练模型的遮罩和包围盒检测图像中的对象
在推理模式下创建模型。从我们训练模型的数据集中加载模型的权重。
加载我们想要检测的图像的包围盒、类别和置信度百分比
*from keras.preprocessing.image import load_img
from keras.preprocessing.image import img_to_array#Loading the model in the inference mode
**model = modellib.MaskRCNN(mode="inference", config=config, model_dir='./')**# loading the trained weights o the custom dataset
**model.load_weights(model_path, by_name=True)**img = load_img("..\\Kangaroo\\kangaroo-master\\kangaroo-master\\images\\00042.jpg")
img = img_to_array(img)# detecting objects in the image
**result= model.detect([img])***
最后显示结果
*image_id = 20
**image, image_meta, gt_class_id, gt_bbox, gt_mask = modellib.load_image_gt(test_set, config, image_id, use_mini_mask=False)****info = test_set.image_info[image_id]**
print("image ID: {}.{} ({}) {}".format(info["source"], info["id"], image_id,
**test_set.image_reference(image_id)))**# Run object detection
**results = model.detect([image], verbose=1)**# Display results
**r = results[0]**
**visualize.display_instances(image, r['rois'], r['masks'], r['class_ids'],
test_set.class_names, r['scores'],
title="Predictions")***
参考资料:
这是 Mask R-CNN 在 Python 3、Keras 和 TensorFlow 上的实现。该模型生成边界框和…
github.com](https://github.com/matterport/Mask_RCNN/)
[## 色彩的飞溅:使用掩膜 R-CNN 和张量流的实例分割
通过构建彩色飞溅滤镜来解释
engineering.matterport.com](https://engineering.matterport.com/splash-of-color-instance-segmentation-with-mask-r-cnn-and-tensorflow-7c761e238b46)
https://machine learning mastery . com/how-to-perform-object-detection-with-yolov 3-in-keras/***
使用 Keras 的 YOLOv3 对象检测
第一部分——CNN,R-CNN,快 R-CNN,更快 R-CNN
这是本系列的第三篇文章,我们将使用 YOLOv3 预测边界框和类。代码可用github
output from YOLO v3
你只需要看一次(YOLO)图像,就可以使用一个卷积网络来预测什么物体存在以及它们在哪里。YOLO 预测多个边界框和这些框的类别概率。
这段代码将使用 yolo v3 中预先训练的权重,然后使用 keras 库预测边界框和类概率
该代码受到 experiencor 的 keras-yolo3 projec 的强烈启发,用于使用 YOLOv3 模型执行对象检测。代码是将代码分解成简单的步骤,使用 yolov3 模型来预测边界框和类。原始代码可从 Huynh Ngoc Anh 的 github 获得。 Yolo3 预训练权重可从 YOLOv3 预训练权重 下载。
YOLOv3 模型使用预先训练的权重来解决标准对象检测问题,如袋鼠数据集、浣熊数据集、红细胞检测等。该模型将用于新图像上的对象检测。
**第一步:**导入所需的库
import os
import scipy.io
import scipy.misc
import numpy as np
import pandas as pd
import PIL
import struct
import cv2
from numpy import expand_dimsimport tensorflow as tf
from skimage.transform import resize
from keras import backend as K
from keras.layers import Input, Lambda, Conv2D, BatchNormalization, LeakyReLU, ZeroPadding2D, UpSampling2Dfrom keras.models import load_model, Model
from keras.layers.merge import add, concatenate
from keras.preprocessing.image import load_img
from keras.preprocessing.image import img_to_arrayimport matplotlib.pyplot as plt
from matplotlib.pyplot import imshow
from matplotlib.patches import Rectangle
%matplotlib inline
第二步:创建一个类 WeightReader 来加载 yolov3 的预训练权重
WeightReader 类将解析该文件并将模型权重加载到内存中,以在我们的 Keras 模型中设置它。
# class to load the pretrained Weights
**class WeightReader:**
**def __init__(self, weight_file)**:
with open(weight_file, 'rb') as w_f:
major, = struct.unpack('i', w_f.read(4))
minor, = struct.unpack('i', w_f.read(4))
revision, = struct.unpack('i', w_f.read(4))if (major*10 + minor) >= 2 and major < 1000 and minor < 1000:
w_f.read(8)
else:
w_f.read(4)transpose = (major > 1000) or (minor > 1000)
binary = w_f.read()self.offset = 0
self.all_weights = np.frombuffer(binary, dtype='float32')
**def read_bytes(self, size)**:
self.offset = self.offset + size
return self.all_weights[self.offset-size:self.offset]**def load_weights(self, model):**
for i in range(106):
try:
conv_layer = model.get_layer('conv_' + str(i))
print("loading weights of convolution #" + str(i))if i not in [81, 93, 105]:
norm_layer = model.get_layer('bnorm_' + str(i))size = np.prod(norm_layer.get_weights()[0].shape)beta = self.read_bytes(size) # bias
gamma = self.read_bytes(size) # scale
mean = self.read_bytes(size) # mean
var = self.read_bytes(size) # varianceweights = norm_layer.set_weights([gamma, beta, mean, var])if len(conv_layer.get_weights()) > 1:
bias = self.read_bytes(np.prod(conv_layer.get_weights()[1].shape))
kernel = self.read_bytes(np.prod(conv_layer.get_weights()[0].shape))
kernel = kernel.reshape(list(reversed(conv_layer.get_weights()[0].shape)))
kernel = kernel.transpose([2,3,1,0])
conv_layer.set_weights([kernel, bias])
else:
kernel = self.read_bytes(np.prod(conv_layer.get_weights()[0].shape))
kernel = kernel.reshape(list(reversed(conv_layer.get_weights()[0].shape)))
kernel = kernel.transpose([2,3,1,0])
conv_layer.set_weights([kernel])
except ValueError:
print("no convolution #" + str(i))
def reset(self):
self.offset = 0
第三步:创建 Yolo v3 模型。
我们首先创建一个函数来创建卷积块
**def _conv_block(inp, convs, skip=True):**
x = inp
count = 0
for conv in convs:
if count == (len(convs) - 2) and skip:
skip_connection = x
count += 1
if conv['stride'] > 1: x = ZeroPadding2D(((1,0),(1,0)))(x) # peculiar padding as darknet prefer left and top
x = Conv2D(conv['filter'],
conv['kernel'],
strides=conv['stride'],
padding='valid' if conv['stride'] > 1 else 'same', # peculiar padding as darknet prefer left and top
name='conv_' + str(conv['layer_idx']),
use_bias=False if conv['bnorm'] else True)(x)
if conv['bnorm']: x = BatchNormalization(epsilon=0.001, name='bnorm_' + str(conv['layer_idx']))(x)
if conv['leaky']: x = LeakyReLU(alpha=0.1, name='leaky_' + str(conv['layer_idx']))(x)return add([skip_connection, x]) if skip else x
接下来,我们创建一个有 108 个卷积层的暗网。我在 CPU 上运行它,在 GPU 上它几乎快了 500 倍
**# creating the YOLO model**
def make_yolov3_model():
input_image = Input(shape=(None, None, 3))# Layer 0 => 4
x = _conv_block(input_image, [{'filter': 32, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 0},
{'filter': 64, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 1},
{'filter': 32, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 2},
{'filter': 64, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 3}])# Layer 5 => 8
x = _conv_block(x, [{'filter': 128, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 5},
{'filter': 64, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 6},
{'filter': 128, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 7}])# Layer 9 => 11
x = _conv_block(x, [{'filter': 64, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 9},
{'filter': 128, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 10}])# Layer 12 => 15
x = _conv_block(x, [{'filter': 256, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 12},
{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 13},
{'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 14}])# Layer 16 => 36
for i in range(7):
x = _conv_block(x, [{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 16+i*3},
{'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 17+i*3}])
skip_36 = x
# Layer 37 => 40
x = _conv_block(x, [{'filter': 512, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 37},
{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 38},
{'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 39}])# Layer 41 => 61
for i in range(7):
x = _conv_block(x, [{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 41+i*3},
{'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 42+i*3}])
skip_61 = x
# Layer 62 => 65
x = _conv_block(x, [{'filter': 1024, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 62},
{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 63},
{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 64}])# Layer 66 => 74
for i in range(3):
x = _conv_block(x, [{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 66+i*3},
{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 67+i*3}])
# Layer 75 => 79
x = _conv_block(x, [{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 75},
{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 76},
{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 77},
{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 78},
{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 79}], skip=False)# Layer 80 => 82
yolo_82 = _conv_block(x, [{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 80},
{'filter': 255, 'kernel': 1, 'stride': 1, 'bnorm': False, 'leaky': False, 'layer_idx': 81}], skip=False)# Layer 83 => 86
x = _conv_block(x, [{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 84}], skip=False)
x = UpSampling2D(2)(x)
x = concatenate([x, skip_61])# Layer 87 => 91
x = _conv_block(x, [{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 87},
{'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 88},
{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 89},
{'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 90},
{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 91}], skip=False)# Layer 92 => 94
yolo_94 = _conv_block(x, [{'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 92},
{'filter': 255, 'kernel': 1, 'stride': 1, 'bnorm': False, 'leaky': False, 'layer_idx': 93}], skip=False)# Layer 95 => 98
x = _conv_block(x, [{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 96}], skip=False)
x = UpSampling2D(2)(x)
x = concatenate([x, skip_36])# Layer 99 => 106
yolo_106 = _conv_block(x, [{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 99},
{'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 100},
{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 101},
{'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 102},
{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 103},
{'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 104},
{'filter': 255, 'kernel': 1, 'stride': 1, 'bnorm': False, 'leaky': False, 'layer_idx': 105}], skip=False)model = Model(input_image, [yolo_82, yolo_94, yolo_106])
return model
步骤 4:我们现在创建 yolo 模型并加载预训练的权重
**# create the yolo v3**
yolov3 = make_yolov3_model()**# load the weights trained on COCO into the model**weight_reader = WeightReader(‘yolov3.weights’)
weight_reader.load_weights(yolov3)
第五步:设置变量。
Yolov3 的输入图像大小是 416 x 416,我们使用 net_h 和 net_w 来设置。
对象阈值设定为 0.5,非最大抑制阈值设定为 0.45
我们设置锚框,然后为上下文中的公共对象(COCO)模型定义 80 个标签以进行预测
**net_h, net_w = 416, 416****obj_thresh, nms_thresh = 0.5, 0.45****anchors** = [[116,90, 156,198, 373,326], [30,61, 62,45, 59,119], [10,13, 16,30, 33,23]]**labels** = [“person”, “bicycle”, “car”, “motorbike”, “aeroplane”, “bus”, “train”, “truck”, “boat”, “traffic light”, “fire hydrant”, “stop sign”, “parking meter”, “bench”, “bird”, “cat”, “dog”, “horse”, “sheep”, “cow”, “elephant”, “bear”, “zebra”, “giraffe”, \
“backpack”, “umbrella”, “handbag”, “tie”, “suitcase”, “frisbee”, “skis”, “snowboard”, “sports ball”, “kite”, “baseball bat”, “baseball glove”, “skateboard”, “surfboard”, “tennis racket”, “bottle”, “wine glass”, “cup”, “fork”, “knife”, “spoon”, “bowl”, “banana”, “apple”, “sandwich”, “orange”, “broccoli”, “carrot”, “hot dog”, “pizza”, “donut”, “cake”, “chair”, “sofa”, “pottedplant”, “bed”, “diningtable”, “toilet”, “tvmonitor”, “laptop”, “mouse”, \
“remote”, “keyboard”, “cell phone”, “microwave”, “oven”, “toaster”, “sink”, “refrigerator”, “book”, “clock”, “vase”, “scissors”, “teddy bear”, “hair drier”, “toothbrush”]
第六步:将图像加载到右边 416 x 416 的输入形状
from numpy import expand_dimsdef **load_image_pixels(filename, shape)**:
# load the image to get its shape
image = load_img(filename)
width, height = image.size
# load the image with the required size
image = load_img(filename, target_size=shape) # convert to numpy array
image = img_to_array(image)
# scale pixel values to [0, 1]
image = image.astype(‘float32’)
image /= 255.0
# add a dimension so that we have one sample
image = expand_dims(image, 0)
**return image, width, height**
步骤 7:为边界框创建一个类。
BoundBox 在输入图像形状和类别概率的上下文中定义每个边界框的角。
**class BoundBox:**
**def __init__**(self, xmin, ymin, xmax, ymax, objness = None, classes = None):
self.xmin = xmin
self.ymin = ymin
self.xmax = xmax
self.ymax = ymax
self.objness = objness
self.classes = classesself.label = -1
self.score = -1**def get_label(self)**:
if self.label == -1:
self.label = np.argmax(self.classes)
return self.label
**def get_score(self)**:
if self.score == -1:
self.score = self.classes[self.get_label()]
return self.score
步骤 8:为定义函数
- 间隔重叠-检查两个间隔是否重叠。当一个间隔在另一个间隔开始之前结束时,两个间隔不重叠。
- 两个盒子的并集交集(IoU)
- 非最大抑制,将包含对象的框以及非最大阈值作为参数
- Sigmoid 函数
**def _sigmoid(x)**:
return 1\. / (1\. + np.exp(-x))**def _interval_overlap(interval_a, interval_b**):
x1, x2 = interval_a
x3, x4 = interval_bif x3 < x1:
if x4 < x1:
return 0
else:
return min(x2,x4) — x1
else:
if x2 < x3:
return 0
else:
return min(x2,x4) — x3
**def bbox_iou(box1, box2**):
intersect_w = _interval_overlap([box1.xmin, box1.xmax], [box2.xmin, box2.xmax])
intersect_h = _interval_overlap([box1.ymin, box1.ymax], [box2.ymin, box2.ymax])
intersect = intersect_w * intersect_hw1, h1 = box1.xmax-box1.xmin, box1.ymax-box1.ymin
w2, h2 = box2.xmax-box2.xmin, box2.ymax-box2.ymin
union = w1*h1 + w2*h2 — intersect
return float(intersect) / union**def do_nms(boxes, nms_thresh)**:
if len(boxes) > 0:
nb_class = len(boxes[0].classes)
else:
return
for c in range(nb_class):
sorted_indices = np.argsort([-box.classes[c] for box in boxes])for i in range(len(sorted_indices)):
index_i = sorted_indices[i]if boxes[index_i].classes[c] == 0: continuefor j in range(i+1, len(sorted_indices)):
index_j = sorted_indices[j]if bbox_iou(boxes[index_i], boxes[index_j]) >= nms_thresh:
boxes[index_j].classes[c] = 0
第九步:解码网络的输出。
我们将遍历 NumPy 数组中的每一个,一次一个,并基于对象阈值解码候选边界框和类预测。
前 4 个元素将是边界框的坐标,第 5 个元素将是对象分数,后跟类别概率
**def decode_netout(netout, anchors, obj_thresh, net_h, net_w):**
grid_h, grid_w = netout.shape[:2]
nb_box = 3
netout = netout.reshape((grid_h, grid_w, nb_box, -1))
nb_class = netout.shape[-1] — 5boxes = [] netout[…, :2] = _sigmoid(netout[…, :2])
netout[…, 4:] = _sigmoid(netout[…, 4:])
netout[…, 5:] = netout[…, 4][…, np.newaxis] * netout[…, 5:]
netout[…, 5:] *= netout[…, 5:] > obj_threshfor i in range(grid_h*grid_w):
row = i / grid_w
col = i % grid_w
for b in range(nb_box):
# 4th element is objectness score
objectness = netout[int(row)][int(col)][b][4]
#objectness = netout[…, :4]
if(objectness.all() <= obj_thresh): continue
# first 4 elements are x, y, w, and h
x, y, w, h = netout[int(row)][int(col)][b][:4]x = (col + x) / grid_w # center position, unit: image width
y = (row + y) / grid_h # center position, unit: image height
w = anchors[2 * b + 0] * np.exp(w) / net_w # unit: image width
h = anchors[2 * b + 1] * np.exp(h) / net_h # unit: image height
# last elements are class probabilities
classes = netout[int(row)][col][b][5:]
**box = BoundBox(x-w/2, y-h/2, x+w/2, y+h/2, objectness, classes)** boxes.append(box)**return boxes**
步骤 10:纠正 Yolo 框。
我们有边界框,但它们需要被拉伸回原始图像的形状。这将允许绘制原始图像和边界框,检测真实对象。
**def correct_yolo_boxes(boxes, image_h, image_w, net_h, net_w):**
if (float(net_w)/image_w) < (float(net_h)/image_h):
new_w = net_w
new_h = (image_h*net_w)/image_w
else:
new_h = net_w
new_w = (image_w*net_h)/image_h
for i in range(len(boxes)):
x_offset, x_scale = (net_w — new_w)/2./net_w, float(new_w)/net_w
y_offset, y_scale = (net_h — new_h)/2./net_h, float(new_h)/net_h
boxes[i].xmin = int((boxes[i].xmin — x_offset) / x_scale * image_w)
boxes[i].xmax = int((boxes[i].xmax — x_offset) / x_scale * image_w)
boxes[i].ymin = int((boxes[i].ymin — y_offset) / y_scale * image_h)
boxes[i].ymax = int((boxes[i].ymax — y_offset) / y_scale * image_h)
步骤 11:获取所有高于指定阈值的盒子。
get_boxes 函数将盒子、标签和阈值的列表作为参数,并返回盒子、标签和分数的并行列表。
**def get_boxes(boxes, labels, thresh):**
v_boxes, v_labels, v_scores = list(), list(), list()
# enumerate all boxes
for box in boxes:
# enumerate all possible labels
for i in range(len(labels)):
# check if the threshold for this label is high enough
if box.classes[i] > thresh:
v_boxes.append(box)
v_labels.append(labels[i])
v_scores.append(box.classes[i]*100)
# don't break, many labels may trigger for one box
return v_boxes, v_labels, v_scores
步骤 12:在图像中的物体周围画一个白框。
from matplotlib.patches import Rectangle**def draw_boxes(filename, v_boxes, v_labels, v_scores):**
# load the image
data = plt.imread(filename)
# plot the image
plt.imshow(data)
# get the context for drawing boxes
ax = plt.gca()
# plot each box
for i in range(len(v_boxes)):box = v_boxes[i]
# get coordinates
y1, x1, y2, x2 = box.ymin, box.xmin, box.ymax, box.xmax
# calculate width and height of the box
width, height = x2 - x1, y2 - y1
# create the shape
rect = Rectangle((x1, y1), width, height, fill=False, color='red')
# draw the box
ax.add_patch(rect)
# draw text and score in top left corner
label = "%s (%.3f)" % (v_labels[i], v_scores[i])
plt.text(x1, y1, label, color='red')
# show the plot
plt.show()
最后,我们结合代码对新图像进行预测。
首先我们使用***load _ image _ pixels()***函数将图像加载到 416 x 416 的输入形状中。
预测盒子起诉 yolov 3**T23 预测()**方法
Yolov3 模型将预测同一对象的多个框。
然后,我们使用***decode _ netout()***函数基于对象阈值对候选包围盒和类别预测进行解码
我们通过使用***correct _ yolo _ boxes()***函数来校正边界框,以将其拉伸回原始图像的形状
边界框将根据 IoU 定义的重叠进行过滤,然后使用 do_nms() 函数应用非最大值抑制。
我们使用 get_boxes() 得到高于指定类阈值 0.6 的盒子,然后使用 draw_boxes() 函数在图像上绘制这些盒子。
# define our new image
photo_filename = 'eagle.png'# load and prepare image
image, image_w, image_h = **load_image_pixels**(photo_filename, (net_w, net_w))# make prediction
**yolos = yolov3.predict(image)**# summarize the shape of the list of arrays
print([a.shape for a in yolos])# define the anchors
anchors = [[116,90, 156,198, 373,326], [30,61, 62,45, 59,119], [10,13, 16,30, 33,23]]# define the probability threshold for detected objects
class_threshold = 0.6boxes = list()for i in range(len(yolos)):
# decode the output of the network
boxes += ***decode_netout***(yolos[i][0], anchors[i], obj_thresh, nms_thresh, net_h, net_w)# correct the sizes of the bounding boxes
**correct_yolo_boxes(boxes, image_h, image_w, net_h, net_w)**# suppress non-maximal boxes
**do_nms**(boxes, nms_thresh)# get the details of the detected objects
v_boxes, v_labels, v_scores = **get_boxes**(boxes, labels, class_threshold)
# summarize what we found
for i in range(len(v_boxes)):
print(v_labels[i], v_scores[i])
# draw what we found
**draw_boxes**(photo_filename, v_boxes, v_labels, v_scores)
原象
绘制边界框和类后的图像
代码可从 Github 获得
参考资料:
YOLO V3 论文作者约瑟夫·雷德蒙、阿里·法尔哈迪
https://pjreddie.com/darknet/yolo/
从 https://pjreddie.com/media/files/yolov3.weights. python 中抓取 yolo3 的预训练重量…
github.com](https://github.com/experiencor/keras-yolo3) [## 如何在 Keras 中使用 YOLOv3 执行对象检测
目标检测是计算机视觉中的一项任务,涉及识别一个或多个目标的存在、位置和类型
machinelearningmastery.com](https://machinelearningmastery.com/how-to-perform-object-detection-with-yolov3-in-keras/)
使用 python 通过基于颜色的图像分割进行目标检测
使用 python 和 OpenCV 绘制轮廓的教程。
Photo by rawpixel.com from Pexels
入门指南
如果你已经安装了 jupyter notebook 或者可以运行 python & OpenCV 的 IDE,只需跳到执行。
工具
我们今天的英雄是蟒蛇。一个免费的开源发行版帮助安装不同的软件包&把它们的混乱整理到隔离的环境中。
关于蟒蛇,维基百科的告诉了我们什么
Anaconda 是用于科学计算(数据科学、机器学习应用、大规模数据处理、预测分析等)的 Python 和 R 编程语言的免费开源发行版。),旨在简化包管理和部署。包版本由包管理系统 conda 管理。Anaconda 发行版有 1200 多万用户使用,它包含 1400 多个适用于 Windows、Linux 和 MacOS 的流行数据科学包。
下面是关于如何下载 Anaconda 的详细教程。
anaconda for Windows&anaconda for Linux。
创造环境
打开 bash (cmd)并键入以下内容
$ conda create -n myEnv python=3
当提示下载软件包时,键入 y (表示是)。
$ source activate myEnv
$ conda install anaconda
$ conda activate myEnv
$ conda install opencv$ jupyter notebook
这将为您在浏览器中打开 jupyter 笔记本。
一些重要术语
轮廓
轮廓可以简单地解释为连接所有连续点(连同边界)的曲线,具有相同的颜色或强度。轮廓是形状分析和物体检测与识别的有用工具。
阈值
对灰度图像应用阈值处理使其成为二值图像。您可以设置一个阈值,低于该阈值的所有值都变成黑色,高于该阈值的所有值都变成白色。
执行
现在,您已经拥有了开始工作所需的一切。
我们将从一个简单的例子开始,向您展示基于颜色的分割是如何工作的。
耐心听我说,直到我们找到好东西。
An Ombre circle — image made using photoshop
如果你想和我一起试试这个,你可以从这里免费得到这张图片。在下面的代码中,我将把这张图片分割成 17 个灰度级。然后用等高线测量每一层的面积。
import cv2
import numpy as npdef viewImage(image):
cv2.namedWindow('Display', cv2.WINDOW_NORMAL)
cv2.imshow('Display', image)
cv2.waitKey(0)
cv2.destroyAllWindows()def grayscale_17_levels (image):
high = 255
while(1):
low = high - 15
col_to_be_changed_low = np.array([low])
col_to_be_changed_high = np.array([high])
curr_mask = cv2.inRange(gray, col_to_be_changed_low,col_to_be_changed_high)
gray[curr_mask > 0] = (high)
high -= 15
if(low == 0 ):
breakimage = cv2.imread('./path/to/image')
viewImage(image)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
grayscale_17_levels(gray)
viewImage(gray)
The same image segmented into 17 gray levels
def get_area_of_each_gray_level(im):## convert image to gray scale (must br done before contouring)
image = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
output = []
high = 255
first = True
while(1):low = high - 15
if(first == False):# making values that are of a greater gray level black
## so it won't get detected
to_be_black_again_low = np.array([high])
to_be_black_again_high = np.array([255])
curr_mask = cv2.inRange(image, to_be_black_again_low,
to_be_black_again_high)
image[curr_mask > 0] = (0)
# making values of this gray level white so we can calculate
# it's area
ret, threshold = cv2.threshold(image, low, 255, 0)
contours, hirerchy = cv2.findContours(threshold,
cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)if(len(contours) > 0):output.append([cv2.contourArea(contours[0])])
cv2.drawContours(im, contours, -1, (0,0,255), 3)high -= 15
first = False
if(low == 0 ):breakreturn output
在这个函数中,我简单地转换了我想要在这个迭代中轮廓化(突出显示)的灰度范围,将所有在这个范围内的灰度统一为一个灰度。我把除了这个范围之外的所有强度都变成黑色(包括更大和更小的强度)。第二步,我对图像进行阈值处理,这样现在只有我想要轮廓的颜色显示为白色,其他颜色都转换为黑色。这一步在这里变化不大,但必须完成,因为轮廓绘制在黑白(阈值)图像上效果最好。
在应用该步骤(阈值处理)之前,下图将是相同的,除了白色环将是灰色的(第 10 个灰度级的灰度(255–15 * 10))
The 10th segment appear alone to be able to calculate its area
image = cv2.imread('./path/to/image')
print(get_area_of_each_gray_level(image))
viewImage(image)
Contours of the 17 gray levels onto the original image
Array containing the value of the areas
这样我们就获得了每个灰度级的面积。
这真的很重要吗?
在我们继续之前,我想强调一下这个话题的重要性。
我是一名计算机工程专业的学生,我正在从事一个名为用于智能肿瘤检测和识别的机器学习项目。
本项目使用基于颜色的图像分割来帮助计算机学习如何检测肿瘤。当处理 MRI 扫描时,程序必须检测所述 MRI 扫描的癌症水平。它通过将扫描分为不同的灰度级来实现,其中最暗的部分充满癌细胞,最接近白色的部分是健康的部分。然后计算肿瘤对每个灰度级的隶属度。有了这些信息,程序就能够识别肿瘤及其阶段。
这个项目基于软计算、模糊逻辑&机器学习,你可以在模糊逻辑上了解更多信息,以及它是如何治愈癌症的。
目标检测
你可以从这里的像素上免费获得这张图片。你只需要修剪它。
在这个图像中,我们只想描绘叶子的轮廓。因为这张图像的纹理非常不规则和不均匀,这意味着虽然没有太多的颜色。这幅图像中绿色的强度和亮度都会发生变化。所以,这里最好的办法是把所有这些不同色调的绿色统一成一种色调。这样,当我们应用轮廓时,它将把叶子作为一个整体对象来处理。
注意:这是在没有任何预处理的情况下对图像应用轮廓的结果。我只是想让你看看叶子的不均匀性是如何让 OpenCV 不明白这只是一个对象。
Contouring without pre-processing, 531 contours detected
import cv2
import numpy as npdef viewImage(image):
cv2.namedWindow('Display', cv2.WINDOW_NORMAL)
cv2.imshow('Display', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
首先,你必须知道你的颜色的 HSV 表示,你可以通过把它的 RGB 转换成 HSV 来知道它,就像下面这样。
## getting green HSV color representation
green = np.uint8([[[0, 255, 0 ]]])
green_hsv = cv2.cvtColor(green,cv2.COLOR_BGR2HSV)
print( green_hsv)
Green HSV color
将图像转换为 HSV :使用 HSV 更容易获得一种颜色的完整范围。HSV,H 代表色调,S 代表饱和度,V 代表数值。我们已经知道绿色是[60,255,255]。世界上所有的果岭都位于[45,100,50]到[75,255,255]之间,即[60-15,100,50]到[60+ 15 ,255,255]。15 只是一个近似值。
我们将此范围转换为[75,255, 200 ]或任何其他浅色(第三个值必须相对较大),因为这是颜色的亮度,当我们对图像进行阈值处理时,该值将使该部分为白色。
image = cv2.imread('./path/to/image.jpg')
hsv_img = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
viewImage(hsv_img) ## 1green_low = np.array([45 , 100, 50] )
green_high = np.array([75, 255, 255])
curr_mask = cv2.inRange(hsv_img, green_low, green_high)
hsv_img[curr_mask > 0] = ([75,255,200])
viewImage(hsv_img) ## 2## converting the HSV image to Gray inorder to be able to apply
## contouring
RGB_again = cv2.cvtColor(hsv_img, cv2.COLOR_HSV2RGB)
gray = cv2.cvtColor(RGB_again, cv2.COLOR_RGB2GRAY)
viewImage(gray) ## 3ret, threshold = cv2.threshold(gray, 90, 255, 0)
viewImage(threshold) ## 4contours, hierarchy = cv2.findContours(threshold,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(image, contours, -1, (0, 0, 255), 3)
viewImage(image) ## 5
Left: Image just after conversion to HSV (1). Right: Image after applying the mask (color unification)(2)
Left: Image after conversion from HSV to gray(3), Right: Threshold Image, final step(4)
Final contour(5)
由于背景中似乎也有不规则性,我们可以用这种方法得到最大的轮廓,最大的轮廓当然是叶子。
我们可以得到轮廓数组中叶子轮廓的索引,从中我们可以得到叶子的面积和中心。
轮廓有许多其他可以利用的特征,如轮廓周长、凸包、边界矩形等。你可以从这里了解更多。
def findGreatesContour(contours):
largest_area = 0
largest_contour_index = -1
i = 0
total_contours = len(contours)
while (i < total_contours ):
area = cv2.contourArea(contours[i])
if(area > largest_area):
largest_area = area
largest_contour_index = i
i+=1
return largest_area, largest_contour_index# to get the center of the contour
cnt = contours[13]
M = cv2.moments(cnt)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])largest_area, largest_contour_index = findGreatesContour(contours)print(largest_area)
print(largest_contour_index)print(len(contours))print(cX)
print(cY)
The result of the print statements
使用 Python 实现不到 10 行代码的对象检测
找出图像中的对象
Home Office (Image by LEEROY Agency from Pixabay)
什么要知道图像中有哪些对象?
或者您可能想计算图像中苹果的数量?
在这篇文章中,我将展示如何用不到 10 行代码用 Python 创建自己的对象检测程序。
如果尚未安装以下 python 库,则需要进行安装:
opencv-python
cvlib
matplotlib
tensorflow
以下代码用于导入所需的 python 库,从存储中读取图像,对图像执行对象检测,并显示带有检测到的对象的边界框和标签的图像。
import cv2
import matplotlib.pyplot as plt
import cvlib as cv
from cvlib.object_detection import draw_bboxim = cv2.imread('apple-256261_640.jpg')bbox, label, conf = cv.detect_common_objects(im)output_image = draw_bbox(im, bbox, label, conf)plt.imshow(output_image)
plt.show()
下面是使用上述代码进行对象检测的一些结果。
(Left) Orignal image of an apple on top of some books (Image by Michal Jarmoluk from Pixabay), (Right) Object detection on original image
(Left) Orignal image of apples and bananas (Image by Michal_o79 from Pixabay), (Right) Object detection on original image
(Left) Orignal image of apples, bananas and oranges (Image by Jose Luis Montesino from Pixabay), (Right) Object detection on original image
你已经准备好了你的物体探测程序。
觉得这个帖子有帮助? 在下面留下你的想法作为评论。
希望实现人脸检测。查看我在 上的帖子如何使用 python 在不到 3 分钟的时间内实现人脸检测。
点击这里 阅读我其他关于 AI/机器学习的帖子。
要了解更多关于 cvlib 库的信息,可以访问下面的链接。
[## cvlib
用于 Python 的高级易用开源计算机视觉库。它的开发重点是实现简单的…
www.cvlib.net](https://www.cvlib.net/)
要了解使用该库可以检测到的所有物体,您可以访问下面的链接。
[## arunponnusamy/对象检测-opencv
此时您不能执行该操作。您已使用另一个标签页或窗口登录。您已在另一个选项卡中注销,或者…
github.com](https://github.com/arunponnusamy/object-detection-opencv/blob/master/yolov3.txt)
这里有一些进一步的阅读材料,有助于理解物体探测是如何工作的:
物体探测领域最有影响力的论文详解
towardsdatascience.com](/object-detection-using-deep-learning-approaches-an-end-to-end-theoretical-perspective-4ca27eee8a9a) [## 基本对象检测算法的逐步介绍(第 1 部分)
你花了多少时间在一个又脏又乱的房子里寻找丢失的房间钥匙?它发生在我们最好的人身上,而且…
www.analyticsvidhya.com](https://www.analyticsvidhya.com/blog/2018/10/a-step-by-step-introduction-to-the-basic-object-detection-algorithms-part-1/)
YOLO 物体检测|为自动驾驶汽车带来视觉
解释和使用 YOLO(你只看一次)计算机视觉算法的对象检测
编辑:写完这篇文章后不久,我制作了一个 YouTube 视频(VSauce Parody)来解释 YOLO。请随意查看!
我们都认为我们的愿景是理所当然的。它是数亿年进化的产物,从一片感光细胞进化成一种被称为人眼的复杂光学奇观。
纵观人类的生存历程,我们的生存依赖于我们理解和解释环境的能力。在我们漫游非洲平原的时候,原始人类需要立即识别威胁并做出反应。
即使在今天,这种情况仍然存在。
看到我们生活的世界的能力让克里斯托弗·哥伦布得以环游世界,让莱昂纳多·达·芬奇得以画出蒙娜丽莎,让小蒂米得以骑独轮车。干得好,提米。
自从智人出现以来,人类拥有了最好的视觉。然而,这种情况不会持续太久。因为人类很懒。最初人类认为:
“嘿,走路太累了,让我们驯养这些大的四足动物,然后骑着它们到处走.”
然后我们想:
“喂,养这些马太费事了,让我们造这些大四轮车到处跑吧”
现在我们认为:
“嘿,驾驶这些汽车太费力了,让我们建立复杂的人工智能算法来为我们做这件事”
于是自动驾驶汽车诞生了——诞生于我们甚至连开车都懒得做的懒惰。
**但是我们到底是如何制造出一辆能够自动驾驶的汽车的呢?**仅仅将一台带有摄像头的电脑连接到汽车上是不够的,因为电脑看图像的方式与我们不同。看一看:
对我们来说,我们看到精致的形状和边缘,我们的大脑可以拼凑出一张脸。但是对一台计算机来说,他们看到的是一组多多 的数字。
这么长时间以来,人类一直不知道如何教会计算机看东西。请想一想:
如果给你一个装满数字的格子,你会用它做什么?你怎么能看穿这个?
更不用说,我上面用的亚伯拉罕·林肯的照片是黑白的。正常的彩色图像是 RGB(红绿蓝)格式,有 3 个独立的颜色通道,而不是 1 个,这意味着图像不仅仅是一个二维数组,而是一个三维数组。计算机如何识别这些数字中的模式来观察物体?这次,仅仅有一堆 if-else 语句是不够的。
进入神经网络。
神经网络受人脑工作方式的启发,由许多层相互连接的神经元组成,这些神经元共同工作。毕竟,如果我们的大脑能学会看东西,为什么人工大脑不能做到呢?神经网络允许计算机自学识别它希望完成的特定任务所需的复杂模式。在这种情况下,目标是教计算机看到并理解它所处的环境。
教汽车看东西的最好方法是使用一种特殊类型的神经网络,称为卷积神经网络。
卷积神经网络(CNN)因其理解空间信息的惊人能力而被用于计算机视觉。这意味着如果我有一张人的照片,即使我旋转它,移动它,挤压和拉伸它,CNN 仍然会识别出它是一个人。
CNN 强大背后的关键是他们使用了一种叫做卷积层的特殊层,从图像中提取特征。初始卷积层识别边缘和拐角等低级特征。随着特征通过更多的卷积层,检测到的特征变得复杂得多。例如,用于检测人的卷积层可能会从边缘到形状到四肢再到人的整体。你可以在这篇 文章 中阅读更多关于卷积神经网络的内容。
The architecture of Convolutional Neural Networks
卷积神经网络本身主要用于图像分类:给定一幅图像,网络将准确地指定给定的类别。例如,使用在皮肤病变数据集上训练的 CNN,它可以学习从给定的图像中诊断不同的皮肤癌。
但是,如果我想知道的不仅仅是图像是否属于某个类,更具体地说,对象的确切位置是什么呢?此外,如果一个图像中有多个不同类别的对象,该怎么办?自动驾驶汽车不能只知道该区域有 5 辆车和 20 个人,它需要知道他们相对于自己的位置**,以便安全导航**。
这就是物体检测的用武之地。通过增强我们的卷积神经网络,我们可以重新利用其惊人的分类属性来定位图像中的类别。我们可以通过一种叫做 YOLO(你只看一次)的算法来做到这一点,这种算法可以进行实时物体检测,非常适合自动驾驶汽车。YOLO 非常快,使用 24 个卷积层,每秒可以处理高达 155 帧。这使得它很容易实现成为一辆自动驾驶汽车。那么它是如何工作的呢?
#YOLO 解释道
正如研究论文所述,YOLO:
单个卷积神经网络同时预测多个边界框和这些框的类概率
YOLO 使用整个图像的特征来预测每个边界框和它们的类别,它同时进行**。与人类相似,YOLO 几乎可以立即识别出给定图像中的物体位置和内容。**
在运行一幅图像时,YOLO 首先将图像分成一个由 S 组成的网格。
在每个网格单元内,YOLO 将预测预定数量的边界框的位置**、大小和置信度得分——本质上是预测一个物体可能存在的类别和潜在位置。如果对象的中心落在网格单元中,则该网格单元的边界框负责准确定位和预测该对象。**
YOLO bounding boxes in action
每个边界框将有 5 个预测:x 坐标、y 坐标、宽度、高度和置信度**。计算出的置信度得分表明模型认为在边界框内存在一个类的置信度,以及它认为该类适合该框的准确度,该框使用一种称为交集/并集的度量。**
并集上的交集用于对象检测,因为它将地面真实边界框与预测边界框进行比较。通过将重叠面积除以并集面积,我们得到一个函数,即奖励严重重叠而惩罚不准确的边界框预测。边界框的目标是最终尽可能精确地将对象限制在图像内,IoU 是确定这一点的一个很好的度量。
****
在图像通过 YOLO 后,它输出 S x S x (B * 5 + C)张量中的预测,其中每个网格单元预测 C 个类别中 B 个边界框的位置和置信度得分**。最终,我们得到了很多边界框——其中大部分与**无关。****
为了过滤出正确的框,具有满足特定置信度分数的预测类的边界框将被保留。这允许我们隔离图像中的所有相关对象。
本质上,YOLO 定位和分类图像/视频中的对象。像 YOLO 这样的物体检测算法,结合像 Li-Dar 这样的自动驾驶汽车上的许多其他传感器,使我们能够建造完全自动驾驶的汽车,比任何人都更快、更安全、更好。如果你有兴趣深入研究自动驾驶汽车,我强烈推荐你阅读 这篇文章 。
既然你已经理解了 YOLO 的工作,让我们看看它是如何工作的。
奔跑的 YOLO
要在您自己的计算机上试用 YOLO,将我的 GitHub 库克隆到您的计算机上。
**[## 西吉尔-文/YOLO
用 YOLO 检测物体(你只看一次)。要跑 YOLO,首先,下载链接的预训练重量…
github.com](https://github.com/Sigil-Wen/YOLO)**
然后从这个 链接 下载预先训练好的重量,并将重量移动到名为 yolo-coco 的文件夹中
在您的命令终端中,找到克隆的存储库,导入所需的依赖项和库,并输入以下命令在任何视频上运行 YOLO。
**python yolo_video.py —输入 —输出
— yolo yolo-coco**
Terminal Window
运行该命令后,YOLO 将在输入视频上运行,最终产品将出现在输出路径中。下面是我在 YOLO 看到的一些东西:
https://www.youtube.com/watch?v=AxhBu2uK86I&
这就对了。你理解并运行了一个物体检测算法!令人难以置信的是,我们距离一个由人工智能司机主导的世界有多近。
- 对跟随我的旅程感兴趣并想要更多这样的内容?跟着我上媒👇或者订阅我的 YouTube 频道
- 你可以在 LinkedIn 和我联系,或者在 sigil.w3n@gmail.com 给我发邮件
- 查看我的 个人网站 和 作品集
- 看看我对 YOLO 的恶搞吧:https://www.youtube.com/watch?v=AxhBu2uK86I&
使用 PyTorch 移动神经网络的对象检测器 Android 应用程序
图沙尔·卡普尔😦【https://www.tusharck.com/】T2
在移动设备上运行机器学习代码是下一件大事。 PyTorch ,在最新发布的 PyTorch 1.3 中,增加了 PyTorch Mobile ,用于在 Android 和 iOS 设备上部署机器学习模型。
在这里,我们将研究创建一个 Android 应用程序,用于图像中的对象检测;比如下图的 GIF。
Demo Run of the application
步骤 1:准备模型
在本教程中,我们将使用预先训练好的 ResNet18 模型。ResNet18 是最先进的计算机视觉模型,有 1000 个分类类别。
- 安装火炬视觉库
pip install torchvision
2.下载并追踪 ResNet18 模型。
我们跟踪模型,因为我们需要一个可执行的 ScriptModule 来进行实时编译。
import torch
import torchvisionresnet18 = torchvision.models.resnet18(pretrained=True)
resnet18.eval()example_inputs = torch.rand(1, 3, 224, 224)resnet18_traced = torch.jit.trace(resnet18, example_inputs = example_inputs)resnet18_traced.save("resnet18_traced.pt")
注:
- 将 resnet18_traced.pt 存储在一个已知的位置,我们将在本教程的后续步骤中用到它。
- 在 torch.rand 中,我们采用了 224,224 的尺寸,因为 ResNet18 接受 224*224 的尺寸。
步骤 2:制作 Android 应用程序
- 如果你还没有下载并安装 Android Studio,安装时说 YES 下载并安装 SDK 。
链接:https://developer.android.com/studio - 打开 Android Studio,点击:
+开始一个新的 Android Studio 项目 - 选择空活动。
4.输入应用程序名称: ObjectDetectorDemo。然后按完成。
5.安装 NDK 在安卓内部运行原生代码:
- 转到工具> SDK 管理器。
- 点击 SDK 工具。
- 勾选 **NDK(并排)**旁边的复选框。
6.添加依赖项。
- 在 build.gradle(模块:app)里面。在依赖项中添加以下内容。
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'**implementation 'org.pytorch:pytorch_android:1.3.0'
implementation 'org.pytorch:pytorch_android_torchvision:1.3.0'**
}
7.添加一个基本布局来加载图像并显示结果。
- 转到app>RES>layout>activity _ main . XML并添加下面的代码。
<ImageView
android:id="@+id/image"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_marginBottom="20dp"
android:scaleType="fitCenter" />
<TextView
android:id="@+id/result_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:text=""
android:textSize="20dp"
android:textStyle="bold"
android:textAllCaps="true"
android:textAlignment="center"
app:layout_constraintTop_toTopOf="@id/button"
app:layout_constraintBottom_toBottomOf="@+id/image" />
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Load Image"
app:layout_constraintBottom_toBottomOf="@+id/result_text"
app:layout_constraintTop_toTopOf="@+id/detect" />
<Button
android:id="@+id/detect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Detect"
android:layout_marginBottom="50dp"
app:layout_constraintBottom_toBottomOf="parent" />
你的布局应该如下图所示。
8.我们需要设置权限来读取设备上存储的图像。
- 转到app>manifest>androidmanifest . XML并将下面的代码添加到 manifest 标签内。
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
- 获取应用程序加载的权限(只有在您授予权限后才会询问)。
—进入主活动 java。将下面的代码添加到 onCreate() 方法中。
if (Build.VERSION.*SDK_INT* >= Build.VERSION_CODES.*M*) {
requestPermissions(new String[] {android.Manifest.permission.*READ_EXTERNAL_STORAGE*}, 1);
}
9.复制模型。
现在我们复制使用 python 脚本创建的模型。
- 从文件浏览器/查找器中打开您的应用程序。
- 转到 app > src > main。
- 创建一个名为资产的文件夹
- 将您的模型复制到该文件夹中。
- 当你打开你的 Android 工作室时,你会看到如下图所示。(如果没有,右击应用程序文件夹并点击同步应用程序)。
10.我们需要列出模型的输出类。
- 转到 app > java。
- 在第一个文件夹中创建一个新的 Java 类名 ModelClasses 。
- 将类的列表定义为(完整的列表有 1000 个类,所以可以在这里复制所有的 check Json 或 check Git 。完整列表和下面列表中的副本):
public static String[] *MODEL_CLASSES* = new String[]{
"tench, Tinca tinca",
"goldfish, Carassius auratus"
.
.
.
}
11.主活动 Java,这里将定义按钮动作,读取图像并调用 PyTorch 模型。参见代码中的注释以获得解释。
12.现在是测试应用程序的时候了。有两种方法:
- 运行在仿真器上【点击此处】。
- 使用安卓设备。(为此您需要启用 USB 调试 [ 点击此处)。
- 运行应用程序后,它应该看起来像页面顶部的 GIF。
链接到 Git 资源库:https://github . com/tusharck/Object-Detector-Android-App-Using-py torch-Mobile-Neural-Network
免责声明:
- “Android”名称、Android 徽标、“Google Play”品牌和其他 Google 商标是 Google LLC 的财产,不属于 Android 开源项目的资产。
- PyTorch、PyTorch、PyTorch 徽标以及与 PyTorch 或网站相关的所有其他商标、服务标志、图形和徽标是 PyTorch 或 PyTorch 许可方的商标或注册商标。
面向对象的机器学习流水线与熊猫和考拉数据框架的 mlflow
使用熊猫、考拉、scikit-learn 和 mlflow 在 Python 中开发支持 Spark 的机器学习管道的端到端流程
在文章 Python 数据预处理使用熊猫数据帧、Spark 数据帧和考拉数据帧中,我使用了一个公共数据集来评估和比较机器学习的典型数据预处理步骤中熊猫、 Spark 和考拉数据帧的基本功能。考拉的主要优势是支持类似 Spark 上熊猫的易用 API。
在本文中,我使用一个更具挑战性的数据集,Kaggle 竞赛的面试出席问题来演示使用熊猫、考拉、 scikit-learn 和 mlflow 用 Python 为熊猫和考拉数据帧开发面向对象的机器学习管道的端到端过程。这是通过以下方式实现的:
- 使用 Pandas DataFrame 和 scikit-learn pipeline API 开发数据预处理管道
- 结合 scikit-learn pipeline API 和 Koalas DataFrame 为 Spark 开发数据预处理管道
- 结合 scikit-learn 和 mlflow 开发机器学习流水线
端到端开发流程基于数据挖掘的跨行业标准流程。如下图所示,它包括六个主要阶段:
- 商业理解
- 数据理解
- 数据准备
- 建模
- 估价
- 部署
图 1: CRISP-DM 流程图(参考维基百科中的来源)
为方便讨论,假设在 Mac 等本地机器上安装了以下 Python 库:
- Anaconda (conda 4.7.10)和 Python 3.6、Numpy、Pandas、Matplotlib 和 Scikit-Learn
- pyspark 2.4.4
- 考拉
- mlflow
使用 Python 3.6 的原因是当前版本的 mlflow 的某些功能(如部署)无法与 Python 3.7 兼容。
1.商业理解
第一阶段是业务理解。这个阶段的关键点是理解要解决的业务问题。作为一个例子,下面是对 Kaggle 面试出勤率问题的简要描述:
给定招聘人员在安排与候选人的面试时提出的一组问题,如何使用候选人对这些问题的回答来预测预期出席者是否会参加安排的面试(是、否或不确定)。
2.数据理解
一旦理解了业务问题,下一步就是确定在哪里(即数据源)以及我们应该如何收集数据,从这些数据中可以构建问题的机器学习解决方案。
研究人员在 2014 年 9 月至 2017 年 1 月的两年多时间里,从印度的招聘行业收集了 Kaggle 面试出勤问题的数据集。
这个数据集是用标签收集的(观察到的出勤的列保存标签),因此它适用于监督机器学习。
以下代码为本文中的所有源代码导入必要的 Python 库,并将数据集加载到考拉数据帧中,并显示数据帧的前五行,如上表所示。
import numpy as np
import pandas as pd
import databricks.koalas as ks
import matplotlib.pyplot as plt
import matplotlib as mpl
from datetime import datetime
import osfrom sklearn.base import BaseEstimator, TransformerMixin
from sklearn.pipeline import Pipeline
from sklearn.externals import joblibimport mlflow
import mlflow.sklearnfrom sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCVfrom sklearn.metrics import make_scorer
from sklearn.metrics import accuracy_score
from sklearn.metrics import f1_score
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score%matplotlib inlineks_df = ks.read_csv('Interview_Attendance_Data.csv')
ks_df.head()
3.数据准备
数据准备的主要目标是清理收集的原始数据集并将其转换为适当的格式,以便转换后的数据可以被目标机器学习模型有效地使用。
在面试考勤原始数据集中, Name(Cand ID) 列包含候选人的唯一标识符,该标识符没有太大的预测能力,因此可以删除。此外,所有列(即对于考拉数据帧从 _c22 到 _c26 的列,或者对于熊猫数据帧从未命名:22 到未命名:26 的列)都没有数据,因此也可以安全地删除。
除了采访日期,数据集中的所有其他列都有分类(文本)值。为了使用机器学习来解决问题,必须将这些分类值转换为数值,因为机器学习模型只能使用数值数据。
面试的日期一栏应拆分为日、月、年,以提高预测能力,因为与作为整体的一串日期相比,个别日、月、年的信息往往与季节性工作更密切相关。
技能组合的性质和候选人籍贯列有大量唯一条目。这些将在一键编码后引入大量新的衍生功能。在数据集不够大的情况下,太多的特征会导致维数灾难问题。为了缓解这一问题,这两列的值被重新划分到更少的存储桶中。
上述数据预处理/转换可以概括为以下步骤:
- 桶装技能组合
- 存储候选人本地位置
- 解析面试日期
- 将分类值改为大写并删除不太有用的特征
- 一次性编码分类值
这些步骤是通过将熊猫和考拉数据帧与 scikit-learn 管道 API(即 BaseEstimator 、 TransformerMixin 和管道)相结合,为熊猫和考拉数据帧开发面向对象的数据预处理管道来实现的。
3.1 转换列值
几个数据预处理步骤共享一个转换数据帧中特定列的值的通用操作。但是,如考拉系列所述,考拉系列不支持一些常见的熊猫数据帧和系列索引机制,如df . iloc【0】。因此,没有简单的方法来遍历和更改考拉数据帧中的列值。
另一个困难是,考拉不允许从头构建一个新的考拉系列对象,然后将它作为一个新列添加到现有的考拉数据框架中。它只允许从考拉数据帧的现有列构建新的考拉系列对象。
通过定义一个全局函数来调用考拉系列对象的 apply ()方法,可以避免上述困难。
def transformColumn(column_values, func, func_type): def transform_column(column_element) -> func_type:
return func(column_element)
cvalues = column_values
cvalues = cvalues.apply(transform_column)
return cvalues
3.2 铲斗技能组合
为了缓解维数灾难问题, BucketSkillset 转换类的转换()方法通过将出现次数少于 9 次的值更改为 Others 的一个相同字符串值,将 Skillset 列的唯一值划分为更小数量的桶。
class BucketSkillset(BaseEstimator, TransformerMixin):
def __init__(self):
self.skillset = ['JAVA/J2EE/Struts/Hibernate', 'Fresher', 'Accounting Operations', 'CDD KYC', 'Routine', 'Oracle',
'JAVA/SPRING/HIBERNATE/JSF', 'Java J2EE', 'SAS', 'Oracle Plsql', 'Java Developer',
'Lending and Liabilities', 'Banking Operations', 'Java', 'Core Java', 'Java J2ee', 'T-24 developer',
'Senior software engineer-Mednet', 'ALS Testing', 'SCCM', 'COTS Developer', 'Analytical R & D',
'Sr Automation Testing', 'Regulatory', 'Hadoop', 'testing', 'Java', 'ETL', 'Publishing']
def fit(self, X, y=None):
return self
def transform(self, X, y=None):
func = lambda x: x if x in self.skillset else 'Others'
X1 = X.copy()
cname = 'Nature of Skillset'
cvalue = X1[cname]
if type(X1) == ks.DataFrame:
cvalue = transformColumn(cvalue, func, str)
X1[cname] = cvalue
elif type(X1) == pd.DataFrame:
X2 = map(func, cvalue)
X1[cname] = pd.Series(X2)
else:
print('BucketSkillset: unsupported dataframe: {}'.format(type(X1)))
pass
return X1
3.3 存储候选人本地位置
与 bucketing skillset 类似,为了缓解维数灾难问题,bucket locationtransformer 类的 transform ()方法通过将那些出现次数少于 12 次的值更改为 Others 的一个相同值,将候选本地位置列的唯一值划分为更小数量的桶。
class BucketLocation(BaseEstimator, TransformerMixin):
def __init__(self):
self.candidate_locations = ['Chennai', 'Hyderabad', 'Bangalore', 'Gurgaon', 'Cuttack', 'Cochin',
'Pune', 'Coimbatore', 'Allahabad', 'Noida', 'Visakapatinam', 'Nagercoil',
'Trivandrum', 'Kolkata', 'Trichy', 'Vellore']
def fit(self, X, y=None):
return self
def transform(self, X, y=None):
X1 = X.copy()
func = lambda x: x if x in self.candidate_locations else 'Others'
cname = 'Candidate Native location'
cvalue = X1[cname]
if type(X1) == ks.DataFrame:
cvalue = transformColumn(cvalue, func, str)
X1[cname] = cvalue
elif type(X1) == pd.DataFrame:
X2 = map(func, cvalue)
X1[cname] = pd.Series(X2)
else:
print('BucketLocation: unsupported dataframe: {}'.format(type(X1)))
pass
return X1
3.4 解析面试日期
采访日期一栏的值很乱,因为使用了各种格式。例如,不仅使用不同的分隔符来分隔日、月和年,而且还遵循日、月和年的不同顺序。这由 ParseInterviewDate 转换器类的 _ parseDate ()和 transform_date ()方法来处理。 transform ()方法的总体功能是将采访日期字符串分成各个日、月和年的值。
class ParseInterviewDate(BaseEstimator, TransformerMixin):
def __init__(self):
pass
def __parseDate(self, string, delimit):
try:
if ('&' in string):
subs = tuple(string.split('&'))
string = subs[0]
except:
print ('TypeError: {}'.format(string))
return None
string = string.strip()
try:
d = datetime.strptime(string, '%d{0}%m{0}%Y'.format(delimit))
except:
try:
d = datetime.strptime(string, '%d{0}%m{0}%y'.format(delimit))
except:
try:
d = datetime.strptime(string, '%d{0}%b{0}%Y'.format(delimit))
except:
try:
d = datetime.strptime(string, '%d{0}%b{0}%y'.format(delimit))
except:
try:
d = datetime.strptime(string, '%b{0}%d{0}%Y'.format(delimit))
except:
try:
d = datetime.strptime(string, '%b{0}%d{0}%y'.format(delimit))
except:
d = None
return d
def fit(self, X, y=None):
return self
def transform(self, X, y=None):
def transform_date(ditem):
if (isinstance(ditem, str) and len(ditem) > 0):
if ('.' in ditem):
d = self.__parseDate(ditem, '.')
elif ('/' in ditem):
d = self.__parseDate(ditem, '/')
elif ('-' in ditem):
d = self.__parseDate(ditem, '-')
elif (' ' in ditem):
d = self.__parseDate(ditem, ' ')
else:
d = None
if (d is None):
return 0, 0, 0
else:
return d.day, d.month, d.year
def get_day(column_element) -> int:
try:
day, month, year = transform_date(column_element)
return int(day)
except:
return 0
def get_month(column_element) -> int:
try:
day, month, year = transform_date(column_element)
return int(month)
except:
return 0
def get_year(column_element) -> int:
try:
day, month, year = transform_date(column_element)
return int(year)
except:
return 0
def pandas_transform_date(X1):
days = []
months = []
years = []
ditems = X1['Date of Interview'].values
for ditem in ditems:
if (isinstance(ditem, str) and len(ditem) > 0):
if ('.' in ditem):
d = self.__parseDate(ditem, '.')
elif ('/' in ditem):
d = self.__parseDate(ditem, '/')
elif ('-' in ditem):
d = self.__parseDate(ditem, '-')
elif (' ' in ditem):
d = self.__parseDate(ditem, ' ')
else:
d = None
if (d is None):
# print("{}, invalid format of interview date!".format(ditem))
days.append(0) # 0 - NaN
months.append(0)
years.append(0)
else:
days.append(d.day)
months.append(d.month)
years.append(d.year)
else:
days.append(0)
months.append(0)
years.append(0)
X1['Year'] = years
X1['Month'] = months
X1['Day'] = days
return X1
X1 = X.copy()
if type(X1) == ks.DataFrame:
X1['Year'] = X1['Date of Interview']
X1['Month'] = X1['Date of Interview']
X1['Day'] = X1['Date of Interview']
func_map = {'Year' : get_year, 'Month' : get_month, 'Day' : get_day}
for cname in func_map:
cvalue = X1[cname]
cvalue = cvalue.apply(func_map[cname])
X1[cname] = cvalue
elif type(X1) == pd.DataFrame:
X1 = pandas_transform_date(X1)
else:
print('ParseInterviewDate: unsupported dataframe: {}'.format(type(X1)))
pass
return X1
3.5 将分类值改为大写,并删除不太有用的特性
features super casetransformer 类的 transform ()方法是将分类特性的值改为大写,同时丢弃不太有用的特性。
class FeaturesUppercase(BaseEstimator, TransformerMixin):
def __init__(self, feature_names, drop_feature_names):
self.feature_names = feature_names
self.drop_feature_names = drop_feature_names
def fit(self, X, y=None):
return self
def transform(self, X, y=None):
func = lambda x: x.strip().upper()
X1 = X.copy()
for fname in self.feature_names:
values = X1[fname]
values = values.fillna('NaN')
if type(X1) == ks.DataFrame:
values = transformColumn(values, func, str)
elif type(X1) == pd.DataFrame:
values = map(lambda x: x.strip().upper(), values)
else:
print('FeaturesUppercase: unsupported dataframe: {}'.format(type(X1)))
X1[fname] = values
# drop less important features
X1 = X1.drop(self.drop_feature_names, axis=1)
return X1
3.6 一次性编码分类值
onehotencodatatransformer 类的 transform ()方法调用 DataFrame 的 get_dummies ()方法对分类值的值进行一次性编码。
class OneHotEncodeData(BaseEstimator, TransformerMixin):
def __init__(self):
self.one_hot_feature_names = ['Client name',
'Industry',
'Location',
'Position to be closed',
'Nature of Skillset',
'Interview Type',
'Gender',
'Candidate Current Location',
'Candidate Job Location',
'Interview Venue',
'Candidate Native location',
'Have you obtained the necessary permission to start at the required time',
'Hope there will be no unscheduled meetings',
'Can I Call you three hours before the interview and follow up on your attendance for the interview',
'Can I have an alternative number/ desk number. I assure you that I will not trouble you too much',
'Have you taken a printout of your updated resume. Have you read the JD and understood the same',
'Are you clear with the venue details and the landmark.',
'Has the call letter been shared',
'Marital Status']
self.label_encoders = None
self.one_hot_encoders = None
def fit(self, X, y=None):
return self
def transform(self, X, y=None):
X1 = X.copy()
if type(X1) == ks.DataFrame:
X1 = ks.get_dummies(X1)
elif type(X1) == pd.DataFrame:
X1 = pd.get_dummies(X1)
else:
print('OneHotEncodeData: unsupported dataframe: {}'.format(type(X1)))
pass
return X1
3.7 将变压器合并到管道中
在 PredictInterview 类的 PreprocessData ()方法中,所有的数据预处理转换器被组合成一个 scikit-learn 管道(详见第 4.3 节)。一旦调用管道对象的 fit_transform ()方法,这些变形器的 fit ()和 transform ()方法将被依次执行。
self.pipeline = Pipeline([
('bucket_skillset', BucketSkillset()),
('bucket_location', BucketLocation()),
('parse_interview_date', ParseInterviewDate()),
('features_to_uppercase', FeaturesUppercase(self.feature_names, self.drop_feature_names)),
('one_hot_encoder', self.oneHotEncoder)
])
4.建模
准备好数据集后,下一步是建模。建模的主要目标包括:
- 识别机器学习模型
- 训练机器学习模型
- 调整机器学习模型的超参数
4.1 识别机器学习模型
有三种主要的高级类型的机器学习和深度学习算法/模型:
- 监督机器学习和深度学习
- 无监督机器学习和深度学习
- 强化学习
有监督的机器学习和深度学习可以分为回归和分类等子类型。每个子类型包括各种机器学习和深度学习算法/模型。例如,监督机器学习分类模型包括决策树分类器、随机森林分类器、GBM 分类器等。
一般来说,给定一个业务问题,有许多不同类型的模型可以用作可能的解决方案。需要对这些不同的模型进行比较,以确定最有希望的模型作为目标业务问题的解决方案。因此,模型识别不能孤立地进行。它依赖于模型训练和模型性能度量的评估/比较。
在本文中,我们简单地选择 sci kit-learnRandomForestClassifier模型进行演示。
4.2 训练模型和调整超参数
一旦模型(例如, RandomForestClassifier )被识别,通常有多个超参数要被调整。超参数是在模型训练可以开始之前需要设置的参数,并且这种超参数值在模型训练期间不会改变。例如,随机森林分类器具有多个超参数,例如估计器的数量、最大深度等。
sciket-learnGridSearchCV是一个流行的库,通过多次自动执行模型的实例来搜索给定模型的超参数的最佳组合。每次执行对应于所选超参数值的唯一组合。 GridSearch 类将使用这个库来寻找估计器的数量和最大深度的最佳组合:
class GridSearch(object):
def __init__(self, cv=10):
self.grid_param = [
{'n_estimators': range(68,69), # range(60, 70)
'max_depth' : range(8,9)} # range(5, 10)}
]
self.cv = cv
self.scoring_function = make_scorer(f1_score, greater_is_better=True)
self.gridSearch = None
def fit(self, X, y):
rfc = RandomForestClassifier()
self.gridSearchCV = GridSearchCV(rfc, self.grid_param, cv=self.cv, scoring=self.scoring_function)
self.gridSearchCV.fit(X, y)
return self.gridSearchCV.best_estimator_
4.3 跟踪模型超参数和性能指标
mlflow 的一个设计功能是跟踪和比较不同模型执行的超参数和性能指标。
PredictInterview 类的 mlFlow ()的方法是训练一个模型,使用训练好的模型预测结果,获得各种模型性能指标,然后调用 mlflow API 来跟踪超参数和性能指标,同时将训练好的模型记录到一个文件中,供以后使用,如部署。
def mlFlow(self):
np.random.seed(40)
with mlflow.start_run():
self.loadData()
self.PreprocessData()
self.trainModel()
self.predictClasses()
accuracy_score, f1_score, rmse_score, mae_score, r2_score = self.getModelMetrics() best_params = self.gridSearch.gridSearchCV.best_params_ mlflow.log_param("n_estimators", best_params["n_estimators"])
mlflow.log_param("max_depth", best_params["max_depth"])
mlflow.log_metric("rmse", rmse_score)
mlflow.log_metric("r2", r2_score)
mlflow.log_metric("mae", mae_score)
mlflow.log_metric("accuracy", accuracy_score)
mlflow.log_metric("f1", f1_score) mlflow.sklearn.log_model(self.rfc, "random_forest_model")
下面的 PredictInterview 类的 Jupyter 记事本和本文中的所有其他源代码都可以在 Github [6]中找到。
class PredictInterview(object):
def __init__(self, use_koalas=True):
self.use_koalas = use_koalas
self.dataset_file_name = 'Interview_Attendance_Data.csv'
self.feature_names = ['Date of Interview',
'Client name',
'Industry',
'Location',
'Position to be closed',
'Nature of Skillset',
'Interview Type',
'Gender',
'Candidate Current Location',
'Candidate Job Location',
'Interview Venue',
'Candidate Native location',
'Have you obtained the necessary permission to start at the required time',
'Hope there will be no unscheduled meetings',
'Can I Call you three hours before the interview and follow up on your attendance for the interview',
'Can I have an alternative number/ desk number. I assure you that I will not trouble you too much',
'Have you taken a printout of your updated resume. Have you read the JD and understood the same',
'Are you clear with the venue details and the landmark.',
'Has the call letter been shared', 'Marital Status']
if self.use_koalas:
self.drop_feature_names = [
'Name(Cand ID)',
'Date of Interview',
'_c22',
'_c23',
'_c24',
'_c25',
'_c26']
else: # use Pandas
self.drop_feature_names = [
'Unnamed: 22',
'Unnamed: 23',
'Unnamed: 24',
'Unnamed: 25',
'Unnamed: 26']
self.dataset = None
self.rfc = None
self.gridSearch = None
self.X_train = None
self.y_train = None
self.X_test = None
self.y_test = None
self.y_pred = None
self.X_clean = None
self.y_clean = None
self.X_train_encoded = None
self.X_test_encoded = None
self.y_train_encoded = None
self.accuracy_score = None
self.f1_score = None
self.oneHotEncoder = None
self.X_test_name_ids = None
self.pipeline = None
def loadData(self, path=None):
if (path != None):
path = os.path.join(path, self.dataset_file_name)
else:
path = self.dataset_file_name
if self.use_koalas:
dataset = ks.read_csv(path)
else:
dataset = pd.read_csv(path)
# shuffle data
self.dataset = dataset.sample(frac=1.0)
return self.dataset
def PreprocessData(self):
y = self.dataset['Observed Attendance'] # extract labels y
if self.use_koalas:
X = self.dataset.drop('Observed Attendance') # extract features X
else:
X = self.dataset.drop(['Observed Attendance'], axis=1)
self.oneHotEncoder = OneHotEncodeData()
self.pipeline = Pipeline([
('bucket_skillset', BucketSkillset()),
('bucket_location', BucketLocation()),
('parse_interview_date', ParseInterviewDate()),
('features_to_uppercase', FeaturesUppercase(self.feature_names, self.drop_feature_names)),
('one_hot_encoder', self.oneHotEncoder)
])
X_1hot = self.pipeline.fit_transform(X)
# fill up missing labels and then change labels to uppercase
y = y.fillna('NaN')
if self.use_koalas:
func = lambda x: x.strip().upper()
y_uppercase = transformColumn(y, func, str)
else:
y_uppercase = map(lambda x: x.strip().upper(), y.values)
y_uppercase = pd.Series(y_uppercase)
# separate labeled records from unlabeled records
self.X_train_encoded = X_1hot[y_uppercase != 'NAN']
self.X_test_encoded = X_1hot[y_uppercase == 'NAN']
# save Names/ID for reporting later one
self.X_test_name_ids = self.dataset['Name(Cand ID)'].loc[y_uppercase == 'NAN']
y_train = y_uppercase.loc[y_uppercase != 'NAN']
# encode labels as follows: 0 - NO, 1 - YES, NAN - NAN
if self.use_koalas:
func = lambda x: 1 if x == 'YES' else 0
y = transformColumn(y_train, func, int)
else:
y = map(lambda x: 1 if x == 'YES' else 0, y_train)
y = pd.Series(y)
self.y_train_encoded = y
self.X_clean = X_1hot
self.y_clean = y_uppercase
return None
def __splitData(self):
if self.use_koalas:
X_train_encoded = self.X_train_encoded.to_numpy()
y_train_encoded = self.y_train_encoded.to_numpy()
else:
X_train_encoded = self.X_train_encoded.values
y_train_encoded = self.y_train_encoded.values
self.X_train, self.X_test, self.y_train, self.y_test = train_test_split(X_train_encoded,
y_train_encoded,
test_size = 0.25, random_state = 0)
return (self.X_train, self.X_test, self.y_train, self.y_test)
def trainModel(self):
X_train, X_test, y_train, y_test = self.__splitData()
self.gridSearch = GridSearch()
self.rfc = self.gridSearch.fit(X_train, y_train)
return self.rfc
def predictClasses(self):
if (self.rfc is None):
print("No trained model available, please train a model first!")
return None
self.y_pred = self.rfc.predict(self.X_test)
return self.y_pred
def getModelMetrics(self):
if (self.y_test is None or self.y_pred is None):
print('Failed to get model performance metrics because y_test is null or y_pred is null!')
return None
self.accuracy_score = accuracy_score(self.y_test, self.y_pred)
self.f1_score = f1_score(self.y_test, self.y_pred)
pred = self.predictAttendanceProbability(self.X_test)[:, 1]
actual = self.y_test.astype(float)
self.rmse_score = np.sqrt(mean_squared_error(actual, pred))
self.mae_score = mean_absolute_error(actual, pred)
self.r2_score = r2_score(actual, pred)
return (self.accuracy_score, self.f1_score, self.rmse_score, self.mae_score, self.r2_score)
def predictNullAttendanceProbability(self):
y_pred = self.rfc.predict_proba(self.X_test_encoded.to_numpy())
return y_pred
def predictNullAttendanceClasses(self):
y_pred = self.rfc.predict(self.X_test_encoded.to_numpy())
return y_pred
def predictAttendanceProbability(self, X):
y_pred = self.rfc.predict_proba(X)
return y_pred
def predictAttendanceClass(self, X):
y_pred = self.rfc.predict(X)
return y_pred
def mlFlow(self):
np.random.seed(40)
with mlflow.start_run():
self.loadData()
self.PreprocessData()
self.trainModel()
self.predictClasses()
accuracy_score, f1_score, rmse_score, mae_score, r2_score = self.getModelMetrics() best_params = self.gridSearch.gridSearchCV.best_params_ mlflow.log_param("n_estimators", best_params["n_estimators"])
mlflow.log_param("max_depth", best_params["max_depth"])
mlflow.log_metric("rmse", rmse_score)
mlflow.log_metric("r2", r2_score)
mlflow.log_metric("mae", mae_score)
mlflow.log_metric("accuracy", accuracy_score)
mlflow.log_metric("f1", f1_score) mlflow.sklearn.log_model(self.rfc, "random_forest_model")
下面的代码显示了如何实例化 PredictInterview 类的一个对象,然后调用它的 mlFlow ()方法。
predictInterview = PredictInterview(use_koalas=True)
predictInterview.mlFlow()
4.4 比较模型超参数和性能指标
一旦在 mlflow 中跟踪了模型的超参数和性能指标,我们可以使用终端或 Jupyter 笔记本启动如下的 mlflow UI(用户界面),查看模型执行的历史记录:
!mlflow ui # for jupyter notebook
假设 mlflow UI 在本地机器上启动,以下 IP 地址和端口号可用于在 Web 浏览器中查看结果:
http://127.0.0.1:5000
下图是 mlflow UI 中模型执行历史的快照:
图 2: 在 mlflow UI 中跟踪超参数和指标
5.估价
一旦机器学习模型被训练成具有预期的性能,下一步就是在受控的接近真实的设置中评估模型的预测结果,以获得模型有效、可靠并且满足部署的业务需求的信心。
例如,对于 Kaggle 面试出勤项目,一种可能的评估方法是使用 mlflow 将模型部署为 Web 服务,然后开发客户端程序来调用模型 Web 服务,以在经过数据准备后获得测试数据集的预测结果。然后,这些预测结果可用于生成报告(例如,表格或 csv 文件),供招聘行业领域专家审阅。
出于演示目的,下面的代码使用阈值为 0.5 的预测结果来为“观察到的出席率”列中缺少值的每个候选人生成概率和预测,并将结果形成为 Pandas 数据帧。
pred_probs = predictInterview.predictNullAttendanceProbability()
pred_classes = predictInterview.predictNullAttendanceClasses()x = predictInterview.X_test_name_ids.to_numpy()
z = zip(x, pred_probs, pred_classes)
answers = ('no', 'yes')result = [[x1, p1[1], answers[c]] for x1, p1, c in z]
result_df = pd.DataFrame(np.array(result), columns=['Names/ID', 'Probability', 'Yes/No'])
result_df.to_csv('interview_prediction.csv')
result_df.head(15)
以下是数据帧的前 15 行:
6.部署
一旦模型评估得出模型可以部署的结论,最后一步就是将评估后的模型部署到生产系统中。如商业数据科学一书中所述,部署的细节取决于目标生产系统。
以 Kaggle 面试考勤项目为例,一种可能的场景是将模型部署为服务器上的 Web 服务,可以被目标生产系统中的其他组件调用,以获得预测结果来辅助工作面试安排。在目标生产系统的开发基于不同于建模语言(例如,Python)的编程语言(例如,Java)的更复杂的情况下,模型有可能需要作为生产系统的组件在目标编程语言中重新实现。
6.1 将模型部署为 Web 服务
如前所述,在 mlflow 中跟踪模型执行的过程中,一个经过训练的模型已经被记录到一个文件中。以下屏幕快照显示了已记录模型的信息:
**图 3:**ml flow UI 中的测井训练模型
与 mlflow 教程类似,下面的代码将使用 mlflow 内置功能来启动一个日志模型作为 Web 服务:
mlflow models serve -m /Users/xyz/machine-learning-spark/mlruns/0/258301f3ac5f42fb99e885968ff17c2a/artifacts/random_forest_model -p 1234
6.2 调用模型 Web 服务来预测结果
为简单起见,在本节中,假设 test_df 是只有一行测试数据的 Pandas 数据帧(面试出席特征向量):
test_df.head()
以下代码可用于将测试数据行发送到模型 Web 服务,以获得预测的面试出席率(1 -是,0 -否):
import requests
import jsonheaders = {'Content-Type': 'application/json',
'Format': 'pandas-split'}url = '[http://127.0.0.1:1234/invocations'](http://127.0.0.1:1234/invocations')headers_json_str = json.dumps(headers)
headers_json_obj = json.loads(headers_json_str)
data_json_obj = test_df.to_json(orient='split')response = requests.post(url, data=data_json_obj, headers = headers_json_obj)response.text
摘要
在本文中,我使用了一个接近真实的具有挑战性的数据集,即 Kaggle 竞赛的面试出勤问题,通过将熊猫和考拉数据帧与 scikit-learn pipeline API 和 mlflow 相结合,演示了一个用 Python 为熊猫和考拉数据帧开发面向对象的机器学习管道的端到端过程。这个端到端的开发过程遵循数据挖掘的跨行业标准过程。为标准流程的每个阶段(除了第一阶段)提供了简短的描述和示例实现代码。Github [6]中提供了一个 Jupyter 笔记本和相应的 Python 源代码文件。
参考
[1]教务长,f .,福塞特,T. (2013)。业务数据科学,奥赖利,2013 年 7 月
[2]杰龙,A. (2017)。使用 Scikit-Learn & TensorFlow 进行动手机器学习,奥赖利,2017 年 3 月
[3] mlflow 1.3.0 教程:https://www.mlflow.org/docs/latest/tutorial.html
[4]面试考勤问题:https://www . ka ggle . com/vishnusraghavan/The-Interview-Attendance-Problem/data
[5]张,于(2019)。使用熊猫数据帧、Spark 数据帧和考拉数据帧的 Python 数据预处理:https://towards Data science . com/python-Data-预处理-使用-熊猫-数据帧-Spark-数据帧-和-考拉-数据帧-e4c 42258 a8f
[6]张,于(2019)。Github 中的 Jupyter 笔记本
披露声明:2019 首创一。观点是作者个人的观点。除非本帖中另有说明,否则 Capital One 不隶属于所提及的任何公司,也不被这些公司认可。使用或展示的所有商标和其他知识产权是其各自所有者的财产。