将 2D 图像离散成多边形和点模型
将复杂的图像简化为简单的数据格式
现实世界是复杂多变的。随着机器人技术和自动驾驶汽车的发展,人们普遍需要将物体简化为更易于管理的形式。此外,我们的世界是连续的(在大多数情况下),然而我们的技术以数字方式解释内容。因此,对于任何世界互动,都会有某种程度的离散化。
对于我正在开发的一个机器人抓取项目,我最近遇到了这个问题。2D 形状是存在的,并且表面信息需要被外推以确定在哪里抓取该形状。幸运的是,OpenCV 有办法将弯曲的轮廓简化成更加多边形的形状。让我们来看看这些函数及其结果。
A key and the result after salience detection.
在得到一幅图像后,我们使用我们最喜欢的显著性方法抓取感兴趣的项目。如上例所示,这为感兴趣的对象创建了一个相当不错的遮罩。可以看出,如果试图进行任何几何操作,都会出现困难。例如,钥匙手柄的曲率会使确定法向量变得困难。此外,分割过程中拾取的噪声会产生错误的边缘。
方便的 OpenCV 函数approxPolyDP()
用于将轮廓近似为更少的点。该函数使用Ramer–Douglas–peu cker 算法来减少点数。这是一个取常数值的递归算法, ε。 算法从寻找两个起始点的最远点开始。如果该点小于 ε ,那么前两个起点之间的所有点都被丢弃。否则,该算法再次平分该组点,并向下递归到下一组点。对每个二等分的集合重复该过程。
A handy animation of theDouglas–Peucker algorithm. Source
通过改变 ε 的值,可以实现不同层次的“离散化”。该值越大,最终形状的多边形程度越高。
From Left to Right, Top to Bottom: ε = 0.5, 2, 5, 20
对于许多应用来说,以上是一个完美的解决方案。然而,一些处理离散现实世界物体的算法会进一步将多边形分解成连续的点。这将涉及更多的信息存储,但可以提供更多的信息。
从上面的多边形创建一个基于点的物体框架并不困难。有了组成多边形的连续顶点的数组,我们可以沿着每条边迭代。对于每条边,确定两个顶点之间的向量。沿着每个向量,根据期望的分辨率生成一定百分比的点。我们使用的分辨率将决定每条边上的点数。
The dotted with a resolution of 10%
利用所描述的方法,相当复杂的形状可以简化为简单的边或点。如果需要与对象进行任何交互或需要进行几何分析,这将非常有用。如果正在构建某个对象识别方案,比较简化的对象可能更有意义。这让我们可以忽略细节和特质。
本文是一个正在进行的项目的一部分。正在进行的 C++代码可以在我的 github 上找到:
用于分割显著的彩色图像并确定抓取的代码。- TimChinenov/GraspPicture
github.com](https://github.com/TimChinenov/GraspPicture)
分类判别网络
我如何使用暹罗网络建立一个只有很少图像的分类器
一个有鉴别能力的网络——它是什么,我们为什么需要它,我们如何建立它?
我们经常遇到的问题是,我们没有足够的高质量图像和标签数据集来训练一个健壮的基于 CNN 的分类器——要么我们没有足够的图像,要么我们没有手动资源来标记它们。用很少的图像建立一个健壮的分类器通常是一个挑战,因为我们需要成千上万的标签图像来训练一个健壮的神经网络体系结构
在面部识别中经常使用有区别的连体结构。根据这篇研究论文,现代面部识别模型通常是使用暹罗网络(也称为一次性学习)建立的
以上是一张连体网。这是简单明了的两个网络——在本例中,两个 Resnet-50 架构的权重相同——一个左网络和一个右网络。每个网络为其对应的图像输出数字串的编码。暹罗网络广泛用于面部识别。在这种情况下,我已经用它对猫和狗进行了分类。这种建筑学习的方式是——一只猫和另一只猫比和一只狗更相似。不同的猫会有不同的特征,但是猫之间的不同会比猫和狗之间的不同小。这两种编码之间的差异是猫和狗的编码之间的欧几里德距离。如果编码属于两个狗或猫图像,我们将目标标记为阳性或 1,否则如果一个图像是狗,另一个图像是猫,反之亦然,我们将目标标记为阴性类别或 0
这方面另一篇有趣的研究论文:
编码片段:
正在加载所有必需的库
from keras.layers import Input, Conv2D, Lambda, merge, Dense, Flatten,MaxPooling2D,Activation, Dropout
from keras.models import Model, Sequential
from keras.regularizers import l2
from keras import backend as K
from keras.optimizers import Adam
from keras import optimizers
#from skimage.io import imshow
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import randomfrom keras.backend.tensorflow_backend import set_session
from keras.applications import resnet50, vgg16, vgg19, xception, densenet, inception_v3, mobilenet, mobilenetv2, nasnet, inception_resnet_v2
import tensorflow as tf
from keras.callbacks import ModelCheckpoint, TensorBoard, CSVLogger, EarlyStopping
from keras.applications.resnet50 import preprocess_input
#from keras.applications.xception import preprocess_input
import os
import datetime
import json
from keras.preprocessing.image import ImageDataGenerator
让我们使用 OpenCV 库抓取和处理图像。我用过 100 张猫的图片和 100 张狗的图片。猫和狗的形象属于不同的品种。
100 张猫咪图片:
100 张狗狗图片:
加载和预处理图像的代码片段:
import glob
import cv2
from random import shuffledog_path = 'Y:\\Partha\\dog_cats_100\\dog\\*.jpg'
cat_path = 'Y:\\Partha\\dog_cats_100\\cat\\*.jpg'addrsd = glob.glob(dog_path)
addrsc = glob.glob(cat_path)
labelsd = [1 for addr in addrsd] # 1 = dog, 0 = cat
labelsc = [0 for addr in addrsc]# loop over the input imagesdatad = []for imagePath in addrsd:
# load the image, pre-process it, and store it in the data list
img = cv2.imread(imagePath)
img = cv2.resize(img, (224, 224), interpolation=cv2.INTER_CUBIC)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
datad.append(img)datac = []
for imagePath in addrsc:
# load the image, pre-process it, and store it in the data list
img = cv2.imread(imagePath)
img = cv2.resize(img, (224, 224), interpolation=cv2.INTER_CUBIC)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
datac.append(img)# to shuffle data
shuffle_data = True
if shuffle_data:
d = list(zip(datad, labelsd))
c = list(zip(datac, labelsc))
e = d + c
shuffle(e)
data, labels = zip(*e)del datad
del datac
del addrsd
del addrsc
Y_train = np.array(labels)
X_train = np.array(data, dtype="int8")#preprocess for Resnet- 50
X_train = preprocess_input(X_train)
定义架构的代码片段:
# Two inputs one each - left and right image
left_input = Input((224,224,3))
right_input = Input((224,224,3))#Import Resnetarchitecture from keras application and initializing each layer with pretrained imagenet weights.'’'
Please note that it’s usually better to intialize the layers with imagenet initializations than random. While training I will be updating the weights for each layer in each epoch. we don’t want to confuse this activity with transfer learning as I am not freezing any layer but initilializing each layer with imagenet weights
'’'convnet = resnet50.ResNet50(weights='imagenet', include_top=False, input_shape=(224,224,3))# Add the final fully connected layersx = convnet.output
x = Flatten()(x)
x = Dense(1024, activation="relu")(x)
preds = Dense(18, activation='sigmoid')(x) # Apply sigmoid
convnet = Model(inputs=convnet.input, outputs=preds)#Applying above model for both the left and right images
encoded_l = convnet(left_input)
encoded_r = convnet(right_input)# Euclidian Distance between the two images or encodings through the Resnet-50 architectureEuc_layer = Lambda(lambda tensor:K.abs(tensor[0] - tensor[1]))# use and add the distance function
Euc_distance = Euc_layer([encoded_l, encoded_r])#identify the prediction
prediction = Dense(1,activation='sigmoid')(Euc_distance)#Define the network with the left and right inputs and the ouput prediction
siamese_net = Model(inputs=[left_input,right_input],outputs=prediction)#define the optimizer. Here I have used SGD with nesterov momentum optim = optimizers.SGD(lr=0.001, decay=.01, momentum=0.9, nesterov=True)#compile the network using binary cross entropy loss and the above optimizer siamese_net.compile(loss="binary_crossentropy",optimizer=optim,metrics=[’accuracy’])
现在,我已经创建了图像对。将有两个标签——1 和 0,或者我们可以说输出的正或负标签或类别。
创建测试训练数据集的代码段
image_list = X_train[:180]
label_list = Y_train[:180]left_input = []
right_input = []
targets = []#Number of pairs per image
pairs = 8#create the dataset to train on
for i in range(len(label_list)):
for j in range(pairs):
# we need to make sure that we are not comparing with the same image
compare_to = i
while compare_to == i:
compare_to = random.randint(0,179)
left_input.append(image_list[i])
right_input.append(image_list[compare_to])
if label_list[i] == label_list[compare_to]:
# if the images are same then label - 1
targets.append(1.)
else:
# if the images are different then label - 0
targets.append(0.)
#remove single-dimensional entries from the shape of the arrays and making them ready to create the train & datasets
#the train data - left right images arrays and target label
left_input = np.squeeze(np.array(left_input))
right_input = np.squeeze(np.array(right_input))
targets = np.squeeze(np.array(targets))# Creating test datasets - left, right images and target labeldog_image = X_train[4] #dog_image = 1, cat_image = 0test_left = []
test_right = []
test_targets = []for i in range(len(Y_train)-180):
test_left.append(dog_image)
test_right.append(X_train[i+180])
test_targets.append(Y_train[i+180])test_left = np.squeeze(np.array(test_left))
test_right = np.squeeze(np.array(test_right))
test_targets = np.squeeze(np.array(test_targets))
在 GPU 中训练网络的代码
import tensorflow as tf
import os
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
session = tf.Session(config=config)
#from keras_input_pipeline import *
os.environ['CUDA_VISIBLE_DEVICES'] = '1'siamese_net.summary()
with tf.device('/gpu:1'):
siamese_net.fit([left_input,right_input], targets,
batch_size=16,
epochs=30,
verbose=1,
validation_data=([test_left,test_right],test_targets))
结果:
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_10 (InputLayer) (None, 224, 224, 3) 0
__________________________________________________________________________________________________
input_11 (InputLayer) (None, 224, 224, 3) 0
__________________________________________________________________________________________________
model_7 (Model) (None, 18) 126367634 input_10[0][0]
input_11[0][0]
__________________________________________________________________________________________________
lambda_4 (Lambda) (None, 18) 0 model_7[1][0]
model_7[2][0]
__________________________________________________________________________________________________
dense_12 (Dense) (None, 1) 19 lambda_4[0][0]
==================================================================================================
Total params: 126,367,653
Trainable params: 126,314,533
Non-trainable params: 53,120
__________________________________________________________________________________________________
Train on 1440 samples, validate on 22 samples
Epoch 1/10
1440/1440 [==============================] - 91s 64ms/step - loss: 0.7086 - acc: 0.5354 - val_loss: 0.6737 - val_acc: 0.5455
Epoch 2/10
1440/1440 [==============================] - 76s 53ms/step - loss: 0.5813 - acc: 0.7049 - val_loss: 0.6257 - val_acc: 0.5909
Epoch 3/10
1440/1440 [==============================] - 76s 53ms/step - loss: 0.4974 - acc: 0.8257 - val_loss: 0.6166 - val_acc: 0.5909
Epoch 4/10
1440/1440 [==============================] - 76s 53ms/step - loss: 0.4494 - acc: 0.8799 - val_loss: 0.6190 - val_acc: 0.5909
Epoch 5/10
1440/1440 [==============================] - 76s 53ms/step - loss: 0.4190 - acc: 0.9042 - val_loss: 0.5966 - val_acc: 0.6364
Epoch 6/10
1440/1440 [==============================] - 76s 53ms/step - loss: 0.3968 - acc: 0.9243 - val_loss: 0.5821 - val_acc: 0.6818
Epoch 7/10
1440/1440 [==============================] - 76s 53ms/step - loss: 0.3806 - acc: 0.9368 - val_loss: 0.5778 - val_acc: 0.6818
Epoch 8/10
1440/1440 [==============================] - 76s 53ms/step - loss: 0.3641 - acc: 0.9535 - val_loss: 0.5508 - val_acc: 0.7273
Epoch 9/10
1440/1440 [==============================] - 76s 53ms/step - loss: 0.3483 - acc: 0.9715 - val_loss: 0.5406 - val_acc: 0.7273
Epoch 10/10
1440/1440 [==============================] - 76s 53ms/step - loss: 0.3390 - acc: 0.9778 - val_loss: 0.5341 - val_acc: 0.7273
我们可以看到,仅用 100 张猫和 100 张狗的图像,我们就在 8 个时期的验证数据集中实现了 72%的准确率,这些图像对是从 200 张图像中创建的。
结论:
我写这篇文章的动机是,我们通常没有成百上千的高质量标记图像来创建健壮的基于 CNN 的分类器。或者,我们可以用很少的图像来训练一个广泛用于面部识别的连体网络,以建立一个分类器。我已经在我的一个材料缺陷检测用例中使用了这种技术,在这个用例中,我们几乎没有缺陷材料的可用图像。为了保持作品的保密性,我在这个博客中用猫和狗的图像来演示这个讨论。如果你喜欢我的博客,请点击拍手按钮,并请继续关注我们未来的博客,因为我经常在机器学习和深度学习方面发表博客。
解开缠绕:NEURIPS 的脚注— 2019
Credit: ‘ Striving for Disentanglement’ by Simon Greig — https://www.flickr.com/photos/xrrr/500039281/
TL;博士:解开,解开。通过这篇博文,我打算尝试总结今年在 2019 年温哥华 NEURIPS-2019 上发表的关于深度学习中的解纠缠的十几篇论文。
充满论文摘要和备忘单的配套 Github repo:https://Github . com/vinay prabhu/distanglement _ neur IPS _ 2019/
背景:表象学习中的解开
在会议周的周四晚上,当我在温哥华会议中心巨大的东展厅的海报会议上闲逛时,我意识到我在过去几天里偶然发现了可能是第五张海报,这需要对作者们工作的一个解开框架进行分析。
Fig 1: (Yet another) Poster on disentanglement at this year’s NEURIPS
快速查看一下的进程,我得到了这个令人震惊的统计数据:今年共有十几篇标题为“解开”的论文被接受。在众多的车间里,我至少还偶然发现了一些。(2017 年 NEURIPS 研讨会期间有 20 多篇论文和演讲,主题是“学习解开表征:从感知到控制”——https://sites.google.com/view/disentanglenips2017,我们今年还举办了一场挑战研讨会:https://www . ai crowd . com/challenges/neur IPS-2019-解开表征-挑战)
我第一次感受到这个术语在统计学习中的用法是在我在 CMU 大学博士旅程的最后阶段(大约 2013 年),当时我读了 Yoshua Bengio 的《深度学习表征:展望》一书,他在书中强调了“成为”的必要性…学习理清观察数据背后的变异因素。(我多么希望他仍然撰写这样的单作者论文)
事实证明,也许让物理学家很懊恼的是,如果你正致力于从 MNIST 上的数字类型中梳理出视觉风格,或者从西里巴上的面部形状中分离出人体和面部特征图像中的形状和姿势,或者努力解开两种组成化合物的混合比例和环境因素(例如为微结构生长而生成的图像中的热波动)的影响,你就在理清头绪。
对于这个术语的确切含义或捕捉其范围的度量标准似乎没有达成共识,斯塔法诺·索阿托在 IPAM 的演讲中的这张相当有趣/尖刻的幻灯片证实了这一点(参考下面的播放列表)
Fig 2: Invariance and disentanglement in deep representations
也就是说,这不是一个存在仅仅少量的经验实验的案例,这些实验都使用他们自己定制的解开概念。事实上,人们已经提出了相当严格的框架,利用来自变分推理、Shannonian 信息论、群论和矩阵分解等领域的强大工具。Deepmind 对这一问题的群论处理似乎已经成为一个首选框架。如果你正在寻找一个简洁的 3 分钟回顾,请参考这个视频,我在西蒙斯学院的一个研讨会上看到的(大约 7 分钟)。(可以在这里找到来自 Deepmind group 的主要作者之一的非常详细的演讲)
Fig 3: Group theoretic framework for disentanglement
对提交的论文的鸟瞰
下面的图 4 是 12 篇论文的鸟瞰图。我粗略地将它们分成两个小节,这取决于的主要感知的论文目标(从我的拙见来看)是分析和/或评论一个预先存在的框架的属性,还是利用一个框架并将其应用于一个有趣的问题领域。请记住,这无疑是一个相当简单的分类,对于面向应用的论文是否对所使用的框架进行了评论和分析,或者分析/评论论文是否不包括现实世界的应用,这并没有很大的指导意义。
Fig 4: Disentanglement papers categorization (NEURIPS -2019)
(可以在这里找到论文链接的 pdf 版本:https://github . com/vinay prabhu/distanglement _ neur IPS _ 2019/blob/master/distanglement _ papers _ tree-diagram . pdf)
他们所说的解开是什么意思?
为了总结这些论文中使用解纠缠的上下文,我创建了一个查找表(见表 1)。在那些作者明确没有专门的小节来定义的情况下,我临时拼凑并提取了要点(因此有了“即兴解释”)。
Table-1(a) Disentanglement context in the application papers
Table-1(b) Disentanglement context in the analysis papers
可复制性和开源代码:
鉴于开源用于产生结果的代码的强劲增长趋势,12 个作者组中的 10 个也共享了他们的 github repos。下面的表 2 显示了这一点:
Table-2: Papers and the open-source code links
现在怎么办?一些想法…
[这里有一些涂鸦,试图让我自己更认真地工作。请半信半疑地接受这些或 12:)
1:调查报告,详细说明要使用的定义、框架和指标。
2:使用卡纳达语-MNIST 语数据集解开作者/写作风格/原籍国。(65 名印度本土志愿者和 10 名美国非本土志愿者)
https://github.com/vinayprabhu/Kannada_MNIST
3:有点令人惊讶的是,没有人尝试抛出一个 K 用户干扰信道模型进行纠缠,并看看类似 https://arxiv.org/pdf/0707.0323.pdf 的干扰对齐技巧是否适用于类似 Dsprites 的数据集
4:从步态表征中分离鞋类型、口袋和设备位置
5:连接与(高光谱)解混 /盲源分离和解纠缠表示学习相关的工作主体。
资源列表:
充满论文摘要和备忘单的配套 github repo。
[## vinayprabhu/distanglement _ neur IPS _ 2019
TL;DR - On 解开解开论文的动物园在这次 NEURIPS(2019)期间,我遇到了大量的…
github.com](https://github.com/vinayprabhu/Disentanglement_NEURIPS_2019)
A.数据集入门:
[1]https://www.github.com/cianeastwood/qedr
https://github.com/deepmind/dsprites-dataset
https://github.com/rr-learning/disentanglement_dataset
(neur IPS 2019:distanglement 的主要道具也向组织者挑战他们共享的资源!)
链接:https://www . ai crowd . com/challenges/neur IPS-2019-distanglement-challenge
[## Google-research/distanglement _ lib
此时您不能执行该操作。您已使用另一个标签页或窗口登录。您已在另一个选项卡中注销,或者…
github.com](https://github.com/google-research/disentanglement_lib/tree/master/disentanglement_lib/evaluation/metrics)
B.视频播放列表:
[1] Y. Bengio 的《从深度学习解开表征到更高层次的认知》
https://www.youtube.com/watch?v=Yr1mOzC93xs&t = 355 秒
[2]\贝塔-VAE(deep mind):【https://www.youtube.com/watch?v=XNGo9xqpgMo】T2
[3]柔性公平表征解缠学习:【https://www.youtube.com/watch?v=nlilKO1AvVs】T4&t = 27s
[4]用于姿态不变人脸识别的解纠缠表征学习 GAN:https://www.youtube.com/watch?v=IjsBTZqCu-I
【5】深层表象中的不变性与解纠缠(趣谈)https://www.youtube.com/watch?v=zbg49SMP5kY
(来自 NEURIPS 2019 作者)
[1]审计模型预测论文:https://www.youtube.com/watch?v=PeZIo0Q_GwE
[2]对 Olivier Bachem 的 Twiml 采访(在 NEURIPS-19 上有 3 篇关于该主题的论文):https://www.youtube.com/watch?v=Gd1nL3WKucY
C.备忘单
Cheat sheet-1: All the abstracts! (Print on A3/2)
Cheat sheet-2: All the essences!
机器学习中的距离函数:用简单语言编写的初级读本,动作点少。
https://www.machinelearningplus.com/statistics/mahalanobis-distance/
大多数数据科学问题的关键是定义给定观测值之间的距离或相似性函数。相似性度量将用于度量在给定的 n 维空间中观察值有多近或多远。对于给定的问题,有许多距离函数可供使用,我不想在这里详细描述所有的距离函数,而只是关于这些函数的简短信息,这些信息是我在数据智能和分析的课程中有机会看到的整体观点(由 IITH 的sob Han Babu博士提供)。本指南面向入门级数据科学学生。
欧几里德距离
这等于两点之间的直线距离或最短距离或位移(…假设是二维的,但也可以是多维的)。这是成对的距离,也是测量两点间距离的默认度量。它对许多任务都很有用,但在数据具有以下特征的情况下要谨慎使用
a)极端或更多异常值。由于该距离是 L2 范数,当数据点之间存在异常值时,异常值的影响被放大。
b)相关性。这个距离没有考虑到观察中的变量可以相互关联(…为什么?)
c)如果数据集中的变量数量很多(比如> 7),余弦距离优于欧几里德距离。为什么?
d)当然,根据 L2 范数的定义,变量是彼此相减的,这使得所有的变量必须是实值。如果欧几里德距离必须应用于分类数据,那么数据首先必须被编码成实数值。然而,这不是衡量分类变量之间距离的最佳方式。(…为什么?)
马哈拉诺比的距离:
该距离是相关性调整后的距离(…(欧几里得)在一对给定的数据点之间。要了解为什么需要去相关,请访问此 页面 查看示例。给出的示例说明了使用欧几里德距离对变量相关的点进行聚类时的问题。
a)如果数据是数字的、相关的且变量数量适中,则最好使用马哈拉诺比距离(…<10)
余弦距离:
顾名思义,它与三角余弦函数有些关系。角的余弦由邻边的长度除以斜边给出。当你考虑两点之间的距离时,想象从原点出发的两个向量,那么向量之间的角度的余弦由点积除以它们的长度给出。这实际上会减轻长短向量的影响,具有异常值的数据点之间的余弦距离不会像欧几里德距离那样被放大。
a)如果有大量变量,最好使用余弦距离(…比方说> 8)。
匹配和雅克卡系数:
匹配系数和 Jaccard 系数在推导过程中非常接近,用于衡量分类变量何时出现在数据中。
让我们以只有两个级别的三个变量为例:
X1 : M,N,Y,N
X2: F,Y,Y,N
这些情况可以编码如下(…任意考虑 M,Y 为 1,N,F 为 0):
1,0,1,0
0,1,1,0
匹配系数:(1 和 0 中的相同匹配(…两者/所有类))/(所有类)= 2/4
Jaccard 系数:(1 中的相同匹配(…仅正/利息类别))/所有类别= 1/4
a)使用 Jaccards coef。在匹配 1 比匹配 0 具有更强的相似直觉的情况下
b)如果数据既是 Jaccard 又是匹配 coef,则使用 Kendall Tau 距离。将数据视为名义类。
混合数据
当数据中既有分类变量又有数值变量时,通常采用以下方法。
a)使用分类的一键编码或其他编码方法将所有内容转换成数字,并使用欧几里德、马氏、余弦或其他(…曼哈顿、相关性等)来测量数据点的距离/相似性。
b)将数据划分为两组变量,即数值变量和分类变量,并使用适当的距离度量分别计算两个分区之间的距离。然后组合成单个测量值,
(W1 * P1 + W2 * P2) / (W1+W2)。
更多参考资料:
许多机器学习算法——有监督的或无监督的,使用距离度量来了解输入数据…
towardsdatascience.com](/importance-of-distance-metrics-in-machine-learning-modelling-e51395ffe60d) [## 机器学习中如何测量距离
这完全取决于你的观点
towardsdatascience.com](/how-to-measure-distances-in-machine-learning-13a396aa34ce)
提取 BERT——如何使用逻辑回归实现 BERT 性能
伯特很棒,无处不在。看起来任何 NLP 任务都可以从使用 BERT 中受益。作者向展示了事实的确如此,从我的经验来看,它像魔法一样有效。它易于使用,处理少量数据,并支持许多不同的语言。似乎没有任何理由不在任何地方使用它。但实际上,是有的。可惜,在实际操作中,并不是那么微不足道。BERT 是一个庞大的模型,超过 1 亿个参数。不仅我们需要一个 GPU 来微调,而且在推理时间上,一个 CPU(甚至很多)是不够的。这意味着,如果我们真的想在任何地方使用 BERT,我们需要在任何地方安装一个 GPU。这在大多数情况下是不切实际的。2015 年,的这篇论文(由 Hinton 等人完成)介绍了一种将一个非常大的神经网络的知识提取到一个小得多的神经网络中的方法,比如教师和学生。方法很简单。我们使用大的神经网络预测来训练小的。主要思想是使用原始预测,即最终激活函数(通常是 softmax 或 sigmoid)之前的预测。假设通过使用原始值,模型能够比使用“硬”预测更好地学习内部表示。Sotmax 将这些值归一化为 1,同时保持最大值较高,并将其他值减小到非常接近零的值。零中的信息很少,所以通过使用原始预测,我们也可以从非预测类中学习。作者在包括 MNIST 和语音识别的几个任务中显示了良好的结果。
不久前,这篇论文的作者将同样的方法应用于…伯特。他们表明,通过将来自 BERT 的信息提取到一个更小的 BiLSTM 神经网络中,我们可以在特定任务中获得相同的性能(甚至更好)。您可以在下表中看到他们的结果。使用 BiLSTM-Soft 实现了最佳性能,这意味着“软预测”,即训练原始逻辑而不是“硬”预测。数据集有: SST-2 是斯坦福情感树库 2, QQP 是 Quora 问题对, MNLI 是多体裁自然语言推理。
在这篇文章中,我想将 BERT 提炼为一个更简单的逻辑回归模型。假设您有一个相对较小的标注数据集和一个大得多的未标注数据集,构建模型的一般框架是:
- 在带标签的数据集上创建一些基线
- 通过在标记集上微调 BERT 来构建一个大模型
- 如果你得到了好的结果(比你的基线更好),使用大模型计算你的未标记集合的原始对数
- 在现在伪标记的集合上训练小得多的模型(逻辑回归)
- 如果你得到了好的结果,可以在任何地方部署小型模型!
如果你对微调 BERT 的基础教程感兴趣,请查看我之前的帖子:
使用 BERT 进行简单文本分类的分步教程
towardsdatascience.com](/bert-to-the-rescue-17671379687f)
我想解决同样的任务(IMDB 审查情绪分类),但与逻辑回归。你可以在这本笔记本里找到所有的代码。
和以前一样,我将使用torchnlp
来加载数据,使用优秀的 PyTorch-Pretrained-BERT 来构建模型。
训练集中有 25,000 条评论,我们将只使用 1000 条作为标记集,另外 5,000 条作为未标记集(为了加快速度,我也只从测试集中选择 1000 条评论):
train_data_full, test_data_full = imdb_dataset(train=True, test=True)
rn.shuffle(train_data_full)
rn.shuffle(test_data_full)
train_data = train_data_full[:1000]
test_data = test_data_full[:1000]
我们做的第一件事是使用逻辑回归创建基线:
我们得到的结果并不太好:
precision recall f1-score supportneg 0.80 0.80 0.80 522
pos 0.78 0.79 0.78 478accuracy 0.79 1000
下一步,是微调 BERT,我将跳过这里的代码,你可以看到它的笔记本或更详细的教程在我以前的职位。结果是一个名为BertBinaryClassifier
的训练模型,它使用 BERT 和一个线性层来提供正/负分类。这种模式的表现是:
precision recall f1-score supportneg 0.88 0.91 0.89 522
pos 0.89 0.86 0.88 478accuracy 0.89 1000
好多好多!如我所说—神奇:)
现在到了有趣的部分,我们使用未标记的集合,并使用我们的微调 BERT 模型来“标记”它:
我们得到:
precision recall f1-score supportneg 0.87 0.89 0.88 522
pos 0.87 0.85 0.86 478accuracy 0.87 1000
没有原来微调过的 BERT 好,但是比基线好多了!现在我们准备将这个小模型部署到生产环境中,享受良好的质量和推理速度。
这里有另外一个理由 5 个理由“逻辑回归”应该是你成为数据科学家的第一件事😃
用空间提取 BERT 模型
如何训练可与大型迁移学习模型相媲美的小型神经网络
迁移学习是近年来自然语言处理领域最具影响力的突破之一。发布不到一年,谷歌的 伯特 及其后代( 罗伯塔 、XLNet等。)称霸大部分 NLP 排行榜。虽然将这些庞大的模型投入生产是一件令人头痛的事情,但有各种解决方案可以显著减小它们的尺寸。在NLP Town我们成功地应用了模型提取来训练 spaCy 的文本分类器,使其在产品评论的情感分析上表现得几乎和 BERT 一样好。
最近,自然语言处理的标准方法发生了巨大的变化。尽管直到一年前,几乎所有的 NLP 模型都是完全从零开始训练的(通常除了预训练的单词嵌入),但今天最安全的成功之路是下载一个预训练的模型,如 BERT,并针对特定的 NLP 任务进行微调。因为这些迁移学习模型已经看到了大量未标记文本的集合,他们已经获得了许多关于语言的知识:他们意识到单词和句子的意义,共指,句法等等。尽管这场革命可能令人兴奋,但像 BERT 这样的模型有太多的参数,它们相当慢而且资源密集。至少对于某些 NLP 任务来说,微调 BERT 就像用大锤砸坚果。
大锤模型
大多数迁移学习模型都很庞大。伯特的base
和multilingual
型号是变形金刚,12 层,隐藏尺寸 768,12 个自关注头——总共不少于 1.1 亿个参数。BERT-large
体育参数高达 340 米。尽管如此,与更近的模型相比,BERT 仍然相形见绌,例如脸书的 XLM,参数为 665M】和 OpenAI 的 GPT-2,参数为 774M 。看起来这种向更大模型发展的趋势肯定会持续一段时间。
General models like BERT can be finetuned for particular NLP tasks (from: Devlin et al. 2018)
当然,语言是一种复杂的现象。很明显,参数相对较少的更传统、更小的模型将无法处理您扔给它们的所有 NLP 任务。然而,对于单个文本分类或序列标记任务,是否真的需要 BERT 及其同类产品的所有表达能力是值得怀疑的。这就是为什么研究人员已经开始研究如何缩小这些模型的尺寸。三种可能的方法已经出现:量化通过用更少的比特编码来降低模型中权重的精度,修剪完全移除模型的某些部分(连接权重、神经元甚至全权重矩阵),而在蒸馏中,目标是训练小模型来模仿大模型的行为。
用于情感分析的模型提取
在我们 NLP Town 的一个夏季项目中,我们和实习生 Simon Lepercq 一起着手调查模型提取对情感分析的有效性。就像的庞、李和韦思亚纳森在他们的开创性论文中所说的那样,我们的目标是建立一个能够区分正面和负面评论的自然语言处理模型。我们收集了六种语言的产品评论:英语、荷兰语、法语、德语、意大利语和西班牙语。我们给一两星的评论贴上标签negative
,给四星或五星的评论贴上标签positive
。我们用 1000 个例子进行训练,1000 个例子进行开发(早期停止),1000 个例子进行测试。
第一步是确定我们任务的基线。在我们的每个数据集中,有相同数量的正面和负面例子,随机基线将获得平均 50%的准确性。作为一个简单的机器学习基线,我们训练了一个空间文本分类模型:一个词汇袋模型的堆叠集成和一个相当简单的具有均值汇聚和注意力的卷积神经网络。为此,我们添加了一个节点的输出层,并让模型在输出得分高于 0.5 时预测positive
,否则预测negative
。这个基线在测试数据上达到了 79.5%(意大利语)和 83.4%(法语)之间的准确率——不算差,但也不是很好的结果。
BERT gives an average error reduction of 45% over our simpler spaCy models.
由于它的训练集很小,我们的挑战非常适合迁移学习。即使诸如巨著之类的测试短语没有出现在训练数据中,BERT 也已经知道它类似于优秀小说、精彩阅读,或者在训练集中很可能出现的另一个类似短语。因此,它应该能够比从零开始训练的简单模型更可靠地预测一个看不见的评论的评级。
为了微调 BERT,我们改编了 PyTorch-Transformers 库中的BERTForSequenceClassification
类用于二进制分类。对于所有六种语言,我们都进行了微调BERT-multilingual-cased
,这是谷歌目前推荐的多语言模式。结果证实了我们的预期:BERT 的准确率在 87.2%(荷兰语)和 91.9%(西班牙语)之间,比我们最初的 spaCy 模型平均高出 8.4%。这意味着 BERT 几乎将测试集上的错误数量减半。
模型蒸馏
不幸的是,伯特也不是没有缺点。我们的六个微调模型中的每一个都占用了将近 700MB 的磁盘空间,它们的推理时间比 spaCy 的要长得多。这使得它们很难部署在资源有限的设备上,或者对许多用户并行使用。为了应对这些挑战,我们求助于模型提炼:我们让微调过的 BERT 模型充当老师,让 spaCy 更简单的卷积模型充当学习模仿老师行为的学生。我们遵循由唐等人(2019) 描述的模型提取方法,该方法表明可以将 BERT 提取为简单的 BiLSTM,并获得类似于具有 100 倍以上参数的 ELMo 模型的结果。
Distillation is a process that extracts the essential aspects from a mixture.
然而,在我们开始训练小模型之前,我们需要更多的数据。为了学习和模仿 BERT 的行为,我们的学生需要看到比原始训练集更多的例子。因此,Tang 等人应用三种方法进行数据扩充(在原始训练数据的基础上创建合成训练数据):
- 屏蔽训练数据中的随机单词。比如我喜欢这本书现在变成了我【屏蔽】这本书。
- 将训练数据中的其他随机词替换为另一个词性相同的词。比如我喜欢这本书变成了我喜欢这个画面。
- 从训练样本中随机抽取长度为 1 到 5 的 n-gram 。
由于我们数据集中的产品评论可能相当长,我们在上面三种方法的基础上增加了第四种方法:
- 从训练示例中随机抽取一个句子。
The process of model distillation.
这些增强方法不仅帮助我们创建了一个比原来大很多倍的训练集;通过采样和替换训练数据的各个部分,他们还通知学生模型什么单词或短语对其老师的输出有影响。此外,为了给它尽可能多的信息,我们没有给学生看老师预测的标签,而是显示它的精确输出值。通过这种方式,小模型可以了解最佳类的确切概率,以及它与其他类相比的情况。唐等(2019) 用老师的逻辑来训练小模型,但是我们的实验表明使用概率也能给出非常好的结果。
蒸馏结果
模型提取的一个很大的优点是它是模型不可知的:教师模型可以是一个黑盒,学生模型可以有任何我们喜欢的架构。为了使我们的实验简单,我们选择了与基线相同的 spaCy 文本分类器作为我们的学生。训练程序也保持不变:我们使用相同的批量大小、学习率、退出和损失函数,当开发数据的准确性停止上升时,停止训练。我们使用上面的增强方法为每种语言收集了大约 60,000 个例子的合成数据集。然后,我们收集了微调后的 BERT 模型对这些数据的预测。与原始训练数据一起,这成为我们较小空间模型的训练数据。
The distilled spaCy models perform almost as well as the original BERT models.
尽管设置很简单,但提取的空间模型明显优于我们的初始空间基线。平均而言,他们的准确度提高了 7.3%(仅比 BERT 模型低 1%),误差减少了 39%。他们的表现表明,对于情感分析这样的特定任务,我们不需要 BERT 提供的所有表达能力。训练一个性能几乎和 BERT 一样好的模型是完全可能的,但是参数要少得多。
结论
随着大型迁移学习模型的日益流行,将 NLP 解决方案投入生产变得越来越具有挑战性。然而,像模型提取这样的方法表明,对于许多任务,你不需要数以亿计的参数来实现高精度。我们在六种语言中进行的情感分析实验表明,训练 spaCy 的卷积神经网络与更复杂的模型架构(如 BERT 的模型架构)相匹敌是可能的。将来,我们希望在 NLP 镇更详细地研究模型提取。例如,我们旨在找出什么样的数据扩充方法最有效,或者我们需要多少合成数据来训练一个更小的模型。
使用 Gabor CNN 辨别名人的长相
摘要:
在这篇论文中,我深入研究了两个名人长得很像的现象,在这种情况下,杰西卡·查斯坦和布莱丝·达拉斯·霍华德是一个用例,以开发一种足够复杂的算法来区分人的面部区域,甚至连人类都感到困惑。神经网络是目前可能的最复杂的鉴别器,考虑到同样的计算能力,更重要的是提供给 CNN 的数据质量。这里,女演员的每个图像被转换成它的 Gabor 滤波器表示,每个表示具有不同方向的 16 个 Gabor 滤波器。Gabor 滤波器似乎是生物图像的最复杂的特征提取方法,因为它们捕捉精细的纹理细节。训练有素的 CNN 能够在 80%的情况下预测名人的正确姓名。人们发现,CNN 更准确地报道了随着时间推移体重波动最小的女演员。可以得出结论,在人脸的 Gabor 表示上训练 CNN 是面部识别的有效混合技术,只要它被给予最新的面部表示。
简介:
面部识别是现代计算机视觉的一个有趣的领域,因为面部甚至不是一个人最显著的生物特征;最准确的鉴别器是虹膜图案或指纹。然而,这种精确的数据并不总是由感兴趣的对象自愿提供的,因此,重要的研究已经进入了从人脸提取足以区分个人的特征的方法。
从目前的参考文献来看,混合方法似乎能产生更好的结果。在[1]中,PCA +CNN 或 SOM+ CNN 方法都优于特征脸技术,即使给定较小的样本量。此外,小波分解方法,无论是哈尔变换还是双正交变换,似乎都是捕捉生物图像(如人脸)细微纹理的最佳特征提取方法[2]。在所有这些中,最健壮的似乎是 Gabor 滤波器。Gabor 滤波器组的组成[3]考虑了其他小波变换不能捕捉的生物纹理中的尺度和方向变化。
本文试图回答这个问题:如果我将最复杂的频率分解(Gabor 滤波器)与胜过任何其他方法的机器学习方法(卷积神经网络[6])相结合,我能否设计出一个足够好的系统来区分两张容易相互混淆的脸(参见图 1 中女演员的并排比较)?
Fig.1 Jessica Chastain and Bryce Howard bare close resemblance to each other
方法:
在谷歌图片上为每个女演员收集了大约 200 张图片,用 Chome 插件下载:Fatkun 批处理下载器下载。我之所以决定这个粗略的数字,是因为我的教授告诉我,每节课至少需要 80 张图片。我用 Python Jupyter notebook 编写了这个系统。这些图像使用 OpenCV 的内置人脸检测算法进行迭代,该算法利用 Haar 级联实现多尺度检测[4]。该函数显然使用皮肤的独特色度值进行检测,例如 YCbCr [5],因为有时手或胸部会被裁剪。然后手动迭代裁剪后的图像,以消除不需要的样本,从而获得高质量的训练集。
然后为每个裁剪的人脸生成 Gabor 滤波器组。该银行由 16 个不同方向的变化;其他参数的值保持不变,因为在裁剪的面部区域中缺少比例变化。标准偏差是通过计算训练集的平均标准偏差(3)得出的。核的大小被设置为标准差的 10 倍,即 30。16 个不同的方向是π内的 16 个分度。λ,正弦波长设置为 4,控制伽柏滤波器高度的γ,设置为 0.04;伽马值越小,伽柏值越高。ψ,相位偏移设置为π/4(参见图 2 中 Gabor 滤波器组的程序遍历和直观表示)。
Fig 2. Methodology work flow and description of Gabor filter bank parameters
一种热编码被用来标记图像,将[1,0]附加到属于 Bryce Dallas 的每个 Gabor 滤波器,将[0,1]附加到 Jessica 的滤波器。然后,每个 Gabor 表示被转换成它的灰色对应物,并被调整到 64x64。标记和调整大小后,图像被加在一起,然后混洗。在被输入 CNN 之前,这些图像被从标签中分离出来。在训练期间,CNN 被评估其将图像正确匹配到其正确标签的能力。
使用了相对经典的 CNN。它有 3 个卷积层,后面是 2 个密集层。Dropout 是随机断开多少节点以避免过拟合的概率,0.25 和 0.5 分别应用于最终的两个密集层。学习速率,即相对于损失梯度调整权重的速率,被设置为 0.001。Adam optimizer 用于其自适应优化功能(参见图 3)。
Fig. 3 Summary of the neural network
该模型被训练了 50 个时期。它相对较快地收敛到高精度(见图 4)。
Fig. 4 History of the training
结果:
我决定在训练集和测试集之间使用 80:20 的分割,所以我为每个女演员组成了一个包含 40 个图像的测试集。为了便于可视化表示,我没有打乱测试集。布莱斯达拉斯的模型预测是体面的(见图 5)。
Fig. 5 Prediction results for Bryce Dallas Howard
Jessica 的结果稍好一些(见图 6)。
Fig. 6 Prediction results for Jessica Chastain
总之,该模型大约 80%准确(参见图 7 的混淆矩阵)。
Fig. 7 Confusion matrix for the model prediction.
讨论:
Gabor 滤波器组和 CNN 的结合能否产生一个能够区分两个长相相似的人的系统?根据这个实验,这绝对是一个有效的方法。复杂的算法肯定可以根据特征提取来识别个人面部。这个实验的限制是数据量有限。一场普通的 Kaggle 图像比赛会有比这更多的数据集。来自互联网的图像质量也不理想。一个有趣的发现是,布莱丝·达拉斯·霍华德的结果不太准确;经过进一步的研究,我发现她的体重增加了很多,其中一些照片进入了训练集。在未来,我想添加形状作为一个特征来训练 CNN,以进一步微调其准确性。
结论:
如果有足够的计算能力,正确的算法和高质量的数据,计算机肯定可以区分个人。尽管指纹和虹膜模式仍然是识别个人的最佳特征,但从人脸中提取正确的特征,然后使用神经网络提供了识别个人的替代方法。考虑到有限的数据和计算能力,提取正确的特征仍然非常重要。这里表明,使用 Gabor 滤波器提取 16 个方向的面部纹理产生了体面的结果。这表明,教授神经网络最重要的特征,无论是纹理还是形状,都比拥有庞大的数据量重要。
参考文献:
[1] Sujata G. Bhele 和 V.H.Mankar .关于人脸识别技术的综述论文。《国际计算机工程高级研究杂志》2012 年 10 月第 1 卷第 8 期
[2]帕拉维·瓦德卡尔,梅加·万哈德。基于离散小波变换的人脸识别。国际先进工程技术杂志,Vol.III/第一期,2012 年 1 月-3 月
[3]尚振鸾,,张,周思岳,,,韩.Gabor 卷积网络。在arXiv:1705.01450 v32018 年 1 月 29 日。
[4]帕迪利亚、科斯塔·菲勒霍和科斯塔。用于人脸检测的 Haar 级联分类器的评估。*世界科学、工程与技术学会,《国际计算机与信息工程杂志》,*2012 年第 6 卷第 4 期
[5] Shruti D Patravali,J . M Wayakule,Apurva D Katre。使用 YCBCR 和 RGB 颜色模型的皮肤分割。国际计算机科学与软件工程高级研究杂志,第 4 卷,第 7 期,2014 年 7 月。
[6] B .戈皮卡、K .斯里拉克斯米、D .阿列赫亚、B .巴斯卡尔·拉奥、B .拉马·莫汉。基于 Gabor 特征提取和神经网络的人脸识别。电子与通信工程杂志,第 10 卷,第 2 期,版本。第二卷(2015 年 3 月至 4 月),第 68–72 页。
使用深度学习的分心驾驶员检测
这篇博客和这个项目是由叶达鑫·阿萨利、阿波奥尔瓦·贾斯蒂、萨德哈娜·科尼、萨提亚·那仁·帕奇戈拉、&贾扬特·瑞辛尼在乔迪普·高什、
教授的指导下共同完成的。请按照这个 GitHub 资源库获取我们的实现代码
简介
驾驶汽车是一项复杂的任务,需要全神贯注。分心驾驶是指任何分散驾驶员对道路注意力的活动。几项研究已经确定了三种主要的分心类型:视觉分心(司机的眼睛离开道路)、手动分心(司机的手离开方向盘)和认知分心(司机的思想离开驾驶任务)。
美国国家公路交通安全管理局(NHTSA)报告称,2018 年有 36750 人死于机动车撞车事故,其中 12%是由于分心驾驶。发短信是最令人担忧的干扰。发送或阅读短信会让你的视线离开路面 5 秒钟。以每小时 55 英里的速度行驶,这相当于闭着眼睛行驶了整个足球场的长度。
许多州现在都有法律禁止开车时发短信、打手机和其他分心的事情。我们相信,计算机视觉可以增强政府的努力,以防止分心驾驶造成的事故。我们的算法自动检测司机的分心活动,并向他们发出警报。我们设想将这种产品嵌入汽车,以防止因分心驾驶而发生事故。
数据
我们获取了 StateFarm 数据集,该数据集包含由安装在汽车上的摄像机捕获的视频快照。训练集有 ~22.4 K 个在类间平均分布的已标记样本和 79.7 K 个未标记测试样本。共有 10 类图像:
Fig: Sample images from the training data
评估指标
在继续构建模型之前,选择正确的指标来衡量其性能是很重要的。准确性是我们首先想到的指标。但是,精度并不是分类问题的最佳度量。准确度仅考虑预测的正确性,即预测的标签是否与真实标签相同。但是,在评估模型的性能时,我们将驾驶员的行为分类为注意力分散的置信度非常重要。幸运的是,我们有一个度量标准来捕捉这一点— 日志丢失。
对数损失(与交叉熵相关)测量分类模型的性能,其中预测输入是 0 到 1 之间的概率值。我们的机器学习模型的目标是最小化这个值。完美的模型的对数损失为 0,并且随着预测概率偏离实际标签而增加。因此,当实际观察标记为 1 时,预测概率为 0.3 将导致高对数损失
Fig: Evaluation Metric
数据泄露
了解了需要实现的目标后,我们开始从头开始构建 CNN 模型。我们添加了常见的疑点——卷积批量标准化、最大池化和密集层。结果——在 3 个时期内,验证集的损失为 0.014,准确率为 99.6%。
Fig: Initial Model Results
嗯,我们考虑了一秒钟,意外地建造了世界上有史以来最好的 CNN 建筑。因此,我们使用这个模型预测了未标记测试集的类。
关键时刻
Fig: Class prediction by the model
哦,好吧。毕竟没有意外之喜。因此,我们更深入地研究了可能出现的问题,我们发现我们的训练数据中有同一个人在一个班级中的多幅图像,这些图像的角度和/或高度或宽度略有变化。这导致了数据泄漏问题,因为相似的图像也在验证中,即模型被训练了许多它试图预测的相同信息。
数据泄露解决方案
为了应对数据泄露的问题,我们根据个人 id 分割图像,而不是使用随机的 80-20 分割。
现在,当我们用修改后的训练集和验证集来拟合我们的模型时,我们看到了更真实的结果。我们实现了 1.76 的损耗和 38.5%的准确率。
Fig: Model fit after countering data leakage
为了进一步改善结果,我们探索使用经过试验和测试的深度神经网络架构。
迁移学习
迁移学习是一种方法,其中为相关任务开发的模型被重新用作第二个任务的模型的起点。我们可以重用为标准计算机视觉基准数据集(如 ImageNet 图像识别挑战)开发的预训练模型的模型权重。通常,激活 softmax 的最后一层会被替换,以适应数据集中的类数量。在大多数情况下,还会添加额外的层来针对特定任务定制解决方案。
考虑到开发用于图像分类的神经网络模型所需的大量计算和时间资源,这是深度学习中的一种流行方法。此外,这些模型通常在数百万张图像上进行训练,这在训练集很小的情况下尤其有用。这些模型架构中的大多数都是业经验证的赢家,我们利用的 VGG16、RESNET50、Xception 和 Mobilenet 模型在 ImageNet 挑战赛中取得了优异的成绩。
图像增强
Fig: Sample code for Image Augmentation
由于我们的训练图像集只有大约 22K 的图像,我们希望从训练集中综合获得更多的图像,以确保模型不会过度拟合,因为神经网络有数百万个参数。图像增强是一种通过执行诸如移动宽度和/或高度、旋转和缩放等动作来从原始图像创建更多图像的技术。参考这篇文章来了解更多关于图像增强的知识。
Fig: Types of image augmentation implemented in our dataset
对于我们的项目,图像增强有一些额外的优势。有时,来自两个不同类别的图像之间的差异可能非常微妙。在这种情况下,从不同的角度对同一幅图像进行多重观察会有所帮助。如果你看下面的图片,我们会发现它们几乎是相似的,但在第一张图片中,该课程是“讲电话——对”,第二张图片属于“发型和化妆”课程。
Fig: Sample of image class confusion (i) Taking on the phone (ii) Hair and Makeup
额外图层
为了最大化迁移学习的价值,我们添加了几个额外的层来帮助模型适应我们的用例。每层的目的:
- 全局平均池层仅保留每个补丁中值的平均值
- 下降层有助于控制过度拟合,因为它会降低一部分参数(额外提示:尝试不同的下降值是个好主意)
- 批量标准化层将输入标准化到下一层,这允许更快和更有弹性的训练
- 密集层是具有特定激活功能的规则全连通层
训练哪几层?
进行迁移学习时的第一个问题是,我们是应该只训练添加到现有架构中的额外层,还是应该训练所有层。自然地,我们从使用 ImageNet 权重开始,并且只训练新的层,因为要训练的参数数量会更少,并且模型会训练得更快。我们看到验证集的准确性在 25 个时期后稳定在 70% 。但是,通过训练所有层,我们能够获得 80%的准确率。因此,我们决定继续训练所有的层。
Fig: Comparison of model accuracy for final vs all trained layers
使用哪种优化器?
优化器通过在目标函数相对于参数的梯度的相反方向上更新参数来最小化由模型参数参数化的目标函数。想要了解更多关于不同优化器的工作原理,你可以参考这篇博客。
深度学习领域最流行的算法是 Adam,它结合了 SGD 和 RMS Prop。对于大多数问题,它一直比其他优化器表现得更好。然而,在我们的案例中,亚当表现出不稳定的下降模式,而 SGD 则在逐渐学习。通过做一些文献调查,我们发现在少数情况下 SGD 优于 Adam,因为 SGD 泛化得更好(链接)。由于 SGD 给出了稳定的结果,我们将其用于所有的模型。
Fig: Accuracy across epochs using: (i)Adam (ii)SGD
使用哪些架构?
我们尝试了多种迁移学习模型,其中权重来自 ImageNet 数据集上的训练,即预训练权重。
- VGG16 VGG16 型号有 16 层。它主要使用卷积技术以及零填充、丢弃、最大池化和扁平化。
Fig: VGG-16 Architecture
- RESNET50 RESNET50 是 VGG16 模型的扩展,有 50 层。为了解决训练更深的网络困难的问题,已经引入了具有参考层输入的“快捷连接”的前馈神经网络。
Fig: Residual learning: a building block
- 例外创建 RESNET 的目的是为了得到更深的网络,而创建例外是为了通过引入 深度可分卷积 得到更宽的网络。通过将标准卷积层分解为深度方向和点方向卷积,计算量显著减少。由于有多个过滤器查看同一级别,因此模型的性能也得到提高。****
Fig: Modified Depthwise Separable Convolution used in Xception
- MobileNet MobileNet是 Google 为 基于移动的视觉应用 开发的模型。事实证明,计算成本至少降低了 9 倍。MobileNet 使用深度方向可分离卷积来构建轻量级深度神经网络。它有两个简单的全局超参数,可以有效地在延迟和准确性之间进行权衡。
迁移学习模型的性能
Fig: Comparison of Transfer Learning Models. MobileNet has the minimum loss on the test set
比较最佳车型
虽然上面的每个体系结构都给了我们很好的结果,但是每个模型对于各个类的性能有很大的差异。从下表中,我们注意到不同的模型对每一类都有最好的精确度。因此,我们决定建立这些模型的集合。
Fig: Accuracy of different algorithms per class
图:特定类别的每个模型的准确性。“绿色”和“红色”表示精确度从高到低
集合模型
现在我们有了 7 个后验概率方差很大的最佳模型,我们尝试了多种集成技术来进一步改善对数损失。
- ****均值集成:这是最简单也是最广泛使用的集成方法,后验概率计算为组件模型预测概率的均值。
- ****修剪平均集合:这是通过从每个图像的组件模型中排除最大和最小概率的平均集合。这有助于进一步平滑我们的预测,从而降低测井损失值。
- ****用于组合的 KNN:由于这些图像都是在司机进行分散注意力的活动或驾驶时从视频片段中截取的,因此有大量来自同一类别的相似图像。基于这一前提,找到相似的图像并对这些图像的概率进行平均有助于我们平滑每一类的预测概率。
为了找到 10 个最近的邻居,我们使用来自 VGG16 迁移学习模型的倒数第二层的输出作为验证集的特征。
Fig: Output of KNN — 10 Nearest Neighbours
Fig: Comparison of ensemble models with MobileNet model as the benchmark
学习
我们相信,从我们的经验中获得的这些知识将有利于任何像我们一样第一次从事深度学习项目的人:
****1。使用 Pickle 文件:你可以为你的项目使用的一个免费资源是‘Google Colab’。由于并行计算,您可以访问 GPU,这有助于处理大量数据。使用 Colab 时,您可以通过一次性读取所有图像并将其保存在 pickle 文件中来执行必要的预处理步骤。这样,您可以通过直接加载 pickle 文件来继续您离开的地方。然后,您可以开始训练您的模型
**2。提前停止和回调:一般深度学习模型都是用大量的历元来训练的。在此过程中,模型可能会在几个时期内提高精度,然后开始发散。训练结束时存储的最终重量将不是最佳值,即它们可能不会给出最小的对数损失。我们可以使用 Keras 中的 回调 功能,该功能仅在一个时期后看到改进时才保存模型的权重。您可以通过使用提前停止,来减少训练时间,您可以设置模型停止看到任何改进后运行的时期数的阈值。
**3。均值或修整均值优于总体的 模型堆叠**:堆叠模型的输入具有高相关性,这会导致输出具有高方差。因此,在这种情况下,更简单的方法是最好的方法。
****4。永远不要忽略最终应用:对 7 个模型进行集成,然后对输出进行 KNN,这给了我们一个很好的分数,但是如果我们必须选择一个模型,它可以用来以最少的资源获得良好但更快的预测,Mobilenet 将是显而易见的选择。Mobilenet 是根据计算限制专门开发的,最适合汽车中的应用,并且在 7 个独立模型中具有最低的对数损失
我们认为,在汽车上安装一个带有摄像头的设备,跟踪司机的动作并提醒他们,可以帮助防止事故发生。
为了说明这一点,我们制作了一个小视频,演示如何使用我们的模型:
Sample video depicting predictions
参考文献:
- 斯坦福 CS231N 系列讲座为 CNN:https://www.youtube.com/watch?v=vT1JzLTH4G4&list = plzutmxvwsnxod 6 wndg 57 YC 3 zfx _ f-RYsq&index = 1
- 如何用 Keras 实现 CNN,tensor flow:https://www . YouTube . com/watch?v = wq 8 bibbpya 2k&list = plqvvaa 0 qudfhtox 0 ajmq 6 tvtgmbzbexn
- CNN 架构:
https://medium . com/analytics-vid hya/CNN-Architectures-lenet-Alex net-vgg-Google net-resnet-and-more-666091488 df5 - 转移学习文章:
- https://medium.com/r/?URL = https % 3A % 2F % 2f towards data science . com % 2f transfer-learning-using-mobilenet-and-keras-c 75 daf 7 ff 299
- https://github.com/bdutta19/kaggle_statefarm
- http://cs 229 . Stanford . edu/proj 2016/report/SamCenLuo-classification of driver distraction-report . pdf
- https://arxiv.org/abs/1704.04861
使用 PySpark 和 Keras 的分布式深度学习管道
Photo Credit: tian kuan
使用 PySpark 实现数据管道化和使用 Keras 进行分布式深度学习的简单方法
介绍
在这本笔记本中,我使用 PySpark、Keras 和 Elephas python 库来构建一个运行在 Spark 上的端到端深度学习管道。Spark 是一个开源的分布式分析引擎,可以以极快的速度处理大量数据。PySpark 只是 Spark 的 python API,它允许您使用一种简单的编程语言,比如 python,并利用 Apache Spark 的强大功能。
目标
我把这个例子放在一起的兴趣是学习和原型。更具体地说,了解更多关于 PySpark 管道的信息,以及我如何将深度学习集成到 PySpark 管道中。我在本地机器上使用 Jupyter 运行了整个项目,为即将到来的项目构建了一个原型,该项目中的数据将是海量的。因为我在 IBM 工作,所以我将把整个分析项目(Jupyter 笔记本)转移到 IBM。这使我能够在一个统一的平台和一个更大的 Spark 集群上进行数据接收、管道化、培训和部署。显然,如果您有一个真实的、相当大的项目或者使用图像数据,您不会在本地机器上这样做。
总的来说,我发现把这个原型或工作示例放在一起并不太难,所以我希望其他人会发现它很有用。我会把这个项目分成 9 个步骤。很多步骤都是不言自明的,但对于其他步骤,我会尽量让它变得不那么痛苦。如果你只想看有解释和代码的笔记本,你可以直接去 GitHub 。如果我能做到,你也能!
第一步
导入库
Figure 1 — 3 Libraries Here: PySpark (Install Spark), Keras, and Elephas
第二步
开始火花会话
您可以使用setAppName()
为您的项目设置一个名称,也可以设置您想要多少工人。我只是在本地运行它,并将它设置为可能有 6 个工人。只是一个警告,如果你的数据不是很大,那么分配工作,特别是在进行机器学习时,实际上可能没什么帮助,并且提供更差的结果。当我做下面的训练时,我将它设置为 1 个工人,但是当我在以后的项目中使用这个原型时,我将更改这些设置。
Figure 2 — Spark Session
第三步
用 Pyspark 加载和预览数据
在这里,我们将加载数据。我们将使用的数据来自一场 Kaggle 竞赛。这是一个典型的银行数据集。我在这里使用了inferSchema
参数,它有助于在加载数据时识别特征类型。根据 PySpark 文档,此需要额外传递一次数据。因为我正在加载的银行数据只有大约 11k 的观察值,所以根本不需要很长时间,但是如果您有一个非常大的数据集,这可能是值得注意的。
Figure 3 — Load Data
加载数据后,我们可以看到模式和各种功能类型。我们所有的功能不是string
型就是integer
。然后我们预览前 5 个观察结果。我非常熟悉 Pandas python 库,所以通过这个例子,你会看到我使用toPandas()
将 spark 数据帧转换成 Pandas 数据帧,并进行一些操作。没有对错,只是对我来说更容易。
Figure 4 — View Schema and Preview Dataframe
最后,我们将删除 2 个日期列,因为我们不会在深度学习模型中使用它们。它们可能是重要的和有特色的,但是我决定把它们放在一起。
Figure 5 — Drop Columns
第四步
创建 Spark 数据管道
现在我们使用 PySpark 创建管道。这实际上是获取您的数据,并根据您传递的特性列表进行转换和矢量化,以便为建模做好准备。对于这个管道和项目,我参考了很多 Apache Spark 文档 “提取、转换和选择特性” 。
下面是一个辅助函数,用于根据数字特征的峰度或偏斜度来选择要标准化的数字特征。upper_skew
和lower_skew
的当前默认值只是一般指导原则(取决于您的阅读位置),但是您可以根据需要修改上下偏斜。
Figure 6 — Select Features to Standardize Function
创建管道
现在我们将进入实际的数据管道。功能列表选择部分可以进一步增强,以更加动态地列出每个功能,但对于这个小数据集,我只保留了cat_features
、num_features
和label
。通过类型选择特性可以类似于我在select_features_to_scale
助手函数中所做的,使用类似这样的list(spark_df.toPandas().select_dtypes(include=['object']).columns)
,它将返回 spark 数据帧中所有对象或字符串类型的列的列表。
我们要做的第一件事是创建一个名为stages
的空列表。这将包含数据管道完成管道内所有转换所需的每个步骤。我打印出管道后阶段的每一步,这样你可以看到从我的代码到列表的连续步骤。
第二部分将是一个基本循环,遍历列表cat_features
中的每个分类特征,然后使用一键编码对这些特征进行索引和编码。StringIndexer
将您的分类特征编码到一个特征索引中,将最高频率标签(计数)作为特征索引0
,依此类推。我将在管道(步骤 5)后预览转换后的数据框,您可以看到根据分类要素创建的每个要素索引。有关更多信息和 StringIndexer 的基本示例,请查看此处的。
在循环中,我们还使用OneHotEncoderEstimator
进行了一些一键编码(OHE)。这个函数只接受一个标签索引,所以如果你有分类数据(对象或字符串),你必须使用StringIndexer
,这样你就可以传递一个标签索引给 OHE 估计器。通过查看几十个例子,我发现一件好事是,你可以使用string_indexer.getOutputCol()
将StringIndexer
输出链接到 OHE 估计器中。如果你有很多要转换的特性,你需要考虑一下它们的名字,OutputCol
,因为你不能仅仅重写特性的名字,所以要有创造性。我们将循环中的所有管道步骤添加到管道列表stages
中。
接下来,我们在标签特征或因变量上再次使用StringIndexer
。然后我们将继续使用上面的select_features_to_scale
辅助函数缩放数值变量。一旦选择了列表,我们将使用VectorAssembler
对这些特征进行矢量化,然后使用StandardScaler
对该矢量中的特征进行标准化。然后我们将这些步骤添加到我们正在进行的管道列表stages
。
最后一步是将我们所有的特征组合成一个向量。我们将通过使用我们的unscaled_features
(所选要缩放的数字特征的名称)列表和原始数字特征列表num_features
之间的差异,从列表num_features
中找到未缩放的数字特征。然后,我们集合或矢量化所有分类 OHE 特征和数字特征,并将该步骤添加到我们的管道stages
。最后,我们将scaled_features
添加到assembled_inputs
中,为我们的建模获得最终的单一特征向量。
Figure 7 — Spark Data Pipeline
通过查看我们按顺序添加的stages
列表,我们可以看到管道中的所有步骤。
Figure 8 — Data Pipeline list ‘stages’
第五步
通过 Spark 管道运行数据
既然*“硬”*部分已经结束,我们可以简单地流水线化阶段,并通过使用fit()
使我们的数据适合流水线。然后我们实际上通过使用transform
来转换数据。
Figure 9 — Pipeline, Fit, and Transform the Data
我们现在可以预览新转换的 PySpark 数据框架,其中包含所有原始和转换后的特征。这可以在 GitHub 的笔记本上更好地查看,但是图 10 显示了一些索引、集合向量和我们的标签索引。
Figure 10 — Preview of Newly Transformed Dataframe
第六步
深度学习模型前的最终数据准备
在建模之前,我们需要做几件最后的事情。首先是创建一个 PySpark 数据帧,该数据帧仅包含来自最近转换的数据帧的 2 个向量。我们建模只需要:features
( X )和label_index
( y )特征。用简单的select
语句就可以很容易地处理 PySpark。然后,只是因为,我们预览数据帧。
Figure 11 — Select Final Features and Label
最后,我们希望重组数据帧,然后将数据分成训练集和测试集。您总是希望在建模之前打乱数据,以避免对数据排序或组织方式的任何偏见,特别是在拆分数据之前打乱数据。
Figure 12 — Order by Random (Shuffle) and Split Data
第七步
用 Keras 建立深度学习模型
我们现在将使用 Keras 建立一个基本的深度学习模型。Keras 被描述为:“一种高级神经网络 API,用 Python 编写,能够在 TensorFlow、CNTK 或 Theano 之上运行。” Keras 文档中的。我发现 Keras 是 python 最简单的深度学习 API 之一。此外,我发现了一个 Keras 的扩展,它允许我在 Spark 上进行简单的分布式深度学习,可以与我的 PySpark 管道集成,所以这似乎是一个很好的选择。
首先,我们需要从数据中确定类的数量以及输入的数量,以便我们可以将这些值插入到我们的 Keras 深度学习模型中。
Figure 13 — Number of Classes and Inputs for Model
接下来我们创建一个基本的深度学习模型。使用 Keras 的model = Sequential()
功能,可以很容易地添加层,并使用所有所需的设置(单位数、辍学率%、正则化-L2、激活函数等)构建深度学习模型。)由于我们的结果标签是二进制的,所以我选择了普通的 Adam 优化器和具有二进制交叉熵的 sigmoid 激活来补偿我们的损失。
Figure 14 — Keras DL Model
一旦模型建立,我们可以查看架构。请注意,我们从 30 个输入/参数增加到 74,242 个。深度学习的妙处,有时也是懒惰:),是自动特征工程。
Figure 15 — Model Summary / Architecture
第八步
用 Elephas 进行分布式深度学习
现在我们已经建立了一个模型,使用 Keras 作为我们的深度学习框架,我们希望在 Spark 上运行该模型,以利用其分布式分析引擎。我们通过使用一个 python 库和一个名为 Elephas 的 Keras 扩展来做到这一点。Elephas 使得在 Apache spark 上运行 Keras 模型变得非常容易,只需几行配置。我发现 Elephas 比我阅读和尝试的其他几个库更容易使用,也更稳定。
我们对 Elephas 做的第一件事是创建一个类似于上面 PySpark 管道项目的估计器。我们可以从 Keras optimizer 函数中设置优化器设置,然后将其传递给我们的 Elephas 估计器。我只显式地使用具有设定学习率的 Adam optimizer,但是您可以使用任何具有各自参数的Keras optimizer(clip norm,beta_1,beta_2 等。).
然后,在 Elephas estimator 中,您可以指定各种项目:特征列、标签列、时期数、训练的批量大小、训练数据的验证分割、损失函数、度量等。我只是使用了来自 Elephas 示例的设置,并稍微修改了代码。因为我的数据很少,所以我只用了一个工人。此外,当我尝试运行 6 个工人(步骤 2)的结果很差。教训是:你可以分发并不意味着你应该:)
Figure 16 — Elephas Estimator for Distributed Deep Learning
注意,在我们运行估计器之后,输出ElephasEstimator_31afcd77fffd
,看起来类似于我们的一个管道stages
列表项。这可以直接传递到我们的 PySpark 管道中,以适应和转换我们的数据,这将在下一步中完成!
第九步
分布式深度学习管道和结果
既然深度学习模型将在 Spark 上运行,使用 Elephas,我们可以完全按照上面使用Pipeline()
的方式来流水线化它。您可以将它添加到我们的stages
列表中,并使用一个新数据集一次性完成所有这些工作,因为它已经全部构建完成,这将非常酷!
Figure 17 — Easy DL Pipeline with PySpark
我在下面创建了另一个名为dl_pipeline_fit_score_results
的帮助函数,它采用深度学习管道dl_pipeline
,然后对训练和测试数据集进行所有的拟合、转换和预测。它还输出数据集及其混淆矩阵的准确性。
Figure 18 — Deep Learning Pipeline Helper Function
让我们在两个数据集上使用我们新的深度学习管道和助手函数,并测试我们的结果!从下面你可以看到,我们可以在训练和测试数据上有大约 80%的准确性,所以这个模型似乎是足够概括的。我承认我正在使用一个大锤模型,一个小得多的“工具”比如一个基本的决策树可能会做得更好。然而,我们现在有一个工作流和原型来引入表格数据,通过转换管道传递数据,并应用深度学习。
Figure 19 — Run DL Pipeline Helper Function, View Results
结论
我希望这个例子有所帮助。我知道这是为了让我了解更多关于 PySpark 管道的知识,并使用 Keras 等简单的深度学习框架在 Spark 上进行深度学习。正如我提到的,我在本地运行所有这些,几乎没有问题,但我的主要目标是为一个即将到来的项目构建原型,该项目将包含一个大规模的数据集。我希望你(和我自己)可以把它作为一个模板,同时对 spark 会话、数据、特性选择以及添加或删除一些管道阶段做一些修改。一如既往,感谢您的阅读,祝您的下一个项目好运!
分布式 SQL 系统综述:雪花 vs 拼接机
SQL 回来了
在经历了多年的大数据、NoSQL 和基于读取的模式的弯路之后,SQL 作为数据操作的通用语言已经有了明显的回归。开发人员需要 SQL 提供的全面表达能力。一个没有 SQL 的世界忽略了 40 多年的数据库研究,并导致应用程序中硬编码的意大利面条式代码来处理 SQL 极其高效地处理的功能,如连接、分组、聚合和(最重要的)更新出错时的回滚。
SQL is a powerful language enabling developers to state what they need without coding how to compute it
幸运的是,现在有了一种称为分布式 SQL 的现代 SQL 体系结构,它不再受到传统 SQL 系统的挑战(成本、可伸缩性、性能、弹性和模式灵活性)。分布式 SQL 的关键属性是数据存储在许多分布式存储位置上,计算在一个网络服务器集群上进行。这带来了前所未有的性能和可伸缩性,因为它在集群中的每个工作节点上并行分配工作。
虽然分布式 SQL 系统有许多共同的特征,但它们也有很大的不同,有些更适合某些工作负载。在这里,我们尝试将 Snowflake 和 Splice Machine 作为分布式 SQL 系统的两个例子进行比较,这两个例子在很多方面都有所不同。
不幸的是,数据系统之间的比较界限已经模糊。例如,一个系统声称拥有数据库的 ACID 属性(即原子性、一致性、隔离性和持久性)这一事实,并不一定意味着它是一个真正能够支持应用程序的事务性 OLTP 系统(关于这个主题的更多细节,请参见 Medium 文章)。另一个例子是灵活性—随着工作负载的扩展,您可以添加更多的工作人员来获得更多的并行性,或者随着工作负载的缩减而减少工作人员并降低成本。许多系统是灵活的,但是只有一些能够自动扩展集群以获得更多(或更少)的并发性或吞吐量。这是拼接机和雪花互不相同的两个示例特征。
在这里,我们将试图提供这些系统的一个平衡的观点,即使我们代表其中的一个。我们将从用例的角度来展示这些系统的不同之处,而不是逐个特性地展示。
下面我们将展示两个完全不同的用例。一个将完全适合拼接机,一个将完全适合雪花。
用例
Application versus Analytics
让我们考虑两个用例,都是在保险行业。用例一是管理客户、政策、索赔和支付的可操作的遗留应用程序。它是一个用 Java 编写的 SQL 应用程序,具有用 React/Node.js 开发的 Javascript 前端,具有以下特征:
- 保险公司的每个实体都有自己的本地化的应用程序实例,运行特定的区域内法规、费率和政策
- 应用程序必须 24/7 可用,并且在工作时间有峰值并发负载
- 该应用程序还运行一组运营报告,管理人员使用这些报告来获得关于业务的日常见解
- 经理们还把它作为一个专门的商业智能查询工具,询问企业“一次性”的问题
- 该系统的用户包括许多支持者,包括代理、经理和通过自助服务门户的消费者
用例二是一个对账过程,它将来自数百万单个交易(在几个业务系统中)的财务业务信息转换为 SAP 等财务系统的分类帐更新。该应用程序具有以下属性:
- ETL 工具(如 Informatica 或 Talend)中设计的一组复杂的批量转换会产生聚合数据
- 每天运行以创建运行汇总,每月运行以关闭帐簿。
- 汇总的数据作为文件输出,由财务对账系统接收
- 财务分析师审查由报告工具(如 Tableau 或 MicroStrategy)构建的批处理报告和仪表板
- 分析师执行临时查询来验证结果
选择解决方案
Use Snowflake or Splice Machine?
对于任何熟悉 Splice Machine 和 Snowflake 的人来说,您可以看到这些用例是为了指出每个引擎的优势而选择的。Splice Machine 的最佳点倾向于运营工作负载,而 Snowflake 的最佳点是大批量数据仓库工作负载。如果你的问题是用例 1,拼接机是最好的选择,原因如下。如果你的问题类似于用例 2,那么雪花是一个更好的选择。下面是每个用例的细分:
传统应用程序工作负载
遗留应用程序有以下要求,这些要求需要相关的系统功能。
Legacy Application Requirements
那么这些系统是如何堆叠起来的呢?
Feature Comparison
Net-Net:要运行一个有并发用户且始终在线的遗留应用程序,一个倾向于操作性工作负载但也有分析能力的系统将会表现得更好。
现在我们来看看雪花闪耀在哪里。
财务对账工作量
这个用例有着完全不同的需求,因此需要不同的特性。
Financial Reconciliation Workload Requirements
那么这些系统是如何堆叠起来的呢?
Feature Comparison
在这个用例中,雪花被设计用来优化大批量分析查询。存储针对这一点以及计算进行了优化。通过将计算与存储分离,雪花系统可以在不使用时完全关闭所有计算节点,从而创建一个经济高效的数据仓库。[更新 2/27/20:拼接机现在可以暂停和重启]。它还支持利用元数据统计数据进行优化,以最大限度地发挥每个员工的潜能。
摘要
Hybrid Transactional and Analytical versus Data Warehouse
Snowflake 和 Splice Machine 分布式 SQL 引擎都很强大,它们在服务工作负载的能力上肯定是重叠的。因此,您的选择标准将取决于您的特定用例。如果您的用例要求决策支持系统支持预先计算的值,可以关闭,并且可以弹性伸缩,那么雪花就是您的选择。另一方面,大多数分布式 SQL 系统无法支持应用程序。如果您希望为具有大量并发用户的任务关键型应用提供支持,并且该应用必须全天候运行,那么 Splice Machine 无疑是您的最佳选择。
实际上,这些系统部署在各种不同的工作负载上。例如,除了运营应用之外,Splice 还通过其基于 Apache Spark 的基础架构处理许多数据仓库、分析和机器学习工作负载。除了数据仓库之外,雪花有时还能驱动应用程序。但是我们在这里所做的是尝试描述用例连续体上的几个极端例子,以帮助您为您的工作负载选择最佳的分布式 SQL 引擎。
有关如何使用拼接机实现应用现代化的更多信息,请参见本白皮书。
分布式矢量表示:简化
可以说是机器学习中最基本的特征表示方法
让我们来玩一个简单的游戏。我们有三个“人”——迈克尔、露西和加布。我们想知道哪个人的播放列表与露西最匹配,是迈克尔还是加布??让我给你一些提示。露西喜欢古典摇滚,加布也是,但迈克尔不喜欢。露西更喜欢器乐版本,迈克尔也是一样,但加布不喜欢他们。露西不喜欢流行音乐,迈克尔彻底讨厌它,但加布绝对是一个流行音乐迷!!
这种信息的表达有帮助吗?你能确定谁的播放列表更符合露西吗?Lucy 和 Micheal 对两种不同的歌曲类型有共同的兴趣,而 Lucy 和 Gab 只有一种共同的歌曲类型。但是你能确定露西和加布分享的一种类型不会超过其他两种吗?(我是说 comeon…是经典摇滚!!!)
如果我告诉你,我有一个神奇的公式,可以把他们的音乐兴趣作为一个单一的值来表达,你会怎么想?Lucy 可以表示为-0.1,Gab 可以表示为-0.3,Micheal 可以表示为 0.7。这当然使工作更容易,因为如果你相信这个公式,我认为很明显露西的播放列表将与 Gab 最匹配。但是我是怎么想出这个公式的呢?让我们从头开始…
什么是分布式表示?
分布式表示指的是特征创建,其中特征可能与原始输入有任何明显的关系,也可能没有,但它们具有比较价值,即类似的输入具有类似的特征。将输入转换为数字表示(或特征)是每个领域中任何机器学习算法的第一步。
为什么非分布式表示还不够?
非分布式表示(也称为单热点向量表示)为每个新的输入可能性添加一个新的向量维度。显然,唯一可能输入的数量越多,特征向量就越长。这种表示有两个主要缺陷,
- 任何两个特征向量之间的距离或“相似性”是相同的。换句话说,这种表示没有关于输入如何相互关联的信息,即没有比较值。
- 由于向量的每个维度代表一个唯一的输入,这种表示不能处理看不见的或未知的输入。
One-hot representation (left) vs distributed representation (right)
例如,如果我们在上面的表示中遇到一个新的输入,circle,会怎么样呢?如果我们使用非分布表示法(左),我们没有表示圆的方法,但如果我们使用分布表示法(右),我们可以将圆表示为垂直、水平和椭圆。这种表示也有助于我们的模型理解圆形更像椭圆而不是矩形。
我如何创建分布式矢量表示?
没有一种创建分布式矢量表示的方法。您可以创建与属性域有逻辑关系的要素,并相应地表示您的输入,就像上面的矩形/椭圆示例中所做的那样(尽管此过程需要专业领域知识)。或者你可以使用更成功的方法,即使用深度学习来创建无法直接解释的特征表示,但保留所需的比较值(就像我在帖子开头的播放列表示例中所做的那样)。
为了创建基于深度学习的分布式向量表示,需要首先创建逻辑特征表示(在大多数情况下实际上是非分布式表示),然后将其传递通过“转换矩阵”(有时也称为嵌入矩阵),以获得分布式向量表示。模型的这一部分通常附加在完整管道的开始,以学习表示。
Converting V unique inputs into an N-dimension vector representation
有许多帮助 ML 管道的向量表示的例子,如 Word2Vec、Gene2Vec、Prot2Vec、Node2Vec、Doc2Vec、Tweet2Vec、Emoji2Vec 等。这个清单非常庞大,尽管其中很大一部分出现在本次回购中。
为什么向量长度很重要?
现在一切似乎都很容易。为什么要浪费这么多空间,让我们学着用一个数值来表示所有的东西,对吗?*不!!*让我来给你讲解一下。
如果我们需要表示 3 个同样相似的输入(像三角形)会怎样?我们能用一维向量来表示它们吗?**不!!**为什么?因为三角形是二维的,咄!!现在,如果我需要表示 4 个同样相似的输入(像一个金字塔)该怎么办?你知道规矩…
每当我们增加特征维数时,我们的特征表示的稀疏性以指数方式增加。使用深度学习的分布式向量表示植根于这样一个事实,即我们提供具有比较值的近似(不一定精确)分布式表示。因此,需要明智地选择特征维度的大小,以便它可以提供足够准确的输入表示,而不会导致特征空间的极度稀疏。
下一步是什么?
分布式矢量表示作为一种核心思想非常流行,并广泛应用于各个领域。在各种各样的问题陈述中,已经提出了多种数字特征表示方法。我相信这种表示的核心思想对于任何机器学习问题都是必不可少的(即使确切的方法发生了显著的变化)。
这个博客是努力创建机器学习领域简化介绍的一部分。点击此处的完整系列
在你一头扎进去之前就知道了
towardsdatascience.com](/machine-learning-simplified-1fe22fec0fac)
或者只是阅读系列的下一篇博客
参加在时间序列分析中使用深度学习的“为什么”和“什么时候”的速成班。
towardsdatascience.com](/time-series-analysis-with-deep-learning-simplified-5c444315d773)
参考
[1] Knapp,Steven K .“用一键法加速 FPGA 宏”电子设计 38.17(1990):71–78。
[2] Mikolov,Tomas 等,“单词和短语的分布式表示及其组合性”神经信息处理系统进展。2013.
[3]勒、阔克和托马斯·米科洛夫。"句子和文档的分布式表示."机器学习国际会议。2014.
[4] Dhingra,Bhuwan 等,“Tweet2vec:基于角色的社交媒体分布式表示”arXiv 预印本 arXiv:1605.03481 (2016)。
介绍 Distython。新的 Python 包实现了新的距离度量
您在为混合型数据集选择正确的距离度量时遇到过问题吗?在大多数情况下,您可以尝试执行要素预处理,然后使用一些流行的距离度量,如欧几里德距离或曼哈顿距离。事实上,这种方法可能是不准确的,并导致 ML 模型的性能下降。看看 Distython — Python 包,它实现了研究论文中新颖的混合型距离度量!
Photo by Charles 🇵🇭 on Unsplash
最近实习的时候遇到过这个问题。我不得不在混合类型的数据集中使用 K-NN 来度量实例之间的相似性,该数据集中也有缺失值!虽然我可能会尝试做一些“花哨”的特征预处理,使其与流行的距离度量一起工作,但我不认为这是获得可靠结果的好方法。这让我开始寻找既能处理混合型数据又能处理缺失值的距离度量。读了几篇论文后,我发现了几个有趣的指标,这促使我在这里发表了另一篇关于它们的文章。与此同时,我也开始研究它们的实现,这让我创建了一个名为 Distython 的小型实用 Python 包!在接下来的文章中,我将解释它如何对您的 ML 模型有用,如何与 Scikit-Learn 集成,并简要解释代码的设计。
我喜欢数据科学的原因是,它吸引了许多对人工智能和数据科学充满热情的志同道合的人。这就是为什么我想在 Linkedin 上与你联系!您也可以通过我的个人网站留下任何反馈和问题😎
Distython 概述
(链接到 GitHub repo 这里)
Distython 是一个小型的、高效的实用程序包,它实现了混合型距离度量。最初,我把它作为个人使用,但是我想如果我把它公开的话可能会更有用。它实现了 3 种算法:异质欧几里德重叠度量(HEOM),价值差异度量(VDM)和异质价值差异度量(HVDM)。所有这些都在我之前的文章中解释过,也可以在这篇研究论文中找到。它们可以直接与一些 Scikit-Learn 算法一起使用(那些允许指定用户定义的度量的算法,例如最近邻或 Dbscan)。你也可以将它们与特定的插补算法一起使用,比如 MICE、K-NN 或 MissForest(我也写了一篇关于它们的文章
Distython 完全基于 Numpy。这使得它更快,并减少了所需的包依赖的数量。这里唯一的缺点是它降低了代码的可读性,但是当你试图创建高效的代码时,这通常是一个问题😀
为什么它有用
它解决了一个以前没有解决的问题,即创建一个公开可用的异构距离度量集合。老实说,当我发现 Scikit-Learn 没有任何异构度量时,我感到非常惊讶。假设数据集要么是连续的,要么是分类的,这似乎是非常错误的,是吧?
非异构方法与分布式
让我们来看一个非常简单的数据集,它包含分类和连续的特征以及一些缺失值。我们需要测量两个实例 A 和 B 之间的距离。
Simple dataset for the purpose of the example
**非异构方法:**例如,如果您要使用 Scikit-Learn,则没有异构度量可供选择。克服这个问题的一个方法是一个变通的解决方案,它可能会给你不正确的结果:
为了计算 A 和B之间的距离,你需要对数字数据进行分类,这会导致信息的丢失。这里的另一个大问题是,在对特性进行编码之后,你将会得到很多列(维数灾难在这里适用)!
你准备好了吗?这里只有一件事要做…将您选择的距离度量应用于这些实例。很简单,是吧?要将度量附加到 Sklearn 的 K-NN 实现中,您只需要一行代码!
Distython with Scikit-Learn
这些指标被设计为可以直接应用于 Scikit-Learn 中的最近邻或 Dbscan 类。您可以将它与 Scikit-Learn 类一起用于您的个人项目,只要它们提供一个调用定制度量函数的接口。请注意,如果我们将指标附加到 Scikit-Learn 类,它会生成函数的开销调用,算法会变慢。
Importing necessary libraries
在上面的代码中,我们导入了必要的库和 HEOM 度量。我们还将使用波士顿房价数据集,因为它同时具有分类和数字特征。
Define the indices for categorical variables and NaN equivalent
这里,我们导入数据并将其定义为 boston_data。这里重要的部分是我们必须告诉 HEOM 度量什么列是分类的。 nan_eqv 在这里用来告诉 HEOMNaN是怎么表示的。需要注意的重要一点是:最近的邻居不能直接处理 np.nan ,所以我们需要来声明某些 nan 等价。
Introducing missingness to the dataset
在上面的代码部分中,我们将缺失引入数据(出于示例的目的)并将其指定为 nan_eqv。
Defining heom_metric and neighbor
然后,我们定义我们的 heom_metric 并提供必要的参数。它必须在 NearestNeighbors 之前定义,因为我们必须提供一个从 heom_metric 到 neighbor 实例*的可调用度量函数。*这就是将 HEOM 与 Scikit-Learn 一起使用的全部内容。这很简单,不是吗?有关使用 Scikit-Learn 的自定义距离度量的更多信息,请点击此处。
Fitting the data and printing the results
在最后一步中,我们拟合模型,并使用 HEOM 作为距离度量函数返回 5 个最近邻。
最后几句话要说
该软件包仍处于早期阶段,所以它可能包含一些错误。请看看 Github 库,并建议一些代码的改进或扩展。我将很高兴地欢迎任何建设性的反馈,并随时为 Distython 做贡献!😉
参考
来自Distython with Scikit-Learn段落的大部分材料摘自我之前的文章这里的。
深入弹性搜索
这篇文章将帮助你对 ElasticSearch 有一个高层次的了解。我们将回顾能够覆盖一个典型项目至少 95%需求的主要功能。如果你是 ElasticSearch 的新手,在这篇文章中你会找到几乎所有问题的答案,这些问题是你在使用新数据库之前应该问的。
什么是 ElasticSearch?
Elasticsearch 是一个全文搜索引擎,存储无模式的 JSON 文档。Elasticsearch 是基于 Apache Lucene 的开源软件,在 Apache 2.0 许可下发布。ElasticSearch 可以处理任何类型的数据,包括文本、数字、地理空间、结构化和非结构化数据。
如何部署
你可以通过 Elasticsearch 服务(可在亚马逊网络服务(AWS)、谷歌云平台(GCP)和阿里云上使用)部署 Elasticsearch,也可以下载并安装在你的硬件上或云中。
文档有关于如何手动下载和安装数据库的说明。
此外,您可以使用 Docker 轻松安装 Elasticsearch:
1.提取图像:
docker pull docker.elastic.co/elasticsearch/elasticsearch:7.4.0
在编写本示例时,版本 7.4.0 是最新的。在官方网站上查看当前版本。
2.在开发模式下运行映像:
docker run -p 9200:9200 -p 9300:9300 -e “discovery.type=single-node” docker.elastic.co/elasticsearch/elasticsearch:7.4.0
看看官方文档中的更多选项。
弹性搜索是如何工作的
Elasticsearch 将文档存储在索引中。就关系数据库而言:索引是一个表,文档是表中的一行。索引是无模式的,所以您可以放置具有不同结构的文档,但是对于键有映射和限制,我们稍后将概述这些限制。
关于 ElasticSearch 的工作原理:
- 当您插入某个文档时,ElasticSearch 将文档字段的值拆分为记号(例如,句子中的每个单词可以是不同的记号),并将这些记号添加到倒排索引中。
- 当用户搜索某个短语时,ElasticSearch 会将该短语拆分成标记,并将这些标记与倒排索引进行匹配。
如果你不知道什么是倒排索引,它是如何工作的,你可以在这里阅读倒排索引的简要说明或者查阅官方文档。
缩放比例
Elasticsearch 是分布式软件,这意味着您可以以集群模式运行 Elasticsearch,其中每个计算节点将托管一个或多个碎片,并作为协调者将操作委托给正确的碎片。Elasticsearch 支持两种最流行的扩展方法,比如分区和复制。
分割
ElasticSearch 索引将被存储到两个或多个分片上。您索引的数据将存储在集群中的一个碎片上。
分身术
ElasticSearch 有一个主碎片和至少一个副本碎片。您索引的数据被写入主碎片和副本碎片。复制副本是主副本的精确拷贝。如果包含主碎片的节点出现故障,副本将接管。
索引
索引是无模式存储,但是您也可以为索引建立严格的文档模式。
创建新索引
curl -X PUT [http://localhost:9200/person](http://localhost:9200/person)
该命令将创建一个名为“ person ”的新的无模式索引,或者如果该索引已经存在,将返回一个错误。
查看索引信息
curl -X GET [http://localhost:9200/person](http://localhost:9200/person)
作为响应,您将看到索引的设置、映射和别名。如果索引不存在,则显示错误消息。
模式或映射
映射是对文档及其包含的字段如何在索引中存储和索引的描述。例如,在映射中,您可以定义以下内容:
- 文档的结构(字段和这些字段的数据类型)
- 如何在索引前转换值
- 哪些字段用于全文搜索
使用自定义映射创建索引
curl -X PUT [http://localhost:9200/person](http://localhost:9200/person) \
-H ‘Content-Type: application/json’ \
-d ‘{
**“mappings”: {
“dynamic”: “strict”,
“properties”: {
“name”: {“type”: “text”},
“email”: {“type”: “text”},
“location”: {“type”: “geo_shape”},
“extra_data”: {“type”: “object”, “dynamic”: true}
}
}**
}’
在这个例子中,您将为具有静态根结构的文档创建映射。一个字段将是一个动态对象,它可以包含任意数量的任意字段(键的数量受索引设置的限制)。
查看现有索引的映射
curl -X GET [http://localhost:9200/person](http://localhost:9200/person)
API 将返回上一个示例中的现有映射。
数据类型
ElasticSearch 支持许多不同的数据类型,以便对这些类型执行特定的搜索。让我们列出最常用的类型:
- 核心数据类型:字符串(文本和关键字)、数字(整数、浮点等)、日期、布尔、二进制等
- 复杂,像对象(hashmap(dictionary))和嵌套(链表(array))
- 特定数据类型,例如,地理形状、IP 等。
每种数据类型都有自己的目标和设置。因此,请查看文档,了解每种类型的更多信息。
注意了。字符串数据类型有两种:文本和关键词。“文本”用于全文搜索(通常在文本中搜索,具有模糊性和其他特征),“关键字”用于聚合、排序和直接匹配(类似于编程语言中的运算符“==”)。
将数据插入索引
插入单个文档
curl -X POST [http://localhost:9200/person/_doc](http://localhost:9200/person/_doc) \
-H ‘Content-Type: application/json’ \
-d ‘{**“name”: “John”, “age”: 30}**’
如果成功,该请求将返回生成的 id 和其他信息。但是您总是可以自己指定 id:
curl -X POST [http://localhost:9200/person/_doc/id-1](http://localhost:9200/person/_doc/id-1) \
-H ‘Content-Type: application/json’ \
-d ‘**{“name”: “Katrin”, “age”: 25}**’
批量插入到一个索引中
curl -X POST [http://localhost:9200/person/_doc/_bulk](http://localhost:9200/person/_doc/_bulk) \
-H ‘Content-Type: application/json’ \
-d ‘**{ “index”:{} }
{ “name”:”Alex”,”age”:25 }
{ “index”:{} }
{ “key1”:”Amely”,”age”:27 }**
‘
注意:批量添加应该以换行符结束。
批量插入到不同的索引中
curl -X POST [http://localhost:9200/_bulk](http://localhost:9200/_bulk) \
-H ‘Content-Type: application/json’ \
-d ‘**{ “index”:{“_index”: “person”} }
{ “name”:”Jack”,”age”: 34 }
{ “index”:{“_index”: “person”} }
{ “name”:”Oscar”,”age”:22 }
{ “index”:{“_index”: “person”} }
{ “name”:”John”,”age”:27 }**
‘
文档的类型
在插入的 URI 中,可以看到“ /_doc/ ”部分。这是文档的类型,但这是从 ElasticSearch 第 6 版开始弃用的东西。
更新文档
curl -X POST [http://localhost:9200/person/_update/id-1](http://localhost:9200/person/_update/id-1) \
-H ‘Content-Type: application/json’ \
-d ‘**{“age”: 24}**’
这是更新一个文档中一个字段的简单例子。Elasticsearch 支持针对复杂案例的更复杂的查询,因此查看文档以了解更多信息。
搜索查询
你知道,为了搜索。您可以在那里找出所有支持查询的列表和这些查询的描述。在本文中,我们将回顾最流行的查询,它可以覆盖一个典型项目中 95%的用例。
将所有字段与文本匹配
curl -X GET [http://localhost:9200/person/**_search?q=john**](http://localhost:9200/person/_search?q=john)
该查询将在任何字段中查找令牌。
全部匹配
curl -X GET [http://localhost:9200/person/_search](http://localhost:9200/person/_search) \
-H ‘Content-Type: application/json’ \
-d ‘{
**“query”: {“match_all”: {}}** }’
只返回所有按 id 排序的文档。
匹配一个
curl -X GET [http://localhost:9200/person/_search](http://localhost:9200/person/_search) \
-H ‘Content-Type: application/json’ \
-d ‘{
**“query”: {
“match”: {
“name”: “John Snow”
}
}** }’
“匹配”是在特定的字段中寻找特定的令牌。在这个例子中,我写了两个标记,这意味着我们要查找在字段“name”中包含标记“John”和/或标记“Snow”的文档。
本例仅适用于一个字段,要通过多个字段进行搜索,您需要另一个查询。
匹配短语
curl -X GET [http://localhost:9200/person/_search](http://localhost:9200/person/_search) \
-H ‘Content-Type: application/json’ \
-d ‘{
**“query”: {
“match_phrase”: {
“name”: “John Snow”
}
}** }’
本示例与上一个示例的不同之处在于,本示例将在字段中查找完整的短语(在本示例中,将是两个标记" john “和” snow ",它们是一个接一个的),并且仅当找到该短语时才返回结果。
如果您向简单的“匹配”发送两个或更多令牌,即使只找到一个令牌,您也会收到结果,但在“匹配 _ 短语”的情况下,您将不会收到结果。
多重匹配
curl -X GET [http://localhost:9200/person/_search](http://localhost:9200/person/_search) \
-H ‘Content-Type: application/json’ \
-d ‘{
**“query”: {
“multi_match”: {
“query”: “John”,
“fields”: [“name”, “age”],
“fuzzines”: 3,
}
}** }’
在这种情况下,我们在所有指定的字段中寻找一个令牌。多匹配查询支持参数“模糊性”,这允许在标记中使用输入错误进行搜索。阅读更多那里。
学期
curl -X GET [http://localhost:9200/person/_search](http://localhost:9200/person/_search) \
-H ‘Content-Type: application/json’ \
-d ‘{
**“query”: {
“term”: {
“name”: {
“value”: “John”
}
}
}** }’
返回字段中包含精确值的文档。意思是,单据中的字段“名称应该正好是“约翰”才能返回该单据。
模糊的
curl -X GET [http://localhost:9200/person/_search](http://localhost:9200/person/_search) \
-H ‘Content-Type: application/json’ \
-d ‘{
**“query”: {
“fuzzy”: {
“name”: {
“value”: “Jahn”
}
}
}** }’
返回包含与搜索值相似的文档。这意味着,可搜索的值可以有一个错别字,就像在这个例子中。
查看搜索查询的典型响应
如果您懒得复制粘贴查询,您可以在这里查看典型响应的正文:
Body of the typical response from ElasticSearch
在响应中,您会收到一个对象,它是字段" hits ,由内部键" hits “中所有匹配的文档组成,在这个键下,您还可以找到” total “和” max_score ",它们由返回的文档中匹配记录的总数和最大分数的信息组成。每个文档记录包括“ _source ”以及文档数据和系统字段,如索引、类型、id 和评分。
结果排名
给结果打分
文档的评分是基于指定查询的字段匹配和应用于搜索的任何附加配置来确定的。在那篇文章中,你可以找到关于 ElasticSearch 评分工作方式的很好的描述。此外,ElasticSearch 允许您指定自定义排名功能。阅读更多那里。
提高分数
如果您通过多个字段进行搜索,并且您认为某些字段比其他字段更重要,您可以提高更重要字段的分数。
提高分数意味着,如果某些字段给你的分数是 3,而你将这个字段的分数提高到 x2,那么这个字段的总分数就是 6 (3*2)。
您可以直接在查询中设置 boost。例如,对于 multi_match 搜索,要将 boosting x2 设置为字段“key 1 ”, X5 设置为字段“key2 ”,应按照以下格式指定字段:
“fields”: [“name^2”, “age^5”]
对于其他查询,您可以在搜索查询对象中添加一个关键字“boost”。例如:
“fuzzy”: {“name”: {“value”: “John”, “boost”: 2}}
按某个字段对结果进行排序
当然,您可以按任何字段对结果进行排序,而不是按分数。看看下面的例子:
curl -X GET [http://localhost:9200/person/_search](http://localhost:9200/person/_search) \
-H ‘Content-Type: application/json’ \
-d ‘{
**“sort”: [
{“age”: {“order”: “asc”}},
{“name”: {“order”: “desc”}},
],
“query”: {“match_all”: {}}** }’
在按字段排序的情况下,所有结果将为零分。
AND-OR-NOT 或布尔查询
通常一个条件不足以得到相关的结果。因此,我们需要一些功能来聚合一个查询下的不同条件,进行连接和析取或排除一些结果。就弹性搜索而言,这些类型的查询称为布尔查询:
必须(和)
必须的作用类似于和。这意味着操作符中的每个查询都必须出现在文档中。
过滤器(不影响评分)
“Filter”的作用类似于“must”,但不会增加结果分数的权重。
应该(或)
工作方式类似于或。如果我们在“ should ”下有两个条件,我们将接收所有有第一个或第二个条件的文件。此字段影响评分,这意味着匹配所有条件的文档将比仅匹配一个条件的文档得分更高。
Must_not(不)
工作起来不像。与“must_not”下的条件匹配的文档不得出现在结果中。
极限和偏移
在现实世界中,我们经常需要存储结果的一些限制和偏移。例如,要跳过前 5 个文档以显示接下来的 10 个文档,您需要以下查询:
curl -X GET [http://localhost:9200/person/_search](http://localhost:9200/person/_search) \
-H ‘Content-Type: application/json’ \
-d ‘{
**“from” : 5,
“size” : 10,**
“query”: {
“match_all”: {}
}
}’
分析器
在将文档插入索引之前,ElasticSearch 会运行分析来准备数据。分析是将文本转换为标记的过程,这些标记将被添加到倒排索引中进行搜索。分析由分析仪执行,分析仪可以是内置的或定制的。默认情况下,ElasticSearch 为每个索引设置了标准分析器。你可以使用分析器将表情符号替换为文本,删除特殊字符,删除停用词等等。
聚集
ElasticSearch 支持很多聚合框架,帮助你基于搜索查询聚合数据。聚合可用于构建索引的分析信息,或返回唯一值,或检查某个字段中的最小/平均/最大值,或其他内容。查看文档,查看所有 Elasticsearch 的内置聚合。可以将许多聚合合并到一个查询中。让我们概述一下构建聚合查询有多容易:
curl -X GET [http://localhost:9200/person/_search](http://localhost:9200/person/_search) \
-H ‘Content-Type: application/json’ \
-d ‘{
**“aggs” : {
“avg_age_key” : { “avg” : { “field” : “age” } },
“max_age_key” : { “max” : { “field” : “age” } },
“min_age_key” : { “min” : { “field” : “age” } },
“uniq_age_key” : { “terms” : { “field” : “age” } }
}**
}’
在这个例子中,我将 4 个聚合命令放入一个查询中。很容易理解这些聚合各自的作用。在响应中,您将在响应对象底部的关键字“ aggregations ”下找到您的聚合结果。
MapReduce
聚合允许您对索引进行良好的分析。但是,如果你需要对大型 Elasticsearch 集群上的大量数据进行复杂的分析,换句话说,你有大数据,Elasticsearch 为你提供了一个在索引上运行 MapReduce 作业的机会。看看官方文档了解更多。
处理
ElasticSearch 不支持交易。
摘要
所有列出的优点和功能允许在许多不同的情况下使用 Elasticsearch:
- 应用程序和网站搜索
- 企业搜索
- 日志记录和日志分析
- 监控许多指标
- 地理空间数据分析和可视化
- 安全或业务分析
而很多基于 ElasticSearch 的现成产品(如 Kibana、Elastic Stack 等)让你花更少的钱和更少的时间来开发你的解决方案。
深入了解 YOLO v3:初学者指南
Screenshot from a video made by Joseph Redmon on Youtube
原载于 2019 年 12 月 30 日https://www . yanjia . Li。
完整源代码请前往https://github . com/ethanyangali/deep-vision/tree/master/YOLO/tensor flow。我真的很感谢你的明星支持我的努力。
序
当自动驾驶汽车在道路上行驶时,它是如何知道摄像头图像中其他车辆的位置的?当人工智能放射科医生阅读 x 光片时,它如何知道病变(异常组织)在哪里?今天,我将通过这个迷人的算法,它可以识别给定图像的类别,还可以定位感兴趣的区域。近年来,有许多算法被引入到深度学习方法中来解决对象检测问题,如 R-CNN,Faster-RCNN 和 Single Shot Detector。其中,我最感兴趣的是一个叫 YOLO 的模特——你只会看一眼。这款车型如此吸引我,不仅是因为它有趣的名字,还有一些对我来说真正有意义的实用设计。2018 年,该型号的最新 V3 已经发布,它实现了许多新的最先进的性能。因为我以前编写过一些 GANs 和图像分类网络,而且 Joseph Redmon 在论文中以一种非常简单的方式描述了它,所以我认为这个检测器只是 CNN 和 FC 层的另一个堆栈,只是神奇地工作得很好。
但我错了。
也许是因为我比一般的工程师更笨,我发现对我来说把这个模型从纸上翻译成实际代码真的很难。即使我在几周内成功做到了这一点(有一次我放弃了,把它放了几个星期),我发现让它工作起来对我来说更加困难。有很多关于 YOLO V3 的博客,GitHub repos,但大多数只是给出了架构的一个非常高层次的概述,不知何故他们就成功了。更糟糕的是,论文本身太冷了,它没有提供实现的许多关键细节,我不得不阅读作者最初的 C 实现(我最后一次编写 C 是什么时候?也许在大学?)来证实我的一些猜测。当有一个 bug 时,我通常不知道它为什么会出现。然后,我最终一步一步地手动调试它,并用我的小计算器计算那些公式。
还好这次我没有放弃,终于成功了。但与此同时,我也强烈地感觉到,互联网上应该有一个更全面的指南来帮助像我这样的哑巴理解这个系统的每个细节。毕竟,如果一个细节出错,整个系统将很快崩溃。我敢肯定,如果我不写下来,我也会在几周内忘记所有这些。所以,我在这里,给你呈现这个“深入 YOLO V3:初学者指南”。我希望你会喜欢它。
先决条件
在进入网络本身之前,我需要先澄清一些先决条件。作为读者,你应该:
1.了解卷积神经网络和深度学习的基础知识
2。了解物体探测任务
3 的思路。对算法内部如何工作有好奇心
如果你在前两项上需要帮助,有很多优秀的资源,如 Udacity 计算机视觉纳米学位、 Cousera 深度学习专业化和 Stanford CS231n
如果你只是想构建一些东西来用你的自定义数据集快速检测一些对象,请查看这个 Tensorflow 对象检测 API
YOLO V3
YOLO V3 是对以前的 YOLO 检测网络的改进。与以前的版本相比,它具有多尺度检测,更强的特征提取网络,以及损失函数的一些变化。因此,这个网络现在可以探测到更多的目标,从大到小。当然,就像其他单次检测器一样,YOLO V3 运行速度也很快,并使实时推断在 GPU 设备上成为可能。嗯,作为一个物体检测的初学者,你可能没有一个清晰的图像,他们在这里意味着什么。但是你会在我后面的文章中逐渐理解它们。目前,只要记住 YOLO V3 是截至 2019 年实时对象检测方面最好的模型之一。
网络架构
Diagram by myself
首先,让我们来谈谈这个网络在高层次图中看起来是什么样子(尽管,网络架构是实现中最不耗时的部分)。整个系统可以分为两大部分:特征提取器和检测器;两者都是多尺度。当一幅新图像进来时,它首先通过特征提取器,这样我们可以在三个(或更多)不同的尺度上获得特征嵌入。然后,这些特征被馈入检测器的三个(或更多)分支,以获得包围盒和类别信息。
暗网-53
YOLO V3 使用的特征提取器被称为 Darknet-53。你可能对 YOLO·V1 之前的暗网版本很熟悉,那里只有 19 层。但那是几年前的事了,图像分类网络已经从仅仅是深层次的堆叠发展了很多。ResNet 带来了跳过连接的想法,以帮助激活通过更深的层传播,而不会减少梯度。Darknet-53 借用了这一思想,成功地将网络从 19 层扩展到 53 层,从下图可以看出。
Diagram from [YOLOv3: An Incremental Improvement](https://arxiv.org/abs/1804.02767)
这个很好理解。将每个矩形中的层视为残余块。整个网络是一个由多个模块组成的链,在模块之间有两个 Conv 层以减少维度。在块内部,只有一个瓶颈结构(1x1 后跟 3x3)加上一个跳过连接。如果目标是像 ImageNet 一样进行多类分类,将添加一个平均池和 1000 路完全连接层以及 softmax 激活。
但是,在对象检测的情况下,我们不会包括这个分类头。相反,我们将为这个特征提取器添加一个“检测”头。由于 YOLO V3 被设计为多尺度检测机,我们也需要多尺度的特征。因此,来自最后三个残差块的特征都被用于后面的检测。在下图中,我假设输入是 416x416,因此三个比例向量将是 52x52、26x26 和 13x13。请注意,如果输入尺寸不同,输出尺寸也会不同。
Diagram by myself
多尺度探测器
一旦我们有了三个特征向量,我们现在可以将它们输入到检测器中。但是我们应该如何构造这个探测器呢?不幸的是,作者在这篇论文中没有解释这一部分。但是我们仍然可以看看他在 Github 上发布的源代码。通过这个配置文件,在最终的 1x1 Conv 层之前使用多个 1x1 和 3x3 Conv 层来形成最终输出。对于中比例尺和小比例尺,它还会连接先前比例尺的要素。通过这样做,小规模检测也可以受益于大规模检测的结果。
Diagram by myself
假设输入图像为(416,416,3),检测器的最终输出将为 *[(52,52,3,(4 + 1 +数量类)),(26,26,3,(4 + 1 +数量类)),(13,13,3,(4 + 1 +数量类))]。列表中的三个项目代表三种秤的检测。但是这个52 x 52 x 3 x(4+1+num _ classes)*矩阵中的单元格是什么意思呢?好问题。这就把我们带到了 2019 年前物体检测算法中最重要的概念:锚盒(prior box)。
锚箱
物体检测的目标是得到一个包围盒和它的类。包围盒通常以一种标准化的 xmin,ymin,xmax,ymax 格式表示。例如,0.5 xmin 和 0.5 ymin 表示框的左上角在图像的中间。直观上,如果我们想得到一个像 0.5 这样的数值,就面临着一个回归问题。我们还不如让网络预测值,并使用均方差来与实际情况进行比较。然而,由于盒子的比例和长宽比的差异很大,研究人员发现,如果我们只是使用这种“暴力”的方式来获得一个包围盒,网络真的很难收敛。因此,在 fast-RCNN 论文中,提出了锚盒的概念。
锚定框是先前的框,其可以具有不同的预定义纵横比。这些纵横比在训练之前通过在整个数据集上运行 K-means 来确定。但是盒子固定在哪里呢?我们需要引入一个叫做网格的新概念。在“古老”的 2013 年,算法通过使用一个窗口滑过整个图像并在每个窗口上运行图像分类来检测对象。然而,这是如此低效,以至于研究人员提议使用 Conv 网一次性计算整个图像(从技术上讲,只有当你并行运行卷积核时。)由于卷积输出特征值的正方形矩阵(如 YOLO 的 13×13、26×26 和 52×52),我们将该矩阵定义为“网格”,并为网格的每个单元分配锚框。换句话说,定位框定位到网格单元,它们共享同一个质心。一旦我们定义了这些锚,我们就可以确定基础事实框与锚框有多少重叠,并选择具有最佳 IOU 的一个并将它们耦合在一起。我猜你也可以声称地面真相箱锚定到这个锚定箱。在我们后面的训练中,我们现在可以预测这些边界框的偏移,而不是预测来自西部的坐标。这是因为我们的地面真相框应该看起来像我们选择的锚框,只需要细微的调整,这给了我们一个很好的训练开端。
Diagram by myself
在 YOLO v3 中,每个网格单元有三个锚盒。我们有三个等级的网格。因此,我们将为每个秤配备 52x52x3、26x26x3 和 13x13x3 锚盒。对于每个锚盒,我们需要预测 3 件事:
1.相对于锚箱的位置偏移: tx,ty,tw,th 。这有 4 个值。
2。指示此框是否包含对象的对象性分数。这有 1 个值。
3。类别概率告诉我们这个盒子属于哪个类别。这有 num_classes 个值。
总的来说,我们为一个锚盒预测了 4 + 1 + num_classes 个值,这就是为什么我们的网络输出一个形状为*52x 52 x3x(4+1+num _ classes)*的矩阵,正如我之前提到的。 tx,ty,tw,th 不是边界框的真实坐标。这只是相对于特定锚盒的相对偏移量。我会在后面的损失函数部分解释这三个预测。
Anchor box 不仅使检测器的实现变得更加困难和容易出错,而且如果你想得到最好的结果,它还在训练之前引入了一个额外的步骤。所以,就我个人而言,我非常讨厌它,觉得这个锚箱的想法更像是一个黑客,而不是一个真正的解决方案。2018 年和 2019 年,研究人员开始质疑锚盒的必要性。像 CornerNet、Object as Points 和 FCOS 这样的论文都讨论了在没有锚盒帮助的情况下从头训练对象检测器的可能性。
损失函数
有了最终的检测输出,我们现在可以计算相对于地面真实标签的损失。损失函数包括四个部分(或者五个,如果你分开 noobj 和 obj):质心(xy)损失、宽度和高度(wh)损失、对象(obj 和 noobj)损失和分类损失。放在一起时,公式是这样的:
Loss = Lambda_Coord * Sum(Mean_Square_Error((tx, ty), (tx’, ty’) * obj_mask)
+ Lambda_Coord * Sum(Mean_Square_Error((tw, th), (tw’, th’) * obj_mask)
+ Sum(Binary_Cross_Entropy(obj, obj’) * obj_mask) + Lambda_Noobj * Sum(Binary_Cross_Entropy(obj, obj’) * (1 -obj_mask) * ignore_mask)
+ Sum(Binary_Cross_Entropy(class, class’))
这看起来很吓人,但是让我把它们一一分解并解释。
xy_loss = Lambda_Coord * Sum(Mean_Square_Error((tx, ty), (tx’, ty’)) * obj_mask)
第一部分是包围盒形心的损失。 tx 和 ty 是相对于地面的质心位置。 tx’ 和 ty’ 是直接来自检测器的质心预测。这种损失越小,预测和地面实况的质心就越接近。由于这是一个回归问题,我们在这里使用均方差。此外,如果对于某些细胞没有来自地面真理的对象,我们不需要将该细胞的损失包括在最终损失中。因此我们在这里也乘以 obj_mask 。 obj_mask 为 1 或 0,表示是否有物体。事实上,我们可以使用 obj 作为 obj_mask , obj 是我将在后面介绍的客观性分数。需要注意的一点是,我们需要对地面实况进行一些计算,以得到这个 tx 和 ty 。所以,我们先来看看如何得到这个值。正如作者在论文中所说:
bx = sigmoid(tx) + Cx
by = sigmoid(ty) + Cy
这里的 bx 和 by 是我们通常用来作为质心位置的绝对值。例如, bx = 0.5,by = 0.5 表示这个盒子的质心就是整个图像的中心。然而,由于我们要计算锚点的质心,我们的网络实际上是预测相对于网格单元左上角的质心。为什么是网格单元?因为每个锚定框都绑定到一个网格单元,所以它们共享同一个质心。所以对网格单元的差异可以代表对锚盒的差异。在上面的公式中, sigmoid(tx) 和 sigmoid(ty) 是相对于网格单元的质心位置。例如, sigmoid(tx) = 0.5,sigmoid(ty) = 0.5 表示质心是当前网格单元的中心(但不是整个图像)。 Cx 和 Cy 代表当前网格单元左上角的绝对位置。因此,如果网格单元是网格 13x13 的第二行第二列中的单元,那么 Cx = 1,Cy = 1 。而如果我们将这个网格单元位置加上相对质心位置,我们将得到绝对质心位置 bx = 0.5 + 1 和 by = 0.5 + 1 。当然,作者不会告诉你,你也需要通过除以网格大小来归一化,所以真正的 bx 将是 1.5/13 = 0.115 。好了,现在我们理解了上面的公式,我们只需要把它反过来,这样我们就可以从 bx 得到 tx ,以便把我们原来的地面真相翻译成目标标签。最后, Lambda_Coord 是 Joe 在 YOLO v1 论文中引入的权重。这是更强调本地化而不是分类。他建议的值是 5。
Diagram from [YOLOv3: An Incremental Improvement](https://arxiv.org/abs/1804.02767)
wh_loss = Lambda_Coord * Sum(Mean_Square_Error((tw, th), (tw’, th’)) * obj_mask)
下一个是宽度和高度的损失。再次,作者说:
bw = exp(tw) * pw
bh = exp(th) * ph
这里 bw 和 bh 仍然是整个图像的绝对宽度和高度。 pw 和 ph 是前一个盒子的宽度和高度(又名。锚箱,为什么有这么多名字)。我们在这里取 e^(tw) 是因为 tw 可能是负数,但是在现实世界中宽度不会是负数。所以这个 exp() 会让它为正。并且我们乘以先前的框宽度 pw 和 ph ,因为预测 exp(tw) 是基于锚框的。所以这个乘法给了我们真正的宽度。身高也一样。同样,我们可以在计算损耗时,将上面的公式反过来将 bw 和 bh 转化为 tx 和 th 。
obj_loss = Sum(Binary_Cross_Entropy(obj, obj’) * obj_mask)noobj_loss = Lambda_Noobj * Sum(Binary_Cross_Entropy(obj, obj’) * (1 — obj_mask) * ignore_mask)
第三项和第四项是客体性和非客体性得分损失。对象性表示当前单元格中有对象的可能性。与 YOLO v2 不同,这里我们将使用二进制交叉熵而不是均方误差。事实上,对于包含对象的单元格,objectness 始终为 1,对于不包含任何对象的单元格,object ness 始终为 0。通过测量这个 obj_loss ,我们可以逐渐教会网络检测感兴趣的区域。在此期间,我们不希望网络通过到处提出对象来欺骗我们。因此,我们需要 noobj_loss 来惩罚那些假阳性提议。我们通过用 1-obj_mask 屏蔽预测得到假阳性。*ignore_mask*
用于确保我们只在当前框与地面真相框没有太多重叠时进行惩罚。如果有,我们倾向于更柔和,因为它实际上非常接近答案。正如我们从论文中看到的,“如果边界框先验不是最好的,但确实与地面真实对象重叠超过某个阈值,我们忽略预测。”由于在我们的基本事实中有比 obj 多得多的 noobj,我们也需要这个 Lambda_Noobj = 0.5 来确保网络不会被没有对象的单元所控制。
class_loss = Sum(Binary_Cross_Entropy(class, class’) * obj_mask)
最后一个损失是分类损失。如果总共有 80 个类,则类和类’将是具有 80 个值的独热编码向量。在 YOLO v3 中,它被更改为多标签分类,而不是多类分类。为什么?因为一些数据集可能包含分层或相关的标签,例如女人和人。因此,每个输出像元可能有不止一个类为真。相应地,我们也对每一类逐一应用二元交叉熵并求和,因为它们并不互斥。就像我们对其他损失所做的那样,我们也乘以这个 obj_mask ,以便我们只计算那些具有基础真值对象的单元格。
为了充分理解这种损失是如何发生的,我建议您用一个真实的网络预测和地面实况来手动浏览它们。用计算器(或 tf.math )计算损失真的能帮你抓住所有的本质细节。我自己做的,这帮助我找到了很多错误。毕竟,细节决定成败。
履行
如果我在这里停止写作,我的帖子将会像网络上的另一篇“YOLO v3 评论”。一旦您从上一节中理解了 YOLO v3 的一般概念,我们现在就可以开始探索我们 YOLO v3 之旅剩下的 90%了:实现。
结构
9 月底,谷歌终于发布了 TensorFlow 2.0.0。这对 TF 来说是一个迷人的里程碑。然而,新的设计并不一定意味着开发者的痛苦会减少。从 2019 年初开始,我就一直在玩 TF 2,因为我一直想以我为 PyTorch 所做的方式编写 TensorFlow 代码。如果不是因为 TensorFlow 强大的制作套件像 TF Serving,TF lite,还有 TF Board 等等。,我估计很多开发者不会为新项目选择 TF。因此,如果您对生产部署没有强烈的需求,我建议您在 PyTorch 甚至 MXNet 中实现 YOLO v3。然而,如果你下定决心坚持使用 TensorFlow,请继续阅读。
TensorFlow 2 正式让渴望模式成为一级公民。简而言之,您现在可以利用原生 Python 代码以动态模式运行图表,而不是使用 TensorFlow 特定的 API 在图表中进行计算。没有更多的图形编译和更容易的调试和控制流程。在性能更重要的情况下,还提供了一个方便的 tf.function decorator 来帮助将代码编译成静态图。但是,现实是,渴望模式和 tf.function 仍然有问题,或者有时没有很好地记录,这使得你在像 YOLO v3 这样复杂的系统中的生活更加艰难。此外,Keras 模型不是很灵活,而定制的训练循环仍然是相当实验性的。所以你用 TF 2 写 YOLO v3 最好的策略就是先从一个最小的工作模板开始,逐渐给这个外壳增加更多的逻辑。通过这样做,我们可以在错误隐藏在一个巨大的嵌套图中之前尽早失败并修复它。
资料组
除了要选择的框架,成功训练最重要的是数据集。在论文中,作者使用 MSCOCO 数据集来验证他的想法。事实上,这是一个很好的数据集,我们应该在这个基准数据集上为我们的模型争取一个好的精度。然而,像这样的大型数据集也可能隐藏代码中的一些错误。例如,如果损失没有下降,你如何知道它只是需要更多的时间来收敛,或者你的损失函数是错误的?即使使用 GPU,训练的速度仍然不够快,无法让你快速迭代和修复东西。因此,我建议您构建一个包含数十个图像的开发集,以确保您的代码首先看起来“工作”。另一种选择是使用 VOC 2007 数据集,它只有 2500 个训练图像。要使用 MSCOCO 或 VOC2007 数据集并创建 TF 记录,可以参考我这里的助手脚本: MSCOCO , VOC2007
预处理
预处理是指将原始数据转换成合适的网络输入格式的操作。对于图像分类任务,我们通常只需要调整图像的大小,并对标签进行一次性编码。但是对于 YOLO v3 来说,事情有点复杂。还记得我说过网络的输出就像*52 x 52 x3x(4+1+num _ classes)*有三种不同的尺度吗?由于我们需要计算基础事实和预测之间的差值,我们还需要首先将基础事实格式化成这样的矩阵。
对于每个真实边界框,我们需要选择最佳的比例和锚。例如,天空中的一个小风筝应该是小比例的(52x52)。如果风筝在图像中更像一个正方形,我们也应该选择那个比例中最正方形的锚。在 YOLO v3 中,作者为 3 个音阶提供了 9 个锚点。我们所需要做的就是选择一个最符合我们地面真相框的。当我实现这个时,我想我也需要锚盒的坐标来计算 IOU。事实上,你不需要。因为我们只是想知道哪个锚点最适合我们的基础真相框,所以我们可以假设所有锚点和基础真相框共享同一个质心。在这种假设下,匹配度就是重叠面积,可以通过最小宽度最小高度*来计算。
在转换过程中,还可以添加一些数据扩充,以增加虚拟训练集的多样性。例如,典型的增强包括随机翻转、随机裁剪和随机转换。然而,这些增强不会阻止您训练一个工作的检测器,所以我不会过多地讨论这个高级主题。
培养
经过这些讨论,你终于有机会运行“*python train . py”*开始你的模型训练了。这也是你遇到大多数 bug 的时候。当你被封锁时,你可以参考我的训练脚本这里。同时,我想提供一些对我自己的训练有帮助的提示。
楠的损失
- 检查你的学习率,确保它不会太高,以至于爆发你的梯度。
- 检查二进制交叉熵中的 0,因为 ln(0)不是数字。您可以从(epsilon,1-epsilon)中截取值。
- 找个例子,一步一步走过你的失落。找出你损失的哪一部分归南。例如,如果宽度/高度损失到 NaN,这可能是因为您从 tw 到 bw 的计算方法是错误的。
亏损居高不下
- 试着提高自己的学习率,看看能不能降得更快。我的从 0.01 开始。但我也见过 1e-4 和 1e-5 的作品。
- 想象你预处理过的基础事实,看看它是否有意义。我之前遇到的一个问题是,我的输出网格是在[y][x]而不是[x][y],但是我的地面真相是反的。
- 再一次,用一个真实的例子来说明你的损失。我在计算对象和类别概率之间的交叉熵时犯了一个错误。
- 我的损失也保持在 50 个 MSCOCO 时代后的 40 左右。然而,结果并没有那么糟糕。
- 仔细检查代码中的坐标格式。YOLO 需要 xywh (质心 x,质心 y,宽度和高度),但大部分数据集都是以 x1y1x2y2 (xmin,ymin,xmax,ymax)的形式出现。
- 仔细检查你的网络架构。不要被一个名为“近距离观察 yolov 3-CyberAILab”的帖子中的图表误导。
- TF . keras . loss . binary _ cross entropy不是你需要的二进制交叉熵之和。
损失较低,但预测失败
- 根据您的观察将 lambda_coord 或 lambda_noobj 调整到损耗。
- 如果你在自己的数据集上训练,并且数据集相对较小,那么损失函数中的 obj_mask 就不会错误地去掉必要的元素。
- 一次又一次,你的损失函数。计算损耗时,它使用单元格中的相对 xywh(也称为 tx,ty,tw,th )。不过,在计算忽略遮罩和 IOU 时,它会在整个图像中使用绝对 xywh。不要把它们混淆了。
- 损失很低,但是没有预测
- 如果您正在使用自定义数据集,请首先检查您的基础事实框的分布。盒子的数量和质量真的会影响网络学习(或欺骗)做什么。
在您的训练集上进行预测,看看您的模型是否至少可以在训练集上过度拟合。
- 多 GPU 训练
- 由于对象检测网络有如此多的参数要训练,因此拥有更多的计算能力总是更好的。然而,TensorFlow 2.0 到目前为止还没有对多 GPU 训练提供很大的支持。要在 TF 中做到这一点,你需要选择一个训练策略,比如 MirroredStrategy,就像我在这里做的。然后将数据集加载器也打包成分布式版本。对于分布式训练的一个警告是,每个批次产生的损失应该除以全局批次大小,因为我们将对所有 GPU 结果进行“减少总和”。例如,如果本地批量大小为 8,并且有 8 个 GPU,那么您的批量损失应该除以全局批量大小 64。一旦你把所有复制品的损耗加起来,最后的结果将是单个例子的平均损耗。
后处理
该检测系统的最后一个组件是后处理器。通常,后处理只是一些琐碎的事情,比如用人类可读的类文本替换机器可读的类 id。不过,在对象检测中,我们还有一个更关键的步骤要做,以获得最终的人类可读结果。这被称为非最大抑制。
让我们回忆一下我们的客观损失。当虚假提议与地面真相有很大重叠时,我们不会用 noobj_loss 来惩罚它。这鼓励网络预测接近的结果,以便我们可以更容易地训练它。此外,虽然在 YOLO 没有使用,但当使用滑动窗口方法时,多个窗口可以预测同一物体。为了消除这些重复的结果,聪明的研究人员设计了一种叫做非最大抑制(NMS)的算法。
NMS 的想法很简单。首先找出具有最佳置信度的检测框,将其添加到最终结果中,然后用该最佳框消除 IOU 超过特定阈值的所有其他框。接下来,你在剩下的盒子中选择另一个最有信心的盒子,重复做同样的事情,直到什么都没有了。在代码中,由于 TensorFlow 在大多数时候需要显式的形状,我们通常会定义一个最大的检测数,如果达到这个数就提前停止。在 YOLO v3 中,我们的分类不再相互排斥,一个检测可以有多个真实的类。然而,一些现有的 NMS 规范没有考虑到这一点,所以在使用时要小心。
结论
Photo by Python Lessons from Analytics Vidhya
YOLO v3 是人工智能崛起时代的杰作,也是 2010 年代卷积神经网络技术和技巧的优秀总结。虽然有许多像 Detectron 这样的交钥匙解决方案来简化制作探测器的过程,但对机器学习工程师来说,编写如此复杂的探测器的实践经验确实是一个很好的学习机会,因为仅仅阅读论文是远远不够的。就像雷伊·达里奥谈到他的哲学时说的那样:
痛苦加反思等于进步。
我希望我的文章可以成为您在实施 YOLO v3 的痛苦旅程中的一座灯塔,也许您也可以在以后与我们分享这一令人愉快的进展。如果你喜欢我的文章或者我的 YOLO v3 源代码,请⭐star⭐我的 repo ,那将是对我最大的支持。
参考
张子豪, YoloV3 在 TensorFlow 2.0 ,Github 中实现
杨云, TensorFlow2.x-YOLOv3 ,Github
- 逍遥王可爱, 史上最详细的 Yolov3 边框预测分析, 知乎专栏
- Joseph Redmon, YOLOv3:增量改进
- 约瑟夫·雷德蒙,阿里·法尔哈迪,YOLO9000:更好,更快,更强
- 约瑟夫·雷德蒙,桑托什·迪夫瓦拉,罗斯·吉斯克,阿里·法尔哈迪,你只看一次:统一的实时物体检测,2016 年 IEEE 计算机视觉与模式识别会议
- Ayoosh Kathuria,YOLO v3 有什么新内容?,走向数据科学
- Python 课程, YOLO v3 理论讲解,分析 Vidhya
- Ayoosh Kathuria, What’s new in YOLO v3?, Towards Data Science
- Python Lessons, YOLO v3 theory explained, Analytics Vidhya
二项式随机变量的散度
Photo by Eddie Zhang on Unsplash
在这篇文章中,我们将了解因变量 试验 或样本的数量如何影响二项变量。在进入这个主题之前,需要理解什么是二项随机变量。如果你不熟悉这个——请访问这个帖子。在这篇文章中,我们将尝试回答两个问题
- "审判应该是相互独立的."对于二项随机变量,这个条件真的有必要吗?
- 我们知道二项随机变量服从离散分布,有没有可能它也可以是连续分布?
在开始回答这些问题之前,让我们回忆一下二项式变量的条件:
- 试验应相互独立。
- 每次试验都可以分为成功或失败。
- 试验次数固定。
- 每次试验的成功概率应保持不变。
现在让我们看看个别问题:
1.审判应该是相互独立的
我们试着用一个例子来理解。考虑一个场景:假设我们想在一家食品超市进行一项调查,我们需要从三个不同的人那里获得关于他们是否购买了一种产品的反馈。表示我们从一家食品超市的所有顾客中随机选择 3 个人,并询问他们是否购买了某种产品。假设商场中有 50%的人购买了该产品,商场中总共有 200 人,你会如何发现被选中的 3 个人购买该产品的概率是多少?
在这种情况下,让我们定义 X =在由 200 名顾客中的 3 名反馈组成的调查中购买该产品的人数。我们需要找到 p(X=3 ),即 200 人中随机选择的 3 个人购买该产品的概率。
在这里,审判是“选人”。实验由固定数量的试验(称为反馈)组成,等于 3 次。如果有人购买了该产品,则可以认为试用成功。因此满足条件 2,3,4。条件 1 呢。让我们来研究一下:
比方说,我们决定收集走出商店的前 3 个人的反馈。据我们所知,商场里 50%的人都购买过这种产品。那么第一个人购买了该产品的概率是多少?它是 50%,即 100/200。现在让我们假设第一个人走出来,我们发现他已经买了这个产品。现在下一个人购买该产品的概率是多少?因为里面有 199 个人,所以是 99/199。现在二审并不独立于一审。因此,它似乎不是二项分布。为了使它独立,我们必须把第一个人送回超市,这不是一个好主意,因为我们不能仅仅因为我们希望我们的试验是独立的,就要求顾客回到店里。
解决方案:根据实验假设
如果样本量小于或等于总人口的 10%,则试验可视为独立的
在上面的例子中,3 小于或等于 200 的 10%。因此,这些试验可以被视为独立的。不管第一个走出商店的人是谁,我们仍然可以认为这是一个独立的事件。
因此:p(X = 3)=(100/200)(100/200)(100/200)
2.有没有可能也可以是连续分布?
随着一个实验中的试验次数越来越多,这个分布看起来就像正态分布,它只不过是一个连续的变量分布。
在上述图示中,试验次数限制为 5 次。当试验趋于无穷大时,二项分布趋于正态分布。
结论
在这篇文章中,我们了解到,如果试验数量小于或等于总人口的 10%,即使是相依试验也可能导致二项分布。我们还观察到随着试验次数的增加,二项分布如何接近正态分布。
寻找下一个后几何随机变量
分而治之:使用 RFM 分析对你的客户进行细分
使用 PYTHON 进行客户细分
了解如何使用 Python 和 RFM 对客户群进行细分,从而做出更好的商业决策
Photo by Linh Pham on Unsplash
大多数经营在线业务的人必须知道从他们平台产生的用户数据中收集洞察力的重要性。了解一些可用性问题、客户偏好、一般购买行为等等。因此,如果你正在建立一个电子商务,存储你的订单、客户及其交易的数据是至关重要的。存储这些数据的主要目的之一是分析客户的行为,并据此设计更有利可图的策略。
在我们公司也没什么不同!我们有一个非常以数据为中心的文化,并一直在寻找新的方法,根据我们可以从中提取的信息来改进我们的产品。我们的核心产品之一是一款饮料配送应用程序,于 2019 年第二季度开始运营。有问题的应用程序是商店和当地生产商销售啤酒、葡萄酒、饮料和类似产品(如零食等)的市场。
由于我们是一家初创公司,而且该产品在市场上还比较新,因此它带来了一些独特的挑战,开发它是一个不断发展的过程。我们目前正在实施各种新功能和缺陷修复。然而,自我们发布以来生成的数据仍然是了解我们的客户以及平台本身的重要来源。
除此之外,将商业智能集成到系统中的追求也推动了这项工作。在这一系列文章中,我们将研究两种不同的方法,根据客户的相似性将他们分成不同的组:经典的 RFM 分析(在第一篇文章中探讨)和 K-Means 聚类(在后续文章中探讨)。
在本系列中,我们将尝试回答其中的一些问题:
- 现有的用户角色(组)是什么?
- 这些用户群有什么特征(平均票、频率、新近度、位置等。)?
当然,由于数据是专有的,并且包含一些敏感信息,我们无法提供原始数据集供您跟踪。除此之外,呈现的一些数据将被预先修改或标准化。
但是不要害怕!你可以在 Kaggle 上找到一些类似的数据集,比如 零售数据分析网上零售数据集 和 零售交易数据 数据集。除此之外,这些文章的目的更多的是教你一些理论和编码,而不是数据和结果本身。
所以,让我们开始吧!
一些先决条件
为了执行这个分析,我们使用了 Python 编程语言和一些基本的数据科学库。虽然并不严格要求您理解代码的所有部分,但是建议您至少对编程有一点了解。
考虑到这一点,下面是我们将在整个系列中使用的一些概念:
- Python 以及对 Numpy、Pandas 和 Scikit 的一些基本了解——学习 APIs
- 一些统计(没什么太花哨的);
- 基本了解一些机器学习概念和术语,如聚类
说完这些,让我们开始讨论吧
首先,什么是分段,为什么需要分段
客户细分是发展成功业务的重要组成部分。客户有不同类型的需求,随着客户和交易基础的增长,了解他们每个人的需求变得越来越困难。在这一点上,你应该能够识别这些差异并采取行动。然而,有许多方法来执行这种分段。我们今天要谈论的是 RFM。
RFM 是选择重要客户最广泛使用的技术之一。这是一种非常流行的客户细分技术,它使用客户过去的购买行为,根据相似性将他们分成不同的组。
RFM 这个名字代表新近性、频率和货币价值。Recency ®表示自客户最后一次购买以来的天数;频率(F)代表客户在被分析的时间范围内购买的次数;最后,货币(M)表示客户在同一时期花费的总金额。
在计算了每个客户的近期、频率和货币价值后,我们需要将他们分成 3 个或更多不同的类别。这样,我们可以根据这些类别对不同类型的客户进行排名。
让我们在一个更实际的场景中来看看,好吗?
数据
我们将从一个数据集开始,该数据集包含在我们的平台上执行的单个订单的信息。数据以交易级别表示,即数据集的每一行包含与单个交易相关的特征,例如日期、时间、支付方式、进行购买的用户的客户端 id 等。
数据集的一些重要特征突出显示如下:
- order_id: 订单的唯一标识符;
- store_id: 订单所在店铺的 id;
- customer_id: 执行订单的客户的 id;
- 支付选项 id: 所使用的支付选项的标识符;
- ****状态:一个表示订单当前状态的整数;
- ****创建:订单创建的日期和时间;
- ****修改日期:订单状态上次修改的日期和时间
- shipping_price: 自明
- total_price: 订单总价,含运费
- 纬度&经度:订单送达的地点
建造 RFM 餐桌
加载订单数据集后,我们需要对其进行分组和汇总,以获得每个客户的客户级数据,如订单数量、总支出、频率、最近等。有了这些信息,我们可以继续进行 RFM 分析。下面突出显示了用于加载数据集和执行分组的代码:
**# load the order dataset
orders = pd.read_csv('data/orders-27-11-2019.csv')# convert 'created' and 'modified' columns to datetime
orders['created'] = pd.to_datetime(orders['created'])
orders['modified'] = pd.to_datetime(orders['modified'])# create a snapshot date with today's date
snapshot_date = max(orders.created) + datetime.timedelta(days=1)# create functions to get recency and tenure
def get_recency(x):
last_purchase = x.max()
return (snapshot_date - last_purchase).days
def get_tenure(x):
first_purchase = x.min()
return (snapshot_date - first_purchase).days# aggregate data by the customers
customers = orders.groupby('customer_id').agg(
recency=('created', get_recency),
tenure=('created', get_tenure),
frequency=('order_id', 'count'),
total_value=('total_price', 'sum'),
mean_value=('total_price', 'mean'),
)# show 5 samples of the grouped dataframe
customers.sample(5)**
Figure 1: customer level dataset
请记住,我们掩盖了真正的价值,所以这就是为什么他们中的一些人可能看起来很奇怪。还要注意的是,我们添加了两个之前没有讨论过的新列:表示客户第一次购买以来的时间的保有权和平均值**,这是不言自明的。这两列将用于改进下一篇文章中介绍的集群。**
我们需要做的下一件事是将最近度**、频率、和总值分成我们之前讨论过的类别。对于我们的用例,我们决定将每个特性分成 4 个四分位数,大致将样本分成 4 个等比例的部分。我们分别把这些分数叫做 R 、 F 、 M 。用于执行此操作的代码如下所示:**
**# use only the necessary columns
rfm = customers[['customer_id', 'recency', 'frequency', 'total_value']]# recency quartile segmentation
r_labels = range(4, 0, -1)
recency = rfm['recency']
r_quartiles, bins = pd.qcut(recency, 4, labels=r_labels, retbins=True)
rfm = rfm.assign(R=r_quartiles.values)# frequency quartile segmentation
f_labels = range(1, 5)
frequency = rfm['frequency'].rank(method='first') # rank to deal with duplicate values
f_quartiles, bins = pd.qcut(frequency, 4, labels=f_labels, retbins=True)
rfm = rfm.assign(F = f_quartiles.values)# monetary value quartile segmentation
m_labels = range(1, 5)
monetary = rfm['total_value']
m_quartiles, bins = pd.qcut(monetary, 4, labels=m_labels, retbins=True)
rfm = rfm.assign(M = m_quartiles.values)# show 5 samples of the newly created scores
rfm[['R', 'F', 'M']].sample(5)**
Figure 2: Initial RFM scores
为了简化分析,我们将 3 个不同的分数( R 、 F 和 M )结合起来创建一个单一的指标是很重要的。
有几种方法可用。第一个是创建一个 RFM 段,将个人得分的 3 个数字连接起来,形成一个从 111(所有三个指标的最低分)到 444(所有三个指标的最高分)的 3 个字符串。这种方法的缺点是创建了许多不同的细分市场(4x4x4 = 64 个细分市场),不容易区分和区分优先级(432 和 234 客户谁更有价值?).
另一种可能性是将 3 个单独的分数相加,得出 RFM 分数,一个从 3(所有指标中可能的最低分)到 12(所有指标中可能的最高分)的数字。这里的缺点是,具有不同购买习惯的客户(例如,来自不同的 RFM 细分市场)可能会落在相同的分数箱上。例如,细分市场 431 和 134 中的两个客户都将得到 8 分。另一方面,我们最终得到了不太明显的分数来进行比较(4+4+4 = 12 个分数),每个分数都具有相同的相关性。
**# Build RFM Segment and RFM Score
def join_rfm(x):
return str(x['R']) + str(x['F']) + str(x['M'])rfm['segment'] = rfm.apply(join_rfm, axis=1)
rfm['score'] = rfm[['R','F','M']].sum(axis=1)# show 5 samples
rfm[['R', 'F', 'M', 'segment', 'score']].sample(5)**
Figure 3: RFM segments and scores
我们可以根据宁滨的得分范围进一步将客户分为 RFM 等级。例如,我们可以说,分数从 3 到 5 的客户是青铜级,从 5 到 9 的客户是白银级,从 9 到 12 的客户是黄金级。
**# group into different tiers
def get_tier(df):
if df['score'] >= 9:
return 'Gold'
elif (df['score'] >= 5) and (df['score'] < 9):
return 'Silver'
else:
return 'Bronze'rfm['tier'] = rfm.apply(get_tier, axis=1)rfm[['R', 'F', 'M', 'segment', 'score', 'tier']].sample(5)**
Figure 4: RFM scores binned into 3 tiers
好吧,但是那告诉你什么?
按照这些简单的步骤,你已经成功地细分了你的客户群!从这里你能去哪里?
您可以通过构建一些汇总来查看每个不同分数**、段、和层级的变量的均值和**标准差****
**# Summary metrics per RFM Score
score_summary = rfm.groupby('score').agg(
mean_recency=('recency', 'mean'),
std_recency=('recency', 'std'),
mean_frequency=('frequency', 'mean'),
std_frequency=('frequency', 'std'),
mean_monetary=('total_value', 'mean'),
std_monetary=('total_value', 'std'),
samples=('customer_id', lambda x: len(x)*100/len(rfm.score))
).round(2)# Get the 10 segments with most customers
popular_segments = rfm.segment.value_counts()[:10].index.tolist()# Summary metrics for the 10 most popular RFM Segments
segment_summary = rfm[rfm.segment.isin(popular_segments)].groupby('segment').agg(
mean_recency=('recency', 'mean'),
std_recency=('recency', 'std'),
mean_frequency=('frequency', 'mean'),
std_frequency=('frequency', 'std'),
mean_monetary=('total_value', 'mean'),
std_monetary=('total_value', 'std'),
samples=('customer_id', lambda x: len(x)*100/len(rfm.score))
).round(2)# Summary metrics per RFM Tier
tier_summary = rfm.groupby('tier').agg(
mean_recency=('recency', 'mean'),
std_recency=('recency', 'std'),
mean_frequency=('frequency', 'mean'),
std_frequency=('frequency', 'std'),
mean_monetary=('total_value', 'mean'),
std_monetary=('total_value', 'std'),
samples_percentage=('customer_id', lambda x: len(x)*100/len(rfm.score))
).round(2)**
最近发生率最低、频率最高和金额最高的客户可以被归类为我们的最佳客户。
另一方面,新近性高的顾客是那些由于某种原因不再光顾商店的顾客。企业应该专注于寻找和理解这些原因,以及一些方法来重新激活那些沉睡的客户。
最后,低频顾客是那些不经常购买的顾客。如果他们的新近度较低,他们可能是新客户,企业应该专注于留住他们以备将来购买。另一方面,如果他们具有较高的新近性,他们也可以被归类为需要重新激活的客户。
结论
至此,我们结束了对 RFM 分割的讨论。但是你不应该停止探索!
除了对客户进行细分,企业还可以使用 RFM 标准来评估客户的购买模式,并通过分析这些客户的反应来评估不同营销策略的有效性。
在系列的下一部分中,我们将深入研究另一种形式的客户细分:使用 K 均值进行聚类。
TL;博士
RFM 分析的步骤可以总结如下:
- 创建 RFM 表;
- 计算每个客户的近期、频率和货币价值;
- 将不同的值分成不同的类别(在我们的例子中是从 1 到 4),创建 R、F 和 M 分数;
- 根据上一步计算的 3 个基本分数,创建组合指标,如 RFM 段、 RFM 分数、 RFM 等级;
- 创建摘要以可视化这些组;
潜入大理:如何使用 NVIDIA 的 GPU 优化图像增强库
Salvador Dalí. The Persistence of Memory. Credit: The Museum of Modern Art
深度学习图像增强管道通常提供速度或灵活性,但永远不会同时提供两者。计算效率高、生产就绪的计算机视觉管道往往用 C++编写,并要求开发人员指定图像变换算法的所有细节,以至于这些管道最终不太适合进一步的即时调整。另一方面,像 Pillow 这样的流行 Python 库提供了高级 API,允许从业者从看似无限的调整组合中进行选择,这些调整可以应用于庞大的图像变换算法库。不幸的是,这种自由带来了性能急剧下降的代价*。*
大理图书馆试图让从业者两全其美。它的图像转换算法本身是用 C++代码编写的,可以充分发挥 NVIDIA GPU 芯片的性能,从而可以在每个批处理的基础上并行执行图像转换,无论用户可以访问多少个 GPU。C++源代码被绑定到一个用户友好的 Python API,通过它,从业者可以定义图像转换管道,该管道可以很好地与 PyTorch 和 TensorFlow 框架一起使用。
为了确定 DALI 是否确实提供了它所宣传的速度和灵活性,我花了一周的大部分时间对这个库进行了一系列自己的实验。剧透:虽然 DALI 绝对带来了速度,但灵活性仍有所欠缺。
达利的承诺
尽管如此,花时间去了解大理是绝对值得的。在 GPU 上进行图像增强的好处是不言而喻的,我的 DALI 图像管道比我写过的任何其他类似的图像增强管道运行得都快。
此外,我刚刚完成了 fast.ai 深度学习课程第二部分的最新课程,并试图建立一个与 fastai 库的新改进版本兼容的 DALI 管道,我们从头开始建立了 fastai 库,作为课程作业的一部分。这被证明是一个有意义的练习,因为根据 fastai 核心开发者 Sylvain Gugger 的说法,fastai 官方库即将发布的第二版将包含许多在我们班上引入的范例,例如一个具有更加灵活的回调集成的训练循环。
在接下来的几段中,我将介绍构建 DALI 管道的基础知识,并指出如何将它们连接到 fastai 的 v2.0 训练循环。你将会看到 DALI 运行的有多快(这真的令人印象深刻),以及我想出的一个非常奇怪的解决办法,试图绕过 DALI 图书馆的一个显著缺点。
搭建舞台
我的 DALI 增强管道包括随机裁剪和调整大小、翻转、透视扭曲和旋转变换,这些是我在 2019 fast.ai 第二部分课程中从零开始学习的实现的。为了设置基线并衡量每个 DALI 变换是否有助于改善结果,我创建了一个简单的四层 CNN 模型,其任务是使用 Imagenette 数据集执行图像分类。Imagenette 是由杰瑞米·霍华德创建的,是 ImageNet 的一个更精简的版本,它允许从业者感受如果在 ImageNet 上训练他们的模型将会如何执行,而实际上不必从头开始训练 ImageNet 的所有部分。我喜欢在原型的早期迭代中使用 Imagenette 进行快速的健全性检查,它已经成为我实验中不可或缺的一部分。
如何建设大理管道
所有 DALI 管道的主干是一个叫做管道的 Python 类。我决定为我的训练和验证数据创建专门的管道类,每个类都继承自这个类。为了创建每个类,我必须定义两个方法。第一个方法__init__()
,是指定每个图像变换操作的超参数的地方。除了像图像旋转和翻转这样的增强,这些操作还可以包括初始图像加载、调整大小、归一化、张量整形和数据类型转换。
第二个方法是define_graph()
,在这里您定义了您希望管道的图像转换执行的顺序。这个方法也是您希望调用 DALI 随机数生成器的地方,这样您就可以将它们作为参数传递给支持随机生成扩充的图像变换操作。define_graph()
将返回一个包含变换图像及其相应标签的元组。
示例:包括旋转变换
下面是我如何使用 DALI 的[ops.Rotate](https://docs.nvidia.com/deeplearning/sdk/dali-developer-guide/docs/supported_ops.html#rotate)
函数将随机生成的图像旋转添加到我的训练管道中:
第一步
在我的管道类’__init__()
方法中,我为旋转操作创建了变量,ops.Rotate
,也为两个随机数生成器创建了变量。第一个随机数生成器[ops.Uniform](https://docs.nvidia.com/deeplearning/sdk/dali-developer-guide/docs/supported_ops.html#uniform)
,将产生一个和我的批量一样长的列表。该列表将包含指定角度(以度为单位)的浮动,通过该角度ops.Rotate
将旋转批次图像。每个角度都是从范围为[-7,7]的均匀分布中随机选取的。第二个随机数生成器,[ops.CoinFlip](https://docs.nvidia.com/deeplearning/sdk/dali-developer-guide/docs/supported_ops.html#nvidia.dali.ops.CoinFlip)
,将创建一个包含 1 和 0 的列表,其长度也与批量大小相同。这些出现在随机指数中,总频率为 7.5%。将该列表传递给旋转变换将确保一批中的任何图像都有 7.5%的机会被旋转:
self.rotate = ops.Rotate(device=’gpu’, interp_type=types.INTERP_NN) self.rotate_range = ops.Uniform(range = (-7, 7))
self.rotate_coin = ops.CoinFlip(probability=0.075)
第二步
在define_graph()
方法中,我实际调用了ops.Uniform
和ops.CoinFlip
随机数生成器来为每批创建新的随机数集合:
angle_range = self.rotate_range()
prob_rotate = self.rotate_coin()
仍然在define_graph()
中,我在准备执行图像旋转的管道点调用ops.Rotate
,将上面两个随机数列表分别传递给它的angle
和mask
属性:
images = self.rotate(images, angle=angle_range, mask=prob_rotate)
DALI 现在将每个训练批次中大约 7.5%的图像旋转-7 到 7 度之间的角度。所有图像旋转同时并行发生!
以下是我的培训和认证管道课程的完整内容:
DALI Imagenette Train & Val Pipelines
构建 DALI 数据加载器
一旦编写了训练和验证管道类,剩下要做的就是创建它们各自的数据加载器(DALI 称之为“迭代器”)。构建一个与 PyTorch 一起工作的数据加载器只需要三行代码:
pipe = ImagenetteTrainPipeline()
pipe.build()
train_dl = DALIClassificationIterator(pipe, pipe.epoch_size('r'), stop_at_epoch=True)
大理速度测试
DALI 管道对象有一个run()
函数,它抓取一批图像,通过管道发送,并返回转换后的图像及其标签。计时这个功能是衡量 DALI 速度最简单的方法。
As far as speed goes, DALI can fly.
我在一个 AWS p2.xlarge 计算实例上运行了我的速度测试,使用了一个 GPU,小批量 64 张图像。我发现我的 Imagenette 训练管道(包含 12 个图像操作)的运行时间只有 40 毫秒多一点!对于流水线中的所有 12 个操作,这相当于每个图像 625 秒。相比之下,在 fast.ai 课程的图像增强课程中,我们看到使用 Pillow 进行图像转换的主要瓶颈是 Pillow 加载单个图像需要 5 ms。
我们还使用 PyTorch JIT 来实现一种图像旋转算法,类似于 DALI,在 GPU 上转换批处理。每批大约运行 4.3 毫秒。假设任何转换的 JIT 实现都需要相同的持续时间(可能是一段时间),快速的粗略计算表明 JIT 性能可能类似于 DALI (4.3 x 12 = 51.6 ms)。DALI 的美妙之处在于,虽然它花了12 行代码来定义执行 JIT 旋转转换的脚本,但是 DALI 只通过一个函数调用就给了我们相同的功能和速度!
DALI + fastai v2.0
对于参加 2019 fast.ai 深度学习第二部分课程的人来说,这里有三个技巧可以让 DALI 的数据加载器与新改进的[Learner()](https://nbviewer.jupyter.org/github/jamesdellinger/fastai_deep_learning_course_part2_v3/blob/master/09_optimizers_my_reimplementation.ipynb?flush_cache=true#Getting-rid-of-the-Runner-class)
对象无缝对接。
招数 1
修改Learner
类,使其正确地索引到 DALI 数据加载器返回的张量中。图像和标签分别包含在'data'
和'labels'
键下:
xb = to_float_tensor(batch[0]['data'])
yb = batch[0]['label'].squeeze().cuda().long()
此外,确保在每个时期后重置 DALI 训练和 val 数据加载器:
self.data.train_dl.reset()
self.data.valid_dl.reset()
招数 2
更改[AvgStats](https://nbviewer.jupyter.org/github/jamesdellinger/fastai_deep_learning_course_part2_v3/blob/master/04_callbacks_my_reimplementation.ipynb?flush_cache=true#AvgStats())
类,使all_stats()
方法返回self.tot_loss
而不是self.tot_loss.item()
。
招数 3
设定变量[combine_scheds()](https://nbviewer.jupyter.org/github/jamesdellinger/fastai_deep_learning_course_part2_v3/blob/master/05_anneal_my_reimplementation.ipynb?flush_cache=true#combine_scheds())
的最大值,超参数调度生成器在跟踪当前迭代相对于训练周期长度的位置时使用该变量:pos = min(1 — 1e-7, pos)
。
最初的意图是,在训练期间,该值将始终低于 1.0。然而,当使用 DALI 时,其在最终迭代的开始处的值有时会是 1.0 或稍大。这会导致 IndexError,因为调度程序被迫索引到一个实际上并不存在的调度阶段!
请随意查阅我的笔记本来查看包含这三个修改的训练循环的工作版本。
大理最明显的不足
我将用一些时间来总结 DALI 库,我认为这是它最显著的缺点:它的一些图像转换操作不能产生随机输出。我发现这尤其具有讽刺意味,因为 DALI 网站用了一整节来宣扬图像增强的好处,即能够随机干扰输入图像,声明:
“将每张图像旋转 10 度并不那么有趣。为了进行有意义的增强,我们希望操作员在给定范围内以随机角度旋转我们的图像。”
如果是这样的话,我想达利的扭曲仿射图像变换应该被视为“没什么意思”,因为它实际上无法生成随机的图像扭曲。更令人沮丧的是,尽管我为编写了自己的逻辑,它根据 DALI 的 warp affine 操作的matrix
参数所期望的约定生成随机仿射变换,但我绝对不可能让我的 DALI 管道在运行时为小批量的图像执行这个逻辑。
不出所料,有人请求支持随机化 warp affine,但 DALI 团队成员解释说warp affine 目前不是优先考虑的,因为团队专注于*“提供最常见网络中使用的操作符。”现在,作为一个前世是软件产品经理的人,我当然赞同优先考虑功能的想法。然而,看着 DALI 团队毫不犹豫地大声宣扬随机图像旋转的好处,我很难理解随机的 warp affinites为什么不能优先。*
尽管如此,一个可取之处是 DALI 团队成员确实鼓励开源贡献来弥补这个特性的不足。这是一件好事,也许很快有一天我会尝试移植我的随机仿射变换逻辑并提交一个 pull 请求。
“合成随机”扭曲仿射变换
我最终不满足于从我的增强管道中省略透视扭曲,也不同意对任何批次的任何图像应用相同的单一、单独的扭曲仿射变换。在尝试执行将由 DALI 的[ops.WarpAffine](https://docs.nvidia.com/deeplearning/sdk/dali-developer-guide/docs/supported_ops.html#nvidia.dali.ops.WarpAffine)
操作执行的仿射变换随机化的逻辑失败后,我决定尝试一种突然出现在我脑海中的公认的非常规变通方法。我把这称为“合成随机”扭曲仿射变换。它是这样工作的:
- 编写一个函数,它可以生成随机仿射变换并传递给
ops.WarpAffine
。我的算法确保随机生成的仿射变换将倾斜图像的视角,但不会不自然地挤压或拉伸图像的内容。 - 在我的管道中添加两到二十个 DALI
ops.WarpAffine
操作。(我做了一些实验来确定合适的量,发现七种效果最好。) - 为我包含在管道中的每个
ops.WarpAffine
操作生成一个唯一的仿射变换。 - 以介于 0.3 和 0.025 之间的概率将管道的每个扭曲仿射变换应用于特定图像。(我发现 0.025 效果最好。)
我的直觉是,通过适当选择扭曲仿射操作的数量,平衡每个操作将被应用的适当概率,我可以同时:
- 最大化应用于小批量图像的透视扭曲变换的种类。
- 充分减少单个图像在每个小批量中应用两个或更多扭曲变换的机会。
所有这一切中的一个亮点是 DALI 的速度:在我的流水线中额外添加两个或二十个 warp affine 操作并没有明显延长处理每个小批量的时间。
请注意,我写我的“合成随机”扭曲变形并不是为了让其他从业者尝试类似的方法。相反,我希望表达的是,无论我的解决方法在多大程度上看起来是非常规的,DALI 的扭曲变换不支持随机化的事实也是非常规的。
三个较小的诡辩
- 潜在的 DALI 用户已经习惯了 PyTorch 的动态特性,应该期待一个绝对静态tensor flow 1.0 版般的体验。当 DALI 团队将管道类命名为“核心方法定义图时,他们并没有开玩笑,所以不要期望能够在其中运行任何自定义算法,正如我在尝试给
ops.WarpAffine
添加随机性时所尝试的那样。目前推荐的方法是创建并编译一个定制的 C++操作符。这并没有让我觉得非常“灵活”,希望 DALI 将扩展其增强选项的广度,这将消除从业者创建定制操作的需要。 - 说到这里,DALI 缺乏对反射填充的支持。我的假设是,这是在我将旋转角度的范围从[-30,30]度缩减到[-7,7]度之前,在我的管道中添加旋转变换并没有提高模型性能的一个重要原因。虽然 DALI 确实允许从业者指定用于填充旋转后图像角落中发现的空像素的单一颜色,但我怀疑使用全绿或全白填充而不是全黑默认填充是否会对我的模型的性能产生有意义的改善。
- 我本打算居中裁剪,然后调整验证集图像的大小。虽然 DALI 的
[ops.Crop](https://docs.nvidia.com/deeplearning/sdk/dali-developer-guide/docs/supported_ops.html#nvidia.dali.ops.Crop)
操作允许我们相对于输入图像的宽度和高度设置裁剪窗口左上角的坐标,但是似乎没有任何方法可以使裁剪窗口的宽度和高度也相对于每个输入图像的宽度和高度缩放。
最后
DALI 提供了一个简洁的 Python API,可以很好地与 PyTorch、TensorFlow 一起使用,并且只需三次调整,也可以顺利地与我们将在 fastai 库的 2.0 版本中看到的训练循环一起工作。通过使用 GPU 优化的代码并行运行图像增强,DALI 不仅实现了它的速度承诺,还消除了编写几行 JIT 脚本的需要,这是我知道的如何在 GPU 上批量运行图像增强的唯一方法。不幸的是,并不是所有的 DALI 图像变换都支持随机化,具有讽刺意味的是,即使 DALI 团队也承认是一个必须具备的特性。虽然 DALI 声称是灵活的,但我试图在 DALI 的 warp affine 操作中建立随机性,结果表明这种灵活性只扩展到那些愿意并能够子类化和编译 C++类的人。在 2019 年,我不确定任何需要使用 C++的东西是否仍然可以被称为“灵活的”。
即便如此,虽然 DALI 的功能集较窄,可能会使我的模型的最终版本更难到达 SOTA 或登上 Kaggle 排行榜,但我仍然计划在我的模型原型制作过程的早期阶段使用 DALI 库。它的 Python API 很容易使用,DALI 运行速度非常快。毕竟我们说的是在 GPU 上批量扩充图像,这里!我希望图书馆能继续填补空白,不断进步。
参考
- 请随意查看笔记本,我在那里试验了本文中讨论的管道。
- DALI 的 GitHub 页面上的 PyTorch ImageNet 培训示例由 Janusz Lisiecki 、 Joaquin Anton 和 Cliff Woolley 创建,它是帮助我了解如何从头开始编写自己的培训和验证管道类的不可或缺的模板。
投身谷歌里程碑式的识别卡格尔竞赛
最近的谷歌地标识别比赛严重影响了我与互联网服务提供商、我的 GPU 的关系,也让我失去了一点耐心。
以下是 Kaggle 比赛说明:
今天,地标识别研究的一个巨大障碍是缺乏大型标注数据集。在这次比赛中,我们展示了迄今为止最大的全球数据集,以促进这一问题的进展。这项比赛要求 Kagglers 建立模型,在具有挑战性的测试图像数据集中识别正确的地标(如果有的话)。
不管怎样,我很兴奋我得到了我的第一枚卡格尔奖章!
庞大的数据集
当我第一次报名参加比赛时,我没有意识到这个数据集有多么庞大。对于 400 多万张图像,仅训练集就有大约 500 多千兆字节!
您的 ISP 有数据上限限制吗?我的的确如此。我意识到这一点,因为我收到了一系列电子邮件,说我离 1,024 的上限只有 10g 了。突然,楼下的电视“坏了”,不能播放我 3 岁孩子最喜欢的节目,我在家的工作变成了在咖啡店的工作。我不想支付超龄费。
在下载这个数据集时,我使用了我家人正常月数据使用量的三倍。太不可思议了。我还觉得庞大的数据集阻碍了其他人获得解决这个挑战的好机会。
使用 Magik 压缩图像和数据集
所以让我们来解决尺寸问题。数据集中的大多数图片都很大(因此有 500 多张),我们不需要它们来参加比赛。因此,我们使用了 ImageMagick 的一个小 mogrify 来快速地将图像调整到 256x256。
find /home/user/data/google2019/ -name ‘*.jpg’ -execdir mogrify -resize 256x256! {} \;
这一步有效地将 500+千兆字节的训练集减少到大约 50 千兆字节。更有用。更重要的是,我已经知道,当训练这么大的数据集时,时间将是一个问题。通过预先调整硬盘上的映像大小,我们无需在训练期间多次重新计算。
以防万一,我保留了原始文件,如果我以后需要不同的大小。
检查文件是否为空
另一个不幸的发现是几个缺少数据的文件被破坏。不完整的记录可能会导致模型在整个训练过程中失败(这可能需要长达 9 个小时)。最好先检查一下。
import os
filenames=os.listdir("/home/user/data/google/")# Are there any empty files?
for filex in tqdm(filenames):
try:
filex=directory+"/"+filex
if (os.stat(filex).st_size == 0): #0 means bad file
print(filex)
except:
print (filex)
任何失败的文件我都用相同类型的地标图片替换。
最常见的地标
有了这么多的文件,另一个大问题就是地标类的数量。最常见的“id 138982”有超过 10,000 个实例,而一些地标只有 1 个。
我们这里有 203,094 个不同的班级,我们真的应该考虑减少这个数字。
维度很难
如果你看看其他 CNN 数据集,你有一些简单的像 Mnist (10 个类)和更复杂的像 ImageNet(1000 个类)。所以一个有 20 万个类别的数据集是很疯狂的。
所以让我们把它变得简单一些,只让它像 ImageNet 一样难。我们选取了 1000 个最常见的类,仅使用了数据集的 11.1%。
不出所料,它训练得很快,ResNet34 的准确率高达 50%左右。通过大量的实验,我得到了类似这样的东西。(我喜欢老派的钢笔和墨水)
Resnet34 experiments for test 1
我当然有差距分数的问题,但我认为大多数人并没有被它超越。实现我的差距分数迭代与公共 Kaggle 分数不完全匹配。然而,这确实表明我走在正确的道路上。较大样本量的班级(前 100 名、前 1000 名、前 2000 名等。),更高的准确性和更高的验证差距分数导致更高的 Kaggle 分数。
然而,随着数据集的增长,我们也遇到了训练时间和预测准确性较低的问题。
失望转向第二阶段测试
我在比赛中表现非常好,第一阶段的公开分数让我进入了前 30 名。我对此非常兴奋,因为我觉得我的训练越来越好了。我的分类和准确性迅速提高。
然而,我的训练很快就遇到了 20,000 个类别的问题。我只有大约 30%的 7000 个不同的类别(我甚至无法预测所有的新类别)。
虽然我在那一点上尝试了几种不同的解决方案,但我被姐妹竞争(谷歌地标检索 2019 )分散了太多注意力,无法回去修复错误。
有待改进的领域
数据清理和地标与非地标
当查看所有训练数据时,我注意到 landmark 138982 是最常见的一个,并决定进行调查。当我们浏览这堂课的 200 张图片时,没有任何标志。例如,见下文。
我们有实验室内部,教室,风景,树前的人,没有任何看起来像地标的东西。有些是彩色的,有些是黑白的(这些图像看起来有一点绿色,只是为了这个功能),还有不少在底部有一个小标签。原来它们来自苏黎世联邦理工学院的图书馆,我仍然不知道为什么它们被认为是地标。
毕竟,国会图书馆里的照片会是国会图书馆吗?
这说不通。
我被傲慢所征服,忽略了包括一个非地标或者关于地标是否存在的数据清理。事后看来,这是一个巨大的错误,检测地标和非地标有许多解决方案。
竞争者讨论地标 vs 非地标:
第 2 名 —不讨论,只放外部数据
第 8 名
第 27 名
寻找最近的邻居
看到最接近测试图像的图像也是非常重要的。我使用 nmslib 来帮助解决这个问题。
根据我的 Resnet50 训练,nmslib 将比较 1 个测试的 softmax 预测与在我的验证集上做出的预测。那么它将找到 K 个最接近该测试图像的验证图像。请看我下面的解释。
如果是 ImageNet,那么狗的图像对狗的预测会更高就说得通了。如果验证集中的 K 个最近邻居是狗,则测试图像很可能是狗。
同样,如果它是一个图像类别 12345,它应该在其 12345 ish 中排名非常高。如果五个最接近的验证图像是 12345,它可能是。
现在,我在这里的错误是使用 softmax,并试图使用成千上万的变量,而不是使用最后的 512 个功能。试图拥有 20 万个类别会消耗大量内存。我崩溃的时候高达 62G。
通过一些疯狂的天文计算,我在最后的提交中放弃了它,去参加另一场比赛。
还有几个问题
此外,因为有一些分散注意力的图像。KNN 会对整个主题进行错误分类。例如,这张图片:
正如你可以看到上面的图片突出的特点,人们正在为摄影师摆好姿势,它匹配最接近下面的照片。
这些比较关注的是人,而不是地标。即便如此,它也无法分辨它们是现代照片还是老照片。我认为如果有更好的架构和更多的类别,我就不会有这个问题了。
KNN 的最佳用途在以下方案中:
第 1 名
第 8 名 第 15 名
第 20 名 第 27 名
更好的架构 为什么是 Resnet50?我在用 ResNeXt 架构保存时遇到了问题,开始 DenseNet 太晚了,而 EfficientNet 功能正在构建中。下一次,我将从 ResNet34 转到 ResNeXt 或新的 EfficientNet 。
我也尝试慢慢增加训练类别的数量。先做 1000,再做 2000,再做 3000,以此类推。这种想法是,学习更少的类别会更快,然后在更广的范围内学习会更准确。比如在学习另一种语言时添加额外的抽认卡。
然而,我在训练和准确性方面没有明显的帮助,我放弃了它。也许台阶太大了,我应该有小一点的。
结论
- 大型数据集很难。有趣但很难。
- KNN 是极其宝贵的,但不会打破你的电脑
- 爱多 GPU 处理
- 快速转移到更大的数据集
再次感谢 Fast.ai ,它教会了我如何走到这一步!
参考资料:
我的 Github 代码
GAP for py torch/fast . ai
Kaggle 竞赛
NMS lib efficient net