为对象检测构建自定义 tf.data 管道
从数据生产到模型优化的完整演练
图片来源于 Carol M. Highsmith 的“阿拉斯加管道”,国会图书馆,公共领域
建立有效的输入管道是训练深度神经网络的重要性能优化。除此之外,数据供应甚至需要良好的结构和透明性,以避免成为培训中的错误来源。虽然目前许多开发都在 PyTorch 上运行,但如果您计划使用边缘设备或想要在具有数 TB 数据的大型训练集群上运行,Tensorflow 仍然是一条不错的道路。这就是带有 tf.data.Dataset 的 tf.data API 的用武之地:拥有一个高效的管道来为您提供训练数据,这些数据是通用的,可以扩展到数据中心维度。虽然,将它与您自己的数据一起使用仍然会令人沮丧,因为您可能会碰到现有教程的一些边缘(我碰到过很多)。
这是我给你的建议,也是我从中最大的收获:不要试图使用任何捷径——使用成熟的管道,因为它应该被使用,事情将会非常容易使用和理解。这就是我为您编写本教程的原因——提供一个端到端的示例,其核心很简单,但利用了 tf.data API 的大部分概念(没有使用任何在名为 cats/dogs 的目录结构中用特殊文件名编写文件的快捷方式)。
我们使用以下内容:
- 3000 张图片
- 每个图像包含三种颜色之一的对象(一个点)
- 每个点被放置在图像上的随机位置
当然,我们希望根据给定的图像来预测点在哪里以及它有什么颜色。简单的任务,但是标签可以用目录结构来表示吗——我认为不可能。我是否希望将标签保存在 CSV 文件中,以便再次与基于 UUID 文件名的图像进行匹配——我尝试过,但并不有趣。
我希望我的数据以数据集记录的形式存储图像数据和标签信息,并希望这些数据流入我的模型中进行训练。
如果你也喜欢这个想法,请继续阅读。
在本文中,我主要关注相关代码,以使基于 tf.data 的管道工作。您将在连接的笔记本中获得完整的锅炉板代码:https://gist . github . com/FHermisch/1a 517121 ECB 11 d0e 0206226 AC 69915 ee
创建图像
“模拟”生成测试数据的大型复杂设置(如数据扩充、机械土耳其等)。),我选择用一些随机数据生成图像,并使用这些。因此,不会有从一些互联网来源加载的“随时可用”的设置。我们为测试和验证数据创建尽可能简单的图像。对我们来说重要的是,我们使用的数据将具有与自定义图像分类和对象检测任务相当的结构复杂性。如前所述,我们的任务是检测图像中矩形的位置以及矩形的颜色。
我们可以只用纯黑的背景。或者——为了有更多的视觉吸引力,使用一些美国宇航局的图像,因为这些图像通常非常令人印象深刻,而且大多是公共领域使用的。
Datatype/shape of base image, web base image:
uint8 / (112, 112, 3) , uint8 / (112, 112, 3)
我们继续 NASA 的图像。注意,两个图像都是 112,112 大小,RGB 颜色通道作为“最后通道”(Tensorflow 样式)。
现在,让我们在这个基础上放置一些随机的东西。我们构建了一个函数,将一个给定颜色的对象放在图像上,并返回该对象被放置的位置。这是我们的简单解决方案,生成一些“物体检测”图像。
在“placeobject”函数中,我们初始化要放置在图像上的对象:
- 构建一个 numpy 数组,数组的大小为对象应有的大小
- 乘以颜色值,将每个像素转换为所需的颜色
为了在图像上放置物体,我们选择一个随机的相对 y 和 x 位置。现在,我们可以计算绝对像素位置,并将对象数据复制到基础图像中。
让我们看一看:现在在我们的基本图像上有一个对象,并且打印的位置与图像上的对象位置相匹配。
Position 0.9109465116914442 0.13220923689802044
我们现在有了一种方法,可以将具有某种颜色的对象放置在我们的基本图像上,并准确地知道我们将该对象放置在什么位置。位置和颜色将是我们以后检测的标签/基础。包含该对象的图像将是我们的训练数据。让我们通过随机选择颜色来自动生成大量的图像和标签。
让我们先生成 5 张图片并打印出标签。
Generated data (112, 112, 3) 0.8395090863371965 0.9547828984929204 ObjColorTypes.SPECIAL
Generated data (112, 112, 3) 0.5531254931489215 0.4768844126516376 ObjColorTypes.GREEN
Generated data (112, 112, 3) 0.47239734539676304 0.23156864975331592 ObjColorTypes.RED
Generated data (112, 112, 3) 0.539600313491926 0.14757914149460205 ObjColorTypes.SPECIAL
Generated data (112, 112, 3) 0.6978451492963156 0.5689848230831969 ObjColorTypes.RED
我们还应该对数据有一个直观的看法。后来,我们想训练一个人工智能从这些图像中学习一些东西——所以对人工智能好一点,在你把它喂给人工智能之前自己看一下:你能看到你想让人工智能看到的东西吗?!
写入 TFRecord 数据
首先,我们设置一个类,它包含了写这些记录所需的所有东西。原谅我,因为我是在 OO 范式中长大的,拥有类和实例化的对象对我来说很自然。您也可以在这里使用带有部分参数或任何其他参数的函数。
import randomclass QTFRec():
def __init__(self, fname):
self.fname = fname
self.tfwriter = tf.io.TFRecordWriter(self.fname)
def _bytes_feature(self, nparr):
return tf.train.Feature(
bytes_list=tf.train.BytesList(value=[nparr.tobytes()]))def _float_feature(self, nparr):
return tf.train.Feature(
float_list=tf.train.FloatList(value=nparr))def write_record(self, image, poslabel, collabel):feature = {
'image_raw': self._float_feature(
image.ravel()),
'img_shape': self._bytes_feature(
np.array(image.shape, dtype=np.uint8).ravel()),
'poslabel': self._float_feature(
poslabel.ravel()),
'collabel': self._float_feature(
collabel.ravel())
}tf_example = tf.train.Example(
features=tf.train.Features(feature=feature))
self.tfwriter.write(
tf_example.SerializeToString())
def close_record(self):
self.tfwriter.flush()
self.tfwriter.close()
在我们的类中,我们构建了一个 TFRecord writer,它将用于以 TFRecord 格式将数据写入磁盘。TFRecord 是一种以顺序方式存储数据示例的方法。其中每个例子由一组特征组成。
我们将‘write _ record’函数中的特征定义为一个字典。在这种情况下,我们有图像数据、图像上对象的位置、对象的颜色,并且我们还想存储图像数据的形状。
TFRecord 允许为特征选择特定的数据类型。对于我们的特性,我们使用字节列表和浮点列表。
现在,我们将实际数据放入特性中:我们从 numpies 中的数据开始,这非常方便。我们把它们平铺开来。ravel()')并将它们放入各自的功能构造函数中。您可能想知道为什么我们将图像数据存储为浮点数?这是一个设计选择(哎呀!—稍后阅读此设计选择的效果),因此我们已经在 0 <=val<=1 range, so we can later feed this directly to the training. You will see that there are a couple of places suitable for data conversions — if you have saved it here as uINT8 you can later convert it in the feeding pipeline.
中存储了带有颜色值的图像数据。我们需要的最后一件事是关闭 writer,以确保所有内容都已写入磁盘。我们添加了一个 close_writer 方法来完成这项工作(小插件:您可以将其更改为与 python 的‘with’语句一起工作)。
就是这样。还有一件事我们稍后会谈到:我们目前没有将验证数据从训练数据中分离出来。有人可能认为会有一个简单的“split_dataset”函数,我们可以在以后使用,但没有数据集。这是可以理解的,因为 tf.data 是为处理数十亿条记录而构建的,不能简单地以某种方式拆分数十亿条记录。我们稍后将扩展我们的类,以实际写入两组记录。但是让我们先继续训练数据…
我们创建了一个 QTFRec 实例,并构建了另一个小类来封装它,并提供了一个正好适合我们的数据生成回调的函数。好了,这个管用。现在,我们可以为我们的训练集生成合理数量的记录。
qtfr = QTFRec(fname)
tfrsaver = TFRsaver( qtfr)
generatedata(baseimg, 3000, tfrsaver.savedata)
qtfr.close_record()
让我们使用这个数据集来设置一个输入管道来训练一个模型。接下来的步骤相当于使用 tfds 的教程,例如来自 tfds 的 MNist。我们将重点关注需要一些转换逻辑的部分,以使我们的数据适应模型训练需求(我们之前也可以用更适合的方式编写数据,但让我们这样做是为了向您展示可以为您的数据和管道放置自定义转换需求的位置)。
建造管道
再次打开写入的数据集很容易。
tfrds = tf.data.TFRecordDataset(TRAINSET_FNAME)
我们可以使用在线文档,但我喜欢使用内置的“帮助(tfrds)”来查看我得到了什么类型以及它提供了什么功能的便捷方式。
不出所料,TFRecordDatasetV2。
值得注意的功能:
- 应用’
,在其上映射一个转换函数,并构建一个转换管道:看起来不错——我们稍后会用到它 - 对于研究结构和内容来说,这听起来很方便
- 其他流水线功能,批处理/混洗等。
- 从管道末端取出一定数量的元素
让我们试着看看会发生什么:
for npelem in tfrds.as_numpy_iterator():
print( type(npelem))
barr = np.frombuffer(npelem, dtype=np.byte )
print( barr.shape)
break
让我们看看它会打印出什么。
<class 'bytes'>
(150629,)
我们得到了一个 150629 的数字形状,这应该是大约 112x112x3 = 37632?等等,发生什么事了?好了,我们将图像数据存储为浮点数(出于方便),因此我们将每个颜色值从一个字节(uint8)扩展到 4 个字节(float32)。我们真的应该改变这一点——所以要经常看看你的数据。想想,明明很清楚却被我错过了。我把它作为一个例子留给你。为了方便起见,有更好的方法来浪费你的磁盘和 IO,然后存储 4 倍的大小。
让我们继续。我们从数据集中得到一个张量,我们可以选择将不同的变换映射到这个集合。我们构建了一个 DataRead 类,作为上面的 writer 类的对等物。
class DataRead():
def __init__(self):
self.feature_description = {
'image_raw':
tf.io.VarLenFeature( dtype=tf.float32),
'img_shape':
tf.io.FixedLenFeature([], tf.string),
'poslabel':
tf.io.VarLenFeature( dtype=tf.float32),
'collabel':
tf.io.VarLenFeature( dtype=tf.float32)
}def prepdata( self, fmap):
pmap = tf.io.parse_single_example(
fmap, self.feature_description)imgraw = tf.sparse.to_dense(pmap['image_raw'])
imshape = tf.io.decode_raw(pmap['img_shape'], tf.uint8)
poslabel = tf.sparse.to_dense(pmap['poslabel'])
collabel = tf.one_hot( tf.cast(
tf.sparse.to_dense(pmap['collabel']), tf.uint8), tf.constant(3))[0]
return (tf.reshape( imgraw, tf.cast(imshape, tf.int32)),
tf.concat( [poslabel,collabel], axis=-1))
我们首先要解析张量,因为里面的所有东西都是字节。因此,为不同的元素设置一个 feature_description 字典。我们的“prepdata”函数稍后将被映射到管道。我们解析单个条目,以便能够访问指定记录中的每个特性。这是为数据添加额外转换代码的好时机。我们必须进行转型:
- 将原始图像数据重新整形为结构化的形状:
我们首先解码形状,并使用它将图像数据重新整形为其原始的 112x112x3 形状 - 将标签数据放在一个张量中:
我们将位置标签与 colortype 连接在一起,之前我们将其转换为一键表示
现在,我们得到了 1x112x112x3 的漂亮图像数据,用于将目标/地面真相的 a 标签训练为 1x5。
输入管道只是将一堆转换映射在一起。映射我们刚刚构建的解析函数。映射缓存函数。在每一次完整迭代后洗牌。从单个管道项目中形成批次。启动一些预取,以便总是在需要时为培训准备好批次。
datar = DataRead()traindat = tfrds.map(
datar.prepdata,
num_parallel_calls=tf.data.experimental.AUTOTUNE)
traindat = traindat.cache()
traindat = traindat.shuffle(
1000, seed=1234, reshuffle_each_iteration=True)
traindat = traindat.batch(
BATCHSIZE, drop_remainder=True)
traindat = traindat.prefetch(
tf.data.experimental.AUTOTUNE)
由于结果仍然是数据集,我们再次使用 as_numpy_iterator 函数。现在,数据以我们转换后的格式弹出,我们可以轻松地可视化图像数据和标签。
[0.602948 0.2850269 0\. 0\. 1\. ]
一个非常简单的物体检测
本文的重点不是如何进行对象检测。所以我们没有进入下一步的细节:
建立一个有一些回旋的模型,最后有一些完全连接的层。输出只是一个 sigmoid,它将被训练以匹配我们的标签(这是非常基本的,但适用于这个极其简化的示例)。
Model: "functional_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) [(None, 112, 112, 3)] 0
_________________________________________________________________
conv2d (Conv2D) (None, 112, 112, 16) 448
_________________________________________________________________
re_lu (ReLU) (None, 112, 112, 16) 0
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 38, 38, 16) 0
_________________________________________________________________
conv2d_1 (Conv2D) (None, 38, 38, 32) 4640
_________________________________________________________________
re_lu_1 (ReLU) (None, 38, 38, 32) 0
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 13, 13, 32) 0
_________________________________________________________________
flatten (Flatten) (None, 5408) 0
_________________________________________________________________
dropout (Dropout) (None, 5408) 0
_________________________________________________________________
dense (Dense) (None, 128) 692352
_________________________________________________________________
re_lu_2 (ReLU) (None, 128) 0
_________________________________________________________________
dense_1 (Dense) (None, 64) 8256
_________________________________________________________________
re_lu_3 (ReLU) (None, 64) 0
_________________________________________________________________
batch_normalization (BatchNo (None, 64) 256
_________________________________________________________________
dense_2 (Dense) (None, 5) 325
=================================================================
Total params: 706,277
Trainable params: 706,149
Non-trainable params: 128
_________________________________________________________________
用 SGD 作为优化器,MeanSquaredError 作为 loss 进行编译。
运行它…哦,等等…我们没有验证数据!
Epoch 1/10
46/46 [==============================] - 1s 15ms/step - loss: 0.1976
Epoch 2/10
46/46 [==============================] - 1s 15ms/step - loss: 0.1801
Epoch 3/10
46/46 [==============================] - 1s 14ms/step - loss: 0.1655
...
我们确实需要一个验证集来获得任何有意义的见解。让我们改变写数据的代码。我们希望保留一定比例的图像用于验证,并将它们保存到另一个数据集。
我们需要添加代码来初始化(并在稍后关闭)一个额外的编写器。在“write_record”中,我们添加了一个随机步骤,该步骤生成一个介于 0 和 1 之间的均匀随机数,并根据与提供的验证分割百分比的比较,将生成的数据发送给训练或验证。
在这里,我们随机分割数据,但这也是放置一些更“智能”逻辑的地方,例如,确保在人脸检测场景中,验证集将只包含根本不在训练集中的人。这种分割逻辑应该接近数据的生成,并且不能在以后或在训练期间完成。
我们再次运行并为训练和验证提供文件名以及 20%分割百分比。
qtfrec = QTFRecVal(FNAME, 0.2, FNAMEVAL)
tfrsaver = TFRsaver( qtfrec)
generatedata(baseimg, 3000, tfrsaver.savedata)
qtfrec.close_record()
运行以生成 3000 幅图像(20%将放入验证集)。
为验证建立第二个管道(我们不必打乱验证)。
tfvalrds = tf.data.TFRecordDataset(FNAMEVAL)
valdat = tfvalrds.map(
datar.prepdata, num_parallel_calls=tf.data.experimental.AUTOTUNE)
valdat = valdat.cache()
valdat = valdat.batch(BATCHSIZE, drop_remainder=True)
valdat = valdat.prefetch( tf.data.experimental.AUTOTUNE)
生成、编译和运行 100 个时期。
...
Epoch 98/100
38/38 [==============================] - 1s 19ms/step - loss: 0.0020 - val_loss: 8.0567e-04
Epoch 99/100
38/38 [==============================] - 1s 20ms/step - loss: 0.0022 - val_loss: 8.2248e-04
Epoch 100/100
38/38 [==============================] - 1s 18ms/step - loss: 0.0021 - val_loss: 7.9342e-04
看看损失是如何演变的。这里有一句关于准确性的话:我们不能使用现成的准确性函数,因为它们不能代表我们所做的。如果您想要一些准确性,您必须提供自己的函数:例如,检查是否预测了正确的颜色值,以及目标位置和预测位置之间的欧几里德距离是否低于某个阈值。
它能预测吗?
我会说是的——大部分是。颜色值预测得非常好,但我预计位置检测会执行得更好…
好消息:您现在可以使用 tf.data 管道为您的检测构建自己的自定义示例了。
Groundtruth label: [0.5074 0.7342 0\. 0\. 1\. ]
Prediction from model: [0.5104 0.7335 0.0157 0.0145 0.9913]
完整的笔记本可作为一个要点:
https://gist . github . com/FHermisch/1a 517121 ECB 11 d0e 0206226 AC 69915 ee
使用 Python 中的 Plotly-Dash 构建房价数据仪表板
查看美国城市房价、收入数据和人口的仪表板示例
以简洁、易于理解的方式向您的受众传达数据项目的结果可能非常困难——当我准备向受众展示数据时,我花费了比我愿意承认的更多的时间来考虑颜色、标记样式、图表格式等。我记得在我作为一名科学家的职业生涯早期,我使用 Excel 制作图表,并思考…为什么为我的数据集选择一种颜色如此困难?如今,数据仪表板似乎风靡一时,这是有原因的——它们可以帮助你以一种美观、易于理解的交付方式传达你的分析结果。虽然我仍然发现自己在制作仪表板时考虑了所有上述的设计因素,但所有这些决定都感觉容易多了。
我不能确切地告诉你为什么,但我发现房子和房价令人着迷。也许这是我多年来一直坚持的 HGTV 饮食习惯😆在考虑我下一篇数据科学文章的主题时,这似乎是一个很好的起点。
虽然我完全赞成尽可能简单地解释事情,但这毕竟是一个编码博客,所以我将在下面解释我创建这个仪表板的工作流程。如果你想跳过技术细节,直奔仪表盘,点击这里。
仪表板中科罗拉多州丹佛市历史和预测房价的时间序列图。图片作者。
对于这个项目,我将使用 Plotly-Dash,这是一个用于创建分析数据 web 应用程序的 Python 库。Plotly 是一个数据可视化库,类似于 matplotlib,但允许您制作交互式图形,并将它们集成到 Dash 中。
我首先前往 Zillow 网站下载房价数据——我使用 Zillow 的所有住宅(单户住宅和公寓)的住宅价值指数(ZHVI)数据集作为原始时间序列数据,这些数据着眼于“中档”价格范围内的住宅。Zillow 将“中间层”定义为价值在第 35 至 65 百分位范围内的房屋,中位数在此范围内。我从 Kaggle 下载了收入数据——虽然有些人认为 Kaggle 数据集对于数据科学项目来说过于“干净”,但我还是选择了它,因为我实际上是在组合一些不同的数据集,为仪表板创建我自己的数据集。最后,我调用 Geonames API 来下载每个城市的纬度、经度和人口数据。当我进入 Zillow 数据集时,我发现了许多需要重命名的城市,以便使用 Geonames API。在我看来,清理数据并不是这份工作中最吸引人的部分,但却是一个没有得到足够重视的重要部分。一些城市的收入数据也有很多条目,所以我按照每个城市的平均值将它们分组:
经过一点清理后,我将城市放入一个 Python 列表中,并使用 Geonames API 遍历它们,以迭代方式下载每个城市的纬度、经度和人口。注意,你需要在 Geonames.org网站上注册才能使用他们的 Python API,关键是你的用户名:
有了这些结果,我将人口数据添加到一个 Python 字典中,将城市名作为键,将人口作为值,这样我就可以很容易地确定哪些城市的人口值为零(也就是说,API 不起作用)。我对 API 提取人口数据的成功率进行了快速统计,发现它成功下载了大约 94%的城市的人口数据。我从维基百科手动添加了另外 6%的人口。记住:编码永远不会让你到达目的地。但是在这种情况下,它让我完成了 94%的路程,这节省了我很多时间。编码并不否定对结果进行手动质量控制的需要。
接下来,我对 income 数据集执行了一个左外连接,以将其与 Zillow 数据集合并。这是一个 SQL 概念,意味着 Zillow 数据集的大小将保持不变。如果 Zillow 数据集中存在某个城市的收入数据,则仅将其添加到该数据集中。如果某个城市没有收入数据,它会用 NaN(不是数字)值填充该列。我还最终确定了在 Dash 中作为数据表使用的数据集:
最后,我还在仪表板上完成了一个用于房价时间序列的数据框架(下面的“df_h”),并在每个城市循环,预测未来两年的房价。我使用了 statsmodels 库中的自回归综合移动平均(ARIMA)模型。对于时间序列数据,这是一个非常好的机器学习算法。如果你对我为什么不使用 Scikit-Learn 来完成这个任务感到好奇,可以在 Github 上查看这个例子。
好吧!我们终于准备好了构建仪表板的所有数据。我是 Jupyter 实验室大多数 Python 开发的忠实粉丝,因为我喜欢在编写代码时能够轻松运行代码。然而,您可能想要切换到某种文本编辑器来在 Dash 中部署您的应用程序——我选择了 Spyder。你可以在 Jupyter 实验室运行 Dash,但我最终更喜欢文本编辑器。
要开始创建应用程序,您需要在计算机上创建一个目录来存储与应用程序相关的所有文件,并且需要在该目录中初始化一个虚拟环境。虚拟环境是您创建的文件夹中 Python 的一个独立实例,您必须重新安装 Python 标准库中未包含的任何库。在您的终端(Mac)中输入以下代码:
$ mkdir housing_dash && cd housing_dash
$ python3 -m venv venv
$ source venv/bin/activate(venv) $ python -m pip install dash
(venv) $ python -m pip install plotly
(venv) $ python -m pip install pandas
(venv) $ python -m pip install gunicorn
将应用程序需要的数据帧添加到这个文件夹中,并在同一个文件夹中创建一个 python 文件(这里的约定很简单,我选择了“app.py”)。此外,您需要在应用程序文件夹中创建以下文件(粗体文本=文件名):
requirements.txt’ 您的虚拟环境使用以下版本的库(如果您运行不同的版本,请相应地调整版本):
dash==1.19.0
pandas==1.2.3
gunicorn==20.0.4
‘runtime.txt’ 在您的虚拟环境中运行 Python 版本:
python-3.9.0
’。gitignore’ :
venv
*.pyc
.DS_Store
.env
‘过程文件’:
web: gunicorn app:server
如果需要检查 Python 或 Python 库的版本,可以在终端中调用以下代码:
#check Python version
(venv) $ python3 --version
Python 3.9.0#check Pandas version
$ python3
>>>import pandas as pd
>>>pd.__version__
'1.2.3'#check Dash version
$ python3
>>>import dash
>>>dash.__version__
'1.19.0'
好了,这就是文件夹设置。下面是我的完整 Dash 应用程序代码。我导入了上面创建的 csv 文件,布置了应用程序,并创建了“应用程序回调”来增加图形的交互性。应用回调使用您定义的函数,并且任何时候输入参数之一改变(例如下拉菜单,用户查询一个表,等等),回调在你指定的输出上调用函数。注意,在函数和回调函数之间不能有任何空格,函数必须放在回调函数下面的下一行,否则将不起作用。
Dash 在其关键字参数中广泛使用字典和列表,因此在构建 Dash 应用程序时,熟悉这些 Python 概念肯定会有所帮助。
要部署应用程序,建议使用 Git 和 Heroku,尽管还有其他方法来部署您的应用程序,以便任何人都可以查看它。在这一部分,我不会讲太多细节,但是如果你已经做到了这一点,你就已经成功了。 Dash 有关于如何做到这一点的优秀文档,网上有大量资源可以帮助你部署你的应用。即使你对 Python 很有经验,部署你的应用程序也可能是一个令人抓狂的过程。如果您的应用程序成功部署到 Heroku,您可能会在第一次在浏览器中加载应用程序时收到一条错误消息,感觉就像这样:
功劳:生活大爆炸理论。
我可以提供一些建议:
- 仔细检查你的 requirements.txt 和 runtime.txt ,确保你用来开发代码的版本与你的。txt 文件。你的 Python 版本也必须完全像这样写:python-3.x.x ',
- 三重检查您的代码,并在您的终端中运行:
heroku --tail
- 深呼吸,耐心点。你能行的😃
当我在做这个项目时,我无法停止思考《瑞克和莫蒂》中外星人试图通过欺骗杰瑞与他们一起开发应用程序来接管地球的那一集😆
鸣谢:Giphy/RickandMorty.com
下面是我使用该应用程序的视频演示和一些截图。时间序列有一个下拉菜单,显示数据框架中每个城市(总共 913 个)的房价与年份的关系,其中大约一半城市有两年的房价预测。此外,当您筛选数据表时,地图和图形将会更新。您可以输入诸如’ > 300000 ‘或’<100000’, and the table will accept multiple queries:
Video by author.
Image by author.
Image by author.
And there you have it! That is how you build an app using Dash in Python. One of my favorite things about data science is how interdisciplinary it is — this could be financial, medical, sales, engineering, or some other kind of data, but the data portion of the project will approximately be the same, regardless of the discipline.
Thanks for reading!
Martin
引用之类的内容
[1] Zillow,2021,Zillow 房屋价值指数数据(ZHVI),所有房屋,时间序列,原始中间层:https://www.zillow.com/research/data/
[2]金橡树研究集团,2017 年,美国家庭收入统计:
https://www . ka ggle . com/goldenoakresearch/us-household-income-stats-geo-locations
2021 年构建数据平台
如何构建一个现代化、可扩展的数据平台来支持您的分析和数据科学项目。
目录:
— 站台
— 整合
— 数据仓库
— 转化
— 演示文稿
— 运输
— 关闭
你知道有句谚语说得好——“给猫剥皮的方法不止一种”
作为一个自豪的猫父母,这对我来说是一个艰难的比喻,但当涉及到 21 世纪的数据时,这种情绪从未如此准确。
诚然,你可以用电子表格、python 脚本或终端命令解决大多数数据问题,但当你开始考虑规模、速度和一致性时,问题很快就会出现。此外,数据领域的一系列工具和流程抑制了协作并推动了工具集的专业化,而不是促进对统计、数据建模和有效可视化等核心数据科学概念的深入理解。
幸运的是,一个一致的框架已经开始出现。构建数据平台的新方法一部分是自己动手,一部分是替我做。它包括将托管服务缝合在一起,并在您的平台中设计足够的灵活性来预测未知。如果操作正确,这种现代基础设施可以让数据专业人员专注于解决复杂的数学和科学问题,而不是简化围绕管理和文档的陈旧流程。
站台
这种构建现代数据平台的方法中的一个关键概念是模块化。尽管有聪明的营销和销售活动,目前还没有一个供应商或技术拥有整个数据领域。因此,了解每个组件是为您的特定项目拼凑正确解决方案的关键…组件如下:
- 来源
- 综合
- 数据仓库
- 转换
- 介绍会;展示会
- 运输
综合
我们假设源组件是显而易见的。数据源有多种形状和大小,集成层应该足够灵活,能够考虑到所有的数据源。
在这个组件的 DYI 频谱上是流行的工具,例如 Airflow ,许多公司用它来构建健壮的端到端数据管道。其他 Apache 产品,如 Kafka 提供了一种更基于事件的数据集成方法,可以与 Airflow 结合使用,以进一步扩展定制数据管道。
托管服务在集成领域已经走过了漫长的道路。除了前面提到的 Apache 项目的企业级版本,如天文学家(气流)和汇流(卡夫卡),这个领域还有几个领导者提供灵活性,但足够固执己见,以有意义的方式帮助加速开发。从基于事件的角度来看, 段 是不可否认的领导者,而five tran等解决方案已经成为更传统的基于 ETL/ELT 的数据集成的实际解决方案。
数据仓库
可能现代数据平台中最模糊也是最关键的组件是数据仓库。这部分是因为 SQL Server、Postgres 和 MySQL 等传统数据库技术仍然非常有效。然而,像雪花这样的新来者的统治地位已经为未来突出了清晰的道路。基于云的数据仓库,如 Snowflake、RedShift 和 BigQuery,在存储、访问和管理数据的方式上比它们的前辈提供了许多好处。
无论您根据自己的情况选择哪种基于云的数据仓库,将该仓库划分为不同功能层的概念仍然是一个不断发展的概念。最佳实践开始出现,建议您的数据仓库至少有两个不同的“区域”;一个存储原始/非结构化数据,另一个存储规范化/转换后的数据。这个话题有很大的争论空间,但是拥有这两个不同区域的总体好处是能够有效地管理不断变化的规则,将原始数据转换为可消化的信息。
转换
如果数据仓库组件是现代数据堆栈中最关键的部分,那么转换组件是最容易被忽略的部分。大多数项目倾向于将转换分散到业务工具、可视化平台和电子表格等人工制品中,但是集中管理数据转换是成熟数据组织的一个明显特征。
随着 ETL 和 ELT 之间的斗争,有效管理转换的想法开始在主流中显现。虽然这看起来有些迂腐,但对一个常见缩写中的字母进行简单重组,开创了一个全新的时代,允许非数据人员参与构建数据产品。这种范式转变也赋予了数据治理和 MDM 等概念新的生命,这些概念严重依赖于业务涉众的输入。
从 DIY 的角度来看,Python 是至高无上的,因为它可以通过像 SQLAlchemy 和 Airflow 这样的模块轻松管理简单的基于 SQL/任务的转换,并且是为由 Tensorflow 、 Scikit-learn 和更多其他模块驱动的更复杂的机器学习转换量身定制的。
从托管服务的角度来看,很难找到比 dbt 更好的产品。虽然所有主要的云提供商(AWS、微软、谷歌)都有自己的一套工具来管理他们平台上的转换,但从平台不可知的角度来看,dbt 似乎走在了前面。
介绍会;展示会
到目前为止,我们讨论的大多数组件都是纯基础设施。虽然大多数数据分析师、工程师和科学家将使用来自数据仓库和转换组件的内容,但大多数最终用户在访问表示层的仪表板之前不会看到任何内容。
坦率地说,表示组件是一个庞大的类别。谁说一个 Jupyter 笔记本,同样包含了变身元素,就不能同时作为演示工具?毕竟, Databricks 已经非常成功地运用了这一策略,因为它们似乎即将成为 20 年代的下一个大型科技公司 IPO 之一。
从历史的角度来看,可视化工具已经主导了转换和表示类别,像 Looker 、 Power BI 、 Qlik 、 Sisense 和 Tableau 这样的工具证明了管理转换和构建漂亮的可视化并不是互斥的概念。
随着数据堆栈的不断发展,我相信表现空间的冠军将是那些加倍重视可视化能力而较少依赖变革能力的人。随着组织集成更多数据源和数据量呈指数级增长,在表示层管理转换不仅会带来挑战,还会产生定义不清的信息和不准确的分析。
运输
考虑到交通因素,这种方法变得非常现代。在过去,终端用户通过仪表盘和外部分析工具消费信息是可以接受的,但越来越明显的是除非数据专业人员能够将他们的洞察力带回记录系统,否则他们的工作可能会毫无意义。
有时被称为“嵌入式分析”,数据传输的概念很简单,因为它弥合了数据工具和记录系统(即客户关系管理、营销自动化和客户成功平台)之间的差距。然而,很少有托管服务能够有效地解决这一问题,即使是已经出现的托管服务也仍在积极开发中。像 Hightouch 、 Census 和 Syncari 这样的公司似乎是第一批穿墙而过的公司,并且可能是大多数项目的唯一选择,除非他们拥有大量的开发人员资源和自动化信息交换的经验。
关闭
就在我写这篇文章的时候,数据格局正在发生变化。围绕数据平台可观察性和安全性的概念正在迅速流行,公司正在一夜之间实现解决这些问题。记住这一点,理解灵活性和不可知论是这条信息的主要内容。虽然这将会发生,但我敢打赌,在一个供应商将整个数据堆栈提取到一个统一的平台之前,还需要几年时间。因此,把这个框架带到未来,理解你将不得不改变你的思维,每天接受新的想法。
使用 Python 和 Dash 构建数据故事
从数据转向可视化,用更少的资源说更多的话,同时增加一些基本设计的深度。
弗朗西斯科·温加罗摄于佩克斯
“有些书可以浅尝辄止,有些书可以囫囵吞下,有些书需要咀嚼消化;也就是说,有些书只能读一部分;其他人阅读,但不是好奇地;有几本书必须完整地读完,而且要用心去读。”~弗朗西斯·培根爵士
我们绝大多数的数据探索都是相对简单的。我们需要比较两个值,或者我们需要查看我们的数据在上下文中是如何出现的。因此,我们很快在我们选择的笔记本上做了一个条形图或折线图,会意地哼了一声,然后继续我们清单上的下一个项目。
有时候我们需要潜得更深。我们需要从传递的信息中再挤出一点点。我们需要把我们的视觉呈现给其他人,而不是浪费他们的时间。在其他情况下,我们只需要充分利用有限的空间。
无论如何,它要求我们更长久地思考我们正在做的事情。它可能会伴随我们一段时间。
在这篇文章中,我将带你踏上一个小小的旅程,把一个稍微复杂一点的视觉化图像放在一起,讲述一个故事。别担心。虽然我已经使用了关键字“复杂”,这将是有用的。也许不能像直接复制和粘贴那样全合一,但至少可以作为一种机制来构建自己的工具包,也许还能产生一些创造性的想法。
只是为了管理预期,这不是一个完整的应用程序走查。这将是一项艰巨的任务。这是通过我自己的一个特定应用程序,引导您完成整个过程,以获得功能性的最终结果。我们的目标是强调一些我认为在寻找答案的最常见的地方缺失的关键概念。我只希望它能帮助一些人,哪怕只是一点点。
为什么是 Dash?
当我第一次开始用 Python 做数据工作时,我非常喜欢 Matplotlib。这很有意义,也符合我的编程方式。很容易很快就做出一些漂亮的图,可以输出用于其他目的,如出版或发行。有一天,我需要一些东西——具体是什么,我记不清了,但它让我冲了出来。
以前,我在网上旅行时遇到过 Plotly 和 Dash,但并没有太在意。我想把事情做完,而不是花时间去学习另一个库的细微差别。但是这一次不同。介绍快速简单,让我的兴趣达到了顶峰。所以我用 Plotly 蘸了蘸我的脚趾。
我被卖了。在大约三行代码中,我有了一个令人惊叹的(至少对我来说)情节,完成了我所需要的。过了一段时间,我冒险进入 Dash,因为我看到了快速创建自己的仪表盘的好处。
我最初的几次尝试完全失败了。这是一个我力不从心的简单例子。我试图跳过简单和基本,去实现我的复杂和理想。但是,我很快就得到它,并能够为我需要的东西建立一个可用的平台。
Dash 为我提供了一个相对轻量级、响应迅速的平台,不需要大型基础设施或技术管道就能快速获得结果。事实上,它与 Plotly 携手合作是一个意外收获。
我对这个平台的一个抱怨是文档经常缺乏。它有许多跳跃,从非常简单到更高级的中级,中间没有桥梁。追查丢失的东西可能会很有挑战性。就像所有的学习领域一样,答案是从知识守卫者手中夺来的,这是一个值得赢得和奖励的胜利,但也是一个主要的痛苦。
这是我决定把这篇文章放在一起的一个重要原因。
关于这个例子
对一些人来说,这可能是一个深奥的例子,但我讨厌使用陈词滥调,如泰坦尼克号生存或植物学或其他一些尝试和真实的比喻。为此,我使用我自己收集的数据存储中的数据。
作为一个金融迷,我对期货市场有一种特殊的亲切感,尤其是贵金属。然而,作为一个商业香肠,我喜欢看到原材料价格和消费者零售价格之间的相互作用。因此,我的数据包括来自多个分销商的市场数据和商品零售价格,涵盖 90 天的时间。市场数据以 5 分钟为间隔收集,而零售数据以 15 分钟为间隔收集。
市场数据来自一个构造为日期时间、开盘、盘高、盘低、收盘、成交量的表(即相当标准的市场数据格式)。零售数据由日期时间、商品、经销商和价格组成。
在很大程度上,我尽了最大努力来确保我的数据是干净的,所以没有太多的摄取处理。我对数据进行的唯一修改是编写视图,将零售价格时间戳时移至最近的 5 分钟间隔。这允许市场和零售数据表之间的清晰连接。出于分析的目的,这是一个无关紧要的变化。
获取数据
所有的艺术都需要一些原材料。在这种情况下,我们的原材料就是数据。这是通过一个简单的查询从数据库中获得的:
SELECT
date(silver.datetime) as datetime,
DATE_PART('week', CURRENT_DATE) - DATE_PART('week', silver.datetime) + 1 AS week_number,
item,
price,
close AS spot
FROM retail_silver_norm_time
JOIN silver ON (retail_silver_norm_time.datetime = round_time(silver.datetime))
WHERE
silver.datetime BETWEEN CURRENT_DATE-90 AND CURRENT_DATE+1
ORDER BY datetime
我们来分析一下。
From retail_silver_norm_time 是规范化的白银零售价格视图。银表只包含市场数据。这两个表使用它们的日期戳连接在一起。因为它们来自不同的来源,日期戳具有不同的精度,但是使用 round 函数解决了这个问题。
在新的笛卡尔积中,我只想要时间戳覆盖 90 天窗口的数据。我使用 PostgreSQL 数据库,所以我使用内置标识符“CURRENT_DATE”。这是一个便利的特性,大多数数据库都有类似的特性。
请注意,我的窗口延伸到今天以后。我这样做是以防万一,以防我得到一个奇怪的日期戳。虽然这种情况并不常见,但在没有服务质量保证的情况下处理数据时,这种情况时有发生。
现在,定义了查询的整个基础,我具体想从大表中得到什么呢?这里我选择了 silver 表的 datetime,它只提取了日期部分。为了保持一致性,我保留了相同的名称。然后,我计算“周数”的值。这将用于最终产品。之后,我提取其他三个主要数据——商品、零售价格和现货价格。
这给了我一切需要建立我的情节。
预处理
当我的数据从数据库中出来时,我试图让它尽可能地直接有用。然而,我有一个令人讨厌的习惯,就是在多个地方使用同一个查询的结果。这没有什么不同,所以我需要做一点数据争论来获得我需要的一切。
对于这一部分,我将查询结果放入 Pandas 数据帧。
注意:现在你可能已经注意到了,我没有深入研究那些在别处很容易找到的项目。如果您不知道如何将查询中的数据放入数据框,在快速搜索的第一页上有一些精彩的教程(比这好得多)。
无论如何,这是我用来运行我的进程的代码:
df = dbf.query_return(qu.silver_regression_query1)
df.columns = ['datetime', 'week_number', 'item', 'price', 'spot']
df = df_cleanup(df)
df = silver_normalizing(df)df['ps_gap'] = df.price - df.spot
df.ps_gap = df.ps_gap.round(2)
df['date'] = pd.to_datetime(df['datetime']).sub(pd.Timestamp('2021-03-01')).dt.daysdf.date = df.date / 10
df.date = df.round()silver_reg_item = df['item'].unique()
让我浏览一下上面的代码。
第一行和第二行只是在我的函数返回查询结果时设置数据帧。第三行通过一个通用的清理函数发送数据帧,以填补空白并执行一些我希望在这个应用程序中使用的例行程序。第四行是一个转换函数,它将商品价格标准化为一个一致的比例,这样我就可以进行比较。只是应用程序中数据的另一个细微差别。
第 5、6 和 7 行是数据列和格式的补充。第六行为现货价格和零售价格之间的差异创建了一个计算列。第六行将该值四舍五入为两位数。第七行创建一个计算列,表示自 3 月 1 日以来的天数。第 8 行和第 9 行是对第 7 行计算的修改。他们将计算出的日期放入 10 的比例中,然后四舍五入到最接近的整数。
进行这些修改的原因并不清楚,也可能令人困惑,但在我们构建图表时,它们确实有其目的。还要注意的是,其中许多可以通过各种方式进行整合,以便在运行时执行。出于本例的目的,我选择将它们分开,以使整个范围更具迭代性,更容易理解。根据您的选择进行整合。
最后一行创建一个新的 dataframe,它只包含条目列表的唯一值。这提供了一种快速简单的方式来按项目进行过滤,在这里这样做消除了另一个查询的需要。
破折号构造
对于某些人来说,这可能有点复杂。但是,我会详细介绍一下。
现在,我们有一个数据集存储在一个经过预处理的数据帧中,其中包括我们的数据群体。这是我们要处理的数据的总和。我已经让你知道了我的数据是如何构建这个的,但是你也可以很容易地使用你自己的数据。重要的部分是获得一组现成的数据。
我们试图得到的是一个过滤后的数据视图,它以图表的形式显示出来。为此,我们需要一些机制。
首先,我们需要一个地方来保存图表。在我的例子中,我使用引导模板(因为我的前端设计技能没有什么值得大书特书的)并填充一个模型。我提出来只是为了说明这是可能的,但实现这一点的核心只是三个基本概念:
- dcc。Graph —这提供了一个占位符和一个目标 id。
- dcc。drop down——这个控件只是给我们一个选择项目的地方。
- dcc。RangeSlider —该控件允许我们按日期进行筛选。
dcc。下拉式
除了许多教程所涉及的内容之外,这里没有什么特别的。最大的方面是 1)id 和 2)填充值的循环。
dcc.Dropdown(
id="silver_regresssion",
options=[{
'label': i,
'value': i
} for i in silver_reg_item],
value='One Ounce Generic Silver')
id 被指定为“silver_regression”。请注意这一点,因为它稍后会回来。
该列表由 silver_reg_item 填充,这是我们在预处理的第 10 行中创建的数据帧。
dcc。范围滑块
rangeslider 不仅可以按项目过滤(从下拉列表中),还可以按日期限制视图。或者在这种情况下,与其说是日期,不如说是从当前周开始的周数。
dcc.RangeSlider(
id='silver_week_slider',
min=df['week_number'].min(),
max=df['week_number'].max(),
value=[
df['week_number'].min(),
df['week_number'].max()
],
marks={int(n): n-1 for n in df['week_number'].unique()},
step=None
)
像以前一样,这些都是从例子中一字不差地摘录下来的。
这是为什么在预处理中使用硬编码值的一个很好的例子。正如您在最小值、最大值和值字段中看到的,所有这些都是基于 dataframe 列值的函数。这些本来是可以动态执行的,但是这会使代码变得更加不明显。
当你创造各种各样的展示时,为了你自己和你之后的人,试着先思考整个过程。早走一步往往可以避免以后的一些麻烦。
dcc。图表
dcc.Graph(id='silverReg')
说真的,正如我之前所说的,Graph 对象只是给了我们一个目标 id。
回电
从长远来看,试镜对我来说是最难理解的事情。它们看起来不直观,解释也不充分。只是在经历了大量的尝试和错误之后,终于有所发现。我也知道不能只有我一个人。所以让我试着用一种我希望是在我学习的时候的方式把这说得更清楚。
回调装饰器使用一些语句来创建接口组件和执行的逻辑之间的管道。使用这种安排,我们可以制作一些高度交互的复杂应用程序。
装饰器中的每个语句都由其功能来表示:输入、输出或状态。输入是反馈给回调的数据。输出是离开回调的数据。状态是当前状态。
对于每个语句,我们有两个维度——相关组件的 id 和数据包。这就是为什么所有的组件都需要被清晰地定义和调用。
这是我的回调装饰器:
@app.callback(
dash.dependencies.Output('silverReg', 'figure'),
[
dash.dependencies.Input('silver_regresssion', 'value'),
dash.dependencies.Input('silver_week_slider', 'value')
]
)
我们可以看到我们有两个输入语句。这些分别与 dropdown id 和 rangeslider id 相关。列表中的第二项是值。
类似地,我们的 output 语句记录了目标 id,返回的数据将是一个数字。这和我们的 dcc 吻合。图形对象。
超越室内设计师
一旦我们确定了输入和输出,并在应用程序主体和逻辑语句之间创建了链接,我们就可以构建后端了。可以说,这是金属与道路相遇的地方,并以一种功能的形式出现。
我将把这些拼凑起来,带您浏览逻辑决策,希望您能理解正在发生的事情及其原因:
def update_graph(silver_reg_item, week_num):
if silver_reg_item is None:
silver_reg_item = "One Ounce Generic Silver"
if week_num is None:
first_week = 52
last_week = 0
else:
first_week = week_num[0]
last_week = week_num[1]
我们的第一行是基本的函数定义。它需要两个参数,这很方便,因为我们的装饰者获得了两个输入(请注意幽默)。
其余的顶部行用于设置默认状态。在 Dash 应用程序中,所有回调都在启动时触发,因此您必须考虑这一点。
第一个条件是定义一个默认项目。第二个条件创建一个默认范围。由于组件中有默认值,这应该不是问题,但如果是,它会创建一个不会破坏任何东西的良好的软状态。
处理
数据处理似乎永远不会结束,对吗?在这里也是如此。由于我们将数据框架过滤成一个特定于项目和日期的框架,因此我们需要实现所有这些,然后执行一些直接计算:
df1 = df[df['item'] == silver_reg_item].copy()
df1 = df1[(df1['week_number'] >= first_week) & (df1['week_number'] <= last_week)]y_max = df1.price.max()
x_min = df1.spot.min()X = df1['price'].values.reshape(-1,1)
Y = df1['spot'].values.reshape(-1,1)linear_regressor = LinearRegression()
linear_regressor.fit(X,Y)df1['Y_pred'] = linear_regressor.predict(X)
这一部分做了几件事。首先,上面两行执行过滤。第一行按项目过滤主数据帧并创建一个副本。我们创建一个副本,这样我们就不会丢失原始主数据帧,并可以继续从中提取数据。第二行根据范围值过滤新的数据帧。这就给我们留下了简短的数据框架。
接下来,我想对数据进行线性回归。这是通过 SciKit 学习库完成的,需要将 dataframe 列转换为 numpy 数组。
第三和第四行从数据帧中分离出最小值和最大值的两个值。这些稍后会用到。
第五行和第六行将现货和价格的 dataframe 列重组为必要的数组。第七行和第八行调用函数,第九行生成一个包含基于它的值的列。这也可以在 Plotly 中直接完成,但我想改为像这样执行函数。
图表
我们在这里。我们一直在构建的代码:
fig = px.scatter(
df1,
y='price',
x='spot',
size=(df1.date * df1.date) * 2,
opacity=.5,
color='ps_gap',
trendline="lowess",
trendline_color_override="purple",
hover_data=["datetime", "price", "spot"],
color_continuous_scale=px.colors.diverging.RdYlGn_r,
range_color=[df1.ps_gap.min(),df1.ps_gap.max()]
)
这是主要情节。它是一个 plotly express 对象,使用我们在前面的过程中创建的 df1 数据帧。作为散点图,它使用现货对零售价格。
如果我把这个图留在那里,它将是一个非常无聊的散点,会提供信息,但不会尽可能多。这是我添加了一些维度的地方。
使用 size 参数,我建立了一种方法,其中使用经过预处理的数据,大小根据距离而变化。随着日期越来越远(或越来越接近 3 月 1 日),尺寸会越来越小。越近的数据点越大。我一眼就能看出仅基于规模的相关性。
第二,我想要一个清晰简洁的方法来识别差异。为此,我使用了颜色和颜色渐变。每个标记的颜色基于“ps_gap”值,但是这些颜色是使用“px.colors.diverging.RdYlGn_r”标度专门选择的。这是一个相对较高的对比度,具有良好的区分度。末尾的“_r”反转渐变。最后,该范围是动态的,基于 ps_gap 列中的最小值和最大值。
这一部分的几个细节是,我将不透明度定义为 0.5,以便在聚类组中提供一些强度变化,并且我添加了一条默认的 LOWESS 趋势线。趋势线是一条平滑的趋势线,补充了上面计算的线性回归。
fig.add_traces(go.Scatter(
y = df1['price'],
x = df1.Y_pred,
name='Regression',
showlegend=False,
line_width=2,
line_color='black',
))
下一个加法是线性回归。为此,我们简单地在图中添加一个轨迹。您可能会注意到这是一个标准 plotly Graph 对象,而不是 plotly express 对象。可以,可以混搭。
此追踪将零售价格作为 Y 值,并将 X 值设置为等于计算的预测值。(是的,我知道那里发生了什么)
当我将图表打印到文件中时,我意识到一个挑战,那就是它往往会丢失上下文。我没有下拉菜单或任何其他标识符来告诉我我在看什么。所以,解决这个挑战的简单方法是用一个注释:
fig.add_annotation(
y=y_max,
x=x_min,
text=silver_reg_item + "<br>" +
str(df1.datetime.min()) + " through " +
str(df1.datetime.max()),
showarrow=False,
font_size=10,
bordercolor='rgba(255,255,255,0.8)',
bgcolor='rgba(235,171,52,0.5)',
borderpad=5
)
该注释根据我们在处理部分定义的最小-最大值来设置位置。这有助于在不修改轴的情况下,没有很多复杂的麻烦地解决问题。
最后一步只是一些格式化:
fig.update_traces(marker=dict(
line=dict(
width=1,
color='black')),
selector=dict(mode='markers'))fig.update_layout(
title="Silver Spot vs. Retail Relationship",
plot_bgcolor="#FFFFFF",
xaxis=dict(
title="Spot",
linecolor="#BCCCDC"
),
yaxis=dict(
title="Retail",
linecolor="#BCCCDC"
),
coloraxis_colorbar=dict(
title="Retail-Spot Variance",
ticks="outside",
tickprefix="$")
)
第一次更新概述了痕迹。这让它们更容易区分。布局的第二次更新设置了标题、轴细节,并格式化了颜色条。
我们做的最后一件事是返回完整的数字:
return fig
这将获得完整的 Plotly 图形,并将其推到 dcc 中的占位符。装饰器中定义的图形。然后 Dash 完成剩下的工作。
最后的结果
辉煌的仪表板
上图是图表在应用程序中的样子。在顶部,我们可以看到下拉菜单,在底部,我们可以看到范围滑块。但是,当我们保存图像时,我们会得到以下结果:
保存的图表
如前所述,保存的图表具有更少的上下文。这使得注释更加重要。
最后的想法
我们走了很长一段路才走到今天,但我想确保关注的不仅仅是呈现的数据的维度,还有幕后的东西。最终的图表既简单又复杂。很好的搭配。
粗略地看一下,我们可以看到数据之间的关系,正如我们对这种散点图的预期。然而,如果我们真的检查它,我们不仅会被轴值吸引,还会被大小和颜色吸引。它用更多同样重要的元素来完善这个故事。
作为一个思维实验,如果我们不考虑尺寸和颜色,我们需要什么来呈现这些数据呢?还有多少其他图表?什么类型的图表?会有效果还是房地产成本太贵?
想象就像写作。我们错开句子结构。有些想法是迅速而明显的。其他的比较密集,需要慎重考虑。但是我们调整每件事的节奏,让我们的观众理解我们需要他们做什么。
有时候,一个非常简单直接的视觉就足够了。其他时候,我们需要挖掘复杂性。最好的方法,尤其是在构建一个内聚的仪表板或报告时,是构建我们需要说的内容。这需要以适当的方式优雅地组合我们的数据,让我们的结论被理解——或者让我们的观众得出他们自己的结论。
最后,进行实验。有些想法行得通,有些则会落空。但是,如果您理解了我在这里抛出的内容,那么您就已经有了构建自己的测试环境来探索数据的概念和工具的开端。
使用 PostgreSQL 在 Python 中构建数据仓库
一种简化的方法来提供健壮且可扩展的数据仓库
娜娜·斯米尔诺娃在 Unsplash 上的照片
介绍
随着当今时代数据的丰富和激增,有一种以有意义的方式存储和重用这些信息财富的内在需求。这就好比厨房里堆满了各种各样的用具和工具,却没有一种有组织的方式来管理它们。嗯,除非你囤积得很快,否则你很可能会用勺子的后端来打开你的午餐罐头。
数据仓库能够以前所未有的方式按需缓存、标记、分析和重用您管理的数据。就像你母亲在她整洁有序的厨房里导航一样。请注意,没有放之四海而皆准的解决方案,有多少个仓库就有多少种建仓方式。
可以说,实现成功的数据仓库有三个关键因素:
- **服务器:**首先,您必须提供一个既健壮又有弹性的分布式数据库系统。
- 索引:理想情况下,你的数据库系统应该有某种形式的索引,允许你以极快的速度访问记录。拥有一个全文索引将是一个额外的收获。
- Dashboard: 你应该有一个暂存区,在这里你可以以一种不变的方式导入、导出、可视化和改变你的数据。
在本教程中,我们将通过创建一个数据仓库来解决上面提到的第一点和最后一点,我们可以在其中存储数据集、数组和记录。此外,我们将创建一个仪表板,在那里我们可以用图形界面与我们的仓库加载,检索,变异和可视化我们的数据。或许在另一篇文章中,我们将实现第二点,即全文索引数据库。
服务器:PostgreSQL
PostgreSQL,简称 Postgres,是一个开放源代码的关系数据库系统,由于其扩展的功能和相对易用性,它通常是开发人员的首选数据库。尽管它被标榜为结构化数据库管理系统,但它也可以存储非结构化数据,包括但不限于数组和二进制对象。然而,最重要的是,Postgres 的图形用户界面使得动态提供和管理数据库变得太容易了,这是其他数据库系统应该注意的。
在我们的数据仓库实现中,我们将使用本地 Postgres 服务器来存储我们所有的数据。在我们继续之前,请使用此链接下载并安装 Postgres。在安装过程中,系统会提示您设置用户名、密码和本地 TCP 端口以连接到您的服务器。默认端口是 5432,您可以保持不变,也可以根据需要进行修改。安装完成后,您可以通过运行 pgAdmin 4 应用程序登录服务器,该应用程序将在您的浏览器上打开一个门户,如下所示。
PostgreSQL 服务器门户。图片作者。
将会有一个默认的数据库,标签为 postgres ,但是您可以通过右击 Databases 菜单,然后选择 Create 来创建您自己的数据库,以提供一个新的数据库。
Python 实现
现在您已经提供了您的服务器和数据库,您应该安装软件包 sqlalchemy ,它将用于通过 Python 连接到我们的数据库。您可以通过在 Anaconda 提示符下键入以下命令来下载并安装这个软件包:
pip install sqlalchemy
此外,下载、安装并导入所有其他必要的库到 Python 脚本中,如下所示:
首先,我们需要在我们的 records_db 数据库之间建立一个连接,并创建一个可以存储记录和数组的表。此外,我们需要创建另一个到 datasets_db 数据库的连接,我们将在其中存储数据集:
按照 Postgres 的命名约定,表名必须以下划线或字母(不是数字)开头,不能包含破折号,并且长度必须少于 64 个字符。对于我们的记录表,我们将创建一个数据类型为文本的名称字段,声明为主键,以及一个数据类型为细节的文本[] 字段,这是一个一维数组的 Postgres 表示法。要获得 Postgres 支持的所有数据类型的详尽列表,请参考此链接。我们将使用名称字段作为主键,稍后将用于搜索记录。此外,请注意,存储您的数据库凭证的一种更安全的方式是将它们保存在一个配置文件中,然后在您的代码中将它们作为参数调用。
随后,我们将创建以下五个函数来写入、更新、读取和列出数据库中的数据:
将字符串连接到查询时,请注意 SQL 注入漏洞。您可以使用参数化来防止 SQL 注入,如本文中的所述。
仪表板:简化
Streamlit 是一个纯 Python web 框架,允许您在指尖实时开发和部署用户界面和应用程序。在本教程中,我们将使用 Streamlit 来呈现一个仪表板,我们可以用它来与 Postgres 数据库进行交互。
在下面的代码片段中,我们使用了几个文本输入小部件来插入数据集的记录、数组和名称的值。此外,我们正在使用 Streamlit 的功能,以图表和数据框的形式交互式地可视化我们的数据集。
您可以在本地浏览器上运行仪表板,方法是在 Anaconda 提示符下键入以下命令。首先,将根目录更改为保存源代码的位置:
cd C:/Users/...
然后键入以下内容运行您的应用程序:
streamlit run file_name.py
结果
这就是你要的,一个可以用来实时标记、写入、读取、更新、上传和可视化我们的数据的控制面板。我们的数据仓库的美妙之处在于,它可以扩展到托管您可能需要的尽可能多的数据,所有这些都在我们刚刚创建的同一结构中!如果你想了解更多关于数据仓库的基础知识,我建议你报名参加科罗拉多大学关于数据仓库的课程。这是提高速度的好方法。
如果您想了解更多关于数据可视化和 Python 的知识,请随时查看以下(附属链接)课程:
使用 Streamlit 开发 Web 应用程序:
使用 Python 实现数据可视化:
面向所有人的 Python 专业化:
GitHub 资源库:
https://github.com/mkhorasani/data_warehouse
新到中?您可以在此订阅并解锁无限文章。
从联邦学校测试数据中建立数据库
乔治·李在 Unsplash 上的照片
在关系数据库中组织国家教育统计中心(NCES)的数据
介绍
作为两个孩子的父母,我和妻子研究了美国几个城市的学校质量。这项研究产生了一些流行的网站,这些网站展示了各个学校质量的各种衡量标准。两个最受欢迎的提供学校质量信息的网站是 GreatSchools.org 和 Niche.com。作为一名数据专家,我想知道这些网站从哪里获得数据。这两个网站都使用了美国教育部国家教育统计中心(NCES)提供的数据(GreatSchools.org 也使用了相当多的州教育部门的数据,比联邦数据更详细)。
在这一系列文章中,我将分享我下载和重构大量数据集的过程。首先,我将使用 Python 来重组数据,将其加载到 Mysql 数据库中,然后我将使用 Python 的一些流行的数据科学库来做一些特别的研究项目。
这第一篇文章的主要目标是介绍我如何成功地使用 Pandas 将从 NCES 获得的 10 年学校级考试成绩加载到 MySQL 数据库中。通过将这些数据加载到关系数据库格式中,我的目标是使大量的分析任务更加有效,因为我将在以后的文章中更深入地研究这些数据。希望这对那些对探索 Pandas 的 SQL 能力感兴趣的人有所帮助。
工具
本系列中的工作将使用几个流行的 Python 库和 MySQL 来执行,并可能使用一些其他工具来节省我的时间。
对于本文,我将使用 4 个 Python 库:Pandas 将用于数据处理任务,OS 和 GLOB 将用于访问目录和文件,SQLAlchemy 将用于连接我将加载数据的 MySQL 数据库。
数据
NCES 每年都会提供一套学校和地区级的绩效档案。每个学校级别的绩效文件显示了每个参与评估的美国学校在评估年度的全部结果。2017–2018 文件共有 265 列。前 9 列包含有关学校和数据收集年份的信息。额外的列包含被评估的学生数量和全校被认为熟练的百分比(熟练百分比),并按各种人口统计和种族群体进行细分。关于文件结构的详细信息可以在美国教育部的 EDFacts 数据文件网站上找到。对于这个项目,我已经保存了从 2009-2010 学年到 2018-2019 学年的所有学校级绩效文件。
构建数据管道
首先,我将导入要使用的库,并建立到 MySQL 数据库的连接。
接下来,我使用 SQLAlchemy 库建立了一个数据库连接,我们稍后将使用它来加载重构的学校级别的性能数据。对于 SQLAlchemy 数据库连接,一个重要的建议是,在建立连接时,将 echo 参数设置为 false,这是我从惨痛的教训中学到的。如果您按照我这里的步骤操作,您将会以 10,000 个数据块的形式向数据库写入数百万行。您不会希望每次成功执行写命令时都将日志写入笔记本。我在一个 jupyter 笔记本上犯了这个错误,写了几个小时数据就用完了内存。对于需要写入数据库的额外任务,我下载了笔记本。py 文件,并从命令行运行它。
最后,本文的主要目标是通过 Pandas 工具将这些数据加载到数据库中。所有重新格式化数据和将字符串值映射到数值的工作都是使用一个类完成的。虽然它们对于让您的程序在 Python 中工作并不重要,但我发现编写类有助于提高代码的可读性和组织不同的函数,以便它们能够很好地协同工作。
在这种情况下,Python 类将完成 5 个主要任务来为我的数据库准备数据:1)读取 csv 文件,2)找到测试分数数据所在的列,3)选择包含学校身份信息(例如,名称、州、机构等)的列,4)处理参与和分数信息并将数据堆叠为垂直格式,5)将测试分数数据中包含的字符串值转换为数值,以便在存储到数据库中时进行分析。
完成这些步骤后,转换后的数据将使用实体属性值(EAV)数据模型存储在我的数据库中。EAV 模型将使我们能够消除数据中的空值,并有效地存储数据以备后用。对于我们的 EAV 模型,每个实体将是一所学校,属性将是学校学生不同分组的测试结果。
处理年度绩效文件的步骤如下:
我用两个参数实例化了这个类。“file”参数将我们引向我们将要处理的文件,“str_replmts”参数是一个字典,它将性能文件的原始版本中包含的字符串值映射到已经定义并存储在字典中的数字替换。
接下来,我创建了一个函数,它识别并构建文件中存储测试分数参与和结果的列的列表。
接下来,有三个不同的函数用于选择包含关于学校的元数据的列。不同的函数需要考虑不同年份之间列名的细微变化。
接下来的两个函数将用于将测试分数数据转换成 EAV 数据格式。在我们的堆栈函数中调用这个转换函数,将字符串值转换成数字。
这个函数利用了将字符串值映射到数字的字典。一旦我们将数据加载到我们的数据库中,以数字形式存储考试分数数据对于促进分析将是非常重要的。这一步是必要的,因为 NCES 文件根据注册参与者的数量存储特定学生统计组的熟练程度分数。采取这项措施是为了保护学生的隐私。例如,为了使这些数据可用于分析目的,必须将字符串值“LE10”(代表“小于或等于 10%的熟练程度”)转换为数值。为了对数据集中的所有字符串值完成这个过程,我收集了所有数据文件中所有字符串值的列表,并在一个 csv 文件中手动执行映射。开发这个映射文件的完整过程超出了本文的范围。映射文件如下所示:
这个文件被转换成一个字典,这样它就可以用来将字符串映射成数字。
类中包含最多功能的函数是最终的 get_scores_data。该函数执行一系列任务,将测试分数数据转换为 EAV 格式,我将使用该格式将数据存储在我的 MySql 数据库中。
这个函数完成 4 个主要任务:1)它选择学校 id 和包含测试分数数据的列,2)它垂直堆叠数据,3)它解析列标题中包含的元数据(例如,什么人口统计组,什么科目,等等。)放入单独的列中,以便在数据加载到数据库中后更容易进行过滤,以及 4)它用上面的“str_replace”字典中的映射值替换非数值。
任务 1:选择学校 id 和包含测试分数数据的列
任务 2:垂直堆叠数据
任务 3:将每一列的元数据解析到单独的字段中。
任务 3:用映射值替换非数字:
在文件准备好写入数据库之前,此函数执行的最后一个操作是过滤/连接操作,该操作创建最终数据集,其中并排显示每个独特年份/人口统计组的参与者数量和熟练程度百分比。
为了对 NCES 文件执行这些操作,我使用了一个 for 循环来处理每个文件,并将其加载到 MySQL 数据库中。
请注意,我在 Pandas 的 to_sql 函数中使用了“chunksize”参数。在后台,SQL Alchemy 正在对 MySQL 数据库执行写语句。需要限制这些语句的长度(原因超出了本文的范围),将每个写命令的长度限制为 10k 记录似乎会阻止数据库关闭连接。关于这个 for 循环的另一个重要注意事项是,在将 for 循环中的下一个文件放入内存进行处理之前,’ del data '语句将删除刚刚加载的数据帧。如果您在内存有限的机器上工作,这可能会很有帮助。
在一台配有 4 个内核和 16GB 内存的 Windows PC 上,对所有文件运行这个 for 循环大约需要 8 个小时。
下面是加载 10 年的性能数据后,我们的最终数据库表的示例。
除了在 EAV 模型中加载学校绩效数据之外。我还需要存储每个学校的附加元数据,以便在不同学校之间和内部进行分析。
我通过调用不同版本的 get_school_index 函数完成了最后一项任务,创建该函数是为了考虑每年文件格式的细微变化,将结果连接到一个数据帧中,然后将该数据帧加载到我的 MySQL 数据库中的一个表中。
一旦加载到数据库中,数据看起来就像这样。
概述
概括地说,我们刚刚使用 Pandas 和其他一些库将大量与美国公立学校考试成绩相关的历史数据加载到 MySQL 数据库中。希望本文能帮助您理解这个数据集,并为您提供一些关于如何使用当代数据处理工具和技术转换和加载数据的想法。感谢阅读!
在 Azure 上构建深度学习图像字幕模型
实践教程
这幅图像中发生了什么?关于如何在云上创建深度学习计算机视觉和 NLP 模型的教程。
你如何描述这幅图像中发生的事情?安托万·华多,“梅泽廷”(1718-1720)。大都会博物馆提供。
在我利用 AI/ML 的新角色入职时,我开始从头开始在云上构建深度学习模型。在大学期间,我在一家艺术博物馆实习,在那里我亲眼目睹了整个行业面临的挑战,即将艺术品数字化,以便让广大观众能够接触到它们。
即使一件艺术品被数字化以供在线访问,也不总是容易找到。用户可能很难在博物馆的数据库中找到一幅他们可能见过但不记得名字的画。他们可能会使用语义语言来描述图像(例如,“坐在长凳上拿着一件乐器的人”),而不是关键短语,如绘画的名称(“梅泽廷”)、日期(1718-20)或艺术家的名字(安东尼·华多)。这个用例启发我建立了一个神经网络,它可以识别图像的关键组成部分,并根据场景生成描述性的说明。
然而,图像字幕模型的应用超出了艺术博物馆的范围。处理许多需要易于搜索的描述的图像的组织(如工厂、报纸或数字档案馆)可以从自动生成标题中受益,而无需依赖劳动密集型的手动描述过程。此外,标题还有支持可访问性标准的额外好处。书面字幕可以通过自适应技术为视力残疾的个人大声朗读。
Azure Cognitive Services 提供图片标记和图片描述服务。然而,我想看看如何从头开始在云中构建和部署一个模型。我的目标是使用 Azure 的工具和服务来训练、测试和部署深度学习模型,并最终看看它的性能与 Azure 的一些现有产品相比如何。
为了建立深度学习模型,杰森·布朗利的图像字幕模型充当了一个切入点。我使用 Azure Databricks 和 Azure Machine Learning 作为创建我的深度学习模型的平台。然而,无论您使用何种云提供商平台,一般的培训和部署工作流程都是相似的:
- 存储数据
- 在 Azure 上设置培训和测试环境
- 加载和预处理数据
- 训练和评估模型
- 注册模型
- 为部署准备模型
- 部署模型以计算目标
- 使用部署的模型(即 web 服务)进行推理
在接下来的内容中,我将介绍我是如何构建这个模型的,并分享为自己构建一个版本的技巧。最后,我讨论了为什么模型会有这样的表现,并找出了改进的机会。
技术堆栈和架构概述
当一幅图像被描述时会发生什么?有两个主要的考虑因素:第一,模型必须识别图像中的关键主题和支持细节。这个组件将由“计算机视觉”组成,该模型识别视觉上表示的内容并输出唯一的特征向量。第二个概念是自然语言处理。然后,该模型必须将其所见转化为一个连贯的句子,概括图像中正在发生的任何事情。
该项目建立在微软 Azure 云之上。具体来说,使用从 Blob 存储器摄取的数据在数据块上训练该模型。然后,这些模型被注册到一个 Azure 机器学习工作空间中,这样模型就可以作为 RESTful API 端点进行部署,以供最终使用。用户可以向端点发送图像的多个公共 URL,并接收从 Azure 计算机视觉认知服务和深度学习模型生成的标题。
下面是一个架构图,展示了所使用的工具和服务:
我如何在 Azure 上构建深度学习模型的架构图。图片作者。
我使用的数据集是 Flickr 8k 数据集,它由从 Flickr 的数据库中抓取的 8000 多张图片组成,该数据库在 Creative Commons 许可下可用。它还包括一个 CSV 文件,其中包含由一个人根据每张图片编写的五个标题(总共超过 40,000 个标题)。每张图片都有多种不同语法风格的标题,因为描述一张图片有多种方式,其中一些比另一些更准确、详细或简洁。
预先训练的单词嵌入模型 GloVe 用于将训练数据中的单词映射到更高维的空间,以提取单词之间的精确关系。
我使用的模型架构是一个递归神经网络 (RNN)和一个数据生成器的公式,用于逐步加载数据。前一种方法通过根据句子中以前的单词选择单词来帮助构建句子。后者避免了在集群节点的内存中一次性加载所有数据的负担。我利用预先训练好的 VGG16 模型来应用迁移学习(可以用 InceptionV3 模型作为替代)。这样,我可以使用 Keras 定制一个现有的模型来满足我的解决方案的需求。
为了评估生成的字幕的质量,我使用了 BLEU 评分,这是一个量化评估,用于评估字幕与“地面真实”(即人类编写的)字幕相比,听起来有多自然。
流程
了解如何通过八步在 Azure 上训练和部署深度学习模型。
1。将数据存储在 Blob 存储器中
Blob 存储是存储大量非结构化数据(如图像)的最佳选择。(您可以在此了解如何创建 Blob 存储帐户。)虽然你可以使用 SDK 或 Azure Portal 将下载的数据存储到 blob 存储中,但是 Azure Storage Explorer 有一个方便的 UI,允许你直接上传数据,而不需要太多的努力。
2。设置您的 Azure 环境虚拟机(VM)并使用 Databricks 挂载点加载数据
创建深度学习模型的环境可以通过 Azure 门户网站创建 Azure 机器学习(AML)工作区和 Databricks 工作区的实例来完成。从那里,您可以启动 Databricks 工作区,创建笔记本电脑,并启动计算集群。
您可以为您的计算集群选择许多不同的配置。对于 Databricks 运行时版本,最好使用版本名称中带有“ML”的运行时,这样可以确保安装的库版本与流行的机器学习库兼容。在数据块上旋转集群的好处在于,节点在不活动后会关闭,这是一个经济上有益的特性。
旁注:如果你使用的是 Flickr 16k 或者 30k 这样的大型数据集,我建议你在 GPU 上训练,这样训练起来更快。 Horovod ,一个诞生于优步的分布式训练框架,可以进一步加速深度学习训练。
如何在 Azure Databricks 中设置机器学习计算集群?图片作者。
在 Databricks 中,数据是通过一个挂载点获取的。这授权数据块中的笔记本读取和写入 Blob 存储容器中的数据,其中所有文件夹(即 Blob)都存在。为您的数据创建一个挂载点的好处是,您不必为您的特定数据源欺骗工具,而是利用数据块文件系统,就好像数据存储在本地一样。
下面是关于如何设置装载点来装载数据的代码片段:
然后,您可以使用前缀dbfs/mnt/mount-name
导航到该目录。例如,可以编写类似于open('dbfs/mnt/mycontainer/Flickr8K_data/captions.csv', 'r')
的代码来读取文件。
3。预处理图像和字幕数据
为了准备用于模型训练的数据,字幕数据集和图像目录需要预处理。这一步包括为训练数据集中出现的所有单词创建一个“词汇字典”(这是模型用来创建字幕的单词库),将图像整形为 VGG16(224×224 像素)模型的目标大小,并使用 vgg 16 模型提取每个图像的特征。
在处理所有字幕并应用标准自然语言处理(NLP)清理实践(例如,使所有单词小写并删除标点符号)后,词汇表大小为 8,763 个单词。
我不会深入讨论关于数据预处理的更多细节,因为在 Brownlee 的演示中已经很好地概述了这一步骤。
4。定义模型并根据数据对其进行训练
有时在 NLP 中,你的训练数据不足以让模型理解单词之间的准确关系。 GloVe 是一种表示向量空间中单词之间相似度的算法。
使用通过斯坦福大学 NLP 项目获得的预训练单词向量有助于区分训练词汇集中单词之间的关系,以创建可理解的句子。我使用了由 200 维向量表示的 40 万个单词的数据集。通过将训练词汇映射到预训练的手套模型,可以将得到的嵌入矩阵加载到模型中进行训练。
使用数据生成器函数加载字幕和图像数据避免了一次性将整个数据集存储在内存中的开销。关于模型架构如何工作的更多细节,我推荐查看 Brownlee 的解释。
在构建模型时,一个句子可以被认为是一系列单词。长短期记忆 (LSTM)是一种特殊类型的 RNN,它是一种神经网络,是处理序列的标准方法,通过特别注意前面的输入来保留序列的上下文。然而,与其他 RNN 不同,LSTM 可以根据前面的单词预测下一个最有可能出现的单词,从而逐字建立标题,其中时间顺序很重要。
下面是神经网络的架构图,它有两个输入,代表图像的特征向量和不完整的字幕。你可以在这里了解更多关于模型的不同部分(如照片特征提取器、序列处理器和解码器)。
该模型以一个“开始标记”开始字幕,并根据跟随前面单词的概率迭代添加单词。图片作者。
以下函数将神经网络架构转换为 Keras 定义的深度学习模型,并输出模型对象进行训练。数据生成器使用 model.fit() 函数将训练数据传递给模型。该模型被训练了 20 个时期,总共花费了 1 小时 36 分钟。
5。将部署模型注册为 Azure 容器实例(ACI)
训练好模型后,现在可以将它部署为 Azure 容器实例(ACI ),这样任何人都可以使用它并为任何图像目录创建标题。通常情况下,如果该模型用于生产,它可以作为 Azure Kubernetes 服务(AKS)部署,您可以在这里了解。
不久前创建的 Azure 机器学习(AML)工作区现在派上了用场。通过以下代码片段调用工作区,出于安全考虑,这需要您登录 Azure 帐户。
第二步是在 Azure 机器学习工作区中注册模型和结果所需的任何基本资产,以便端点可以访问它们。我注册了 model 和 tokenizer(词汇),因为输出句子的构造在整个过程中都引用了 tokenizer 来预测句子中最有可能出现的下一个单词。
让我们也确认一下模型和标记器是否成功注册了。
6。通过编写入口脚本准备部署配置
端点如何知道如何使用训练好的模型并给出输出?所有这些都是在 ACI 的入口脚本中定义的。输入脚本定义了预期的用户输入类型、如何处理数据以及如何格式化结果。
下面的代码定义了一个完成四个步骤的端点:
- 接收到映像目录的 SAS URI,并访问注册的模型和令牌化器
- 调用训练好的模型为每个图像构建标题
- 调用 Azure 计算机视觉认知服务资源来生成补充标题
- 以 JSON 格式返回自定义模型和 Azure 资源的标题
入口脚本必须特别有两个函数, init() 和 *run()。*前者加载重要资产;后者获取数据,并执行大部分繁重的工作,对图像进行预处理并构建字幕。下面的代码片段将脚本写入名为“score.py”的本地文件,该文件将包含在 ACI 部署的配置中。
7。部署模型以计算目标
ACI 还需要一个已定义的环境,该环境具有对输入进行评分所需的必备依赖关系。环境和入口脚本在推理配置中定义。
关键时刻:一旦 ACI 被定义,它就可以作为 Azure Web 服务进行部署,这样它就可以被调用了。从部署返回的评分 URI 将用于通过 REST API 调用测试端点,因此复制 URI 并保存起来以备后用。
8。通过调用 Azure Web 服务为新数据打分
测试模型端点有两种方法:用 Python 或 Postman。端点接受图像的任何公共 URL。正如预期的那样,端点返回来自训练模型的标题和 Azure Cognitive Service 的图像标题服务进行比较。用来自任何网站的图片或您自己的图片进行测试,以查看不同的结果。
在 Python 中,使用请求库来构造 API 调用如下:
如果您更喜欢在 Postman 中工作,以获得更快的调试和 UI 的便利性,该过程如下所示:
在 Postman 中测试模型端点的第 1 步。图片作者。
在 Postman 中测试模型端点的第 2 步。图片作者。
瞧吧!您现在已经成功地在 Azure 上训练和部署了深度学习模型!
点击了解更多关于在云上部署机器学习模型的信息。
项目成果
并非所有的字幕都是同等创建的。描述一幅图像有很多种方式,每种方式在语法上都可能是正确的。但是有些比其他的更简洁和流畅。
如前所述,BLEU 评分是衡量翻译质量的可靠方法,评分范围为 0 到 1。可以导入 NLTK Bleu 评分包,通过将预测的字幕与人类编写的实际字幕进行比较来评估模型的性能。
权重为(1.0,0,0,0)的 BLEU 得分为 0.48。通常,介于 0.50 和 1 之间的 BLEU 分数提供了良好的结果。点击了解有关计算 BLEU 分数的更多信息。
模型评估和改进
模型性能仍有改进的空间。它对背景简单、只包含少数几个不同主题的图像表现得相当好。
然而,它有时很难准确识别对象是什么(例如,将一个模糊的人物标记为一个人或无生命的物体标记为狗),以及在室内拍摄的照片,或者如果在室外,在某些季节拍摄的照片。我想知道为什么会这样,以及将来可以做些什么来使字幕更加准确和真实。
该模型适用于温暖天气拍摄的户外照片,但不适用于冬季场景。(图片来源通过知识共享)
我的假设是训练数据集有偏差。这些数据是在 2014 年从 Flickr 上搜集来的,当时数码相机已经普及,并被休闲摄影师所使用。许多照片是在面向家庭的环境中拍摄的,如体育比赛或后院。虽然也有在其他环境下拍摄的照片,但绝大多数照片包含了狗捡球、小孩在草地上玩耍以及一群人。
对于在图像中描绘的场景类型中有更多差异的较大数据集,我们可以期望在图像上得到模型以前没有见过的更准确的结果。然而,较大数据集的代价是较长的训练时间。为了缓解这种情况,一种选择是对数据进行分区,或者将其转换为 Delta Lake 或 parquet 文件,同时进行分布式培训或使用 GPU 虚拟机。
如果想提高模型的准确性,一种选择是使用训练模型时使用的超参数。例如,增加时期(即,模型通过整个训练数据集的次数)同时保持批量大小(即,它一次训练多少个样本)相对较小有助于模型捕捉数据集中更多的细微差别,因为梯度下降收敛的速度降低了。纪元数量和批量大小之间的适当平衡可以产生一个整体性能更好的模型,而不会增加训练时间。
改善结果的另一种方法是增加神经网络的复杂性,例如在模型结构中增加更多的层。通过使模型更加复杂,它可以产生潜在的更好的结果,因为它能够更详细地训练数据。
一旦模型得到改进并可以投入生产,它就可以作为 AKS 而不是 ACI 实例进行部署,用于大型目录。
最后的想法
来自 Flickr 8k 数据集的一只快乐的狗的训练照片,通过 Creative Commons 从 Flickr 获得。
和许多人一样,我发现吴恩达的深度学习和机器学习课程对了解深度学习领域非常有帮助。如果这些话题对你来说是新的,我建议试试他的视频。
概括地说,我们介绍了如何在 Azure 上设置机器学习环境,使用 Databricks 中的 Keras 定义和训练深度学习模型,并将该模型部署为容器实例,以便对新图像进行评估。
使用单词嵌入和余弦相似度构建可部署的吉拉 Bug 相似度引擎
循序渐进的方法
巴黎武装博物馆的一台英格玛机(鸣谢:作者自己的收藏)
介绍
在之前的文章中,我详细介绍了如何使用亚马逊的 BlazingText 来构建吉拉 bug 分类引擎。在那里,我还引用了相应的 Gitlab 库。在README.md
那里,我非常简要地提到了关于 word2vec 、 word 嵌入和作为输出生成到word2vec
算法的vectors.txt
文件。
在本文中,我们将使用相同的vectors.txt
单词嵌入来构建一个文本相似度引擎,以在给定的 bug 语料库中找到相似的 bug。
什么是单词嵌入?
我可能无法解释什么是单词嵌入,但是 YouTube 上有一个很好的视频很好地解释了这个问题。简而言之,在单词嵌入中,我们基于它们的上下文将语料库中的每个单词映射到一个向量空间,在这种情况下,上下文是与语料库中其他单词的接近度。
你们中的一些人可能会觉得这很有趣:macheads101 使用 word2vec 算法创建了一个单词嵌入,使用的是他在一段时间内收集的 Twitter tweets,然后创建了一个漂亮的小 web 应用程序来演示如何使用嵌入在语料库中找到相似的单词。
在我上面描述的vectors.txt
的例子中,我也绘制了向量空间的 case 图,只是为了对数据有个感觉:
向量的 t-SNE 图. txt
(本质上,t-SNE 是一种降维算法,其中我们将一个 n 维空间映射到一个较低的维度(在这种情况下是 2-d 用于绘图)。另一种流行的降维算法是 PCA)
正如你所看到的,有一些单词根据它们的上下文被认为是“相似”的。注意到图中的那些长条纹了吗?这些对应于吉拉门票中的代码片段。注意到它们都聚集在一起了吗?
这种将单词表示为向量的方式的好处是,单词之间的“距离”接近于相似性或更确切地说是亲和力或“它们属于一起”的概念。有几种方法来衡量这一点,[2]有一个很好的列表。为了简单起见,我们将只使用余弦相似度(正如我们将在后面看到的,这是一个不错的主意)。
但是我们不是在和单个单词打交道
现在,记住最初的目标:我们如何估计吉拉臭虫的相似性?我们可以扩展这个单词相似性的概念吗?
在[3]中,Yatin Vij 提供了几个选项:
- TF-IDF 加权单词嵌入
- Doc2Vec
- 平均单词嵌入
同样,为了简单起见,我们将使用(3) ie。对 bug 中的所有单词执行向量加法(在这种情况下,只有 bug 摘要和描述)。但是在[3]中,这种方法要求平均而不是求和。如果一个 bug 比另一个 bug 有更多的单词,这将是一个问题,对吗?如果我们使用余弦相似度就不会,因为与欧几里德距离不同,余弦相似度使用向量空间上两点之间的角度来确定相似度。这意味着可以使用矢量和来代替平均值。
这概括了主要思想。接下来就是尝试一下,看看是否有希望成功。在下面的部分中,我将引用在这里找到的代码:
(为什么是“墨鱼”?我的上一个项目叫做“章鱼”,所以我就顺其自然了)
算法探索
在本节中,我将浏览在cuttlefish
存储库中找到的TextSimilarityExplorations.ipynb
。这个 Python 笔记本是为在 Google Colab 上运行而设计的,我推荐它在那里运行。
设置
基本设置材料:
第一部分即。drive.mount('/content/drive')
基本上挂载你的 gdrive,这样你就可以从 gdrive 读入数据到 Colab。
读入数据
第一次读入vectors.txt
:
wv = KeyedVectors.load_word2vec_format('/content/gdrive/My Drive/Colab Notebooks/Octopus2/BlazingText/vectors.txt', binary=False)
注意:您必须根据您上传vectors.txt
文件的位置相应地修改路径(如果您计划运行笔记本)。
接下来,读入动物园管理员数据:
dataset = pd.read_csv("/content/gdrive/My Drive/Colab Notebooks/Octopus2/JIRA_OPEN_DATA_ZOOKEEPER.csv")
接下来,我们只需要id
、title
和description
字段,我们还将title
和description
组合成一个名为features
的字段
df = dataset[["id","title","description"]]
df["features"] = df["title"] + " " + df["description"]
features
本质上是我们将要比较相似性的“文档”。
预处理
接下来,我们删除功能 ie 中不需要的文本。电子邮件地址和网址不能提供大量信息:
df.features = df.features.str.replace(r"[\n\r\t]+", " ")
df.features = df.features.str.replace(r"([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})", " ")
df.features = df.features.str.replace(r"((http[s]?|ftp):\/)?\/?([^:\/\s]+)((\/\w+)*\/)([\w\-\.]+[^#?\s]+)(.*)?(#[\w\-]+)?", " ")
审判
接下来的部分是问题的核心。我还跳过了一些代码,因为它们对我来说是探索性的代码。
首先是一些有用的函数:
第一个sentance2vector
基本上是把一个句子(没错,是拼写错误)转换成 100 维向量来匹配vectors.txt
中的单词嵌入。还要注意,我必须跳过词汇表中没有的单词。如果特性中的所有单词都不在词汇表中,这可能很糟糕。我认为情况不会是这样。
接下来是cosineSimilarity
函数,它不言自明。
然后,我在数据帧中创建另一列来存储分数 ie。相似性估计并将其初始化为零:
df["score"] = np.zeros(len(df))
接下来是主要计算:
for i in range(0,len(df)):
vect = sentance2vector(df.iloc[i].features)
df.iloc[i,4] = cosineSimilarity(vector, vect)
注意:vector
是df
中第一行features
列的矢量版本。
这实际上是计算第一行df
相对于所有其他行的相似性。
那么相似度估计有什么好的吗?
为了找到答案,我需要把小麦从谷壳中分离出来。也就是说,只选择相似度高的 bug(我选择的阈值是 0.87)。
df1 = df.loc[df.score > 0.87]df2 = df1[["features","score"]]df2
然后我看了看那些高分的,和“目标”bug 比较:
Quota is not correctly rehydrated on snapshot reload traverseNode in DataTree will never actually traverse the limit nodes properly.
一些得分较高的是:
C Client might not cleanup correctly during close I was looking through the c-client code and noticed a situation where a counter can be incorrectly incremented and a small memory leak can occur.In zookeeper.c : add_completion(), if close_requested is true, then the completion will not be queued. But at the end, outstanding_sync is still incremented and free() never called on the newly allocated completion_list_t. I will submit for review a diff that I believe corrects this issue.andThe last logged zxid calculated by zookeeper servers could cause problems in leader election if data gets corrupted. It is possible that the last loggged zxid as reported by all the servers during leader election is not the last zxid that the server can upload data to. It is very much possible that some transaction or snapshot gets corrupted and the servers actually do not have valid data till last logged zxid. We need to make sure that what the servers report as there last logged zxid, they are able to load data till that zxid.
这还不错。第一个处理数据结构问题,目标也是如此。第二个与一些数据处理的过程问题有关,目标也是如此。
所以问题来了:在传统的 ML 中,你有一个标记数据集,你把它分成训练/测试子集,并使用测试子集来评估你的算法执行得有多好。在这种情况下,除非你非常熟悉 bug,否则执行评估真的非常具有挑战性。执行评估的一个简单方法是部署它,使用它,并有一种方法对算法进行评分(这个想法类似于 A/B 测试)。
部署
使这个算法可部署的一个简单方法是将其构建为 Docker 容器,并将其发布在 Docker 注册表上。在以前的文章(参见“构建可部署模型”一节)中,我描述了 octopus2 项目的步骤。对于墨鱼来说,步骤几乎是一样的,除了这里,我没有自己构建app.py
,而是使用了我开发的一种方法,首先将app.py
原型化为 Jupyter 笔记本,然后将其转换为容器的app.py
。
构建过程
我将首先从高级构建过程开始。为此,我们应该查看.gitlab-ci.yml
文件:
构建主要有两个阶段:(a)从Cuttlefish.ipynb
构建app.py
;以及(b)构建和部署 Docker 容器。
(b)是一个漂亮的锅炉板,在前面的文章中也有描述。(a)基本上只是用nbconvert
把笔记本转换成app.py
,用remove_lines.py
去掉不想要的代码(稍后再解释)。
墨鱼网
如果你仔细看看这个笔记本,你会注意到它几乎是一个清理过的TextSimilarityExplorations.ipynb
版本。还有额外的标准 Flask 代码使它成为一个 API,差不多就是这样。
注意所有的# START REMOVE
和# END REMOVE
。这些是为了让remove_lines.py
知道删除哪些代码块。共有 3 个代码块:(a)第pip install
部分(b)第app.run()
部分。(b)适合作为笔记本测试代码,但不适合作为 Docker 容器部署。这将需要:
app.run(host='0.0.0.0', port='33')
运行发动机
我们终于到了有趣的部分!第一部分是做一个这样的docker run
:
docker run --publish 5000:33 --name cuttlefish foohm71/cuttlefish:master
接下来我们给你一个选择:使用笔记本调用 API(testing.ipynb
)或者运行脚本(test.sh
)。两个人都很直率。test.sh
只是一个使用test.json
中有效负载的 curl,testing.ipynb
只是使用requests
对 API 进行同样的调用。
repo 上的README.md
也描述了有效载荷格式:
仅此而已。我希望您发现这很有用!
(对于那些希望在 GCP 或 AWS 上部署这个的人来说,README.md
有一些关于如何做的提示)
参考
[1]词嵌入,machead101,2017 年 7 月,【https://youtu.be/5PL0TmQhItY
[2]文本相似度:估计两个文本的相似程度,阿德里安·锡格河,2018 年 7 月,https://medium . com/@ adriensieg/Text-Similarities-da 019229 c894
[3]结合单词嵌入形成文档嵌入,Yatin Vij,2019 年 9 月,https://www . analyticsvidhya . com/blog/2020/08/top-4-sentence-embedding-techniques-using-python/
用 Python 中的 Scikit Learn 构建人脸识别系统
计算机视觉
使用 Insightface 嵌入比较人脸
图片由 Gerd Altmann 从 Pixabay 拍摄
什么是人脸识别?
人脸识别的任务是将一个未知人的脸与数据库中存储的图像进行比较。映射可以是一对一或一对多,这取决于我们运行的是人脸验证还是人脸识别。
在本教程中,我们感兴趣的是建立一个面部识别系统,该系统将验证图像(通常称为探针图像)是否存在于预先存在的面部数据库(通常称为评估集)中。
直觉
建立这样一个系统有四个主要步骤:
1.检测图像中的人脸
可用的人脸检测模型有 MTCNN、FaceNet、Dlib 等。
2.裁剪和对齐面以获得一致性
OpenCV 库提供了这一步所需的所有工具。
3.找到每个面的矢量表示
由于程序不能直接处理 jpg 或 png 文件,我们需要某种方法将图像转换成数字。在本教程中,我们将使用 Insightface 模型为人脸创建多维(512-d)嵌入,从而封装与人脸相关的有用的语义信息。
为了使用一个库来处理这三个步骤,我们将使用insightface
。特别是,我们将使用 Insightface 的 ArcFace 模型。
InsightFace 是一个开源的深度人脸分析模型,用于人脸识别、人脸检测和人脸对齐任务。
4.比较嵌入
一旦我们将每个独特的脸转换成一个向量,比较脸的本质归结为比较相应的嵌入。我们将利用这些嵌入来训练一个 sci-kit 学习模型。
附:如果你想跟随,代码可以在Github上找到。
设置
创建虚拟环境(可选):
python3 -m venv face_search_env
激活此环境:
source face_search_env/bin/activate
此环境中的必要安装:
pip install mxnet==1.8.0.post0
pip install -U insightface==0.2.1
pip install onnx==1.10.1
pip install onnxruntime==1.8.1
更重要的是,一旦完成 pip 安装insightface
:
-从 onedrive 下载羚羊车型发布。(它包含两个用于检测和识别的预训练模型)。
-放在*~/.insightface/models/*
下,所以*~/.insightface/models/antelope/*.onnx*
有 onnx 款。
如果设置正确,应该是这样的:
如果你在antelope
目录中查找,你会找到两个用于人脸检测和识别的onnx
模型:
注意:自从上周最新发布的 *insightface*
0.4.1 以来,安装并不像我希望的那样简单(至少对我来说是这样)。因此,我将在本教程中使用 0.2.1。以后我会相应更新 Github 上的代码。如果你卡住了,请看这里的说明https://github.com/deepinsight/insightface/issues/891#issue-489506780。
资料组
我们将使用 Kaggle 上可用的耶鲁人脸数据集,该数据集包含 15 个人的大约 165 幅灰度图像(即每个身份 11 幅独特的图像)。这些图像由各种各样的表情、姿势和照明配置组成。
一旦你有了数据集,继续把它解压到你的项目中新创建的data
目录中(参见 Github 上的项目目录结构)。
让我们开始吧…
如果你想跟随,Jupyter 笔记本可以在 Github 上找到。
进口
*import os
import pickle
import numpy as np
from PIL import Image
from typing import List
from tqdm import tqdm
from insightface.app import FaceAnalysis
from sklearn.neighbors import NearestNeighbors*
加载 Insightface 模型
一旦insightface
安装完毕,我们必须调用app=FaceAnalysis(name="model_name")
来加载模型。
因为我们将onnx
模型存储在羚羊目录中:
*app = FaceAnalysis(name="antelope")
app.prepare(ctx_id=0, det_size=(640, 640))*
生成 Insightface 嵌入
使用insightface
模型为图像生成嵌入非常简单。例如:
**# Generating embeddings for an image*img_emb_results = app.get(np.asarray(img))
img_emb = img_emb_results[0].embedding
img_emb.shape------------OUTPUT---------------
(512,)*
资料组
在使用这个数据集之前,我们必须修复目录中文件的扩展名,使文件名以.gif
结尾。(或.jpg
、.png
等)。
例如,下面的代码片段将把文件名subject01.glasses
改为subject01_glasses.gif
。
接下来,我们将数据分为评估集和探测集:每个受试者的 90%或 10 张图像将成为评估集的一部分,剩余的 10%或每个受试者的 1 张图像将用于探测集中。
为了避免采样偏差,将使用名为create_probe_eval_set()
的辅助功能随机选择每个对象的探头图像。它将包含属于特定主题的 11 个图像(的文件名)的列表作为输入,并返回长度为 1 和 10 的两个列表。前者包含用于探针组的文件名,而后者包含用于评估组的文件名。
生成嵌入
由create_probe_eval_set()
返回的两个列表被顺序地提供给一个叫做generate_embs()
的帮助函数。对于列表中的每个文件名,它读取灰度图像,将其转换为 RGB,计算相应的嵌入,最后返回嵌入和图像标签(从文件名中提取)。
现在我们有了一个生成嵌入的框架,让我们继续使用generate_embs()
为探针和评估集创建嵌入。
需要考虑的事情很少:
- *由
os.listdir()
返回的文件是完全随机的,因此第 3 行的排序很重要。*为什么我们需要排序的文件名?记住第 11 行的create_probe_eval_set()
要求在任何一次迭代中所有文件都属于一个特定的主题。
**
os.listdir()的输出,不排序(左)和排序(右)
- 【可选如果我们使用
sklearn
提供的分层*train_test_split
功能,我们可以替换create_probe_eval_set()
功能,去掉for
循环,并简化上面代码片段中的几行代码。然而,对于本教程,我认为清晰比代码简单更重要。*
通常,insightface
无法检测到人脸,并随后为其生成空嵌入。这解释了为什么probe_set
或eval_set
列表中的一些条目可能是空的。重要的是,我们要过滤掉它们,只保留非空值。
为此,我们创建了另一个名为filter_empty_embs()
的助手函数:
它将图像集(或者是probe_set
或者是eval_set
)作为输入,并移除那些insightface
不能生成嵌入的元素(见第 6 行)。在此之后,它还更新标签(或者是probe_labels
或者是eval_labels
)(见第 7 行),使得集合和标签具有相同的长度。
最后,我们可以仅获得在评估集和探测集中的良好指数的 512-d 嵌入:
***assert** len(evaluation_embs) == len(evaluation_labels)
**assert** len(probe_embs) == len(probe_labels)*
有了这两个数据集,我们现在准备使用 Sklearn 库中实现的一种流行的无监督学习方法来构建我们的人脸识别系统。
创建人脸识别系统
我们使用评估嵌入为X
的.fit()
来训练最近邻模型。对于无监督的最近邻学习来说,这是一种巧妙的技术。
最近邻方法允许我们找到距离新点最近的预定数量的训练样本。
注意:一般来说,距离可以是任何度量标准,例如欧几里德、曼哈顿、余弦、M inkowski、等。
因为我们正在实现一个无监督的学习方法,请注意,我们没有向fit
方法传递任何标签,即evaluation_label
。我们在这里所做的就是将评价集中的人脸嵌入映射到一个潜在空间。
**为什么??,你问。
简单回答:通过提前将训练集存储在内存中,我们能够在推理时间内加快对其最近邻居的搜索。
这是怎么做到的?
简单回答:在内存中以优化的方式存储树非常有用,特别是当训练集很大并且搜索新点的邻居变得计算量很大时。
基于邻居的方法被称为非一般化机器学习方法,因为它们简单地“记住”所有的训练数据(可能被转换成快速索引结构,如球树或 KD 树)。 [ 来源
注:见 本 Stackoverflow 讨论如果你还不服气!
推理
对于每个新的探针图像,我们可以通过使用nn.neighbours()
方法搜索它的顶部 k 邻居来发现它是否存在于评估集中。举个例子,
*# Example inference on test imagedists, inds = nn.kneighbors(X = probe_img_emb.reshape(1,-1),
n_neighbors = 3,
return_distances = True
)*
如果评估集中返回的索引(inds
)处的标签与探测图像的原始/真实标签完全匹配,那么我们知道我们已经在验证系统中找到了我们的脸。
我们已经将上述逻辑包装到了print_ID_results()
方法中。它将探头图像路径、评估集标签和verbose
标志作为输入,以指定是否应该显示详细的结果。
这里需要注意一些重要的事情:
inds
包含evaluation_labels
集合中最近邻居的索引(第 6 行)。例如,inds = [[2,0,11]]
表示发现evaluation_labels
中索引=2 处的标签最接近探头图像,随后是索引= 0 处的标签。- 由于对于任何图像,
nn.neighbors
将返回非空响应,如果返回的距离小于或等于 0.6(第 12 行),我们必须仅将这些结果视为面部 ID 匹配**。(p . s . 0.6 的选择完全是任意的)。
例如,继续上面的例子,其中inds = [[2,0, 11]]
和假设dists = [[0.4, 0.6, 0.9]]
,我们将只考虑索引=2 和索引= 0 处的标签(在evaluation_labels
中)作为真正的面部匹配,因为最后一个邻居的dist
对于它来说太大而不是真正的匹配。**
作为快速检查,让我们看看当我们输入一个婴儿的脸作为探测图像时系统的反应。不出所料,它显示没有找到匹配的面孔!然而,我们将verbose
设置为 True,因此我们可以在数据库中看到其伪最近邻居的标签和距离,所有这些看起来都相当大(> 0.8)。
评估人脸识别系统
测试该系统是否好的方法之一是查看在前 k 个邻居中有多少相关结果。相关结果是真实标签与预测标签相匹配的结果。该度量通常被称为 k 处的精度,其中 k 是预先确定的。**
例如,从探针集中选择一个图像(或者更确切地说是一个嵌入),其真实标签为“subject01”。如果nn.neighbors
为该图像返回的前两个pred_labels
为[‘subject01 ‘,’ subject01’],则表示k=2
在 k (p@k)处的精度为 100%。同样,如果pred_labels
中只有一个值等于‘subject 05’,p@k 就是 50%,以此类推…
*dists, inds = nn.kneighbors(X=probe_embs_example.reshape(1, -1),
n_neighbors=2,
return_distance=True)pred_labels = [evaluation_labels[i] for i in inds[0] ]
pred_labels----- OUTPUT ------['002', '002']*
让我们继续计算整个探针组的平均p@k
值:
厉害!90%不算太差,但肯定可以改进(但那是另一次)…
你能坚持到底,我向你致敬!希望这篇热情洋溢的关于人脸识别的介绍足以让你入门,人脸识别是计算机视觉研究的一个活跃领域。一如既往,如果有更简单的方法来做我在本文中提到的一些事情,请让我知道。
直到下次:)
在 Django 中构建数据输入的快速 Web 界面
数据输入
关于如何安装 Django 并利用它快速构建数据输入的 Web 界面的教程
图片来自 Pixabay 的 Innova Labs
在本文中,我描述了一个在 Django 中为数据输入构建快速 Web 界面的简单策略。本文涵盖以下主题:
- Django 概述
- 安装 Django
- 创建新项目
- 创建新应用程序
- 创建您的数据库
- 构建数据输入 Web 界面
1 Django 概述
Django 是一个 Python Web 框架,旨在构建快速 Web 应用程序。Django 的一个实例叫做项目。一个项目可能包含一个或多个应用。
Django 遵循模型-模板-视图(MTV)架构。MTV 与模型-视图-控制器(MVC)架构的不同之处在于控制器部分已经由框架本身通过模板实现。
Django MTV 架构由以下组件组成:
- 模型:定义逻辑数据结构。实际上,模型是一个 Python 类,它代表数据库中的一个表。所有表示数据库逻辑结构的类都存储在一个名为 models.py 的脚本中。
- 视图:它定义了业务逻辑,也就是说,它与模型进行通信,并将其转换成模板可读的格式。视图是一个 Python 函数,它将请求作为输入,并将 Web 响应作为输出返回。所有代表业务逻辑的功能都存储在一个名为 views.py 的脚本中。
- 模板:定义一个文件的结构或者布局,比如 HTML 文件。**它是通过 Django 模板语言编码的文本文档或 Python 字符串。**所有的模板都存放在名为 templates 的 App 的子目录下。
下图展示了 Django MTV 架构以及模型、模板和视图组件之间的交互方式:
作者图片
2 安装 Django
首先,必须通过以下命令安装 Django 库:
pip3 install django
您可以决定将它直接安装在您的主工作环境中,或者安装到一个特定的virtualenv
中。本文解释了如何构建一个 Python virtualenv
。
在创建新项目之前,必须配置关联的数据库。因此,你的机器上必须有一个正在运行的关系数据库(如 MySQL 或 Postgres)。我个人安装了 MySQL 服务器。由于我有一个旧的安装 XAMPP ,我已经利用了它提供的 MySQL 服务器。在 PhPMyAdmin 中,我创建了一个名为 *mywebsite_db 的数据库,*最初为空。姜戈会填充它。
为了与 SQL 服务器通信,Django 需要一个用于 SQL 的 Python 驱动程序。在 MySQL 的情况下,你应该安装mysqlclient
Python 库。
根据您的操作系统,您可能会遇到一些安装问题,尤其是当您的操作系统非常旧的时候。就我个人而言,我在安装mysqlclient
库时遇到了一些问题,通过遵循链接中提出的建议,我克服了这些问题。
在接下来的部分中,我将概述如何构建和运行 Django 应用程序。更多细节可以参考 Django 官方文档。
3 开始一个新项目
一个 Django 网站实例被称为项目。在一个项目中,您可以运行许多 Web 应用程序。你可以使用 django-admin 工具创建一个新项目:
django-admin startproject mywebsite
其中 mywebsite 是您项目的名称。这个命令将在您运行前一个命令的文件夹中创建一个名为 mywebsite 的新目录。
作者图片
文件夹 mywebsites 包含两个元素:
manage.py
,这是用于配置的主脚本- 与您的项目同名的文件夹(inner mywebsite )。
左图显示了内部 mywebsite 文件夹的内容。
在运行新项目之前,您必须在内部mywebsite/settings.py
脚本中配置数据库参数,如下所示:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': '*mywebsite_db*',
'USER': 'root',
'PASSWORD' : 'YOUR PASSWORD'
}
}
您可以使用 manage.py 和runserver
命令从这个文件夹中运行开发 Web 服务器:
cd mywebsite
python3 manage.py runserver
4 启动新的应用程序
在一个项目中,你可以随心所欲地运行应用程序。为了创建应用程序,您可以进入项目文件夹并运行以下命令:
python3 manage.py startapp myapp
其中 myapp 是应用名称。
注意,一旦创建了一个项目,对它的所有操作都将通过 **manage.py**
脚本来完成,例如 startapp 、 migrate (您将在本文后面读到,等等)。
如果你想激活新的应用程序,你必须把它添加到活动应用程序列表中,包含在内部mywebsite/setting.py
脚本中。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
**# Add you new application,
'****myapp.apps.MyappConfig****'**
]
现在你必须告诉网络服务器一个新的应用程序已经被添加。你可以通过migrate
命令来完成。您可以从项目的根目录启动它:
python3 manage.py migrate
5 创建您的数据库
现在环境准备好了。你可以专注于内容,在数据模型和网站展示方面。
可以在以下文件中修改模型:
myapp/models.py
数据库中的每个表对应于模型中的一个类。例如,该表可以很容易地转换成下面的模型。
作者图片
**from** **django.db** **import** models
**class** **Person**(models.Model):
Name = models.CharField(max_length=64)
Surname = models.CharField(max_length=64)
BirthDate = models.DateTimeField()
Sex = models.CharField(max_length=1)
该模型独立于特定的 SQL 数据库,因此可以由 Django 翻译成任何 SQL 数据库(MySQL、Postgres……)
一旦创建了模型,您必须告诉 Django 您已经对您的模型做了一些更改。这可以通过makemigrations
命令完成,然后是migrate
命令,该命令应用所有尚未应用的迁移:
python3 manage.py makemigrations myapp
python3 manage.py migrate
6 构建数据输入 Web 界面
为了创建 Web 界面,您可以利用基于管理界面的 Django 功能。您可以通过以下命令激活管理界面:
python3 manage.py **createsuperuser**
现在,您必须授予管理员对所有模型类的访问权限。这可以通过编辑myapp/admin.py
脚本来完成。您应该为希望授予管理员访问权限的每个表添加一个注册选项:
**from** **django.contrib** **import** admin
**from** **.models** **import** Person
admin.site.register(Person)
管理界面将在以下地址可用:http://127 . 0 . 0 . 1:8000/admin/。它看起来将如下所示:
作者图片
那都是乡亲们!
摘要
在本文中,我描述了如何在 Django 中为数据输入构建一个快速的 Web 界面。在环境的初始设置之后,部署 Web 界面非常简单,因为它可以利用 Django 提供的管理界面。
而你,你利用哪个框架来构建快速的 Web 界面?给我留言吧!我很乐意听听你的意见!
如果你已经走了这么远来阅读,对我来说今天已经很多了。谢谢!你可以在这篇文章中读到更多关于我的信息。
如果你想更多地了解 Django 是如何工作的,你可以阅读这篇有趣的文章,作者里努·古尔,他解释了 Django 的 MTV 架构。
相关文章
离开前再多说一句:你可以从我的 Github 库:)下载本教程的完整代码
新到中?您可以每月订阅几美元,并解锁无限的文章— 单击此处。
构建一个完全部署的太阳能计算器应用程序来模拟为您的家庭节省成本
实践教程
构建生产级太阳能计算器应用程序的过程,包括 Python 模拟、D3.js 可视化、用 Javascript 编写的交互式绘图工具、PostgreSQL 后端等等。
几个月前,我完成了纽约城市大学专业研究学院的数据科学硕士学位。我写了一篇关于我决定攻读数据科学研究生学位的深入文章,并权衡了在该领域采用更传统的学习和发展方法的利弊。如果你对这个话题感兴趣,或者你自己正在努力做这个决定,请在这里随意阅读。
在这篇文章中,我将讨论我作为研究生的最后几个月,通过一个太阳能计算器顶点项目进行讨论,这个项目是我和一个城市大学 SPS 的学生一起开发的。鉴于项目的复杂性(以及我们缩短的三个月时间表),我们决定将开发的主要部分分成两个子项目:
- 开发和编写一个 Python 模拟,根据用户输入到我们的应用程序中的特定输入来估计用户家庭的太阳能发电量,并将其扩展以衡量 15 年内的成本节约。
- 开发和部署完整的基于 web 的应用程序以及我们的 Python 模拟、Flask 框架、前端 JavaScript 绘图工具和 PostgreSQL 后端背后的工程。
因为我承担了第二个子项目的大部分工作,所以我将在本文中更详细地介绍这个过程。但是,如果您对模拟计算、我们用于创建这些预测的数据以及关于我们整体研究/发现的更多信息感兴趣,请随时在 Youtube 上观看我们的最终演示视频:
我们最终的顶点演示视频,提供了我们工作的背景,并概述了我们项目的所有组成部分|作者视频
初始研究和动机
在美国,在过去的 25 年里,在住宅屋顶安装太阳能电池板的势头越来越猛。利用太阳能为家庭供电不仅有助于减轻气候变化的影响,而且随着时间的推移,还可以为住宅消费者节省大量资金。不幸的是,根据 2019 年的皮尤调查,只有 6%的美国房主表示他们已经在家里安装了太阳能电池板。
由于这个紧缩的统计数据,我们真的很有兴趣了解更多关于当前太阳能市场的情况,以及在美国发展的一些障碍。我们发现,尽管消费者犹豫的原因很多,但其中一个主要原因是面板安装的前期成本。根据你需要为你的家提供足够电力的太阳能电池阵列的大小,安装费可能在 10,000 美元到 25,000 美元之间(税收优惠前)。对许多人来说,这个成本有点令人望而生畏。然而,重要的是要明白,一旦你投资了太阳能,你就从当地的电网服务提供商那里获得了大量的能源独立,同时也大大降低了你每月的能源费用。随着时间的推移,您每月节省的电费可能会抵消安装的初始成本。因此,我们认为,如果我们能够构建一个应用程序,将这一点有效地传达给公众,我们也许能够说服那些“犹豫不决”的人进行初始投资。
发展我们的目标
最终,我们的应用程序的目标变成了一个工具,旨在更好地告知纽约奥尔巴尼的居民将他们的住宅转换为太阳能的财务收益,并明确他们何时可能期望达到“收支平衡”点,即他们在家中安装电池板的初始投资成本将被他们通过停止常规电网服务节省的能源抵消。
为了做到这一点,我们需要开发一个 15 年的能源成本模拟,其中考虑了用户房屋的许多组成部分(即房屋的总面积、屋顶表面积、房屋建造年份、家庭成员人数等)。),以及他们的屋顶通常接受的阳光照射量(具体取决于房屋的纬度/经度和屋顶方向)。然后,我们将获取这些数据,通过我们的模拟运行这些数据,计算一个面板系统可以产生的能量,并将其与同一时期的电网服务成本进行比较。
尽管我们希望将来将该项目扩展到其他地区,但考虑到缩短的时间表和能源使用/成本数据的限制,我们不得不将地理范围缩小到奥尔巴尼大都市区。
解决围绕应用功能的问题
你可能在想…好吧,这听起来是一个很酷的项目,但是你实际上如何构建这个应用程序呢?为了完成这一过程,我们必须解决一些关于应用程序功能的重要问题:
- 对于我们应用程序的用户来说,我们需要为他们特定的家庭获取大量的输入。我们如何设计一个用户界面,允许用户输入他们特定家庭的所有必要信息?
*回答:*在 Javascript/Typescript 中构建一个包含有角度的材质表单的用户界面,获取用户输入。然后,我们可以将这些信息从应用程序的前端发送到后端 PostgreSQL 数据库。这些数据的交换可以通过 Python 和 Flask 作为 API 端点来处理。
- 对于不知道房屋面积的用户,或者想要调查屋顶面板的不同面积计算,我们如何让他们进一步调查并确保他们给出准确的估计?
回答:我们可以利用 Google Maps API 在页面加载中显示特定地址的用户住宅的航拍面积,然后覆盖一个平方英尺绘图工具/计算器(内置于 JavaScript 中),以允许用户绘制/确定其住宅的正确平方英尺估算值(参见下面的最终产品截图示例)。
- 我们如何在部署环境中运行我们的太阳能计算器评估和脚本?
*答:*我们可以利用 Heroku dynos 和云计算在部署的环境中运行这些脚本,最终可以连接到我们的前端和后端环境。通过 Flask 端点触发这些脚本,dynos 就可以执行这些脚本并运行我们的函数。
- 我们如何帮助用户可视化他们的成本节约?
回答:由于我们的 UI 将在 Javascript/Typescript 中构建,我们可以使用 D3.js 库创建视觉效果。我们可以展示从我们 15 年的模拟中得出的估计值,并与奥尔巴尼地区坚持常规电网服务的成本进行比较,以显示潜在客户的房屋何时能够从最初的太阳能电池板安装投资中“收支平衡”。
应用程序是如何设计的
在确定了这些大问题的解决方案并制定了最终的项目目标后,我们决定开始构建。经过大约两个月的紧张开发,我们提出了一个可行的解决方案。这里有一个图表和它是如何工作的简要概述:
开发框架和基础设施|作者图片
使用的编程语言、云资源和库 —该应用程序拥有完全托管的前端和后端基础架构。应用程序的后端部分是用 Python 构建的,并利用 Flask 库来服务一系列连接到我们前端系统的 HTTP 端点。运行在 Flask API 上的 Python 脚本执行关键功能,将用户输入数据从前端连接到 PostgreSQL 数据库,并在用户导航应用程序时管理 POST 和 GET 请求。这个后端系统完全由 Heroku 托管和管理。前端基础设施是用 Typescript 和 JavaScript 编写的,并利用 Angular JavaScript 框架来设计组件的样式,管理表单中的用户输入,创建与 Google Maps API 的交互,并为我们的 D3.js 可视化和仪表板提供画布。
应用程序如何工作
在页面加载时,用户看到的初始页面是一个请求家庭地址的表单。该表单链接到 Google Maps API,并在用户开始输入时提供自动填充选项,这些选项在地理上是有界限的,并限制在我们定义的纽约奥尔巴尼中心周围 0.1 度的纬度和经度(纬度 42.6526 度,-经度 73.7562 度)。这是为了确保我们的应用程序能够提供准确的估计,因为我们正在利用奥尔巴尼地区的特定数据,包括我们的太阳辐射数据/计算以及能源使用信息。
当输入奥尔巴尼特定的地址时,应用程序将打开一个表单,允许用户输入用于我们模拟的其他重要因素,包括估计的房屋面积、房屋建造年份、居住在房屋中的家庭成员数量、屋顶方向,以及估计适合他们屋顶的太阳能电池阵列的大小。为了帮助这些输入(特别是屋顶方向和太阳能电池阵列大小),我们创建了一个交互式绘图工具,允许用户直接在他们屋顶的航拍图像上计算他们预期的太阳能电池板阵列的面积。因为居住在用户家中的家庭成员数量是可变的,所以我们也提供了一个输入字段来获取该信息。一旦在表单中识别并确认了所有输入,就会显示一个最终的交互式地图和绘图工具,要求用户识别他们的屋顶相对于南纬的方位,最终从用户那里收集方位角。
JavaScript 内置的自定义绘图工具,覆盖谷歌地图以测量用户屋顶的平方英尺|作者图片
在这些输入完成之后,然后向用户提供所有计算/输入信息的汇总表。此表为用户提供了最后一次机会来查看我们的模型计算中将使用的数字,并纠正任何看似错误的内容。
确认用户输入和收集数据的最终汇总表-一旦用户确认,这些字段将用于创建成本估算|按作者分类的图像
当用户确认这些值时,前三个数字用于计算与美国东北部地区的平均数字相比的家庭能源使用的预计比率。然后,该比率用于向用户提供估计的平均每月能源账单,用户可以调整该比率以定制模拟中使用的比率,并确保该比率更接近他们的能源使用。
估计的平均月账单是通过用户和/或作者的 Realtor.com |图片提供的上述住房信息计算出来的
当用户准备好了,并且平均每月能源账单估计似乎正确时,应用程序运行模拟并提供概述和总结结果的可视化。此外,该应用程序提供了四种不同的场景,根据我们的研究,将效率和初始投资的两个极端值配对。每一个可视化都提供了一种额外的方式来观察所讨论的太阳能电池板系统的预计性能。
D3.js 可视化和摘要
我们使用 D3.js 库创建,内置翻转功能、工具提示和其他数据可视化最佳实践,使用户可以访问这些指标。
可视化 1 内置于 D3.js |作者图片
可视化#1: 显示了如果您放弃一个太阳能电池板系统,您预计要支付的长期(15 年)费用,以及您对所选太阳能系统的现金支出的估计。两条线的交点是你开始省钱的时间点,与没有面板系统的情况相比。鼠标悬停事件和工具提示会显示相对于最初 15 年太阳能投资的预计成本。
D3.js 中内置的可视化 2 |作者图片
可视化#2: 显示了预计的太阳能财务前景和估算的无太阳能电力成本之间的差异。从亏损到盈利的转变发生在第一张图中两条线相交的那一年,而每个条形表示给定年份之后的财务增量。相对于如果你的整个能源系统依赖常规电网服务你将支付的费用,条形图从负值变为正值的那一年,是你预期达到盈亏平衡点的时候。鼠标悬停事件也可用于查看自太阳能安装以来每年的相关增量计算。
D3.js 中内置的可视化 3 |作者图片
可视化#3: 在这张特殊的图表中,我们提供了高效率和低效率系统的预计每月产量的快照。覆盖在其上的是一条线,显示估计的每月能源使用量,左轴显示千瓦时,右轴显示每月成本(基于纽约州奥尔巴尼市 11.74/千瓦时的价格)。如果需要的话,这里可以使用鼠标悬停事件来隐藏特定的效率,以便为用户提供更好的清晰度。
提供不同的面板和成本方案
虽然这些可视化本身很有趣,但我们也想为用户提供一些不同的选项。由于成本预测和“盈亏平衡”范围在很大程度上受电池板安装的初始成本以及安装的太阳能电池板的效率的影响,我们希望展示一系列可能发生的情况。通过在场景中循环,用户可以很好地了解模拟中的变化。为这些场景选择的范围(初始安装成本为 12,000 美元或 20,000 美元,电池板效率为 15%或 19%)基于我们的研究和当前太阳能市场的可接受标准。
可视化效果根据为初始面板安装成本和面板效率选择的方案动态更新|图片由作者提供
结论
最后,应用程序很好地组合在一起,但是我们还有很多工作要做。我们能够创建一个用户界面和应用程序,让用户调查转换到太阳能的好处,并展示随着时间的推移进行转换的财务收益。然而,最重要的是,该应用程序针对个人的家庭,为每个用户提供了更现实的时间表。我们很高兴地看到,当我们将我们的许多模拟计算与其他领先工具(如谷歌的项目天窗)进行比较时,我们的“盈亏平衡”估计正好在他们自己的预测范围内。
最终,我们希望将这个项目扩展到更大的地理区域。虽然该应用程序目前只适用于纽约州奥尔巴尼地区,但我们认为它有潜力扩展到更广泛的受众。如果任何人有兴趣在这个持续的项目上合作,请不要犹豫联系我们!
如果您想亲自试用该应用程序,您可以访问此处:
(警告:由于成本限制,自最初发布以来,我们不得不缩减云基础架构的规模。因此,您可能会经历更长的加载时间)。
如果你喜欢这篇文章,请随意查看我的其他帖子。此外,我创建了一个数据科学作品集,您可以在这里查看。我的投资组合也有我的联系方式,如果你想接触这个项目或任何其他人!
用 PyTorch 构建 GAN
凭空产生逼真的图像?
Goodfellow 等人在 2014 年提出的生成对抗网络(GANs)彻底改变了计算机视觉中图像生成的一个领域——没有人能相信这些令人惊叹和生动的图像实际上是纯粹由机器生成的。事实上,人们过去认为生成的任务是不可能的,并对甘的力量感到惊讶,因为传统上,根本没有我们可以比较我们生成的图像的地面真相。
本文介绍了创建 GAN 背后的简单直觉,然后通过 PyTorch 实现了一个卷积 GAN 及其训练过程。
甘背后的直觉
与传统分类不同,在传统分类中,我们的网络预测可以直接与真实的正确答案进行比较,生成图像的“正确性”很难定义和测量。Goodfellow 等人在他们的原始论文生成对抗网络,中提出了一个有趣的想法:使用一个训练非常好的分类器来区分生成的图像和实际图像。如果存在这样的分类器,我们可以创建并训练一个生成器网络,直到它能够输出可以完全欺骗分类器的图像。
图一。甘的管道。作者创建的图像。
GAN 是这一过程的产物:它包含一个基于给定数据集生成图像的生成器,以及一个用于区分图像是真实图像还是生成图像的鉴别器(分类器)。GAN 的详细流水线如图 1 所示。
损失函数
优化发生器和鉴别器都很困难,因为正如你可能想象的那样,这两个网络有着完全相反的目标:发生器希望创造尽可能真实的东西,但鉴别器希望区分产生的材料。
为了说明这一点,我们让 D(x) 是来自鉴别器的输出,这是 x 是真实图像的概率, G(z) 是我们的生成器的输出。鉴别器类似于二元分类器,因此鉴别器的目标是最大化功能:
本质上是没有负号的二元交叉熵损失。另一方面,生成器的目标是最小化鉴别器做出正确决定的机会,因此它的目标是最小化函数。因此,最终的损失函数将是两个分类器之间的极小最大博弈,可以如下所示:
这将在理论上收敛到以 0.5 的概率预测一切的鉴别器。
然而,在实践中,极大极小游戏通常会导致网络不收敛,因此仔细调整训练过程是很重要的。学习率等超参数在训练 GAN 时更为重要——微小的变化都可能导致 GAN 产生单一输出,而不管输入噪声如何。
计算环境
图书馆
整个程序是通过 PyTorch 库(包括 torchvision)构建的。GAN 生成结果的可视化使用 Matplotlib 库绘制。以下代码导入所有库:
资料组
数据集是训练 gan 时的一个重要方面。图像的非结构化本质意味着任何给定的类别(即,狗、猫或手写数字)可以具有可能数据的分布,并且这种分布最终是 GAN 生成的内容的基础。
为了进行演示,本文将使用最简单的 MNIST 数据集,它包含了 60000 个从 0 到 9 的手写数字图像。像 MNIST 这样的非结构化数据集实际上可以在 Graviti 上找到。这是一家年轻的初创公司,希望用非结构化数据集帮助社区,他们在自己的平台上有一些最好的公共非结构化数据集,包括 MNIST。
硬件要求
最好在 GPU 上训练神经网络,因为它们可以显著提高训练速度。但是,如果只有 CPU 可用,您仍然可以测试程序。要让您的程序自己决定硬件,只需使用以下代码:
履行
网络体系结构
由于数字的简单性,两种架构——鉴别器和发生器——由完全连接的层构成。请注意,有时全连接 GAN 比 DCGAN 更容易收敛。
以下是两种架构的 PyTorch 实现:
培养
在训练 GAN 时,我们优化了鉴别器的结果,同时改进了我们的生成器。因此,在每次迭代过程中会有两个相互矛盾的损失来同时优化它们。我们输入到生成器中的是随机噪声,生成器应该根据给定噪声的细微差异来创建图像:
结果
经过 100 个时期后,我们可以绘制数据集,并查看从随机噪声中生成的数字的结果:
图二。GAN 生成的结果。作者创建的图像。
如上所示,生成的结果看起来确实很像真实的结果。考虑到网络相当简单,结果确实看起来很有希望!
不仅仅是内容创作
甘的创作与之前在计算机视觉领域的工作大相径庭。随后出现的大量应用让学术界惊讶于深度网络的能力。一些惊人的工作描述如下。
CycleGAN
Zhu 等人的 CycleGAN 引入了一个概念,该概念将图像从域 X 转换到域 Y 而不需要成对样本。马变成了斑马,夏天的阳光变成了暴风雪,CycleGAN 的结果令人惊讶而准确。
图 3。朱等人展示的 CycleGAN 结果图片来自其 Github 页面。
高根
Nvidia 利用 GAN 的力量,根据画笔的语义,将简单的绘画转换为优雅而逼真的照片。尽管训练资源在计算上很昂贵,但它创造了一个全新的研究和应用领域。
图 3。高干的生成结果。左为原图,右为生成结果。作者创建的图像。
AdvGAN
GANs 还被扩展到清理对立的图像,并将它们转换成不会欺骗分类的干净的例子。更多关于对抗性攻击和防御的信息可以在这里找到。
结论
所以你有它!希望这篇文章提供了如何自己构建 GAN 的概述。完整的实现可以在下面的 Github 资源库中找到:
https://github.com/ttchengab/MnistGAN
感谢您坚持到现在🙏!我会在计算机视觉/深度学习的不同领域发布更多内容。一定要看看我关于计算机视觉方法的其他文章!
在 AWS 上的 Dash 中构建基于图的杂货推荐系统
照片由 PhotoMIX 公司从 Pexels 拍摄
为了准备最近在一家大型零售商的工作面试,我想接触一些零售数据,同时专注于回答一个现实生活中的商业问题——我们如何才能建立更有利可图的杂货篮。在最近熟悉了 Python 中的 NetworkX 包之后,我看到了一个极好的机会来构建一些视觉上吸引人的和交互式的东西来帮助回答这个问题。这篇文章概述了:
1)阐述商业案例
2)在 NetworkX 中构建图形
3)提出基于图表的建议
4)在 Dash 中创建一个应用程序,并将其部署到 AWS
所有的代码都在我的 GitHub 上。
阐述商业案例
我们怎样才能让购物者在我们店多消费,在其他店少消费?我从这个问题开始,并认为通过可视化数据集中篮子中产品之间的关系,人们可能很容易找到一些产品,这些产品本质上是将其他产品“粘合”在一起的。然后我们可以推测,这些将篮子粘在一起的产品——这些锚定产品——让顾客进入商店,如果我们降低这些商品的价格,我们可以让顾客来我们的商店而不是其他商店。但是为了可持续发展,我们需要在其他地方弥补不足。因此,看一下篮子,我们可以提高不太重要的商品的价格/利润,这些商品不会让顾客进门,但仍然会落在篮子里。最近我发现了 python 中的 NetworkX 包,我认为将食品杂货显示为网络中的节点是一种很好的方式,可以直观地表示篮子中物品之间的关系。
我开始寻找一个数据集,并在 Kaggle 上找到了这个优秀的数据集(随后,我还在 Dunnhumby 上找到了一个优秀的数据集——名为“完整的旅程”——我仍希望能深入其中)。
我认为,观察购买小篮子(底部四分位数,少于 5 件商品)的客户和购买中等尺寸篮子(第二个四分位数,5 至 9 件商品)的客户的图形结构之间的差异也会很有趣。
在网络中构建图形 X
如果我没有首先提到将这些数据显示为图表并不一定是找到主打产品的最佳方式,那将是我的失职。还有其他方法,例如在购物篮分析中使用的 apriori 算法,但是,我真的很想尝试最初从图形可视化中构建洞察力,以及在以后使用图形的结构来构建建议,所以使用了这种方法。另一件要注意的事情是,虽然从逻辑上看,人们也可以只查看哪 20 种产品被购买最多,并将其视为锚定产品,但也有可能有一些产品位于前 1000 名,但不是前 20 名,它们仍然是将许多其他产品粘合在一起的产品。如果你是南非人,篮子里的主打商品可能是你的 braai(烧烤)的 boerewors(一种香肠),但是这种商品不太可能是你的商店最畅销的商品之一,但了解这种杂货商品如何与其他商品结合在一起仍然是有价值的。
NetworkX 的使用非常简单。只要对数据做一点预处理,篮子项目就被视为在同一个篮子中的节点之间创建了边的节点。然后,根据两个项目一起出现在一个篮子中的次数对边进行加权。为了给出一个具体的例子,下图显示了项目的子图,这些子图表示在数据集中带有杏仁的篮子中找到的所有项目,以及这些项目之间的联系。
杏仁出现的杂货子图;作者图片
下面的代码显示了如何构建节点—边—节点对,然后将它们传递给 NetworkX 以创建网络:
瞧,我们有了一个图表网络。最棒的是,我们现在可以看到网络中节点之间的连接。我在这里省略了代码,因为它有点长,但是看一看 GitHub 页面上的 Jupyter 笔记本,就知道如何将这些代码组合在一起了。这里有如此多的数据,以至于我认为这是一个使用 Plotly 构建交互式绘图的理想机会。
小篮子的杂货图表的图像;作者图片
探究这张图表,我们可以看到一些东西。首先,这里有几个非常重要的项目(颜色较深的项目)似乎与大多数其他项目有联系。这些是香蕉和 T2 草莓。现在,回到最初的业务问题,“我们可以用这些信息做什么?”,这表明我们可以考虑降低这些商品的价格,以吸引更多的顾客。现在,如果我们看一下图表“云”之外的商品,例如 l 大鸡蛋,这些是我们可以提高价格(并增加利润)的商品,因为这些商品不太可能是顾客专门来我们商店购买的,但仍然落在许多篮子里。
这里值得注意的是,我期望在网络图中看到不同的聚类模式。我想象会有多个集群,或者网络中的高密度区域,其中某些种类的产品相互连接;然而,事实并非如此。虽然这在一定程度上削弱了以图形网络格式直观探索食品杂货的价值,但构建图形仍然允许人们使用它来创建推荐系统。
构建基于图的推荐系统
对于那些不熟悉推荐系统的人,请查看维基百科页面。虽然诸如协同过滤之类的方法对影响我们许多选择的大量推荐负责,例如网飞推荐,但是基于图的推荐系统也已经在许多应用中使用。在本文中,基于图的推荐器仅仅关注于推荐相似的产品,而没有考虑客户以前购买过什么。对于一篇关于二分图推荐系统(即用户和产品)的优秀文章,请查看本文。
我采用了两种方法来构建推荐系统。第一种方法是最明显的,由此推荐系统推荐一个项目的前 K 个邻居,按照当前项目的邻居的边权重排序。第二种方法更有趣,也以稍微不同的方式看待推荐。
第二种方法为图中的每个节点生成一个图嵌入,其中每个节点的嵌入类似于创建词向量时创建的嵌入(如果您不熟悉词向量,请查看这篇精彩的文章)。事实上,用于创建这些嵌入的方法之一使用与创建单词嵌入完全相同的过程来创建节点嵌入。本文中介绍的方法称为深度行走。我是在阅读了这篇关于主题的精彩综述后构建的,这篇综述比我在这里要详细得多。基本上,节点嵌入是通过在网络中选择一个节点,然后从该节点开始进行 k 步随机行走,并记录在网络中访问的每个其他节点来创建的。这 k+1 个节点的选择然后被存储为一个字符串(k+1,因为我们在字符串中包括第一个节点)。在购物清单示例中,假设您从“杏仁”节点开始,那么您可以通过从该节点进行随机遍历来制作一串如下所示的节点:
杏仁->玉米片->初榨橄榄油->香蕉->苹果->柠檬
您将对网络中的每个节点执行此操作 n 次,采取 k 个步骤,并存储所有这些长度为 k+1 的“字符串”,这些“字符串”组成了流程中访问的节点。一旦你有了这些节点串的大小为 k x n 的集合,你就可以使用一个漂亮的包比如 Gensim 来学习这些节点的 Word2Vec 嵌入,而 viola,你已经为你的图形创建了嵌入。下面是这方面的代码,但请记住,这是大量借用自这篇伟大的文章,你应该看看。
现在,从上面的代码中可以看出,查找相似条目的方法很简单,只需使用 Word2Vec 方法。最 _ 相似()。
然而,在比较这里给出的两种推荐方法时,需要考虑一些事情。“最相似”嵌入方法的巧妙之处在于,每个节点的表示捕获节点的上下文信息的方式与 Word2Vec 捕获单词的上下文信息的方式相同。然而,当我们购买鸡汤时,我们可能会对人们用鸡汤购买的其他商品比对上下文相似的商品更感兴趣。当使用这两种方法时,我们最终会得到如下建议:
**与杏仁最相似的东西:**可乐罐、开心果、葡萄干、芒果干、经典烘焙咖啡、各种减肥茶、盐&胡椒开心果、爆米花
**杏仁的近邻:**苏打水,香蕉,什锦沙拉,开心果,覆盆子,柠檬,蓝莓,草莓,无籽黄瓜,格兰诺拉麦片
那么,哪个更好呢?很难说。最近的邻居更一般。香蕉、覆盆子和草莓放在很多篮子里,因此,在这种情况下,一些推荐可能不会比推荐顾客无论如何都会购买的普通产品更有价值。最相似嵌入法表明推荐项目与主项目扮演相似的角色(通过它们的上下文嵌入),因此可能互补性较弱,可能更容易互换。
尽管这很有趣,也很好玩,但这表明,为了真正增加价值,基于与其他客户的相似性向客户推荐商品可能是值得的。我看的数据集没有客户层面的数据,但如果你看看这篇文章(如上所述),它提供了一个同时使用客户和商品数据进行推荐的绝佳例子。
创建互动 Dash 应用
虽然 Dash 在许多方面与 R-Shiny 非常相似,但这种差异导致了一个相当尖锐的学习曲线。在用 Flask、JS 和 HTML 开发了一两个小东西之后,我发现 Dash 利用 CSS 的一些方式很难理解——例如,用 CSS 类“六列”代替“列-6 ”,并花了一些时间来理解这一点。虽然我不会在这里对 Dash 应用程序进行过多的详细描述——你可以在我的 GitHub 账户中查看代码——但我会给出一个简单的概述。
Dash 应用的购物清单生成器部分;作者图片
由于 Plotly 和 Dash 合作得非常好,我决定将这个项目转化为一个交互式应用程序,并部署到 AWS。我在 Medium 上找到了这篇精彩的文章,它带我完成了在 AWS 上将一个应用程序上传到 Elastic Beanstalk 的基本步骤,并让我的应用程序毫不费力地运行起来。我不会重述那里写的内容,因为它做得很好,没有必要,但是按照那篇文章,几分钟后你就可以开始了。
关于构建应用程序的过程,需要考虑几件事情。Dash 有一个回调系统,允许您的函数改变页面上的 Dash 对象。尝试用大量动态创建的移动部件做一些事情可能是一个相当大的挑战,尽管一旦你掌握了它,你可以做很多事情。学习曲线很陡,但我认为对于未来的仪表板来说这是值得的。为了防止应用程序在每次加载选项卡或下拉列表改变时都必须重新生成图表,我将图表数据保存为 pickle 文件,对于推荐者选项卡,在页面加载时加载它。这可能不是最佳实践,但它完成了工作。我也不太可能为这个小小的网络应用赢得任何 UX/用户界面奖,但这个想法更多的是建立一些东西和探索一些数据,而不是制作一些看起来完美的东西。
包裹
这个项目确实帮助我开始尝试首先回答一个商业问题,即“我们如何让顾客在我们的商店购买更多的商品”。通过找到一种可视化探索数据的新方法,我能够推翻我最初关于商品聚集在一起的假设——看起来更像是一个像云一样的杂货网络。然而,这让我开始探索使用图表来为产品提供建议,并最终构建了一个基本的 Dash 应用程序来在线探索数据。总的来说,这是一次极好的学习经历,让我有了许多改进的想法:
- 我们可以构建一个包含客户和订单的图表,根据与其他客户的相似性向客户推荐产品
- 还有其他方法来创建图嵌入,可以进行探索和比较
- 将财务(价格和成本)数据添加到分析中,对于了解哪些产品可以涨价以及对整体篮子价值的影响是有价值的
用 Python 构建混合假新闻侦探应用
使用无监督 K 均值和有监督支持向量分类器算法的组合
当今世界,网络上到处都是新闻。我们可以在 CNN、BBC News、India Today、NDTV 和 Times of India 等各种新闻网站以及脸书、Twitter 和 Instagram 等社交媒体网站上轻松找到它们,只需轻点鼠标。新闻的目的是让我们了解身边正在发生的事情。
路易斯·科尔特斯在 Unsplash 上的照片
不幸的是,我们今天听到的消息的可信度值得怀疑。有人很容易在社交媒体网站上发布随机的东西,而不验证它是否真实。一些人,特别是政党成员,也可能在社交媒体网站上组建团体,歪曲新闻,恶意传播宣传,误导人们,转移对实际问题的注意力。尽管如此,许多主流新闻频道和报纸还是带着选择性的偏见进行报道,传播针对个人和社区的仇恨、阴谋论,其唯一目的是获得更多的电视收视率或简称为 TRP。也就是说,民主的第四大支柱正在瓦解。
照片由 Nijwam Swargiary 在 Unsplash 拍摄
新闻来源的可信度在新冠肺炎疫情期间创下新低。这一时期出现了各种错误信息运动,特别是在美国,劝说人们不要遵循 COVID 适当的行为和接种疫苗。因此,在相信这个消息之前,核实事实是必要的。
于是出现了各种事实核查组织,如 Alt News、BOOM 和 Factly,它们核查一篇新闻文章的可靠性。这些组织通过与目击者或有关当局核实,对新闻文章进行人工分类。但是,在许多情况下,他们可以认为反对他们意识形态的新闻文章是假的。所以,这些事实核查网站的可靠性也值得怀疑。因此,我们需要的是一种对新闻标题进行分类的 AI 方式。这在自然语言处理中是可能的。
为什么要混合模式?
现在的问题是,我们应该使用哪种方法。一般情况下,我们使用监督学习算法对新闻进行分类。这种方法有两个问题:
- 首先,通常可用的数据集都有新闻标题,这些标题被一些事实核查机构手动标记为真实或虚假。我们永远不能确定这个组织是公正的。
- 第二,监督学习算法需要这些标签进行训练。因此,人们最终偏向了分类器,违背了使用人工智能的初衷。
我们必须明白,在极少数情况下,消息肯定是真的或假的。大多数情况下,我们无法确定。所以,我们应该寻找的是,新闻是真是假的概率。
我们将使用的模型是由非监督 K 均值聚类算法和监督支持向量分类算法混合而成的。K Means 算法通过捕捉特定单词的用法将新闻标题组织成簇。支持向量算法从这些聚类中学习,并预测未知新闻标题所属的类别。
如果你想了解我的仪表盘是什么样子,请访问 https://fake-news-headlines-detective.herokuapp.com/。
数据来源:
除了我在上一节提到的原因,通常可用的数据集包含来自美国的新闻。因此,我必须自己创建一个数据集,通过收集热门新闻网站的新闻标题进行训练,如《印度时报》、《新闻 18》、《印度快报》和《共和世界》。此外,我还从 Op India、News Punch 和 Great Game India 等纯数字新闻网站收集了这些信息,这些网站被国际事实核查网络(IFCN)视为假新闻网站。
为了训练,我从印度和美国的各种来源收集了 14787 个标题。GitHub 存储库中有这个数据集。同样的链接是https://github.com/pradeepprajan/Fake-News-Detective。您还可以找到包含 Python 代码的 Jupyter 笔记本。
安装必要的软件包:
除了常用的软件包之外,我们还需要安装以下特定于 dashboard 应用程序的软件包:
- 自然语言工具包(NLTK)
- 海生的
- Scikit 学习
- Plotly
- 破折号
- 仪表板引导组件
第 1 部分:用 K-Means 对标题进行聚类
导入必要的库
在开始之前,我们导入必要的库。可以参考我分享过的 GitHub 库中的 Jupyter 笔记本。
数据的读取和预处理:
首先,我们读取收集的训练数据集。因为数据集中的行是按照新闻源排序的,所以我们对它们进行了洗牌。
dataset = pd.read_csv('NewsArticles.csv',encoding='unicode escape')
dataset = dataset.drop(columns=['Unnamed: 0'])
dataset_copy = dataset.copy()
dataset_copy = dataset_copy.sample(frac=1).reset_index(drop=**True**)
一些探索性的数据分析:
我们找到每个来源发布的新闻文章的数量,并构建一个条形图。
news_articles_count = dataset_copy.groupby('Source_code',as_index=**False**).count()
news_articles_count = news_articles_count.rename(columns={'Headlines' : 'Article_Count'})
news_articles_count = news_articles_count.drop(columns='Sources')news_articles_bar_plot = sns.barplot(x=news_articles_count['Source_code'],y=news_articles_count['Article_Count'])
作者图片
创建单词语料库:
现在,我们创建一个单词语料库。在此过程中,我们执行以下操作:
- 删除特殊字符,因为它们对我们的模型没什么用。
- 将大写字母更改为小写字母,因为我们希望我们的模型将大写和小写的同一个单词识别为一个单词。比如新闻,新闻,新闻,要被算法识别为一个词。
- 将标题符号化,即将标题拆分成单词。
- 将单词词条化。它将单词转换成有意义的基本形式。
在创建语料库之前,我们安装了 S topwords 、 **Punkt、**和 Wordnet 字典。
lemmatizer = WordNetLemmatizer()
nltk.download('stopwords')
nltk.download('punkt')
nltk.download('wordnet')corpus = []
**for** i **in** range(0,len(dataset_copy)):
message = re.sub('[^a-zA-Z0-9]', ' ', dataset_copy['Headlines'][i])
message = message.lower()
message = word_tokenize(message)
message = [lemmatizer.lemmatize(w) **for** w **in** message **if** **not** w **in** stopwords.words('english')]
message = ' '.join(message)
corpus.append(message)
print(corpus)
将单词库转换成向量:
我们通过 TF-IDF(词频-逆文档频)矢量化将词语料库中的标题转化为向量。此外,我们将每个向量的维数限制为 1000。
tfidf_v = TfidfVectorizer(max_features=1000)
X = tfidf_v.fit_transform(corpus).toarray()
特征缩放:
在执行任何非监督分类之前,特征缩放是必须的。
scaler = StandardScaler()
X = scaler.fit_transform(X)
应用 PCA:
因为形成的每个向量的维数是 1000,所以我们应用 PCA。这消除了维数灾难。
pca = PCA(n_components=2)
pca_result = pca.fit_transform(X)
K 均值聚类:
在执行聚类之前,我们需要使用肘方法找出所需的聚类数。
wcss = []
**for** i **in** range(1,20):
kmeans = cluster.KMeans(n_clusters=i,init='k- means++',max_iter=300,n_init=10,random_state=0)
kmeans.fit(pca_result)
wcss.append(kmeans.inertia_)
print("Cluster", i, "Intertia", kmeans.inertia_)
plt.plot(range(1,20),wcss)
plt.title('The Elbow Curve')
plt.xlabel('Number of clusters')
plt.ylabel('WCSS')
plt.show()
作者图片
对应于图中“肘部”的聚类数为 6。因此,我们需要 6 个集群。
现在,我们执行聚类。我们还绘制了一个散点图来证明这一点。
Kmeans = cluster.KMeans(n_clusters=6,init='k-means++',max_iter=500,verbose=**True**,random_state=0)
clustered = Kmeans.fit_predict(pca_result)
PCA_df = pd.DataFrame(pca_result)
PCA_df['cluster'] = clustered
PCA_df.columns = ['x1','x2','cluster']
k_means_figure = sns.scatterplot(data=PCA_df,x='x1',y='x2',hue='cluster',legend="full",alpha=0.5)
作者图片
不得不承认聚类并不完美。但是我们必须明白,数据科学就是从获得的结果中推导出结论。因为我们可以从这种聚类中得出新闻的可信度,所以我们可以自信地说 K-Means 聚类对于可用的数据集工作得很好。结论在文末。
保存模型:
在 Pickle 的帮助下,我们保存模型以备将来使用。
Kmeans = Kmeans.fit(pca_result)
filename = 'news_classifier_KMeans2.sav'
pickle.dump(Kmeans, open(filename, 'wb'))
第二部分:预测那些看不见的标题群
监督培训
我们首先创建一个 pandas 数据框,其中包含用于训练的标题和由 K-Means 算法分配的聚类(在“预测”列下)。
现在,我们使用监督分类算法,如逻辑回归、K-最近邻、和支持向量分类器,对用于“预测”列聚类的相同新闻标题进行训练,然后选择具有最高准确度的算法。相同的代码可以在我的 GitHub 库中找到。结果总结如下:
从表中我们发现支持向量分类器和朴素贝叶斯的准确率最高,为 99.93 %。在这里,我们用前者来训练。
SVC_classifier = SVC(C=300,kernel='linear',random_state=0)
SVC_classifier.fit(pca_result,y_train)# y_train is the 'Predictions' column of the Data Frame used for supervised training.
预测未知标题的聚类:
这是包含未显示标题的表格。我通过使用 Newsdata.io 提供的 API 键获得了它们,我们称之为测试数据集。
为了获得准确的结果,我们结合测试和训练数据集,并重复预处理步骤。然后,我们将生成的 NumPy 数组拆分回训练和测试数据集的数组。pca_result_test 是测试数据集对应的 NumPy 数组。
现在,我们预测看不见的标题所属的群集:
clustered_test = SVC_classifier.predict(pca_result_test)
我们再次创建一个熊猫数据框,其中包含看不见的标题和预测的聚类。
然后,我们将训练和测试数据集的分类编号映射到类别编号 1 到 6,以使分类更加直观。
我们最终保存两个数据帧。我们将在创建仪表板应用程序时使用它们。
第 3 部分:构建仪表板应用程序并将其部署在 Heroku 上
最后,我们使用 Python 的 Dash 和 Plotly 包构建了一个交互式 web dashboard 应用程序,并将其部署在 Heroku 上。Heroku 是一个云平台即服务,开发者可以免费部署他们的应用程序。
推论和结论:
阅读完本文后,您可能想知道 K-Means 聚类在这种情况下是如何工作的。原因是 K-Means 分类器通过识别某些词的用法将标题组织成簇。TF-IDF 分配给每个单词的值与该单词在标题中出现的次数成正比,与包含该单词的标题的数量成反比。可信度存疑的新闻标题数量较多,拉低了频繁词的 TF-IDF 值。因此,这些标题的数据点更接近 K 均值散点图中的原点。另一方面,可信的新闻标题数量较少,因此频繁出现的词具有较高的 TF-IDF 值。因此,数据点远离散点图中的原点。
类别描述如下:
类别 1:
新闻主要是关于燃料、黄金和白银价格。新闻是假的可能性微乎其微。类别 1 下的标题数量是 53。常用词有— 燃油、汽油、翘尾、黄金、白银、和暴跌。
类别 2:
新闻是假的几率稍微高一点。头条大多是关于经济、股市、铁路、航空、汽车、智能手机等。该类别下的标题数量为 798 个。常用的词有— 市场、俏皮、银行、三星、铁路、Flipkart、和制造。
类别 3:
新闻有中等概率是假的。新闻标题大多是关于健康(大多与 COVID 相关)、经济、基础设施和技术的。一些政治新闻标题也出现在这个集群中。这个类别有 4089 个标题。常用的词有比特币、锁定、COVID、疫苗、限制、解锁、项目和销售。
类别 4:
发现虚假和被操纵的新闻的概率很高。新闻标题大多与印度和世界的政治以及疫情有关。今天印度国内外主流媒体发表的大部分新闻文章都属于这一类。这一类别还包含说服人们不要服用 COVID 疫苗的宣传和谣言。但是我们也可以在这里找到一些体育和名人新闻。总共有 7018 条新闻标题属于这一类。常用词有— COVID、疫苗、洗手液、男演员、女演员、网球、火柴、莫迪、拜登、流氓、敌对、列宁主义、愚蠢、和叛乱。
类别 5:
找到有偏见的、被操纵的和虚假的新闻的概率非常高。这一类包含了很多宣传。这个分类下有 2800 个标题。常用词有— 莫迪、拉胡尔·甘地、国会、BJP、自由派、左派、穆斯林、印度教、暴动、政府、巴基斯坦、邪教、骗局、谋杀、和中国。
类别 6:
这是一个异常类别。这个类别有 29 个标题。所有的标题都来自 Alt-Market,这是一家以发布假新闻而闻名的新闻媒体。这是他们下一期时事通讯的发布通知。因此,我们可以忽略它们。
下面是显示每个类别下的文章数量的条形图。
作者图片
分类 1 到 3 下的训练数据集中标题的数量总共是 4940,与其他分类下的 9847 相比要少。相应的百分比分别为 33%和 67%。
那么,我们能从中得出什么结论呢?只有 1、2、3 类下的新闻标题才值得关注。其他标题下的新闻标题,除了体育标题,一般都是有毒的和固执己见的,更有可能在 WhatsApp 上转发或发布在脸书和推特上。因此,我们必须半信半疑地对待它们,或者忽略它们。