TowardsDataScience-博客中文翻译-2021-三十五-
TowardsDataScience 博客中文翻译 2021(三十五)
5 分钟内完成端到端计算机视觉管道
如何使用 TorchVision 在不到 5 分钟的时间内编写一个完整的计算机视觉管道
编写一个完整而快速的计算机视觉管道,用于产品原型制作或作为构建更复杂系统的构件,正变得越来越重要。在这里,我们将讨论如何使用流行的 TorchVision 库在 5 分钟或更短的时间内做到这一点。

里卡多·安南达尔在 Unsplash 上拍摄的照片
目录
- 做好准备
- 数据集准备
- 模型构建
- 模型评估
- 结论
做好准备
首先,您应该安装 PyTorch 和 TorchVision 库。建议您在康达虚拟环境中进行。
- 进入 PyTorch 安装页面,将写好的命令行复制粘贴到您的终端,例如:
conda install pytorch torchvision torchaudio -c pytorch
2.通过在终端中运行以下命令,确保安装已经完成。如果没有输出,那么你已经完成了这一部分!
python -c "import torch"
python -c "import torchvision"
数据集准备
对于本教程,我们将使用一个更受欢迎的数据集 MNIST (从 0 到 9 的数字图像集合)。

tensorflow.org MNIST 数据集(资料来源)
首先,我们将对数据集进行转换,以便它们能够很好地适应我们即将推出的 AlexNet 模型(即将输入图像调整为 224,将MNIST 图像转换为张量格式)。
import torch
from torchvision import datasets, transforms, models
from torch.autograd import Variabletransform = transforms.Compose(
[
transforms.Resize(224),
transforms.ToTensor(),
])
然后,我们通过传递上面的 transform 对象来下载训练集和测试集。
mnist_trainset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)mnist_testset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
其中根表示下载和存储数据的目录。
通过找出数据集的大小来检查您的下载是否完成。
len(mnist_trainset)
>>> 60000 len(mnist_testset)
>>> 10000
然后,我们需要创建用于批处理目的的 dataloader。
train_loader = torch.utils.data.DataLoader(mnist_trainset,
batch_size=4,
shuffle=True)test_loader = torch.utils.data.DataLoader(mnist_testset,
batch_size=4,
shuffle=True)
模型构建
初始化模型也很容易。为了便于讨论,我们将使用带有预训练权重的 AlexNet (您可以选择使用随机权重初始化模型,并使用上述基准数据集或使用您自己的预定义数据集重新训练它)。你可以在这里找到火炬视觉支持的模型集合。
alexnet = models.alexnet(pretrained= True)
在那里您可以通过运行
alexnet

AlexNet 模型摘要
上面的第一行表示(功能)块中的 Conv2d 层的输入通道大小为 3 (RGB),而我们的 MNIST 数据集只有一个颜色通道:
alexnet.features[0] = torch.nn.Conv2d(**1**,64, kernel_size=(11,11), stride=(4,4), padding=(2,2))
进一步的修改也可以像这样容易地完成!
模型评估
现在,我们准备训练我们的模型。
- 定义损失函数和优化器
对于这种情况,我们分别使用交叉熵和随机梯度下降(SGD)。
criterion=torch.nn.CrossEntropyLoss()
optimizer=torch.optim.SGD(alexnet.parameters(),lr=0.01,momentum=0.9)
2.训练和验证
print ("Training begins...")
num_of_epochs = 3for epoch in range(num_of_epochs):
running_loss=0
for i, data in enumerate(train_loader, 0):
image, label = data
image = Variable(image)
label = Variable(label)
optimizer.zero_grad()
outputs = alexnet(image)
loss = criterion(outputs, label)
loss.backward()
optimizer.step()
running_loss += loss.data
if i%100==99:
print (f'[epoch {epoch + 1}, imgs {i + 1}] loss: {running_loss/100}')
running_loss = 0print("Training finishes...")
上述训练和验证子程序是标准的(即涉及 zero_grad()、backward()和 step()函数)。当然,你可以修改超参数,包括训练时期,学习率等,以达到最优化。
3.推论
与训练类似,我们可以通过以下方式进行推断:
alexnet.eval()
correct=0
total=0for data in test_loader:
images, labels = data
outputs = alexnet(Variable(images))
_ , predicted = torch.max(outputs, 1)
total += labels.size(0)
结论
您刚刚构建了一个端到端的 CV 管道,从数据获取、转换、模型构建、培训、验证到推理,全部在五分钟之内完成!我还演示了如何轻松地修改您的模型以适应您自己的数据集。可以在整个模型中进行类似的调整。
做订阅我的邮件简讯:https://tinyurl.com/2npw2fnz在那里我定期用通俗易懂的语言和漂亮的可视化方式总结 AI 研究论文。
端到端数据块-Horovod 用例

当开始一个大的机器学习项目时,有许多平台可供选择。本文将给出在数据块上使用 Tensorflow & Horovod 训练神经网络的指南。
背景
在 SureWx (发音为“确定的天气”),我们已经为航空应用开发了定制的天气预报。这符合我们为航空公司提供的从登机口到起飞的数据驱动产品的 T4 套件。在冬季,操作决策变得复杂,因为降水要求在飞机离开地面之前采取额外的清洁和安全措施。更准确的天气预报有助于确保飞机按时起飞。这就是为什么我们一直在使用来自全球传感器网络的数据结合雷达数据来训练神经网络,以预测短期天气。这篇文章包含了我们在这个过程中学到的与模型的架构和训练相关的最重要的发现。
1.将数据保存在 TFRecord 批中
数据从您的 S3 存储桶中读取,该存储桶安装在 spark 集群上的/dbfs 中。只要您的 EC2 实例与您的 S3 时段在同一个区域,您只需为到 S3 的 GET 请求付费(但不包括数据传输)。如果您的数据没有进行批处理,GET 请求会很快增加。
无阴影示例:
0.0004 美元/ 1000 个请求* 200,000 个文件/时期* 1000 个时期= 80 美元
每个模型 80 美元的 S3 成本是一笔巨大的意外成本,在某些情况下甚至可能超过 GPU 成本。如果您已经为定型生成了 tf.data.dataset,则可以通过将数据写入 TFRecords 来轻松地对数据进行预批处理。您可以在这里找到编写和解析 TFRecords 的指南:
https://www.tensorflow.org/tutorials/load_data/tfrecord#writing_a_tfrecord_file
2。解析时交错
当您进行解析时,确保使用并行化的交错来获得最佳性能。此外,为了在添加更多节点时有效地扩大培训规模,tf.data.Dataset 需要在解析文件之前应用分片。这样,每个碎片只将一小部分文件读入内存。
import glob
import tensorflow as tfdef parse_fn(serialized): features = \
{
'radar': tf.io.FixedLenFeature([], tf.string),
'stationd_data': tf.io.FixedLenFeature([], tf.string),
'label': tf.io.FixedLenFeature([], tf.string)
} # Parse the serialized data so we get a dict with our data.
parsed_example = tf.io.parse_single_example(serialized=serialized, features=features) # Get the image as raw bytes.
radar_raw = parsed_example['radar']
station_data_raw = parsed_example['station_data']
label_raw = parsed_example['label'] # Decode the raw bytes so it becomes a tensor with type.
radar = tf.io.decode_raw(radar_raw, tf.float32)
radar = tf.reshape(radar, radar_shape) station_data = tf.io.decode_raw(station_data_raw, tf.float64)
station_data = tf.reshape(station_data, station_data_shape) label = tf.io.decode_raw(label_raw, tf.float32)
label = tf.reshape(label, label_shape) return (radar, station_data), labelfiles = glob.glob(f'{data_path}/*.tfrecord')files_ds = tf.data.Dataset.from_tensor_slices(files)
files_ds = files_ds.shard(self.shards, shard_index)ds = files_ds.interleave(lambda x: tf.data.TFRecordDataset(x)).map(lambda x: parse_fn(x), num_parallel_calls = tf.data.experimental.AUTOTUNE)
3.选择 S3 路径来优化 S3 的分片
批量保存数据的另一个原因是为了避免 S3 节流和网络错误。在 S3,每个前缀每秒钟最多只能有 5500 个请求。这可能看起来很多——但是如果你同时训练许多模型,你可以达到这个极限。用优化 S3 分片的文件路径保存预先批处理的 TFRecords 是一种很好的做法。请参见下面的指南。
https://aws.amazon.com/premiumsupport/knowledge-center/s3-object-key-naming-pattern/
4.错误处理
训练可能会因为各种原因而中断:错误的数据、训练中的不稳定性、回调中的错误、点实例被取走…为了不浪费资源,您希望您的模型进行检查点检查和恢复。
keras.callbacks.ModelCheckpoint(f'{checkpoint_path}_{time}', save_weights_only = False, monitor='val_loss', mode='min', save_best_only=True))keras.callbacks.ModelCheckpoint(checkpoint_path, save_weights_only = False, save_best_only=False, save_freq='epoch', period=3))
第一次回调是保存培训课程中产生的最佳模型。第二个回调是每隔几个时期保存一次模型,如果训练中断,可以启动一个新的作业并加载模型。如果没有这两次复试,你会丢掉自上一个最佳模特以来的所有训练。两次回调很容易,何必浪费 GPU 时间?
5.多输入混合数据
处理混合数据(例如:图像和时间序列数据)的 ETL 可能很难设计。在我们的案例中,我们将雷达图像序列与来自传感器的时间序列数据相结合。我们使用 Spark 构建数据框架,将我们的图像与传感器数据对齐。例如,假设我们想要将图像与最近 30 分钟的传感器数据进行匹配。我们对时间序列数据应用窗口函数,将所有时间序列数据保存到单个字节数组中。
import pandas as pd
import numpy as np
from pyspark.sql.types import BinaryType
from pyspark.sql.functions import pandas_udfdf # sensor time series data. # Stack the sensor data columns together and serialize
[@pandas_udf](http://twitter.com/pandas_udf)(BinaryType())
def serialize_udf(s1: pd.Series, s2: pd.Series) -> bytearray:
"""Takes multiple pandas series, stacks them, and converts the resulting array to bytes"""
data = np.dstack((s1.to_numpy(), s2.to_numpy()))
data = data.astype(np.double)
data = data[0,:,:]return data.tobytes()w = Window.orderBy(f.col("time").cast('long')).rangeBetween(-30*60, 0)df = df.withColumn('sensor_data', serialize_udf(*config['sensor_cols']).over(w))pdf = df.toPandas()
然后,在 tf.data.dataset 中读取时间序列数据之前,需要应用一个解析器。
sensor_data_bytes_list = list(pdf['sensor_data'].to_numpy())
sensor_data_list = list(map(lambda blob: np.frombuffer(blob, np.double), sensor_data_bytes_list))
sensor_data_array = np.concatenate(sensor_data_list)
ds = tf.data.Dataset.from_tensor_slices(sensor_data_array)
逮到你了
- 通过 HorovodRunner 在 spark 上运行 Horovod,你需要支付 1 个不能用于计算的 GPU 实例——这是 Spark 驱动程序的开销,Horovod 不能用于训练。
- Databricks 上没有 Tensorboard profiler 扩展,如果您是第一次设计 tensorflow 数据管道,最好先在单台 GPU 服务器上进行优化。
- Databricks 上的 Horovod 没有弹性——当 spot 实例被删除时,训练就会停止。如果你打算采用现货价格,你应该预料到培训会有很多中断。
- 获取 g4dn EC2 spot 实例相当困难(截至 2021 年)。Databricks 无法在 g4da 实例上运行,其他 EC2 GPU 实例也没有这么好的价值。
结论
Databricks 创建了一个很好的套件,将数据、ETL 和分布式培训都放在同一个地方。希望这些小技巧能让你的开发更轻松!祝你好运。
端到端深度学习:一个不同的视角
实践教程
用 YOLOv5,TorchServe & React Native 从一个想法到一个原生应用

每当有一篇关于端到端深度学习项目的文章,它都包括训练深度学习模型,部署 Flask API,然后确保它工作,或者它广泛地包括使用 Streamlit 或类似的东西创建 web 演示。这种方法的问题在于,它谈论的是一条已经过尝试和测试的直截了当的典型路径。它只需要用一个等效物替换拼图中的一片,比如用分类模型替换情感分析模型,等等,一个新的项目就可以创建了,但是线框基本保持不变。从教育的角度来看,这种方法是非常有效的,因为它教给你关于领域的知识,但是从工程的角度来看,一个典型的深度学习项目可能会有很大的不同。
工程更多的是设计和建造。知道“如何做”比遵循一个固定的模式或一步一步地建立一个项目更重要。在 2021 年,当有这么多可供选择的框架,解决同一问题的多种方法,部署模型的不同方法,以及为它们构建演示时,知道选择哪一个是很重要的,更重要的是知道如何从可用选项中选择一个。
注意:对于本文的其余部分,项目和深度学习项目可以互换使用。
当一个人开始学习深度学习概念时,项目是为了学习特定的技术或子领域,如 NLP 或 CV。今天,如果你去像https://paperswithcode.com/这样的资源,你会发现对不同问题的研究,这些问题已经成为跨领域的,或者已经变得非常难以用传统的监督学习方法解决,因此为了解决这些问题,像自我监督学习、半监督学习、因果推理等方法已经被实验。这就引出了一个重要的问题,什么是处理项目的好方法?应该选择一个领域,并在此基础上解决问题,还是应该选择一个问题,并了解解决它需要什么?让我们试着在这篇文章中回答这些问题。
本文的其余部分将讨论一个特定项目的过程,它的一些组件可能不像其他组件那样熟悉,但是重点是引导您完成这个过程,特别是处理这样一个项目的思维过程,而不是作为特定项目的教程。为了与没有任何固定路径或固定模式的想法产生共鸣,这篇文章是如何制定项目的文章之一,它省略了与讨论不直接相关的细节。
在进一步讨论之前,让我们先看看这个项目的最终产品和它所涉及的内容。

这个项目包括建立一个应用程序,当显示食物成分时,会告诉你这些成分可以用在哪些菜肴中。该应用程序的简单用例是捕获配料的图像,应用程序将检测配料本身,并向您提供检测到的配料列表以及可能使用这些配料的食谱。

项目架构
这个项目分为三个不同的部分。第一部分包括使用深度学习模型来检测食品成分,第二部分包括创建一个端点来将其部署为服务,第三部分包括在原生应用程序中实际使用它。
在这一点上,您一定想知道,这与任何其他一帆风顺的项目演练或演示有什么不同,但是随着您继续阅读,差异将变得越来越明显。本文的目的不是一步一步地灌输创建这样一个项目的过程,而是给你一个直觉,告诉你在创建这样一个项目的过程中需要做些什么,以及在不同的阶段需要做哪些积极的决策。
在这一点上,如果说解决问题或从事项目没有固定的模式,但总有一些东西是有帮助的,那就有点多余了。
“选择让你兴奋的事情。选择一些鼓励你努力的事情。”
知道你在做什么很重要,但是知道你为什么要做同样重要。当事情不像你希望或期望的那样运行时,它会非常有用(这种情况经常发生!).它让你受到鼓舞,让你对项目有一个清晰的视角。但仅此而已。这是拼图中唯一的伪集。其余的可以在我们进行项目时研究。
让我们谈一谈这个项目的动机吧。我是《厨艺大师》的超级粉丝(大部分都是),对于那些生活在岩石下,从来没有看过它的人来说,只需要知道它是关于烹饪的。节目中有一个环节叫做神秘盒子挑战,要求参赛者在给定一些神秘配料的情况下做出一道菜。这让我思考如何在深度学习和软件工程的舒适区解决这个问题。灯泡熄灭了,于是诞生了 MBC App 的想法。
在这一点上,稍微继续一下,讨论一下这个过程与现实生活中的项目有多接近是很好的。通常情况下,要么是现有的产品,要么是正在开发的新产品,使用任何类型的人工智能都是一种选择,在今天的时代,从初创公司到大型科技巨头,都将人工智能作为他们解决方案的第一选择,因此我们的思维过程在某种程度上符合行业的思维过程。
既然我们已经解决了动机,并且我们已经决定不遵循任何特定的途径来解决问题,难道我们根本就没有想到解决方案吗?嗯,这对我们这些深度学习爱好者来说也太大胆了。需要明确区分过程和方法。“过程”是一个宽泛的术语,描述了解决问题需要做什么,而做这件事的方法可能有很大的不同,而方法则是谈论完成某件特定事情的步骤。例如,知道是将一个问题作为回归还是分类来处理,可能是这个过程的一部分,而从 ResNet、EfficientNet、VGG 中选择哪一个特定的主干可以由特定的方法组成。
所以,让我们来谈谈解决手头问题的过程。我们需要一种获得原料的方法。这可以简单到让用户输入名字,但为什么他们不直接用谷歌搜索,而不用我们如此热情打造的应用程序呢?那么,这可能是一个多类问题,用户只需点击或上传食材的图片,应用程序就会基于此推荐食谱吗?可能是一个很好的选择,但是听起来太安全了,不适合尝试。那么有什么替代方案呢?我们可以使用对象检测来检测不同的成分。即使这样也是一种选择,是从监督的角度接近它,还是使用一些自我监督的定位技术。然而,后者超出了我们的讨论范围,所以我们将坚持使用对象检测。
既然已经解决了这个问题,我们想如何创建这个应用程序呢?它应该是一个脚本,用户在安装完所有依赖项后会在他们的系统上运行它,还是应该是一个 web 部署?对于我们试图解决的问题来说,这两种选择听起来都是错误的,而创建一个可以在一开始就使用的本地应用程序听起来既用户友好又适合这项任务。
现在让我们深入到构成这个项目的三个不同的部分,并谈论他们各自的工作。
YOLOv5 培训渠道
我们决定使用一个对象检测模型来检测不同的成分,作为流程的一部分,但我们当时没有决定如何完成,以及从各种可用选项中为其选择什么规范。有各种流行的目标检测模型,如 fast-RCNN,Mask-RCNN,SSD,YOLO 等。你可以找到对这些模型进行全面比较的论文,但我们宁愿保持简短,并证明我们的选择,而不是专注于我们没有选择的那些模型。

来源:https://github.com/ultralytics/yolov5
YOLO v5 是这些物体检测模型列表中的最新成员之一。它在 GPU 上的预测速度以及 COCO metric (范围从 0 到 1)方面都优于所有最近的模型。它有一个编写良好的代码库,并且根据项目的规模和需求,它提供了各种可供选择的方案。除此之外,它还提供了很好的可移植性。虽然是用 Pytorch 编写的,但它可以转换成 Onnx、CoreML、TFLite 和 Torchscript(我们将使用这些格式)等格式。此外,它转换成 Onnx 本身的能力打开了许多可能性。因此,由于其编写良好的代码库和良好的可移植性,我们选择 YOLOv5 作为我们的对象检测模型,特别是 YOLOv5m,作为速度和平均精度(AP)之间的良好权衡。
太好了!现在,我们需要做的就是训练对象检测模型,我们准备好了,对吗?为了打破泡沫,没有。我们谈了很多关于过程的问题,但我们没有谈到数据本身,这是任何数据科学管道的主要元素之一。所以我们先把模型和流程别上,再说数据。
先说此时的另一大区别。我们总是听到诸如“大数据”、“数据是新的黄金”、“大量数据”之类的术语和短语,但事实是非结构化和未处理的数据大量存在。同样,当我们开始学习深度学习和数据科学时,我们会遇到像 Iris 或 MNIST 等数据集。这些都是经过良好处理的数据,要做到这一点需要付出努力。这种努力大部分时间都没有实现,但可以考虑管理一个包含 70,000 张不同数字、不同笔迹的图像的数据集,或者更大、与项目更相关的数据集,如 COCO 数据集。收集和处理如此大规模的数据集既不容易也不便宜。大多数时候,在专业场景中,与任务相关的数据是不可用的,甚至在考虑开始整个训练-测试-实验循环之前,需要收集、整理和处理这些数据。
这就是我们的情况。我们需要一个食品成分的数据集,以及所有这些图像中不同标签的边界框信息。剧透一下,这样的数据集并不容易获得,因此我们需要付出额外的努力来自己准备数据集,这与其说是一个有趣的编码挑战,不如说是一个枯燥的手工任务,但同样是整个生命周期中非常重要的一部分。
项目生命周期中的这一步包括我们收集数据,用边界框信息对其进行注释,在将数据传递给模型进行训练之前对其进行结构化。为了简单起见,我们宁愿选择用 10-20 种配料,而不是一大堆配料,不眠之夜,一壶满满的咖啡。食材是根据它们在印度食物 101 数据集中不同菜肴中的出现频率来选择的。截至目前,只选择了 10 种成分。这个数据集在后面的部分也很重要,那时我们将不得不根据公认的配料来预测菜肴,但后面会有更多的内容。
咖哩叶、咖喱酱、酥油、姜、果酱、牛奶、米粉、糖、番茄、乌拉尔豆
现在我们已经有了我们想要处理的前 10 种菜肴的列表,我们需要一种收集这些数据的方法,并且也是一种结构良好的方法。有多种方法可以做到这一点。人们可以四处炫耀他们的摄影技术,并获得这 10 种成分的图像,但这听起来太耗费体力了。工程学也是关于狡猾和懒惰的,这导致了艰苦工作的巧妙替代。因此,一个简单的替代方法是从不同的免费来源和网络搜索下载数据。但是更聪明的方法是编写一个网络抓取器,从不同的免费来源自动获取数据。
我们选择第三个选项。编写一个 web scraper 来为我们完成这项任务,比我们自己手动点击和收集图像要好。我们收集数据的方式是,每个标签(食品成分)都有一个单独的文件夹,我们可以控制想要抓取的图像数量。
**food-ingredients**/
|--**curry-leaves**/
|--**garam-masala**/
.
.
.
|--**urad-dal**/
在回到模型本身之前,还有一项任务没有完成,并且与前一项任务相比,备选方案更少。在每个图像上标注和创建边界框的任务。我们可以使用像 LabelImg 这样的工具手动完成这项工作,或者我们可以选择一些完全不同的方法,比如半监督对象定位,但是我们不要谈论后者,因为它超出了本讨论的范围。让我们沿着这条轨道,手动注释数据。YOLO v5 项目期望一个非常特殊的数据目录结构:

文件夹结构
因此,我们转换上述数据,用下面这段代码复制上述文件夹结构:
之后,使用 LabelImg 工具,我们为列和列中的每个图像生成边界框。当我们回到最初的动机,提醒自己当初为什么要做这个项目的时候。
太好了!既然标记已经完成,数据也已经正确组织,我们可以继续在自定义数据集上训练 YOLO v5 模型。我们将使用 yolov5m 并编写我们的数据配置,如下所示:
既然现在一切都准备好了,剩下的就是以期望的格式训练和导出我们的模型,供以后使用。因为它涉及到在本地或通过 Google Colab 之类的服务运行一些脚本,所以在这里可以很容易地跳过,培训和整个第 1 部分的参考资料可以在这里找到:
https://github.com/himanshu-dutta/mbc-object-detection-yolov5
推理终点

来源:https://github.com/pytorch/serve
在服务于机器学习或深度学习模型时,有多种选择可供选择。从更熟悉的 Streamlit 和 Flask 到稍微少谈的“edge 上的部署”、以及不断涌现的新选项,比如 FastAPI(我还没玩过)。
我们需要选择最适合我们目的的选项。由于我们决定为项目创建一个本地应用程序,我们可以选择本地部署模型,选项有【Onnxjs】或 TensorFlow.js 。这个选项的问题是,Onnx 不能与 React Native 一起正常工作(因为应用程序将使用它来构建),尽管 TF.js 有一个兼容 React Native 的框架,但它有一些需要解决的错误。这限制了我们创建 API 端点的选择。为此,我们选择 TorchServe ,它本质上使用 C++绑定来部署 Torchscript 模型。选择 TorchServe 的原因是可以编写端点的 pythonic 风格,以及它的 Torchscript 部署,这是速度的原因。
端点处理程序由方法组成,这些方法无论如何都是您的代码库(在我们的例子中是 YOLOv5 代码库)的一部分。它由以下方法组成:
**__init__**: Used to initialize the endpoint, can be image transforms, or encoders for text, etc.**preprocess**: Method used to process the input batch before feeding it to the model and making inference, such as given an input image, transforming it in the desired way.**inferencce**: This method is where the actual inference happens. The preprocessed input is passed to the model, and output is obtained from it. Other smaller changes to the output can also be performed here.**postprocess:** After the inference is made the output is passed to this method, where the output can be packaged in the desired way, before the response is made for the given input, such as converting class index to label names, etc.Note: Although the method names are predefined, what is actually done inside these methods is completely upto us. Above are the suggeested best practices.
现在我们已经有了端点处理程序,所有需要的就是实际的端点函数,它只是简单地做我们在处理程序中描述的事情,预处理数据,对数据进行推断,并进行后处理以返回响应。
可以使用类似如下的命令部署端点:
上面的脚本简单地将我们保存的模型归档到一个兼容的" model archive" 文件中,该文件打包了所有必需的脚本、模型图和权重,然后该文件被传递给 TorchServe 命令来启动端点。
本机应用程序
第三部分对项目来说非常具体。有多种方法来演示或创建一个完整的项目前端。有时将你的工作打包成一个库或框架是很好的,在其他时候,它可以是一个完整的 web 或本地应用程序。此外,还有像 Streamlit 这样的选项,当您必须快速简单地演示某些东西时,这些选项真的非常有用。但是作为一个工程背景的人,学习一些新的东西是需要做的。
对于特定的项目,编写原生应用程序最有意义。但为了讨论方便,我们来考虑一下其他选项。一个相近的替代方案是部署一个简单的 web 应用程序。由于我们在 React Native 中工作,如果我们考虑以后在 React 中工作,很多代码本身是可重用的,所以代码库具有良好的可重用性。除此之外,web 应用程序不像本地应用程序那样用户友好,因此我们将继续使用本地应用程序。
虽然我们已经决定了应用程序的用例,但是我们还没有弄清楚用例要实现需要做什么。对此有两个好的选择:
- 我们可以让用户将相机指向食物成分,应用程序将继续对我们的端点进行异步调用,并根据响应继续建议食谱。为了让这种替代方案发挥作用,我们的端点必须具有高响应性,这有时是不可能的,因为端点可能运行在 CPU 上。与此同时,API 调用必须连续进行,这对于用例来说是多余的。
- 另一种方法是让用户单击食物成分的图像,然后调用 API 来获得推断,根据推断可以建议食谱。
由于后一种选择更可行,我们继续进行。在决定了进行推理的首选方式之后,我们现在需要决定应用程序所需的 UI 和组件。我们肯定需要一个相机组件和一个图像组件(用于显示静态图像),此外还需要一些按钮供用户点击图像、翻转相机等,以及一个可以列出配料和食谱的部分。在检测到的成分周围画出边界框也很好。这些元素中的大部分已经内置在 React Native 中,但是我们必须自己构建“边界框”组件。
“边界框组件是一个透明的矩形框,左上角有一个文本组件,它直接呈现在图像组件上。现在我们所有的组件都准备好了,我们需要将它们组装成一个工作应用程序,它本身将是一个单屏幕应用程序,看起来就像我们在开始时看到的图像。进行推断的代码如下所示:
它只是以 Base64 字符串的形式发送图像,并将响应组织成合适的格式,传递给不同的组件进行渲染和进一步处理。它还确保成分的预测边界框和标签具有高于设定阈值的置信度。
现在,我们需要根据检测到的成分来建议一些食谱,这可以是从对一些现有服务进行外部 API 调用到在应用程序本身中拥有食谱记录的任何范围。为了简单起见,我们将使用从“印度食物 101”数据集提取的食谱列表,并使用该列表根据检测到的成分来建议食谱。
现在,这取决于我们如何互动和用户友好,我们希望这个应用程序。如果我们愿意,我们甚至可以添加更多的用例和特性。但是由于我们到目前为止所开发的应用程序符合我们最初的想法,我们将在此停止。

应用程序屏幕
希望这篇文章能给你提供一个很好的动机去研究和寻找你自己的项目工作方式,而不是遵循任何固定的模式。每个项目都有一些要传授的知识,但重要的是将这些知识用于未来的努力,包括技术性和非技术性的。
项目参考
[1]https://github . com/himan Shu-dutta/MBC-object-detection-yolov 5
[2]https://github.com/himanshu-dutta/mystery-box-challenge-app
参考
[1] Yolo v5
[3] 反应原生
端到端深度学习项目:第 1 部分
初学者的数据科学
用 Keras 实现迁移学习的高效网络图像分类模型
注意:这是从零开始实现深度学习项目的两部分系列的第一部分。第 1 部分包括问题陈述的设置、数据预处理、迁移学习背后的直觉、特征提取、微调和模型评估。 第二部分 涵盖了 Flask app 的实现及其在 Heroku 上的后续部署。为了保持连续性,请遵循教程。代码在Github上。

你可以在这里玩烧瓶应用程序。
介绍
我非常幸运地在这样的环境中工作:( a)数据生成的基础设施和架构随时可用,( b)数据争论由分析师处理,( c) MLOps 由数据工程师的独立部门处理。这些额外津贴给了我自由,让我可以专注于我最喜欢的事情——数据建模。话虽如此,如果我不得不独自完成一个完整的项目,我总是想至少学习一些基础知识。这正是这篇文章背后的动机。
我决定实施一个端到端 DL 项目,因为存在一些主要与他们的部署相关的挑战——由于我们必须处理的模型的大小——以及对模型进行微调以适应我们特定的用例。
该项目将包括三个部分:
- 第 1 部分:设置(虚拟环境、训练数据集等。)、模型训练(用 Keras 微调、学习曲线监控等。),测试。
- 第 2 部分:构建 Flask 应用程序并在 Heroku 上部署。
这个由两部分组成的系列的目的是为您提供源代码、提示、技巧,并熟悉使用深度学习模型时常见的运行时错误。我确信这些在在数据科学面试中解释项目时会派上用场。
提醒:这篇文章(以及后续文章)中的一些内容将会被详细讨论,因为它的目的是让人们(尤其是早期研究人员)理解一些设计决策背后的原因/利弊,并在面试中完美地回答这些问题。
https://github.com/V-Sher/house-interior-prediction
第 1 部分:设置
虚拟环境
使用终端,在项目目录中创建一个名为e2eproject的虚拟环境,并激活它。
python3 -m venv e2eproject
source e2eproject/bin/activate
资料组
我们将使用 Kaggle 公开发布的房子房间数据集。
您可以手动下载它,然后将它移动到您的项目目录或中。在终端中使用以下命令将它直接下载到您的项目目录中。
附注:在运行以下命令之前,确保您在项目目录中。
kaggle datasets download -d robinreni/house-rooms-image-dataset — unzip
工作
我们将致力于图像分类任务。具体来说,我们将开发一个模型,该模型可以检测给定卧室图像的房屋内部是现代(M 类)还是古老(O 类)。这种模型可以在再抵押期间或在出售财产时用于财产估价。
你可能已经注意到了,这个数据集是没有标签的,然而,我的一个朋友慷慨地提供了大约 450 张图片的手工标签。(标签已在 Github repo 中提供。)尽管这不是一个很大的数据集规模,但我们仍然能够在保留的测试集上达到几乎 80%的准确率。此外,用于微调、改进模型度量等的适当技术。以确定是否值得花费更多的时间来标记额外的数据点。
第二部分:模特培训
让我们创建model.ipynb笔记本。
装置
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.applications import EfficientNetB0
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.callbacks import ModelCheckpoint
from tensorflow.keras.layers import BatchNormalization
from tensorflow.keras.layers import Dropout
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Input
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.models import Modelfrom sklearn.metrics import classification_report, confusion_matrix
from sklearn.model_selection import train_test_split
from imutils import paths
from tqdm import tqdmimport matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
import seaborn as sns
import numpy as np
import shutil
import os
注意:你可能需要做一些 *pip install XXX* 来让上面的单元格工作。
辅助变量和函数
ORIG_INPUT_DATASET = "House_Room_Dataset/Bedroom"TRAIN = "training"
VAL = evaluation"
TEST = "testing"BASE_PATH = "dataset"
BATCH_SIZE = 32
CLASSES = ["Modern", "Old"]
我们将只处理卧室图片,因此ORIG_INPUT_DATASET指向卧室子目录。BASE_PATH是我们将存储图像的训练、测试和验证分割的目录的路径。这将是空的。
*def* plot_hist(*hist*, *metric*):
if *metric* == 'auc':
plt.plot(*hist*.history["auc"])
plt.plot(*hist*.history["val_auc"]) else:
plt.plot(*hist*.history["loss"])
plt.plot(*hist*.history["val_loss"]) plt.style.use("ggplot")
plt.title("model {}".format(*metric*))
plt.ylabel("{}".format(*metric*))
plt.xlabel("epoch")
plt.legend(["train", "validation"], *loc*="upper left")
plt.show()
这是一些样板代码,用于绘制两种类型的学习曲线——AUC 对 epoch 和 loss 对 epoch。
注意:如果您使用的是除 *auc* 之外的指标,比如说 *accuracy* ,请确保将上面代码片段中的 *auc* 更新为 *accuracy* ,将 *val_auc* 更新为 *val_accuracy* 。
加载标签
([labels.txt](https://github.com/V-Sher/house-interior-prediction/blob/main/labels.txt)已作为回购的一部分。)
*# Reading labels from the txt file*
with open("labels.txt", 'r') as f:
manual_labels = f.read()*# Extracting individual labels into a list*
labels = [i for i in manual_labels]
len(labels)********* OUTPUT **********
451
要检查数据集是否平衡:
from collections import Counterprint(Counter(labels).keys())
print(Counter(labels).values())********* OUTPUT **********
*dict_keys(['O', 'M'])
dict_values([271, 180])*
看起来在我们的数据集中,老房子比现代房子多(尽管差距不是很大)。因此,抛弃准确性并选择更适合处理类别不平衡的指标,即 AUC(ROC 曲线下面积)是有意义的。
列车测试验证拆分
在我们进行分割之前,对文件名进行排序是很重要的,因为我们有第个 451 图像的标签(在House_Room_Dataset/Bedroom 子目录中),而不仅仅是随机的 451 图像。默认情况下,os.listdir()以随机的顺序返回文件,我们不应该依赖它。
*# sorting files in the order they appear*
files = os.listdir(ORIG_INPUT_DATASET)
files.sort(*key*=*lambda* *f*: *int*(f.split('_')[1].split('.')[0]))# checking to see the correct file order
files[:5]********* OUTPUT **********
['bed_1.jpg', 'bed_2.jpg', 'bed_3.jpg', 'bed_4.jpg', 'bed_8.jpg']
现在我们知道我们有正确的 451 张图片,让我们继续进行训练-测试-验证分割。我们将分别分配大约 75%、15%和 10%的数据用于训练、验证和测试。
*# splitting files into train and test sets*
trainX, testX, trainY, testY = train_test_split(files[:len(labels)],
labels,
*stratify*=labels,
*train_size*=0.90)*# further splitting of train set into train and val sets*
trainX, valX, trainY, valY = train_test_split(trainX, trainY, *stratify*=trainY, *train_size*=0.85)*# Checking the size of train, test, eval*
len(trainX), len(trainY), len(valX), len(valY), len(testX), len(testY)********* OUTPUT **********
(344, 344, 61, 61, 46, 46)
使用 Sklearn 的train_test_split()方法,我们首先将整个数据集分割成训练集和测试集,然后将训练数据再次分割成训练集和验证集。由labels到stratify是很重要的,因为我们想要在所有三个集合(训练、测试和验证)中按比例分配现代和旧图像。
构建训练数据集目录
在代码的后面,您会注意到,在训练期间,我们不会将整个数据集加载到内存中。相反,我们将利用 Keras 的[.flow_from_directory()](https://keras.io/api/preprocessing/image/)功能来实现批处理。但是,该函数希望数据被组织到如下目录中:

图 Keras 中批量读取图像的目录结构。M 级和 O 级分别指现代和旧。
为了让我们的图像文件以上述格式组织起来,我们将使用下面这个简短的片段:

当代码片段运行时,您应该能够使用 [tqdm](/data-scientists-guide-to-efficient-coding-in-python-670c78a7bf79) 模块看到进度,一旦它完成,您将发现创建了三个新的子目录— dataset/training、dataset/evaluation和dataset/validation,在每个子目录中,将有两个子子目录,分别用于现代和古老的房屋。

作为健全性检查,让我们看看在每个子目录中有预期数量的图像。
trainPath = os.path.join(BASE_PATH, TRAIN)
valPath = os.path.join(BASE_PATH, VAL)
testPath = os.path.join(BASE_PATH, TEST)totalTrain = len(*list*(paths.list_images(trainPath)))
totalVal = len(*list*(paths.list_images(valPath)))
totalTest = len(*list*(paths.list_images(testPath)))print(totalTrain, totalTest, totalVal)********** OUTPUT *******
344 46 61
注意:如果您的自定义数据是下面描述的结构,有一个有用的 python 包叫做split _ folders可以用来获取图 1 中定义的目录结构中的数据。
dataset/
class1/
img1.jpg
img2.jpg
...
class2/
img3.jpg
...
...
图像预处理
因为我们处理的样本量相当有限,所以通常建议使用旋转、缩放、平移等方法随机放大图像。
虽然认为数据扩充增加了可用的训练数据量可能很诱人,但它实际上做的是获取训练样本并对其应用随机变换[ 来源 ]。总体而言,样本量保持不变。
Keras 允许亮度,旋转,缩放,剪切等随机增强。使用图像数据生成器,最好的部分是所有这些都是在模型拟合过程中动态完成,也就是说,您不需要预先计算它们。
训练数据扩充:
*trainAug = ImageDataGenerator(
*rotation_range*=90,
*zoom_range*=[0.5, 1.0],
*width_shift_range*=0.3,
*height_shift_range*=0.25,
*shear_range*=0.15,
*horizontal_flip*=True,
*fill_mode*="nearest",
*brightness_range*=[0.2, 1.0]
)*
width_shift、height_shift、zoom_range、rotation_range等大部分参数应该是直观的(如果没有,看一下官方 Keras* 文档 ) 。*
需要注意的重要一点是,当您执行缩放或旋转操作时,图像中可能会产生一些空白区域/像素,必须使用fill_mode中提到的适当技术进行填充。
验证数据扩充:
*valAug = ImageDataGenerator()*
您将会看到,在为验证数据初始化数据扩充对象时,我们没有提供任何参数。这意味着我们将对它们都使用默认值,即 0。换句话说,我们没有应用任何扩展(没有缩放,宽度移动,水平翻转,等等。)添加到验证集,因为在训练期间评估模型时,该集应被视为测试集。
测试数据扩充:
*testAug = ImageDataGenerator()*
遵循与上面相同的逻辑,我们没有对测试集应用任何扩充。
创建数据生成器
如前所述,我们需要创建一些数据生成器,以便在训练期间将这些增强图像成批地提供给模型。为此,我们可以使用[flow_from_directory()](https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/image/ImageDataGenerator#flow_from_directory) 发生器函数。
**# Create training batches whilst creating augmented images on the fly*trainGen = trainAug.flow_from_directory(
*directory*=trainPath,
*target_size*=(224,224),
*save_to_dir*='dataset/augmented/train',
*save_prefix*='train',
*shuffle*=True
)*# Create val batches* valGen = valAug.flow_from_directory(
*directory*=valPath,
*target_size*=(224,224),
*shuffle*=True
)*
需要考虑的几件重要事情:
- 在每种情况下,
directory被设置为训练(或验证)图像所在的路径。 - 将
target_size指定为(224,224)可以确保所有图像都将被调整到这个尺寸。 - 我们还将把
save_to_dir设置为保存增强图像(前缀在save_prefix中指定)的目录路径,这些图像将在训练过程中动态创建。这提供了一个很好的健全性检查,以查看图像是否得到了应有的随机变换。注意:如果你想事先检查一下,也就是在训练开始之前,这里有一个我在stack overflow上找到的快速片段。** - 最后,
shuffle被设置为True,因为我们希望样本在批处理生成器中被混洗,这样当model.fit()请求一个批处理时,就会给出随机样本。这样做将确保不同时期之间的批次看起来不一样,并最终使模型更加健壮。
**# Create test batches*testGen = testAug.flow_from_directory(
*directory*=testPath,
*target_size*=(224,224),
*shuffle*=False
)*
除了为testGen设置正确的directory路径,还有一件主要的事情需要考虑:
shuffle必须设置为False。
你问,为什么?
因为,现在我们不希望样本在测试批次生成器中被混洗。只有当 shuffle 设定为 False 时,才会按照提供的文件名称的顺序创建批处理。在模型评估期间,需要将文件名(即真实标签,可使用*testGen.classes*访问)与预测标签进行匹配。
有趣的事实:如果你现在检查 *trainGen.classes* 的输出,希望它们被打乱,你会失望的。为什么?因为当在模型拟合期间请求一个批次时,洗牌是动态发生的。【stack overflow*】。*
训练过程背后的直觉
我们可以从头开始训练一个模型,但这肯定会表现不佳——主要是因为我们的数据集如此之小。在这种情况下,利用迁移学习的力量是有意义的。
迁移学习是指在新数据集上微调预训练模型的过程。这使它能够识别从未训练过的类!
简而言之,迁移学习允许我们利用模型在训练中获得的知识来识别狗和猫,这样它现在可以用来预测房子内部是否现代。
但是它为什么会起作用呢?
因为我们挑选的任何基础模型(即预训练模型)通常都是在如此大的图像语料库上训练的,所以一般来说,它能够学习图像的良好矢量表示。剩下要做的就是在区分定制类时使用这些表示(在我们的例子中,是老房子还是现代房子)。
鸣谢:我想花点时间对我在这篇文章的研究阶段发现的几个博客( 这个 , 这个 ,这个 )大声疾呼。这些被证明是真正的瑰宝,帮助我详细理解了迁移学习的概念。我真的很感激你所有的见解,这些见解让我为我的读者简化了代码/解释。
使用 Keras 进行迁移学习
迁移学习包括两个主要步骤:
- *特征提取**:将一个预先训练好的模型(并冻结其权重)作为基础模型,然后在基础模型上训练一个新的分类器,使其准确输出 N 个值(其中 N 为类别数)。
- **【可选】微调:一旦训练好分类器,从基础模型中解冻几个层,使其很好地适应新的数据集。
*新的分类器可以是:
- 密集层的堆叠(即完全连接的层)。
运筹学
- 单个全局池层(将整个要素地图缩小为单个值—
maxpool、avgpool)。这是优选的,因为有 0 个参数需要优化(因此是本文的选择),所以过度拟合较少。
**根据您的数据集与预训练模型最初训练时的数据集的不同程度,少数会有所不同。请记住,如果两个数据集非常相似,那么只解冻所有图层的一部分可能会有所帮助。
微调步骤虽然是可选的,但对于自定义数据集与训练基本模型的数据集完全不同的用例来说,这一步非常重要。此外,与特征提取步骤相比,这可能需要更多的历元。因为更多的时期粗略地转化为更高的过拟合机会,所以建议在仔细监控损失/精度曲线之后使用(模型训练的)早期停止。
型号选择背后的直觉
回到这个百万美元的问题——我们应该选择哪个模型作为微调的基础模型?显然,有相当多的选项可用,可以在 Keras 文档中找到这里。虽然我最初的选择是 ResNet-50,因为它很受欢迎,但我最终决定继续使用 EfficientNet ,因为它们可以实现与 SOTA 型号类似的结果,同时需要更少的 FLOPS 。此外,论文提到它们的性能在** 迁移学习任务上与 SOTA 模型不相上下,同时平均需要的参数少 9.6 倍。哇哦⭐️**
EfficientNet 模型有很多种(EfficientNetB0、EfficientNetB1、…… EfficientB7),它们在架构(即网络深度、宽度)和资源限制方面略有不同。这些模型中的每一个都需要特定图像形状的图像,如本表中所述。鉴于我们正在使用224x224分辨率的图像,我们将使用 EfficientNetB0。
特征提取步骤的模型训练
注意:在本教程中,我们将使用 Tensorflow 的 Keras API。如果您是 Keras 新手,我已经写了两个初级 Keras 教程(part 1,part 2),内容涵盖了网络架构、神经元、激活函数、隐藏层(密集、下降、最大池、展平)等,比这里讨论的要详细得多。请随时参考他们的快速复习!**
我们从使用imagenet权重创建一个EfficientNetB0基础模型开始。
**baseModel = EfficientNetB0(
*weights*="imagenet",
*include_top*=False, # make sure top layer is not included
*input_tensor*=Input(*shape*=(224, 224, 3)),
*pooling*="avg"
)**
需要考虑的事情很少:
include_top必须设置为False,因为 EfficientNet 网络架构中的顶层(即最终层)是一个Dense层,它输出 1000 个对应于 ImageNet 数据集的类。我们显然不需要这个!- 如果您没记错的话,我们选择新的分类器作为全局池层(而不是密集层的堆栈)。好消息是,Keras API 已经允许我们在实例化
EfficientNetB0对象的同时完成这个。我们可以简单地将pooling参数设置为avg。默认为None。
下一步是通过将每层的trainable设置为False来冻结权重:
***# freeze the weights*for layer in baseModel.layers:
layer.trainable = False**
现在是时候在上面创建一个新的分类器了,它将准确地产生两个类(M或O)。为此,我们需要确保这个分类器模型的最后一层是具有两个输出神经元的Dense层。在这两者之间,我们还包括了BatchNormalization和Dropout层,用于正则化。
***# training a new classifier on top (Functional Keras Model)*x = baseModel.output
Layer_1 = BatchNormalization()(x)
Layer_2 = Dropout(0.5)(Layer_1)
output_layer = Dense(len(CLASSES), *activation*="softmax")(Layer_2)model = Model(*inputs* = baseModel.input, *outputs* = output_layer)**
注意:有两种方法可以建立这个 Keras 分类器模型:顺序的(最基本的)和功能的(对于具有多个输入/输出的复杂网络)。上面的代码片段是作为一个功能网络编写的,因为如果您使用 *model.summary()* 来检查它,它会使网络架构更加清晰。同样,我们可以创建一个类似下面的序列模型,结果将是相同的。
***# Another way to create the classifier on top of basemodel*model = tf.keras.Sequential()
model.add(baseModel)
model.add(BatchNormalization())
model.add(Dropout(0.5))
model.add(Dense(len(CLASSES), activation="softmax"))**
最后,让我们用Adam优化器和一个相对较大的learning_rate = 1e-3来编译模型。由于我们有两个可能的输出类,我们将监控binary_crossentropy损失(如果您处理两个以上的类,请使用categorical_crossentropy)并根据tf.keras.metrics.AUC中实现的 AUC 度量评估模型的有效性。
***# compile*opt = Adam(*learning_rate*=1e-3)
model.compile(*optimizer*=opt,
*loss*='binary_crossentropy',
*metrics*=[tf.keras.metrics.AUC()]
)**
在使用fit()训练模型之前要做的最后一件事是实现EarlyStopping和ModelCheckpoint。
前者将确保模型不会在不必要的情况下训练更多的时期。这是通过监控val_loss来完成的,一旦没有进一步的改进,即不能进一步最小化,训练就停止。
后者将在给定的文件路径中保存最佳模型——在我们的例子中是feature_extraction.h5。我们将再次监控验证损失,并保存所有时期的最佳模型。
注意:这里有一篇优秀的 文章 更详细地解释了早期停止和模型检查点实现!
***# implementing early stopping* es = EarlyStopping(
*monitor*='val_loss', #metric to monitor
*mode*='min', # whether to min or max the metric monitored
*patience*=10, # epochs to wait before declaring stopped training
*verbose*=1 # output epoch when training was stopped
)*# implementing model checkpoint* mc = ModelCheckpoint(
'feature_extraction.h5',
*monitor*='val_loss',
*mode*='min',
*verbose*=1, # display epoch+accuracy everytime model is saved
*save_best_only*=True
)**
最后,是模型训练的时候了:
***# Training the model*hist = model.fit(
*x*=trainGen,
*epochs*=25,
*verbose*=2,
*validation_data*=valGen,
*steps_per_epoch*=totalTrain // BATCH_SIZE,
*callbacks*=[es, mc]
)**

最终时期的损失和 AUC 分数
快速看一下 AUC 和损失曲线,我们可以找到模型收敛的证据(意味着模型已经准备好进行微调)。
**
****
AUC 和损失的学习曲线**
右图中一个有趣的观察结果是,我们的验证损失低于训练损失。起初,我认为有一些数据泄漏的问题,但后来我发现了这篇优秀的文章解释了为什么这是完全正常的,有时会在训练中发生。
总结两个可能的原因(从文章本身):
- 原因#1:正规化(如辍学)只适用于培训期间,而不是在验证期间。因为正则化牺牲训练精度来提高验证/测试精度,所以验证损失可以低于训练损失。
- 原因#2:我们的验证集太小(只有 61 幅图像),也许它比训练集更容易,即不具有代表性的验证数据集。
特征提取步骤后的模型测试
我们将使用一些样板代码来评估使用.predict()获得的模型预测。记住predIdxs将类似于[0.8, 0.2],即M和O两个类的 softmax 值,所以确保使用np.argmax选择两个中的最大值。我们使用testGen.class_indices来检查从类名到类索引的映射。
**testGen.reset()predIdxs = model.predict(
*x*=testGen,
*steps*=(totalTest // BATCH_SIZE) + 1
)predIdxs = np.argmax(predIdxs, *axis* = 1)
print("No. of test images", len(predIdxs))
print(testGen.class_indices)cm = confusion_matrix(testGen.classes, predIdxs)
heatmap = sns.heatmap(cm, *annot*=True)
plt.xlabel("Predicted")
plt.ylabel("Actual")
plt.show()********* OUTPUT********
No. of test images 46
{'M': 0, 'O': 1}**

微调步骤的模型训练
我们将从解冻当前模型的最后几层开始,但是,我们不应该随意打开或关闭层。有许多技术和技巧可用于微调模型(例如,参见这个和这个),但有一些是我发现最有用的:
- 当在该步骤编译模型时,与特征提取步骤相比,使用甚至更小的学习速率。较小的学习速率意味着需要更多的历元,因为每次更新时网络权重的变化较小。
- 这些层需要保持冷冻。
- 在网络架构中,卷积块需要在整体 中打开或关闭。
例如:考虑model.summary()输出的最后几行。正如你所看到的,这些层被整齐地组织成块,最后一块是block7d。首先,我们将解冻block7d中的所有图层(然而,任何BathcNormalization图层都将保持原样)以及随后的 7 个图层(其中大部分是我们在构建新的分类器头时定义的)。总的来说,网络的最后 20 层将是解冻的候选层。****
****____________________________________________________________________
Layer (type) Output Shape Param # ====================================================================
.
.
.block6d_project_conv (Conv2D) (None, 7, 7, 192) 221184 ____________________________________________________________________block6d_project_bn (BatchNormal (None, 7, 7, 192) 768 ____________________________________________________________________block6d_drop (Dropout) (None, 7, 7, 192) 0 ____________________________________________________________________block6d_add (Add) (None, 7, 7, 192) 0 ____________________________________________________________________block7a_expand_conv (Conv2D) (None, 7, 7, 1152) 221184 ____________________________________________________________________block7a_expand_bn (BatchNormali (None, 7, 7, 1152) 4608 ________________________________________________________________block7a_expand_activation (Acti (None, 7, 7, 1152) 0 ____________________________________________________________________block7a_dwconv (DepthwiseConv2D (None, 7, 7, 1152) 10368 ____________________________________________________________________ .
.
.****
我已经将用于微调的代码捆绑到一个名为fine_tune_model()的函数中。大部分代码是从特征提取步骤开始重复的。
*****def* fine_tune_model(*model*): *# unfreeze last conv block i.e. block7a* for layer in *model*.layers[-20:]:
if not isinstance(layer, BatchNormalization):
layer.trainable = True *# check which of these are trainable and which aren't* for layer in *model*.layers:
print("{}: {}".format(layer, layer.trainable)) *# compile (with an even smaller learning rate)* opt = Adam(*learning_rate*=1e-5)
*model*.compile(
*optimizer*=opt,
*loss*='binary_crossentropy',
*metrics*=[tf.keras.metrics.AUC()]
) return *model*model_fine_tuned = fine_tune_model(model)****
因为微调也将使用相同的数据发生器,即trainGen、valGen和testGen,所以重置它们以便它们从数据集中的第一个样本开始非常重要。
****trainGen.reset()
valGen.reset()
testGen.reset()****
最后,让我们设置提前停止和模型检查点(注意,我们已经将patience增加到 20,因为我们现在将训练更长时间,即 50 epochs),然后开始训练。
*****# implementing early stopping* es_tune = EarlyStopping(
*monitor*='val_loss',
*mode*='min',
*patience*=20,
*verbose*=1
)*# implementing model checkpoint* mc_tune = ModelCheckpoint(
'fine_tuned_house.h5',
*monitor*='val_loss',
*mode*='min',
*verbose*=1,
*save_best_only*=True
)hist = model_fine_tuned.fit(
*x*=trainGen,
*steps_per_epoch*=totalTrain // BATCH_SIZE,
*validation_data*=valGen,
*epochs*=50,
*verbose*=2,
*callbacks*=[es_tune, mc_tune]
)****

特征提取步骤后的模型测试

通过与之前的混淆矩阵进行比较,我们只成功地将正确预测的图像数量增加了 2 个(参见两张热图中的对角线值)。
作为最后的健全性检查,查看这个微调步骤是否显示出任何过度拟合的迹象也是很好的。
********
验证损失不稳定,稳定在 0.55 左右,表明模型没有过度拟合。总的来说,验证集预测的 AUC 确实随着更多的时期变得更好,但回报却在减少。(简单地说,更长时间的培训似乎不会对我们的案例有实质性的帮助)。
起初,我认为训练曲线的波动是由于批量大小,因为它们在网络如何学习中发挥了作用。类似地,过大的学习速率会阻碍收敛,并导致损失函数波动,陷入局部最小值。然而,无论是增加批量还是降低学习率都无助于平滑梯度。
想到的另一种可能的解释是,网络已经达到了其关于给定数据集的容量,即,它不能再从中学习更多。这是可能的,因为我们正在尝试训练一个相对较大的网络(记住,我们已经解冻了一些额外的层,这意味着存在更多的可训练参数),仅使用 344 个样本,这些样本无法提供足够的信息来学习问题(进一步)。
注意:在将更多图像推进训练过程以希望改进模型之前,可能值得对模型超参数、train:val 分割、预训练权重的选择(来自 嘈杂学生 训练的权重已知比来自 ImageNet 训练的权重更好https://arxiv.org/pdf/1911.04252.pdf)以及网络架构本身进行修改。****
未来的工作
在最近的论文和视频中已经证实,使用未标记和标记数据集的联合训练优于管道,在管道中,我们首先使用未标记数据进行预训练,然后在标记数据上进行微调。这被称为半监督学习,将是我们下一个教程的重点。这将允许我们充分利用数据集中难以获得标签的剩余图像。
坚持了这么久,很值得称赞。🥂
前往第 2 部分了解如何将这个训练好的模型包装在 flask 应用程序中。我们还准备写一个又快又脏的前端,最后在 Heroku 上部署 app。
在那之前:)
我喜欢写循序渐进的初学者指南、操作指南、面试问题、ML/AI 中使用的解码术语等。如果你想完全访问我的所有文章(以及其他媒体上的文章),那么你可以使用 我的链接**这里 注册。**
**** https://varshitasher.medium.com/six-reasons-to-switch-to-this-podcast-app-today-3a396ada0a2b ****
利用 Docker 和 Apache Airflow 从头开始构建端到端机器学习管道
从环境设置到管道实施

作者图片
介绍
这篇文章描述了用 Docker 在 Apache Airflow 上实现一个示例机器学习管道,涵盖了从头开始设置一个工作本地环境所需的所有步骤。
问题是
让我们想象一下,有一个 Jupyter 笔记本,上面有一个完善的机器学习实验,包括从原始数据到相当高性能的模型的所有阶段。
在我们的场景中,新的输入数据是由每天的批次提供的,一旦提供了新的批次,就应该执行训练过程,以便调整模型的参数来适应数据变化。
此外,应跟踪训练条件和参数以及表现,目的是监测不同训练期的结果。
最后,获得的模型应该被保存起来,并提供给其他系统用于推理,同时允许对每个生成的模型进行版本控制。
因此,我们希望创建一个管道来:
- 从外部来源获取每日批次,并存储它们以备将来使用。
- 将数据拆分为训练集和测试集,并存储它们以供参考。
- 为超参数调整执行 k 倍交叉验证训练,并选择最佳参数集。
- 评估测试集的性能。
- 存储实验结果(最佳参数、训练条件、测试集性能)
- 对所有数据(训练集和测试集)拟合最佳估计值。
- 保存获得的模型以备将来使用。
这些要求可以通过以下渠道实现:

图片作者。
我们将使用 Apache Airflow 实现这个管道,这是一个流行的开源 orchestrator,允许以编程方式创作、调度和监控工作流。
环境设置
设置很简单,唯一的先决条件是安装了 Docker Compose 。
我们从 Airflow 的官方 Docker Compose yaml 文件开始,并应用以下更改:
- 将air flow _ _ CORE _ _ EXECUTOR设置为 LocalExecutor ,因为我们的目的是在本地运行管道。
- 删除 Redis 、 Flower 和 Worker 服务及其依赖项的定义,因为本地执行不需要它们。
- 将air flow _ _ CORE _ _ LOAD _ EXAMPLES设置为 false ,因为我们不希望在访问 web UI 时加载本地示例。
- 用$ { _ PIP _ ADDITIONAL _ REQUIREMENTS:-scikit-learn }填充_ PIP _ ADDITIONAL _ REQUIREMENTS,因为我们将在本例中使用 scikit-learn 库。
- 再创建两个 Docker 卷,分别为:
5.1 。/data:/opt/airflow/data ,以便存储数据。
5.2 。/models:/opt/airflow/models ,用于存储模型对象。
docker-compose.yaml 文件如下:
一旦文件保存在项目的新文件夹中,我们可以从命令行运行:
我们可以通过执行以下命令来检查正在运行的容器:

图片作者。
我们还可以检查 web 服务器生成的日志:

作者图片
确认 web UI 正确启动,并且可以通过位于 localhost:8080 的浏览器访问:

气流 UI。登录凭证在 Docker 撰写配置文件中定义。图片作者。
由于尚未创建 DAG,UI 显示一个空列表。
由于我们将连接到 PosgreSQL 数据库,我们必须创建一个连接对象:从主页中,我们应该选择管理,然后选择连接,,最后添加一条新记录。在编制表格时,我们应注明 Postgres 为连接类型:

连接的创建形式:连接用于与外部源交互。图片作者。
值得注意的是,在 UI 中指定的连接 Id 将在 Python 代码中使用,以与 PostgreSQL 数据库进行交互。
我们现在可以在主机上的项目根目录下的“ dags ”文件夹下创建我们的管道,它们将被 Airflow 自动解析并在 UI 中可用。
管道实施
我们可以把任何工作流程想象成一系列有序的步骤。
在 Airflow 中,组成工作流的每一步都被称为任务,但是仅仅有任务是不够的,因为我们需要知道何时要执行它们,以什么顺序执行,它们之间的关系和依赖关系。
因此,我们需要将我们的原子步骤包装在一个配置文件中,该文件在一个结构化的工作流中组织分散的活动。
在 Airflow 中,这个配置文件被称为 DAG⁴ (有向无环图),它代表了我们管道的主干。
简化的 DAG 可能如下所示:

图片作者。
我们可能还希望并行运行特定的任务。例如,在交叉验证训练后,我们可能希望保存超参数调整中出现的最佳参数集,同时在整个数据集上拟合最佳估计量。
这可以通过任务组对象来实现:

任务 2 和任务 3 可以并行执行。图片作者。
我们现在可以在项目的 dags 文件夹下创建 Python 文件 ml_pipeline.py :
所有任务都是从名为 operators⁵ 的预定义模板中创建的。特别是,我们使用 PostgresOperator 对 PostgreSQL 执行静态 SQL 查询,使用 PythonOperator 执行 Python 代码。
在 dags 文件夹下,我们可以创建一个名为 utils 的子文件夹来存储每个任务调用的函数。
在 utils 文件夹中,我们首先创建一个简单的配置文件来存储所有与训练相关的参数(例如训练-测试分割率等)和持久性(数据库连接、模式、表等):
我们还创建了一个函数,它将在与 docker compose 配置文件中定义的数据量相关联的路径中加载和保存数据文件:
第一个任务函数获取批处理数据。对于这个例子,我们将简单地从 scikit-learn 加载“乳腺癌数据集”⁶,以避免我们的测试环境中的外部依赖性:
然后,我们的数据集被分成:
- 训练集:用于交叉验证程序,以便找到最佳参数集(超参数调整)。
- 测试集:用于估计样本外性能。
同时,我们在 PostgreSQL 数据库上保存一份批处理数据的副本:
我们执行 k 倍交叉验证训练,以找到最佳的超参数集。我们的培训程序包括:
- 缩放训练数据。
- 执行主成分分析(PCA)进行降维。
- 拟合逻辑回归分类器。
一旦训练过程完成,我们用最佳估计器评估测试集的性能。
实验结果保存在 PostgreSQL 数据库中:
现在,我们可以在整个数据集上拟合最佳估计值,并最终使模型可用于推断:
在第一个任务中执行的两条 SQL 语句放在 sql 子文件夹中:
项目文件夹结构现在如下所示:

项目文件夹的结构。图片作者。
通过在 localhost:8080 打开浏览器,我们的 DAG 应该是可见的:

通过单击 DAG 的名称,可以在专用视图中查看更多信息:

气流用户界面的图形视图中显示的管道。图片作者。

出现在气流用户界面树视图中的管道。图片作者。
执行管道
我们可以通过启用 DAG 名称左侧的滑块并按下屏幕右侧的播放按钮,从气流 UI 触发管道。
一旦触发工作流,就可以实时监控其进度,因为每个任务都根据 DAG 的定义进行排队和运行:

DAG 执行后,所有任务都呈现“成功”状态。图片作者。
我们还可以通过甘特图观察每项任务花了多长时间。不出所料,k 倍交叉验证训练是最耗时的任务:

甘特图视图。图片作者。
模型和数据分别保存在模型和数据项目文件夹中,这得益于通过 Docker compose 配置文件安装在容器上的卷。
我们还可以从容器内部观察它们,如下所示:

图片作者。
我们将传入的批处理数据和训练结果保存在与 Airflow metastore 关联的本地 PostgreSQL 中的数据库表中。
我们可以从命令行检查这些表:

图片作者。
通过该表,我们可以跟踪不同的跑步、训练条件和最佳参数,以及测试集的准确性。
类似地,我们可以检查获取后保存的批量数据:

图片作者。
结论
Apache Airflow 是一个流行的开源 orchestrator。我们通过 Docker Compose 安装了它,并将其配置为在本地环境中运行。我们还实现了一个完全可操作的机器学习管道。
在这个例子中,为了简单起见,避免了额外的服务和到外部工具的连接。
重要的是要记住:
- 气流是一个的指挥者。理想情况下,它应该而不是执行任务,而是简单地将它们包装在一个允许调度、监控和扩展的逻辑结构中。
- 我们利用本地执行器来实现一个用于测试目的的本地工作环境。尽管如此,为了能够将任务扩展和推送到工作节点,应该使用其他类型的执行器,例如芹菜 Executor⁸ 或库伯内特 Executor⁹ 。
- 我们将数据存储在与气流的 metastore 相关联的本机 PostgreSQL 中。这允许创建一个工作示例,而无需指定进一步的服务。然而,职责分离和生命周期解耦需要将管道数据存储在编制器组件的外部。
- 我们通过利用_ PIP _ ADDITIONAL _ REQUIREMENTS配置属性安装了所需的依赖项。虽然对于测试来说很方便,但是不建议在生产系统中使用。自定义图像应建立而不是⁰.
- 在涉及大型数据集的真实世界场景中,Python 和 Pandas(以及 csv 文件)不会是最有利于数据操作的方法,而 Spark 则更可取。
- 使用 Apache Airflow 的最佳实践可在此处找到。
参考
[1]https://airflow.apache.org/
[2]https://docs.docker.com/compose/install/
【3】https://air flow . Apache . org/docs/Apache-air flow/stable/docker-compose . YAML
【4】https://air flow . Apache . org/docs/Apache-air flow/stable/concepts/DAGs . html
[5]https://air flow . Apache . org/docs/Apache-air flow/stable/concepts/operators . html
[6]https://archive . ics . UCI . edu/ml/datasets/Breast+Cancer+Wisconsin+(诊断)
https://github.com/NicoloAlbanese/airflow-ml-pipeline-mvp
【8】https://air flow . Apache . org/docs/Apache-air flow/stable/executor/celery . html
[9]https://air flow . Apache . org/docs/Apache-air flow/stable/executor/kubernetes . html
https://airflow.apache.org/docs/docker-stack/build.html
[11]https://air flow . Apache . org/docs/Apache-air flow/stable/best-practices . html
端到端机器学习项目:电信客户流失
实践教程
分析 IBM 电信数据(Kaggle 数据集)

杰里米·贝赞格在 Unsplash 上的照片
预测客户流失对于电信公司能够有效留住客户至关重要。获得新客户比留住现有客户的成本更高。出于这个原因,大型电信公司正在寻求开发模型来预测哪些客户更有可能改变并采取相应的行动。
在本文中,我们建立了一个模型来预测客户流失的可能性,方法是分析其特征:(1) 人口统计信息、( 2) 账户信息和(3) 服务信息。目标是获得一个数据驱动的解决方案,使我们能够降低流失率,从而提高客户满意度和公司收入。
数据集
本文中使用的数据集可在ka ggle(CC BY-NC-ND)和中获得,包含十九列(自变量),表明一个虚构电信公司的客户的特征。Churn列(响应变量)表示客户是否在上个月离开。类别No包括上个月没有离开公司的客户,而类别Yes包括决定终止与公司关系的客户。分析的目标是获得客户特征和流失之间的关系。
https://www.kaggle.com/blastchar/telco-customer-churn
原始的 IBM 数据可以在下面的链接中找到:
Kaggle 中可用的数据集是对原始 IBM 数据的改编。
项目步骤
该项目由以下部分组成:
- 数据读取
- 探索性数据分析和数据清洗
- 数据可视化
- 特征重要性
- 特色工程
- 设置基线
- 拆分训练集和测试集中的数据
- 评估多种算法
- 选择的算法:梯度增强
- 超参数调谐
- 模型的性能
- 得出结论—总结
1.数据读取
分析的第一步包括使用pandas.read_csv函数读取和存储 Pandas 数据帧中的数据。

作者创造的形象

作者创造的形象
如上图所示,数据集包含 19 个自变量,可分为 3 组:
(1)人口统计信息
gender:客户是女性还是男性(Female、Male)。SeniorCitizen:客户是否为老年人(0、1)。Partner:客户是否有伴侣(Yes、No)。Dependents:客户是否有家属(Yes、No)。
(2)客户账户信息
tenure:客户在公司的月数(多个不同的数值)。Contract:表示客户当前的合同类型(Month-to-Month、One year、Two year)。PaperlessBilling:客户是否无纸化开票(Yes、No)。PaymentMethod:客户的付款方式(Electronic check、Mailed check、Bank transfer (automatic)、Credit Card (automatic))。MontlyCharges:每月向客户收取的金额(多个不同的数值)。TotalCharges:向客户收取的总金额(多个不同的数值)。
(3)服务信息
PhoneService:客户端是否有电话服务(Yes、No)。MultipleLines:客户端是否有多条线路(No phone service、No、Yes)。InternetServices:客户是否向公司订购了互联网服务(DSL、Fiber optic、No)OnlineSecurity:客户端是否有在线安全(No internet service、No、Yes)。OnlineBackup:客户端是否有在线备份(No internet service、No、Yes)。DeviceProtection:客户端是否有设备保护(No internet service、No、Yes)。TechSupport:客户是否有技术支持(No internet service、No、Yes)。StreamingTV:客户端是否有流媒体电视(No internet service、No、Yes)。StreamingMovies:客户端是否有流媒体电影(No internet service、No、Yes)。
2.探索性数据分析和数据清理
探索性数据分析包括分析数据集的主要特征,通常采用可视化方法和汇总统计。目标是理解数据,发现模式和异常,并在执行进一步评估之前检查假设。
缺少值和数据类型
在 EDA 之初,我们希望了解尽可能多的关于数据的信息,这就是pandas.DataFrame.info方法派上用场的时候。该方法打印数据帧的简明摘要,包括列名及其数据类型、非空值的数量以及数据帧使用的内存量。

作者创造的形象
如上图所示,数据集包含 7043 个观察值和 21 列。显然,数据集上没有空值;然而,我们观察到列TotalCharges被错误地检测为对象。该列表示向客户收取的总金额,因此它是一个数字变量。为了进一步分析,我们需要将这个列转换成一个数字数据类型。为此,我们可以使用pd.to_numeric功能。默认情况下,该函数在看到非数字数据时会引发异常;然而,我们可以使用参数errors='coerce'跳过这些情况,用一个NaN来代替它们。
我们现在可以观察到列TotalCharges有 11 个缺失值。

作者创造的形象
这些观察值的任期也为 0,即使这些条目的MontlyCharges不为空。这一信息似乎是矛盾的,因此,我们决定从数据集中删除这些意见。
删除 customerID 列
customerID一栏对于解释客户是否会流失是没有用的。因此,我们从数据集中删除该列。
付款方式面额
如下所示,一些支付方式名称在括号中包含单词 automatic。这些名称太长,无法在进一步的可视化中用作刻度标签。因此,我们从PaymentMethod列的条目中删除了括号中的澄清。


3.数据可视化
在本节中,我们通过使用可视化来分析数据。
响应变量
下面的条形图显示了与响应变量 : no和yes的每一类相对应的观察值的百分比。如下所示,这是一个不平衡的数据集,因为两个类别在所有观测值中的分布并不均匀,其中no是多数类别( 73.42% )。建模的时候,这种不平衡会导致大量的漏报,我们后面会看到。

作者创造的形象
在本文中,我们将使用标准化堆积条形图来分析结果中每个独立分类变量的影响。
一个标准化堆积条形图使每一列具有相同的高度,因此它对于比较总数没有用处;然而,非常适合于比较所有独立变量组的响应变量如何变化。
另一方面,我们使用直方图来评估结果中每个独立数值变量的影响。如前所述,数据集不平衡;因此,我们需要画出每一类的概率密度函数(density=True),以便能够适当地比较两种分布。
人口统计信息
下面的代码为每个人口统计属性(gender、SeniorCitizen、Partner、Dependents)创建一个堆积百分比条形图,显示属性的每个类别的Churn的百分比。

人口统计信息—由作者创建的图像
如上所示,每个条形是自变量的一个类别,它被细分以显示每个响应类的比例(No和Yes)。
通过分析人口统计属性,我们可以提取出以下结论:
- 老年人的流失率几乎是年轻人的两倍。
- 我们不期望性别有显著的预测能力。当客户是男性或女性时,显示出相似的流失率。
- 有合作伙伴的客户比没有合作伙伴的客户流失少。
客户账户信息—分类变量
正如我们对人口统计属性所做的那样,我们对客户帐户属性的每个类别(Contract、PaperlessBilling、PaymentMethod)评估了Churn的百分比。

客户帐户信息—由作者创建的图像
通过分析客户账户属性,我们可以得出以下结论:
- 与拥有年度合同的客户相比,拥有月度合同的客户拥有更高的流失率。
- 选择电子支票作为支付方式的客户更有可能离开公司。
- 订阅无纸化账单的客户比未订阅的客户流失更多。
客户账户信息—数字变量
下面的图显示了tenure、MontlyCharges、TotalCharges按Churn的分布。对于所有数字属性,两个类别(No和Yes)的分布是不同的,这表明所有属性都将有助于确定客户是否翻盘。

客户帐户信息—由作者创建的图像
通过分析上面的直方图,我们可以提取出下面的结论:
- 当月费高时,流失率往往更大。
- 新客户(任期短的客户)更有可能流失。
- 总费用高的客户不太可能离开公司。
服务信息
最后,我们用堆积条形图评估每个类别的服务列的目标百分比。

服务信息—由作者创建的图像
通过评估服务属性,我们可以得出以下结论:
- 我们不期望手机属性 (
PhoneService和MultipleLines) 有显著的预测能力。两个独立变量中所有类别的流失百分比几乎相同。 - 拥有在线安全的客户比没有的客户流失少。
- 没有技术支持的客户比有技术支持的客户更容易流失。
通过查看上面的图表,我们可以确定与检测客户流失最相关的属性。我们希望这些属性在我们未来的模型中是有区别的。
4.特征重要性
互信息—线性和非线性关系的分析
互信息基于熵估计测量两个变量之间的相互依赖性。在机器学习中,我们感兴趣的是评估每个自变量与响应变量之间的依赖程度。互信息的值越高,相关性程度越高,这表明独立变量将有助于预测目标。
Scikit-Learn 库在metrics包中实现了交互信息。下面的代码计算数据集的每个分类变量和Churn变量之间的互信息得分。

作者创造的形象
互信息不仅能让我们更好地理解我们的数据,还能让我们识别出完全独立于目标的预测变量。如上图所示,gender、PhoneService、MultipleLines的互信息得分真的接近于 0 ,意味着这些变量与目标没有很强的关系。这些信息与我们之前通过可视化数据得出的结论一致。在接下来的步骤中,我们应该考虑在训练前从数据集中删除这些变量,因为它们不能提供预测结果的有用信息。

特征重要性(交互信息得分)-由作者创建的图像
互信息将相关性的概念扩展到非线性关系,因为与皮尔逊相关系数不同,该方法不仅能够检测线性关系,还能够检测非线性关系。
5.特征工程
特征工程是从数据中提取特征并将其转换成适合机器学习模型的格式的过程。在这个项目中,我们需要转换数字和分类变量。大多数机器学习算法需要数值;因此,在训练模型之前,应该将数据集中所有可用的分类属性编码到数字标签中。此外,我们需要将数字列转换成通用的刻度。这将防止具有大值的列主导学习过程。在这个项目中实现的技术将在下面详细描述。所有的转换都只使用熊猫来实现;然而,我们也提供了一个使用 Scikit-Learn 的替代实现。如您所见,有多种方法可以解决同一个问题😃。
没有修改
SeniorCitizen列已经是二进制列,不应修改。
标签编码
标签编码用于将分类值替换为数值。这个编码用一个数字标签替换每个类别。在本项目中,我们使用带有以下二进制变量的标签编码:(1) gender、(2) Partner、(3) Dependents、(4) PaperlessBilling、(5) PhoneService、(6) Churn。
一键编码
一键编码为分类变量的每一级创建一个新的二进制列。新列包含 0 和 1,表示数据中是否存在该类别。在这个项目中,我们对以下分类变量应用一键编码:(1) Contract、(2) PaymentMethod、(3) MultipleLines、(4) InternetServices、(5) OnlineSecurity、(6) OnlineBackup、(7) DeviceProtection、(8) TechSupport、(9) StreamingTV和(10) StreamingMovies。
这种编码的主要缺点是数据集的维数显著增加(维数灾难);因此,当分类列有大量唯一值时,应避免使用此方法。
正常化
数据规范化是机器学习中的常见做法,包括将数字列转换为通用比例。在机器学习中,有些特征值与其他特征值相差很多倍。具有较高值的特征将主导学习过程;然而,这并不意味着这些变量对预测目标更重要。数据标准化将多尺度数据转换为相同尺度。归一化后,所有变量对模型都有相似的影响,提高了学习算法的稳定性和性能。
统计学中有多种归一化技术。在这个项目中,我们将使用最小-最大方法将数字列(tenure、MontlyCharges和TotalCharges)调整到一个通用的比例。最小-最大方法(通常称为归一化)通过减去特征的最小值,然后除以范围,将特征重新缩放到 [0,1] 的固定范围。
6.设定基线
在机器学习中,我们经常使用一个叫做基线的简单分类器来评估一个模型的性能。在这个分类问题中,没有流失的客户比率(最频繁的类别)可以用作基线来评估生成的模型的质量。这些模型应该优于未来预测所考虑的基线能力。
7.拆分训练集和测试集中的数据
建立模型的第一步是将数据分成两组,通常称为训练和测试集。机器学习算法使用训练集来构建模型。测试集包含不属于学习过程的样本,用于评估模型的性能。使用看不见的数据来评估模型的质量以保证客观的评估是很重要的。

训练集和测试集—作者创建的图像
首先,我们创建一个变量X来存储数据集的独立属性。此外,我们创建一个变量y来只存储目标变量 ( Churn)。

然后,我们可以使用sklearn.model_selection包中的train_test_split函数来创建训练集和测试集。
8.评估多种算法
算法选择是任何机器学习项目中的一个关键挑战,因为没有一种算法是所有项目中最好的。通常,我们需要评估一组潜在候选人,并选择那些提供更好性能的候选人进行进一步评估。
在这个项目中,我们比较了 6 种不同的算法,它们都已经在 Scikit-Learn 中实现。
- 虚拟分类器(基线)
- K 个最近邻居
- 逻辑回归
- 支持向量机
- 随机森林
- 梯度推进

评估多种算法—图片由作者创建
如下所示,所有模型在预测精度方面都优于虚拟分类器模型。因此,我们可以确认机器学习适用于我们的问题,因为我们观察到基线上的改进。

重要的是要记住我们已经使用默认的超参数训练了所有的算法。许多机器学习算法的准确性对选择用于训练模型的超参数高度敏感。更深入的分析将包括在为超参数调整选择一个(或多个)模型之前,对更广泛的超参数(不仅仅是默认值)进行评估。尽管如此,这超出了本文的范围。在本例中,我们将仅进一步评估使用默认超参数呈现更高准确性的模型。如上所示,这对应于梯度增强模型,其显示了接近 80%的准确度。
9.选择的算法:梯度增强
梯度推进是一种非常流行的机器学习集成方法,基于多个模型的顺序训练进行预测。在梯度推进中,首先,使用原始数据的随机样本建立一个模型。拟合模型后,进行预测并计算模型的残差。残差是实际值和模型预测值之间的差值。然后,根据前一个树的残差训练一个新的树,再次计算这个新模型的残差。我们重复这个过程,直到达到一个阈值(残差接近 0),这意味着实际值和预测值之间的差异非常小。最后,你取所有模型预测(数据的预测和误差的预测)的总和,做出最终预测。

梯度推进分类器—由作者创建的图像
我们可以使用来自sklearn.ensemble模块的GradientBoostingClassifier类,通过 Scikit-Learn 轻松构建一个梯度增强分类器。在创建了模型之后,我们需要训练它(使用.fit方法)并且通过比较预测值(.predict方法)和实际的类值来测试它的性能,正如你在上面的代码中看到的。
如 Scikit-Learn 文档(以下链接)所示,GradientBoostingClassifier有多个超参数;其中一些列举如下:
learning_rate:每棵树对最终预测的贡献。n_estimators:要执行的决策树数量(提升阶段)。max_depth:单个回归估计量的最大深度。max_features:寻找最佳分割时要考虑的特征数量。min_samples_split:分割内部节点所需的最小样本数。
https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingClassifier.html
下一步是找到超参数的组合,从而对我们的数据进行最佳分类。这个过程称为超参数调整。
10.超参数调谐
到目前为止,我们已经将数据分为训练集用于学习模型的参数,以及测试集用于评估其性能。机器学习过程的下一步是执行超参数调整。超参数的选择包括根据超参数的不同组合测试模型的性能,根据选择的度量和验证方法选择那些性能最好的。
对于超参数调整,我们需要将我们的训练数据再次分成一组用于训练的数据和一组用于测试超参数的数据(通常称为验证集)。使用 k 倍 交叉验证进行超参数调优是非常常见的做法。训练集被再次分成 k 个大小相等的样本,1 个样本用于测试,其余 k-1 个样本用于训练模型,重复 k 次。然后,k 个评估度量(在这种情况下是精确度)被平均以产生单个估计量。
需要强调的是验证集用于超参数选择,而不是评估我们模型的最终性能,如下图所示。

交叉验证的超参数调整—图片由作者创建
有多种技术可以找到模型的最佳超参数。最常用的方法有(1) 网格搜索、(2) 随机搜索、(3) 贝叶斯优化。网格搜索测试超参数的所有组合并选择表现最好的一个。这是一个非常耗时的方法,尤其是当要尝试的超参数和值的数量非常大的时候。
在随机搜索中,您指定一个超参数网格,然后随机组合被选择,其中每个超参数组合被采样的机会相等。我们不分析超参数的所有组合,而只分析这些组合的随机样本。这种方法在计算上比尝试所有组合更有效;然而,它也有一些缺点。随机搜索的主要缺点是并非网格的所有区域都被均匀覆盖,尤其是当从网格中选择的组合数量很少时。

网格搜索与随机搜索——作者创建的图像
我们可以使用sklearn.model_selection包中的RandomSearchCV类在 Scikit-learn 中实现随机搜索。
首先,我们使用字典(grid_parameters)指定超参数值的网格,其中键表示超参数,而值是我们想要评估的选项集。然后,我们定义了RandomizedSearchCV对象,用于从这个网格中尝试不同的随机组合。被采样的超参数组合的数量在n_iter参数中定义。自然地,增加n_iter将在大多数情况下导致更精确的结果,因为更多的组合被采样;然而,在许多情况下,性能的提高并不显著。

作者创造的形象
拟合网格对象后,我们可以使用best_params_属性获得最佳超参数。如上所述,最好的超参数是: {'n_estimators': 90,' min_samples_split': 3,' max_features': 'log2 ',' max_depth': 3} 。
11.模型的性能
机器学习过程的最后一步是通过使用混淆矩阵和一些评估指标来检查模型(最佳超参数)的性能。
混淆矩阵
混淆矩阵,也称为误差矩阵,用于通过检查正确和错误分类的观察值数量来评估机器学习模型的性能。矩阵的每一列包含预测类别,而每一行代表实际类别,反之亦然。在完美的分类中,除了对角线之外,混淆矩阵将全为零。主对角线之外的所有元素代表错误分类。重要的是要记住,混淆矩阵允许我们观察错误分类的模式(错误分类的类别和程度)。
在二元分类问题中,混淆矩阵是一个由 4 个元素组成的 2 乘 2 矩阵:
- TP(真阳性):被正确归类为患病的脊柱问题患者人数。
- TN(真阴性):被正确分类为健康的无病变患者的数量。
- FP(假阳性):被错误归类为患病的健康患者人数。
- FN(假阴性):被误分类为健康的脊柱疾病患者数量。

困惑矩阵——作者创造的图像
既然模型已经训练好了,是时候使用测试集来评估它的性能了。首先,我们使用之前的模型(具有最佳超参数的梯度推进分类器)来预测测试数据的类别标签(使用predict方法)。然后,我们使用来自sklearn.metrics包的confusion_matrix函数构建混淆矩阵,以检查哪些观察值被正确分类。输出是一个 NumPy 数组,其中行表示真实值而列表示预测类。

如上所示,测试数据的 1402 个观察值被模型正确分类(1154 个真阴性和 248 个真阳性)。相反,我们可以观察到 356 个错误分类(156 个假阳性和 200 个假阴性)。
评估指标
评估模型的质量是机器学习过程的基本部分。最常用的绩效评估指标是根据混淆矩阵的元素计算出来的。
- 准确性:它代表被正确分类的预测的比例。准确性是最常用的评估指标;但是,请务必记住,在处理不平衡的数据集时,准确性可能会产生误导。

- 灵敏度:表示被识别为阳性的样本(患病患者)的比例。

- 特异性:表示阴性样本(健康患者)的比例。

- 精度:代表实际正确的正面预测比例。

我们可以使用混淆矩阵的数字来手动计算评估指标。或者,Scikit-learn 已经实现了函数classification_report,该函数为提供了关键评估指标的摘要。分类报告包含每个类别达到的精度、灵敏度、f1 值和支持度(样本数)。

作者创造的形象
如上图所示,我们获得了 0.55 (248/(200+248))的灵敏度和 0.88 (1154/(1154+156))的特异性。所获得的模型更准确地预测了不会流失的客户。这不应该让我们感到惊讶,因为梯度提升分类器通常偏向于具有更多观察值的类别。
您可能已经注意到,之前的总结不包含分类的准确性。然而,这可以通过使用metrics模块中的函数accuracy_score很容易地计算出来。

正如您所观察到的,超参数调整几乎没有增加模型的准确性。
12.得出结论——总结
在本帖中,我们使用电信客户流失数据集完成了一个完整的端到端机器学习项目。我们从清理数据开始,并用可视化分析它。然后,为了能够建立机器学习模型,我们将分类数据转换为数字变量(特征工程)。在转换数据后,我们使用默认参数尝试了 6 种不同的机器学习算法。最后,我们调整了用于模型优化的梯度提升分类器(最佳性能模型)的超参数,获得了近 80% (比基线高近 6%)的准确度。
需要强调的是,机器学习任务的确切步骤因项目而异。虽然在文章中我们遵循了线性过程,但机器学习项目往往是迭代的,而不是线性过程,当我们对我们试图解决的问题了解更多时,以前的步骤往往会被重新审视。
阿曼达·伊格莱西亚斯
端到端机器学习项目:使用 Flask 和 Heroku 将模型训练和部署为 Web 应用程序

作者图片

https://www . pexels . com/photo/high-angle-photo-of-robot-2599244/
使用机器学习构建糖尿病预测应用

https://www . pexels . com/photo/time-lapse-photography-of-blue-lights-373543/
商业问题:人工智能未来将发挥巨大作用的领域之一是医学。医生和研究人员一直试图使用机器学习和深度学习来学习癌症和其他慢性疾病的发生,方法是使用通过我们 DNA 和其他生活方式属性的蛋白质组合获得的数百万个数据点。在未来,我们可能能够提前十年或二十年知道我们患癌症的几率,从而帮助我们避免癌症。幸运的是,在我寻找一个好的医学科学数据集时,我在 Kaggle 上看到了这个皮马印第安人糖尿病数据集。它是从国家糖尿病、消化和肾病研究所收集的。这个数据集很小,有 9 个特征和 768 个观察值,足以解决预测一个人患糖尿病的概率的问题。
下面是我从数据源本身引用的所有特性的简要描述,供您参考。
链接到数据集:https://www.kaggle.com/uciml/pima-indians-diabetes-database
在阅读之前,请随意感受一下这个应用程序(😃:
数据集详情
1:怀孕次数:怀孕次数
2:葡萄糖:口服葡萄糖耐量试验中 2 小时的血浆葡萄糖浓度。
3:血压:舒张压(毫米汞柱)
4:皮肤厚度:三头肌皮褶厚度(mm)
5:胰岛素:2 小时血清胰岛素(μU/ml)
6:身体质量指数:身体质量指数(体重公斤/(身高米)
7:糖尿病谱系功能:糖尿病谱系功能
8:年龄:年龄(岁)
9:结果:768 个类变量(0 或 1)中的 268 个为 1,其余为 0
所有的变量要么是已知的,要么可以在简单的血液测试中获得,而“结果”(糖尿病/非糖尿病)是我们需要预测的。
我们将探索不同的功能,并在尝试不同的机器学习算法(如逻辑回归、支持向量机、决策森林、梯度推进)之前执行各种预处理技术,最后我们还将探索神经网络。一旦我们有了最佳模型,我们将使用 Pickle 保存我们的模型,并使用 Flask web 框架开发一个糖尿病预测应用程序,然后使用 Heroku 部署它。
我们开始吧。拿杯咖啡!!
结构的简要概述:如果您不想概述准备和建模部分,请随意跳到步骤 2。
第一步:数据准备和模型建立
在这一步中,我们将探索数据,进行所需的预处理,并尝试各种机器学习模型,如逻辑回归、支持向量机、随机森林、梯度推进以及神经网络等最先进的模型。
第二步:使用 Flask 和 HTML 构建应用
在这里,我们将从步骤 1 中获取性能最好的模型,并使用 Flask 和 HTML 构建一个 web 应用程序。
步骤 3:使用 Heroku 部署应用
最终,我们将通过 Heroku 部署我们的工作应用,让全世界使用我们的产品。
第一步:数据准备和模型建立
你可以在我的 Jupiter 笔记本上继续看下去,该笔记本可以从以下网址获得:https://github . com/garo disk/Diabetes-prediction-app-using-ML/blob/main/Diabetes % 20 prediction % 20 using % 20 machine % 20 learning . ipynb
#importing reqd libraries
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
df.head()
对于数据的感觉,让我们打印头部:

作者图片

作者图片

作者图片
尽管数据的顶级概览显示没有空值,但更深入的分析显示许多属性都有 0 值,这没有任何意义。怎么会有人的身体质量指数/皮肤厚度/年龄是 0 呢?
让我们看看每个属性有多少个零值,并把它们转换成空值。我们稍后将使用插补技术处理这些空值。

每个属性的值为零(图片由作者提供)

用空值替换零(作者图片)
现在,我们已经将所有的零值转换为空值,我们的下一步是估算这些空值。在这一点上,许多人只是使用一个简单的均值/中位数插补,他们使用整个列进行计算,这是不正确的。
为了输入每个空值,我们将查看结果是否属于糖尿病患者。我们将根据我们将看到的结果,使用特定属性的中值进行估算。如果一个空值属于糖尿病患者,我们将只使用糖尿病患者的记录来寻找中位数,同样,如果它属于非糖尿病患者,我们将使用非糖尿病患者的记录来寻找中位数。

作者图片

基于结果的推断(图片由作者提供)
让我们分析一下相关图和直方图,以进一步了解数据。

作者图片

作者图片

红色部分为糖尿病患者,蓝色部分为非糖尿病患者(图片由作者提供)

作者图片
我们可以看到,对于大多数属性,与非糖尿病人的分布(蓝色部分)相比,糖尿病人的分布(红色部分)向右移动。这基本上告诉我们一个故事,糖尿病患者更可能是一个老年人,具有更高的身体质量指数、皮肤厚度和葡萄糖水平。
接下来,我们将绘制这些属性的箱线图,以清楚地看到这些结果(糖尿病和非糖尿病)的每个属性的分布差异。



作者图片



作者图片


我们现在可以清楚地看到不同之处(图片由作者提供)
这是结果变量的分布:

作者图片
该数据包含 500 名非糖尿病人和 268 名糖尿病人。
现在,让我们使用 PCA 和 t-SNE 将数据可视化在二维平面上,以获得更好的直觉。

作者图片

作者图片

作者图片
PCA 在 2-d 可视化方面做得相当不错,因为 2 个主成分包含了数据总方差的大约 50%。现在,让我们试试 t-SNE,它更适合在 2-d 上可视化,因为它使用概率分布,并试图使相似的数据点在 2-d 平面上彼此更接近。

作者图片

作者图片
它确实做了一件伟大的工作。我们可以看到糖尿病人和非糖尿病人大多聚集在 t-SNE 图上。
现在,在建模之前,我们必须对数据进行缩放,因为所有属性的缩放比例不同。除了树算法,大多数机器学习算法,尤其是那些使用梯度下降或距离度量的算法,都需要缩放。
讨论最多的两种缩放方法是规范化和标准化。 正常化 通常意味着将数值重新调整到[0,1]的范围内。 标准化 通常意味着重新调整数据,使平均值为 0,标准差为 1(单位方差)。
规范化 vs .标准化是机器学习新人中永恒的问题。
- 当您知道数据的分布不符合高斯分布时,可以使用归一化。这在不假设任何数据分布的算法中很有用,例如 K-最近邻和神经网络。
- 另一方面,在数据遵循高斯分布的情况下,标准化会有所帮助。然而,这并不一定是真的。此外,与标准化不同,标准化没有边界范围。因此,即使您的数据中有异常值,它们也不会受到标准化的影响。
- 没有硬性的规则告诉我们什么时候对数据进行规范化或标准化。我们总是可以从将您的模型与原始的、规范化的和标准化的数据进行拟合开始,并比较性能以获得最佳结果。
- 重要提示:在训练数据上安装缩放器,然后用它来转换测试数据,这是一个很好的实践。这将避免模型测试过程中的任何数据泄漏。此外,通常不需要目标值的缩放。
现在,让我们再次查看每个属性的分布,以了解哪些属性遵循高斯分布。

作者图片
只有葡萄糖、血压和身体质量指数遵循高斯分布,其中标准化是有意义的,但是由于没有硬性规定,我们将尝试三种情况并比较它们的性能。
- 对所有属性进行标准化,并在测试集上检查逻辑回归模型的性能
- 在所有属性上使用标准化,并在测试集上检查逻辑回归模型的性能
- 对遵循高斯分布的属性使用标准化,对其余属性使用标准化,并观察性能
在上述 3 种方法中,归一化法在使用逻辑回归模型的测试集上具有最佳精度 0.83 。

作者图片

作者图片
重复注释:要记住的另一件重要事情是,只在训练集上使用标准标量,然后用它来缩放测试集,以避免数据泄漏。我们还将保存预处理器,以便在我们的机器学习应用程序中进一步使用。
逻辑回归(准确率- 83%):
现在,让我们尝试其他机器学习算法:
K-最近邻(准确率- 87%):

作者图片
支持向量机(准确率- 88%-89%):

作者图片
随机森林(准确率- 88%-89%):

作者图片
梯度增强(准确率- 87%-88%):

作者图片
在数据集上表现最好的机器学习算法是支持向量机和随机森林;两者的准确率都在 88%到 89%之间,但是支持向量机对于部署来说更加简单,并且当进一步的数据进入训练时将花费更少的时间。在使用 SVM 之前,让我们尝试看看神经网络如何在数据集上执行。

作者图片

作者图片

作者图片

作者图片
在测试集上,神经网络只有 85%的准确率。这是非常可能的,因为神经网络创建了一组更复杂的隐藏层,但同时,它需要越来越多的例子来获得更好的结果。我们的数据只包含 768 个观察值,它的表现很好。
神经网络(准确率- 85%)
现在,作为最后一步,我们将把预测的 SVM 模型保存到. h5 或。使用类似于pickle的库来绑定文件。

作者图片
第二步:使用 Flask 和 HTML 构建应用
下一步是将这个模型打包成一个 web 服务,当通过 POST 请求获得数据时,它会返回糖尿病预测概率作为响应。
为此,我们将使用 Flask web 框架,这是一个在 Python 中开发 web 服务的常用轻量级框架。
Flask 是一个 web 框架,可以用来比较快速的开发 web 应用。你可以在这里 找到一个快速开发 的演练。
下面的**app.py**文件中的代码本质上是建立主页,并为用户提供index.html:
#import relevant libraries for flask, html rendering and loading the #ML modelfrom flask import Flask,request, url_for, redirect, render_template
import pickle
import pandas as pdapp = Flask(__name__)#loading the SVM model and the preprocessor
model = pickle.load(open(“svm_model.pkl”, “rb”))
std = pickle.load(open(‘std.pkl’,’rb’))#Index.html will be returned for the input
[@app](http://twitter.com/app).route(‘/’)
def hello_world():
return render_template(“index.html”)#predict function, POST method to take in inputs
[@app](http://twitter.com/app).route(‘/predict’,methods=[‘POST’,’GET’])
def predict():#take inputs for all the attributes through the HTML form
pregnancies = request.form[‘1’]
glucose = request.form[‘2’]
bloodpressure = request.form[‘3’]
skinthickness = request.form[‘4’]
insulin = request.form[‘5’]
bmi = request.form[‘6’]
diabetespedigreefunction = request.form[‘7’]
age = request.form[‘8’]#form a dataframe with the inpus and run the preprocessor as used in the training
row_df = pd.DataFrame([pd.Series([pregnancies, glucose, bloodpressure, skinthickness, insulin, bmi, diabetespedigreefunction, age])])
row_df = pd.DataFrame(std.transform(row_df))
print(row_df)#predict the probability and return the probability of being a diabetic
prediction=model.predict_proba(row_df)
output=’{0:.{1}f}’.format(prediction[0][1], 2)
output_print = str(float(output)*100)+’%’
if float(output)>0.5:
return render_template(‘result.html’,pred=f’You have a chance of having diabetes.\nProbability of you being a diabetic is {output_print}.\nEat clean and exercise regularly’)
else:
return render_template(‘result.html’,pred=f’Congratulations, you are safe.\n Probability of you being a diabetic is {output_print}’)if __name__ == ‘__main__’:
app.run(debug=True)
详细步骤(app.py):
创建一个新文件 app.py。
导入 flask 模块,通过实例化 Flask 类创建 Flask 应用程序。
#import relevant libraries for flask, html rendering and loading the ML modelfrom flask import Flask,request, url_for, redirect, render_template
import pickle
import pandas as pdapp = Flask(__name__)
现在,让我们导入保存的预处理元素和模型。
#loading the SVM model and the preprocessor
model = pickle.load(open(“svm_model.pkl”, “rb”))
std = pickle.load(open(‘std.pkl’,’rb’))
现在,让我们定义将呈现index.html网页(使用 HTML 创建)的路径。这个文件有 CSS 运行和外观的背景,并有相关的字段供用户输入属性值。
#Index.html will be returned for the input
[@app](http://twitter.com/app).route(‘/’)
def hello_world():
return render_template(“index.html”)
让我们也定义一下predict/路线和与之对应的函数,该函数将接受不同的输入值,并使用 SVM 模型返回预测值。
- 首先,我们将使用请求方法从用户处获取数据,并将值存储在各自的变量中。
- 现在,我们将 预处理 使用我们上面加载的标量 预处理器 并使用 模型 到 预测 一个人患糖尿病的概率
- 接下来,我们将呈现result.html页面,并根据 预测 显示相关输出
*#predict function, POST method to take in inputs
[@app](http://twitter.com/app).route(‘/predict’,methods=[‘POST’,’GET’])
def predict():#take inputs for all the attributes through the HTML form
pregnancies = request.form[‘1’]
glucose = request.form[‘2’]
bloodpressure = request.form[‘3’]
skinthickness = request.form[‘4’]
insulin = request.form[‘5’]
bmi = request.form[‘6’]
diabetespedigreefunction = request.form[‘7’]
age = request.form[‘8’]#form a dataframe with the inpus and run the preprocessor as used in the training
row_df = pd.DataFrame([pd.Series([pregnancies, glucose, bloodpressure, skinthickness, insulin, bmi, diabetespedigreefunction, age])])
row_df = pd.DataFrame(std.transform(row_df))
print(row_df)#predict the probability and return the probability of being a diabetic
prediction=model.predict_proba(row_df)
output=’{0:.{1}f}’.format(prediction[0][1], 2)
output_print = str(float(output)*100)+’%’
if float(output)>0.5:
return render_template(‘result.html’,pred=f’You have a chance of having diabetes.\nProbability of you being a diabetic is {output_print}.\nEat clean and exercise regularly’)
else:
return render_template(‘result.html’,pred=f’Congratulations, you are safe.\n Probability of you being a diabetic is {output_print}’)*
现在,让我们在运行 flask 应用程序之前放置最后一段代码。
*if __name__ == '__main__':
app.run(debug=True)*
从终端,我们可以使用 python 环境运行应用程序:

作者图片
是时候庆祝了。我们的应用程序正在本地运行,如果你也遵循代码。如果没有,不要担心,我们也将为公众部署在 Heroku 上。 http://127.0.0.1:5000/

主页(图片由作者提供)
第三步:使用 Heroku 部署应用

作者图片
什么是 Heroku?
Heroku 是一个平台即服务工具,允许开发者托管他们的无服务器代码。这意味着人们可以开发脚本来为特定的目的服务。Heroku 平台本身托管在 AWS(亚马逊网络服务)上,AWS 是一个基础设施即服务工具。
我们将使用 Heroku 进行托管,因为他们有一个很好的非商业应用免费层。
部署应用程序有多种方式。最常见的一种方式是* 构建一个 docker 然后将 docker 部署到Heroku*平台中。在这里,由于数据和模型是公开的,我们将只使用 Github,然后在 Heroku 中部署 Github 存储库。**
让我们首先为应用程序创建所需的文件夹结构。
*diabetes(root)
|____templates
|___index.html #main html page to enter the data
|___result.html #Page returned after pressing submit |____static
|____css #code for the look and feel of the web app
|_____js
|____app.py #main file with flask and prediction code
|_____svm_model.pkl #model
|_____std.pkl #preprocessor
|_____requirements.txt #Library list with versions
|_____Procfile*
- 模板:index.html包含了引入 web 表单的 HTML 代码,用户可以在其中输入不同属性的值。【result.html】**包含了预测页面的代码。**
- static : static 包含了 CSS,其中包含了 HTML 页面外观的代码
- app.py 是主文件,如前一节所述
- svm_model 和 std.pkl 分别是模型和预处理器,将用于在新数据中进行预测
- requirements.txt 包含了所有被使用的库及其版本的细节
*Flask==1.1.1
gunicorn==19.9.0
itsdangerous==1.1.0
Jinja2==2.10.1
MarkupSafe==1.1.1
Werkzeug==0.15.5
numpy>=1.9.2
scipy>=0.15.1
scikit-learn>=0.18
matplotlib>=1.4.3
pandas>=0.19*
6。Procfile :包含在服务器上运行应用程序的命令。
*web: gunicorn app:app*
上面的第一个 app 是包含我们的应用程序(app.py)和代码的 python 文件的名称。第二个是路由的名称,如下所示。
*#predict function, POST method to take in inputs
[@app](http://twitter.com/app).route(‘/predict’,methods=[‘POST’,’GET’])
def predict():#take inputs for all the attributes through the HTML form
pregnancies = request.form[‘1’]
glucose = request.form[‘2’]
bloodpressure = request.form[‘3’]
skinthickness = request.form[‘4’]
insulin = request.form[‘5’]
bmi = request.form[‘6’]
diabetespedigreefunction = request.form[‘7’]
age = request.form[‘8’]*
guni corn:并发处理传入 HTTP 请求的 web 应用程序比一次只处理一个请求的 Web 应用程序更有效地利用 dyno 资源。因此,我们建议在开发和运行生产服务时使用支持并发请求处理的 web 服务器。**
现在我们已经万事俱备,下一步是将项目提交给一个新的 Github 库。
我刚刚在一个新的 Github 存储库中上传了糖尿病根文件夹,以及上面描述的结构中的所有文件。

作者图片

Github 截图(图片由作者提供)
我们只需要创建一个 Heroku 帐户,创建一个新的应用程序,连接到 Github 并部署我们新创建的存储库。

创建新应用程序(图片由作者提供)

作者图片

连接到右边的 Github 库(图片由作者提供)
恭喜,我们能够部署我们的机器学习应用程序了。现在,让我们访问 web 应用程序链接,并使用不同的属性值检查成为糖尿病患者的概率。
请随意使用 web 应用程序。以下是链接:
https://predict-diabetes-using-ml.herokuapp.com/

主页(图片由作者提供)

非糖尿病人的输出(图片由作者提供)

糖尿病患者的输出(图片由作者提供)
按照我的代码,这里是我的 Github 库的链接:https://Github . com/garo disk/Diabetes-prediction-app-using-ML
感谢阅读。
你可以在 Linkedin 上联系我:https://www.linkedin.com/in/saket-garodia/
以下是我的一些其他博客:
推荐系统(使用 Spark):https://towards data science . com/building-a-Recommendation-engine-to-recommended-books-in-Spark-f 09334d 47d 67
模拟
https://towards data science . com/gambling-with-a-statistics-brain-AE 4 E0 b 854 ca 2
购物篮分析
电影推荐系统
https://medium.com/analytics-vidhya/the-world-of-recommender-systems-e4ea504341ac
信用违约分析
参考资料:
https://www.upgrad.com/blog/deploying-machine-learning-models-on-heroku/ https://devcenter.heroku.com/articles/python-gunicorn https://stackabuse.com/deploying-a-flask-application-to-heroku/ https://www.analyticsvidhya.com/blog/2020/04/feature-scaling-machine-learning-normalization-standardization/
谢谢大家!!*
使用纽约死亡率数据的端到端闪亮应用教程
面向初学者的概念概述和逐步指南

照片由energepic.com从派克斯拍摄
许多数据科学家和数据分析师使用 Shiny 来创建交互式可视化和 web 应用程序。虽然 Shiny 是一个 RStudio 产品,并且非常用户友好,但 Shiny 应用程序的开发与您可能通过 RMarkdown 文件中的 tidyverse 进行的数据可视化和探索有很大不同。因此,即使对于有经验的 R 用户来说,也可能有一点闪亮的学习曲线,本教程旨在通过一个简短的概念概述以及一步一步创建一个示例 Shiny 应用程序来介绍 Shiny 的用法和功能。
在本教程中,我们将创建以下闪亮的应用程序,可以在它的完整交互形式这里找到。相关的 GitHub repo 可以在这里找到。该仪表板允许用户按年份、性别和种族/民族探索代表纽约人十大死亡原因的数据。我们将有一个条形图,显示与这些主要死亡原因相关的死亡人数,以及一个描述主要死亡原因、相关死亡人数、相关死亡率以及相关年龄调整死亡率的表格。

简短闪亮的概述
什么是闪亮的?
Shiny 是一个 R 包,旨在促进交互式 web 应用和仪表板的创建。所有的编码都可以在 R 中完成,所以有效地使用 Shiny 不需要任何 web 开发技能。然而,Shiny 确实使用 CSS、HTML 和 JavaScript 框架,因此熟悉这些语言可以促进更高级的应用程序开发。您可以轻松托管和部署闪亮的应用程序,RStudio 分别通过 shinyapps.io 和 Shiny Server 方便地提供云和内部部署选项。
我强烈建议花些时间探索一下闪亮的画廊。这是一个很好的资源,在这里你可以看到其他人用 Shiny 构建了什么,以及所有的底层代码。画廊里还有闪亮的演示,由闪亮的开发者设计,通过简单的例子来说明各种功能。
用户界面
闪亮的应用程序由用户界面(UI)和响应 UI 并生成输出的服务器组成。
UI 本质上负责应用程序的外观和输入。通过改变应用程序的布局和颜色,或者通过创建额外的选项卡,可以在 UI 中设置外观。
用户通过小部件创建输入,这些小部件编码在闪亮应用的 UI 部分。小部件可以有多种形式,比如按钮、复选框、滑块和文本输入。RStudio 的闪亮小部件库允许你浏览可用的小部件选项,以及产生它们的相关代码。
计算机网络服务器
服务器是您构建输出的地方,例如图形、地图、值和表格。服务器将包含生成这些输出的代码,以及关于输出应该如何根据 UI 中的给定输入而改变的 R 指令。
数据源和数据清理
我们将在本教程中使用的数据集可通过 NYC OpenData 公开获取。该数据代表了 2007 年至 2014 年间纽约市居民按性别和种族分列的十大死亡原因。鉴于我在公共卫生方面的训练,我认为探索种族和性别与死亡原因之间的关系以及这些关系如何随着时间的推移而变化会很有趣。
为了下载这些数据,请访问 NYC OpenData 站点获取该数据源。数据将以 CSV 文件的形式出现在下面突出显示的“导出”下。当您单击 CSV 按钮时,下载应该会立即开始。

这些数据相当整洁,但是我们还需要做一些清理工作。我将文件名改为“nyc_death.csv ”,并将其放在一个新的 R 项目中名为“data”的文件夹中。
首先,让我们将“tidyverse”包加载到一个新的 RMarkdown 文件中:
library(tidyverse)
下一个代码块执行一些基本的清理过程。首先,我们使用 read_csv()函数加载死亡率数据。然后,我们使用“看门人”包中的 clean_names()函数将所有变量名放入 snake case 中。接下来,我们执行一些过滤。出于此应用程序的目的,我们只需要具有已识别的种族/民族的个人,这样我们就可以过滤掉种族 _ 民族值为“未声明/未知”或“其他种族/民族”的条目该数据集还包括每个种族和性别组合的前 10 大死亡原因,以及所有其他原因。我们只需要这些死亡的主要原因,所以过滤掉 leading_cause 值为“所有其他原因”的行最后,我们将 as.numeric()应用于我们将使用的三个变量,以便它们具有适合我们分析的格式:
death = read_csv("./data/nyc_death.csv") %>%
janitor::clean_names() %>%
filter(
race_ethnicity != "Not Stated/Unknown",
race_ethnicity != "Other Race/ Ethnicity",
leading_cause != "All Other Causes"
) %>%
mutate(
deaths = as.numeric(deaths),
death_rate = as.numeric(death_rate),
age_adjusted_death_rate = as.numeric(age_adjusted_death_rate))
在进行了这种基本清理之后,我们的数据看起来像这样:

总的来说,这看起来不错。然而,我们的 leading_cause 变量的值非常长,并且包括死亡原因代码。我们的目标观众是普通观众,所以这些死因代码可能没有那么多信息。这个变量也将用于我们 Shiny 应用程序的条形图部分,x 轴标签直接来自这个 leading_cause 变量。这些非常长的描述会导致我们的条形图的 x 轴标签可读性很差。因此,将这些值编辑成简短的、描述性的标签而没有死因代码是有意义的。
做出这种改变的第一步是确定我们需要改变什么样的价值观。因此,我们需要知道这个数据集中存在的 leading_cause 变量的所有唯一值。unique()函数可以给我们这些信息:
unique(death$leading_cause)
我们可以看到 leading_cause 有 19 个独特的值,我们需要考虑缩短:

为了编辑这些值,我们需要深入下面的长 mutate()步骤。在 mutate()步骤的第一行,我们使用 str_replace_all()删除这个 leading_cause 变量中的所有括号。正如您在上面看到的,目前所有的值都有包含在括号中的死亡原因代码。当文本中包含括号时,我们用来替换文本的函数 str_replace()无法正常工作,因此立即删除括号是有意义的。
接下来,我们简单地使用 str_replace()来替换每个死亡原因值的文本。(注意:我直接从 unique()输出中复制了这些值,以便于我自己使用——但是如果您这样做的话,不要忘记删除括号!)str_replace()函数接受以下三个参数:应该进行字符串替换的变量名、要替换的文本模式以及要替换的内容。正如您将在下面看到的,这些 str_replace 步骤中的每一步都将 leading_cause 作为感兴趣的变量,然后是原始死因标签,最后是在我们的条形图中看起来更好的缩短版本:
death =
death %>%
mutate(
leading_cause = str_replace_all(leading_cause, "[//(//)]", ""),
leading_cause = str_replace(leading_cause, "Influenza Flu and Pneumonia J09-J18", "Influenza & Pneumonia"),
leading_cause = str_replace(leading_cause, "Accidents Except Drug Posioning V01-X39, X43, X45-X59, Y85-Y86", "Accidents"),
leading_cause = str_replace(leading_cause, "Cerebrovascular Disease Stroke: I60-I69", "Cerebrovascular Disease"),
leading_cause = str_replace(leading_cause, "Assault Homicide: Y87.1, X85-Y09", "Assault"),
leading_cause = str_replace(leading_cause, "Essential Hypertension and Renal Diseases (I10, I12)", "Hypertension & Renal Dis."),
leading_cause = str_replace(leading_cause, "Human Immunodeficiency Virus Disease HIV: B20-B24", "HIV"),
leading_cause = str_replace(leading_cause, "Diseases of Heart I00-I09, I11, I13, I20-I51", "Diseases of Heart"),
leading_cause = str_replace(leading_cause, "Alzheimer's Disease G30", "Alzheimer's Disease"),
leading_cause = str_replace(leading_cause, "Chronic Liver Disease and Cirrhosis K70, K73", "Chronic Liver Disease/Cirrhosis"),
leading_cause = str_replace(leading_cause, "Malignant Neoplasms Cancer: C00-C97", "Malignant Neoplasms"),
leading_cause = str_replace(leading_cause, "Diabetes Mellitus E10-E14", "Diabetes Mellitus"),
leading_cause = str_replace(leading_cause, "Mental and Behavioral Disorders due to Accidental Poisoning and Other Psychoactive Substance Use F11-F16, F18-F19, X40-X42, X44", "Accidental Poisoning/Substance Use"),
leading_cause = str_replace(leading_cause, "Septicemia A40-A41", "Septicemia"),
leading_cause = str_replace(leading_cause, "Chronic Lower Respiratory Diseases J40-J47", "Chronic Lower Respiratory Dis."),
leading_cause = str_replace(leading_cause, "Nephritis, Nephrotic Syndrome and Nephrisis N00-N07, N17-N19, N25-N27", "Nephritis"),
leading_cause = str_replace(leading_cause, "Certain Conditions originating in the Perinatal Period P00-P96", "Perinatal Period Conditions"),
leading_cause = str_replace(leading_cause, "Viral Hepatitis B15-B19", "Viral Hepatitis"),
leading_cause = str_replace(leading_cause, "Intentional Self-Harm Suicide: X60-X84, Y87.0", "Suicide"),
leading_cause = str_replace(leading_cause, "Congenital Malformations, Deformations, and Chromosomal Abnormalities Q00-Q99", "Congenital Malformations")
)
现在,我们的 leading_cause 变量中的值看起来更整洁,并且在我们的条形图中会更好地工作:

我们的数据现在可以使用了,最后一步是导出这个最终数据集,以便在我们闪亮的应用程序中使用。这是通过 write_csv()函数完成的:
write_csv(death, "./data/cleaned_nyc_data.csv")
现在我们准备好开始闪亮登场了!
闪亮的应用程序开发
创建新的闪亮应用程序
我们要做的第一件事是在 r 中创建新的 Shiny app,在 RStudio 中选择文件→新建文件→ Shiny Web App:

系统将提示您选择应用程序名称、类型和位置。应用程序类型指的是 UI 和服务器是否共存于一个文件中,或者是否分别有单独的文件。这种选择很大程度上是个人喜好问题。对于包含大量代码的复杂应用程序,使用多文件类型通常更容易,一些人发现即使对于简单的应用程序,这种分离也更简单。就个人而言,我喜欢把简单应用的所有代码放在一个地方,所以我选择了“单个文件”你可以给你的应用程序起任何名字,并把它放在你电脑的任何地方,但在这里我把它命名为“nyc_mortality ”,并把它放在我的桌面上。做出选择后,点击右下角的“创建”:

你的应用。r 文件现在应该出现了。RStudio 非常擅长向用户提供关于其作品的有用信息,Shiny 也不例外。当你创建一个新的应用程序时,这个自动填充的代码实际上是一个完整的应用程序。你可以点击右上角的“运行 App”试试看:

这个旧的忠实喷泉数据应用程序将会出现,它在左侧包含一个滑块小部件,在右侧包含一个直方图。您可以使用滑块来更改直方图中的条柱数量。玩一会儿这个,让你自己熟悉闪亮的应用程序如何以最简单的形式工作。

当你准备好了,清除旧的忠实间歇泉数据代码,以便我们可以用我们自己的代码替换它。您可以删除除第一行之外的所有内容:
library(shiny)
最后一行:
shinyApp(ui = ui, server = server)
注意:我有时会发现,与其立即删除所有的示例代码,不如将这些代码作为模板,在我创建应用程序时逐渐替换它们。这实际上是我第一次创建这个应用程序时所做的!
在我们继续之前,让我们加载我们将用于创建此应用程序的其他包,并让我们清理的数据可用。
加载库:
library(dplyr)
library(ggplot2)
library(readr)
确保您的应用程序中有我们之前创建的已清理数据集。r 文件是。完成后,您可以简单地使用 read_csv()使我们的数据可用:
nyc_mortality = read_csv("cleaned_nyc_data.csv")
让我们继续编写我们自己闪亮的应用程序。
编写 UI 代码
回顾一下,这是我们将要创建的应用程序:

请记住,UI 本质上控制了用户看到的内容和与之交互的内容。因此,我们将尝试使用我们的 UI 代码完成三件事情:创建“2007–2014 年纽约市种族和性别死亡率”标题,在右侧创建年份、性别和种族/民族选择器,并为我们将在服务器步骤中创建的图和表设置布局。
我们在一个定义 UI 的长代码块中完成所有这三件事。所有这些代码都包含在 fluidPage()命令中。请记住,Shiny 使用了驼色外壳,这对于我们这些习惯使用蛇形外壳的人来说可能有点令人沮丧。首先,我们使用 titlePanel()函数创建标题,只需用引号将它括起来。接下来,我们在侧边栏中创建三个小部件。这部分代码很多,我们一步一步来。
我们的侧边栏只有一个面板,所以所有三个小部件都包含在 sidebarLayout()函数中的一个 sidebarPanel()函数中。我们的第一个小部件是一个下拉选择器,它是用 selectInput()函数创建的。inputId 参数决定了您在编写服务器代码时如何引用这个小部件,而 label 参数是您设置将在应用程序本身的小部件上方显示的文本的位置。choices 参数顾名思义—在这里,您可以指定小部件中可供选择的选项。有几种不同的方法来指定选择,我们将在下面介绍其中的三种。对于 selectInput()小部件,我们的选择只是数据集中表示的所有年份的列表。
接下来是我们的第一个单选按钮()小部件。我们再次分配一个 inputId 和 label,但是现在我们创建的选择略有不同。在原始数据中,性别的两个选项是“F”和“m”。在某些情况下,这已经足够提供信息了,但是在这些单选按钮选择器旁边写上“女性”和“男性”会更清楚,看起来也更好。我们可以简单地做到这一点,在“选择”论证中指出“女性”=“F”和“男性”=“m”
然后,我们添加第二个 radioButtons()小部件。同样,inputId 和 label 参数非常简单,但是我们在这里采用第三种方法来处理 choices 参数。我们希望能够根据每个可用的种族/民族进行筛选,但不想挖掘数据来找出这些值是什么。幸运的是,我们可以通过再次使用 unique()函数来绕过这一步。此函数使单选按钮微件的可用选项成为 nyc_mortality 数据集中 race _ ethylation 变量的所有唯一值。
最后但同样重要的是,我们设置了主面板的布局。正如您在上面看到的,这个面板将包含一个条形图和一个表格。我们实际上还没有创建这些输出——我们只是告诉 Shiny 一旦我们创建了它们,它们会去哪里。我们用 mainPanel()函数来实现这一点,每个输出都有一行代码。首先使用 plotOutput()函数调用条形图,在引号中,我们将未来的条形图命名为“deathPlot”接下来使用 DT 包中的 dataTableOutput()函数调用我们的表,并将其命名为“deathTable”。
ui = fluidPage(# Application title
titlePanel("NYC Mortality by Race and Sex, 2007-2014"),# Sidebar with a 3 inputs
sidebarLayout(
sidebarPanel(
selectInput(inputId = "year",
label = "Select Year:",
choices = c("2007",
"2008",
"2009",
"2010",
"2011",
"2012",
"2013",
"2014")),
radioButtons(inputId = "sex",
label = "Sex:",
choices = c(
"Female" = "F",
"Male" = "M"
)),
radioButtons(inputId = "race",
label = "Race/Ethnicity:",
choices = unique(nyc_mortality$race_ethnicity))
),# Show plot and table
mainPanel(
plotOutput("deathPlot"),
DT::dataTableOutput("deathTable")
)
)
)
我们的 UI 现在应该可以运行了,我们可以继续运行服务器了。
服务器编码
回想一下,服务器是我们创建输出的地方(在本例中是我们的条形图和表格),也是我们告诉他们如何对用户通过小部件创建的输入做出反应的地方。
服务器是一个接受输入和输出参数的函数,我们创建定制服务器的所有代码都在这个函数中。首先,我们创建一个我标记为“selections”的对象,它根据用户在小部件中的输入过滤 nyc_mortality 数据。这是使用 reactive()函数完成的,这是有意义的,因为这些图对用户输入做出“反应”。在 reactive()函数中,我们首先需要在 UI 中创建的三个输入。这些名称应该与在 UI 中创建的 inputId 名称完全一致。接下来,我们告诉 nyc_mortality 数据集根据“年份”输入过滤年份变量,根据“性别”输入过滤性别变量,根据“种族”输入过滤种族 _ 民族变量。
我们现在可以创建两个输出:条形图和表格。我们从柱状图开始,它已经被确定为“死亡图”。请记住,您为该图指定的名称必须与您在 UI 的 mainPanel()函数中指定的名称完全匹配。我们告诉 Shiny 使用 renderPlot()函数输出一个图,然后使用 ggplot()以相当标准的方式创建这个图。如果您熟悉 ggplot(),那么这段代码看起来会很熟悉,尽管我们做了一些调整,使这个图对用户输入作出反应。第一个变化来自“数据”参数。我们没有将数据源指定为 nyc_mortality(我们一直在使用的清理过的数据集),而是将数据指定为我们刚刚创建的“selections()”对象。我们不想一次性绘制 nyc_mortality 数据集中的所有数据,相反,我们只想绘制用户选择的年份、年龄和性别组合的数据。通过将“selections()”指定为我们的数据源,我们只将 nyc_mortality 数据集的过滤版本传递给我们的绘图。然后我们继续讨论 aes()规范,告诉 ggplot 你希望 x 轴和 y 轴是什么。在我们的案例中,我们希望这些主要的死亡原因作为我们的 x 轴,相关死亡的数量作为我们的 y 轴。然而,ggplot 自动在 x 轴上按字母顺序排列字符变量,这对于我们的绘图不是特别有用的排列。相反,按照死亡人数递减来排列死亡的主要原因更有意义。我们使用 reorder()函数来实现这一点,并在函数中指定 leading_cause 变量应该按照死亡人数的减少(用“-”指定)来排序。
接下来我们告诉 ggplot,这个图应该是一个使用 geom_bar()的条形图。“stat = 'identity '”位代码表明 ggplot 不需要尝试聚合我们的数据——我们将通过“death”变量直接提供 y 值。在代码的 geom_bar()部分,我们要做的最后一件事是使用“color”和“fill”参数为我们的图形条指定蓝色。最后,我们使用标准的 ggplot()代码为我们的图表提供一个标题和轴标签,然后将 x 轴标签倾斜 45 度角,使我们的图表可读性更好。
最后,我们使用“DT”包创建一个名为 deathTable 的表。在 DT 包的 renderDataTable()函数中,我们使用了同样来自 DT 包的 DataTable()函数。我们再次使用 selections()对象,而不是完整的 nyc_mortality 数据集。在括号中,我们指定了我们感兴趣的四列:leading_case、deaths、death_rate 和 age_adjusted_death_rate。colnames()函数允许我们为每个变量指定更易读的名称。这些名称将作为列标题出现在我们的最终表格中。在“选项”参数下,我们指出数据应该根据第 2 列(死亡人数)降序排列。就像我们的柱状图一样,按死亡人数降序排列死因比按字母顺序排列的默认选项信息量更大。
server = function(input, output) {selections = reactive({
req(input$year)
req(input$sex)
req(input$race)
filter(nyc_mortality, year == input$year) %>%
filter(sex %in% input$sex) %>%
filter(race_ethnicity %in% input$race)})output$deathPlot = renderPlot({
ggplot(data = selections(), aes(x = reorder(leading_cause, -deaths), y = deaths)) +
geom_bar(stat = 'identity', color = 'steelblue', fill = 'steelblue') +
labs(
title = "Top 10 Leading Causes of Death",
x = "Causes",
y = "Number of Deaths"
) +
theme(axis.text.x = element_text(angle = 45, hjust=1))
})
output$deathTable =
DT::renderDataTable({
DT::datatable(selections()[,c("leading_cause", "deaths", "death_rate", "age_adjusted_death_rate")],
colnames = c("Leading Cause of Death", "Number of Deaths", "Death Rate", "Age-Adjusted Death Rate"),
options = list(order = list(2, 'des')),
rownames = FALSE,
)
})
}
现在我们的服务器也编码好了!如果您之前不小心删除了它,请确保您的服务器代码后面有以下代码行:
shinyApp(ui = ui, server = server)
该应用程序现已完成,请点击右上角的“运行应用程序”。

花些时间摆弄左侧面板中的小部件,以确保输出正确反应。如果一切都正常,我们就可以开始托管应用程序了!

托管闪亮的应用程序
虽然有许多方法可以托管闪亮的应用程序,但 shinyapps.io 对于第一次使用闪亮应用程序的用户来说是理想的,因为它免费且易于使用。因此,我们将使用这个工具来托管我们的应用程序。
如果您没有链接到 RStudio 的 shinyapps.io 帐户,您可以遵循 RStudio 创建的本简单指南的第2.1 节至第 2.3.1 节。
一旦建立了连接,选择闪亮应用右上角的“发布”。接下来,从出现的下拉菜单中选择“发布应用程序…”。

将出现以下窗口。您的 shinyapps.io 帐户应该已经连接,并且应该已经检查了必要的文件。让左下角的“启动浏览器”处于选中状态,这样一旦部署完成,你完成的应用程序就会在你的浏览器中弹出。确保你对应用程序的标题满意,然后点击“发布”!

部署将立即开始,可能需要几分钟时间。完成后,您的浏览器应该会打开并显示您新部署的应用程序。如果你登录到你的 shinyapps.io 账户,你应该会看到你的应用已经启动并运行了!

结论
Shiny 是一个非常有用的工具,R 用户可以用它来构建各种仪表板和交互式产品。它能做的比我们在本教程中介绍的要多得多,但希望你现在能理解 Shiny 的基础知识。既然你已经创建了自己的闪亮应用,我建议你再回头看看闪亮图库。希望你现在对这些应用程序是如何创建的有了更好的理解,也许你会为你的下一个闪亮的项目带来灵感。
最后一点建议——闪亮开发的一个缺点是很难一路检查你的工作。在这个例子中,我们在应用程序能够运行之前编写了所有的代码。当你开发更复杂的应用程序时,我建议在添加更多功能和复杂性之前,先从简单开始,以确保你的应用程序能够正常工作。当这成为可能的时候,它可以帮你省去很多麻烦。
只要您在添加代码时小心谨慎,并充分利用您可以在线获得的丰富资源(尤其是由 RStudio 创建的资源!),探索 Shiny 众多令人激动的功能应该没有问题。
端到端时间序列分析和预测:萨里马克斯、LSTM 和预言家三重奏(上)
预测赫尔辛基市选定地点的日总能耗

塔皮奥·哈贾在 Unsplash 上拍摄的照片
第二部分:端到端时间序列分析与预测:萨里马克斯、LSTM 和先知三人组(第二部分)|作者:孙乐| 2021 年 12 月|中
简介
时间序列,或按时间顺序排列的一系列数据点,是一种普遍存在的数据类型。经济学家通过观察经济在过去的表现来分析经济;天气预报部分基于历史数据等等。总的来说,任何涉及时间维度的定量问题都不可避免地涉及到处理时间序列数据。因此,时间序列分析和预测一直是一个活跃的研究领域,对学术界和商界都有切实的回报。
在本文中,我们将带您了解目前用于预测时间序列的 3 种最流行的技术/工具:经典的 SARIMAX 模型、LSTM 神经网络和 Prophet。所有的内容将基于我们上面提到的项目。其范围是分析和预测赫尔辛基市选定地点的总能耗水平。具体来说,我们将按照以下顺序浏览这篇文章:
- 数据
- 探索性数据分析
- 基线模型
- 萨里马克斯
- LSTM
- 先知
对于更多“铁杆”观众来说,你可以在项目网站上的我们的笔记本中直接看到代码(不幸的是,有注释:)。
数据
我们从 www.avoindata.fi 获取数据集,这是一个由芬兰数字和人口数据服务机构维护的开放式数据存储库。你可以在能源类别中找到,标题为‘赫尔辛基市公用事业和服务设施的能源消耗数据’在数据集的页面上,有一个链接将你带到 API 文档。
对于我们的项目,我们使用了 2017 年至 2020 年(不含)三年的每日和每小时电力消耗数据。这是因为我们发现这只是用于建模的适量数据——不需要旧数据,并且由于疫情,2020 年本身在能源消耗方面已经是一个显著的异常值——不需要新数据。获取的原始数据集大致如下:

马上,我们可以看到有很多冗余信息,甚至包括大楼地址。要获得总能耗,需要对沿locationName特征的值求和。然而,这一过程因位置缺失值的常见程度而变得复杂,这意味着需要进行插值。然而,经过一些工作后,我们得到了想要的数据形式(当然,你可以在笔记本中看到我们所做的详细工作):

数据准备到此为止。现在我们进入可视化的有趣部分!
探索性数据分析

整个数据集的图
通过对数据的初步了解,我们已经可以识别出显著的年度(季节性)电力需求模式,在冬春季达到峰值,然后在夏季降至最低点。我们的假设可以通过查看每年的数据来进一步验证。

数据正如我们预期的那样,在 3 年中几乎呈现出相同的模式。也就是说,我们可以在 2018 年 9 月看到一些异常情况,能源需求意外大幅下降。对此进行了进一步的调查,但没有确定的结果。
更进一步,我们可以放大每年的数据,分析月、周、日的模式。

以 2017 年前两个月的数据为例。这里的重复模式非常引人注目。通过将它们与各自的日期进行比较,很明显数据具有 7 天的季节性,换句话说,是每周一次。从该图中,我们可以看到能源消耗在工作日达到高峰,然后在周末显著下降。这非常符合我们的预期,因为消耗大量能源的工厂和办公室都遵循这样的时间表。
时间序列分解
现在我们对数据有了更好的感觉,我们将通过使用一种叫做 时间序列分解 的统计技术来获得更多的技术。从概念上讲,它的工作原理是将时间序列分成 3 个部分:
- 趋势周期——长期或不固定频率的数据增加或减少。随着时间的推移,随着人口和商业的增加,能源需求也会增加。
- 季节性-当时间序列受季节性因素影响时会出现模式。例:我们刚刚在数据中看到的每周模式。
- 残差——去除上述两种成分后的余数。由于每个时间序列本质上都是一个随机过程,因此数据中总会存在随机噪声。一栋建筑可能有几个坏了的灯泡,或者一些员工生病了,能量水平因此波动。
特别是,我们使用了在包[statsmodels](https://www.statsmodels.org/stable/index.html)内的类[STL](https://www.statsmodels.org/devel/generated/statsmodels.tsa.seasonal.STL.html)中实现的黄土分解。为清楚起见,我们仅对一年的数据进行分解:
stl **=** STL(ts2017,seasonal**=**7,robust**=True**)
res **=** stl**.**fit()
fig **=** res**.**plot()

可见,剧情只是进一步印证了我们对数据的既有信念。分解完成后,探索阶段就结束了。现在,是建模时间了!
基线模型
在每个建模过程中,都需要一个基线模型,其结果可以用来评估我们的主要模型。在我们的案例中,我们选择使用线性回归模型,因为它简单高效。
关于建模过程本身,我们的测试集在 2019 年 12 月合成了所有数据。你可能已经猜到了,由于假期的原因,预测这个时间段是非常具有挑战性的。别担心,这正是我们的意图!我们想看看这些模型是如何处理非常时期的。而且,一点点挑战只会让事情变得更有趣,不是吗?
事不宜迟,我们用几行代码通过线性回归得到预测:

现在我们知道,我们的模型需要比大约 90,000 千瓦时的误差做得更好!
萨里马克斯
要使用这种技术,我们首先需要了解基本知识。
什么是 SARIMAX?
带外生因素的季节性自回归综合移动平均线,或 SARIMAX ,是 ARIMA 类模型的扩展。直观地说,ARIMA 模型由两部分组成:自回归项(AR)和移动平均项(MA)。前者将某一时间的值视为过去值的加权和。后者也将相同的值建模为加权和,但是是过去的残差(confer。时间序列分解。还有一个综合项(I)来区分时间序列(我们将在下面进一步讨论)。由于这是一个涉及大量数学知识的丰富话题,我们强烈建议你做进一步阅读以获得更好的理解。
总的来说,ARIMA 是一个非常正派的模特。然而,这种香草版本的问题是它不能处理季节性——这是一个很大的弱点。萨里玛来了——萨里玛的前身。SARIMA 模型的一个简写符号是:

其中 p =非季节性自回归(AR)订单, d =非季节性差异, q =非季节性移动平均(MA)订单, P =季节性 AR 订单, D =季节性差异, Q =季节性 MA 订单, S =重复季节性模式的长度。从现在起,我们将使用这种符号。通过添加季节性 AR 和季节性 MA 组件,SARIMA 解决了季节性问题。
SARIMAX 只是通过增加处理外部变量的能力来扩展这个框架。Holidays 是首选,但是如果需要的话,您也可以获得您自己的特定于领域的特性。在我们的例子中,我们从包[holidays](https://pypi.org/project/holidays/)中获取了芬兰的假期列表:

我们在项目中使用的 SARIMAX 实现也来自于包statsmodels。
落后
滞后只是一个系列中时间步长的延迟。考虑一个时间索引 t,相对于 t 的滞后 1 时间索引简单来说就是 t-1,滞后 2 就是 t-2,等等。
平稳性
平稳时间序列的均值、方差和自相关结构不随时间变化。换句话说,它没有任何周期/趋势或季节性。ARMA 模型系列实际上就是建立在这个概念上的。

来源:维基百科
自相关函数(ACF)和偏自相关函数(PACF)

这两个函数都测量时间 t 的数据与其过去的值 t-1,t-2,… 的相关程度,但是有一个关键的不同。ACF 还测量了相关滞后之前的间接相关性,而 PCAF 没有。在实践中,他们的图对许多任务至关重要,尤其是为 SARIMAX 模型选择参数。你可以在这里阅读更多关于如何解读这样的情节。


有了基础知识之后,我们继续使用 Box-Jenkins 程序对时间序列进行建模。
模型识别
差分以实现平稳性
我们首先确保我们的数据是稳定的。查看我们最初制作的图表,很明显,数据并不是稳定的,没有如此明显的趋势和季节性。然而,我们可以通过使用一个统计测试使我们的猜测更加科学:增强的 Dickey-Fuller 测试,也在statsmodels包中实现。
**def** test_stationarity(timeseries,maxlag):
*# Perform Dickey-Fuller test:*
print('Results of Dickey-Fuller Test:')
dftest **=** adfuller(timeseries,maxlag**=maxlag,**
autolag**=**'AIC')
dfoutput **=** pd**.**Series(dftest[0:4], index**=**['Test Statistic','p-value','#Lags Used','Number of Observations Used'])
**for** key,value **in** dftest[4]**.**items():
dfoutput['Critical Value (%s)'**%key**] = value
print (round(dfoutput,3))

正如所料,p 值大于 0.05。因此,我们不能拒绝零假设,时间序列不是平稳的。现在的问题是,“我们如何做到这一点?”程序中的答案是差分——由积分项的 d 和 D 阶表示。对数据求差仅仅意味着取数据点与其滞后版本之间的差。直观上,这类似于对函数求导。

这看起来更像!我们再次求助于迪基-富勒试验进行验证。

完美!现在我们知道了 SARIMAX 中的综合术语的参数:季节性和非季节性术语的 1。建模过程的下一步是使用 ACF 和 PACF 图找到 AR 和 MA 项的顺序。
使用 ACF 和 PACF 图确定 p 和 q


在图中,我们可以发现错综复杂的自相关模式,没有明确的解释。因此,季节性和非季节性 AR 和 MA 条款的订单无法果断选择,使我们的流程变得复杂。这证明了现实生活中的统计数据可能比教科书上的例子要混乱得多,我们不得不面对自己选择的不确定性。幸运的是,我们可以通过使用一种叫做网格搜索的优化方法来避免任何不科学的猜测。
模型估计
网格搜索
该算法简单地对所有参数组合进行穷举搜索。将根据我们选择的损失函数来选择它们中最好的一个。在我们的案例中,我们按照 ARMA 建模过程中的标准使用流行的赤池信息标准(AIC) 。
**def** sarimax(ts,exo,all_param):
results **=** []
**for** param **in** all_param:
**try**:
mod **=** SARIMAX(ts,
exog **=** exo,
order**=**param[0],
seasonal_order**=**param[1])
res **=** mod**.**fit()
results**.**append((res,res**.**aic,param))
print('Tried out SARIMAX{}x{} - AIC:{}'**.**format(param[0], param[1], round(res**.**aic,2)))
**except** Exception **as** e:
print(e)
**continue**
**return** results*# set parameter range*
p,d,q **=** range(0,3),[1],range(0,3)
P,D,Q,s **=** range(0,3),[1],range(0,3),[7]
*# list of all parameter combos*
pdq **=** list(itertools**.**product(p, d, q))
seasonal_pdq **=** list(itertools**.**product(P, D, Q, s))
all_param **=** list(itertools**.**product(pdq,seasonal_pdq))
all_res **=** sarimax(train,exo_train,all_param)
搜索结束后,我们列出了前 5 名模特:

我们可以马上使用第一组参数。即使这个模型违反了简约原则(参数总和< 6),裕量足够小,我们可以从一些灵活性中获益。
模型验证
残留诊断
为了确定模型的拟合优度,我们可以使用标准假设来检查其残差:它们应该正态分布在 0 附近,或者换句话说,白噪声。
我们可以通过查看显示残差分布的各种图来验证这一点。这可以使用plot_diagnostics方法方便地生成。此外,还可以使用 Ljung-Box 测试进行更精确的测试。
res**.**plot_diagnostics(figsize**=**(15, 12))
plt**.**show()
print("Ljung-box p-values:\n" **+** str(res**.**test_serial_correlation(method**=**'ljungbox')[0][1]))
res**.**summary()


在图中,残差似乎正态分布在 0 附近,这是我们需要的条件,尾部略重。然而,看一下 Ljung 盒统计,我们不能拒绝数据不是独立分布的假设,因为从 6 开始,对于某些滞后,p 值小于α=0.05。
尽管如此,让我们使用这个模型在测试集上进行预测,并自己进行判断。
fig, ax **=** plt**.**subplots(figsize**=**(12,7))
ax**.**set(title**=**'Energy consumption', ylabel**=**'kWh')ts**.**plot(ax**=**ax, style **=** 'o')
pred**.**predicted_mean**.**plot(ax**=**ax, style**=**'o')
ci **=** pred_ci**.**loc[demo_start:]
ax**.**fill_between(ci**.**index, ci**.**iloc[:,0], ci**.**iloc[:,1], color**=**'r', alpha**=**0.1)

总的来说,样本内预测似乎非常符合时间序列!没有明显的误差模式,除了该模型似乎比周末更好地预测了工作日的能量水平。现在,让我们看看它如何在测试集上预测:

样本外预测看起来也很令人满意!在该月的前 2 周,预测值与实际值吻合得很好,预测中没有系统误差。然而,可能有一个例外,12 月 6 日的数值是非常不正确的。幸运的是,我们知道原因很简单:那天是芬兰的独立日。
关于寒假季节,模型不幸地没有做得那么好。在此期间,尽管增加了外生的“假期”变量,但预测始终高估了用电量。显然,不仅圣诞假期能量下降,整个寒假期间也是如此。并且,该信息没有被合并到模型中。这解释了为什么错误率仍然很高——与基线模型相比没有太大的改进。
我们用一个不同的训练测试分割在幕后重做了这个过程,结果比预期的好得多。误差率下降到 30,000 千瓦时左右或低于 5%。对于一类 50 年前理论化的模型来说还不错,对吧?然而,所有这些都显示了在非常时期进行预测的挑战,强调了对更好技术的需求。除此之外,当数据表现可预测时,该模型仍然显示出预测的前景。
哎呀…这个帖子已经太长了!此外,这似乎是一个很好的地方停下来,所有的 SARIMAX 部署都包括在内。在下一篇文章中,我们将会谈到时间序列分析教科书中的“现代”部分。我们会发现用 Prophet 拟合时间序列的过程。也就是说,非常感谢你穿过文本墙走了这么远:)。下次见!
第二部分:端到端时间序列分析与预测:萨里马克斯、LSTM 和先知三人组(第二部分)|孙乐| 2021 年 12 月|中
同样,对于渴望更多未来预测的感兴趣的观众,你可以在项目网站上的我们的笔记本中看到所有的代码以及更多的技术评论。
数据科学工程基础
小窍门
你的代码周围的代码

“数据科学分析基础”帖子中的编程概念涵盖了当数据摆在你面前时如何处理数据。如果您的工作流看起来像是从 Google Drive 下载一个 CSV 文件到您的笔记本电脑上,分析数据,然后将 PDF 文件附加到报告中,那么这些概念就足够了。
然而,当您开始一个需要组合来自数百个 CSV 的数据的项目时,会发生什么呢?点击和拖动只能让你到此为止——即使你有耐心,你的经理可能没有!在你不得不通过一个没有好的用户界面的 API 来访问数据之前,这也只是一个时间问题。
类似地,也许你被分配到一个项目,该项目有一个现有的代码库,程序员希望在处理代码时有最佳实践。虽然一次性的脚本可能在学校期间就已经停止了,但是[1] 如果你不能以一种易于阅读、重用和被他人修改的方式来组织你的代码,你就活在借来的时间里。
这就是分析数据之外的编程技能的用武之地。在本帖中,我们将涵盖以下软件工程概念:
- 访问数据
- 版本控制
- 面向对象编程
- 虚拟环境
- 写作测试
访问数据
在这一节,我们将介绍 如何使用代码来访问数据。 这是一项跨越整个分析-工程领域的技能,但我认为与其说是一项分析技能,不如说是一项“工程”技能。
作为一名数据科学家,你很少会通过 Google Drive 或 Dropbox 的点击式图形用户界面来访问数据。相反,您将访问的大部分数据将驻留在 SQL (结构化查询语言)数据库或API(应用程序编程接口)之后。也有可能你需要使用网络抓取来访问不提供 API 的网站上的数据。本节将介绍这三种访问数据的方式。
结构化查询语言
除非你的公司很小,否则它的数据会比一两个硬盘所能容纳的还要多。随着数据量的增长,以最小化冗余和检索时间的方式组织数据至关重要;优化安全性和可靠性;正式陈述数据的不同部分如何相互关联;并让多个用户同时读取(和写入)数据。
实现这一点的主要方法是使用关系数据库,您可以使用 SQL 对其进行查询。[2]关系数据库本质上是一组表,表之间有定义的关系。
例如,如果你拥有一家网上商店,你不需要在顾客订购的每件商品旁边保存顾客的每一个细节;您可以将客户信息分离到一个表中(customers),将订单信息分离到另一个表中(orders),只需用orders中的一个名为customer_id的列将订单与客户相关联。使用 SQL,您可以轻松快速地从两个表中提取相关数据,即使表增长到数千万或数亿行。[3]
在您的角色中,您可能会非常频繁地使用 SQL,可能每天都在使用,所以我强烈建议您花时间来完善这项技能。幸运的是,SQL 不是一种庞大的语言,你可能只需要从数据库中查询数据,而不是创建数据库或表格,这更属于数据工程师的领域。在这篇文章中,我们将关注简单到中级的查询。
下面是一个用主流 SQL 语言之一的 Postgres 编写的简单查询。我们从表students中选择name和animal列,使用AS关键字为返回的表中的列创建别名或临时名称。最终结果经过过滤,因此返回的行中只有学生最喜欢的动物是海象的行。[4]
我们也可以为表格使用别名,下面我们将为users、sql_pros和transactions使用别名。在这个例子中,我们以两种方式连接表;在第一个查询中,我们使用了一个LEFT JOIN,它保留了users中的所有行,但是删除了sql_pros中没有 ID 的行。在第二个查询中,我们执行了一个FULL JOIN,它保留了users和transactions中的所有行。
对于各种连接,要记住的主要事情是连接后要保留的行:只保留在两个表中匹配的行(INNER)、全部在左(LEFT)或右(RIGHT)或全部在两个表中(FULL)。
聚合数据是另一个关键的 SQL 技能。下面,我们创建一个包含学生姓名和平均成绩的表格。因为在这个例子中,name列与grades在不同的表中,所以我们在聚合后连接这些表。
对于更复杂的查询,我喜欢引入WITH {tab} AS结构,它允许您编写基于其他查询输出的查询。在下面的查询中,我们执行以下步骤:
- 为每个用户创建一个带有平均值和标准偏差
price的查找表 - 加入我们对原始
orders表的查找 - 使用我们的查找来过滤掉不在每个用户的平均订单价格的三个标准差范围内的任何行
这个查询方便地返回离群值,我们可以更仔细地检查。注意,这是一个查询,但是由于使用了WITH语法,我们可以在逻辑上将它视为两个查询。
最后,我们快速提一下写到一个数据库。写入数据库,尤其是生产环境中的数据库,很可能会受到软件工程团队的严格监督——一个好的团队会有适当的程序来验证写入的数据与表模式、防止 SQL 注入攻击,以及确保所有写入都被记录。但是如果您对您想要写入的数据库拥有完全控制权,下面是添加行的基本语法:
这是更新和删除的语法。非常确定你知道你在做什么,因为没有“撤销”命令![5]
与 API 交互
除了 SQL,你访问数据的另一个主要方式是通过 API 或者应用编程接口。6
API 就像银行的入口:这是(希望)访问银行内容的唯一方式,你必须遵循一定的规则才能进入:你不能携带武器,你必须步行进入,如果你没有穿衬衫,你会被拒之门外,等等。另一种方式认为它就像一个电源插座——除非你的和弦插头形状正确,否则你无法通电。
requests库让我们直接从 Python 中查询 API。对于没有任何安全需求的 API 来说,这个过程很简单:你只需要 API 在互联网上的位置,也就是它们的 URL ,或者通用资源定位器。我们所做的就是向 URL 提出一个 HTTP GET请求,然后解码从服务 API 的服务器返回的 JSON 。
但是有时我们需要一些额外的步骤来访问数据。在访问公司的专有数据时,有(或者应该有!)严格限制谁被授权与数据交互。在下面的例子中,我们使用boto3来访问亚马逊网络服务 S3 、云存储市场领导者中的一个文件。注意,当我们与 S3 建立连接时,我们需要传递安全凭证(存储在os.environ对象中)。
网页抓取
如果你想从一个没有提供方便的 API 的外部网站上收集数据怎么办?为此,我们求助于网页抓取。网页抓取的基本前提是编写代码,遍历网页的 HTML ,找到指定的标签(例如标题、表格、图片)并记录它们的信息。抓取对于自动化来说是理想的,因为 HTML 具有高度规则的、基于树的结构,所有元素都有清晰的标识符。
虽然抓取听起来很复杂,但实际上相当简单。我们首先通过用requests.get从一个网站请求HTML 来模仿一个网络浏览器(例如 Firefox,Chrome)。(然后,我们的浏览器实际上呈现内容,但我们将坚持 HTML 作为一个很长的字符串。)
然后,我们使用 Python 的 Beautiful Soup 库将 HTML 组织成一个大型嵌套字典。然后,通过指定我们感兴趣的 HTML 标签,我们可以从这个对象中提取我们想要的信息。下面,我们打印出维基百科网页抓取页面的所有标题。

版本控制
当你做一个不能在几分钟内完成的项目时,你需要检查点来保存你的进度。即使项目已经完成,也许你会对一个额外的特性有一个很好的想法,或者你会找到一种方法来改进一段代码。
除非变化很小,你不会想要修改工作的版本,而是想要一个副本 ,在那里你可以进行修改并与原始版本进行比较。类似地,对于更大的项目来说,如果新的变更破坏了代码,能够回滚到旧的检查点是至关重要的。如果有多人在处理同一个文件,这一点尤其重要!
这就是像 Git 这样的版本控制系统发挥作用的地方。相比之下,有几十个文件称为my_project.py、my_project_final.py、my_project_final_REAL.py等。相反,你有一个树状的项目时间表,漂浮在你电脑的一个文件夹里。代码有一个“主”分支,你只能修改副本。
每当您更新一个分支时,所有的变更都会被自动标记,并且对主分支的变更需要至少一个其他人的审核。(从技术上来说,他们不会,但实际上在任何职业环境中都是如此。)
随着时间的推移,您的项目结构可能看起来像这样。

来源:堆栈溢出
灰色的线是master分支(现在称为main 【7】),蓝色和黄色的线是在不同点分支的副本(develop和myfeature),经过修改,然后合并回master。在更大的公司,你可以有几十个分支并行运行,这对于让开发团队同时在同一个代码库上工作是必不可少的。然而,客户看到的唯一分支是main。
使用 Git 的实际代码很简单。下面是 Mac 终端中 bash 中的一些命令,在这里我们:
git checkout main
git checkout -b DS-123-Add-outlier-check
git push --set-upstream origin DS-123-Add-outlier-check
现在,在我们的新分支上,我们可以自由地对代码进行我们想要的任何更改。假设我们通过添加一个移除异常值的步骤来修改preprocessor.py。当我们想要保存我们的更改时,我们在终端中键入以下内容。
git add preprocessor.py
git commit -m "Add outlier check"
git push
这些步骤只体现在DS-123-Add-outlier-check上,不体现在main上。这让我们准备代码,直到它准备好被推送到main。
如果我们破坏了一些东西,想恢复到以前的状态呢?我们使用提交的散列检查提交,告诉 Git 忽略有错误的提交,然后将我们的更改推送到分支。
git checkout abc123 # go to old checkpoint
git revert bad456 # "delete" the bad checkpoint
git push # update the branch

Neil 和 Zulma Scott 在 Unsplash 上的照片
面向对象编程
随着项目中代码数量的增长,它通常遵循这种增长组织的模式:
生产级 Python 最适合组织的第四级,在这一级可以轻松地跨上下文添加、修改和重用代码。一个团队的代码通常会根据公司产品(例如“数据质量警报”、“价格预测”、“客户流失预测”)组织成模块,这些模块又包含类,这些类包含协同工作的功能集合。下面是一个名为Student的类的简单例子。
类别可以存储在具有相同名称的.py文件中,分组到具有相似类别的文件夹中。模块是包含所有文件夹的目录。我们可以有一个data_processing模块,例如,具有这样的目录结构[9]:
data_processing
| init.py
| cleaners
| | init.py
| | data_cleaner.py
| visualizers
| | init.py
| | error_logger.py
| | dashboarder.py
| services
| | init.py
| | data_loader.py
| | database_writer.py
在cleaners子目录中,data_cleaner.py包含了一个DataCleaner类,其中包含了清理数据的方法。data_cleaner.py的前 60 行可能如下所示:
这个代码块比其他代码块长很多,它甚至不包括帮助函数_find_outliers或调用DataLoader的代码。对于生产级编码,你需要围绕你的核心功能构建更多的架构来确保你的代码:
- 可以被其他人阅读和修改,而不仅仅是你
- 是否足够模块化以用于管道和多种环境
- 如果它收到一些意外的输入,不会使这些管道停止工作
上面的代码有一个详细的文档字符串、类型提示、参数默认值设置为文件顶部的全局变量,以及一个针对意外行为的警告日志。
这些附加组件有助于解释我们的代码对其他开发人员以及我们自己有什么作用!作为进一步的预防措施,我们可以加入错误处理来处理错误数据类型的参数,否则会导致脚本失败。

瑞安·赫顿在 Unsplash 上拍摄的照片
虚拟环境
除非我们的代码非常简单,否则我们需要导入外部库(比如pandas和numpy)。正如比尔·索鲁尔所说的,这些外部依赖“是魔鬼”代码是不断进化的,有时你一年前写的脚本在使用它的依赖项的最新版本时不再有效。 Python 3 与 Python 2 的向后不兼容是出了名的,比如pandas v1.0 弃用或移除了几个Series和DataFrame操作。
防止改变依赖关系的一种方法是拍摄项目外部库的“快照”以及它们的确切版本号。然后我们可以创建一个虚拟环境,让我们重现当你创建你的项目并且一切正常运行时“外部代码世界”的样子。
在终端中,我们可以使用 Python 内置的virtualenv模块来创建虚拟环境。在这里,我们创建一个名为venv的。
python -m virtualenv venv
然后,我们可以通过键入以下代码进入这个虚拟环境:
source venv/bin/activate
我们的新环境没有我们的全球环境所拥有的外部库。例如,即使您在创建虚拟环境之前安装了scikit-learn,在venv中也不存在scikit-learn。每当我们创造一个新的环境时,我们都是从一张白纸开始!
因此,我们需要在venv内部安装我们项目所需的每个库。如果需要,我们可以使用<package>==<version>语法指定版本号。
pip install pymongo
pip install scikit-learn==0.24
一旦我们的虚拟环境下载了所有软件包,并且您验证了您的应用程序的工作情况,我们就可以使用以下命令将所有软件包及其版本号保存到文件requirements.txt中。pip freeze返回所有下载的库,并且>操作员将输出导入文件,而不是在屏幕上打印。
pip freeze > requirements.txt
将来,为了确保我们的项目如预期的那样工作,我们可以创建一个新的虚拟环境,并安装所有依赖关系的精确版本pip install -r requirements.txt。这对于相对小规模的工作来说非常有用,但是如果您正在部署一个包,例如到 PyPI 本身以便其他人下载,您会想要深入更高级的 [setuptools](https://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-dependencies) 方法。

写作测试
当我们编写一个函数时,代码应该按照我们希望它做的去做。但是更进一步,我们的代码也应该不做其他任何事情。虽然这看起来很明显,但是这个额外的步骤需要我们的代码中有很多额外的架构。这里有一个简单的例子:
当得到非数字输入时,我们的multiply函数转向字符串和列表连接。这可能不是我们想要的!
人们很容易认为这些例子无关紧要,但如果multiply在管道深处,接收前一个函数的输出,而前一个函数接收前一个函数的输出,这种情况很容易发生。例如,我们可以有一个find_anomalies函数,如果没有异常就返回0,如果有异常就返回一个值列表。(不要这样做——尝试总是从函数返回相同的数据类型。)如果multiply接受find_anomalies的输出,我们将根据find_anomalies返回的结果得到截然不同的结果,这可能会破坏我们的管道。
捕捉这些问题并学习如何编写更好的代码的方法是编写测试。测试基本上就像它听起来的那样:对于一些输入有一个“正确的答案”输出,你检查你的代码是否产生正确的输出。测试在检查多少代码上有所不同:你可以为整个应用程序编写测试,为组件的子集编写测试,或者为单个功能编写测试。下面我将简要介绍最后一种类型,单元测试。
一套好的功能单元测试将涵盖三种类型的输入:好的、特殊的和坏的。一个好的输入就像它听起来的那样:这个函数打算接收的输入,就像我们的multiply函数的整数或浮点数。一个特殊的输入是一种“边缘情况”,可能是触发内部逻辑产生不同输出的某种情况。最后,一个坏的输入是我们的函数无法处理的——我们的函数应该失败。
让我们重写multiply [10],然后写一些测试。第 8-12 行,大约是我们函数的 83%,现在完全致力于确保multiply不会中断。这些代码行真的做到了——我们最终不需要为错误的输入编写测试,因为我们不能向multiply扔任何东西导致它中断。相信我,我试过了。
然后我们可以使用pytest库来编写我们的测试。第一步是明确定义我们的输入类型及其预期输出。我喜欢使用嵌套字典,其中顶层是我们的测试用例(例如,int-int,意味着a和b都是整数),内层是我们的参数名及其值的字典。然后我们可以使用 **kwarg 符号将字典解包到我们正在测试的函数中。[11]
实际的测试发生在assert语句中,如果第一个参数返回True,这些语句将保持沉默,如果第二个参数返回False,这些语句将引发一个AssertionError。如果测试失败,最好在断言字符串中详细说明,因为这将帮助您查明错误。
我建议将您的测试组织在一个目录中,该目录反映了您的存储库的结构,如下所示。
src
| custom_math
| | easy
| | | add.py
| | | subtract.py
| | hard
| | | multiply.py
| | | divide.py
| tests
| | test_easy
| | | test_add.py
| | | test_subtract.py
| | test_hard
| | | test_multiply.py
| | | test_divide.py
然后,您可以导航到您的项目的根文件夹(上面的src,然后只需键入pytest tests来运行所有的测试。请注意,函数、类和文件名需要以test开头或结尾,以便pytest将它们识别为测试。
一旦你做到了这一点,通过模仿依赖函数和断言异常,让你的测试更上一层楼。
总结想法
正如“数据科学统计要点”的帖子一样,当谈到数据科学的有用软件工程实践时,很难不写一本教科书。当我开始转向数据科学时,我最大的知识缺口是工程。在部署模型时,除了实际的分析之外,还有很多东西。你如何创建一个应用而不是一个一次性的脚本?你如何可持续地编写成千上万行代码,或者与其他人一起开发潜在的数百万行代码?
这篇文章中的概念,从管理依赖性到编写弹性代码,应该有助于填补这一知识空白。如果你渴望更多,看看 Python decorators ,创建自己的 API 端点, Docker 容器化,以及像 Heroku 这样的应用托管服务。如果你对这篇文章感兴趣,可以看看我写的关于数据科学工程的其他文章,告诉我你想知道更多的内容。
最好,
马特
进一步阅读
https://betterprogramming.pub/how-to-efficiently-validate-input-types-in-python-functions-1f662f45c24c 💔-levels-of-technical-abstraction-when-sharing-your-code-50ddc6e73946>
脚注
1.你的代码周围的代码
这一针既是针对我的,也是针对其他人的。在研究生院,我有数百行长的 R 脚本,重新运行它们以不同的方式处理数据总感觉像在做手术。不应该是那样的!回过头来看,如果有一些基本的软件工程和项目管理最佳实践,博士学位会变得容易得多。也许为了下一个博士学位…
2.结构化查询语言
关系数据库的一个流行的替代品是 NoSQL,或者非关系数据库(也称为“不仅仅是 SQL”))与具有严格定义的关系的表相比,像 MongoDB 这样的 NoSQL 数据库在如何存储数据方面允许更大的灵活性。查看我在这篇文章中对 SQL 和 NoSQL 数据库的深入研究。
3.结构化查询语言
在索引正确的数据库中搜索、插入和删除记录的规模预计为 O (日志 n ) 。这意味着对于每一个数量级,遍历数据只需要一个额外的步骤!我觉得这令人难以置信。
4.结构化查询语言
在这篇文章的研究中,我偶然发现了这个(有点争议的) SQL 风格指南。这是一篇有趣的文章,我决定采用这篇文章中 SQL 示例的一些布局技巧。我学到的主要东西是在代码中间有一条空白的“河”, SQL 关键字在左边,代码的其余部分在右边——这使得快速浏览查询正在做什么变得非常容易。但是,如果我找不到一种简单的方法来自动格式化代码,我怀疑我是否会坚持使用它来进行快速分析。
5.结构化查询语言
如果这真的发生在你身上,希望你公司的工程团队定期备份数据库,并且能够回滚到更早的版本。如果他们没有,而你不小心删除了你公司的所有数据,那么 Quora 建议你辞职,找一家工程实践更好的公司!
6.与 API 交互
事实上,API 和 SQL 是携手并进的。当您从 API 请求数据时,您的请求很可能被转换成 SQL 查询,然后在数据库上执行。
7.版本控制
2020 年 10 月, GitHub 将默认分支从master重命名为main,删除了对奴隶制不必要的引用。
8.版本控制
Git 分支的最佳命名惯例是通过它们的 JIRA 票据 ID 来引用它们。如果您的公司将 Git 与 JIRA 集成在一起,其他开发人员将会看到该分支是否正在开发中,是否有一个活跃的拉请求,或者是否已经被合并到main。一个更好的(“最好的”-呃?)做法是在分支名称中包含是否是热修复,支持请求,部分路线图等。
9.面向对象编程
init.py文件允许从目录外导入类。这使得下一个代码块中的from ..services import DataLoader能够工作。类似地,如果你已经安装了 [data_processing](https://realpython.com/python-wheels/) 模块,你可以在任何脚本中通过输入:
# With init files
from data_processing import DataLoader
如果没有 init 文件,您需要键入:
# Without init files
from data_processing.services.data_loader import DataLoader
这不仅不太方便,而且如果您决定重新排列data_processing模块中的目录,还会有外部脚本崩溃的风险。你可以在这里阅读更多关于 init.py 文件的内容。
10.写作测试
如果我们真的想彻底,我们应该创建一个助手函数来检查multiply的输入。这使得我们的功能集中在尽可能少的任务上,使得编写测试和调试更加容易。这看起来是这样的:
11.写作测试
我发现解包元组和字典让编写测试变得有趣多了。这里是拆包和不拆包的符号。
利用喜力的全球规模,设计当地建议

(图片由喜力公司提供)
喜力公司正在利用数据和分析技术对其 157 年的历史进行创新。推荐系统越来越多地通过过滤信息来支持我们的业务流程。例如,我们的销售团队为酒吧的啤酒组合提供建议的系统。然而,我们的国际规模既是优势也是挑战。我们的目标是利用我们的国际影响力,协调各子公司的推荐基础设施。通过机器学习操作方法,我们在 Azure 平台上创建了标准化的推荐系统。自助服务、自动化和协作的文化使我们能够快速将推荐系统扩展到本地子公司。这项工作被 RecSys 会议接受。
喜力公司在 70 多个国家设有子公司,是最国际化的啤酒酿造商。在这些子公司中,对分析解决方案的需求增加,如过滤信息以预测偏好的推荐系统。这有助于我们的销售团队更好地建议商店(如酒吧)向顾客提供哪些啤酒。在开始的情况下,这些子公司独立开发他们的推荐系统。导致各种技术选择和数据来源的多样性。从全球角度来看,这种缺乏标准化是一个重大的工程挑战,因为它支持复杂,维护昂贵,并且没有利用喜力的规模。在期望的情况下,本地推荐系统在子公司之间被协调。确保没有技术债务的高交付速度。因此,这项工作集中在如何在企业内扩展标准化的推荐系统,同时保留分散的所有权并结合本地规范?
MLOps 方法
我们从使用机器学习操作(MLOps)方法应对这一挑战的经验中汲取了经验。MLOps 由关注效率、可靠性和再现性的系统启动实践组成。它是 ML 和 DevOps(开发运营)的结合。这是一次重要的合并,因为机器学习解决方案是由代码和数据形成的。与只覆盖代码的常规软件工程相比。我们的 MLOps 生命周期有四个阶段,因为我们相信数据管理是成功的推荐系统的基础部分。这个生命周期不是单向或单一的过程。推荐系统通过并行动作进化。

机器学习操作生命周期(图片由作者提供)
MLOps 方法有五种最佳实践:
- 管道自动化:通过自动化操作和连接管道中的服务来消除手动操作和延迟。例如,通过发布具有持续集成和持续部署管道的解决方案。
- 数据可用性:通过功能存储访问经验证的数据集,并在数据目录中进行索引,以确保可重复的机器学习。
- 可交换工件:所有的机器学习模型、代码、配置都是版本控制的、描述性的、PEP8 一致的。带有文档的解决方案模式可用于常见的架构工件。在生产中,代码脚本比笔记本更受欢迎。
- 可观察性:深入了解系统组件以确保性能并确定问题的根本原因。度量、事件、日志和跟踪都被收集起来,产品团队可以访问,而不仅仅是工程师。
- 基于策略的安全性:发布分为四个环境:开发、测试、验收和生产。对这些环境的授权是通过基于属性的访问控制(ABAC)来管理的,它具有控制平面和数据平面功能。机密存储在特定于环境的密钥库中。
推荐系统示例
根据这种 MLOps 方法,我们创建了一个可在企业内扩展的推荐系统。推荐系统是运行在 Azure 云上的内部解决方案模式的解耦架构。数据管理阶段从上游服务获取原始数据,并将其转换为集中可用的标准化数据集。在建模阶段,使用 Azure ML 工作空间对这些数据进行建模,该工作空间充当推荐引擎。开发阶段创建内部解决方案模式,确保资源之间的可靠性和互操作性。操作阶段使用 CI/CD 管道自动部署资源。这一阶段还为当地利益相关者提供了可访问的可观察性,以发现和检查可能的问题。这种标准化的解决方案减少了每个子公司的工程时间,同时保持了有效整合本地规范和分配所有权的灵活性。

推荐系统(作者图片)
通过资源和解决方案模式标准化跨子公司的推荐系统简化了同事之间的协作。他们可以轻松地交换知识、机器学习模型和其他工件。内部教程是一致的,如果需要,全球团队会提供额外的支持。从而受益于喜力啤酒的全球规模和经验。
结论
满足企业中推荐系统的本地需求需要具有最小技术债务的高效工程。我们解决了在企业内部扩展本地推荐系统的挑战。本文描述了一种 MLOps 方法和相应开发的推荐系统。从这个过程中,我们了解到喜力推荐系统的工程应该促进三个原则:自助服务、自动化和协作。
自助服务、自动化和协作
这些原则授权子公司开发在整个公司内统一的推荐系统。从而利用喜力的规模来实现高交付速度,同时仍然结合当地的规格。我们相信,这些工程原则对其他有类似数据驱动的雄心和挑战的企业也很有价值。此外,我们认为这些并不局限于推荐系统,而是适用于在全球范围内扩展各种类型的机器学习应用
会议文件和视频
这项关于推荐系统的工程工作被接受为第 15 届 ACM 推荐系统会议的行业海报。这是一个主要的国际论坛,由领先的研究人员和从业人员介绍该领域的研究和技术。今年,这是喜力和我自己的主场比赛,在阿姆斯特丹的 Beurs van Berlage 和全球在线举行混合会议。阅读短文(开放访问)中的完整作品或观看闪电谈话。
这篇文章站在(喜力公司)人才的肩膀上。他们的想法、代码和反馈塑造了我们的思想,并为这项工作做出了贡献。
英语专业转脸书数据科学家
办公时间
加速您的数据科学职业生涯的学习资源

信用:Pexels
我已经正式接受了脸书大学数据科学家的职位。大约四年前的今天,我写了第一行代码。同样,我在大学期间从未学过微积分或线性代数。所以我的工作是为我量身定做的…
扎实的基础超级重要,下面是对我帮助最大的资源。
第 1 部分:资源
Python(基础)
从开始,用 Python 将枯燥的东西自动化。这里的重点是自动化枯燥的东西。作为一个(以前的)非编码者,网络抓取有一种魔力。你能为自己做的最好的事情就是尽早迷上 python!
在你已经掌握了 5-10 章的 ATBS 之后,有必要更详细地了解一下 Python 作为一种编程语言的结构。科里·斯查费几乎在所有事情上都有资源——暂时放过弗拉斯克和姜戈吧!你现在需要的是面向对象编程(OOP)、生成器、装饰器等等。在构建我们自己的数据结构之前,我们需要彻底理解 python 的内置数据结构!
数据结构和算法
背靠背 SWE 从伪代码的角度讨论每一个算法,(读作而不是 python!)重点是理解数据结构和算法(DS & A)本身,而不是背代码。熟悉递归、树结构和动态编程是一个好主意——这些想法到处都有,比如强化学习、隐马尔可夫模型等。
为了测试你的理解,试着在 LeetCode 上解决问题。这将帮助你为编码面试做好准备。该网站面向软件工程师,而不是数据科学家;你不需要为“难”的问题而烦恼。但是处理几乎每一个“简单”的问题和适度抽样的“中等”问题将使数据科学家面试的编码部分易于管理。
概率论与统计
大多数人都是从既定的、事实上的方法开始他们的统计之旅的,即频率主义。这就是 p 值、置信区间和方差分析的来源。这些算法在计算上很简单,但分析师所需的假设非常微妙。作为一名概率统计新手,这有助于断章取义地使用给定的程序和/或曲解其结果。频繁主义在工作场所极其普遍,所以你必须在某个时候了解它。但是我相信这对早期**建立直觉很重要。为此,我建议从贝叶斯统计开始。算法在计算上更复杂,然而,结果的解释更简单。这是理想的,因为计算可以自动化,可以用软件抽象,但直觉不能。
为此,先从想贝氏说起。作者从零开始实现所有算法,从简单的直觉开始;这是理想的,因为它会帮助你用概率理论弄脏你的手,这是黄金!你不需要读完整本书,但是对术语、先验、可能性、证据和后验有个感觉是很重要的。
在建立了一些基本的直觉之后,是时候在现实世界的设置中获得一些更强大的实践经验了。《统计学再思考》在我看来是关于概率和(贝叶斯)统计学的最好的书。教科书不是免费的——但是讲座是免费的!代码最初是用 R 和 Stan 编写的。然而,随着我们对 python 的了解越来越多,最好留在 python 生态系统中,因此我链接了教科书代码的 PyMC3 端口。PyMC3 是一种“概率编程语言”——换句话说,你用各种观察到的和/或潜在的变量来指定概率分布的网络,并推断系统作为其参数的函数如何表现。
最后,你需要知道一些常客的统计数据。为此,我推荐stat quest——因为它将尽可能直观地涵盖主题,同时强调所做的假设。
微积分和线性代数
许多统计学是建立在微积分和线性代数的基础上的。例如,由多变量高斯分布包围的区域由其协方差矩阵的行列式来缩放。这是什么意思??和参数通常是由常客确定的!)使用最大似然估计——换句话说,导数。这里高层次直觉的最佳资源是 3Blue1Brown 。如果你想要一些微分和积分的实践经验,我推荐有机化学导师。他没有线性代数的播放列表,所以我联系了有列表的 PatrickJMT 。
数据争论
直接跳到熊猫身上可能很有诱惑力。我们快到了!但是首先,掌握好 SQL 是很重要的。Web Dev 简化版提供了一个很棒的 1 小时 SQL 速成班。重要的是您掌握了基础知识( Select、From、Where、Group by、Having 和 Joins ),然后掌握了相关子查询。这之后,真的是“熟能生巧!”参考 LeetCode(之前介绍过)来测试你的 SQL 精通程度。再次,针对“简单”和“中等”的问题。
在理解了处理数据争论的 SQL 方法之后,您就为熊猫做好了准备。再次,请参考科里斯查费寻求帮助。像 SQL 一样,真正的试金石是处理实践问题。我推荐 StrataScratch ,这是一种数据争论面试问题的 LeetCode。
机器学习
如果你遵循了我关于学习排序的建议,那么你一定已经注意到我把机器学习(ML)留到了最后。太多有抱负的数据科学家直接跳入 ML,而没有正确激励它所需的其他一切的坚实基础。StatQuest(前面介绍过)有关于 ML 的高级直觉的好内容。然而,掌握一个 ML 算法的最好方法是自己实现它。凭借你在 OOP、微积分、线性代数和概率论方面的新知识——信不信由你——你实际上已经准备好了!为此,我强烈推荐send ex。
产品感
产品意识是你处理模糊的产品问题、制定假设、识别结果性指标(也称为关键绩效指标——KPI)、设计实验和解释结果的能力。大多数数据科学家不会接受一个给定的 ML 模型,并将其准确性从 90%提高到 95%的任务。更有可能的情况是,数据科学家会被利益相关者提出一个问题,需要从头开始构建一个分析。为此,我强烈推荐数据面试 Pro 。
并非每份工作都以技术意义上的“产品”为中心,比如 Instagram Stories 或谷歌搜索。有些产品确实是有形的商品。为此,轻松的经济学观点会有所帮助。速成班有一个很棒的关于经济的播放列表。没有必要看微观经济学以外的东西,当然,除非你很好奇。
第 2 部分:一般提示
这里有一些关于你学习努力的一般性建议。
基础
打好坚实的基础是关键;科学家们期望知道的材料数据非常庞大。如果你直接跳到 ML,你会不断地回到基础,这是一个很差的构建搜索空间的方法。最好在基本原理上建立一个适当的基础,这样梯度下降的参考就不会把你带到一个意想不到的微分之旅。
同样,精通 SQL 和熊猫。如果您不清楚基本的 SQL 语法,没有人会对您掌握 PySpark 留下深刻印象…
集成是一个“美好的东西”
差异化是必须的!但是集成很繁琐,也很麻烦。底线是整合是逆向分化。通常,一个易处理的(可解的)解决方案并不存在。积分在概率论中会经常出现(一般是证明,比如从二项式 PMF 推导泊松 PMF。)然而,正如你从思考贝叶斯和统计反思中学到的,我们经常通过模拟来近似积分。最好能正确掌握蒙特卡罗方法(尤其是马尔可夫链蒙特卡罗方法)的工作原理。我概述的资源将带你到这里!
忘记深度学习
深度学习(DL)现在火了!但是一个鲜为人知的秘密是,能够使用 DL 的数据科学家的供应超过了对这类数据科学家的需求。另一个鲜为人知的秘密是——大多数数据科学家无论如何都不会用到 DL o 这个工作。第三个鲜为人知的秘密——领域知识可以弥补技术诀窍的缺乏。如果你知道一个系统是有效的,你对如何在其中构建你的实验和分析有很好的先验信念。如果你知道如何设计真正重要的特性,你就不需要使用高度灵活的 ML 模型。专注于使用更简单的统计和 ML 模型来回答问题,不要回避通过领域知识来缩小搜索空间。
自动化被高估了
每隔 2-3 年,谷歌、脸书和亚马逊都会以计算机视觉(CV)、自然语言处理(NLP)或强化学习(RL)的新进展登上头条。)这三者都有一个共同的主题——它们是没有具体解释的黑箱模型。简单地说——如果模型不主动消耗电力和使用计算资源,它就毫无用处。线性回归会吐出参数;无论您的机器是否被读入内存,您都可以使用这些来做出业务决策。CV、NL 和 RL 则不然。这些模型最适合自动化任务,人类通常可以完成这些任务(将英语翻译成意大利语、面部识别和驾驶汽车。)
会有一些 DS 角色会说,“设计一个算法,它会根据实时需求对产品进行动态定价。“但是这些工作很少。大多数工作会要求你“为所有消费者优化一个产品的固定价格假设你在一家杂货店工作,你已经使用逻辑回归来估计需求曲线微分,以确定一加仑牛奶的最优价格。现在假设你预计今天会有 1000 个顾客,而你有 500 加仑的油,需要卖掉或者扔掉。你应该把价格降低多少,才能卖出每加仑汽油?**
没有理由设计一个强化学习模型来回答这个问题!通过逻辑回归(一个斜率和一个截距输入到一个 sigmoid 函数中),你已经有了需求曲线。)假设当前价格对应于 40%的顾客购买牛奶。一千个客户的 40%就是 400 个客户。如果 400 名顾客购买了 400 加仑牛奶,那么你预计你将不得不扔掉 100 单位。然而,如果您简单地在需求曲线上搜索对应于 50%购买机会的价格,并将牛奶的价格设置为这个值,那么您会期望所有 500 个单位都被售出!
自动化功能强大,但在对普通数据科学家工作的适用性方面被高估了。使用领域知识和参数模型被严重低估了——我希望这个例子能说服你!
祝你好运!
喜欢我的内容就订阅:)
英语拼写比你想象的还要糟糕
八万多字的拼写和发音分析

发音网络。稍后查看更多|作者图片
我们都知道单词片段‘ough’可以有很多不同的发音。
- C 到
- D 到
- Pl 到
- 尽管如此
- 穿过到
- +更多(不太常见)的
这是一个经常被引用的英语拼写错误的例子。学习不同地发音同一个字母集需要大量的经验,并且在学习新单词时是令人沮丧的。
另一个例子是拼写土豆的“正确”方法。
虽然这将它推向了极端,但它仍然是这种现象的一个有效例子。它让你思考。
如果我们发出的每个声音都只有一种拼写方式,那会好得多,不是吗?
输入 国际音标 。
这个字母表试图标准化“以书面形式表示语音”,这听起来很棒!最后,不含糊!
问题是,人类能够发出的不同声音数量巨大。
en和 th 中的 th 音有唯一字母真的值得吗?事实上,这些声音之间有一个连续的频谱;一个从哪里开始,另一个从哪里开始?

国际音标肺辅音图通过识别 方式 和 位置 音 |截图来自维基百科
音调符号(每个字母周围的小符号)试图代表甚至更微妙的发音差异。这样做的结果是超过一百个不同的字母来代表声音。
概括起来
英语的字母数量相对于单词发音的数量来说太少了,这迫使字母代表多种发音,使得学习这门语言变得困难。
国际音标有更多的字母来代表单词的发音,导致了令人难以置信的臃肿的书面文本(公平地说,这不是它存在的原因)。
想象这个问题

国际语音词典(ISLE)的一部分|作者截图
*https://github.com/ethanr-2000/bad-spelling-generator
作为开发错误拼写生成器的一部分,我想出了一种将拼写和发音分解成音节的方法——在书面文本和语音之间提供一致的链接。
比如劝有一个 p ə ɹ的发音。s w ei d ,所以我根据和 suade 将单词分解为。
这里的数据源是国际语音词典,取自 Python 包 Pysle 。它包含了成千上万的单词和它们的发音。
由于单一的数据来源,这些单词更倾向于美式发音。在数据分析过程中要注意这种偏差。
我运行了一些 Python 脚本,从 80,000 多个英语单词中创建了关于音节发音和拼写的数据库。让我们一起探索这些数据,看看我们会发现什么。*
最易发音的音节
同理到有很多发音,让我们看看哪组字母的发音最多。

一个音节有多少种发音方法?|作者图片
音节 su 和 cha 的 17 种独特发音!?我认为很糟糕!
看看苏的数据,我们就能明白这实际上意味着什么。这是每个发音的打印输出,也是它所在单词的一个例子:
*Pronunciations for the letters 'su':
0 | ʃə | cen**su**rable
1 | sə | cap**su**lar
2 | zə | re**su**rrect
3 | ʃu | cen**su**al
4 | ʒə | clau**su**la
5 | su | ba**su**to
6 | ʃʊ | as**su**rance
7 | ʒu | audiovi**su**al
8 | zu | je**su**
9 | sʌ | blood**su**cker
10 | ʒʊ | cae**su**ra
11 | sjʊ | con**su**lar
12 | zjə | cha**su**ble
13 | sju | di**su**nite
14 | sʊ | e**su**rient
15 | zʊ | u**su**fruct
16 | zju | unpre**su**ming*
我对这种多样性感到惊讶。作为一个以英语为母语的人,你在日常生活中不会想到这个词,但几乎所有这些词你都可以凭经验正确发音。
诚然有些非常相似,但是试着把苏中的每一个字都念成血* 苏 满满你就会意识到一个细微的变化会产生多么大的差别。*
拼写最易变的音节
把前面的图表翻过来,我们来看看的读音有哪些的拼法。

每种声音有多少种拼法?|作者图片
发音 si (发音见)有超过 40 种不同的拼法?这似乎不合理。我们来看一下数据。
这是音节拼法的列表和一个可以找到它的例句。
*Spellings for pronunciation 'si':
0 | c | c
1 | cae | caecilian
2 | ce | abecedarian
3 | cea | ceases
4 | cee | divorcee
5 | cei | ceiling
6 | ceip | receiptor
7 | cey | pricey
8 | ci | acierate
9 | cie | facie
10 | cis | precis
11 | coe | biocoenosis
12 | cy | abbacy
13 | pse | psephological
14 | sai | saiva
15 | scae | muscae
16 | sce | ascesis
17 | scei | transceiver
18 | sci | biosciences
19 | scie | bioscience
20 | se | antiserum
21 | sea | battersea
22 | see | endorsee
23 | sei | caseinogen
24 | seig | seigneur
25 | seu | transeunt
26 | sey | anglesey
27 | si | albigensian
28 | sie | besieging
29 | sig | monsignor
30 | ssae | fossae
31 | sse | colosseum
32 | ssee | addressee
33 | ssey | odyssey
34 | ssi | aglossia
35 | ssie | aussie
36 | ssis | chassis
37 | ssy | bessy
38 | sy | apostasy
39 | ze | yangtze
40 | zi | ritziest
41 | zy | chintzy*
除了 18 和 19(有点可疑),我要说都是有效的。
你认为用 40 种不同的方法来写一个声音合理吗?英语好像是这么认为的。
最常见的音节
作为一个题外话,我感兴趣的是在我使用的单词列表中最常出现的音节。
简单地计算每个音节的字数的问题是,它不能公平地代表最“常见”的声音。这是因为我们不知道每个词在日常生活中的相对使用频率。
考虑到这一点,请看下图。

前 20 个最常见的音节音是什么?|作者图片
,如 a bandon,是用词最多的,紧随其后的是 ri (ree)和 li (lee)。前三个之后,会有一个很大的下降。
让我们缩小来看看前 200 个音节。

最常见的前 200 个音节是什么?|作者图片
在第 100 个最常见的音节附近,出现该音节的单词不到 500 个(80,000+个),这似乎表明绝大多数单词使用相同的几个音。
这让我想起了帕累托法则,也就是 80/20 法则,20%的原因构成了 80%的结果。在这里,原因是声音,效果是使用它们的单词。**
以下是该数据的“帕累托图”。

与不太常见的音节相比,语言中最常见的音节有多大分量?|作者图片
在这种情况下,效果更加不均衡。大约 20%的音节音构成了我们发出的所有声音的 90%。
言语的网络
我想要回答的下一个问题是这些音节是如何连接在一起的。假设已知一个单词中有另一个音节,那么每个音节出现在这个单词中的可能性有多大?
例如,如果我知道一个单词以 si、这个音开始,那么接下来最有可能是什么音呢?由此产生的网络图是我最喜欢的视觉效果之一。
我计算了前 20 个最常见的音节之间出现的频率。节点大小代表音节出现的总字数。

哪些音节经常出现在一起?|作者图片
的和的之间的紧密联系表明它们经常出现在单词中,而 di 和 tI 之间的微弱联系表明这些音节很少出现在单词中。**
总的来看,似乎两个音节越相似,它们之间的联系就越小。
元音的荒谬
最后,让我们记住我们如何开始这篇文章。
通过比较音节发音和拼写,我们能够识别发音差异很大的拼写,以及可能来自大量不同拼写的声音。
我们一直专注于音节,但在这个发音不一致的世界里,元音和是最糟糕的罪犯。**
让我们看看每个元音能发出的所有不同的音。在下面的热图中,深色代表字母/声音组合的大量单词。

五个元音分别发什么音?|作者图片
我们看到的是,有一些特定的声音,即使不是全部,也可以由大多数元音发出。 i 、 ʌ 、 ɑ 、 ə 、 I 这几个音都可以由 3 个以上的元音组成!
这还没有考虑元音组合(如 ai) ,以及不同的口音和方言,这些会让元音听起来更加混乱!
我想说的是,单词中使用的元音并不能明确地识别它所代表的声音,这可能会让任何试图学习这门语言的人感到沮丧。
关键要点
如果用一句话来概括这篇文章,那将是:书面语很少提供对口语的直观理解。****
当然,它可以给你指明正确的方向,但这需要多年的经验。即使这样,结果也是模糊的。
如果你想看到这种现象的发生,可以看看 Bad Spelling Generator,它通过替换其他单词中发音相同的音节来重新拼写单词。结果可能是荒谬的。
***http://badspellinggenerator.com/
如果你喜欢这篇文章,可以考虑关注我,看看我的其他作品,比如这个关于从推文中提取情感的项目。
***
英语到印地语的神经机器翻译
实践教程
探索所有 Seq2Seq 任务中普遍存在的基本 NLP 预处理和编码器-解码器架构

图片经由 Adobe Stock 授权给 Maharshi Roy
自该领域的研究开始以来,机器翻译无疑是自然语言处理中最常遇到的问题之一。通过本教程,我们将探索一个编码器-解码器 NMT 模型采用 LSTM。为了简单起见,我将在后面的文章中讨论基于注意力机制的更好的性能架构。
资料组
我使用了 IIT 孟买英语-印地语语料库作为本教程的数据集,因为它是可用于执行英语-印地语翻译任务的最广泛的语料库之一。呈现的数据实际上是每种语言的两个独立文件中的句子列表,看起来像:


单独文件中的英语和印地语句子对(图片由作者提供)
预处理
作为基本的预处理,我们需要删除标点符号,数字,多余的空格,并将其转换为小写。封装在函数中的非常标准的东西:
预处理功能
值得注意的一件重要的事情是目前的北印度语句子的恶名。除了通过上述函数运行句子之外,还需要对句子进行净化,以删除其中包含的英文字符。这实际上导致了训练中的NaNbug,耗费了大量的调试时间。我们可以通过使用 regex 将任何英文字符替换为空字符串来避免这个问题,如下所示:
hindi_sentences = [re.sub(r'[a-zA-Z]','',hi) for hi in hindi_sentences
LSTMs 通常不会执行很长的序列(虽然比 RNNs 好,但仍然无法与 Transformers 相比)。因此,我们将数据集缩减为包含长度不超过 10 个单词的句子。同样出于演示的目的,我们考虑总共 25000 个句子来保持较低的训练时间。我们可以实现以下目标:
准备数据
在翻译过程中,我们的模型如何知道什么时候开始和结束?为此,我们专门使用了
hi_data = ['<START> ' + hi + ' <END>’ for hi in hi_data]
虽然这个巫毒步骤的确切用法目前可能还不清楚,但是一旦我们讨论了架构和事情是如何工作的,就很容易理解了。在这一点上,我们仍然有字符语句形式的数据,需要将其转换为数字表示,以便模型进行处理。具体来说,我们需要为英语和印地语句子创建一个单词索引表示。我们可以使用 tensorflow 的内置令牌化器来解除手动执行这一任务,如下所示:
这里需要注意的一个重要参数是 oov_token ,,它本质上代表词汇之外。这是专门用在我们想限制我们的词汇量,说只有前 5000 个单词。在这种情况下,任何不受欢迎的单词(基于实例计数> 5000 的单词排名)将被参数值替换,并被视为不包含在词汇表中。这样,我们可以放松我们的模型,忽略数据集中出现的模糊和罕见的单词所造成的任何影响。另一个需要注意的要点是,我们为英语和印地语单词的 vocab 大小加 1,以容纳填充数字(稍后讨论)。
准备解码器输入和输出
NMT 模式的运作方式可以理解为一个两阶段的过程。在第一阶段,编码器模块一次一个单词地接收输入序列。因为,我们使用的是 LSTM 层,随着每个单词的消耗,内部状态向量【h,c】会得到更新。在最后一个字之后,我们只关心要输入解码器的这些向量的最终值。然后,第二阶段通过摄取编码器的最终[h,c]向量并消耗目标句子的每个标记来工作,从< START >开始。为每个令牌产生的输出将与预期的相匹配,并且生成误差梯度来更新模型。在用尽目标输入后,我们期望我们的模型产生一个< END >令牌,表示这个数据样本的完成。从下图中可以更好地理解这一点。

训练阶段“我会回来的”的旅程(图片由作者提供)
我们从《终结者》(1984) ~《我会回来的》来演示阿诺德的著名对白的翻译。我们可以看到,解码器 LSTM 是用编码器的最终[h,c]初始化的。我们可以将这一步理解为将输入句子的思想/摘要传递给解码器 LSTM。灰色方框表示 LSTM 单位的输出,红色方框表示每个时间步长的目标令牌。当第一令牌< START >被馈送到解码器 LSTM 中时,我们期望它产生मैं,然而它产生तुम,说明了对误差梯度的贡献(用于随时间反向传播)。从上图中可以清楚地看到,我们应该按照下面的方式重新排列我们的目标句子,这可以使用下面的代码块来实现:
decoder-input: <START> मैं वापस आऊंगा
decoder-output: मैं वापस आऊंगा <END>
将解码器输入和输出以及填充序列对齐到固定长度
LSTMs 仅适用于固定长度的序列。既然我们选择了不超过 maxlen 、的句子长度,那么将所有实例填充到那个长度似乎是合乎逻辑的。
模型架构
为了保持较低的训练时间,同时具有足够的维度来捕捉数据中的变化,我们选择将我们的向量维度( d_model )设为 256。
编码器
如上面的代码片段所示,我们关心最终的内部状态【h,c】,而不是编码器 LSTM 的输出。
解码器
如上所述,解码器 LSTM 由编码器内部状态初始化,并且其输出被馈送到密集层,该密集层的单元数量等于具有 softmax 激活的目标 vocab 大小( hindi_vocab_size )。想法是在目标词汇表的所有单词上生成概率分布。选择具有最高值的一个,意味着这个特定的令牌是最有可能的,这正是我们在这里所做的。我们使用稀疏分类交叉熵作为要优化的损失函数。
培训和结果
我在 95%-5%的训练测试分割上训练了上述网络大约 100 个历元,在此期间,我观察到它在 80 个历元时遭遇 NaN loss。因此,我使用模型检查点来保存中间解决方案,如下所示:
检查点并保存最佳验证准确性
考虑到 google colab 中有限的电源和断开连接(如果保持空闲训练),我不得不在一个相当乏味的模型和有限的训练数据上妥协,用它我实现了 93%的训练准确率和 26%的验证准确率😢。
推理模型
加载保存的模型。我们需要使用 tensorflow 模型的 get_layer API 引入预训练层,以便在我们的推理工作流中重用。
从加载的预训练层准备推理模型:
推理模型
推理模型重用了经过训练的编码器和解码器模型。编码器部分将经过训练的嵌入层作为输入,并输出最终的【h,c】(实质上也引入 LSTM 层),供解码器使用。解码器以类似的方式将第一令牌的训练目标嵌入层和编码器最终状态【h,c】作为输入。对于每个后续令牌,我们提供先前的解码器内部状态(本质上给出当前单词的上下文,并在此基础上预测下一个单词)。从下面的推理算法可以更好地理解这一点:
翻译步骤
在翻译步骤中,我们将英语句子输入编码器,得到输出状态【h,c】。主要的翻译发生在第 10–18 行,在这里我们获得当前单词的下一个单词,并更新内部状态以用于下一步。这个循环一直重复,直到我们得到一个<结束>令牌或者到达 maxlen 令牌。
使用谷歌翻译进行推理和 BleU 评分
为了进行推理,我使用了一些句子,并使用了谷歌翻译的其他翻译版本来计算语料库 BleU 的分数。我从训练集中选择样本只是因为我们的验证分数很低,我预计我们的模型在测试集上的表现也同样糟糕(不适合演示)。
注 1: BleU 或双语评价 Understudy Score 是评价预测句子与参考语料库相似度的度量。它在候选数据和参考数据之间的 N 元匹配上使用精确召回概念。数学解释超出了本文的范围,因此,我建议读者仔细阅读 Jason Brownlee 的一篇受欢迎的博客文章。
注 2: 谷歌翻译 API 不是一个免费功能,使用说明可以在相应的 GCP 文档中找到。
下面显示了执行的一些实例。很明显,鉴于我们的模型在训练数据上的出色表现和大约 0.42 的 BleU 分数(一个令人惊讶的高值),我们的模型已经过度拟合了。一旦看到新的数据,这个模型就会彻底失败,这一点也不奇怪。
Input: accerciser accessibility explorer
Prediction: एक्सेर्साइसर पहुंचनीयता अन्वेषक
Dataset Reference: एक्सेर्साइसर पहुंचनीयता अन्वेषक
Google Translated Reference: accerciser अभिगम्यता एक्सप्लोररInput: the default plugin layout for the bottom panel
Prediction: ऊपरी पटल के लिए डिफोल्ट प्लगइन खाका
Dataset Reference: निचले पटल के लिए डिफोल्ट प्लगइन खाका
Google Translated Reference: निचले पैनल के लिए डिफ़ॉल्ट प्लगइन लेआउटInput: highlight duration
Prediction: अवधि को हाइलाइट रकें
Dataset Reference: अवधि को हाइलाइट रकें
Google Translated Reference: हाइलाइट अवधि
几十年来,神经机器翻译无疑一直是 NLP 的一个挑战性问题。虽然,接近完美的实现,如谷歌翻译等。今天存在,使我们的生活更容易,但我们在这个领域还有很长的路要走,特别是对于低资源语言。尝试探索这个问题对我来说是一个巨大的学习曲线,我希望这个社区的读者可以从我的分享经历中受益。完整的笔记本可在这里获得。
用这些小贴士提升你的 kedro 体验
从软件包安装、批量保存到子流水线和日志记录。愿这些建议能让你的 kedro 生活更轻松。

目录
什么是 kedro,我们为什么要使用它?
#1。设计结构采用模块化管道
#2。使用 Jupyter 笔记本建立流程
#3。对单独的管道使用日志记录
#4。批量保存结果
#5。
软件包版本控制 #6。常用功能存储位置
本文假设您知道如何独立开发 kedro 项目,或者至少在其官方页面上浏览过“宇宙飞船”示例。我将简单介绍一下这个工具,然后看看我的提示列表,这是我用【kedro 】(适用于所有版本= < 0.17.0) 进行了几个月的试验(和努力)的结果
我已经写了一个关于地理人口分析的“ kedro 动手”系列,本文中的知识已经在那里实现了:
什么是 kedro,为什么要用它?
Kedro 是一个开源 Python 框架,用于创建可复制、可维护和模块化的数据科学代码。它从软件工程中借用概念,并将其应用于机器学习代码;应用的概念包括模块化、关注点分离和版本控制。[1]
使用 Kedro 的主要优势是以一种非常流畅的方式在数据科学领域应用软件和数据工程的概念。
Jo Stichbury 关于 kedro 的文章是一个很好的开始:
#1.模块化管道设计结构
在开始任何项目之前,坐下来规划整体结构是明智的。当旋转一个新的 kedro 项目时,它会创建一个标准的文件夹结构,如下所示:

作者图片
对于一个简单的数据科学项目,上面的结构已经足够好了。然而,当处理一个更复杂的项目时,虽然您可以添加更多的节点并使管道更长,但它会击败 kedro 的“干净组织”优势。所以我的经验法则是“如果管道有 5 个以上的节点,让我们看看我是否能把它们分成子管道”(如果不能,尽量把节点数保持在 5 到 10 之间)。为什么“5”—因为对我来说,在 Pycharm 窗口中,你不用向下滚动就能看到大约 5 个节点。
如果管道有 5 个以上的节点,让我们看看是否可以将它们分成子管道

例如:我的 CheapAtlas 项目中有 3 个节点的管道。作者图片
为了避免这些混乱,您可以创建一个子管道(或官方文档中的模块化管道):
kedro pipeline create <pipeline_name>
当运行上面的命令时,除了生成额外的文件夹,kedro 还将添加参数和目录 YAML 文件到:
conf/base/pipelines/<sub_pipeline_name>/
因此,如果您为数据准备流程名称preparation_1和preparation_2 创建 2 个子管道,则文件夹结构将更新如下:

conf 和 src 文件夹中的更改。作者图片
在这里,设置*conf/base/pipelines*中的 YAML 文件将拥有优先于根*conf/base*文件夹中的 YAML 文件。换句话说,您可以单独为**preparation_1** 或**preparation_2** 指定目录中的哪些数据集和哪些参数,而无需在根目录. yml 和 parameters.yml 中列出所有数据集和参数
也可以在子管道下创建子管道。但这一次,你需要做一些手动复制文件和文件夹。
不仅如此,您可以在子管道中创建子管道,这使得组织任务更加清晰。但是,在这种情况下,您还不能使用 kedro CLI。所以我们必须自己创建文件夹和复制样板文件。继续上面的例子,如果我想在**preparation_1**下创建一个子管道**preparation_1_a** 和**preparation_1_b** 会发生什么

亚-亚管道。作者图片
这样,*conf/base/pipelines/preparation_1*下的 YAML 文件将同时应用于**preparation_1_a** 和**preparation_1_b**。要连接它们,您需要在**preparation_1/pipelines.py** 中的 create_pipeline() 下指定您想要连接的所有子子管道,而不是像普通的 pipelines.py 文件那样列出所有节点(请将< name_of_package >改为您的项目包名称)
preparation_1 文件夹下的 pipelines.py。作者图片
#2.使用 Jupyter 笔记本建立流程
搬到 kedro 不代表放弃笔记本。我本人非常喜欢与 Jupyter Lab 合作(最近他们发布了 3.0 版本!)因为我可以轻松地快速测试和构建我的流。Kedro 还安装了 Jupyter Notebook,但是您应该升级软件包,因为它们使用的是旧版本的 0.4x。
pip uninstall jupyter
pip install jupyter
pip install jupyterlab
然后,您可以通过以下方式启动 Jupyter 集群(记住将终端光标放在项目文件夹中):
kedro jupyter lab
你可以阅读 kedro 关于与笔记本整合的官方文档。对我来说,使用笔记本进行 EDA 是一个很大的优势,因为你可以轻松地制作图表材料。此外,用多个笔记本建立一个完整的机器学习工作流也更容易理解:你可以从 0_EDA 到 1_data_preparation 等等开始,并将它们都放在**notebooks**文件夹下。

【CheapAtlas 项目的 Jupiter 笔记本文件夹示例。作者图片
#3.对单独的管道使用日志记录
Kedro 官方文档有一些关于日志记录的指南,我认为这是调试的关键部分。通常,当不使用记录器时,您可以直接将结果打印到 CLI 终端。但是,如果您的 kedro 管道生成大量日志或打印出大量信息,那么就很难使用终端窗口进行追溯(而且您甚至没有在外部保存日志文件)。
要启用记录器,最好将其插入到**nodes.py**中的函数中
# for logging
import logging
log = logging.getLogger(__name__)< codes here >
logging.info(abc)
logging.warn(abc)
logging.error(abc)
我通常会尽量多地记录*warnings* 和*errors* ,但尽量少记录*infos* ,因为讽刺的是,它们是你最不想存储的信息。要了解有关不同级别的日志记录的更多信息,请参考此处:
另一个技巧是指定处理程序来记录到管道的特定文件夹中——这意味着如果您正在运行子-子管道,日志应该保存在该管道的相应文件夹中。从上面的例子中可以看出,如果您只想保存**prepration_1_a** 子管道的日志,文件应该保存在这里:

为日志文件分而治之。作者图片
同样,结构看起来很清楚,但是如果您的管道不是那么复杂并且不生成长日志,我仍然建议坚持默认设置,只使用一个日志文件。
#4.批量保存结果
我同意 Lou Kratz 关于 kedro 的一个问题是,kedro 不支持像用增量数据集读取那样好的写入结果。这意味着,如果您正在处理一个运行了 6-7 个小时的大型数据集,突然内存不足或遇到一些错误…您的结果将不会被保存(哎哟!).问题已经在这里提出,让我们等待更新。
与此同时,我们可以通过将保存结果分成多个文件并使用日志记录来跟踪它们来实现一种变通方法。如果您想将结果保存到一个文件中,使用pandas.write_csv的追加功能,如下所示。使用这种方法,当在*pipelines.py* 中声明节点时,您不必指定**Outputs**(参见我在技巧#1 中的 3 个节点的例子)。记得直接在*nodes.py*写日志跟踪进程就行了
import os
idx = (put the tracking index in the code section before this)# if file does not exist write header
if not os.path.isfile('filename.csv'):
df.to_csv('filename.csv', header='column_names')
else: # else it exists so append without writing the header
df.to_csv('filename.csv', mode='a', header=False)
# logging here for tracking with idx as the index
logging.info(f'Append result for element {idx} complete!')
将结果分成多个保存文件或使用 pandas.write_csv()的 append 函数,并使用日志记录来跟踪进度
#5.包版本控制
而 kedro 的功能是用kedro build-reqs自动建立一个包需求列表,你可以用pip install -r requirements.txt安装所有的需求。然而,我发现这给我的项目带来了很多麻烦(因为一些已知的原因,比如pip vs conda install以及 python version = >,结果是使用 3.7 在这一点上是最稳定的——当然,还有一些未知的原因)。
过了一段时间,我发现保持事情一致性的最好方法是在requirements.in 中放入“硬锁”包版本,而不是依赖自动命令。一篇关于为什么requirements.txt不够以及如何使用pip freeze和pip-compile 的好文章,在这里。
#6.常用功能存储位置
在开发管道时,有时您希望重用已经在其他管道中开发的代码块。为了避免引用存储代码的nodes.py,最好创建一个文件夹来存储src/common中的那些函数,然后使用它作为引用的中心点。
在项目开始时,最好在 src/common 中为可重用函数创建一个文件夹,以便您可以在以后引用它们
继续上面的例子,当开发 preparation_1_b 时,您注意到有一个来自preparation_1_a/nodes.py的函数,您希望重用。通常情况下,你会这样写preparation_1_b/nodes.py:
from <name_of_package>.preparation_1.preparation_1_a.nodes import a,b,c
你应该做的是
from <name_of_package>.commons.helpers import a,b,c

在 src/commons 中存储可重用的函数。作者图片
感谢阅读!如果你能留下回复,我会很高兴,我们可以进一步讨论这个话题。
参考
[1] QuantumBlack。(2020).什么是 Kedro?— Kedro 0.17.0 文档。[在线]可从以下网址获取:https://kedro . readthedocs . io/en/stable/01 _ introduction/01 _ introduction . html【2021 年 1 月 24 日获取】。
用 SQLite 增强你的熊猫技能
python 内置数据库 SQLite 的一个简单技巧,使您的数据操作更加灵活和轻松。

Pandas 是一个强大的 Python 包,可以处理你的数据。但是,您是否遇到过一些任务,让您觉得‘如果我能在这里使用 SQL 查询就好了!’?我个人觉得在 pandas 中连接多个表并只提取那些您想要的列时特别烦人。例如,您想要连接 5 个表。您完全可以只用一个 SQL 查询就做到这一点。但是在熊猫身上,你要做 4 次归并,a+b,(a+b)+c,((a+b)+c)+d,…更糟糕的是,每次合并时,pandas 将保留所有列,尽管您可能只需要另一个表中的一两列。如果你和我有相同或相似的问题,你来对地方了。
这个问题让我想到了 SQLite,这是一个内置的轻量级 Python 数据库。内置意味着它是 Python 自带的,你不必再运行任何 pip/conda install 来安装这个包。有多神奇?!让我们看看这个魔术是如何运作的。有两种方法可以实现这一点,要么使用内置的 sqlite3 包,要么也可以使用 sqlalchemy。我将在下面说明这两个问题。
只需导入软件包,无需任何其他安装(您可能需要安装 sqlalchemy):
import sqlite3 as sl
import sqlalchemy as sa
from sqlalchemy import create_engine
不要担心驱动程序或连接字符串等。您可以像下面这样简单地创建连接:
# SQLite
sl_conn = sl.connect(‘{your db name}.db’)# SQLAlchemy
engine = create_engine('sqlite:///PATH/TO/DB/{your db name}.db',echo=True)
sa_conn = engine.connect()
如果数据库不存在,这将自动创建数据库,然后连接到它。否则,它将直接连接到数据库。多方便啊?
请注意,这将创建一个 db 文件。在 sqlalchemy 的create_engine中,将echo属性设置为 True 将会打印出日志。如果不想看到整个日志被打印出来,设置为 False。
一旦你创建了连接,通过熊猫的to_sql和read_sql功能与数据库进行交互就更加容易了。
假设您已经有了一些想要使用 SQL 查询的 pandas 数据帧。第一步是将数据帧导入到我们刚刚创建的数据库中。
# sa_conn and sl_conn can be used interchangeably
df_1.to_sql(‘table_1’, sa_conn, if_exists='replace')
df_2.to_sql(‘table_2’, sl_conn, if_exists='replace')
df_3.to_sql(‘table_3’, sa_conn, if_exists='replace')
您可以在数据库中指定表名,它不必与数据帧的名称相同。使用if_exists来指定如果该表已经存在于数据库中,您想要做什么。选项包括“失败”、“替换”、“追加”。
- ' fail ':引发 ValueError。(默认)
- ' replace ':在插入新值之前删除该表。
- ' append ':向现有表中插入新值。
一旦数据库中有了表,就可以开始使用 SQL 查询它们。
query = ```
SELECT
*
FROM table_1
INNER JOIN table_2 ON table_1_key=table_2_key
INNER JOIN table_3 ON table_1_key=table_3_key
```
joined_table = pd.read_sql(query, sa_conn)
首先将您的查询定义为一个字符串,如果查询很长并且有多行,使用`````,然后使用 pandas 的函数read_sql从数据库中提取数据并将其转换为 pandas dataframe。
使用 SQL 客户端工具与数据库进行交互也是完全不需要动脑筋的。可以使用 DBeaver 或者 DB Browser for SQLite (DB4S)之类的工具。
迪贝弗
打开 DBeaver 并选择 SQLite 作为数据库驱动程序。

浏览到之前生成的 DB 文件,然后单击 finish。

DB4S
DB Browser for SQLite 也不例外。单击“打开数据库”,然后导航到数据库文件并打开它。

恭喜你!!现在你知道如何利用 Python 的内置数据库工具 SQLite,将你的熊猫技能带到另一个水平!通过集成 pandas 和 SQL 查询,您的数据处理技能变得更加灵活和强大。
用边缘图增强你的散点图
在您的 Plotly Express 散点图上显示额外信息

图示测井数据的散点图,在两个轴上显示边际图。图片由作者提供。
散点图是数据科学中常用的数据可视化工具。它们允许我们在二维图上画出两个数值变量,如点。从这些图中,我们可以了解这两个变量之间是否存在关系,以及这种关系的强度如何。
在这个简短的教程中,我们将使用优秀的 Plotly 库来可视化一个数据集,我们将了解如何在 y 轴和 x 轴的边缘添加边缘图,以增强我们对数据的可视化和理解。
我已经介绍了在 plotly 和 matplotlib 中创建散点图,您可以在下面找到:
本教程的一部分在我的散点图视频中有所涉及
普洛特利图书馆
Plotly 是一个基于网络的工具包,用于生成强大的交互式数据可视化。这是非常有效的,可以用很少的代码行生成图。这是一个受欢迎的库,包含各种各样的图表,包括统计、金融、地图、机器学习等等。
Plotly 库有两种主要用途:
- Plotly Graph Objects,这是一个用于创建图形、轨迹和布局的低级接口
- Plotly Express,它是 Plotly 图形对象的高级包装器。Plotly Express 允许用户键入更简单的语法来生成相同的绘图。
使用 Plotly Express 创建散点图
加载库&数据
第一步是加载 pandas ,它将用于加载我们的数据,以及 plotly.express 用于查看数据。
import pandas as pd
import plotly.express as px
一旦导入了库,我们就可以导入数据了。
我们将在本文中使用的数据集来自 Xeek 和 FORCE(https://xeek.ai/challenges/force-well-logs/overview)举办的岩性预测机器学习竞赛。竞赛的目的是从由 98 口训练井组成的数据集中预测岩性,每口井的测井完整性程度不同。目的是根据测井测量预测岩相。要下载该文件,请导航到上面链接的数据部分。原始数据源可在:https://github . com/bolgebrygg/Force-2020-机器学习-竞赛下载
df = pd.read_csv('xeek_subset_example.csv')
然后我们可以调用df来查看数据帧的前五行和后五行。

包含测井数据的数据帧。图片由作者提供。
我们得到的是上面的数据帧。我们的数据集包含两个测井测量值(ρb-体积密度和 NPHI-中子孔隙度),一个深度曲线和一个地质解释的岩性。
创建散点图
用 Plotly Express 创建散点图非常简单,我们指定数据帧和我们想要绘制的列。
px.scatter(data_frame=df, x='NPHI', y='RHOB', range_x=[0, 1],range_y=[3, 1], color='LITH')
这将返回以下散点图。目前,它看起来有点乱,因为许多岩性有重叠的价值。这是因为解释的岩性是基于许多不同的测井测量和岩屑描述而产生的。

测井数据散点图的简单图示。图片由作者提供。
点击图例中的 LITH 名称,可以隐藏单个 LITH 组。

简单绘图表示滤波后的测井数据散点图。图片由作者提供。
向 Plotly Express 散点图添加边际图
边缘图是可以附加到 y 轴和 x 轴边缘的迷你图。Plotly Express 中有四种不同类型的边际图。
箱线图
箱线图是一种基于五个关键数字显示数据分布的图形和标准化方法:最小值、第一个四分位数(第 25 个百分位数)、中值(第二个四分位数)。/第 50 个百分位数)、第 3 个四分位数(第 75 个百分位数)和“最大值”。最小值和最大值分别定义为 Q1-1.5 * IQR 和 Q3 + 1.5 * IQR。任何超出这些限制的点都被称为异常值。

箱线图的图形描述,突出显示关键组成部分,包括中位数、四分位数、异常值和四分位数间距。作者创建的图像。
边缘箱线图可以添加到单个轴上,如下所示
px.scatter(data_frame=df, x='NPHI', y='RHOB', range_x=[0, 1],range_y=[3, 1], color='LITH',
marginal_y='box')

用 y 轴上的一系列箱线图来表示散点图。图片由作者提供。
或者通过指定关键字参数marginal_y和marginal_x的值来指定两个轴。
px.scatter(data_frame=df, x='NPHI', y='RHOB', range_x=[0, 1],range_y=[3, 1], color='LITH',
marginal_y='box', marginal_x='box')

用轴上的箱线图来表示散点图。图片由作者提供。
地毯图
Rug 图用于显示数据分布,可添加如下内容:
px.scatter(data_frame=df, x='NPHI', y='RHOB', range_x=[0, 1],range_y=[3, 1], color='LITH',
marginal_y='rug', marginal_x='rug')

用轴上的地毯图来表达散点图。图片由作者提供。
直方图
直方图是一种优秀的数据可视化工具,看起来类似于条形图。然而,直方图使我们能够深入了解一组数据中的值的分布,并使我们能够在一个简洁的图中显示大量数据。在岩石物理学和地球科学领域,我们可以使用直方图来识别异常值,也可以挑选关键的解释参数。例如,来自伽马射线的粘土体积或页岩体积端点。
要将边缘图更改为直方图,我们的操作如下:
px.scatter(data_frame=df, x='NPHI', y='RHOB', range_x=[0, 1],range_y=[3, 1], color='LITH',
marginal_y='histogram', marginal_x='histogram')

用轴上的直方图边缘图来表示散点图。图片由作者提供。
小提琴情节
Violin 图类似于 box 图,但它们也结合了核密度估计图的强大功能。除了说明箱线图显示的关键统计点之外,它还允许我们深入了解数据的分布。
px.scatter(data_frame=df, x='NPHI', y='RHOB', range_x=[0, 1],range_y=[3, 1], color='LITH',
marginal_y='violin', marginal_x='violin')

用轴上的小提琴边际图来表达散点图。图片由作者提供。
混合边际图
您不必在两个轴上绘制相同的图,您可以在 x 轴上使用直方图,在 y 轴上使用小提琴图。
px.scatter(data_frame=df, x='NPHI', y='RHOB', range_x=[0, 1],range_y=[3, 1], color='LITH',
marginal_y='violin', marginal_x='histogram')

用轴上的混合边际图来表示散点图。图片由作者提供。
摘要
在这个简短的教程中,我们看到了如何使用测井数据在 plotly express 散点图上显示各种边际图。这些图可以增强我们的数据可视化,并为我们提供有关数据分布的更多信息。
感谢阅读!
如果您觉得这篇文章有用,请随时查看我的其他文章,这些文章介绍了 Python 和测井数据的各个方面。你也可以在GitHub找到我在这篇文章和其他文章中使用的代码。
如果你想联系我,你可以在LinkedIn或者我的 网站 找到我。
有兴趣了解更多关于 python 和测井数据或岩石物理学的知识吗?跟我上 中 。
如果你喜欢阅读这些教程,并想支持我作为一名作家和创作者,那么请考虑报名成为一名媒体成员。一个月 5 美元,你就可以无限制地阅读数千篇各种主题的文章。如果您使用 我的链接 ,注册,我将为您赚取一小笔佣金,无需额外费用!
https://andymcdonaldgeo.medium.com/membership
利用安讯士上的图像增强您的电力 BI 报告
你有没有想过在 axis 上使用图像而不是“常规”文本?或者,甚至在切片机内?检查这个简单的技术,你就可以开始了!

作者图片
如果你经常关注我的故事,你应该已经注意到我经常试图增强 Power BI 提供的内置可视化效果。在我看来,在某些情况下,作为一名 Power BI 开发人员,你可以超越自我,使用简单的技巧将用户体验提升到一个新的水平。
不久前,我正在为我们的客户支持部门创建一个报表解决方案。基本上,支持代理执行四种不同类型的与客户的交互—聊天、电子邮件、电话和调查。此外,管理人员需要一份报告来衡量每种特定类型的交互数量,以便他们可以确定高峰时间并为特定任务分配代理。
搭建舞台
对于这个例子,我将保持事情简单。有一个包含交互唯一 id、交互日期和交互类型的表。第一步是创建一个显式度量,它将计算交互的数量。
Total Interactions = COUNT(Sheet1[Interaction ID])
这是我此刻的视觉效果:

作者图片
如你所见,这里没什么特别的,“经典”柱形图。所以,让我们试着给它注入一些活力。我已经写过 DAX 中的 UNICHAR() 函数,以及如何在某些场景中利用这个函数。该函数返回 Unicode 字符,以表示您提供的数值。
现在,这个想法是用文字的视觉表现来代替文本值:聊天、电子邮件、调查和电话。有一系列不同的符号可以用 Unicode 表示。这里的是非常全面的符号列表,以及它们的 Unicode 值。
替换文本值
第一步是在 Power BI 数据模型中创建一个新表,它将保存关于 Unicode 值的数据。

作者图片
将表加载到数据模型后,我将切换到模型视图,并在该表和我的原始表之间建立“一对多”关系:

作者图片
之后,我将在我的交互 Unicodes 表中创建一个新的 DAX 列。神奇的事情发生了——我们将使用 UNICHAR() 函数,作为参数,我们将提供来自 Unicode 列的值:
Interaction Type Icon = UNICHAR('Interactions Unicodes'[Unicode])
瞧,现在看看这一列中的值!

作者图片
当我们建立了表之间的关系后,让我们抓取该列,并将其放在柱形图的 x 轴上:

作者图片
看起来很酷,对吧?您可以增加/减少 x 轴的字体大小,就像您对“正常”文本所做的一样——并且您的图标将被调整大小!
此外,我们还可以将这些图标用作切片器!让我向您展示如何使用它来增强您的用户体验。

作者图片
正如你在上面的插图中看到的,我已经很好地格式化了切片器,看起来像一组按钮(通过使用水平方向),所以我现在可以通过点击它来切片我的数据!我还创建了一个矩阵视觉,你可以看到我们的图标在那里,就像普通的文本列一样。最后,UNICHAR()函数将返回文本值,您可以像处理普通文本一样处理它。
结论
要记住的关键事情是:在创建 Power BI 报告时,不要让这个技巧成为主要的“工作方式”!当然,在大多数情况下,你应该坚持在你的视觉效果中显示价值的传统方式。但是,在某些情况下,应用这种技术可以增强用户体验。
在我看来,如果你决定使用这个技巧,有两件事非常重要:
- 处理有限数量的类别——正如你在我的例子中看到的,有固定数量的可能类别(聊天、电话、电子邮件和调查),所以没有轴/切片器被太多图标弄得混乱的危险
- 图标必须提供“常规”文本值 的上下文——用户必须明白每个图标代表什么。如果不是这样,你的报告将会导致混乱而不是清晰,这也是在使用这种技术之前要三思的另一个有效的理由
像往常一样,尝试在增强用户体验和使用这些非标准技术可能导致的额外开销之间找到适当的平衡。
感谢阅读!
使用 pycodestyle 增强 Python 代码的可读性
基于 PEP-8 风格约定自动检查 Python 脚本的可读性和质量

Emmanuel Ikwuegbu 在 Unsplash 上拍摄的照片
编程是数据从业者工具箱中不可或缺的技能,虽然创建一个脚本来执行基本功能很容易,但是大规模编写良好的可读代码需要更多的工作和思考。
鉴于 Python 在数据科学中的受欢迎程度,我将深入研究使用 pycodestyle 进行样式向导检查,以提高 Python 代码的质量和可读性。
内容
关于 PEP-8
pycodestyle 检查器根据 PEP-8 样式约定提供代码建议。那么 PEP-8 到底是什么?
PEP 代表 Python 增强提议,而 PEP-8 是概述编写 Python 代码最佳实践的指南。于 2001 年创作,其主要目标是通过标准化代码样式来提高整体代码的一致性和可读性。
需要注意的一点是,PEP-8 旨在作为指南,而不是旨在被解释为始终严格遵守的圣经指示。
动机
快速浏览一下 PEP-8 文档会立即发现有太多的最佳实践需要记住。
此外,已经花了这么多精力编写了这么多行代码,您肯定不希望浪费更多时间来手动检查脚本的可读性。
这就是 pycodestyle 发挥作用的地方,它可以自动分析您的 Python 脚本,并指出代码可以改进的具体领域。

格伦·卡斯滕斯-彼得斯在 Unsplash 上的照片
装置
这个包被命名为 pep8 ,但是为了减少混淆,被重新命名为 pycodestyle 。这是在 Python 的创造者(吉多·范·罗苏姆 ) 强调工具不应该以样式指南命名之后,因为人们可能会根据 pep8 (工具)的行为与 PEP-8(样式指南)进行“斗争”。
pip 是首选的安装程序,您可以通过在终端中运行以下命令来安装或升级 pycodestyle :
# *Install pycodestyle*
pip install pycodestyle# *Upgrade pycodestyle*
pip install --upgrade pycodestyle
基本用法
最直接的用法是在 Python 脚本上运行 pycodestyle 。py 文件)作为终端中的命令。让我们使用下面的示例脚本(名为pycodestyle _ sample _ script . py)进行演示:
我们通过运行这个简单的命令让 pycodestyle 开始工作:
pycodestyle pycodestyle_sample_script.py
输出指定了违反 PEP-8 样式约定的代码位置:

运行 pycodestyle | Image by Author 后的输出
每行中用冒号分隔的一对数字(如 3:19)分别指行号和字符号。
例如,6:64 E202 whitespace before ')'的输出意味着在第 6 行中,在第64字符标记处有一个意外的空白。
您可以通过解析统计数据参数来查看错误发生的频率:
pycodestyle --statistics -qq pycodestyle_sample_script.py

pycodestyle 确定的错误频率|按作者分类的图片
在上面的输出中,我们看到在右括号“ ) ”前出现了 4 次意外的空白。
高级用法
我们也可以将 pycodestyle 直接导入到我们的 Python 代码中来执行自动化测试。这对于自动测试多个脚本的编码风格一致性非常有用。
例如,可以编写以下类来自动检查是否符合 PEP-8 约定:
**import** **unittest**
**import** **pycodestyle**
**class** **TestCodeFormat**(unittest.TestCase):
**def** test_conformance(self):
*"""Test that the scripts conform to PEP-8."""*
style = pycodestyle.StyleGuide(quiet=**True**)
result = style.check_files(['file1.py', 'file2.py'])
self.assertEqual(result.total_errors, 0, "Found style
errors")
该工具也可以被配置,以便基于我们定义的样式规则首选项来完成测试。例如,我们可以删除不希望在检查中检测到的特定错误:
style = pycodestyle.StyleGuide(ignore=['E201', 'E202', 'E501'])
或者,我们可以指导 pycodestyle 一起使用不同的配置文件(包含一组特定的样式规则)。
**import** **pycodestyle**
style = pycodestyle.StyleGuide(config_file='/path/to/tox.ini')
还有更多功能可以满足您的需求,请访问pycodestyle的文档页面了解更多详情。
结论
在本文中,我们看了如何使用pycodestyle工具来检查我们的 Python 脚本是否符合 PEP-8 代码样式约定。
代码被阅读的次数比被编写的次数多,所以我们的代码是一致的、可理解的和结构整洁的是至关重要的。相信我,你的合作者和你未来的自己会为此感谢你的。
欢迎您加入我的数据科学学习之旅!关注此媒体页面以了解更多数据科学内容,或者在 LinkedIn 上联系我。编码快乐!
参考
*
请随意查看远程 Python 职位,这是一个关于远程 Python 相关机会的职位公告板。*
用用于异常检测的存储器模块增强自动编码器
理解大数据
视频、异常和自动编码器

视频、异常和自动编码器
检测视频流中的异常是一项艰巨的任务。即使深度学习技术的兴起可以利用闭路电视摄像头生成的大量数据,这项任务仍然很难解决,因为顾名思义,异常是罕见的,几乎不可能为监督学习标注所有类型的异常。在本文中,我们将介绍如何检测视频馈送中的异常,以及当代作品如何使用内存模块来提高性能。
正如我们所提到的,一个注释良好的、全面的数据集对于异常检测来说是非常难以构建的。这就是为什么无监督学习被用于异常检测,即深度自动编码器。
自动编码器以无人监督的方式进行训练,也就是说,我们给神经网络分配表示学习的任务。本质上,神经网络具有编码器-解码器架构,其中编码器学习压缩的特征集(表示),解码器使用该表示来重构输入(通常,但不总是)。
自动编码器如何用于异常检测?
对于异常检测,自动编码器的任务是重建输入。仅在来自正常场景的图像上训练自动编码器,并且假设当模型遇到异常数据时,自动编码器将具有高重建误差。然而,这一假设在实践中并不成立。经验表明,卷积神经网络(CNN)的表示能力非常强大,足以以低重构误差重构甚至异常的帧。
已经观察到,有时自动编码器“概括”得很好,以至于它也可以很好地重建异常,导致异常的漏检。
内存模块在这一切中处于什么位置?
Gong 等人和 Park 等人的工作试图通过向网络架构添加存储模块来解决这个问题。这两部作品都试图记忆正常数据的原型元素,并使用这种记忆来扭曲异常数据的重构。更简单地说,他们不仅仅使用来自编码器的表示,还使用“存储项”来降低 CNN 的表示能力。在这些工作中,记忆模块的使用略有不同。让我们剖析一下这些实现。
龚等,MemAE

基于 MemAE 的异常检测。这个可视化显示了一个简化的版本,其中只有一个记忆项目被查询。MemAE 专门从存储器项目重建输出。图片由巩等人提供。
MemAE 本质上是一个编码器-解码器,在编码器和解码器之间添加了一个存储模块。编码器用于构建输入的表示,该表示用于查询最相关的存储项目,该存储项目用于解码器的重建。在训练阶段,记忆项目被更新以“记忆”正常数据的原型特征。当运行推理时,存储项目被冻结,并且由于存储模块仅识别正常数据,当异常实体出现在输入中时,它将挑选次优特征。异常帧的重构误差将比正常帧的重构误差高得多,这反过来用于异常检测。
Park 等人,异常检测的记忆引导常态(MNAD)

学习记忆引导的异常检测常态。我们将这个网络称为 MNAD。蓝色+红色特征块将被称为更新特征。注意红色的钥匙上有一个相应的(类似的)蓝色记忆物品。图片由朴等人提供。
继 MemAE 之后,Park 等人引入了类似的解决方案来处理 CNN 自动编码器的表示能力。他们建议使用编码器表示和存储项目作为解码器的输入来进行重建。他们将编码器表示(HxWxC)拆分为 HxW 键(1x1xC),而不是专门使用内存项目进行重建。这些键用于在训练阶段更新记忆项目和获取相关记忆项目。一旦存储器项目被聚集,它们就被堆叠到编码器表示,并被输入到解码器,用于输入的重建。

颜色意味着相似。编码器按键上堆叠有类似的存储项目。当输入是正常帧时,这将很好地耦合,但是密钥、存储器对对于异常帧将是次优的。
其思想是通过构建 2C 宽的表示(如图所示的 updated_features)将内存项用作某种噪声,其中编码器键上堆叠有类似的内存项。如果该帧是异常的,则存储项目不会像正常帧那样紧密匹配。
这些作品的核心区别是什么?
这两个作品的主要区别在于 MemAE 试图最佳地记忆项目,使得存储器模块可以单独地构造具有足够信息的表示,以帮助解码器重构输入;而 MNAD 试图将存储项目用作某种噪声过滤器。updated_features 将是编码器特征和相关存储器项目的串联。在异常输入的情况下,存储器项将仍然对应于正常场景特征,而编码器特征将是异常帧的特征,这在理论上应该增加重建误差。
MemAE 还建议使用 3D Convs 来处理视频中的时间信息,而 MNAD 则通过作为输入批次(4-5 个输入帧)提供的运动线索来处理时间信息。
MNAD 引入了一个测试时间内存更新方案,该方案允许在所有推理时间内对内存模块进行微调。
这些变化给 MNAD 带来了什么?
因为 MNAD 使用编码器表示和存储器模块来构建 updated_features,所以存储器模块没有必要具有大量的存储器项目(MemAE 为 2000 个,MNAD 为 10 个),并且因为存储器模块的大小很小,所以该模块必须具有在查询中很好分布的不同特征。MNAD 引入了特征分离性损失和紧凑性损失,这激励了用不同的存储项目来填充存储模块。
特征分离性损失促使模型减小每个特征与其最近特征之间的距离,同时增大该特征与第二最近特征之间的距离。这看起来就像三重损失。
三重损失是用于机器学习算法的损失函数,其中基线(锚)输入与正(真)输入和负(假)输入进行比较。从基线(锚)输入到正(真)输入的距离最小,从基线(锚)输入到负(假)输入的距离最大。⁹
紧凑性损失激励模型减少编码器构建的表示的类内变化。
总之,这两种损失有助于增加存储在存储模块中的存储项目的多样性,并增加存储模块的辨别能力。

t-SNE 图描绘了具有分离性损失、没有分离性损失和具有我们提出的对力分布的监督的特征(将在本文后面讨论)。
哪个好?
MNAD 始终优于大多数竞争对手,作者主要将性能归功于其内存模块的实现方式和测试时间内存更新方案。
这些结论具体而准确吗?
我们为 RC2020 challenge⁴重新实施了 MNAD,并写了一份关于其可重复性的 report⁵。
这项活动的主要目标是鼓励发表和分享可靠的和可复制的科学成果。为了支持这一点,本次挑战赛的目标是通过邀请社区成员选择一篇论文来调查顶级会议上发表的论文的可重复性,并通过复制计算实验(通过新的实施方案或使用作者提供的代码/数据或其他信息)来验证论文中的实证结果和主张。⁴
让我们更深入地挖掘一下这个理论在多大程度上符合预期。
这些任务被低估了吗?
MNAD 使用两个代理任务来测试他们的模型。
- 重建任务。
- 预测任务。
对于重建任务,模型被馈送 1 幅图像,并且模型的任务是重建该帧。
对于预测任务,该模型被馈送一批 5 个图像(用于时间信息),并且该模型的任务是预测第 6 帧。
这两个任务的模型架构非常类似于 U-Net。主要区别在于,跳过连接需要移除以用于重建任务。这是因为其中一个输入要素是目标要素,通过跳过连接,模型将基本学会将输入复制到输出。
这引发了两个问题。
- 预测任务允许在输入要素中嵌入时态信息,那么该任务是否在一定程度上提高了性能?
- 预测任务还允许模型跳过连接;跳过连接如何影响模型的行为?
从经验上来说,我们表明跳过连接会带来一些性能提升。为了绝对确定是这种情况,我们必须在重建任务中向模型引入 skip 连接。为了实现这一点,我们在输入中添加了不同概率(5%、25%、40%)的椒盐噪声,并让模型对输入进行降噪。由于输入要素不再包含精确的目标要素,因此模型无法通过跳过连接来复制输入。我们看到了一个原始的性能增益 ~4% ,但是它没有超过预测任务的基准。值得注意的是,预测任务确实将时间信息嵌入到输入特征中,这意味着在预测任务上训练的模型将能够对时间和时空异常进行分类,而在重建任务上训练的模型将完全错过时间异常。
因此,根据经验,这两个问题的答案是是的。预测任务允许嵌入时间信息和引入跳过连接,这两者都显示出改进的性能。
这个任务到底是如何影响模型行为的?
重建任务没有任何时间信息,因为它只接受一个输入并重建一个输出,因此只能识别空间异常。另一方面,预测任务允许将时间信息嵌入到输入特征中,这有助于模型识别空间、时间和时空异常。
为了显示任务如何影响模型的性能,我们综合生成了一个具有空间、时间和时空异常的数据集。正常情况包括半径为 10 个像素的圆以每帧 5 个像素的速度移动。空间异常包括以相同速度移动的正方形,而时间异常包括以每帧 10 个像素的速度移动的圆形。时空异常是以每帧 10 个像素移动的方块。
下图显示了在预测任务中训练的模型中,输出假象是如何更加突出的。

空间异常:数据以前从未有过正方形,两个模型都将正方形重建为填充正常数据的圆。

时间异常:圆圈移动得更快,这影响了预测任务的输入批次(由于输入中没有时间信息,因此重建时缺少该信息),只有在预测任务中训练的模型显示出伪影。

时空异常:输入中有移动速度比普通圆形更快的正方形。两种模型都将正方形转换成圆形,但是在预测任务上训练的模型也引入了时间假象,这使得分类任务更容易。
这些是唯一可能被低估的因素吗?
MNAD 在 UCSD Ped2⁶、中大 Avenue⁷和 Shanghaitech⁸数据集上进行了基准测试。上海理工大学的数据集比 UCSD 的 Ped2 数据集和 CUHK 大道要复杂得多。当在 Shanghaitech 数据集上重现 MNAD 的结果时,我们注意到在一个使用内存项的试探法中有很多 NaN 值。深入研究后,我们发现内存模块也没有按预期工作。这些特征只与一小部分记忆项目相关。这种偏斜的分布本质上会使内存模块的引入变得完全无用。对于 Shanghaitech 数据集,我们发现所有的特征都只与一个记忆项目相关。这意味着对于异常或正常帧,密钥、存储项目对将是相同的。

记忆张量将总是被映射到这个键,使得记忆模块完全过时。
我们引入了额外的监督,以强制统一分布,这有助于击败报告的分数。这不是一个理想的解决方案,因为我们正在强制一个可能不是最优的特定分布,但是这个黑客确实让管道按照预期工作。

强制统一内存分配。
其他观察结果
我们还观察到,skip 连接在解码输出时涉及的内容非常多,以至于内存模块和中间表示一点也不重要。我们用 0、1 和随机张量代替中间表示,输出在光学上没有变化。这让我们看到了重建和预测任务中测试时间记忆更新的性能差异。预测任务没有受到太大影响,但重建任务将受到巨大打击,这是由于没有跳跃连接来完成繁重的工作。
结论
我们的实验表明,内存模块的添加提高了 UCSD Ped2 和 CUHK 大道等简单数据集的性能。对于像上海理工大学数据集这样更复杂的数据集来说,结论还没有出来。与没有内存条的型号相比,内存条的添加降低了性能。我们发现,在如此复杂的数据集上进行训练,将导致记忆模块被搁置一旁,变得完全过时。我们提出的解决这一问题的额外监督并不理想,但确实让我们将模型评估为比 Park 等人报告的分数略高的分数。对于 ShanghaiTech 数据集,最大的问题仍然是在重建任务上训练的模型的分数略高于预测任务的分数。鉴于重建任务完全忽略了时间异常,这导致了对该数据集的存储模块的功效的质疑。有可能的是,即使在建议的监督之后,该模型也发现更容易严重依赖于跳跃连接,并试图找到表面特征来帮助解码这些特征。也有可能数据集对于模型来说噪音太大。
最后,我们可以通过这些拟议的修订达到报告的分数,但我们的结论是,为上海科技这样的复杂数据集建立稳定的管道仍有工作要做。我希望这篇文章对用于异常检测的内存模块有一个像样的介绍!
参考
[1]马克·克莱默(1991)。“使用自联想神经网络的非线性主成分分析” (PDF)。爱车日报。37(2):233–243。doi:10.1002/AIC . 690370209。
https://arxiv.org/pdf/1904.02639.pdf
[3]https://arxiv.org/pdf/2003.13228.pdf
https://paperswithcode.com/rc2020
https://arxiv.org/ftp/arxiv/papers/2101/2101.12382.pdf
6李维新,维杰·马哈德万,和努诺·赛洛斯。拥挤场景中的异常检测和定位。IEEE TPAMI,2013 年。
7陆,,和贾亚嘉。MATLAB 中 150 FPS 的异常事件检测。2013 年在 ICCV。
[8]罗伟新,,高胜华.堆叠 RNN 框架下基于稀疏编码的异常检测的再探讨。2017 年在 ICCV。
[9]https://en.wikipedia.org/wiki/Triplet_loss
【https://github.com/alchemi5t/MNADrc
利用 MIRNet 增强微光图像
在弱光下拍摄的照明图像

图像增强(来源:https://github . com/sou mik 12345/MIRNet/blob/master/assets/lol _ results . gif)
在我们的一生中,我们都点击过很多图片,但我特别面临的一个问题是,我相信你们都一定经历过在昏暗的光线下点击图片。如果你没有夜视功能或非常高端的相机,图像可能会非常模糊或光线很弱,这使得图像中的内容几乎看不见。
现在,如果你没有夜视相机,而你又喜欢拍照,那么这篇文章就是为你准备的,因为在这篇文章中,我将向你展示如何增强你所有的弱光图像,使它们清晰可见。所以让我们开始吧。
在本文中,我们将使用 MIRNet 模型进行图像增强。它用于增强低光图像,这是一个预先训练好的模型,可以很容易地从 Github repo 下载。对于这篇文章,我使用的是 Saumik Rakshit 的 GitHub repo。
克隆 Github 存储库
对于本文,我们将使用 Google Collab。对于初始设置,我们需要打开一个 collab 笔记本,并使用以下命令克隆所需的 Github 存储库:
!git clone [https://github.com/soumik12345/MIRNet](https://github.com/soumik12345/MIRNet)
#Setting up current directory
%cd MIRNet
安装 Wandb
Wandb 用于组织和分析机器学习实验。它与框架无关,比 TensorBoard 更轻。每当我们使用 wandb 运行一个脚本时,超参数和输出度量都会被保存下来,可以进一步可视化。我们将通过运行下面给出的命令来安装它。
!pip install -q wandb
导入所需的库
下一个重要的步骤是导入这个项目所需的所有库。
from glob import glob #
from PIL import Image #For image operations
from matplotlib import pyplot as plt #for visualization
# Importing required dependencies from MIRNet
from mirnet.inference import Inferer
from mirnet.utils import download_dataset, plot_resultimport tensorflow as tf # For modelling
import numpy as np # For mathematical computation
下载数据集和预先训练的权重
在这一步中,我们将下载模型和预定义权重的数据。这将为我们的图像增强项目创建具有所需预训练权重的模型。通过运行下面给出的命令,我们将完成这一步。
inferer = Inferer()
inferer.download_weights('1sUlRD5MTRKKGxtqyYDpTv7T3jOW6aVAL')
inferer.build_model(num_rrg=3, num_mrb=2, channels=64, weights_path='low_light_weights_best.h5')inferer.model.summary()

预训练模型(来源:作者)
接下来,我们将权重加载到模型中并保存模型。下面给出的命令将解决这个问题。
inferer.model.save('mirnet')
现在,我们需要将一张图片加载到 google collab 中,以便我们可以使用它进行增强。
IMAGE_LOC = '/content/123.JPG' # change the name of image to you desired image
最后一步
这是最后一步,我们将把输入图像传递给模型,模型将生成增强的输出图像。下面给出的代码适用于这种情况。
original_image, output_image = inferer.infer(IMAGE_LOC)
plot_result(original_image, output_image)

输入图像和增强图像(来源:作者)
在这里,你可以清楚地看到我们的模型是如何将一个低亮度的图像增强为清晰可见的图像的。
继续尝试不同的图片,如果你发现任何困难,你可以在回复部分张贴出来。
这篇文章是与皮尤什·英加尔合作的
在你走之前
感谢 的阅读!如果你想与我取得联系,请随时在 hmix13@gmail.com 上联系我或我的 LinkedIn 个人资料 。可以查看我的Github*简介针对不同的数据科学项目和包教程。还有,随意探索* 我的简介 ,阅读我写过的与数据科学相关的不同文章。
PyTorch 中的混合增强神经网络
随机混合图像,效果更好?
随着深度学习的指数级改进,图像分类已经成为蓬勃发展的领域之一。传统的图像识别任务严重依赖于处理方法,例如扩展/腐蚀、核和频域变换,然而特征提取的困难最终限制了通过这些方法取得的进展。另一方面,神经网络专注于寻找输入图像和输出标签之间的关系,以为此目的“调整”架构。虽然准确性的提高非常显著,但网络通常需要大量数据来进行训练,因此许多研究现在都专注于执行数据扩充,即从预先存在的数据集增加数据量的过程。
本文介绍了一个简单却惊人有效的增强策略——mixup,通过 PyTorch 实现并比较结果。
在混淆之前——为什么要增加数据?
基于一组给定的训练数据来训练和更新神经网络架构内部的参数。然而,由于训练数据仅覆盖可能数据的整个分布的某一部分,网络可能在分布的“可见”部分上过度拟合。因此,我们用于训练的数据越多,理论上就能更好地描述整个分布。
虽然我们拥有的数据数量有限,但我们总是可以尝试稍微改变图像,并将它们用作“新”样本,输入网络进行训练。这个过程被称为数据扩充。
什么是 Mixup?

图一。图像混合的简单可视化。
假设我们正在对狗和猫的图像进行分类,给我们一组带有标签的图像(即,【1,0】->狗,【0,1】-->猫),混合过程就是简单地平均出两幅图像及其相应的标签作为新数据。
具体来说,我们可以用数学方法写出 mixup 的概念:

其中 x,y 是 xᵢ (标号 yᵢ )和 xⱼ (标号 y ⱼ )的混合图像和标号,λ是来自给定β分布的随机数。
这提供了不同类别之间的连续数据样本,直观地扩展了给定训练集的分布,从而使网络在测试阶段更加健壮。
在任何网络上使用 mixup
由于 mixup 仅仅是一种数据扩充方法,它与任何分类网络体系结构都是正交的,这意味着您可以在具有任何网络的数据集中实现这一点,以解决分类问题。
基于原论文mixup:Beyond experimental Risk Minimization,张等人对多个数据集和架构进行了实验,经验表明 mix up 的好处不仅仅是一次性的特例。
计算环境
图书馆
整个程序是通过 PyTorch 库(包括 torchvision)构建的。混音的概念需要从 beta 分布中生成样本,这可以从 NumPy 库中获得,我们还使用随机库来查找混音的随机图像。以下代码导入所有库:
资料组
为了演示,我们在传统的图像分类上应用了混合的概念,CIFAR-10 似乎是最可行的选择。CIFAR-10 包含 10 个类别的 60000 个彩色图像(每个类别 6000 个),以 5:1 的比例分成训练集和测试集。这些图像分类起来相当简单,但比最基本的数字识别数据集 MNIST 还要难。
有许多方法可以下载 CIFAR-10 数据集,包括从多伦多大学网站或使用 torchvision 数据集。值得一提的一个特定平台是 Graviti 开放数据集 平台,它包含数百个数据集及其对应的作者,以及每个数据集的指定训练任务(即分类、对象检测)的标签。您可以下载其他分类数据集,如 CompCars 或 SVHN,来测试 mixup 在不同场景中带来的改进。该公司目前正在开发他们的 SDK,尽管目前直接加载数据需要额外的时间,但在不久的将来会非常有用,因为他们正在快速改进批量下载。
硬件要求
最好在 GPU 上训练神经网络,因为它可以显著提高训练速度。但是,如果只有 CPU 可用,您仍然可以测试程序。要让您的程序自己决定硬件,只需使用以下代码:
履行
网络
目标是看到混合的结果,而不是网络本身。因此,为了演示的目的,实现了一个简单的 4 层卷积神经网络(CNN ),后面是 2 层全连接层。注意,对于混合和非混合训练过程,应用相同的网络来确保比较的公平性。
我们可以构建如下的简单网络:
混合
混合阶段是在数据集加载过程中完成的。因此,我们必须编写自己的数据集,而不是使用 torchvision.datasets 提供的默认数据集。
以下是通过合并 NumPy 的 beta 分布函数实现的简单混合:
请注意,我们并没有对所有的图像进行混音,而是对大约五分之一的图像进行混音。我们还使用了 0.2 的贝塔分布。您可以更改不同实验的混合图像的分布和数量。也许你会取得更好的成绩!
培训和评估
下面的代码显示了培训过程。我们将批量大小设置为 128,学习速率设置为 1e-3,总的历元数设置为 30。整个训练进行了两次——有和没有混淆。损失也必须由我们自己定义,因为目前 BCE 损失不允许带小数的标签:
为了评估混淆的影响,我们基于有和没有混淆的三次试验来计算最终精度。在没有混淆的情况下,网络在测试集上产生了大约 74.5%的准确率,而在有了混淆的的上,准确率提升到了大约 76.5% !
超越图像分类
虽然 mixup 推动了图像分类的最新精度,但研究表明,它的好处扩展到了其他计算机视觉任务,如生成和对对立示例的鲁棒性。研究文献也已经将该概念扩展到 3D 表示中,这也被证明是非常有效的(例如, 【点混合 )。
结论
所以你有它!希望这篇文章给你一个基本的概述和指导,告诉你如何将 mixup 应用到你的图像分类网络训练中。完整的实现可以在下面的 Github 资源库中找到:
https://github.com/ttchengab/mixup.git
感谢您坚持到现在🙏! 我会在计算机视觉/深度学习的不同领域发布更多内容。一定要看看我关于 VAE 的其他文章,一次学习,等等!
使用绘图填充增强测井曲线的可视化
使用 matplotlib 和 fill_betweenx() 将颜色填充应用于测井数据

使用 fill_betweenx 记录绘图阴影。[图片由作者创建]
Matplotlib 是 Python 中一个很棒的库,我总是一次又一次地使用它来处理测井记录。由于其高度的灵活性,开始使用它可能会很棘手,但是一旦您掌握了基础知识,它就可以成为数据可视化的强大工具。
处理测井数据时,通常会对数据应用颜色填充,以帮助快速识别感兴趣的区域。例如,识别岩性或含烃层段。当我在网上搜索实现颜色填充的方法时,大部分时间,文章都指向在一条线和图上的 x 轴之间填充。显示如何将阴影应用于测井曲线的结果明显较少,测井曲线的最长轴通常沿着 y 轴,或者通常沿着 y 轴。
这篇文章是我的 Python &岩石物理学系列的一部分。详情可以在这里找到。
在本文中,我将通过四个不同的例子来说明如何使用简单的填充来增强测井数据的外观。其中包括:
- 从曲线到图/轨迹边缘的简单颜色填充
- 从曲线到图/轨迹两边的颜色填充
- 从曲线到图/轨迹边缘的可变填充
- 两条曲线(密度和中子孔隙度)之间的填充,当它们相交时会发生变化
对于下面的例子,你可以在我的 GitHub 知识库中找到我的 Jupyter 笔记本和数据集,链接如下。
https://github.com/andymcdgeo/Petrophysics-Python-Series
我的 YouTube 频道上的以下视频演示了如何使用 fill_betweenx()应用一些填充。
使用 matplotlib 应用填充
设置库和加载数据
首先,在开始处理实际数据之前,我们将导入一些公共库。在本文中,我们将使用 pandas 、 matplotlib 和 numpy 。这三个库允许我们加载、处理和可视化我们的数据。此外,通常用于存储和传输数据的数据是。las 文件。为此,我们将使用优秀的 lasio 库来加载这些数据。你可以在我之前的文章中找到更多关于这个的信息。
导入 pandas,matplotlib,lasio 和 numpy 库。
导入和查看 LAS 数据
我们正在使用的数据集来自于 2018 年发布的公开发布的 Equinor Volve 场数据集。本教程中使用的文件来自 15/9- 19A 井,其中包含一组很好的测井数据。
要开始加载我们的 las 文件,我们可以使用 lasio 中的以下方法:
使用 lasio 库在 python 中加载 las 文件。
为了使绘图更容易,我们还将把 las 文件转换成 pandas 数据帧,并创建一个包含深度曲线的新列,该列基于数据帧索引。
然后,我们可以通过调用数据帧的.describe()方法来找出数据集中包含的内容,如下所示。
返回关于数据帧内容的统计信息。
这将返回一个简单但非常有用的汇总表,详细列出所有曲线的统计数据。

由于我们已经将深度曲线作为一列添加到数据帧中,我们可以很容易地获得数据的最小值和最大值。请注意,这可能不一定是我在上一篇文章中使用 Matplotlib 可视化油井数据覆盖中看到的所有曲线的全部范围。
用简单的填充绘制我们的数据
现在我们已经加载了数据,并且确认了我们已经得到了想要的曲线,我们可以开始绘制数据了。在这篇文章中,我将使用.plot()方法直接从数据帧中绘图。
在下面的代码中,您会看到我指定了许多参数:
- x 和 y 轴
c指定线条的颜色lw指定线条宽度legend用于打开或关闭图例。适用于多条曲线/直线figsize以英寸为单位指定图形的尺寸
代码的其余部分允许设置轴极限(ylim和xlim)。请注意,当我们在 y 轴上绘制深度时,我们必须翻转数字,以便最深的深度是第一个数字,最浅的深度是第二个数字。
当我们执行代码时,我们得到了我们的绘图:

使用 matplotlib 绘制的伽马射线数据。
我们可以通过添加一个从图的左边缘延伸到曲线值的简单填充来进一步增强这个图。这是通过使用.fill_betweenx()实现的。
要使用这个函数,我们需要传递 y 值(深度),被着色的曲线(GR)和我们从 GR 曲线着色的值(0)。然后,我们可以通过使用facecolor参数轻松指定填充的颜色。
使用 fill_betweenx 在曲线和 y 轴之间添加颜色。
当我们运行这段代码时,我们得到了一个稍微好看一点的图形:

y 轴带有颜色填充的伽马射线数据。
我们可以更进一步,通过复制线条并将“阴影”的值与颜色一起更改为“阴影”,以相反的方式绘制阴影:
使用 fill_betweenx 在曲线的左侧和右侧添加额外的阴影。

伽马射线图左侧为绿色阴影,右侧为黄色阴影,便于识别清洁层段和泥质层段。
我们的情节立刻变得更好。我们可以很快看出哪里有更清晰的音程,哪里有更清晰的音程。
用可变填充绘制数据
通过在伽马射线曲线和 y 轴之间应用可变填充,我们可以将我们的图带到下一个级别。您会注意到,与上面的代码相比,下面的代码扩展了很多。
我们首先要确定我们将把阴影分成多少种颜色。这是通过将我们的 x 轴值赋给一个变量并使用span = abs(left_col_value — right_col_value)计算出它们之间的绝对差值来实现的。这给了我们一系列的价值观。
然后,我们使用cmap= plt.get_cmap('nipy_spectral')从大量的彩色地图中抓取我们选择的彩色地图。完整的颜色列表可以在这里找到。对于这个例子,我选择了 nipy_spectral。
下一段代码看起来与上面类似,除了 x 限制现在由变量left_col_value控制。还有right_col_value。这允许我们只在一个地方而不是在多个地方改变极限值。
最后一部分是 for 循环,它遍历在第 14 行创建的数组中的每个颜色索引值,并从颜色图中获取一种颜色。然后(第 26 行)我们使用 fill_betweenx 方法来应用该颜色。注意,我们现在在参数中使用了where = curve >= index。这允许我们在曲线值大于或等于索引值时使用合适的颜色。
运行代码后,我们会生成以下图形:

使用 fill_betweenx 使用可变渐变填充的伽马射线图。
乍一看,我们的绘图要好得多,并允许根据显示的颜色轻松识别相似值的区域。
在两条不同比例的曲线之间应用明暗处理
最后一个例子说明了如何将岩性阴影应用于密度和中子孔隙度曲线。这两条曲线通常根据交叉进行着色。当密度移到中子孔隙度的左边时,我们可能有一个多孔储集岩。当交叉以相反的方式发生时,密度在中子孔隙度的右边,我们可能有页岩。
请注意,这是非常简化的,这两条曲线相互交叉有许多不同的原因。
显示密度和中子孔隙度数据的第一步是将它们添加到图中。在这种情况下,我们必须创建一个图形并添加多个轴,而不是使用df.plot()。为了让我们画出中子孔隙度,我们必须把它作为一个额外的轴加进去。使用ax1.twiny()我们可以共享两条曲线之间的深度曲线。

matplotlib 图上的密度和中子孔隙度。
我们现在可以添加阴影。这比预期的要复杂一点,因为两条曲线的比例不同。通常,中子孔隙度从 45 到-15 孔隙度单位(p.u)(十进制为 0.45 到-0.15),密度从 1.95 到 2.95 g/cc。
我们必须添加额外的代码,将一条曲线缩放到另一条曲线的单位比例。第 21 行和第 30 行之间的代码部分是从下面的 stackoverflow 帖子中获得的:这里的和这里的。
当我们运行这段代码时,我们得到了下面的图。

以不同比例测量的中子孔隙度和密度数据的可变颜色填充。
这使我们能够很容易地识别潜在储油层的位置。为了确认这些部分是什么,需要进行进一步的分析。在进行解释时,您应该始终查看其他测井曲线,以帮助您理解和解释。
摘要
总之,在处理测井数据时,matplotlib 是一个强大的数据可视化工具。我们可以轻松地在轨迹上显示我们的日志,并在线条之间进行填充,以帮助可视化和解释我们的数据。在这篇文章中,我介绍了如何在曲线和绘图边缘之间应用固定颜色填充和可变渐变填充,以及如何在不同比例的两条曲线之间进行填充。
“作者创作的所有图片”
感谢阅读!
如果你觉得这篇文章很有用,可以看看我的其他文章,看看 Python 和测井数据的各个方面。你也可以在GitHub找到我和其他人在本文中使用的代码。
如果你想联系我,你可以在LinkedIn*或者在我的* 网站 找到我。
有兴趣了解更多关于 python 和测井数据或岩石物理学的知识吗?跟我上 中 。
增强您的分析可读性——熊猫教程
改善数据框/图表格式的 3 个技巧

马库斯·斯皮斯克在 Unsplash 上的照片
你的日常生活数据分析
作为一名数据科学家/分析师,您的工作是生成一份包含许多商业决策见解的报告。报告可以通过一些有用的工具制作,如 Microsoft Excel、SAP,也可以用编程语言定制,如 SAS、R 或 Python。结果可以通过内部电子邮件发送给利益相关方,或者通过集中的仪表板发布。
和其他人一样,我是一名数据分析师,在日常生活中使用 python 制作报告或演示文稿。我通常的任务是在 2-3 小时内做一个特别的分析,然后提交给管理团队。
为了得到我想要的结果,我必须启动我的 Jupiter 笔记本内核,并快速编写代码来产生数字。在那之后,我可能会把结果放在微软的 PowerPoint 上,加上一些基本的脚注,然后把它们发给我的主管,让他在一天结束之前作出一个重要的决定。
一个棘手的问题是,由于时间限制,我必须消化信息,编写代码以产生结果,并将其放入 Microsoft PowerPoint 中,以一种漂亮的格式呈现出来。
不幸的是,我使用的编程语言可能不包含使您的报告对管理团队来说更好看的功能,例如,在数字中使用逗号,或者不使用科学符号来显示高数字。
如果你提交的报告没有考虑到这些方面,管理团队可能会对你的报告抱怨很多,有时,他们会看都不看就把它扔进垃圾桶。那会让你很恼火,因为你在这上面花费了时间和精力。

丹尼尔·帕斯夸在 Unsplash 上拍摄的照片
要解决这个问题,您可以将编程语言的结果放到 Microsoft Excel 中,并根据需要手动更改格式。Excel 是一个很好的工具。糟糕的是你必须手动完成。如果我们能在编程过程中实现自动化呢?那就太好了,不是吗?
原样
让我们看看我为这个例子制作的数据框。这是公司需要的收入额。如您所见,这是从pandas数据帧返回的默认结果。没有任何配置,这就是你得到的。

作者图片
我总是从我的主管或首席执行官那里得到一个评价。
你能让更具可读性和更容易比较吗?”
解决方法可能是将该数字除以一百万,并将单位放在表格的上方。你必须记住的一件事是,它应该在你的演讲中保持一致。如果有 100 张表需要复制呢?很艰难,对吧。
我发现您可以通过编程来修复它。我花了很多时间从网上收集了以下代码片段。非常感谢堆栈溢出!我认为与你们分享它可以让任何发现我的这些问题的人受益。你应该减少花在修饰性评论上的时间,然后把注意力集中在内容的有效性上。
如何改善?你可能会问。
人类可读格式
我收到的最多的评论是,你能把数字四舍五入,并在末尾加上符号,比如 M 代表百万,K 代表千吗?这样会让你的表格更好看,减少读者眼中不必要的信息。很多时候,我们不需要这么精确来决定去哪里。
这是将您的pandas数据框中的数字转换成您想要的格式的函数。
def human_readable_format(value, pos=None): '''
Convert number in dataframe to human readable format
`pos` argument is to used with the matplotlib ticker formatter.
''' assign_unit = 0
units = ['', 'K', 'M', 'B']
while value >= 1_000:
value /= 1_000
assign_unit += 1 return f"{value:.2f} {units[assign_unit]}"

作者图片
Tada!这是你将得到的结果。读起来容易多了,对吧?
这个函数的缺点是它将你的数字转换成一个字符串,这意味着你将失去从一个数据帧中排序的能力。这个问题可以通过先对您想要的值进行排序,然后再应用它们来解决。
您可以将结果保存为 excel 或 CSV 文件,并放入 PowerPoint 中。我的方法通常是截图并直接放入演示中。
这段代码节省了我大量复制多个表的时间,因为当您从主管那里得到评论时,您必须刷新所有的评论。假设演示中有 100 张表。对于手动逐表制作的人来说简直是噩梦。
同样,格式化后,我们也可以在matplotlib图中使用它。我认为如果你使用pandas库进行数据分析,那么matplotlib将是你绘制图表的首选。

作者图片
您可以使用人类可读的格式(如您的表格)来设置此图表的 y 轴,方法是
import matplotlib.ticker as mticker
import matplotlib.pyplot as pltfig , ax = plt.subplots();
df['value_9'].plot(ax=ax);
ax.yaxis.set_major_formatter(
mticker.FuncFormatter(human_readable_format)
)
看起来更有说服力。

作者图片
突出显示单元格
有时你需要指出表外的重要数字、趋势或信息。您心中有一个逻辑规则,比如突出显示收款金额最大值的月份。该数量可以根据数据中的基础事务而变化。如果您想动态突出显示它,您必须通过编程来实现。
这是第二件我用得最多的东西,让我的桌子看起来更好。它帮助你传达信息,提高你讲故事的能力。强调其余部分中重要的部分。
def highlight_max_value(series): # get True, or False status of each value in series
boolean_mask = series == series.max() # return color is orange when the boolean mask is True
res = [f"color : orange" if max_val else '' for max_val in boolean_mask] return resdf.style.apply(highlight_max_value)

作者图片
有时,您会发现数据中的潜在趋势更容易理解。如果不进行适当的重新排列,你无法从一大堆数据中发现模式。
少即是多
最后一个不是给你的数据框/图添加有趣的东西,而是把它去掉。有时候少即是多。数据框或图中的组件越少,传达的信息就越好。读者或接受者只能吸收他们必须吸收的东西。

作者图片
你可以在这里改很多东西,然后就会是这个样子。
# Prepare data setrevenue = df[['value_9']].copy()
revenue['pct'] = revenue['value_9'] * 100 / revenue['value_9'].sum()
revenue = revenue.sort_values('pct', ascending=False).reset_index(drop=True)
revenue['cumsum_pct'] = revenue['pct'].cumsum()import matplotlib.ticker as mticker
import matplotlib.pyplot as plt
import seaborn as sns# enlarge font size for the graphsns.set_context('talk')# plot the bar chart to show the revenue amountfig , ax = plt.subplots(figsize=(9,6));
revenue['value_9'].plot.bar(ax=ax);
ax.yaxis.set_major_formatter(mticker.FuncFormatter(human_readable_format))
plt.title('Revenue generated');# plot cumulative revenue in percentage
# to show the impact of first 3 customersax2 = plt.twinx(ax)
revenue['cumsum_pct'].plot(ax=ax2, color='orange');
ax2.yaxis.set_major_formatter(mticker.PercentFormatter())
sns.despine();

作者图片
通过整理数据并添加一些信息,可以使用更直观的图表进行决策。例如,我们知道只有前 3 个客户占我们收入的 80 %以上。所以让他们保持良好的关系比什么都重要。
总结
在一个新的时代,数据分析员使用编程语言得出一份报告或演示。它减少了手动任务的大量时间,但如上所述,还有更复杂的事情要处理。这是一种权衡。
我想我今天与你们分享的技巧和诀窍在某种程度上会有所帮助。我花时间寻找代码片段并适应我的工作。如果它被整合在一个地方,我可以随时回头看,这将是方便的。另外,你们也可以看看这篇文章!
本文所有代码都可以在这里找到!
帕泰鲁什·西达
如果你喜欢这篇文章,并希望看到更多这样的东西。
用这些建议充实你的 Jupyter 笔记本

技术工具包
增强工作流程文档的实用技巧
Jupyter Notebook(从这里开始的笔记本)的一个漂亮的特性是能够在代码单元格旁边使用降价单元格。这些降价单元格使我们能够更清晰地表达文档,以便将来的用户更容易理解笔记本的工作流程。在这篇文章中,我分享了一些技巧来丰富 markdown 单元格中的文档,而不必安装任何扩展。

📝 0.使恢复活力的事物
如果你一直在使用笔记本电脑,你可能已经知道降价的基本知识。如果你需要复习,这里有一个一分钟的关于 Markdown 常用语法的介绍:
#### Headers
# Header 1
## Header 2#### Styles
*Italic*, **bold**, _underscore_, ~~strikethrough~~#### Hyperlink
[hyperlink](https://www.markdownguide.org/)#### Table
| Default | Left-aligned | Center-aligned | Right-aligned |
|---------|:-------------|:--------------:|--------------:|
| Default | Left | Center | Right |#### Others
* Bulleted item 1
* Bulleted subitem 1
* Bulleted item 2***
1\. Ordered item 1
1.1\. Ordered subitem 1
2\. Ordered item 2***
- [ ] Unchecked box
- [x] Checked box
其渲染输出:
更新了基础知识之后,是时候看看 Markdown 单元格更复杂的特性来丰富文档以提高可读性了。
🎨 1.颜色代码文本
大段的黑白文字读起来会令人沮丧。丰富黑白文本并提高文本可读性的一种方法是添加颜色来突出和突出关键部分。这里有三种不同的方法来给文本添加颜色:
🎨 1.1.彩色文本
我们可以使用 html <font>标签来改变文本的颜色。我们可以使用颜色名称或十六进制颜色代码:
Example: <font color=green>green text</font>, <font color=blue>*blue italised text*</font> and <font color=#FF0000>**red bold text**</font>.

如果你想探索更多的颜色名称,这个可能会派上用场。如果颜色名称不能很好地抓住你想要的,你可以探索十六进制颜色来获得更广泛的选择。这里是我最喜欢的探索十六进制颜色的资源。
🎨 1.2.突出显示文本
我们还可以用 html <mark>标签突出显示文本:
In addition, we can also <mark>highlight text</mark>.

现在更容易将注意力吸引到文本中突出显示的部分。
🎨 1.3.使用警报
最后,我们可以使用 bootstrap alert 设置背景和字体颜色的格式,使文本文档更容易阅读:
<div class="alert alert-info">Example text highlighted in blue background.</div>
<div class="alert alert-success">Example text highlighted in green background.</div>
<div class="alert alert-warning">Example text highlighted in yellow background.</div>
<div class="alert alert-danger">Example text highlighted in red background.</div>

这些格式都很好看!添加颜色可以突出重点,让文档不那么枯燥,从而立即提高笔记本文档的可读性。这样就变得更容易略读,快速获得要点。
📍 2.适当设置文本格式
丰富文档的另一种方法是使用合适的更丰富的文本格式。让我们看看三种不同的文本格式:
📍2.1 用 LaTeX 插入数学方程
在笔记本文档中经常需要引用数学方程。有了$,我们可以使用 LaTeX 显示格式良好的数学公式:
$$logloss(\theta) = - {1 \over m} \sum_{i=1}^m (y_i \ln(\hat p(y_i=1)) + (1-y_i) \ln(1-\hat p(y_i=1)))$$

当等式用双$包裹时,它将居中对齐。如果我们使用单个$,它将是左对齐的。或者,我们也可以使用以下语法来编写公式:
\begin{equation} logloss(\theta) = - {1 \over m} \sum_{i=1}^m (y_i \ln(\hat p(y_i=1)) + (1-y_i) \ln(1-\hat p(y_i=1))) \end{equation}
如果你对 LaTeX 不熟悉,可以看看这个指南或者这个来入门。
📍 2.2.使用代码块
有时,在 markdown 单元格中显示代码引用而不是在代码单元格中运行它们是很有用的。我们可以使用单引号```来内联显示代码块:
If you haven't installed textblob, you can install it with: `pip install texblob`.

对于更大的代码块,我们可以使用三重反斜线`````:
If you haven't installed libraries, you can install them with the following command:
conda install pandas, numpy, sklearn -y
pip install textblob

如果我们在第一个三元组```后指定语言名称,代码块将在适用的地方进行彩色格式化:
```python
{"minimum": 10, "maximum": 50, "name": "optimiser"}

## 📍 2.3.使用引号缩进
缩进是设置文本格式以提高可读性的另一种方式。我们可以用`>`添加缩进:
Sample non-indented sentence here.
Note: Indented text.

# 📹 3.添加媒体
> 一幅画胜过千言万语。
文档不一定总是用文字。图像和其他媒体可以帮助我们传达用文字难以表达的思想。添加相关媒体是为未来用户提供必要信息来丰富文档的另一个好方法。
## 📷 3.1.嵌入包含 gif 的图像
我们可以使用 html `<img>`标签向 markdown 单元格添加图像:
```

在这里,example.jpeg与笔记本保存在同一个文件夹中。我们可以使用width或height参数来改变图像的大小。例如,<img src=”example.jpeg” width=500>会将显示的图像调整到所需的宽度,同时保持宽高比。
如果在静态图像中添加图表或其他数据可视化不能很好地捕捉我们想要传达的信息,我们可以嵌入 GIF,一个使用完全相同语法结构的动画图像:
<img src="example.gif"/>

文件的路径也可以是一个 web 链接:
<img src="[https://media.giphy.com/media/XtdLpKOzoxJCzlFY4n/giphy.gif](https://media.giphy.com/media/l46C93LNM33JJ1SMw/giphy.gif)">
📹 3.2.嵌入视频
如果 gif 不够,下一步就是用视频。我们可以使用 html <video>标签来显示一段视频:
<video controls src="example.mp4" width=600/>

例如,屏幕记录如何完成一项任务,将其保存为视频文件,并将视频嵌入笔记本中,这对未来的用户可能很有用。
⚪ ️3.3.添加形状和表情符号
一段很长的纯文本可能会很无聊,读起来没有吸引力。优雅地添加形状和表情符号可以使文本阅读起来更有趣、更有吸引力:
➤ Bullet point one</br>
➤ Bullet point two</br>
➤ Bullet point three

✅ Sample text A</br>
✅ Sample text B</br>
✅ Sample text C

查看这个来探索更多的形状(和表情符号)。这个表情符号备忘单在按名字搜索表情符号时很有用。
瞧啊。这些是我丰富 Jupyter 笔记本文档的技巧。✨:我们可能不会一次使用所有这些功能。但是当合适的机会出现时,知道如何使用它们会让你有所准备。

克里斯·劳顿在 Unsplash 上拍摄的照片
您想要访问更多这样的内容吗?媒体会员可以无限制地访问媒体上的任何文章。如果您使用 我的推荐链接成为会员,您的一部分会费将直接用于支持我。
谢谢你看我的帖子。如果你想了解更多关于减价的信息,请查看本指南。如果你感兴趣,这里有我的一些帖子的链接:
◼️ 用这些技巧整理你的 Jupyter 笔记本
◼️ 有用的 IPython 魔法命令
◼️python 虚拟环境数据科学简介
◼️git 数据科学简介
◼️python 中的简单数据可视化,你会发现有用的
◼️ 6 个简单的技巧,让你在 Seaborn (Python)中的情节更漂亮、更定制
◼️️ 5
再见🏃💨
无需离开笔记本电脑即可丰富您的位置数据
Iggy Python API 简介

地理信息在房地产等行业的定价中扮演着重要角色。住宅周边的特征——它的(不)舒适性、美学和步行性——都会影响它的价值。
假设你是一家房地产公司的新数据科学家。你的第一项任务是改进你所在小组的定价模式。您有一种直觉,即添加模型要素来描述住宅附近可能会导致更准确的价格预测,但您并不确定。你将如何开始你的实验?
如果不花大量时间收集位置数据,不花大量时间阅读投影、路由算法或空间连接方面的内容,如何测试您的假设?
在 Iggy,我们一直在开发工具,使任何公司都可以轻松地将这种类型的邻里情报纳入其分析中。有了我们的 API 和它的新 Python 包装器,在你的项目中试验位置信息就像一个pip install和一些方法调用一样简单。
在围绕我们的房地产场景的几篇文章的第一篇中,我们将快速演示如何使用 Iggy API 通过 Python 中的位置信息来丰富属性数据。只需几个 API 调用,我们就可以快速地向房屋销售数据集中添加描述邻近特征的要素,如必需品的邻近度、树木覆盖度和人口密度。
带上你自己的数据集
我们将在本次演示中使用的数据集是自 2019 年以来佛罗里达州皮内拉斯县的 1000 套独立的单户住宅销售的公开样本。它包括关于销售日期和价格的信息,以及关于房产本身的一些重要信息。
让我们看看我们必须从什么开始。
生成的数据字典如下所示:

这里有一系列的数据来描述每一处房产:它的物理特征(square_footage、floor_system)、它的状况(frontage),甚至一些评估数据(log_land_value)。当然还有销售信息,包括sale_date和log_price。
现在,假设现有的定价模型使用其余的特性作为输入来预测每个房屋的销售价格(log_price)。虽然现有的信息提供了大量的个人住房特征的细节,但有一些重要的因素被遗漏了。具体来说,这些数据并没有告诉我们任何关于每处房产所在社区的信息:去杂货店或咖啡店方便吗?它是在一个绿树成荫的社区吗?周边空气质量怎么样?
我们这里的数据当然是有用的,但我们不需要太多努力就可以做得更好。
Iggy 通过一组 API 调用,利用邻域智能轻松丰富该数据集:
- Iggy 的
/lookupAPI 端点提供关于输入位置本身的信息。这包括空气质量、光污染和树冠等物理信息,以及人口密度和学校集水区。 /points_of_interest端点给出了关于周围社区到便利设施的距离的信息(通过步行、骑自行车或开车的时间)。/amenities_score给出一个分数,代表一个地点周围生活设施质量的多样性(如公园、面包店、餐馆、杂货店、娱乐)。
我们新的 Python 库旨在简化在 Python 脚本中访问 Iggy API 的过程。实例化一个iggyapi对象需要一个 Iggy API 密匙(你可以通过我们的主页上的“获得早期访问”链接来请求):
然后,我们可以使用内置函数调用任何端点。下面是一个调用/lookup端点来找出输入点的空气质量的示例:
Iggy Python 库是为数据科学家构建的
Iggy Python 库通过iggyfeature模块提供了一种基于来自 Iggy API 的信息定义“特性”的方法。由于我们从包含地理坐标的数据框开始,我们可以使用iggyfeature为依赖 Iggy API 调用的数据框定义新列。
例如,如果我们想要构建一个要素来计算到最近的杂货店的距离,我们可以按如下方式进行:
在上面的要素定义中,我们指定该要素源自 Iggy 的/points_of_interest端点(IggyPOIFeature),它专门寻找grocery_stores,并且它将返回到输入点 30 英里内的任何杂货店的最小(calc_method='min')距离。
此外,iggyfeature.IggyFeatureSet类帮助我们在一行代码中组合一系列特性,并用多个特性丰富整个数据框。
因此,让我们创建一些新的 Iggy 特征来添加到我们的数据中。我们将向每一行添加以下内容:
-
从 28 个类别和 3 个品牌中的一个到最近的兴趣点的距离,包括便利设施(如餐馆、面包店和干洗店)和非便利设施(包括废物管理和监狱)。有关当前可用的兴趣点类型的完整列表,请参见我们的文档。
-
15 分钟步行范围内的餐馆、酒吧、咖啡店、面包店、杂货店的数量
-
该位置的光污染、树冠、空气质量和人口密度指数的值
-
步行或开车 10 分钟内,房子的舒适度得分为。宜人性指数是一个 Iggy 指数,用于衡量一个地区生活质量的多样性(如公园、餐馆等)。
使用上面的代码,我们定义了 42 个新的特征或列,可以添加到我们的数据中来描述每个属性的本地邻域。让我们将它们组合成一个IggyFeatureSet,并用它来一次性丰富数据帧:
现在,如果我们再次使用 Featuretools 来检查 Iggy 丰富的数据框中的新要素,我们会看到每个房屋销售的新信息元素,每个元素都提供了关于周围邻居的附加上下文。

那里!我们已经向模型中添加了 42 个新的基于位置的要素,而无需进行任何空间连接,甚至无需查看地图。现在,我们可以自由地试验这些新特性,以快速确定它们是否有助于定价模型。
在接下来的一篇文章中,我们将浏览这些分析,看看哪些特性在我们的房地产定价场景中是最重要的。在那之前…
- 如果你有兴趣了解更多关于 Iggy API 的信息,请查看我们的文档并把和添加到我们的等待列表中以请求你的 API 密钥。
- 我们一直在添加新的数据集!看一看我们的数据路线图,了解即将出现的情况。
- 您希望我们包含哪些基于位置的数据?去我们的Iggy Discord社区告诉我们吧。
我们很期待看到你会用 Iggy 做些什么!
集成分类:简要概述及实例
使用集合分类法将兴趣点分类为机场、火车站和公交车站

查尔斯·德鲁维奥在 Unsplash 上拍摄的照片
注 :本文是关于交通 POI 数据分类系列文章的最后一篇。 第一篇文章 试图使用各种机器学习模型将记录分类为机场、公交车站和火车站。 第二篇文章 的中心思想是使用特征约简算法来调整第一篇文章中的模型,以提供更高的精度。 第三篇 看起来使用了 Spark 多层感知器分类器来正确地分类这些记录。查看这些文章,了解该项目在寻找 POI 数据分类的最佳方法方面是如何发展的。
本文将使用集成分类,使用步行交通模式对来自安全图的 POI 记录进行正确分类。SafeGraph 是一家数据提供商,为数百家企业和类别提供 POI 数据。它免费向学术界提供数据。对于这个项目,我选择使用 SafeGraph 模式数据将记录分类为不同的兴趣点。模式数据的模式可以在这里找到:模式信息
什么是集合分类:
集成学习是多个“弱学习者”一起使用来创建一个机器学习模型的概念,该模型能够比他们每个人单独使用时表现得更好。大多数时候,这些弱学习者自己表现不好,因为他们要么有高偏差,要么有高方差。在集成模型中组合多个弱学习器的要点是减少这种偏差和方差。

兴趣点数据的集成分类
在我们开始总体分类之前,我们必须首先加载数据。这个特殊的步骤在本系列的第一篇文章和第二篇文章中都有涉及。满足本文需求的数据加载和预处理的基本步骤是:
在我们进入集合分类概念的第一步之前,我们必须首先加载我们将用于这个项目的数据:加载数据的过程可以在笔记本中找到,并且已经在系列的第一部分中详细解释。采取的步骤是:
- 删除不必要的列- ['parent_safegraph_place_id ',' placekey ',' safegraph_place_id ',' parent_placekey ',' parent_placekey ',' safegraph_brand_ids ',' brands ',' poi_cbg']
- 创建地面实况列,将每个记录建立为机场、汽车站、机场或未知
- 删除未知记录以清除无法识别的记录
- 使用 pyspark 水平分解 JSON 字符串的列
- 阵列的水平分解列
- 使用 Sklearn LabelEncoder 包转换类列
作为这些转换的结果,输出的数据如下所示,并具有以下各列:

Raw_visit_counts: 在日期范围内,我们的小组中对此兴趣点的访问次数。
Raw_visitor_counts: 在日期范围内,来自我们面板的访问该兴趣点的独立访问者的数量。
Distance_from_home: 访客(我们已确定其住所的访客)离家的中值距离,以米为单位。
中值 _ 停留时间:中值最小停留时间,以分钟为单位。
分时段停留(分解为< 5,5–10,11–20,21–60,61–120,121–240):键是分钟的范围,值是在该持续时间内的访问次数
Popularity_by_day(分解为周一至周日):一周中的某一天到日期范围内每天(当地时间)的访问次数的映射
Popularity_by_hour(分解为 Popularity _ 1-Popularity _ 24):一天中的某个小时到当地时间日期范围内每小时的访问次数的映射。数组中的第一个元素对应于从午夜到凌晨 1 点的时间
Device_type(分解为 ios 和 Android): 使用 Android 和 IOS 的 POI 的访客数量。仅显示至少包含 2 个设备的设备类型,包含少于 5 个设备的任何类别都报告为
既然数据已经准备好了,我们可以开始集成学习方面了。
对于本文的这一部分,我们将使用 Sklearn 投票分类器。这是 Sklearn 包中集成学习的内置模型。然而,在使用这种方法之前,我们需要首先训练我们在这个模型中使用的三个弱学习者。我们使用的模型与本系列文章第一部分中的模型相同,包括高斯朴素贝叶斯模型、决策树模型和 K-最近邻模型。
#Training all three modelsfrom sklearn.neighbors import KNeighborsClassifierfrom sklearn.naive_bayes import GaussianNBfrom sklearn.tree import DecisionTreeClassifierdtree_model = DecisionTreeClassifier(max_depth = 3).fit(X_train, y_train)gnb = GaussianNB().fit(X_train, y_train)knn = KNeighborsClassifier(n_neighbors = 22).fit(X_train, y_train)
既然已经训练了所有三个模型,我们可以使用这些模型来执行该过程的下一个步骤,即调用模型并将创建的模型聚合到字典中以供集合模型使用
from sklearn.ensemble import VotingClassifierestimators=[(‘knn’, knn), (‘gnb’, gnb), (‘dtree_model’, dtree_model)]ensemble = VotingClassifier(estimators, voting=’hard’)
在训练和测试模型时,我们会收到以下输出:
ensemble.fit(X_train, y_train)ensemble.score(X_test, y_test)

from sklearn.metrics import confusion_matrixprediction = ensemble.predict(X_test)confusion_matrix(y_test, prediction)

plot_confusion_matrix(ensemble, X_test, y_test, normalize=’true’, values_format = ‘.3f’, display_labels=[‘Airport’,’Bus’,’Train’])

该模型的精确度略低于用于创建集合分类模型的模型中最有效的模型的精确度(. 75 vs .68)。该模型的性能比高斯朴素贝叶斯模型(. 265)好得多,并且性能与 K-最近邻分类器(. 679)相当。看到模型的表现优于这三个精度的平均值,可以说对这个特定数据集使用集成分类是提高预测精度的有效方法。和以前一样,模型缺陷的主要原因是数据集中的不平衡和缺少公交车站记录。正如我们在 Spark Deep Learning 文章中看到的那样,这个问题并没有通过手动重新平衡数据来解决,因此未来分类工作的最佳行动方针是不惜一切代价避免严重不平衡的数据,否则准确性会严重下降。
结论
通过这个项目,我们能够确定集成分类的重要性以及当使用几个弱学习者来创建模型时该技术可以具有的效率。从这个练习中,我们能够推导出一个比三个弱学习者的平均准确度更好的模型。这是 POI 分类系列文章的结论。通过我们在该系列的最后 4 部分所做的分析,我们可以看到使用机器学习对 SafeGraph 数据进行分类的效率,以及预测的准确性可以相当高。
提问?
我邀请你在 SafeGraph 社区的 #safegraphdata 频道问他们,这是一个面向数据爱好者的免费 Slack 社区。获得支持、共享您的工作或与 GIS 社区中的其他人联系。通过 SafeGraph 社区,学者们可以免费访问美国、英国和加拿大 700 多万家企业的数据。
集成学习:打包和提升
入门
如何组合弱学习者来建立更强的学习者,以减少你的 ML 模型中的偏差和变化

图一。由 Freepik 在 Flaticon 上打包和提升|电子表格、机器人和创意图标
在使用机器学习算法时,偏差和方差的权衡是一个关键问题。幸运的是,机器学习实践者可以利用一些基于集成学习的技术来解决偏差和方差权衡,这些技术是打包和提升。因此,在这篇博客中,我们将解释 bagging 和 boosting 如何工作,它们的组件是什么,以及如何在你的 ML 问题中实现它们,因此这篇博客将分为以下几个部分:
- 什么是装袋?
- 什么是助推?
- AdaBoost
- 梯度增强
什么是装袋?
Bagging 或 Bootstrap Aggregation 由 Leo Breiman 于 1996 年正式提出[ 3 ]。 Bagging 是一种集成学习技术,旨在通过实现一组同构机器学习算法来减少错误学习。 bagging 的关键思想是使用多个基础学习器,这些学习器使用来自训练集的随机样本单独训练,通过投票或平均方法,产生更稳定和准确的模型。
bagging 技术的两个主要组成部分是:带替换的随机抽样(bootstrapping)和一套同构机器学习算法(集成学习)。 bagging 过程很容易理解,首先从训练集中提取出“ n 个子集,然后用这些子集训练同类型的“ n 个基础学习器。为了进行预测,向“ n ”个学习器中的每一个提供测试样本,对每个学习器的输出进行平均(在回归的情况下)或投票(在分类的情况下)。图 2 显示了装袋架构的概述。

图一。打包|作者图片
重要的是要注意到子集的数量以及每个子集的项目数量将由您的 ML 问题的性质决定,对于要使用的 ML 算法的类型也是如此。此外,Leo Breiman 在他的论文中提到,他注意到与回归问题相比,森林分类问题需要更多的子集。
为了实现打包,scikit-learn 提供了一个函数来轻松完成。对于一个基本的执行,我们只需要提供一些参数,例如基本学习器、估计器数量和每个子集的最大样本数量。
代码片段 1。装袋实施
在之前的代码片段中,为众所周知的乳腺癌数据集创建了基于 bagging 的模型。当基础学习器被实现为决策树时,从训练集中随机创建 5 个子集进行替换(以训练 5 个决策树模型)。每个子集的项目数为 50。通过运行它,我们将获得:
*Train score: 0.9583568075117371
Test score: 0.941048951048951*
打包的一个主要优点是它可以并行执行,因为估算器之间没有依赖性。对于小数据集,几个估计器就足够了(比如上面的例子),大数据集可能需要更多的估计器。
很好,到目前为止我们已经看到了什么是装袋以及它是如何工作的。让我们看看助推是什么,它的组成部分以及为什么它与装袋有关,让我们开始吧!
什么是助推?
Boosting 是一种集成学习技术,与 bagging 一样,利用一组基础学习器来提高一个 ML 模型的稳定性和有效性。 boosting 架构背后的思想是生成顺序假设,其中每个假设都试图改进或纠正前一个假设所犯的错误[ 4 ]。 boosting 的中心思想是以顺序方式实现同质 ML 算法,其中每个 ML 算法都试图通过关注前一个 ML 算法产生的错误来提高模型的稳定性。每个基础学习器的错误被认为是通过序列中的下一个基础学习器来改进的方式,是增强*技术的所有变体之间的关键区别。*
升压技术多年来一直在研究和改进,升压的核心思想增加了几个变体,其中最流行的有: AdaBoost (自适应升压)、梯度升压和 XGBoost (极端梯度升压)。如上所述,基于升压的技术之间的关键区别在于惩罚误差的方式(通过修改 权重 或最小化损失函数*)以及数据采样的方式。*
为了更好地理解一些增强技术之间的差异,让我们大致看看 AdaBoost 和渐变增强是如何工作的,这是增强技术的两个最常见的变体,让我们开始吧!
adaboost 算法
AdaBoost 是一种基于 boosting 技术的算法,由 Freund 和 Schapire [ 5 ]于 1995 年提出。 AdaBoost 实现了一个权重向量来惩罚那些被错误推断的样本(通过增加权重)并奖励那些被正确推断的样本(通过减少权重)。更新这个权重向量将生成一个分布,其中更有可能提取那些具有更高权重的样本(即那些被错误推断的样本),这个样本将被引入序列中的下一个基本学习器。这将重复进行,直到满足停止标准。同样,序列中的每个基础学习者将被分配一个权重,表现越好,权重越高,该基础学习者对最终决策的影响越大。最后,为了进行预测,序列中的每个基础学习者将被提供测试数据,每个模型的每个预测将被投票(对于分类情况)或平均(对于回归情况)。在图 3 中,我们观察到了 AdaBoost 操作的描述性架构。

图 3。AdaBoost:描述性建筑|作者图片
Scikit-learn 提供了实现 AdaBoost 技术的函数,让我们看看如何执行一个基本的实现:
代码片段 2。AdaBoost 实现
正如我们所看到的,我们使用的基本学习器是一个决策树(建议它是一个决策树,但是,你可以尝试一些其他的 ML 算法),我们也只为基本学习器序列定义了 5 个估计器(这对我们试图预测的玩具数据集来说足够了),运行它我们将获得以下结果:
*Train score: 0.9694835680751174
Test score: 0.958041958041958*
很好,我们已经大致了解了 AdaBoost 的工作原理,现在让我们看看梯度增强怎么样,以及我们如何实现它。
梯度推进
梯度增强方法不像 AdaBoost 那样实现权重的向量。顾名思义,它实现了对给定损失函数优化的梯度的计算。梯度提升的核心思想是基于以顺序方式最小化每个学习者基础的残差,这种最小化是通过应用于特定损失函数(用于分类或回归)的梯度计算来实现的。然后,添加到序列中的每个基础学习器将最小化由前一个基础学习器确定的残差。这将重复进行,直到误差函数接近零,或者直到完成了指定数量的基础学习器。最后,为了进行预测,每个基本学习器被输入测试数据,其输出被参数化并随后相加以生成最终预测。**

图 4。梯度推进:描述性建筑|作者图片
就像 Bagging 和 AdaBoost 一样, scikit-learn 提供了实现渐变增强的函数,让我们看看如何做一个基本的实现:
梯度推进默认情况下与决策树一起工作,这就是为什么在实现中我们没有定义特定的基础学习器。我们定义序列中的每棵树的最大深度为 2,树的数量为 5,每棵树的学习率为 0.1,运行该函数我们得到:
*Train score: 0.9906103286384976
Test score: 0.965034965034965*
太棒了,这样我们就完成了对打包、助推和部分助推实现的探索,就这样!
结论
在这个博客中,我们已经看到了两种最广泛应用的集成学习技术。
正如我们所见,打包是一种执行随机样本替换来训练“n”基础学习者的技术,这允许模型被并行处理。正是因为这种随机抽样,装袋是一种最大限度地减少方差的技术。另一方面, boosting 是一种顺序构建的技术,其中序列中的每个模型都试图关注前一个基础学习器的错误。虽然增强是一种主要允许减少方差的技术,但它很容易过度拟合模型。
参考
[1]https://quant dare . com/bagging-and-boosting 之间的区别是什么/
[2]https://arxiv.org/pdf/0804.2752.pdf
[3]https://link.springer.com/article/10.1023/A:1018054314350
[4]https://www . cs . Princeton . edu/courses/archive/SPR 07/cos 424/papers/boosting-survey . pdf
[5]https://www . face-rec . org/algorithms/Boosting-Ensemble/decision-theory _ generalization . pdf
机器学习中的集成方法
基于树的集成方法综述

在 Unsplash 上由 Samuel Sianipar 拍照
概观
机器学习中的集成方法是利用一个以上的模型来获得改进的预测的算法。这篇文章将介绍基于树的集成方法。我们先来看看他们是如何利用 德尔菲法 和Bootstrap Aggregation(简称 Bagging)来提高预测能力的。然后,进入 boosting ,这是一种算法使用弱学习器组合来提升性能的技术。将涵盖 scikit-learn 中的以下 集成算法 :

使用基于树的集成方法,我们知道没有一个模型可以给我们完美的估计,所以我们使用多个模型来进行预测,并对它们进行平均。通过这样做,高估和低估很可能会抵消并提高我们的预测。
自举聚合
Bagging 是 Bootstrap Aggregation 的缩写,是使集成方法成为可能的主要概念之一。Bootstrap 聚合由两个概念组成:bootstrap 重采样和聚合。 Bootstrap 重采样 是一种采样技术,其中创建数据集的子集,并进行替换。Bootstrap aggregation是一种利用这些子集,对它们的预测进行平均的技术。这个过程非常适用于决策树,因为它们容易过度拟合,并且有助于减少预测的方差。使用自举聚合来训练集成算法的过程是:
- 取数据集的样本,替换
- 在这个子集上训练分类器
- 重复第 1 步和第 2 步,直到所有分类器都完成了它们自己子集的训练
- 用集合中的每个分类器进行预测
- 使用集成方法,例如最大投票、平均、加权平均,将来自所有分类器的预测聚集成一个。

作者图片
在使用集成分类器之前,让我们先为基线安装一个决策树分类器。我将使用 泰坦尼克号数据集 进行二元分类,目标是Survived特征。出于本概述的目的,正在使用的数据集已被清理。我们将使用来自 sklearn 的 管道来执行预处理步骤。有关数据集中要素的描述,请参阅下面的数据字典。


****
决策树分类器以 98%的准确度分数过度适合训练数据。分类器在准确率为 76%的测试数据上表现不佳。为了防止决策树过度拟合,我们可以设置超参数,比如树的最大深度。然而,我们通常会通过创建另一个决策树来获得更好的结果。为了做到这一点,我们可以用一个打包分类器来装配一个打包决策树的集合。bagging 分类器是一种基于树的集成方法,它在从数据中抽取的每个随机子集上拟合一个分类器,然后将各个预测聚合为一个。下面,我们将使用 sklearn 的 装袋分类器 创建一个装袋管道。我们将把n_estimators参数设置为 200 (200 个分类树),把max_samples参数设置为 20(用来训练每个估计器的样本数)。

通过使用 bagging 分类器,我们能够将测试集的准确率提高到 79%。该分类器没有过度拟合,在训练集和测试集上具有相似的准确度分数。我们通过替换重新采样数据来引导,并且通过使用所有预测来聚集以得出一个预测。因为我们在同一个模型中引导和聚合,所以我们是打包。
随机森林

随机森林分类器 是一种集成方法,类似于 bagging 分类器,但使用特征的子集,而不是数据集中的所有特征。在随机森林分类器的装袋阶段,对三分之二的训练数据进行采样,并替换集合中的每棵树。这一部分用于构建树,剩余的三分之一用于计算 出袋误差 ,这是对集合中每棵树的性能的连续无偏估计。随机森林还利用 子空间采样方法 在集合中的树之间提供更多的可变性。这种方法只为树中的每个节点随机选择一个特征子集。通过使用这些子集,我们将得到一个包含不同决策树的随机森林。有了在数据的不同子集上训练的决策树的集合,该模型将是不太敏感的噪声数据。让我们继续创建一个带有随机森林分类器的管道,我们将把n_estimators参数设置为 500,把max_samples参数设置为 20。

随机森林分类器在测试集和训练集上都达到了 81%的准确率。这是对 bagging 分类器的一点改进,但是我们也使用了 500 棵分类树,而不是 200 棵。更详细的解释, 下面是 一篇关于随机森林的伟大论文。
极度随机化的树

科林·沃茨在 Unsplash 上的照片
如果我们想给我们的组合增加更多的随机性,我们可以使用 额外的树分类器 。与随机森林分类器类似,额外的树也将使用要素的随机子集,但不是选择最佳分支路径,而是随机选择一个。这降低了我们的模型对训练数据中的特征的依赖,并且将有助于防止过度拟合。我们将在下面安装一个额外的树分类器,参数n_estimators设置为 400,参数max_samples设置为 30。额外树算法需要注意的一点是,bootstrap 参数默认设置为 False。为了对替换的数据进行采样,我们需要将该参数设置为 true。

额外的树分类器比随机森林分类器表现稍好,准确性提高了 1%。
助推

Boosting 是指顺序训练弱学习者建立模型的过程。 弱学习者 是机器学习算法,表现略好于随机机会。boosting 算法的预测通常分配权重来确定每个学习者输入的重要性。通过组合给予每个学习者的权重,具有正确分类的树的集体权重将否决来自不正确分类的具有较高权重的树。升压过程如下:
- 训练一个弱学习者
- 评估学习者分类错误的地方
- 训练另一个薄弱的学习者,重点放在之前的学习者分类错误的领域
- 重复此过程,直到满足某些停止标准,例如性能稳定期
自适应增压
Adaboost(adaptive boosting 的简称)是最早发明的 boosting 算法。它通过顺序更新两组权重来工作,一组用于数据点,一组用于弱学习者。被错误分类的点被赋予更大的权重,因此下一个弱学习者可以关注这些点。在序列的末尾,对做出更好预测的学习者给予更高的权重。这尤其适用于对先前学习者没有正确分类的数据点做出正确预测的学习者。给学习者的权重然后被用作最终投票来确定集合的预测。这里的主要思想是 adaboost 通过不断调整采样数据的分布来训练下一个分类器,从而创建新的分类器。

仅使用默认参数,adaboost 分类器表现得相当好,并且还获得了比上面使用的强学习器更高的 f1 分数。
梯度推进
梯度提升树 算法比 adaboost 更高级,使用 梯度下降 。与 adaboost 类似,梯度增强也是从训练弱学习者开始的。然而,它更进一步,计算每个数据点的 残差 。这将表明学习者的预测有多错误。使用残差和损失函数计算总损失。梯度和总损失然后被用于训练下一个学习者。学习者错误的损失被赋予更重的权重,这反过来允许我们使用梯度下降来推动算法创建下一个学习者来关注这些数据点。需要注意的是,梯度下降中的步长通常是我们想要限制的。这导致较小的学习率,以帮助找到收敛的最佳值。

梯度提升分类器在测试集上表现良好,准确率达到 82%。然而,这个模型看起来确实过于适合训练数据,准确性分数下降了 9%。
极端梯度推进
XGBoost (极限梯度增强的简称)是目前性能最高的梯度增强算法版本。xgboost 有许多优化,在所有梯度提升算法中提供最快的训练时间。最大的一个问题是 xgboost 已经被配置为在计算机 CPU 的所有内核中均匀分布树的构造。XGBoost 使用 二阶导数 来最小化误差,这与在梯度提升树中使用基本学习器的损失函数相反。

XGBoost 分类器在测试集上表现最好,准确率为 84%。这是通过 0.15 的学习率(默认为 0.1)实现的。分类器看起来确实稍微过度适合训练数据,在训练集和测试集之间的准确度分数下降了 6%。
结论
机器学习中的集成方法可以显著提高模型的性能。虽然我们讨论了一些不同的算法,但有许多算法利用了这些集成技术,我建议对它们进行深入研究。上面使用的分类器没有优化,可以改进。使用这些分类器执行网格搜索将有助于找到最佳参数,但也将更加耗时。我希望所涉及的概念是清楚的和有帮助的。谢谢你花时间看我的帖子!
参考资料:
- 德尔菲法——概述、流程和应用。(2020 年 7 月 15 日)。检索自https://corporatefinanciinstitute . com/resources/knowledge/other/Delphi-method/
- Python 中的集成学习。(未注明)。检索自https://www . data camp . com/community/tutorials/ensemble-learning-python?UTM _ source = AdWords _ PPC&UTM _ campaignid = 1565261270&UTM _ adgroupid = 67750485268&UTM _ device = c&UTM _ keyword =&UTM _ match type = b&UTM _ network = g&UTM _ adpossion =&UTM _ creative = 295208661502【中
- 1.11。整体方法。(未注明)。检索自https://scikit-learn.org/stable/modules/ensemble.html
- sk learn . ensemble . bagging classifier .(未标明)。检索自https://sci kit-learn . org/stable/modules/generated/sk learn . ensemble . bagging classifier . html
- sk learn . ensemble . randomforestclassifier .(未标明)。检索自https://sci kit-learn . org/stable/modules/generated/sk learn . ensemble . randomforestclassifier . html
- Bootstrap 聚合,随机森林和 Boosted 树。(未注明)。检索自https://www . quant start . com/articles/bootstrap-aggregation-random-forests-and-boosted-trees/
- 琼斯,C. (2014 年 2 月 12 日)。检索自https://businessforecastblog . com/random-subspace-ensemble-methods-random-forest-algorithm/
- sk learn . ensemble . gradientboostingclassifier .(未标明)。检索自https://scikit-learn . org/stable/modules/generated/sk learn . ensemble . gradientboostingclassifier . html
- sk learn . ensemble . adaboostclassifier .(未标明)。检索自https://sci kit-learn . org/stable/modules/generated/sk learn . ensemble . adaboostclassifier . html
- 迈尔,a .,宾德,h .,格费勒,o .,&施密德,M. (2014 年 11 月 18 日)。Boosting 算法的发展——从机器学习到统计建模。检索自https://arxiv.org/abs/1403.1452
- XGBoost 文档。(未注明)。检索自【https://xgboost.readthedocs.io/en/latest/】T21
集合模型
简单演练中的整体技术学习指南

Rene b hmer 在 Unsplash 上拍摄的照片
在我们的生活中,当我们做出一个重要的决定时,比如申请一个大学项目,签一份工作合同,我们倾向于寻求一条建议。我们试图收集尽可能多的信息,并联系多位专家。因为这样的决定会影响我们的未来,所以我们更信任通过这样的过程做出的决定。机器学习预测遵循类似的行为。模型处理给定的输入并产生结果。结果是基于模型在训练期间所看到的模式的预测。
在许多情况下,一个模型是不够的,本文将阐明这一点。什么时候以及为什么我们需要多个模型?如何训练那些模特?这些模型提供了什么样的多样性。因此,让我们直接进入主题,但要快速概述一下。
概观
集成模型是一种在预测过程中结合多个其他模型的机器学习方法。这些模型被称为基本估计量。这是一种解决方案,可以克服构建单个评估器的以下技术挑战:
- 高方差:模型对学习特征的输入非常敏感。
- 低准确性:一个模型或一个算法来拟合整个训练数据可能不足以满足预期。
- 特征噪声和偏差:模型在进行预测时严重依赖于一个或几个特征。
集成算法
单一算法可能无法对给定的数据集做出完美的预测。机器学习算法有其局限性,并且产生具有高精确度的模型是具有挑战性的。如果我们建立并结合多个模型,整体精度可能会得到提升。这种组合可以通过聚合每个模型的输出来实现,它有两个目标:减少模型误差和保持其泛化能力。实现这种聚合的方法可以使用一些技术来实现。一些教科书将这样的架构称为 元算法 。

图 1:使用多种算法使模型预测多样化。
集成学习
构建集成模型不仅关注所用算法的方差。例如,我们可以构建多个 C45 模型,其中每个模型都在学习专门预测某个方面的特定模式。那些模型被称为弱学习器,可以用来获得元模型。在这种集成学习器的架构中,输入被传递给每个弱学习器,同时收集它们的预测。组合预测可用于建立最终的集合模型。
要提到的一个重要方面是,那些弱学习者可以用不同的方式映射具有不同决策边界的特征。

图 2:使用相同算法的多个弱学习器的聚合预测。
集成技术
制袋材料
bagging 的思想是基于使训练数据可用于迭代学习过程。每个模型使用训练数据集的稍微不同的子集来学习前一个模型产生的误差。装袋减少了差异,最大限度地减少了过度拟合。这种技术的一个例子是随机森林算法。
- 自举:装袋是基于自举抽样技术。引导通过替换创建多组原始训练数据。替换允许在集合中复制样本实例。每个子集具有相同的大小,并且可以用于并行训练模型。

图 3:通过组合来自多个模型的预测进行最终预测的 Bagging 技术。
- 随机森林:使用训练样本子集以及特征子集来构建多个分裂树。构建多个决策树来适应每个训练集。样本/特征的分布通常以随机模式实现。
- 额外树集成:是另一种集成技术,其中来自许多决策树的预测被组合。类似于随机森林,它结合了大量的决策树。然而,额外树在随机选择分裂时使用整个样本。
助推
- 自适应增强(AdaBoost): 是一组算法,我们在几个弱学习器的基础上建立模型[1]。正如我们前面提到的,这些学习者被称为弱学习者,因为他们通常很简单,预测能力有限。AdaBoost 的自适应能力使该技术成为最早成功的二元分类器之一。顺序决策树是这种适应性的核心,其中每一棵树都基于精确度的先验知识来调整其权重。因此,我们以顺序而不是并行的过程来执行这种技术的训练。在这种技术中,训练和测量估计误差的过程可以重复给定的迭代次数,或者当误差率没有显著变化时。

图 AdaBoost 的顺序学习产生更强的学习模型。
- 梯度推进:梯度推进算法是具有高预测性能的伟大技术。Xgboost [2]、LightGBM [3]和 CatBoost 是流行的提升算法,可用于回归和分类问题。在证明了他们有能力赢得一些卡格尔比赛后,他们的受欢迎程度显著增加。
堆垛
我们之前已经看到,组合模型可以使用聚合方法来实现(例如,投票选择分类或平均选择回归模型)。堆叠类似于助推模型;它们产生更强有力的预测。堆叠是学习如何从所有弱学习者的预测中创建这样一个更强的模型的过程。

图 5:在集合架构中进行最终预测的堆叠技术。
请注意,这里学习的(作为特征)是来自每个模型的预测。
混合
非常类似于堆叠方法,除了最终模型正在学习验证和测试数据集以及预测。因此,所使用的特性被扩展到包括验证集。
分类问题
因为分类只是一个归类过程。如果我们有多个标签,我们需要决定:我们应该建立一个单一的多标签分类器吗?还是要建立多个二元分类器?如果我们决定建立一些二元分类器,我们需要解释每个模型的预测。例如,如果我们想要识别四个对象,每个模型都会告诉输入数据是否属于该类别。因此,每个模型都提供了一个隶属概率。类似地,我们可以结合这些分类器建立最终的集成模型。
回归问题
在前面的函数中,我们使用得到的概率来确定最佳拟合隶属度。在回归问题中,我们不处理是或否的问题。我们需要找到最佳的预测数值。我们可以对收集的预测进行平均。
汇总预测
当我们集成多种算法来调整预测过程以组合多种模型时,我们需要一种聚合方法。可以使用三种主要技术:
- 最大投票:该技术中的最终预测是基于对分类问题的多数投票做出的。
- 平均:通常用于对预测进行平均的回归问题。例如,在对最终分类进行平均时,也可以使用概率。
- 加权平均:有时候,在生成最终预测时,我们需要给一些模型/算法赋予权重。
例子
我们将使用下面的例子来说明如何建立一个集合模型。在这个例子中使用了泰坦尼克号的数据集,我们试图用不同的技术来预测泰坦尼克号的存活率。图 6 和图 7 显示了数据集样本和乘客年龄的目标列分布。

图 Titanic 数据集的一个示例,显示它由 12 列组成。

图 7:年龄列到目标的分布。
泰坦尼克号数据集是需要大量特征工程的分类问题之一。图 8 显示了 Parch(父母和子女)等一些特征与家庭规模之间的强相关性。我们将尝试仅关注模型构建以及如何将集合模型应用于此用例。

图 8:特性的相关矩阵图。数据集列。
我们将使用不同的算法和技术;因此,我们将创建一个模型对象来增加代码的可重用性。
# Model Class to be used for different ML algorithms
class ClassifierModel(object):
def __init__(self, clf, params=None):
self.clf = clf(**params)def train(self, x_train, y_train):
self.clf.fit(x_train, y_train)
def fit(self,x,y):
return self.clf.fit(x,y)
def feature_importances(self,x,y):
return self.clf.fit(x,y).feature_importances_
def predict(self, x):
return self.clf.predict(x)def trainModel(model, x_train, y_train, x_test, n_folds, seed):
cv = KFold(n_splits= n_folds, random_state=seed)
scores = cross_val_score(model.clf, x_train, y_train, scoring='accuracy', cv=cv, n_jobs=-1)
return scores
随机森林分类器
# Random Forest parameters
rf_params = {
'n_estimators': 400,
'max_depth': 5,
'min_samples_leaf': 3,
'max_features' : 'sqrt',
}
rfc_model = ClassifierModel(clf=RandomForestClassifier, params=rf_params)
rfc_scores = trainModel(rfc_model,x_train, y_train, x_test, 5, 0)
rfc_scores
额外树分类器
# Extra Trees Parameters
et_params = {
'n_jobs': -1,
'n_estimators':400,
'max_depth': 5,
'min_samples_leaf': 2,
}
etc_model = ClassifierModel(clf=ExtraTreesClassifier, params=et_params)
etc_scores = trainModel(etc_model,x_train, y_train, x_test, 5, 0) # Random Forest
etc_scores
AdaBoost 分类器
# AdaBoost parameters
ada_params = {
'n_estimators': 400,
'learning_rate' : 0.65
}
ada_model = ClassifierModel(clf=AdaBoostClassifier, params=ada_params)
ada_scores = trainModel(ada_model,x_train, y_train, x_test, 5, 0) # Random Forest
ada_scores
XGBoost 分类器
# Gradient Boosting parameters
gb_params = {
'n_estimators': 400,
'max_depth': 6,
}
gbc_model = ClassifierModel(clf=GradientBoostingClassifier, params=gb_params)
gbc_scores = trainModel(gbc_model,x_train, y_train, x_test, 5, 0) # Random Forest
gbc_scores
让我们将所有的模型交叉验证准确度合并在五个折叠上。

现在让我们建立一个堆叠模型,其中一个新的更强的模型从所有这些弱学习者那里学习预测。我们用于训练先前模型的标签向量将保持不变。这些特征是从每个分类器收集的预测。
x_train = np.column_stack(( etc_train_pred, rfc_train_pred, ada_train_pred, gbc_train_pred, svc_train_pred))
现在让我们来看看构建 XGBoost 模型只学习结果预测是否会执行得更好。但是,在此之前,我们将快速浏览一下分类器预测之间的相关性。

图 9:分类器投票标签之间的皮尔逊相关性。
我们现在将构建一个模型来组合来自多个起作用的分类器的预测。
def trainStackModel(x_train, y_train, x_test, n_folds, seed):
cv = KFold(n_splits= n_folds, random_state=seed)
gbm = xgb.XGBClassifier(
n_estimators= 2000,
max_depth= 4,
min_child_weight= 2,
gamma=0.9,
subsample=0.8,
colsample_bytree=0.8,
objective= 'binary:logistic',
scale_pos_weight=1).fit(x_train, y_train)
scores = cross_val_score(gbm, x_train, y_train, scoring='accuracy', cv=cv)
return scores
先前创建的基本分类器代表 0 级模型,而新的 XGBoost 模型代表 1 级模型。该组合说明了基于样本数据预测训练的元模型。新叠加模型与基础分类器的准确性之间的快速比较如下所示:

仔细的考虑
- 噪声、偏差和方差:来自多个模型的决策组合有助于提高整体性能。因此,使用集合模型的一个关键因素是克服这些问题:噪声、偏差和方差。如果集合模型在这种情况下不能提供集体经验来提高精确度,那么就需要对这种应用进行仔细的重新思考。
- 简单和可解释:机器学习模型,尤其是那些投入生产环境的模型,更倾向于简单而不是复杂。根据经验,解释最终模型决策的能力随着系综而降低。
- 一般化:有许多人声称集成模型具有更强的一般化能力,但是其他报告的用例显示了更多的一般化错误。因此,没有仔细训练过程的集成模型很可能很快产生高过拟合模型。
- 推理时间:虽然我们可以轻松接受更长的模型训练时间,但推理时间仍然很关键。将集合模型部署到生产中时,传递多个模型所需的时间会增加,并可能降低预测任务的吞吐量。
摘要
集成模型是一种很好的机器学习方法。集合模型有多种用于分类和回归问题的技术。我们已经发现了这种模型的类型,我们如何建立一个简单的集合模型,以及它们如何提高模型的准确性。代码的完整例子可以在我的 Github 上找到。
谢谢大家!
参考
[1] Yoav Freund 和 Robert E. Schapire。在线学习的决策理论概括及其在 boosting 中的应用。计算机和系统科学杂志,55(1):119–139,1997 年 8 月。
[2]陈、田琦和卡洛斯·盖斯特林。" Xgboost:一个可扩展的树增强系统."第 22 届 acm sigkdd 知识发现和数据挖掘国际会议论文集。2016.
[3]柯,,等.“Lightgbm:一种高效的梯度推进决策树”神经信息处理系统进展30(2017):3146–3154。
进入 j(r)VAE:划分,(旋转),和排序…卡片
思想和理论
引入联合(旋转不变)值,可以执行无监督分类,同时解开相关(连续)的变异因素。
马克西姆·兹亚迪诺夫&谢尔盖·加里宁
美国田纳西州橡树岭橡树岭国家实验室纳米材料科学和计算科学与工程中心
什么是联合(旋转不变)变分自动编码器,为什么我们需要它们?第一个问题的简短答案是, j ( r )VAE 是一种变分自动编码器,其中一个潜在变量是离散的,其余的是连续的。因此, j VAE 试图通过将数据与特定的离散类相关联来对数据进行聚类,并通过潜在变量来理清类内的数据表示。第二个问题的答案是,在实验世界中,我们经常不知道我们在处理什么,但非常想找到答案。
在他们的职业生涯中,这篇笔记的两位作者都专注于纳米和原子分辨率的成像和光谱学。M.Z .的职业生涯始于在日本东京工业大学进行扫描隧道显微镜和第一性原理计算,探索用氢和氧原子修饰的石墨烯的电子性质。在搬到橡树岭后,他逐渐过渡到数据分析和应用深度/机器学习(ML/DL)领域,他的第一篇 DL 论文专注于电子显微镜数据集的分析。s . v . k .1996 年作为韩国 PosTech 的访问学生开始熟悉扫描探针显微镜,并从 1998 年作为 U. Penn 的研究生与 Dawn Bonnell 一起工作开始将显微镜作为职业。在 25 年里,他积累了扫描探针显微镜、电子显微镜和化学成像方面的专业知识。所有这些技术的共同点是它们能够产生大量数据,从多晶薄膜颗粒中的极化分布到高温超导体的电子结构,再到支撑量子计算 Kitaev 材料类物理学的结构扭曲。
作为经过培训和培养的显微镜专家,我们试图从这些数据中了解材料的物理和化学性质,或者寻求对材料结构和功能的定性理解(“是否存在位错?我们看到点缺陷的证据了吗?”),或者通过细化定量的物理模型()极化在界面上如何表现?挠曲电张量的值是多少?”),或者通过发现新奇的现象(嗯……这就怪了……”)。然而,多年来,这种分析都是基于人类的分析和理解——花费无数的时间使用当时的可视化软件来寻找特定的对象,同时在分析软件中改变对比度、过滤、颜色方案。很明显,即使对于 2D 图像来说,这也不是最好的策略(尽管在宝丽来时代,这种策略甚至更糟糕)。对于现在电子和扫描探针显微镜中常见的多维光谱数据集,或图像堆栈,如电子显微镜中原子进化的电影,这是不可能的。
因此,问题是机器学习是否可以帮助我们发现数据中的“有趣”对象。如果我们确切地知道我们在寻找什么,即有标签,这就变成了一个经典的监督学习问题。作者在 2017 年率先将深度卷积神经网络(DCNN)用于电子显微镜图像中的原子发现,实现了对包含数百幅图像的海量数据集的分析,并创建了原子缺陷库,探索点缺陷反应等[1–3]。在这种情况下,ML 有效地执行了人工级别的任务,只是速度更快。
然而,在许多情况下,我们的目标是在我们的数据中寻找新的和不寻常的对象和行为。例如,我们可能希望找到形成无定形固体结构的组成构件。或者,我们试图了解高温超导体表面的电子密度模式是如何组织的,以及是否存在任何潜在的顺序。作为第三个例子,我们的目标是从原子晶格的图像中发现铁电畴结构。我们应该如何着手呢?
这种分析的一种可能策略是描述符工程和随后的无监督学习。我们将图像细分为多个子图像,并探索后者。在没有关于该系统的任何先验知识的情况下,可以在矩形网格上选取子图像,非常类似于在 DCNN 的输入卷积层中所做的。或者,它们可以以感兴趣的对象为中心,为分析提供推理偏差。对于原子级解析的数据,将描述符放在原子列的中心是很自然的,而对于铁电畴,我们可以将它们放在畴壁上。附带说明一下,位置编码的选择可能是一个非常有趣的领域。
有了描述符,我们可以寻找不同种类的原子结构(如发现“分子”)或寻找单个结构单元的应变状态。第一个任务是通过聚类完成的。使用合适的算法,无论是k-均值、高斯混合模型还是 UMAP,我们都试图将我们的描述符分成不同的组。第二个可以通过基于物理的分析来实现,如果我们确切地知道我们在寻找什么的话。有时我们会这样做——然而,无论你要找的东西在不在那里,找到它总是有风险的,而且会错过新奇的发现。因此,我们希望使用无监督的 ML 来找到数据中的相关特征,这正是变分自动编码器能够解开数据表示并找到数据中的先验未知可变性因素的地方。
然而,通常我们需要同时做这两件事!例如,在观察 STEM 中的原子运动时,我们可以观察到结构单元,如理想石墨烯的 5 元环、6 元环和 7 元环以及缺陷,还可以观察到它们在应变下的变形。同样,我们可以在铁电材料中看到美丽的畴结构,人脑会自然地将它们分成不同的类型,并阐明每种类型内的变形。VAEs 能做到吗?
当然,一种方法是在一个潜在空间中编码全部数据集。在这种情况下,每个编码对象对应于一个单独的潜在向量,我们可以简单地对潜在空间中的点进行聚类。然而,在这种情况下,数据的表示(理解为数据集内相关特征的平滑变化)将不会很好地被解开。从某种意义上说,阶级之间的差异主导了这一过程。比较简单的( r )VAE 和条件的( r )VAE 从我们的上一篇文章中得到的理清的数据——显然,当类已知时,我们可以更深入地挖掘数据结构,特征在整个潜在空间(而不是分段)变得一致。
那么,我们如何继续呢?一种方法是将其中一个潜在方向离散化,这种方法我们称之为 j VAE(关节用 j )。在这种情况下,在训练期间,自动编码器既将离散类分配给变量,又找到最佳连续潜在代码,即同时执行分类和解开相关变量。
然而,这并不简单,因为离散(分类)潜在变量不能通过样本反向传播。一种解决方案是用来自 Gumbel-Softmax 分布的可微分样本替换来自分类分布的不可微分样本[4,5]。另一个解决方案是通过完全枚举边缘化离散的潜在变量。这种方法通常与高计算成本相关,尽管最近可用的概率编程库如 Pyro 通过启用并行枚举使这项任务变得更容易。我们对这两种方法都进行了实验,发现它们通常在我们的数据集上产生相似的结果。对于本文,我们利用了基于 Gumbel-Softmax 技巧的方法。
现在,让我们看看例子。我们不使用标准的 MNIST 数据集,而是制作我们自己的扑克牌套装数据集,包括单色梅花、黑桃、方块和红心。我们推测,来自 GalaxyZoo 的星系图像,一些生物物体(细菌,硅藻,病毒)都可以是一个很好的探索主题——无论是从美学还是科学的角度。然而,卡片提供了一个理想的玩具模型。在这里,钻石的形状与其他三只手截然不同。它们旋转 90 度相当于单轴压缩和调整大小,给可能的仿射变换的结果带来了有趣的退化。类似地,红桃和黑桃在相当小的细节上有所不同,而黑桃和梅花(没有尾巴)分别有三重对称和镜像对称。因此,尽管简单,牌套形成了一个有趣的符号集合。

具有不同仿射变换的卡片集合的典型对象。在左边(a = 12,t = 0.1,s = 10),在右边(a = 120,t = 0.1,s = 10)。作者提供的图片。
首先,我们创建一个具有不同参数的纸牌符号生成器。我们从 Word 中的数字化符号开始,将其转换为图像,然后使用一组仿射变换(包括旋转、剪切和平移)来准备我们的数据集。这里,角度从- a 到 a 均匀分布,平移从- t 到 t 均匀分布,剪切从- s 到 s 均匀分布。上面显示了这个数据集的两个实现的例子。换句话说,这里我们有多个定义明确的对象类,它们有几个连续的特征(大小和位置)。现在,让我们假设我们,就像约翰·斯诺一样,对这些物体一无所知,看看机器学习是否能帮助理清这里发生的事情!

cards 数据集上的主成分分析。作者提供的图片。
正如我们在之前的博文中指出的,最近我们几乎停止了主成分分析。在这个数据集上运行快速 PCA 说明了为什么——PCA 组件非常复杂,而且数量很多。对于足够大的数据集,将有与图像中的像素数量一样多的有效分量。所以,不是很有用。
我们可以运行前一篇文章中的代码,快速查看这个数据集上的 VAE 分析。下面显示的是相应的潜在空间和潜在表示。

VAE 对 t = 0.1 和不同方向和剪切强度的卡片数据集进行分析。从左到右(a = 12,s = 1),(a = 12,s = 10),(a = 120,s = 1),(a = 120,s = 10)。选择颜色方案来代表手。作者提供的图片。
对于小的旋转无序( a = 12),潜在空间清楚地形成对应于每只手的分离良好的簇,旋转角度和剪切分量在潜在空间的对应部分内平滑地变化。同时,对于大的 a = 120,在潜在空间的分布变得非常复杂。我们把这种行为归因于这样一个事实,即由于单位圆(SO(2)李群)不能在曲面上很好地表示,潜在空间表示应该包含包含物体的非物理形状的“cowlick”缺陷。鉴于我们有几个结构,VAE 试图通过角度来确定物体的方向,并根据形状和大小将它们分开,从而形成一个非常复杂的结构。

对 t = 0.1 和不同方向和剪切强度的卡片数据集进行 rVAE 分析。从左到右(a = 12,s = 1),(a = 12,s = 10),(a = 120,s = 1),(a = 120,s = 10)。选择颜色方案来代表手。作者提供的图片。
相比之下,上面显示的是对相同数据的 r VAE 分析。在这种情况下,通过构建潜在向量,旋转角被分离为独立变量。出现的行为真的很了不起。对于小角度和剪切无序, a = 12, s = 1,指针在系统的潜在空间中形成四个非常明显的组。如果剪切无序度增加, a = 12, s = 10,这些团簇拉长,在潜空间形成清晰可见的 1D 流形。当我们增加方向无序度, a = 120 时,我们开始看到每手牌有一个以上的群集,例如,有一个群集对应于旋转 120 度的梅花和黑桃的两个方向。换句话说,具有所选先验的 r VAE 可以补偿旋转角度的小偏差,但是倾向于发现退化的最小值。作为理解这种行为的一个简单练习,考虑球杆图像围绕中心轴旋转时它们之间的结构相似性(或相关性)。相关性在 120 度和 240 度时有明显的最小值,在这两个角度,主叶重叠,只有小尾巴的位置不同。这里,对于小的剪切变形, s = 1,我们有对应于手的旋转变量的明确定义的簇,对于大的剪切变形, s = 10,它们再次变成伸长的流形。请注意,在某种意义上,我们已经解开了数据的表示,在这种情况下是剪切。此外,比较钻石和非钻石的流形形状是很奇怪的,前者更细长,更扩散,这完全是因为存在相当于 90 度旋转的仿射变换!

jVAE 分析为 a = 12,t = 0.1,s = 10。作者提供的图片。
现在,进入 j VAE。欢迎您在附带的笔记本中使用不同的模型参数,这里我们将只显示两个示例的值。对于小的无序, a = 12 和 s = 10,简单的 j VAE(没有旋转潜变量)以无人监督的方式出色地分离了对象。请注意,第一次潜在流形遍历如何捕获关于取向无序(+剪切变形)的信息,而第二次潜在流形遍历如何捕获偏心位移(+剪切变形)。

jVAE 分析为 a = 120,t = 0.1,s = 10。作者提供的图片。
对于大混乱来说,简单的 j VAE 无法将阶层分开。从上图可以看出,每个推断出的“类别”都包含方块、黑桃、梅花和红心。我们注意到,不同于( r )VAE, j ( r )VAE 倾向于对模型参数的初始化相当敏感(即,解码器和编码器神经网络中的权重的初始化以及训练集的洗牌),尽管在定性水平上结果基本上保持不变。在笔记本中随意试验不同的种子,看看它们对结果有什么影响。

jrVAE 分析为 a = 120,t = 0.1,s = 10。作者提供的图片。
相比之下, jr VAE 具有三个“特殊的”连续潜变量,用于吸收旋转和平移无序,对相同的无序参数显示出更好的分离。在这里,梅花和方块都有自己的潜在空间,而红桃和黑桃共享另一个潜在空间(这里我们要补充的是,简单地给 j VAE 增加三个(或更多)标准连续潜在变量不会解决它无法对大混乱进行分类的问题)。

编码后的潜在空间为 jVAE(左)和 jrVAE(右)为 a = 120,t = 0.1,s = 10。作者提供的图片。
最后,让我们看看我们的 jr VAE 类与原始类的对应程度。请注意,推断类的顺序是随机的(取决于初始化),并不对应于基本事实标签的顺序。我们可以看到,总的来说,在没有任何监督的情况下,它在区分不同类别方面做得相当出色。

当 a = 120,t = 0.1,s = 10 时,jrVAE 的相关矩阵(推断值与实际值之比)。图片由作者提供。
请注意,性能可以进一步微调;然而,在这种情况下,我们将把它调向这个特定系统的“已知”答案,而未知系统是未知的。
以上总结了 j VAE 和 jr VAE 的介绍。请随意使用笔记本,并将其应用到您的数据集中。作者在他们对扫描探针和电子显微镜中的原子分辨和介观成像的研究中使用了 VAE 及其扩展,但这些方法可以应用于更广泛的光学、化学和其他成像,以及跨其他计算机科学领域。还请查看我们的 AtomAI 软件包,用于将该工具和其他深度/机器学习工具应用于科学成像。
最后,在科学界,我们感谢资助这项研究的赞助商。这项工作在橡树岭国家实验室纳米材料科学中心(CNMS)进行并得到支持,该中心是美国能源部科学用户设施办公室。您可以使用此链接进行虚拟漫游,如果您想了解更多,请告诉我们。
可执行的 Google Colab 笔记本在这里有
参考文献
1.齐亚特迪诺夫,m;戴克,o;马克索夫,a。李,谢峰;三,X. H 肖;联合国科特迪瓦行动;瓦苏代万河;杰西;原子分辨扫描透射电子显微镜图像的深度学习:化学识别和跟踪局部转换。ACS Nano 2017,11 (12),12742–12752。
2.马克索夫,a。戴克,o;王;肖;Geohegan 博士;萨姆普特;瓦苏德万;杰西;加里宁公司;Ziatdinov,m,《WS2 中电子束诱导转变过程中缺陷和相位演化的深度学习分析》。npj 计算。脱线。2019, 5, 12.
3.齐亚特迪诺夫,m;戴克,o;李;萨姆普特;杰西;瓦苏德万;构建和探索石墨烯中的原子缺陷库:扫描透射电子和扫描隧道显微镜研究。Sci。Adv. 2019,5 (9),eaaw8989。
4.张;顾;使用 Gumbel-Softmax 进行分类重新参数化。arXiv 预印本 arXiv:1611.01144 2016。
5.杜邦,e,学习解开连续和离散的表现。arXiv 预印本 arXiv:1804.00104。
企业 AI:如何从炒作转向价值
更系统地参与业务。

由 Freepik 设计
说到企业人工智能,大爆炸已经过去了。
在过去十年的大部分时间里,组织一直在大规模收集和存储数据。在云计算的帮助下,数据的处理比以往任何时候都更便宜、更容易,数据科学家可以专注于他们最擅长的事情:建立强大的机器学习模型,并大规模转变业务。
听起来好得难以置信?嗯,那是因为它是。
在与企业高管交谈时,我经常听到内部资产的治理、人才的管理以及绘制清晰的技术路线图的困难是人工智能实现其承诺的主要障碍。
虽然 76%的企业在其 2021 年 IT 预算中优先考虑人工智能,但其实施带来了单靠金钱无法解决的挑战。信息技术官员和业务单位之间需要强有力的自上而下的联合,才能跨越从宣传到价值的鸿沟。
首席信息官不断投入时间和精力来评估最新的技术趋势。他们需要选择最有可能对生产力产生积极影响的未来技术堆栈,并简化关键人工智能用例的部署。
任务是艰巨的。
他们必须合理化用于数据准备的过多工具,概述部署功能商店以及当前数据管道战略的利弊,或者设计一种有效的 MLOps 方法来监控机器学习模型在生产中的价值。
我们还没有谈到混合本地/云环境中的安全性。
然而,经过几个月的研究,他们经常对缺乏企业采用感到困惑。迫于自身 KPI 的压力,业务部门没有时间改变策略,采用新的工具和流程。并且经常希望他们从第一天开始就参与进来,以避免这些新协议在纸面上看起来有多棒与从事分析工作的人的日常工作之间的脱节。
让我们来探讨两个主要的挑战,并探讨如何应对它们。
访问正确的数据
随着监管机构不断加强对隐私问题的审查,概述自助式数据访问的基础,同时实施正确的治理,对于胆小的人来说并不是一个挑战。它肯定不能由 It 部门单独管理。
关键是根据保持竞争力的需要,重新调整一些通常由过时流程管理的内部安全策略。必须做更多的工作,让企业参与这项重要的工作。
例如,围绕令牌化的最新趋势为处理匿名数据带来了光明的前景。另一方面,它也增加了已经错综复杂的技术堆栈的复杂性,将用例构思推迟了几个月。
尽早让业务部门参与进来有助于定义路线图并缩短实施时间。
它们最能识别哪些数据集是关键的,哪些数据集可以冻结在云中,或者哪些进程收集敏感的 PII 数据。有了这些见解,IT 部门可以预测未来防范网络威胁的需求,并定义治理策略。
与项目范围和交付时间表保持一致
业务和分析专业知识之间的内耗是挫折的另一个原因。让每个人都在同一页上定义变革性的解决方案并不是一项简单的任务。不言而喻,一个成功的人工智能战略包括他们之间更大的合作。
很少有人成功设计出合适的论坛来交流想法,并围绕如何构建模型建立信任。这种特权通常属于具有横向集成结构的数字原生代,在这种结构中,数据科学家和业务分析师之间的协作更加流畅。
因此,成熟的组织试图从创业文化中复制成功的关键要素。像 sprints 和敏捷方法这样的术语被广泛采用,以尊重时间限制。但是,这就是发布符合客户期望的数字产品所需要的一切吗?
围绕产品发布的治理是繁重的,执行也不是微不足道的。
然而,解决一些简单的问题可以积极地改变组织构建和部署分析和人工智能的方式。
为什么我们要等到模型准备好投入生产时才让验证团队参与进来?随着项目的进行,他们可以发表自己的意见,并参与业务和分析团队之间的规划会议。
为什么我们需要完成完整的渠道来了解企业需要什么来监控价值?最初几个 sprints 的目标可能是创建一个企业日常使用的仪表板模型,并验证他们在 UI 中拥有所有 KPI。
为什么我们需要一个完整的产品来让数据工程师研究工作流程中的敏感组件?他们应该能够更早地访问项目,并邀请数据科学家来预测潜在的部署不足。
企业人工智能不仅仅是获取技术并将其带给数据专家。分析领导者、技术官员和业务部门之间的健康合作是重新思考企业内部治理的基础。路线图应该是雄心勃勃的,而不牺牲灵活性。
短期成果就是资本;它们有助于建立信心,摒弃旧的流程,并保持发展势头,以建立一种因创新而繁荣的文化。
具有市场的企业数据科学工作台——使人工智能民主化的生态系统
介绍
Gartner 表示,到 2025 年, 80%的数据和分析治理计划将被视为重要的业务能力,它们关注的是业务成果,而不是数据标准。问题是,“企业应该参与人工智能系统的端到端生命周期和决策过程”。几乎每个企业都有针对其数据科学团队的流程,这些流程专注于教授计算机并做出预测,通常比人类更快、更准确。在将这些预测应用于商业的过程中,他们意识到跑道还没有准备好起飞。
让人工智能接近中小企业的最佳方式是让人工智能民主化。这直接将人工智能工具行业置于一个更好的位置,所有年轻玩家都在争夺市场空间。随着思维模式转向“利用人工智能创造价值,展示更成熟、更具规模的人工智能产品平台”,企业正在将大量预算投向人工智能运营工具集。

AI 的虚空是“用 AI 创造价值”[图片由作者提供]
informatica 的研究还显示,数据科学团队花费近四分之三的时间来构建和维护人工智能服务。在过去的几年里,这个领域已经被行业巨头如谷歌、苹果、IBM、AWS、优步、脸书和其他引入统一平台的企业产品的技术创新所开发。现在的主张是,数据科学团队应该能够花更多的时间来构建结构和决策模型。这个创新的时代带来了关于数据平台、模型培训和开发、人工智能模型的生产和服务的专门平台和工具。
根据 Gartner 的预测,到 2024 年,70%的企业将使用云和基于云的人工智能基础设施来运营人工智能,从而大大缓解对集成和升级的担忧。工具集可用性的这种峰值增长带来了关于工具集的企业可见性、资源的优化使用、模型人工制品的重新发明、业务流程的不一致性的挑战,因此仍然使业务远离数据科学团队。以下是面临的三大挑战:

企业对人工智能运营的挑战[图片由作者提供]
机会
企业面临的机遇是“创建一个多租户组织范围的生态系统来运营人工智能”。数据科学是一项团队运动,企业中包括业务、分析、科学、工程和运营在内的所有团队都需要在方向上保持一致,以展示商业价值。业务度量应该评估模型的适用性,并且应该支持持续的监控。现有信息的可发现性超越了孤立团队的界限。为了长期的可维护性和成本节约,在任何人工智能工具投资之前,需要将现有的企业范围的系统结合起来。
“民主化”是领导人谈论 AI 和 AI 对每个人的价值时的关键目标之一。民主化为在产品线中嵌入智能、标准化和可重用性带来了最小的障碍,增加了企业中所有团队对人工智能的采用,从而提高了管理端到端人工智能工作流的能力。
有鉴于此,企业特意放弃“筒仓”,因为它是人工智能驱动的增长的敌人。

运营人工智能的机会[图片由作者提供]
基础触角—数据、人员、流程、产品和平台(D4P)
企业学习表明,要创建一个全组织的生态系统,没有“没有放之四海而皆准的尺寸”。生态系统需要缝合、粘合和扩展,以建立一个标准化和合规的平台来运行生产就绪的人工智能用例。对于企业来说,这个生态系统的基本触角包括数据、人员、流程、产品和平台[D4P]。为了说明这一点,我们以“新客户注册期间的欺诈检测”的工作流程为例,该流程包括
- 【数据】与数百万条记录相比,在几秒钟内检测重复项
- 【人员】操作员需要接受欺诈检测等智能功能的培训
- 【流程】与注册后检测相比,业务工作流程需要进行更改,以包括实时欺诈检测
- 【产品】新客户注册屏幕需要增强以嵌入人工智能
- 【平台】需要托管的人工智能服务需要共存于现有产品基础设施中,遵守系统性能 SLA
因此,这个生态系统的基础需要与“D4P”协同构建。设计和产品思维仍然是帮助构建支持业务、技术和运营需求的生态系统的基础层。
核心原则
现在,人工智能的时代正被算法提供的价值所引领。对于企业来说,分析和算法被编织到公司的业务结构中,因此要求业务以不同的方式运作,主要关注人工智能的治理和生命周期管理。因此,以下是定义该生态系统特征的核心原则。

核心原则—数据科学工作台和市场[图片由作者提供]
“人工智能的价值”的定义是增强人类决策和互动,而不是专注于自动化。这种思维过程为企业提供了拥有分析和算法的完全自主权。企业与数据、科学和工程团队一起定义增强需求。数据和模型定义由企业确定的可测量的 KPI 控制。工程和 IT 团队正在应对产品和基础设施需求的变化,从而实现人工智能与企业产品套件的无缝集成。
积木
“自助服务”是这个生态系统中应该同样适用的基本特征。为了补充这一特点,行业领导者和技术供应商都发布了拖放,无代码人工智能工具,以将预测集成到应用程序中。因此,出现的警示故事是偏见、可发现性和可解释性问题的受害者。自助服务的时间价值如下图所示:

企业人工智能市场的价值[图片由作者提供]
这里的策略是建立一个具有市场的工作台,从而使中小企业、工程和科学成为人工智能专家。数据科学工作台的构建模块将考虑可发现性、自助服务、标准化和整体治理。通过内置的访问、消费、治理和所有权,整体块结构如下图所示:

数据科学工作台和市场的构建模块[图片由作者提供]
构建工作台的方法和策略
我们之前讨论过,人工智能工具和库市场每天都充斥着技术供应商推出的新产品。通常,当有多余的选项可用时,团队会兴奋地引入他们选择的工具来满足他们的需求。我认为这种方法是将最好的工具集引入组织的一个很好的策略。现在有了更多的工具集,随着时间的推移,围绕可见性和可维护性的其他挑战也随之而来。“一个集中的工作台和市场解决了许多这样的挑战,并由一个由 CDO、首席信息官、首席技术官和人工智能官组成的中央管理机构来管理。”
工作台的基本元素是构建一个通信层,在这些不同的工具集之间交换信息,因此团队仍然可以自主地使用适合他们需求的工具/库。市场的概念,提供了提出任何想法的机会,从而提供了管理端到端生命周期的能力,包括数据、建模、指标跟踪和模型管理。创建工作流的能力提供了为数据/模型设计端到端流程的灵活性,同时考虑了模型管理和所有权的访问策略、数据令牌化、业务审批、指标和阈值。受开放式座舱设计启发的仪表板提供了高可视性和微调控制,所有相关角色都可以操作。这是核心能力,

核心能力—带有市场的数据科学工作台[图片由作者提供]
结论
组织将需要大量定制的解决方案来解决一系列不同的人工智能用例。在大多数企业中,很快就可以获得初始启动投资来构建最初的几个模型,但随着时间的推移,人们的期望是构建一个“引导”工具包和一个面向未来的中央统一平台。带有 marketplace 产品的工作台将是“ 以数据为中心的人工智能开发 的关键使能器,与数据数量相比,它更关注具有全面数据质量的商业价值“。有了 workbench,灵活的模型开发和测试环境将可以通过有限的访问机制进行访问,从而实现人工智能增强方面的创新。
Butterfield 指出,长期的成功取决于协作的灵活性、跨学科、跨部门、数据的完整性、风险评估的准确性和多模式方式。"work bench with market platforms ",帮助建立拥有与科学、工程和运营部门合作的人工智能开发的企业文化。机器学习 PaaS 解决方案将成为焦点,并仍然是企业采用人工智能的关键推动因素。
使用 Google Dialogflow 和 Flutter 的企业级多平台聊天机器人(第二部分)

作者图片
参见第一部分这里
1)第一部分概述
第一篇文章描述了如何配置我们的第一个 Dialogflow 代理意图来管理一个简单的对话,其目标是提供两国之间的贸易额。用户可以用不同的方式提出问题,只需要几个训练短语,这些短语包含带有参数的硬编码文本答案,并且还提醒用户所请求的参数。
这篇文章和接下来的文章将更进一步,让你一步一步地构建一个连接到开放数据 Euler Hermes API 的真正的聊天机器人。因为它们技术性更强,所以需要一些编程知识。
2)履行
实现是在满足某些条件时由 Dialogflow 执行的步骤。这意味着用户提出的问题可以被 Dialogflow 引擎理解,并且用户已经提供了所有请求的参数。然后执行一个代码。它可以是一个 URL,指向你自己用 NodeJS、Java 编写的 Web 服务。NET 或内置 Dialogflow 代码编辑器中的 NodeJS 代码。
2.1。启用 Webhook

作者图片
首先要做的是激活 Fulfillment Webhook 按钮。
- 点击左侧的意图菜单
- 选择您之前在第一部分中创建的意向(名为“生态出口与索赔”)
- 滚动到屏幕底部,然后激活按钮'为此目的启用 Webhook 调用'
- 点击顶部的保存按钮
2.2。Webhook 内嵌编辑器
有两种不同的方式来执行由实现触发的您自己的代码:
- WebService/API :如果 Webhook 切换按钮被启用,您可以添加您的 web 服务 URL,它将接收来自 Dialogflow 的 POST 请求,以响应意图匹配的用户查询的形式。API 可以用不同的语言编写(NodeJS、Java、.网)
- 使用 Webhook 内嵌编辑器允许您通过云函数(NodeJS)在 Dialogflow 中直接构建和管理履行。
在本文中,我们将使用 Webhook 内嵌编辑器来更好地理解和更容易地部署代理。
2.3。使用 Google Assistant 和 NodeJS 代码的基本实现
这里有一个基本履行代码的例子,它将允许您与 Google Assistant 集成,并在满足履行时显示一个简单的数据表。
首先,点击左侧的意图菜单,然后执行以下操作:
- 创建一个新的意图,并将其命名为'测试'
- 点击新创建的'测试意图
- 只添加一个训练短语: testxxx
- 在“测试”意图/部分“实现”的页面底部:启用切换按钮“为此意图启用 Webhook 调用
然后,点击完成菜单,并在内嵌编辑器中添加以下样本。该部分将负责创建一个包含一些项目的简单表格:
...
//
// >>> Sample for Test Google Assistant Handler
// with testxxx keyword
//
function testGoogleAssistantHandler(agent) {
// Google Assistant Connection
if (agent.requestSource === 'ACTIONS_ON_GOOGLE') {
let conv = agent.conv();
conv.ask('This is a simple table example for Google Assistant.');
conv.ask(new Table({
dividers: true,
columns: ['header 1', 'header 2', 'header 3'],
rows: [
['row 1 item 1', 'row 1 item 2', 'row 1 item 3'],
['row 2 item 1', 'row 2 item 2', 'row 2 item 3'],
],
}));
console.log("===> JSON : " + conv.json);
agent.add(conv);
}
}
...
现在,点击集成菜单,然后执行以下操作:
- 点击谷歌助手整合链接
- 显示一个弹出窗口,点击测试链接
- 将显示一个谷歌助手模拟器

作者图片
现在你应该可以看到谷歌助手的动作控制台了。
- 首先键入'与我的测试应用程序对话'以启动您的代理(或点击同名的建议按钮)
- 键入以下内容:' testxxx '
您应该会看到以下结果:

作者图片
请注意,您应该在移动应用程序上看到相同的内容。接下来的部分将描述如何为移动设备设置谷歌助手:

作者图片
现在你知道我在读' Le Point '和' Le Figaro '但不仅仅是;)
2.4。Euler Hermes OpenData API 的使用
正如在第一部分的文章中所解释的,我们的目标是在两个国家之间进行贸易往来。
在这个对话流中注入了几个基本参数:
- 原始出口国
- 目的地进口国
- 日期范围(从/到)
- 可选:筛选列表所依据的简短文本
- 可选:列表中显示的最大行数
API 结果由以下元素组成:
- 导出组类别
- 出口类别
- 出口金额
- 索赔金额
- 交通灯(红色、橙色、绿色)
下面是我们管理 Euler Hermes API 的通用 NodeJSgettradeflow函数。
基本上,参数是从用户对话中注入的。然后调用 API。结果被发送回另一个调用者函数,该函数将负责每个不同目标平台(Web、Google Assistant 或原生移动应用程序)的数据呈现。
// .
// Call Euler Hermes Web Service and get the data
// .
function getTradeFlows (countryCodeExport, countryCodeImport, periodFrom, periodTo, filter, topNN) {
return new Promise((resolve, reject) => {
console.log('In Function Get Trade Flows');
console.log("Country Export: " + countryCodeExport);
console.log("Country Import: " + countryCodeImport);
console.log("Period From: " + periodFrom);
console.log("Period To: " + periodTo);
console.log("filter: " + filter);
console.log("TopNN: " + topNN);
var defaultTopNN = 50;
// Convert dates into ISO String
var isoDateFrom = new Date(periodFrom).toISOString().substring(0, 10);
var isoDateTo = new Date(periodTo).toISOString().substring(0, 10);
var pathString = "/api/v2/catalog/datasets/exports-vs-claims/aggregates?" +
"select=dec_tp_nace_grp_name, buyer_nace_name, sum(exp_eur), sum(clm_eur), traffic_light" +
"&group_by=dec_tp_nace_grp_name, buyer_nace_name, traffic_light" +
"&where=period IN ['" + isoDateFrom + "'..'" + isoDateTo + "']";
if (countryCodeExport != "") {
pathString+= " AND ins_ctry = '" + countryCodeExport + "'";
}
if (countryCodeImport != "") {
pathString+= " AND buy_ctry = '" + countryCodeImport + "'";
}
if (filter != "") {
pathString+= " AND '" + filter + "'";
}
if (topNN != "") {
pathString+= "&order_by=sum(exp_eur) desc";
pathString+= "&limit=" + topNN;
}
else {
pathString+= "&limit=" + defaultTopNN;
}
pathString = encodeURI(pathString);
console.log ('path string:' + pathString);
var req = https.get({
host: "opendata.eulerhermes.com",
path: pathString
}, function (resp) {
var json = "";
resp.on('data', function(chunk) {
json += chunk;
});
resp.on('end', function(){
var jsonData = JSON.parse(json);
var records = jsonData.aggregations;
if (records === null) {
console.log("Rejection - records are null");
reject();
return;
}
var period = "";
var exportAmount = 0;
var claimAmount = 0;
var buyerNaceGroupName = "";
var buyerNaceName = "";
var trafficLight = "";
let rows = [];
for (var i = 0; i < records.length; i++) {
//console.log("==> record: " + i + " " + records[i]["buyer_nace_name"]);
buyerNaceGroupName = records[i]["dec_tp_nace_grp_name"].substring(0, 40);
buyerNaceName = records[i]["buyer_nace_name"].substring(0, 40);
exportAmount = kFormatter(records[i]["sum(exp_eur)"]);
claimAmount = kFormatter(records[i]["sum(clm_eur)"]);
trafficLight = records[i]["traffic_light"].substring(0, 1);
rows.push([buyerNaceGroupName, buyerNaceName, exportAmount, claimAmount, trafficLight]);
}
console.log ("Trade Flows for:" + rows);
resolve(rows);
});
resp.on('error', (error) => {
console.log(`Error calling the Euler Hermes API: ${error}`);
reject();
});
});
});
}
// K formatting
function kFormatter(num) {
return (num/1000).toFixed(0);
}
2.5。谷歌助手中的结果展示
既然我们能够从 Euler Hermes 获得贸易流,那么让我们构建一个基本函数,将数据呈现给用户。
// .
// Euler Hermes Trade Flows and Payment Handler - Google Assistant
// .
function exportsVSClaimsEHHandler(agent) {
let conv = agent.conv();
var parameters = "";
// Google Assistant Connection
if (agent.requestSource === 'ACTIONS_ON_GOOGLE') {
parameters = conv.parameters;
}
console.log("=======> Parameters " + parameters);
var countryCodeExport = parameters['geo-country-code-export'];
var countryCodeImport = parameters['geo-country-code-import'];
var periodFrom = parameters['date-period-from'];
var periodTo = parameters['date-period-to'];
var filter = parameters['filter'];
var topNN = parameters['topnn'];
return getTradeFlows(countryCodeExport, countryCodeImport, periodFrom, periodTo, filter, topNN)
.then((rows) => {
// Google Assistant Connection
if (agent.requestSource === 'ACTIONS_ON_GOOGLE') {
if (rows.length === 0) {
conv.ask(`No data has been found for this selection`);
conv.ask(new Suggestions(intentSuggestions));
return;
}
conv.ask(`Here's the Trade Flows and Payment Defaults data`);
conv.ask(new Table({
title: 'Trade Flows/Payment Defaults',
subtitle: 'Euler-Hermes OpenData',
image: new Image({
url: 'https://s3-eu-west-1.amazonaws.com/aws-ec2-eu-1-opendatasoft-staticfileset/euler-hermes/logo?tstamp=155013988259',
alt: 'Alt Text',
}),
dividers: true,
columns: ['Sector Group', 'Sector',
{
header: 'Export Amount',
align: 'TRAILING',
},
{
header: 'Claim Amount',
align: 'TRAILING',
},
{
header: 'Traff. Light',
align: 'CENTER',
}],
rows: rows
}));
conv.ask(new Suggestions(intentSuggestions));
agent.add(conv);
}
});
}
一旦达到 Euler Hermes 交易流的履行,将调用此函数处理程序。
该功能有几个主要步骤:
- 将用户请求中的参数解析为变量(出口和进口国家、周期……)
- 调用我们在上一节中添加的用于管理 Euler Hermes API 的gettradeflow函数
- 返回由以下元素组成的列表(表对象):
部门组
部门
出口金额
索赔金额
交通灯
现在让我们回到 Google Assistant 模拟器,键入以下请求:
- 获取 20180101 至 2018 01 02FR 至 de 之间的贸易流量
您应该会看到以下结果:

作者图片
2.6。高级功能—使用代理跟进意图启用数据过滤
现在,我们希望允许用户过滤显示的数据。但问题是,每次用户键入新的请求时,当前的上下文都会丢失,因为 Dialogflow 无法理解该请求链接到了以前显示的贸易流列表。
这可以通过跟进意图功能来实现。
下面将允许用户使用文本值进行过滤和/或将列表限制为前 n 条记录。
执行以下步骤来创建新的跟进意向:
- 点击意图菜单项
- 将鼠标移至您的环保出口与索赔主要意图上
- 应出现名为创建跟进意图的按钮。点击它
- 将跟进意图命名如下:'eco-eh-exports-vs-claims-filter '
现在让我们添加一些将在我们的脚本和主要意图中使用的上下文变量。
在上下文部分,添加以下内容:

作者图片
- 在输入上下文字段中,添加 1 个名为'eco-eh-exports-vs-claims-follow-up的变量
- 在输出上下文字段中,添加两个变量'过滤和' eco-eh-exports-vs-claims-follow '(与输入相同)
在训练短语部分添加以下短语。这个例子的完整短语列表可以在上一篇文章中下载,但是您也可以添加类似的短语,比如 limit rows 10,top 10,filter by motor,select by motor。突出显示的部分被视为变量。

作者图片
在与训练短语相关的动作和参数中添加以下内容:

作者图片
最后,启用按钮“启用此意图的 Webhook 调用
现在你完成了后续的意图。点击‘保存’按钮。
让我们更改名为'eco-eh-exports-vs-claims'的主要意图的输出上下文,以便将上下文从主要意图传输到后续意图。
在上下文部分:

作者图片
- 在输出上下文字段中,添加 1 个名为‘eco-eh-exports-vs-claims-follow up’的变量
- 保存意图
2.7。处理履约码中的数据过滤
现在已经配置了后续意图,让我们在实现代码中添加过滤器处理程序的相应代码。
- 点击履行菜单项
- 在标准处理程序之后添加以下处理程序代码,以处理筛选列表
请注意,' filter '和' topnn '参数在此用作结果过滤器。此外,处理程序还将添加一些建议按钮,可以帮助用户点击它们,直接过滤特定的国家或部门。
// .
// Euler Hermes Trade Flows and Payment - Filter Handler - Google Assistant
// .
function exportsVSClaimsEHFilterHandler(agent) {
let conv = agent.conv();
const parameters = conv.contexts.get("eco-eh-exports-vs-claims-followup").parameters;
console.log("=======> Parameters " + parameters);
var countryCodeExport = parameters['geo-country-code-export'];
var countryCodeImport = parameters['geo-country-code-import'];
var periodFrom = parameters['date-period-from'];
var periodTo = parameters['date-period-to'];
var filter = parameters['filter'];
var topNN = parameters['topnn'];
conv.ask('Current Filter Parameter: ' + filter );
return getTradeFlows(countryCodeExport, countryCodeImport, periodFrom, periodTo, filter, topNN)
.then((rows) => {
if (rows.length === 0) {
conv.ask(`No data has been found for this selection`);
conv.ask(new Suggestions(intentSuggestions));
return;
}
conv.ask(`Here's the filtered Trade Flows and Payment Defaults data`);
conv.ask(new Table({
title: 'Trade Flows/Payment Defaults',
subtitle: 'Euler-Hermes OpenData' + ' filtered by: "' + filter + '"',
image: new Image({
url: 'https://s3-eu-west-1.amazonaws.com/aws-ec2-eu-1-opendatasoft-staticfileset/euler-hermes/logo?tstamp=155013988259',
alt: 'Alt Text',
}),
dividers: true,
columns: ['Sector Group', 'Sector',
{
header: 'Export Amount',
align: 'TRAILING',
},
{
header: 'Claim Amount',
align: 'TRAILING',
},
{
header: 'Traff. Light',
align: 'CENTER',
}],
rows: rows
}));
conv.ask(new Suggestions(intentSuggestions));
agent.add(conv);
});
}
3)闲聊
您的代理可以学习如何支持闲聊,而无需任何额外的开发。默认情况下,它会用预定义的短语进行响应。
点击左边菜单项上的小对话。
现在,您可以使用下面的表格来定制对最受欢迎的请求的响应。

作者图片
4)代理测试
本节解释如何以两种不同方式测试我们的代理:
- 谷歌助手模拟器(网络应用)
- 移动设备上的谷歌助手
4.1。谷歌助手模拟器
现在让我们回到集成菜单,运行谷歌助手模拟器。点击“与我的测试应用程序对话”按钮,然后再次输入以下请求:
- 获取从 20180101 到 20180102 的 FR 到 de 之间的贸易流量
您应该会看到与前面部分相同的结果:

作者图片
显示贸易流列表后,尝试键入以下内容来过滤列表。此短语将仅显示包含文本' motor' sector '的项目,但仅显示前 10 条记录:
- 被电机过滤的前 10 名
您应该会看到以下结果:

作者图片
4.2。移动设备上的谷歌助手
让我们在移动设备上测试我们的代理。(在 Android 或 iOS 上)
首先,请确保您使用的是与 Google Dialogflow 相同的 Google 帐户。
然后,执行以下操作,在您的设备上运行并初始化您的 Google Assistant。这个例子适用于 Android,但也可以在 iOS 上完成。
- 按住 Android 设备的中央按钮,直到谷歌助手出现
- 点击右上角的“账户按钮,选择正确的谷歌账户。如果尚未设置,您可以添加一个新帐户并进行配置。确保主语言设置为'英语'。
- 现在,您可以使用键盘或语音来执行以下操作:
说出或键入“与我的测试应用程序对话”来运行代理
说出或键入以下请求:获取从 20180101 到 20180102 的 FR 到 de 之间的贸易流量
您应该会在移动设备上看到以下结果:

作者图片
最后,说出或键入以下内容,像前面一样过滤列表:
- 按建设筛选出的前 8 名
您应该会在移动设备上看到以下结果:

作者图片
下一篇文章(第三部分)将向您展示如何:
超越谷歌助手布局的限制
在 Flutter 中设计一个多平台应用,能够:
- 安全认证并使用我们的 Dialogflow 代理
- 在移动设备上显示丰富灵活的用户界面
- 管理自定义的负载对话流消息
使用 Google Dialogflow & Flutter 的企业级多平台虚拟助理(第三部分)
第三部分:在 Flutter 中设计一个简单的 iOS 应用程序,与您的 Dialogflow 代理进行通信

图片:作者
参见第一部分和第二部分
1)第二部分概述
上一篇文章描述了如何使用 Google Dialogflow 构建一个简单的聊天机器人,它能够使用 Euler Hermes Open Data web services API 在两个国家之间交换贸易流。表示层依靠 Google Assistant 来显示结果。但是你可能已经注意到谷歌助手是非常有限的,因为你不能自定义组件的布局。例如,表格对象最多可以显示 4–5 列,无需任何水平滚动。
2)本文的总结
这一条的目标如下:
- 超越谷歌助手布局的限制
- 设计一个简单的颤振多平台应用程序,能够:
-
安全认证并使用我们的 Dialogflow 代理
-
在移动设备上显示丰富灵活的用户界面(这里是 iOS,但也可以是 Android 或 Web 应用程序)
-
管理定制的有效负载 Dialogflow 消息,以在 Dialogflow 和我们的 Flutter 应用程序之间交换信息
额外的好处是设计的移动应用程序将是动态的。它可以根据从 NLP 对话后端引擎发送的有效负载消息中包含的业务逻辑做出反应并显示组件。
3)颤振引入和 SDK 安装
Flutter 是 Google 的 UI 工具包,用于从单个代码库构建本地编译的移动、网络和桌面应用。与同等框架相比,本机意味着高性能。
你可以在这里浏览下面的链接来探索 Flutter 的 showcase,它展示了使用该框架的一些特性和例子。
下载和安装 Flutter SDK 的说明可以在这里找到。
您可能还需要安装额外的组件( XCode for MacOS…),这取决于您正在使用的操作系统(MacOS、Windows、Linux)以及您想要用于测试的目标平台(iOS、Android、Web)。
最后,您还需要安装一个开发环境。这里,我们将使用与 Flutters 和 XCode 很好集成的 IntelliJ。下载请到以下链接。
4)重新审视了对话流履行代码
让我们开始稍微改变一下我们以前的文章(NodeJS)中写的 Google dialog flow Cloud fulfillment 函数。这里的想法是改变代码的方式,它将继续与谷歌助理和我们新的颤振 iOS 应用程序。
这意味着我们的代理人的行为会根据打电话的客户而变化。基本上,添加了一些条件来区分 Google Assistant 消息和我们移动应用程序的消息。
这些条件检查了 agent.requestSource 的内容。ACTIONS_ON_GOOGLE' 表示请求来自 Google Assistant。如果 agent.requestSource 为 null ,我们认为它来自我们的移动 Flutter 应用程序。(参见带有“//移动应用程序”注释的代码)
您可能知道,如果您向除 Google Assistant 之外的任何其他应用程序发送 Google Assistant 消息,而 Google Assistant 不是为此设计的(Web、移动应用程序),那么您发送的消息将被 Dialogflow 上的错误拒绝,并且您的组件将不会显示。
那么,还有办法在 web 或移动应用程序中显示一个表或任何其他 Google Assistant 组件吗?答案是肯定的!
在这段代码中,请注意使用 有效载荷 消息的部分,以避免 Dialogflow 出现此错误消息。构建一个表对象,然后将其 JSON 表示存储到有效负载消息中。稍后您将看到,这条消息将作为响应发送回移动应用程序。然后,移动应用程序将解析它,并使用一个新的表格小部件向用户显示响应。
履行源代码:
// .
// Euler Hermes Trade Flows and Payment Handler - Google Assistant
// .
function exportsVSClaimsEHHandler(agent) {
let conv = agent.conv();
var parameters = "";
// Google Assistant Connection
if (agent.requestSource === 'ACTIONS_ON_GOOGLE') {
parameters = conv.parameters;
}
// Mobile Connection
else if (agent.requestSource == null) {
parameters = agent.parameters;
}
console.log("=======> Parameters " + parameters);
var countryCodeExport = parameters['geo-country-code-export'];
var countryCodeImport = parameters['geo-country-code-import'];
var periodFrom = parameters['date-period-from'];
var periodTo = parameters['date-period-to'];
var filter = parameters['filter'];
var topNN = parameters['topnn'];
return getTradeFlows(countryCodeExport, countryCodeImport, periodFrom, periodTo, filter, topNN)
.then((rows) => {
// Google Assistant Connection
if (agent.requestSource === 'ACTIONS_ON_GOOGLE') {
if (rows.length === 0) {
conv.ask(`No data has been found for this selection`);
conv.ask(new Suggestions(intentSuggestions));
return;
}
conv.ask(`Here's the Trade Flows and Payment Defaults data`);
conv.ask(new Table({
title: 'Trade Flows/Payment Defaults',
subtitle: 'Euler-Hermes OpenData',
image: new Image({
url: 'https://s3-eu-west-1.amazonaws.com/aws-ec2-eu-1-opendatasoft-staticfileset/euler-hermes/logo?tstamp=155013988259',
alt: 'Alt Text',
}),
dividers: true,
columns: ['Sector Group', 'Sector',
{
header: 'Export Amount',
align: 'TRAILING',
},
{
header: 'Claim Amount',
align: 'TRAILING',
},
{
header: 'Traff. Light',
align: 'CENTER',
}],
rows: rows
}));
conv.ask(new Suggestions(intentSuggestions));
agent.add(conv);
}
// Mobile Connection
if (agent.requestSource == null) {
if (rows.length === 0) {
agent.add(`No data has been found for this selection`);
agent.add(new Suggestions(intentSuggestions));
return;
}
agent.add(`Here's the Trade Flows and Payment Defaults data`);
let payload = new Table({
title: 'Trade Flows/Payment Defaults',
subtitle: 'Euler-Hermes OpenData',
image: new Image({
url: 'https://s3-eu-west-1.amazonaws.com/aws-ec2-eu-1-opendatasoft-staticfileset/euler-hermes/logo?tstamp=155013988259',
alt: 'Alt Text',
}),
dividers: true,
columns: ['Sector Group', 'Sector',
{
header: 'Export Amount',
align: 'TRAILING',
},
{
header: 'Claim Amount',
align: 'TRAILING',
},
{
header: 'Traff. Light',
align: 'CENTER',
}],
rows: rows
});
var rr = new RichResponse("Trade Flows for mobile", payload);
rr.platform = 'ACTIONS_ON_MOBILE';
rr.type = 'tableCard';
agent.add(new Payload('platform: ACTIONS_ON_MOBILE', rr, {rawPayload: true, sendAsMessage: true}));
//agent.add(new Suggestions(intentSuggestions));
}
});
}
5)设置一个与我们的 Dialogflow 代理通信的简单 iOS 应用程序
在这里,我们将创建一个带有 IntelliJ 的 iOS 应用程序,它与我们的代理进行通信。对于 Android 手机来说,这应该是完全相同的步骤,这是使用 Flutter 的主要好处之一。您可以用相同的代码定位几个平台。
颤振应用设置
一旦设置好您的 Flutter 和 IntelliJ 环境,请创建一个新的 Flutter 应用程序,并根据您的需要命名,例如:business _ API _ chatbot。
然后,打开 iOS 模拟器,创建一个新的 iOS 12 设备。
当您准备好运行应用程序时,您的屏幕应该是这样的:

图片:Autor
API 密钥设置
首先,进入谷歌云平台控制台,在左侧菜单中选择“ API 和服务,然后选择“凭证”。

图片:Autor
创建并下载 API 密匙,它将允许您从 Flutter 应用程序与您的 Dialogflow 代理进行通信。
将密钥命名为' credentials.json ,并将密钥保存到您之前创建的 Flutter 应用程序的' assets 文件夹中。

编辑' pubspec.yaml '文件,确保' asset '条目设置如下:

6)向我们的 iOS 应用程序添加新的小部件,以显示从我们的 Dialogflow 代理发送的 Euler Hermes 数据
最终的应用程序实际上能够显示几个小部件:简单消息、基本卡片、转盘和数据表。但是在这里,我们将只关注将在以下部分创建的简单消息和数据表。
首先,我们需要创建我们的移动消息结构,它应该反映从我们的 Dialogflow 代理发送的有效负载消息结构。
基本上,包含 Euler Hermes 数据的有效负载消息由列标题和数据行组成:
- 集团部门
- 部门
- 两国间的出口量
- 两个国家之间的索赔金额
- 交通灯(绿色、琥珀色、红色)
TableCard 类将存储从我们的 Dialogflow 代理接收的有效负载消息提供的所有信息。
请注意, TypeMobileMessage 类确保只执行有效载荷消息类型。
现在,请创建一个新的 Dart 文件,命名如下: mobile_message.dart
class TableColumn {
String colHeader;
TableColumn(Map column){
this.colHeader = column['header'];
}
}
class TableRow {
String sectorGroup;
String sector;
String exportAmount;
String claimAmount;
String trafficLight;
TableRow(Map row){
this.sectorGroup = row['cells'][0]['text'];
this.sector = row['cells'][1]['text'];
this.exportAmount = row['cells'][2]['text'];
this.claimAmount = row['cells'][3]['text'];
this.trafficLight = row['cells'][4]['text'];
}
}
class TableCard {
List<TableColumn> cols=[];
List<TableRow> rows=[];
TableCard(Map response){
List<dynamic> listCols = response['payload']['items'][1]['tableCard']['columnProperties'];
for(var i=0;i<listCols.length;i++){
cols.add(new TableColumn(listCols[i]));
}
List<dynamic> listRows = response['payload']['items'][1]['tableCard']['rows'];
for(var i=0;i<listRows.length;i++){
rows.add(new TableRow(listRows[i]));
}
}
}
class TypeMobileMessage{
String platform;
String type;
TypeMobileMessage(Map message){
if (message.containsKey("payload")) {
this.platform = message['payload']['platform'];
this.type = message['payload']['type'];
}
}
}
现在让我们看看如何在我们的移动应用程序中显示一条简单的文本消息。下面是一个简单的文本小部件的代码,当我们的 Dialogflow 代理向移动应用程序发回一个回答时,将显示这个小部件。一个谷歌助理图标将与文本消息一起显示,让用户可以轻松识别答案来自我们的 Dialogflow 代理。
请创建新的 Dart 文件,并将其命名为: simple_message.dart
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class SimpleMessage extends StatelessWidget {
SimpleMessage({this.text, this.name, this.type});
final String text;
final String name;
final bool type;
List<Widget> otherMessage(context) {
return <Widget>[
new Container(
margin: const EdgeInsets.only(right: 16.0),
child: new CircleAvatar(
child: new Image.asset("img/googleasistant.png"),
backgroundColor: Colors.*transparent*,
),
),
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
margin: const EdgeInsets.only(top: 5.0),
decoration: BoxDecoration(
color: Colors.*white*,
borderRadius: BorderRadius.all(Radius.circular(20.0))),
child: Padding(
padding: EdgeInsets.only(
top: 10.0, bottom: 10.0, left: 10.0, right: 10.0),
child: new Text(text, style: TextStyle(color: Colors.*black*),),
),
),
],
),
),
];
}
@override
Widget build(BuildContext context) {
return new Container(
margin: const EdgeInsets.symmetric(vertical: 10.0),
child: new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: this.type ? myMessage(context) : otherMessage(context),
),
);
}
}
使用新的文本小部件后,您的应用程序应该是这样的:

图片:Autor
最后一步是创建一个新的 表 小部件,它将包含我们来自 Euler Hermes 的数据。请使用以下代码创建一个新的 Dart 文件,并将其命名为: tableHorizCard.dart
import 'package:business_api_chatbot/mobile_message.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class TableHorizCardWidget extends StatelessWidget {
TableHorizCardWidget({this.tableCard});
final TableCard tableCard;
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return Container(
child: Scaffold(
//backgroundColor: Colors.black54,
//appBar: AppBar(),
body: Center(
child: Container(
color: Colors.*white*,
height: 500,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
columns:
<DataColumn>[
DataColumn(
label: Text(tableCard.cols[0].colHeader, style: TextStyle(fontWeight: FontWeight.*bold*)),
),
DataColumn(
label: Text(tableCard.cols[1].colHeader, style: TextStyle(fontWeight: FontWeight.*bold*)),
),
DataColumn(
label: Text(tableCard.cols[2].colHeader, style: TextStyle(fontWeight: FontWeight.*bold*)), numeric:true,
),
DataColumn(
label: Text(tableCard.cols[3].colHeader, style: TextStyle(fontWeight: FontWeight.*bold*)), numeric:true,
),
DataColumn(
label: Text(tableCard.cols[4].colHeader, style: TextStyle(fontWeight: FontWeight.*bold*)),
),
],
rows: <DataRow>[
for (var item in tableCard.rows)
DataRow(
cells: <DataCell>[
DataCell(Text(item.sectorGroup)),
DataCell(Text(item.sector)),
DataCell(Text(item.exportAmount, textAlign:TextAlign.right,)),
DataCell(Text(item.claimAmount, textAlign:TextAlign.right)),
DataCell(Text(
item.trafficLight, textAlign: TextAlign.center,
style: TextStyle(backgroundColor: item.trafficLight == "R" ? Colors.*red* : Colors.*green*))),
],
),
],
),
),
),
),
),
),
height: MediaQuery
.*of*(context)
.size
.height,
);
}
}
使用这个新的表小部件,您的应用程序应该是这样的。对于我们的文章来说,这个类是非常简单的,但是只需很小的努力就可以有一个复杂得多的布局。例如,固定列显示机制可以在水平滚动中实现。
请注意,您的移动应用程序的行为类似于我们在上一篇文章中创建的 Google Assistant 应用程序。但是,有了这个移动应用程序,有无限的可能性来创建新的丰富的窗口小部件与摆动,没有更多的布局限制!

图片:Autor
企业机器学习——为什么构建和训练“真实世界”模型很难
行业笔记,企业机器学习系列
用通俗易懂的英语为企业中的机器学习项目的生命周期、所涉及的角色以及构建模型的挑战提供了一个简明的指南

什么是企业 ML?
交付一个为你的公司提供真正商业价值的机器学习(ML)应用需要什么?
一旦你做到了这一点,并且证明了 ML 可以给公司带来实质性的好处,你如何将这一努力扩展到额外的用例,并真正开始实现 ML 的承诺呢?
然后,您如何在整个组织中扩展 ML,并简化 ML 开发和交付过程,以标准化 ML 计划、共享和重用工作并快速迭代?
一些世界领先的科技公司采用的最佳实践是什么?

通过企业中的 ML 应用交付商业价值(图片由作者提供)
通过一系列的文章,我的目标是探索这些迷人的问题,并理解沿途的挑战和学习。
- 一个人如何在“真实世界”中训练一个 ML 模型,这与“在实验室”构建一个 ML 原型有什么不同?
- 您如何将该模型投入生产,并使其月复一月地保持最佳性能?
- 处于这一趋势前沿的公司已经部署了哪些基础设施、系统架构和工具?
- 您如何构建数据管道来从您公司收集的大量数据中提取价值,并将其用于您的 ML 和分析用例?
在第一篇文章中,我们将深入探讨构建和训练 ML 模型的关键步骤,因为这是这个过程的核心。
为了做到这一点,让我们首先设置背景,并获得一个组织的整体 ML 旅程的高层次概述。
在 ML 旅程中走向成熟
假设一家公司已经收集了一组丰富的数据,并希望利用机器学习来显著改善其客户的体验或以主要方式影响其业务运营。
通常,大多数能够成功利用 ML 的公司都会经历不同的成熟阶段。

一个组织会经历 ML 成熟的各个阶段(图片由作者提供)
- 开始—识别问题并定义业务目标。开始 R&D 活动,开发一个 ML 模型。
- 飞行员——第一个受训的模型。生产中的应用程序就绪。
- 早期阶段——生产一两年的少数型号。
- 中间阶段——跨多个部门的多种业务场景中的多个生产模型。
- 高级——敏捷的 ML 应用程序开发、标准化的工具以及快速实验和交付的流程。
但这是一条艰难的道路。许多公司在第一阶段就陷入困境,无法从他们的 ML 投资中获取切实的商业价值。
既然我们已经在高层次上讨论了长期的旅程,那么让我们沿着这条路径缩小到单个 ML 项目,并查看端到端涉及的步骤。
ML 应用生命周期和角色
交付一个 ML 应用程序包括几个任务。在过去的几年中,行业中出现了许多专门的角色来执行这些任务。
请注意,这一领域仍在发展,这一术语并不标准。如果你去几家不同的公司,问他们这些任务和角色,你可能会得到稍微不同的解释。这些角色之间的界限有些模糊。在某些情况下,同一个人可能会履行所有这些职责。
然而,其中一些概念开始具体化。所以对我们来说,大致了解一下这个过程还是很有用的。

ML 应用生命周期(图片由作者提供)
- 该流程从数据科学家构建和训练模型开始,通常使用训练数据的快照。
- 然后,数据工程师设置数据管道,从公司的分析商店中获取训练数据。管道还可能从运营数据源填充分析存储。
- 然后,经过训练的模型必须与最终用户使用的业务应用程序集成。它获取模型用来进行预测的输入数据。预测的结果然后被呈现给最终用户。这是由应用程序开发人员或产品工程师完成的。
- ML 工程师将 ML 应用程序部署到生产中。
- 最后,ML Ops 负责保持应用程序在生产环境中运行并监控其性能。
正如我们刚刚看到的,应用程序生命周期的第一阶段是构建和训练 ML 模型。这通常是项目中最“迷人”和最具技术挑战性的部分。让我们放大来看看它涉及到什么。
培训一个“真实世界”的 ML 模型和一个“演示”ML 项目有什么不同?
互联网上不乏资源、教程、在线课程和项目,涵盖了为一系列应用构建机器学习或深度学习模型的每个可能的技术方面。然而,它们中的大部分涵盖了在一个非常受控的“演示”环境中构建 ML 模型。
这与你在“真实世界”中会遇到的有什么不同?到目前为止,最大的区别与访问带标签的数据集有关。演示项目总是从已经为您准备好的精心策划的数据集开始。数据已经被清理和系统标记。因为数据集中的字段已经被选择并确定了范围,所以这个问题被巧妙地限制住了。
另一方面,在一个真实的项目中,你从一张白纸开始。准备数据集成为项目中要求最高的方面之一。你必须解决一些棘手的问题:
- 有哪些数据来源?
- 我们如何查询和提取数据?
- 它们包含哪些字段?
- 我们将使用哪些数据特征?
- 我们如何获得标签?
- 数据格式是否正确?
- 是否存在缺失值或垃圾值?
- 我们应该使用什么样的数据切片/数据段?
- 我需要多少训练数据?
- 我如何扩充我的数据?
第二,在一个演示项目中,重点通常是挤出你的模型的最后一滴准确性,或者得到最先进的结果。在许多在线比赛中,79.0345 和 79.0312 之间的指标得分差异可能意味着排行榜上的数百个排名。
在现实世界的项目中,多花几周时间来获得一个或两个百分点的度量改进,可能不值得。您的模型的准确性可能只是整体业务成果中的一个因素。更重要的是快速交付一个工作解决方案,让客户有明显的改进,获得反馈并快速迭代。
模型构建和培训工作流程
假设要解决的问题和业务目标是明确的,并且您对如何解决它有一个初步的假设。通常,创建 ML 模型涉及几个步骤,由数据科学家或数据科学家团队执行。

ML 模型工作流程(图片由作者提供)
- 数据发现:您可以从浏览数据源开始,发现您想要使用的数据集。重要的是,您还需要确定哪些数据将用作目标标签。
- 数据清理:数据可能比较乱,需要验证和清理。可能存在缺失值或无效值、异常值、重复值等。一些字段可能有无用的值,例如,像“客户流失原因”这样的字段有很多值,只是简单地表示“未知”。某些值的格式可能不正确,例如数字、日期。如果您正在处理图像,您可能会看到模糊的图片、不同大小和分辨率的图像、光线不足或从奇怪的角度拍摄的照片。
- 探索性数据分析(EDA) :查看数据分布,以识别字段之间的模式和关系。你可以识别季节性趋势或将数据分成相关的部分等。
- 特征工程:通过丰富一些字段,执行聚合或汇总,或者通过组合多个字段进行计算,从而衍生出新的特征。例如,您可以使用日期字段提取自月初或年初以来的天数的新要素,或者提取是否是假日的新要素。
- 特征选择:识别对模型预测结果最有用的特征。移除对模型没有任何价值的特征。
- 模型选择:你可能会尝试几种不同的机器学习算法或深度学习架构,以找到性能最佳的一种。
- 超参数调整:对于每个模型,都有几个超参数值需要优化,例如神经网络中隐藏层的数量和每层的大小。
- 模型训练:挑选一个模型,选择一些数据特征,尝试一些超参数,训练模型。
- 模型评估:对照验证数据集测试模型。跟踪和比较每个模型的指标。
- 推理:在确定了一个有希望的潜在模型(以及特征和超参数)之后,构建逻辑来对看不见的数据进行预测。
- 重复,重复,再重复:改变一些东西,尝试一个不同的想法,并不断地这样做很多很多次,直到你找到一个效果很好的东西。
您必须保持细致的记录,以跟踪您的每一次试验,所使用的数据特征和超参数,以及所获得的指标。这将有助于您回过头来为进一步的研究确定有前途的模型,并能够重新运行测试和再现结果。
模型构建挑战
建立一个 ML 模型是困难的。这项工作是非常研究性和反复性的,需要大量的试验和错误。
不像大多数软件开发项目,你知道如何解决手边的问题,ML 项目有许多不确定性和未知性。一开始,你可能不知道解决方案是什么,甚至不知道它是否可行,也不知道可能需要多长时间。估计和计划时间表需要相当多的猜测。
通常,ML 模型是黑箱。当一个模型未能产生预期的结果时,您可能不确定它失败的确切原因。在许多情况下,解决方法是简单地提出另一个假设或尝试不同的东西,并希望它能提高性能。
终于,模型做好了!现在艰苦的工作真正开始了…
经过几周或几个月的工作,您最终解决了问题,并且拥有了一个在开发环境中运行良好的模型。
然而,正如我们将在下一阶段看到的,这只是到达目的地的很小一部分。更大的挑战和陷阱还在前面。
模型开发通常在 Jupyter 笔记本上完成。很可能使用 CSV 或 Excel 文件中数据集的静态快照来训练模型。培训在开发人员的本地笔记本电脑上运行,或者在云中的虚拟机上运行。
换句话说,模型的开发是相当独立的,与公司的应用程序和数据管道相隔离。集成模型并将其部署到生产环境中的艰苦工作即将开始…
结论
正如我们刚刚看到的,企业 ML 是一个旅程,像所有的旅程一样,它始于第一步,即建立您的 ML 模型。从很多方面来说,这一部分既技术复杂又令人兴奋。
然而,并不是这个阶段,而是下一个阶段,往往会绊倒许多项目,使它们无法重见天日。现在我们已经对“模型构建”阶段有了一个概念,我们准备在下一篇文章中看看“将模型投入生产”阶段。
最后,如果你喜欢这篇文章,你可能也会喜欢我关于变形金刚、音频深度学习和地理定位机器学习的其他系列。
企业 ML——为什么将你的模型投入生产比构建它需要更长的时间
企业机器学习系列
关于模型部署的复杂性以及与企业应用程序和数据管道集成的简明指南。数据科学家、数据工程师、ML 工程师和 ML Ops 用简单的英语做什么。

里卡多·罗查在 Unsplash 上的照片
比方说,我们已经确定了我们公司的一个高影响业务问题,建立了一个 ML(机器学习)模型来解决它,训练它,并对预测结果感到满意。这是一个需要大量研究和实验才能解决的难题。因此,我们很高兴终于能够使用该模型来解决我们用户的问题!
然而,我们很快就会发现,构建模型本身只是冰山一角。将这种模型投入生产的大部分艰苦工作仍在我们前面。我发现第二阶段可能会占用项目 90%的时间和精力。
那么这个阶段由什么组成呢?为什么要花这么多时间?这是本文的重点。
在几篇文章中,我的目标是探索一个组织的 ML 旅程的各个方面,从部署它的第一个 ML 模型到建立一个敏捷开发和部署过程,以快速试验和交付 ML 项目。如果你感兴趣,这是我关于这个话题的另一篇文章:
- 建立和训练“真实世界”模型 ( 企业中机器学习项目的工作流程,以及建立模型的挑战)
为了理解第二阶段需要做什么,让我们先看看在第一阶段结束时交付了什么。
模型构建和培训阶段交付了什么?
模型通常由数据科学团队构建和训练。准备好之后,我们会在 Jupyter 笔记本上记下模型代码和经过训练的砝码。
- 它通常使用数据集的静态快照(可能在 CSV 或 Excel 文件中)进行训练。
- 快照可能是整个数据集的子集。
- 培训在开发人员的本地笔记本电脑上运行,或者在云中的虚拟机上运行
换句话说,模型的开发是相当独立的,与公司的应用程序和数据管道相隔离。
“生产”是什么意思?
当一个模型投入生产时,它以两种模式运行:
- 实时推理-对新输入数据执行在线预测,一次一个样本
- 重新训练-用于每晚或每周使用当前刷新的数据集对模型进行离线重新训练
这两种模式涉及的要求和任务非常不同。这意味着模型被放入两个生产环境中:
- 用于执行推理和服务预测的服务环境
- 再培训的培训环境

生产中的实时推理和再培训(图片由作者提供)
实时推理是大多数人在想到“生产”时会想到的。但是也有很多用例是做批量推理而不是实时的。
- 批量推断-每晚或每周对完整数据集执行离线预测

生产中的批量推理和再培训(图片由作者提供)
对于这些模式中的每一种,现在都需要将模型与公司的生产系统(业务应用程序、数据管道和部署基础设施)集成在一起。让我们打开每个领域的包装,看看它们需要什么。
我们将从关注实时推理开始,之后,我们将检查批量案例(再训练和批量推理)。出现的一些复杂性是 ML 特有的,但是许多是标准的软件工程挑战。
推理—应用程序集成
模型通常不是一个独立的实体。它是面向最终用户的商业应用程序的一部分,例如电子商务网站的推荐模型。该模型需要与应用程序的交互流和业务逻辑相集成。
应用程序可能通过 UI 从最终用户那里获得输入,并将其传递给模型。或者,它可能从 API 端点或流数据系统获得输入。例如,批准信用卡交易的欺诈检测算法可能会处理来自 Kafka 主题的交易输入。
类似地,模型的输出被应用程序使用。它可能会在 UI 中返回给用户,或者应用程序可能会使用模型的预测来做出一些决策,作为其业务逻辑的一部分。
需要建立模型和应用程序之间的进程间通信。例如,我们可以将模型部署为通过 API 调用访问的它自己的服务。或者,如果应用程序也是用相同的编程语言(例如 Python)编写的,它可以只对模型代码进行本地函数调用。
这项工作通常由与数据科学家密切合作的应用程序开发人员来完成。与软件开发项目中模块之间的任何集成一样,这需要协作来确保关于来回流动的数据的格式和语义的假设在双方都是一致的。我们都知道可能会突然出现的各种问题。例如,如果模型期望一个数字“数量”字段为非负,应用程序会在将它传递给模型之前进行验证吗?或者模型应该执行检查吗?应用程序以什么格式传递日期,模型是否期望相同的格式?

实时推理生命周期(图片由作者提供)
推断—数据集成
该模型不再依赖于包含进行预测所需的所有要素的静态数据集。它需要从组织的数据存储中获取“实时”数据。
这些功能可能位于事务性数据源(如 SQL 或 NoSQL 数据库)中,也可能位于半结构化或非结构化数据集(如日志文件或文本文档)中。也许有些功能是通过调用 API 来获取的,无论是内部微服务或应用(如 SAP)还是外部第三方端点。
如果这些数据的位置或格式不正确,可能需要构建一些 ETL(提取、转换、加载)作业来将数据预取到应用程序将使用的存储中。
处理所有的数据集成问题可能是一项艰巨的任务。例如:
- 访问需求 —如何连接到每个数据源,其安全和访问控制策略是什么?
- 处理错误——如果请求超时,或者系统停机了怎么办?
- 匹配延迟 —对数据源的查询需要多长时间,而我们需要多快响应用户?
- 敏感数据 —是否存在必须屏蔽或匿名的个人身份信息。
- 解密 —在模型可以使用数据之前,数据需要被解密吗?
- 国际化 —模型能处理必要的字符编码和数字/日期格式吗?
- 还有更多…
这个工具是由数据工程师构建的。对于这一阶段,他们也将与数据科学家进行互动,以确保假设是一致的,并且集成进展顺利。模型所做的数据清理和预处理是否足够了,或者是否需要构建更多的转换?
推理-部署
现在是时候将模型部署到生产环境中了。任何软件部署都需要考虑以下所有因素:
- 模特托管——在手机 app 上?在内部数据中心还是在云上?在嵌入式设备上?
- 模型封装 —它需要哪些依赖软件和 ML 库?这些库通常不同于常规的应用程序库。
- 协同定位 —模型会与应用程序协同定位吗?还是作为外部服务?
- 车型配置设置 —如何维护和更新?
- 所需的系统资源 — CPU、RAM、磁盘,最重要的是 GPU,因为这可能需要专门的硬件。
- 非功能需求 —请求流量的容量和吞吐量?预期的响应时间和延迟是多少?
- 自动扩展 —需要什么样的基础设施来支持它?
- 集装箱化——需要打包进码头集装箱吗?容器编排和资源调度将如何完成?
- 安全需求 —存储凭证,管理私钥以访问数据?
- 云服务 —如果部署到云中,是否需要与任何云服务集成,例如(亚马逊网络服务)AWS S3?AWS 访问控制权限呢?
- 自动化部署工具 —供应、部署和配置基础设施并安装软件。
- CI/CD —自动化单元或集成测试,以与组织的 CI/CD 管道集成。
ML 工程师负责实现这个阶段,并将应用程序部署到生产环境中。最后,您能够将应用程序呈现在客户面前,这是一个重要的里程碑!
然而,现在还不是高枕无忧的时候😃。现在开始监控应用程序的 ML Ops 任务,以确保它在生产中继续以最佳状态运行。
推理——监控
监控的目标是检查您的模型在生产中是否继续使用实时客户数据做出正确的预测,就像在开发过程中一样。很有可能你的指标没有那么好。
此外,您需要像监控任何应用程序一样监控所有标准 DevOps 应用程序指标—延迟、响应时间、吞吐量以及系统指标,如 CPU 利用率、RAM 等。您将运行正常的健康检查,以确保应用程序的正常运行时间和稳定性。
同样重要的是,监控需要是一个持续的过程,因为您的模型的评估指标很有可能会随着时间而恶化。将您的评估指标与过去的指标进行比较,以检查是否偏离了历史趋势。
这可能是因为数据漂移。
推断—数据验证
随着时间的推移,您的数据将会发展和变化-可能会添加新的数据源,收集新的特征值,新的客户会输入与以前不同的值的数据。这意味着您的数据分布可能会改变。
所以用当前数据验证你的模型需要一个持续的活动。仅查看全球数据集的评估指标是不够的。您还应该评估数据的不同部分和片段的指标。很有可能随着您业务的发展以及客户人口统计、偏好和行为的变化,您的数据段也会发生变化。
最初构建模型时所做的数据假设可能不再成立。考虑到这一点,您的模型也需要发展。模型所做的数据清理和预处理可能也需要更新。
这将我们带到第二种生产模式——定期进行批量再训练,以便模型继续从新数据中学习。让我们从开发模型开始,看看在生产中设置批量再培训所需的任务。

再培训生命周期(图片由作者提供)
再培训—数据集成
当我们讨论用于推断的数据集成时,它涉及到获取最新“实时”数据的单个样本。另一方面,在重新训练期间,我们需要获取完整的历史数据集。此外,这种再培训以批处理方式进行,比如每晚或每周。
历史并不一定意味着“旧的和过时的”数据,例如,它可以包括直到昨天收集的所有数据。
该数据集通常位于组织的分析存储中,如数据仓库或数据湖。如果那里没有一些数据,您可能需要构建额外的 ETL 作业,以所需的格式将数据传输到仓库中。
再培训—应用集成
因为我们只是对模型本身进行重新训练,所以不涉及整个应用程序。因此不需要任何应用程序集成工作。
再培训—部署
再培训可能会使用大量数据,可能远远超过开发期间使用的数据。
你需要弄清楚训练模型所需的硬件基础设施——它的 GPU 和 RAM 要求是什么?由于训练需要在合理的时间内完成,因此需要分布在集群中的许多节点上,以便训练并行进行。每个节点将需要由资源调度器提供和管理,以便硬件资源可以有效地分配给每个训练过程。
该设置还需要确保这些大数据量可以有效地传输到正在执行训练的所有节点。
在我们结束之前,让我们看一下我们的第三个生产用例——批量推理场景。
批量推断
通常,推理不必一次为单个数据项“实时”运行。在许多用例中,它可以作为批处理作业运行,其中大量数据样本的输出结果是预先计算和缓存的。
然后,可以根据用例以不同的方式使用预先计算的结果。例如。
- 它们可以存储在数据仓库中,供业务分析师进行报告或交互式分析。
- 当用户下次登录时,应用程序可以将它们缓存并显示给用户。
- 或者它们可以被缓存并被另一个下游应用程序用作输入要素。
例如,预测客户流失可能性的模型(即他们停止向你购买)可以每周或每天晚上运行。然后,这些结果可用于为所有被归类为高风险的客户开展特别促销活动。或者,当他们下次访问该网站时,可以向他们提供一个报价。
批处理推理模型可以作为应用程序网络的工作流的一部分进行部署。每个应用程序在其依赖关系完成后执行。
实时推理中出现的许多相同的应用程序和数据集成问题也适用于此。另一方面,批量推理没有相同的响应时间和延迟需求。但是,它确实有很高的吞吐量要求,因为它要处理巨大的数据量。
结论
正如我们刚刚看到的,将一个模型投入生产有许多挑战和大量的工作。即使在数据科学家准备好一个训练有素的模型之后,一个组织中还有许多角色聚集在一起,最终将它带给您的客户,并让它月复一月地运行。只有这样,组织才能真正获得利用机器学习的好处。
我们现在已经看到了构建和训练真实世界模型,然后将其投入生产的复杂性。在下一篇文章中,我们将看看领先的技术公司如何解决这些问题,以快速顺利地生产 ML 应用程序。
最后,如果你喜欢这篇文章,你可能也会喜欢我关于变形金刚、音频深度学习和地理定位机器学习的其他系列。
ML 的实体嵌入
试试看:它们比一键编码执行得更好。

连续地表示分类数据,就像这幅热带草原的连续视图。【来源】
这篇文章是在机器学习算法中使用嵌入来表示分类变量的指南。我看过其他文章讨论这个方法,但是没有一篇真正向展示如何用代码来做。所以我决定用我自己的帖子来填补这个空白。
如果你对代码感兴趣,可以直接跳到实现部分。还有一个附随笔记本,管道全部铺设完毕。
为什么是实体嵌入?
简而言之,它们比一键编码性能更好,因为它们以紧凑和连续的方式表示分类变量。
从神经网络到 k-最近邻和树集成,我们可以用嵌入代替一键编码来表示几乎任何建模算法中的分类变量。尽管一键编码忽略了特征值之间的信息关系,但实体嵌入可以在嵌入空间中将相关值更紧密地映射在一起,从而揭示数据的内在连续性( Guo 2016 )。
例如,当使用单词嵌入(本质上与实体嵌入相同)来表示每个类别时,一个完美的嵌入集将保持这样的关系: king - queen =丈夫-妻子 。
分类变量中的值实际上总是表现出某种关系。另一个例子:如果用实体嵌入来表示颜色,“棕色”和“黑色”在一个元素中具有相似的值,表示阴影,而在另一个元素中具有不同的值,例如,一个元素表示原色的组成。这种表示让模型了解每个变量之间的相互关系,从而简化学习过程并提高性能。
利用这些优势实际上很简单,只需用嵌入矩阵(取自用这些类别训练的神经网络)替换独热矩阵。

Rossman 数据集上不使用和使用实体嵌入来表示分类变量的算法的性能(平均绝对百分比误差—越低越好)( Guo 2016 )。
说够了;让我们进入实现。
履行
我将使用 PyTorch、fastai 和 sklearn。管道有三个步骤:
1.用嵌入来训练神经网络
# import modules, read data, and define options
from fastai.tabular.all import *
df = pd.read_csv('/train.csv', low_memory=False)
cont,cat = cont_cat_split(df_nn, max_card=9000, dep_var='target')
procs = [Categorify, Normalize]
splits = RandomSplitter()(df)
device =torch.device('cuda' if torch.cuda.is_available() else 'cpu')# feed the data into the Learner and train
to_nn = TabularPandas(nn, procs, cat, cont,
splits=splits, y_names='target')
dls = to_nn.dataloaders(1024, device = device)
learn = tabular_learner(dls, layers=[500,250], n_out=1)
learn.fit_one_cycle(12, 3e-3)
最终结果是一个具有嵌入层的神经网络,可用于表示每个分类变量。通常嵌入的大小是一个要指定的超参数,但是 fast.ai 通过基于变量的基数自动推断适当的嵌入大小来简化这一过程。
这一步可以在纯 PyTorch 或者 TensorFlow 中完成;如果您选择这样做,请确保修改后续代码的适当部分。
2.用嵌入向量替换每个分类值
def embed_features(learner, xs):
"""
learner: fastai Learner used to train the neural net
xs: DataFrame containing input variables. Categorical values are defined by their rank.
::return:: copy of `xs` with embeddings replacing each categorical variable
"""
xs = xs.copy()
for i,col in enumerate(learn.dls.cat_names):
# get matrix containing each row's embedding vector
emb = learn.model.embeds[i]
emb_data = emb(tensor(xs[col], dtype=torch.int64))
emb_names = [f'{col}_{j}' for j in range(emb_data.shape[1])]
# join the embedded category and drop the old feature column
feat_df = pd.DataFrame(data=emb_data, index=xs.index,
columns=emb_names)
xs = xs.drop(col, axis=1)
xs = xs.join(feat_df)
return xs
这个函数将每个分类列(一个大小为 n_rows 的向量)扩展成一个形状为的嵌入矩阵(n_rows,embedding_dim)。现在我们用它来嵌入数据的分类列。
emb_xs = embed_features(learn, to.train.xs)
emb_valid_xs = embed_features(learn, to.valid.xs)
在不亲自试验的情况下遵循代码是一项艰巨的任务,因此我在示例数据集的之前提供了,在之后提供了:

应用实体嵌入方法之前的数据集。

数据集 在 之后应用实体嵌入方法。
3.在嵌入数据上训练 ML 算法
大部分繁重的工作已经完成;我们现在可以以标准方式训练我们的机器学习算法,但将嵌入的数据作为输入传递。
下面是一个训练随机森林的管道示例:
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_auc_score
rf = RandomForestClassifier(n_estimators=40, max_samples=100_000,
max_features=.5, min_samples_leaf=5)
rf = rf.fit(emb_xs, y)
roc_auc_score(rf.predict(emb_valid_xs), to.valid.y)
如果您希望使用不同的算法,只需将RandomForestClassifier替换为GradientBoostingClassifier或任何其他算法,您应该会看到相对于一次性编码方法的性能提升。
特征选择
还有最后一点要考虑。现在,我们已经将按值排序的列扩展为嵌入矩阵,从而有了更多的特性。树集合会慢得多,因为有更多的特征要循环通过,并且相应地有更多的分裂要评估。使用一键编码,决策树可以快速训练,将样本分成 1 或 0 的组。
然而,在嵌入式表示中,每一列都包含一个连续的区间 (0,1) 。因此,树必须将区间分成几个区间,并分别评估每个区间。因此,对于决策树来说,实体嵌入表示比一次性编码计算量大得多,这意味着训练和推理需要更长的时间。
我们可以改善这个问题:选择最显著的特征,只在这些特征上训练。
Sklearn tree ensembles 可以使用模型的feature_importances_属性自动计算特征重要性,该属性返回对应于每个特征列的百分比列表。为了计算这些百分比,sklearn 遍历每棵树上的每个分裂,对使用某个特征的每个分裂的信息增益求和,并使用该累积的信息增益作为贡献的代理。
m = RandomForestClassifier().fit(emb_xs, y)
fi = pd.DataFrame({'cols':df.columns, 'imp':m.feature_importances_})
emb_xs_filt = emb_xs.loc[fi['imp'] > .002]
代码简单明了:拟合一个模型,定义一个将每个特性与它们的特性重要性联系起来的DataFrame,删除任何特性重要性等于或低于. 002 的列(一个要优化的超参数)。
我们的精简数据集在一小部分维度上提供了与全尺寸数据集一样好的性能。然而,更少的列意味着更不容易过度拟合,并且对于新数据的变化更健壮,所以即使时间限制不是问题,应用特征选择也是一个好主意。
如果在使用树集合时时间紧迫,并且无法使用实体嵌入方法,那么就像表示任何顺序变量一样表示分类变量。序数风格的排序表示更快,至少和一键编码一样好( Wright 2019 )。
结论
对于一键编码,有更好的替代方法。使用来自经过训练的神经网络的嵌入来表示分类变量优于机器学习算法中的一键编码方法。实体嵌入可以以连续的方式表示分类变量,保留不同数据值之间的关系,从而有助于模型的训练。如果需要可解释性,性能更好的机器学习模型可以用于集成或作为神经网络的替代品。
参考
- 郭,程等。范畴变量的实体嵌入。 arXiv:1604.06737
- 莱特 MN,柯尼希 IR。2019.“随机森林中分类预测因子的分裂”。 PeerJ。https://doi.org/10.7717/peerj.6339。
- 霍华德杰里米。针对拥有 Fastai 和 PyTorch 的程序员的深度学习:没有博士学位的 ai 应用。https://www . Amazon . com/Deep-Learning-Coders-fastai-py torch/DP/1492045527
感谢 fastai 论坛和 fastai 书籍【3】提供了学习资料的资源。
现有抽象文本摘要模型的问题——甚至是 SOTA
NLP 研究论文摘要

在这篇博客中,我尝试根据我的理解总结了论文 抽象文本摘要的实体级事实一致性 。请随时评论你的想法!
问题陈述
众所周知,现有的抽象文本摘要模型倾向于产生虚假信息。现在,这可能发生在实体级别(生成额外的实体)或实体关系级别(实体出现的上下文被错误地生成)。本文仅在实体级量化事实一致性,而将关系级一致性留给未来的工作。他们提出了一种度量标准来量化模型产生的这种幻觉,并提出了一系列措施和训练方案,这些措施和方案可以帮助模型更好地执行并生成实体级别的事实上正确的摘要。
提议的方法
首先,本文引入了 3 个度量标准来量化生成的摘要中实体级的一致性。其细节在下面讨论—
韵律学
- Precision-source—prec(s))量化幻觉程度模型有 w.r.t 源文件。数学上定义为 —
prec(s) = N (h ∩ s)/N (h)
这里,N(h)和 N(s)分别指生成的摘要和源文档中的命名实体集合。他们使用 SpaCy 的 NER 模型来提取这些实体。prec(s)的低值意味着幻觉严重,因为在这种情况下 N(h)会很高,因为生成的概要中的唯一实体的计数会很高。
- 精确目标— 精确目标(prec(t)) 与 prec(s)做相同的事情,但 w.r.t .为实际汇总。这个想法是计算模型在假设汇总中生成多少个实体也是实际汇总的一部分。数学上,它被给定为—
prec(t) = N (h ∩ t)/N (h)
这里,N(h)和 N(t)分别指生成/假设和实际摘要中的命名实体集合。
- Recall-target—下的Recall-target(Recall(t))其思路是计算实际摘要中有多少实体没有出现在模型生成的假设摘要中。数学上,它被给定为—
回忆(t) = N (h∩t)/N (t)
这里,N(h)和 N(t)分别指生成/假设和实际摘要中的命名实体集合。为了得到一个可量化的数字,他们将 prec(t)和 prec(s)合并,并表示为 F1 分数。数学上,它被给定为—
F1 = 2 预测召回次数(t)/(预测召回次数+召回次数)
基于实体的数据过滤
作者假设幻觉问题很大程度上嵌入了训练数据本身。因此,为了验证这一点,他们计算了 3 个流行数据集(即 Newsroom、CNN/DM 和 XSUM)的 prec(s)分数。

来自源的实际摘要|图像中命名实体和 prec 的平均数量(%)
正如我们在上表中看到的,新闻编辑室和 CNN/DM 数据集的幻觉率接近 10%,XSUM 的幻觉率接近 20%。因此,如果看到这些都是非常重要的数字,那么模型也会学习这些模式,并且注定会在生成摘要时产生幻觉。
因此,作者提出了一种数据过滤方法,其中,如果摘要中的任何实体在源文档中不存在,则从摘要中删除该特定句子。万一,如果基本事实摘要只包含一个句子,并且它需要被丢弃,我们从数据集中移除文档-摘要对。因此,从所有 3 个黄金标准数据集中删除句子后,大量数据被删除。例如,下表显示了 XSUM 数据集的相同情况。第一行是实际计数,第二行是数据过滤步骤后的计数。

对来自源的 XSUM |图像进行数据过滤
根据实际数据和过滤数据训练的模型的评估分数在结果部分提及。
多任务学习
在这种情况下,他们提出了一个额外的任务对源文件(编码器端)中值得摘要的命名实体进行分类。有摘要价值的实体被定义为来自源文档的命名实体,其也存在于基本事实摘要中。为了实现这一点,他们用一个生物方案 (B-Begin,I-Inside,O-outside) 来标记源文档中的每个标记,这是一种非常标准的技术,用于标记文本片段中的单个/多个单词实体。下图以图形方式显示了流程—

多任务学习|作者图片
数学上可以表示为—

这种损失背后的直觉是,这种损失将迫使编码器对其表示进行建模,以便捕获关于值得汇总的实体的相关信息。现在,当解码器获得这种实体丰富的表示时,它可以产生相当好的有思想的一代 (希望导致较少的幻觉)?
除了 BIO 损失,他们还将典型的 MLE 损失用于训练序列生成模型,该模型将从解码器端传播损失。数学上可以表示为—

这里,θ,x,y,I 分别是模型参数,输入记号,输出记号和第 I 个记号。最后,他们最小化联合损失L(I)= L(I)_ MLE+αL(I)_ BIO,其中α为超参数。他们根据验证集在 0.1 到 0.5 之间选择α。
加入显著实体和摘要生成(JAENS)
除了前两种方法之外,他们还探索了另一种生成方法,其中,他们不是仅仅生成摘要,而是训练序列模型来生成值得摘要的命名实体的序列,随后是特殊标记,然后是摘要标记。
这种方法背后的直觉是,在生成摘要令牌的同时,解码器可以通过自我关注机制关注值得摘要的实体。因为它将在学习生成摘要的同时共同学习识别值得摘要的命名实体。下图以图形方式显示了流程—

JAENS 建筑|作者图片
结果
正如我们在下表中看到的,使用所提出的方法,幻觉率有了相当好的降低。

各种训练方案对比|图片来自来源
如果你愿意,你也可以查看我写的其他研究论文摘要。
所以,是的,这就是我的博客。我也有同样的多语言字幕视频漫游,如果你喜欢看视频而不是文字(就像我:D 一样),一定要看看
请随意阅读整篇论文,并向作者说“嗨”,感谢他们的贡献。
论文标题: 抽象文本摘要的实体级事实一致性
论文链接:https://arxiv.org/pdf/2102.09130.pdf
作者: 冯楠、拉梅什·纳拉帕蒂、、西塞罗·诺盖拉·多斯桑托斯、朱恒辉、张德娇、凯思琳·麦克科恩、冰翔
另外,如果你喜欢读这篇文章,你可以选择在【https://www.buymeacoffee.com/TechvizCoffee】——因为我实际上不喝咖啡:)非常感谢!完全是可选的,自愿的:)

熵,简单地解释
让不太直观的概念变得直观

安妮·斯普拉特在 Unsplash 上的照片
熵是信息论的核心概念之一,对机器学习领域贡献巨大,主要由(但不限于)基于树的算法(决策树、随机森林、bagging、boosting 等)作为其核心来决定要分割的特征(或基于熵构建的 IG/Gini)
尽管它们有广泛的应用,巨大的影响,以及它们在数学上有多大的意义,但它们不是很直观
如果你第一次听到熵这个词,这个故事将有助于理解它们背后的核心概念。如果你很了解它,也许想向你的侄子或侄女(或者不了解统计的人)解释,这个故事会有所帮助
故事是这样的:
你坐在教室里,这是你最好的朋友的生日。你可以看到他走向每张桌子,分发巧克力,每个人都被要求从他的巧克力袋中挑选一块巧克力。假设他包里有两种不同口味的巧克力:
- 黑巧克力
- 牛奶巧克力
现在他来到你的桌前,让你从他的包里随机挑选一块巧克力
熵
假设你喜欢黑巧克力&无论何时,你都喜欢黑巧克力胜过牛奶巧克力。因为他是你最好的朋友,他愿意就他包里的东西给你提示。让我们考虑以下场景
- 他说“袋子里还剩 6 块巧克力,都是黑的”你欣喜若狂,因为你确定你肯定会得到黑巧克力,所以马上跳了起来。没有惊喜的成分
- 他说“袋子里还剩 6 块巧克力,其中 4 块是深色的,另外 2 块是牛奶巧克力”你仍然很有信心随机抽取的巧克力仍然是深色的,但你不再是 100%有信心了。随机挑选巧克力的惊喜成分正在增加。
- 他说“袋子里还剩 6 块巧克力,其中 3 块是黑色的,其余 3 块是牛奶巧克力”。现在你担心了,因为你随机挑选的巧克力可能是。在这种情况下,出其不意的因素达到了顶峰。
- 他说“袋子里还剩 6 块巧克力,其中 4 块是牛奶巧克力,剩下 2 块是黑巧克力”。现在你更担心了,因为你随机挑选的巧克力很可能是牛奶巧克力。惊喜的成分很低(即使它对你不利)
- 他说“包里还剩 6 块巧克力,都是牛奶巧克力”。现在你很难过,因为你确定不管你选什么,都是牛奶巧克力。一点惊喜都没有了
在上面的例子中,惊奇的元素只不过是熵。在情景 3 中,两种巧克力在袋子里的数量相等,这种惊喜的成分是最大的。当巧克力的比例变得有利于某一种巧克力时,你的惊奇感就会减少(不管你喜欢什么;) )
我们学到了什么
简单地说,熵是用数学表达的惊奇元素。二元分类的等式是:

其中 p(0)是一等品的比例& p(1)是二等品的比例。当惊奇元素最大时,熵取最大值 1,即在二进制类的情况下,当类分布为 50%-50%时。每当一个类完全接管另一个类时,即 100%-0%分布时,它取最小值零
推广到多功能场景:

看待熵的另一种方式是帮助理解概率分布的不可预测性。在案例 1 和 5 中,分布是高度可预测的,而在案例 3 中,它是非常不可预测的
总之,熵提供了对数据集中的不确定性或无序的度量,作为数据科学家/ML 工程师,我们的目标是创建模型,以减少/最小化数据集标签系统中的熵(在分类问题的情况下)
软演员-评论家中的熵(上)
思想和理论
在概率论中,与熵相关的原理有两个:最大熵的原理和最小交叉熵的原理。一开始我们注意到有两种类型的熵,然而还有更多。

资料来源:123rf.com
熵的许多面
首先让我们强调一下,最大熵原理和最小交叉熵原理都不是定理,它们只是统计推断的原理。这和哲学教义很像。然而,这些学说当然有数学含义。所以我们有两种不同类型的熵:熵和交叉熵。它们由所谓的相关熵连接:

熵、交叉熵和 KL 散度(图片由作者提供)
相对熵的另一个更广为人知的名字是 kull back-lei bler 散度,或 KL 散度。
“……这一措施在技术文献中可能以九种不同的名称出现……我自己的偏好是术语歧视信息及其最小歧视信息(MDI)原则的基础”S . kull back【5】**
熵作为一种学习工具
在强化学习中, 探索 vs .是概念的重要组成部分。在没有充分探索的情况下过快地做出决定可能会是一个大失败。探索是学习的主要组成部分。众所周知,在动作中加入噪音是探索中合理的事情之一。熵是另一个强大的探索工具。高熵可以确保我们避免重复利用相同的不一致性。
香农熵
熵是一种通常与无序或不确定状态联系在一起的物理属性。设 X 为某个离散变量,{ x ₁ ,…, xn }为概率为 X 的可能值pᵢ= p(xᵢ)**。那么信息熵,或者仅仅是熵,或者香农熵定义如下:

熵作为不确定性的度量(图片由作者提供)
直观地说,熵是变量的分布【p】的不确定性度量。一个典型的例子是与硬币相关的概率分布。让硬币公平,即正面和反面都有相等的概率: p = p ( 正面)=p(反面)= 1/2。然后,****
H(p*)=-(1/2 log(1/2)+1/2 log(1/2))=-log(1/2)=-(-1)= 1,既然我们考虑以 2 为底的 log,这是最大不确定性,因为很难预测接下来的折腾。让我们取一个不公平的硬币, p =0.1。然后,*
*H(p*)=-(0.1 log(0.1)+0.9 log(0.9))=-0.332-0.137 = 0.469。在这种情况下,不确定性明显小于最大不确定性= 1。

****熵曲线(图片由作者提供)
均匀概率产生最大的不确定性,因此也产生最大的熵。
有关熵曲线生成,请参见应用程序 2 中的 python 代码。
统计力学中的最大熵
在统计力学中,玻尔兹曼分布是一种概率分布,它给出了一个系统的状态 i 的概率 pᵢ :

玻尔兹曼分布最大化熵 H(p) ( 图片由作者提供)**
其中εᵢ是状态t5】I, T 是系统的温度, k 是玻尔兹曼常数。玻尔兹曼分布是最大化熵H(p)的分布。玻尔兹曼分布也被称为吉布斯分布。****
“……一种涉及熵概念的方法,它的存在依赖于热力学第二定律,对许多人来说无疑是牵强的,可能会因为晦涩难懂而使初学者感到厌恶。这种不便可能被一种方法的优点所抵消,这种方法使热力学第二定律如此突出,并给它如此清晰和基本的表达。”( J.W .吉布斯,【6】)
玻尔兹曼分布与 softmax 函数有关,它在机器学习和神经网络中起着核心作用。我们将在下一篇文章中回到这个问题上(第二部分)
内置熵项的贝尔曼方程
带有熵项的目标函数
SAC 算法基于最大熵 RL 框架。行动者学习机制优化策略以最大化策略的期望收益和期望熵。标准目标(最大化期望回报)增加了一个熵项【H(p)😗***

熵项 H 用α加权的目标函数(作者图片)
这里, γ 为折扣因子,0<γ<1,要求保证奖励和熵之和是有限的; α > 0 是所谓的温度参数,决定了熵项 H(p) 的相对重要性; R_k 是在时间 k 的奖励;𝜋(*||s _ k)-在状态 s_k 执行的策略 𝜋 的概率分布;τ = ( s_k,a_k) - 对(状态,动作)在时间 k 。****
最大报酬和最大熵
在 SAC 目标函数(3)中,奖励 R_k 增加如下:

熵增奖励(图片由作者提供)
这意味着标准的最大回报目标增加了熵最大化。SAC 算法旨在同时最大化预期收益和熵。**
“…最大熵公式在探索和稳健性方面提供了实质性的改进…最大熵策略在面对模型和估计错误时是稳健的…它们通过获得不同的行为来改进探索。”【1】**
状态值和动作值功能
状态-价值函数v𝜋(s _ t)估计如果我们在状态 s_t 开始时的期望收益。包括熵项**的状态值函数定义如下:****

带熵项的状态值函数(图片由作者提供)
动作值函数 Q𝜋( s _t, a _t) 估计期望值,如果我们在状态 s_t, 开始并执行任意动作 a_t. 包含熵项的动作值函数定义如下**

带有熵项的动作值函数(图片由作者提供)
软状态值功能
注意,在熵项中,和从 k=1 开始。因此,动作值函数 Q𝜋( s _ t , a _ t ) 与状态值函数v𝜋(s _ t)**仅在一项上不同:****

状态值和动作值函数之间的联系(图片由作者提供)
将表达式(2)中的熵 H(p) 代入(6):

软状态值函数(图片由作者提供)
函数v𝜋(s)就是所谓的软态值函数**,参见 “软优演算法及应用 ”,第 5 页****
软贝尔曼方程
让我们把等式中的第一项和第一项分开。(5)

轻微变换的动作值函数(图片由作者提供)
通过代入 k = p +1 我们得到

代入 k = p+1 ( 图片由作者提供)
eq 中 γ 后的表达式。(9)是状态值函数v𝜋(s _{t+1 })**。****

软贝尔曼方程(图片由作者提供)
情商。(10)是将修正后的贝尔曼方程,参见 《软优算法与应用 》,第 2 页.此方程称为 软贝尔曼方程 .熵是这个方程不可分割的一部分。**
演员-评论家神经网络
下面,我们展示与一些张量和神经网络的计算相关的 SAC 算法的片断。使用神经网络 ***critic***计算 q 值***qf1***、***qf2***、***qf1_next***、***qf2_next***、、。张量***next_state_action***、***next_state_log_pi***使用神经网络***policy***(actor)计算。网络***critic***由类 QNetwork 定义,网络***policy***由类 GaussianPolicy 定义。第三种神经网络将在下一篇文章中讨论(第 2 部分,软行动者-批评家中的熵)。******
计算张量 V(s)和 Q(s,a)
数组***state_batch,******action_batch,******reward_batch,*******next_action_batch,***和***mask_batch*** 是从类***ReplayMemory***中先前保存的剧集中提取的。***
来自(7)的软状态值函数V(s*)由张量***min_qf_next***表示:*****

S oft 状态值函数 ***V*(*s*)** ( 图片由作者提供)
来自贝尔曼方程(10)的软动作值函数 Q ( s, ) 由张量***next_q_value*** 表示:

软动作值函数 Q ( s,a ) ( 图片由作者提供)
获取张量 qf1_next,qf2_next,**min_qf_next** 和 next_q_value
结论
J.W. Gibbs 使用了热力学中的熵概念,约翰·冯·诺依曼将经典的 Gibbs 熵扩展到量子力学领域来表征纠缠的熵。根据约翰·冯·诺依曼的建议,克劳德·香农将这种类似于统计力学中使用的缺失信息实体命名为熵。这样信息论就诞生了。很明显,熵的概念,作为一个可以通过系统的单个部分来描述系统整体特征的概念,在各个知识领域都非常有用。今天,我们看到熵概念在与深度学习和人工智能相关的系统中非常有用。我们可能很快就会看到其他概念和定律,比如熵将物理学和人工智能联系起来。

App 1。软影评人的一些特征
不符合政策
SAC 是一个非策略算法。这意味着,SAC 算法允许我们重用已经收集的数据。
*****memory = *ReplayMemory*(replay_size)
# Sample a batch from memory, _batch_size_ = 256
state_batch, action_batch, reward_batch, next_state_batch, mask_batch = memory.sample(batch_size=batch_size)*****
至于 on-policy 学习算法,如近似策略优化 (PPO)和软 Q 学习 (SQL),这些算法的样本复杂度都很低。这些算法需要为每个梯度步骤收集新的样本,这变得非常昂贵。
随机演员-评论家训练
非策略算法 DDPG (深度确定性策略梯度)可以被视为一种确定性行动者-批评家算法和一种近似Q-学习算法。然而,这两种算法之间的相互作用使得 DDPG 容易受到超参数设置的影响。SAC 避免了基于软 Q 学习的先验非策略最大熵算法中与近似推理相关的潜在不稳定性。相反,这个 SAC 将政策外的行动者-批评家训练与一个随机行动者相结合,并进一步以
最大化这个行动者的熵为目标熵最大化* 。*****
双 Q 绝招
软演员评论家不是 TD3 的直接继承者(大约同时出版),但它结合了剪辑的双 Q 技巧:
*****qf1, qf2 = self.critic(state_batch, action_batch)
qf1_loss = F.mse_loss(qf1, next_q_value)
qf2_loss = F.mse_loss(qf2, next_q_value) pi, log_pi, _ = self.policy.sample(state_batch) qf1_pi, qf2_pi = self.critic(state_batch, pi)
min_qf_pi = torch.min(qf1_pi, qf2_pi) policy_loss = ((self.alpha * log_pi) - min_qf_pi).mean()*****
在政策改进步骤中,两个 Q 函数用于减轻正偏差。****
最大熵与熵正则化
在 PPO 算法中,一个熵正则项被添加到目标函数中,以保证充分探索:

添加熵加成 ( 图片由作者提供)**
SAC 中最大熵的原理提供的探索允许代理发现比使用熵正则项的其他算法(如 PPO)更快更好的策略。
App 2。Python 代码: 熵曲线生成
参考文献
[1] 软行动者-批评家:带随机行动者的离策最大熵深度强化学习,2018,arXiv
[2] 软演员-评论家算法及应用 s,2019,arXiv
[3] 基于深度能量策略的强化学习,2017,arXiv
[4] E.T .杰恩斯,信息理论和统计力学。一、二(1957 年)
[5] S .库尔贝克,致编辑的信:库尔贝克-莱布勒距离,1987 年
6 J.W.Gibbs,流体热力学中的图解法,2011 年检索,维基资源
7 软演员-评论家, 2018,OpenAI,Spinning Up
[8] 软演员-评论家揭秘,2019,TDS
[9] 深度 RL 的三个方面:噪声、高估和勘探,2020,TDS
[10] 深度 Q 网络中一对相互关联的神经网络,2020,TDS
【Bellman 方程在 Deep RL 中是如何工作的?,2020 年,全港发展策略
[12] 深度强化学习中的探索策略,2020, github.io
[13]Project-HopperBulletEnv with Soft 演员-评论家(SAC) ,2020,github
[14] 近似策略优化算法,2017,arXiv
[15] 深度强化学习的连续控制,v6,2015,arXiv
[16] 熵在软优评(下),2021
软演员-评论家中的熵(下)
SAC 算法执行在策略评估和策略改进之间交替的迭代。在策略评估步骤中,算法根据最大熵和软贝尔曼方程计算中策略 𝜋 的值。在策略改进步骤中,使用最小交叉熵将目标策略更新为尽可能接近先前策略的 softmax 函数。在该步骤中,对于每个状态,通过 Kullback-Leiber 散度来更新策略。

资料来源:123rf.com
查找策略
在第一部分中,按照 SAC 算法,利用最大熵原理,我们构造了软状态值函数V(s),软动作值函数 Q (s,a【T32)和T35 下面的目标是建立一个寻找最佳策略的算法𝜋(a|s)。为此,SAC 算法使用KL-divergence 和 softmax 函数实现了最小交叉熵原理。
KL-divergence
交叉熵 H ( p,q )和 KL-散度
设 p 和 q 为事件集合上的离散概率分布{ x ₁ ,…, x_n }与某个离散变量 X 分布相对于分布的交叉熵在给定的一组事件上定义如下:**

****交叉熵(图片由作者提供)
这里 E p [∙]是相对于分布 p 的期望值算子。
对于概率空间相同的离散概率分布 p 和qXkull back-lei bler 散度(或 KL-divergence) 从 q 到 p**

KL-divergence (图片作者提供)
熵和交叉熵通过 KL-divergence 联系起来:

熵、交叉熵和 KL 散度(图片由作者提供)
KL- 散度仅当对于所有 x 从q(x*)= 0遵循 p (x)=0 时定义。如果 p (x)=0 ,那么根据洛必达法则,和中的相应项解释为 0*

****p(x)= 0 的项的贡献(图片由作者提供)
设 p 是固定的参考分布,那么交叉熵和 KL-散度直到一个加法常数都是相同的(因为 p 是固定的)。当 p=q, KL-divergence 取最小值 = 0,交叉熵 H(p,q) 取最小值 H(p,q) = H(p) 。
KL-散度和距离度量
交叉熵 H ( p , q ) 不能作为距离度量,因为它有一个缺点就是H(p,p)=H(KL 散度不能是距离度量,因为它不是对称的,即dᴋʟ(p∩q)≦dᴋʟ(q∩p【t128******

高斯对的 KL 散度,由应用程序 2.1 的 python 代码生成
KL 散度可以被认为是分布 q 离分布有多远的度量,但是它不能被用作距离度量。然而,KL 散度的无穷小形式,所有二阶偏导数(Hessian)的矩阵构成了被称为费希尔信息度量的度量。根据陈佐夫定理、、【Do17】费希尔信息度量是黎曼度量的一种变体,是黎曼几何中必不可少的部分。
KL-散度估计的偏差
KL-散度可能高度依赖于样本的数量,并且当样本的数量趋于无穷大时,KL-散度估计可能不会收敛到好的结果。这方面的一个例子如图(a)所示,我们有两个固定的高斯分布:p~N(0,2) 和 q ~ N ( 8,2 ) 。然而,样本数量在区间【500,5000】内变化。KL 散度随着样本数量的增加而线性增加。
如果样本数量是固定的,而分布参数是变化的,则 KL-散度估计可能会有很大的偏差,并且该偏差可能高度依赖于分布。在图(b)中,我们又有了两个高斯分布:p~N(0,2) 和 q~ N (3,std) : ***mean*** = 3 和***std***在区间【1,20】上运行。在这个区间内,KL-散度变化很大。参见【Pe08】、【第 18 期】了解更多关于 KL-散度估计的详细信息。

(a)dᴋʟ(p∩q)对样本数的依赖性,(b)dᴋʟ(p∩q对标准差的依赖性(图(a)和(b)为****
演员兼评论家
确定性策略与随机策略
对于离散动作空间的情况,有一个成功的算法*【DQN】(深 Q 网)。将 DQN 方法转移到演员-评论家架构的连续动作空间的成功尝试之一是算法【DDPG】,其关键组件是【Li15】。确定性策略的主要优点是,与随机策略相比,可以更有效地计算策略梯度,随机策略需要更多的样本来计算,【Si14】。*****
然而 DDPG 的、一个、一个常见故障模式是学习到的 Q 函数经常高估 Q 值。众所周知的事实是,如果探索不够有效,就无法学习到好的政策。在[ Ma19 ]中确立了一个不那么微不足道的事实:
“…如果探索确实始终如一地发现了奖励,但发现得不够早,演员-评论家算法可能会陷入一种配置,奖励的样本会被忽略。”
如【Ma19】所示,对于一个确定性的策略梯度更新,即使在非常简单的环境(比如 1D-托伊)的情况下,即使行为策略定期检测到奖励,演员更新也会停止。
随机因素和术语“软”
在第 1 部分中,我们考虑了统计力学中使用的玻尔兹曼分布。这是一个概率分布,给出了系统状态的概率:**

玻尔兹曼分布和 p 分区函数(图片由作者提供)
在统计力学中,方程(4)的归一化分母称为配分函数,用 Z 表示。为了更精确地表达配分函数,我们需要使用积分,而不是求和,但是对于我们的目的,使用离散的情况就足够了。**
在概率论中, softmax 函数 p(x) ,如同玻尔兹曼分布,是一个取实数其中 i 遍历事件空间**{ 1******

Softmax 函数 p(x)和分区函数 Z (图片由作者提供)
函数映射【xᵢ】到半开区间 (0,1】,所有【p(xᵢ】之和为 1 。由此可见,函数 p 是一个概率分布在事件空间{ 1, … , n }** 。softmax 函数也称为归一化指数变换。Softmax 是一种在事件之间创造一种竞争的方式。特别地,在软行动者-批评家中,随机代理使用 softmax 执行选择动作。Softmax 将选择最大值的 赢者通吃 策略改为随机策略,其中每个动作都可以按一定概率选择。这就是“软”这个词的动机【Br89】。显然,在软演员-评论家中,术语“软”指的是使用了 softmax 函数的事实。**********
配分函数 Z 也出现在神经网络中,并且具有与统计力学中相同的含义:离散项的和,其是如等式(5)中的 softmax 函数中的归一化分母。
政策改进步骤
在软行动者-批评家中,策略通过以下公式更新:

更新政策(图片作者提供)
等式(6)中的第一个分布是分布𝜋'(∙|s _ t)。这是高斯分布π集合中的一个分布。本质上,这种分布提供了最小交叉熵。与等式(2’)中 KL 散度的最小值相同。****
“由于在实践中我们更喜欢易处理的策略,因此我们将额外将策略限制为某个策略集π,例如,它可以对应于一个参数化的分布族,如高斯分布、【SAC-1】。****
等式(6)中的第二分布是使用 softmax 函数构建的,分母是配分函数(5)。为前一个策略函数 𝜋_old 获得的状态值函数 Q ( s_t,aᵢ ) 的值构成了等式(5)中 softmax 函数的一组【xᵢ值。
预期 KL-散度
让我们从方程(6)转换 KL 散度。根据(2),因为比率的对数是对数的差,所以 KL 散度看起来像这样:

将 KL-divergence 转换为对数差(图片由作者提供)
我们的目标是最小化高斯分布π集上的 KL 散度。对数分区函数不依赖于动作和策略𝜋'∈π的选择,那么对数分区函数logz(s_t)**可以被丢弃。此外,可以通过直接最小化方程(7)中的预期 KL-散度乘以温度参数 α 来学习策略参数****

最小化预期 KL 散度的目标函数(图片由作者提供)
等式(7’)用于计算***policy_loss*** 张量。
策略计算步骤

策略计算步骤(图片由作者提供)
SAC 实施提示
SAC 代理的初始化函数
在 SAC 代理**__init__**函数中,***self.critic***``***self.critic_target***成员属于类型QNetwork**和***self.policy***属于类型高斯策略。 model.py 中定义的 QNetwork 和高斯策略类为神经网络。******
soft _ actor _ critic _ agent 类的函数 _ _ init _ _
政策性亏损和批评性亏损

张量 policy_loss 代表 eq。(7)出自【SAC-2】

张量 Q1 _ 损耗和 Q2 _ 损耗代表 eq。(5)出自【SAC-2】
- 以
***state_batch***为输入的***policy.sample***函数返回张量***action_batch_pi***和***log_pi***。 两个张量都用于计算***policy_loss***的。 - 有两个(评论家)神经网络
***Q1***和***Q2***,[***QNetwork***](https://github.com/Rafael1s/Deep-Reinforcement-Learning-Algorithms/blob/master/HopperBulletEnv-v0-SAC/sac_agent.py),【hop 20】两个实例。请注意,SAC(如在 TD3 算法中)使用两个评论家神经网络来减轻策略改进步骤中的正偏差。** ***policy.sample***函数,以***next_state_batch***为输入,返回张量***next_state_action***和***next_state_log_pi***。 两个张量都用于两个 Q 损失值***Q1_loss, Q2_loss***的计算。- 两个张量
***Q1_loss***和***Q2_loss***由 PyTorch***mse_loss()***函数计算。对***mse_loss()***的是通过两种方式得到的:(一)动作值函数 Q(s,a) ,(二)软贝尔曼方程(第一部分,【10】)。与***mse_loss()***的自变量之差称为软更夫残差*。*****
计算张量保单 _ 损失和 Q1 _ 损失,Q2 _ 损失
实施方案

函数 update_parameters 中发生了什么(图片由作者提供)
上图基本上反映了 SAC 算法的核心***update_parameters*** 函数中发生的事情。
结论
与交叉熵相关的 KL 散度几乎可以认为是概率分布空间上的距离度量。在无穷小形式中,KL 散度甚至与黎曼几何中的某些特定距离度量相一致。这种由 KL-divergence 生成的“几乎”度量用于考虑高斯分布空间中的接近度,其中在改进的每一步都寻求最佳策略。事实证明,随机策略比确定性策略工作得更好,特别是存在这样的例子,其中确定性策略的最优策略梯度更新被停止,并且即使对于非常简单的环境也不能改进策略。

资料来源:123rf.com
第三部分会有什么?
如何计算**log 𝜋(a|s)**,SAC 上下文中的重新参数化技巧,以及其他类似的东西…****
App 1。吉布斯不等式
设 p= { p ₁, … , p_ n} 和q ={q₁,…,q_ n}** 为两个概率分布。假设 I 是所有 i 的集合,其中*为非零。然后*****

吉布斯不等式
让我们看看为什么吉布斯不等式成立。首先,

这里,y = ln(x)
然后

吉布斯不等式的证明
所有非零值 pᵢ 之和为 1 。一些非零的【qᵢ】可能被排除在总和之外,因为根据定义,我们只选择那些不等于零的指数。从 ln( x ) 到log₂(x)我们只需要用 ln2 除所有项。******
App 2。 Python 代码:KL-divergence
App 2.1。高斯对
App 2.2 KL-散度对标准偏差的依赖性
参考文献
[Br89] J. Bridly,将随机模型识别算法训练为网络可以导致参数的最大互信息估计,1989,NeurIPS Proceedings
[Do17] J.Dowty,指数族的陈佐夫定理,2017,arXiv
[Hop20] 项目—HopperBulletEnv with Soft Actor-critical(SAC),2020,github
[Li15] T.Lillicrap 等,深度强化学习的连续控制,2015,arXiv
[Ma19] G. Matheron 等人,DDPG 的问题:理解具有稀疏回报的确定性环境中的失败,2019,arXiv
[No18] Y.-K. Noh 等人,kull back-lei bler 散度最近邻估计的偏差减少和度量学习,2018,IEEE Xplore
[Pe08] F. Perez-Cruz,连续分布的 Kullback-Leibler 散度估计,2008,IEEE Xplore
【Sa18】Y . Sako,是“softmax”这个词把你逼疯了,2018,Medium
[SAC-1] T.Haarnoja 等人,软行动者-批评家:带随机行动者的离策最大熵深度强化学习,2018,arXiv
[SAC-2] T.Haarnoja 等,软演员-评论家算法及应用 s,2019,arXiv
[Si14] D.Silver 等人,确定性策略梯度算法,2014,DeepMind Technologies
[St20a] R. Stekolshchik,深度 Q 网络中一对相互关联的神经网络,2020,TDS
[St20b] R. Stekolshchik,深层 RL 的三个方面:噪声、高估和勘探,2020,TDS
[St21] R. Stekolshchik,软演员-评论家中的熵(上),2021 年,TDS
EPL 分析和游戏周 20 预测
使用 xG 统计预测英超联赛结果的数据驱动尝试
这是我的 EPL 预测系列的一篇文章。在游戏周 9 之后,由于其他任务,我无法更新这个系列。嗯,我现在回来了,希望从现在开始定期发表!
你可以在这里查看第八周比赛的预测和实际表现。
预期目标或 xG 是用于预测的参数。如果你对理解预测算法感兴趣,我建议你看看这篇文章,其中有详细的解释。
截至第 19 周比赛的分析

EPL 表格,包括净胜球、进球数和进球数(图片由作者提供)
在 19 个比赛周之后,红魔以 40 分高居榜首。然而,很难说他们是否会在第 20 场比赛后保持领先,因为他们的同城对手以 38 分和一场比赛的优势紧随其后。狐狸队在打了 19 场比赛后也拿到了 38 分,和曼联队一样。
曼联在安菲尔德拿到一分,并赢得了双赛周的第二场比赛,对阵富勒姆,保住了榜首位置。
曼城和莱斯特城都在第 19 轮比赛中获得了 6 分。卫冕冠军正在努力寻找进球的机会,他们在过去的 4 场联赛中都没有进球。红军在主场被红魔逼平后,在第二场比赛中被伯恩利重创。这是伯恩利46 年来第一次在安菲尔德获胜,结束了利物浦的安菲尔德不败纪录,该纪录可追溯至 2017 年 4 月。对于克洛普的利物浦来说,目前获得冠军联赛席位本身似乎非常困难,在过去两个赛季的表现之后,他们预计将在至少几年内统治联赛。
蓝军在最后一个转会窗口投入巨资后,正在努力让他们的大炮开火。他们在克拉文农场赢得了双赛周的第一场比赛。然而,对兰帕德和他的团队来说,狐狸是一块难啃的骨头。对于切尔西的老板和俱乐部传奇人物弗兰克·兰帕德来说,0:2 输给狐狸是压垮骆驼的最后一根稻草,使他失去了在斯坦福桥的工作。

每场比赛 xG 得分与 xG 失球(图片由作者提供)
在 19 场比赛后,卫冕冠军尽管在最后 4 场比赛中没有进球,但在 xG 以每场 2 分左右的比分领先,紧随其后的是曼城。阿斯顿维拉、利兹、曼联、切尔西也都创造了场均 1.5xG 以上。
水晶宫、伯恩利、纽卡斯尔、西布朗、南安普顿和谢联都在创造力上挣扎,甚至无法每场创造 1 个 xG。
曼城似乎是最好的防守单位,让对手每场比赛创造大约 0.75 xG。大多数球队每场比赛承认 1 到 1.5 xG。西布朗和利兹的场均失球超过 1.5 克。

每场比赛 xG 得分与 xG 失球(图片由作者提供)
根据 xG 得分和 xG 失球,球队可以分为 4 个象限,如上图所示。
水平虚线显示每场比赛的平均 xG 得分。水平虚线上方的球队是强攻方,下方的球队是弱攻方。
垂直虚线表示每场比赛的平均 xG 失球。左边的队伍防守强,右边的队伍防守弱。

每次匹配的增量 xG(图片由作者提供)
尽管没有合适的前锋,曼城显然是德尔塔 xG 最好的球队。另一方面,西布朗在球场的两端苦苦挣扎,进攻最弱,防守漏洞百出。
利兹联队在每场比赛创造的 xG 中名列前茅。然而,球队的 delta xG 是负的,因为对手发现很容易穿透利兹的防守,这是贝尔萨必须立即研究的问题。
游戏周 20 预测
在进行预测之前,让我澄清一下,这是一个非常简单的算法,只是基于过去的 xG,所以只能预期基线性能。该算法也不能预测高得分游戏。
下表提供了对第 20 周比赛的预测。
GD 的绝对值显示了比赛的竞争力。该值越高,预计匹配越偏向一侧,预测的准确性也越高。
GD 值越低,这场比赛就越有可能成为任何人的游戏。GD 的正值表示主场胜,负值表示客场胜。

GW 9 的 xG 预测(图片由作者提供)
西布罗姆维奇 vs 曼城有望成为比赛周最一边倒的比赛。我们可以期待曼城在山楂球场轻松击败对手。阿斯顿维拉也有望在伯恩利轻松取胜。曼联、布莱顿和切尔西也有望在本周赢得主场比赛。
埃弗顿对莱斯特是最势均力敌的比赛,可能是任何人的比赛。
本周最值得期待的比赛是穆里尼奥的热刺主场迎战克洛普的利物浦。这场比赛将是两个最好的教练之间的战斗。这场比赛预计会非常激烈,红军稍占上风。

铁锤帮、枪手和孔雀队可以期待赢得他们的比赛,尽管他们在客场比赛中面临着来自东道主的激烈竞争。
比赛周总共 10 场比赛中只有 3 场对主队有利。
更新:预测与实际表现
在第 20 周的比赛中,我们的算法在 10 场比赛中有 9 场能够预测 xG 的方向。在预测纽卡斯尔对利兹的比赛中,算法是错误的,在这场比赛中,东道主的 xG 比客队高,然而以 1-2 输掉了比赛。

预测与实际 xG(图片由作者提供)

托马斯·塞勒在 Unsplash 上的照片
EPL 分析和游戏周 21 预测
使用 xG 统计预测英超联赛结果的数据驱动尝试
这是我的 EPL 预测系列的一篇文章。你可以在这里查看第 20 周比赛的预测和它的实际表现。
预期目标或 xG 是用于预测的参数。如果你对理解预测算法感兴趣,我推荐你去看看这篇文章,里面有详细的解释。
截至第 20 周比赛的分析

EPL 表格,包括净胜球、进球数和进球数(图片由作者提供)
20 分钟后,曼城以 41 分高居榜首,因为他们的本地对手在主场输给谢菲尔德联队后丢分。佩普瓜迪奥拉的球队领先红魔 1 分,并且还有一场比赛。
上个比赛周,莱斯特、热刺、埃弗顿、切尔西和阿斯顿维拉都丢了分。
然而,卫冕冠军在托特纳姆热刺球场以 1-3 战胜穆里尼奥的球队,确保了全取三分。在经历了四场英超联赛的干旱后,这对红军来说是一剂急需的强心剂。铁锤帮和枪手保持着良好的状态,赢得了各自的比赛。

每场比赛 xG 得分与 xG 失球(图片由作者提供)
在 20 个比赛周之后,卫冕冠军尽管最近在进球得分上有所下滑,但在 xG 场均得分上领先大约 2 分。自从最近几个游戏周以来,曼城进球机器一直在稳步提高其每场比赛的 xG 得分。阿斯顿维拉、利兹、曼联、切尔西也都创造了场均 1.5xG 以上。
水晶宫、伯恩利、纽卡斯尔、西布朗、南安普顿和谢联都在创造力上挣扎,甚至无法每场创造 1 个 xG。
曼城似乎是最好的防守单位,让对手每场比赛创造大约 0.75 xG。大多数球队每场比赛承认 1 到 1.5 xG。西布朗和利兹的场均失球超过 1.5 克。

每场比赛 xG 得分与 xG 失球(图片由作者提供)
根据 xG 得分和 xG 失球,球队可以分为 4 个象限,如上图所示。
水平虚线显示每场比赛的平均 xG 得分。水平虚线上方的球队是强攻方,下方的球队是弱攻方。
垂直虚线显示每场比赛的平均 xG 失球。左边的队伍防守强,右边的队伍防守弱。

每次匹配的增量 xG(图片由作者提供)
尽管没有合适的前锋,曼城显然是德尔塔 xG 最好的球队。另一方面,西布朗在球场的两端苦苦挣扎,进攻最弱,防守漏洞百出。
利兹联队在每场比赛创造的 xG 中名列前茅。然而,球队的 delta xG 是负的,因为对手发现很容易穿透利兹的防守,这是贝尔萨必须立即研究的问题。让事情变得更糟的是,最近几周,该团队还在努力提高创造力。
游戏第 21 周预测
在进行预测之前,让我澄清一下,这是一个非常简单的算法,只是基于过去的 xG,所以只能预期基线性能。该算法也不能预测高得分游戏。然而,该模型在预测比赛势头方面表现得相当好。你可以在下面看看实际表现如何与前一周的预测相比较。
下表提供了对第 21 周比赛的预测。
GD 的绝对值显示了比赛的竞争力。该值越高,预计匹配越偏向一侧,预测的准确性也越高。
GD 值越低,这场比赛就越有可能成为任何人的游戏。GD 的正值表示主场胜,负值表示客场胜。

GW 9 的 xG 预测(图片由作者提供)
像前一周一样,刀片队再次前往曼彻斯特,在本周的比赛中迎战榜首球队。如果他们能击败曼城,我们可以期待在比赛周之后看到另一支球队夺冠!哦,这个季节太有趣了!然而,该模型预测这场比赛将是最一边倒的一场比赛,曼城队将大获全胜。考虑到他们令人难以置信的状态,曼城很有可能会击败他们。
布莱顿对马刺和枪手对红魔估计是周末竞争最均衡的比赛,算法稍微有利于客队。

预测的 xG(图片由作者提供)
切尔西的新任主教练图切尔有一个很好的机会来庆祝他在俱乐部的首胜,这个周末他将在主场迎来红葡萄酒。富勒姆和阿斯顿维拉也分别在客场对阵西布朗和阿斯顿维拉。
埃弗顿、利物浦和莱斯特有相当大的机会赢得本周的比赛。
莱斯特队主场迎战利兹队估计会是一场进球盛宴,莱斯特队有望获胜。水晶宫 vs 狼队,低挡防守之战估计是低得分手。
更新:实际结果与预测

在预测 xG 差异超过 0.20 的 7 次预测中,有 5 次预测与实际性能相匹配。南安普顿对阿斯顿维拉和埃弗顿对纽卡斯尔比赛的预测明显错误。阿斯顿维拉设法从他们的圣玛丽体育场的访问中获得 3 分,然而 xG 数据表明东道主是场上更好的球队。
埃弗顿对纽卡斯尔的比赛也很糟糕,太妃糖期待一场轻松的胜利,但多亏了卡勒姆·威尔逊的魔术,喜鹊在古迪逊公园确保了一场 2-0 的胜利。
布莱顿对热刺的比赛被认为是一场非常激烈的比赛,但穆里尼奥的球队甚至没有与正在保级的布莱顿进行比赛。
你可能会感兴趣的另一篇文章:
EPL 分析和游戏周 22 预测
使用 xG 统计预测英超联赛结果的数据驱动尝试
这是我的 EPL 预测系列的一篇文章。你可以查看第 21 周比赛的预测,以及它与实际表现的对比。
预期目标或 xG 是用于预测的参数。如果你对理解预测算法感兴趣,我推荐你去看看这篇文章,里面有详细的解释。
截至第 21 周比赛的分析

EPL 表(图片由作者提供)
在第 21 轮比赛后,曼城以 44 分高居榜首。佩普瓜迪奥拉的球队在少赛一场的情况下领先红魔 3 分。正如克洛普所说,公民明显领先。
上周的比赛中,排名第二的曼彻斯特联队在酋长球场与枪手互交白卷,丢掉了一些分数。莱斯特城主场不敌利兹,滑落至积分榜第四位。莱斯特的损失被卫冕冠军利用,他们以 1-3 战胜了目前排名第五的铁锤帮,攀升至第三位。
积分榜中游看起来非常有趣,7 支球队仅相差 4 分。
穆里尼奥的马刺在没有队长和攻击线前锋哈里·基恩的情况下造访布莱顿。比赛以 1-0 结束,海鸥队领先,热刺队甚至没有进行一场像样的比赛。切尔西在新教练托马斯·图赫尔的带领下取得了首场胜利,对手是伯恩利,他们的两个进球分别来自后卫阿兹皮里库塔和阿朗索。尽管他们所有的明星球员都回到了古迪逊公园,太妃糖还是输给了喜鹊。

xG 得分 vs xG 失球(图片由作者提供)
经过 21 场比赛,卫冕冠军在 xG 场均得分领先 2 分左右。自从最近几个游戏周以来,曼城进球机器一直在稳步提高其每场比赛的 xG 得分。阿斯顿维拉、利兹、曼联、切尔西也都创造了场均 1.5xG 以上。
水晶宫,伯恩利,新城,西布朗,狼队和谢联都在创造力上挣扎,甚至不能每场比赛创造 1 个 xG。
曼城似乎是最好的防守单位,让对手每场比赛创造大约 0.75 xG。大多数球队每场比赛承认 1 到 1.5 xG。西布朗和利兹的场均失球超过 1.5 克。

xG 得分 vs xG 失球(图片由作者提供)
根据 xG 得分和 xG 失球,球队可以分为 4 个象限,如上图所示。
水平虚线显示每场比赛的平均 xG 得分。水平虚线上方的球队是强攻方,下方的球队是弱攻方。
垂直虚线表示每场比赛的平均 xG 失球。左边的队伍防守强,右边的队伍防守弱。

Delta xG(图片由作者提供)
曼城在 delta xG 方面遥遥领先于其他球队,xG 得分和 xG 失球之间的差距超过 1。卫冕冠军是另外一支 delta xG 在 0.5 以上的队伍。另一方面,西布朗在球场的两端苦苦挣扎,进攻最弱,防守漏洞百出。
利兹联队在每场比赛创造的 xG 中名列前茅。然而,球队的 delta xG 是负的,因为对手发现很容易穿透利兹的防守,这是贝尔萨必须立即研究的问题。刚刚超过降级区的海鸥队在 delta-xG 方面排名第 7。他们很不走运,没有把场上表现转化成积分。
游戏第 22 周预测
在进行预测之前,让我澄清一下,这是一个非常简单的算法,只是基于过去的 xG,所以只能预期基线性能。该算法也不能预测高得分游戏。该模型也没有考虑球队的选择,球员因伤缺席,阵型,战术变化等。
然而,该模型在预测比赛的势头方面表现得相当好。你可以在下面看看实际表现如何与前一周的预测相比较。
下表提供了对第 22 周比赛的预测。
GD 的绝对值显示了比赛的竞争力。该值越高,预计匹配越偏向一侧,预测的准确性也越高。
GD 值越低,这场比赛就越有可能成为任何人的游戏。GD 的正值表示主场胜,负值表示客场胜。

EPL 预测 GW 22(图片由作者提供)
伯恩利 vs 曼城似乎是 Gameweek 22 最一边倒的比赛。曼城有望在这场比赛中全取三分。莱斯特也有望在克拉文农场赢得与富勒姆的比赛。
刀片,曼联和红军有望轻松赢得各自的比赛。
阿斯顿维拉 vs 西汉姆,纽卡斯尔 vs 水晶宫,利兹联 vs 埃弗顿,热刺 vs 切尔西估计是非常接近的比赛,没有明确的热门。然而,阿斯顿维拉和切尔西比他们的对手略胜一筹。

GW 22 预测(图片由作者提供)
更新:预测与实际表现

成为会员
我希望你喜欢这篇文章,我强烈推荐 注册中级会员 来阅读更多我写的文章或成千上万其他作者写的各种主题的故事。
你的会员费直接支持我和你看的其他作家。你还可以在 Medium 上看到所有的故事。
阿莱西奥·费斯塔在 Unsplash 上拍摄的照片
正则化最小二乘与最大化后验概率函数的等价性
机器学习推导
为什么正则化最小二乘等价于最大后验解,是正态分布数据的最优算法

在 Unsplash 上拍摄的 ThisisEngineering RAEng
本文比较蒂科诺夫 L2 正则化和最大后验概率函数。这是一个相当代数密集,所以我建议拿起笔和纸,尝试自己!
偏差方差困境
许多机器学习模型的一个问题是它们倾向于过度拟合数据。过度拟合是机器学习中的一种现象,发生在模型对训练数据学习得太好,使其概括能力变差的时候。
学习机的偏差和方差之间有一个微妙的平衡。
偏差:学习机器对未知数据进行归纳的能力
方差:当我们改变随机机器被训练的数据时,随机机器的预测的期望偏差
一台性能良好的机器将具有高偏差和低方差。为了获得更高的偏差,我们需要一台复杂的机器,能够对看不见的数据进行归纳。然而,机器必须足够简单,方差不会增加太多。
蒂科诺夫 L2 正规化
一种增加机器复杂性而不增加方差的方法是调整机器。
正规化试图将训练过程中的过度适应最小化。当机器超载时,它们的重量会急剧增加。L2 正则化惩罚权重的大小以减少过度拟合。

L2 正则化的工作原理是,首先向最小二乘误差添加一项,惩罚权重 w 的大小。相对于 w 求微分并找到最小误差,得到上面所示的伪逆解,其中γ是 L2 正则化项。
最大后验概率
根据贝叶斯定理,后验概率与先验乘似然成正比。
高斯分布似然和共轭先验的表达式如下所示。似然和先验概率都是多元高斯的形式。


其中多元高斯函数如下所示:

由数据、平均值和协方差矩阵参数化。
通过取似然和先验的乘积,然后取自然对数并最大化,可以找到产生最大后验概率的权重。对数函数用于简化,由于它是单调函数,我们希望最大化它,因此它不会影响最大化的结果。

后验概率函数结果对数的上方。因此,最大化后验概率相当于最小化下面的等式。

当最小化时,该等式与本文开头所示的正则化最小二乘法中所示的误差函数相同。因此,通过最大化后验概率函数来寻找最佳权重等价于最小化正则化最小二乘函数。
结论
最小化正则化最小二乘误差相当于最大化后验概率。对于正态分布数据和无限数据,正则化最小二乘算法是最优的。这就是这个算法如此强大的原因,也是它一直沿用至今的原因。
支持我👏
希望这对你有帮助,如果你喜欢它,你可以 跟我来!
您也可以成为 中级会员 使用我的推荐链接,访问我的所有文章和更多:https://diegounzuetaruedas.medium.com/membership
你可能喜欢的其他文章
参考文献
主教,哥伦比亚特区,2006 年。模式识别和机器学习
无效假设的证据?一个等价测试的例子
当结果不显著时使用的统计工具(R 中的例子)

布雷特·乔丹在 Unsplash 上的照片
假设你进行了一项研究,测试儿童和青少年对不公平待遇的道德厌恶之间的差异。收集数据并运行分析后,您会发现两者之间没有显著的统计差异。当这种情况发生时,通常将结果解释为零假设的证据,也就是说,儿童和青少年在道德厌恶方面没有真正的差异。然而,这是对非显著性结果的误解,因为不可能显示在人群中完全没有影响。
Quertemont (2011)指出,不重要的结果可能由于三种不同的原因而出现:
1.在收集或编码数据的过程中出现了错误,掩盖了其他重要的结果。这也包括测量误差(不精确)。
2.这项研究没有足够的统计能力来证明在人群水平上确实存在这种影响。由于抽样误差,结果是“假等价”。
3.在人群层面实际上并没有真正的影响(或者说影响可以忽略不计)。结果是“真正的等价”。
虽然不可能显示在人群中没有影响,但我们可以使用统计数据来显示人群中影响的大小低于某个值的可能性,该值被认为太低而无用(Quertemont,2011)。这就是等价测试的情况。
等价测试
等效性检验起源于生物等效性研究,即如果药物在一定时间后的吸收率和在血液中的浓度水平相同,则认为药物是生物等效的。
如前所述,等效性测试检验是否可以拒绝存在极端到足以被认为有意义的影响的假设(Lakens 等人,2018)。

经典零假设显著性检验和等价性检验的区别。图片作者。
正如你从上图中看到的,为了进行等价测试,研究者必须定义感兴趣的最小效应大小。让我们看看 Lakens 等人(2018 年)的例子,让它更清楚:
在与专家进行广泛讨论后,研究人员决定,只要性别差异与人口差异的偏差不超过 0.06,这种差异就太小了,不必在意。假设总体中的预期真实差异为 0.015,研究人员将测试观察到的差异是否超出 0.055 和 0.075 的边界值(或等价界限)。如果在两个单边测试中可以拒绝至少与这些边界值一样极端的差异[……],研究人员将得出结论,应用率在统计上是相等的;性别差异将被视为微不足道,也不会花钱解决参与方面的性别差异。
要了解如何证明感兴趣的最小效应大小,请查看 Lakens 等人(2018 年)的论文。
r 代码
读取数据
我们将使用 R 函数 read.delim 读取数据集
EqT <- read.delim("[https://raw.githubusercontent.com/rafavsbastos/data/main/EqT.txt](https://raw.githubusercontent.com/rafavsbastos/data/main/EqT.txt)")
我们为数据框选择的名称是 EqT。使用View(EqT)我们将看到我们的数据集:

其中性别分为 1 =男性,2 =女性;和 SPPD =偏见和歧视的自我认知(巴斯托斯等人,2021)。
装载到斯特包
为了开始操作数据,我们需要下载 TOSTER 包。只需运行以下代码。
install.packages(“TOSTER”)
好的。现在包在你的电脑上。现在,我们需要让它开始与library(TOSTER)一起工作。
汇总统计数据
使用 TOSTER 包,可以使用汇总统计数据执行等效性测试。然后,我们将计算每组的平均值、标准偏差和样本量。
aggregate(x = EqT$SPPD, by = list(EqT$sex), FUN = "mean")
aggregate(x = EqT$SPPD, by = list(EqT$sex), FUN = "sd")
aggregate(x = EqT$SPPD, by = list(EqT$sex), FUN = "length")
它给出了以下输出:

男性的平均值= 2.99,标准差= 1.82,有 197 名参与者,而女性的平均值= 3.03,标准差= 1.90,有 374 名参与者。
R 中的等价测试
现在,我们将把这些结果放在一行代码中,这将为我们提供信息输出和图表。注意,这里的上下等价界只是一个例子,它不应该被认为是一个真实的结果。
TOSTtwo(m1 = 2.991117, m2 = 3.036096, sd1 = 1.823769, sd2 = 1.904216, n1 = 197, n2 = 374,
low_eqbound_d = -0.06, high_eqbound_d = 0.06, alpha = 0.05,
var.equal = FALSE)
测试的参数在最后一行代码的括号内定义。要对您自己的数据执行测试,只需复制这几行代码,用您自己研究中的相应值替换这些值,然后运行代码。结果和图将自动打印。运行代码“help("TOSTtwo ")”会提供一个包含更多详细信息的帮助文件。
输出如下:

和图:

正如我们所看到的,基于等价性检验和零假设检验,我们可以得出结论,观察到的效应在统计上不等于零,在统计上不等于零。
评论
在这篇文章中,我展示了等价测试的重要性以及这些统计数据对于研究的重要性。我还展示了一个使用两个单边测试(TOST)的例子,虽然等价测试也可以使用其他统计数据。重要的是要注意等效测试的一个限制:能力。为了进行这种统计,你可能需要一个超过 100 甚至 500 的样本量来获得足够的功效(Goertzen 和 Cribbie,2010)。这意味着研究人员需要投入更多的资金来做这些统计。
接触
请随时通过以下方式联系我
【Gmail:rafavsbastos@gmail.com
咨询与合作网站:rafavsbastos.wixsite.com/website LinkedIn:linkedin.com/in/rafael-valdece-sousa-bastos/ Github:github.com/rafavsbastos
参考
E.Quertemont,如何从统计上显示一种效果的缺失,2011 年,心理比利时, 51 (2),109–127。
D.Lakens,A. M. Scheel,P. M. Isager,《心理学研究的等效测试:一个教程》,2018 年,心理科学方法与实践进展, 1 (2),259–269。
R.V. S. Bastos,F. C. Novaes,J. C. Natividade,《偏见和歧视自我认知量表:有效性和其他心理测量性质的证据》, 2021 年,提交给同行评审的手稿。
J.R. Goertzen 和 R. A. Cribbie,《检测缺乏关联:等效测试方法》, 2010 年,英国数学和统计心理学杂志, 63 (3),527–537。
分类模型中的可疑区域
朴素贝叶斯模型中具有可疑区域的分析示例。
由吉纳·戈麦斯
与格雷戈·佩奇
可疑区域:概述
通常,当我们度量分类模型的性能时,我们通过使用某个特定数据集中的所有记录来完成。例如,我们可能会说,一个模型“对训练集达到了 71%的准确性,对验证集达到了 68.4%的准确性”,或者该模型“对训练集的敏感度为 81.2%。”但是,并没有要求我们在进行预测时必须考虑所有的记录。
相反,在某些情况下,我们可能希望根据模型做出分类决策的潜在概率,只关注某个特定的记录子集。
例如,假设我们正在运行订阅服务,并且我们已经建立了一个模型来预测客户的订阅续订。与其只关注“是”或“否”的分类预测结果,我们可能更喜欢关注我们数据库中每个消费者的更新概率。为了以最有效的方式集中我们的精力,我们可能会忽略续订预测低于 0.20 的客户(也许他们是一个失败的原因),以及那些概率高于 0.80 的客户(他们似乎是续订的“锁”)。通过将我们的精力引向概率在 0.20 和 0.80 之间的人,我们可以最有效地利用我们的营销资源。
或者,企业可能会遇到这样一种情况,在这种情况下,关注类成员的概率非常高的记录是最有意义的。也许我们正在出售豪华度假屋出租,我们的方法依赖于人力密集型,亲自推销。在这种情况下,我们可以将销售资源集中在那些购买概率超过某个高阈值的客户身上——通过只锁定这些客户,我们可以避免
在应用预测建模中,库恩和约翰逊描述了“模棱两可或不确定区域”的使用,在该区域中,某个分类根本无法预测。例如,对于两类问题,建模者可以简单地将预测概率在 0.40 到 0.60 之间的样本标记为“不确定”或“不确定”,而使用其余样本进行计算和更详细的分析。 1
白俄罗斯二手车:准备数据&建立模型
在下面的模型中,我们从包含白俄罗斯二手车供应信息的数据集开始。在宁滨之后,二手车价格(我们的结果变量)被分成三个相对平均大小的组,在宁滨之后,其他几个输入变量被用于朴素贝叶斯过程,
这个二手的 CC0 公共领域汽车数据集,由从 Kaggle 下载的数据科学家 Kirill Lepchenkov 创建,包含 38145 个观察值。
要下载 CC0 公共领域数据集,请点击链接:【https://www.kaggle.com/lepchenkov/usedcarscatalog
变量
制造商名称:汽车的制造商名称。有 50 多个分类选项,如讴歌,阿尔法罗密欧,奥迪,宝马,别克,雪佛兰,奔驰,日产,沃尔沃等。
变速器:变速器是机械的还是自动的。
颜色:汽车外观的颜色,有 12 种独特的级别。
year _ producted:汽车生产的年份。这个变量是数字,但是我们把它转换成一个因子。“老”车是指在中间年份之前生产的车,“新”车是指在中间年份或之后生产的车。
发动机 _ 燃料:汽车使用的燃料类型(柴油、电动或燃气)
engine_has_gas: 汽车的发动机是否装有丙烷罐和油管。
engine_capacity: 发动机容量是一个以升为单位的数字变量,但我们将其转换为具有 5 个级别的分类变量:最低发动机容量、略低于平均发动机容量、平均发动机容量、略高于平均发动机容量和最高发动机容量。
车身 _ 类型:汽车的车身类型。这个变量有 12 个级别,包括敞篷车,轿跑车,豪华轿车,皮卡,轿车,通用,SUV 和其他。
已 _ 质保:车是否还有有效质保。
状态:该变量包含三个级别:新建、已拥有和紧急。紧急意味着汽车已经被损坏,有时是严重的。
drive_train: 汽车是否有前置、后置或全轮驱动的传动系统。
is _ exchange:车主是否准备将这辆车换成另一辆车,只需很少或不需要额外付款。
location_region: 有六个级别的类别,每个级别代表白俄罗斯的不同地区。
duration_listed: 汽车在目录中的上市时间,以天为单位。变量是数字,但是我们把它转换成两类:更短的和更长的。
照片数量:目录中发布的汽车照片数量。这个变量是数字,但我们把它转换成两个类别:更高和更低。
price_USD: 目录中所列汽车的价格,单位为美元。这个变量是数值型的,但是我们转换成了一个分类变量,有三个分类:便宜的、中等的和昂贵的。
首先,我们需要加载我们将用于分析的所有库,并将数据集加载到环境中。此处使用 read_csv2(),因为数据集包含一些西里尔字符,还因为它包含一个“;”列之间的分隔符。
原始数据集附带了一系列 10 个具有布尔值的其他特征变量。我们不清楚这些变量的含义,因此我们从数据集中删除了它们。

我们需要绑定 price_usd 变量,以便它可以用作朴素贝叶斯模型中的分类结果。
首先,我们确定了 386 条缺少 price_usd 值的记录,并从数据集中删除了这些记录:
在 quantile()函数的帮助下,如下所示,我们将记录分成三个平衡的组,分别标记为“便宜”、“中等”和“昂贵”

以下是三个结果类别组,每组中的记录数量几乎相同:

修改后的数据集将被重命名为 carsMod。
接下来,我们将字符变量和逻辑变量转换成因子。


engine_capacity 变量是数值型的,因此我们将其转换为四个分类箱,如下所示:
我们将里程表变量转换成五个分类箱,如下所示:
变量 year_produced、number_of_photos、up_counter、和 duration_listed 均根据各自的中值分为两个结果组。这些转换按如下方式进行:
最后,如下所示,我们的变量被完全转换为因子:

然后,我们对这些数据进行分区,随机将 60%的记录分配给训练集,另外 40%分配给验证集。这里使用种子值 699 是为了代码的可复制性。
为了评估我们的潜在独立变量在朴素贝叶斯模型中的适用性,我们构建了比例柱状图。这些柱状图仅使用训练集数据构建,它们展示了这些潜在输入变量的不同级别之间 price_usd 的比例差异。





基于这些图表,我们决定删除列出的和可交换的,因为这些变量的不同水平不会显著影响价格。
我们还删除了制造商名称和型号名称,它们分别包含 55 个和 1,116 个唯一级别。
接下来,我们运行朴素贝叶斯分类模型:
然后,我们使用训练集数据生成了以下混淆矩阵:


从上面显示的两个混淆矩阵中,我们可以看到训练集和验证集的模型性能非常相似,这表明模型没有过度适应训练集。在这两种情况下,我们都可以看到朴素规则(无信息率)的显著优势,准确率提高了一倍以上。
改变游戏:缩小关注范围
如果我们的目标不是预测一辆车将落入三个结果类别中的哪一个,而是将我们的注意力转移到最有可能落入特定结果类别的车,会怎么样?通过检查与类别预测相关联的概率,我们可以实现这一点。
首先,我们使用带有 type="raw "参数的 predict()函数,该函数返回我们的模型分配给每个记录的类成员的百分比可能性。(为了简洁起见,我们不会解释这些数字产生的整个过程,但是我们推荐这个 YouTube 教程来分解分类概率是如何产生的)。
将结果类概率预测附加到验证集之后,我们可以使用 dplyr 中的 arrange()函数按照预测的类概率对记录进行排序。例如,下面的第一行代码给出了验证集中最有可能属于“便宜”结果组的 500 辆汽车;它下面的一行生成一个表,显示这 500 条记录的实际结果类。

从这个组中,我们可以看到 89.2%的汽车属于“便宜”的结果组。如果我们专门寻找价格未知但预计在此范围内的汽车,这样的过程将具有巨大的实用价值——对于我们决定更仔细检查的每 10 辆汽车,9 辆将定价在我们的目标范围内。
我们可以对其他结果类使用类似的过程:

最后,我们可以对“昂贵的”结果组重复这个过程:

当我们在模型中筛选出我们的模型最强烈地预测为“昂贵”的 500 条记录时,我们几乎总是会识别出昂贵的汽车。
但是等等,这不是作弊吗?
首先,仅对潜在分类概率满足某个阈值的记录进行分类的概念可能看起来不公平。在某些方面,这是不公平的——如果我们将使用这种方法的模型的性能与试图对它看到的每条记录进行分类的模型的性能进行比较,那么是的,这将是一个完全不公平的比较。
当然,计划发布或分享结果的公司或研究人员必须非常明确地向观众披露,这些结果是在可疑区域的记录被排除在考虑范围之外之后才获得的。例如,我们永远不能声称上面显示的朴素贝叶斯模型在标记昂贵的汽车时达到了“98.6%的准确率”,而没有非常清楚地指出这只是在以这样一种方式过滤数据后发生的,即只有最有可能属于“昂贵”组的汽车保留下来。
然而,在商业世界中,模型不是为黑客马拉松或出版物而构建的。相反,它们是出于实际目的而构建的,从大量记录中消除“可疑区域”记录可以带来非常重要的实际好处。对于回答诸如客户是否会续订、潜在客户是否会购买或当前客户是否会升级到高级计划等业务问题,支持模型分类预测的概率可以提供大量信息线索,表明企业应集中精力在哪些方面,以获得最大的投资回报(ROI)。承认 51%的可能性和 99%的可能性是非常有意义的不同——即使它们导致相同的预测分类结果——是从任何分类模型生成最佳可能 ROI 的一大步。

摄影师 Pix 的生活。(2015 年 8 月 11 日)。Fotografì a de paisaje de coches 汽车查看照片。从 Pexels 检索。
参考文献
- 库恩,马克斯和杰尔·约翰逊。应用预测建模。斯普林格,2013 年:第 254 页。
- 基里尔·列普琴科。(2019 年 12 月 2 日)。特许 CC0 公共领域二手车目录数据集。从卡格尔那里取回的。https://www.kaggle.com/lepchenkov/usedcarscatalog
变量误差模型:戴明回归
实践教程
当到处都是错误的时候,得到正确的结论

介绍
机器学习通常都是关于以下问题:
给定一个数据集(X,y),其中 X 是特征矩阵,y 是目标向量,求一个 f,f(X) ≈ y。
因为目标值 y 存在误差,我们通常做而不是执行严格的等式àlaf(X)=y。这些错误源于这样一个事实,即我们通常无法观察到宇宙中的所有事物并将其放入我们的特征矩阵中。即使如果我们可以,量子力学告诉我们,系统中可能仍然存在随机性,这使得我们有可能在相同的输入下获得不同的结果。
长话短说,标签是嘈杂的,我们通过引入一个误差项 ɛ、来处理这个问题,比如线性回归情况下的 y = ax + b + ɛ* 。目前没有新消息。但请回答我以下问题:
有没有想过特征变量 X 的错误?
我当然没有。所以让我告诉你什么时候这很重要。
动机
我喜欢经典的、不起眼的例子。它们不会让你分心,让你专注于新事物,一步一步来。所以,我们用下面这个:预测一个人的身高,给定体重!以前从没做过这个,对吗?😉
你的目标是找到体重和身高的关系。如果一个人比另一个人重 1 公斤,你能说他们的身高是多少?你想使用线性模型,所以基本任务是计算直线的斜率。
故事:你邀请了 500 人参加你的创新研究,并量了他们的体重和身高。
转折:实验结束后,你注意到你的秤相当不准确——在反复称自己的体重后,你得到了很多满地的数字,尽管它们的均值至少在你的真实体重附近。其他 500 个重量级也是如此。哎呀。这种情况怎么处理?重新运行整个实验肯定是而不是一个选项。

数据
我们从生成一些数据开始,这些数据可能是你在实验中获得的:首先,我们生成真实的重量 x_true ,并从中导出身高 y 。然后,我们给权重 x_true 添加一些噪声,并将结果称为 x 。
注意:我用 X 代替 X,因为我们这里只有一个单一的特性。
import numpy as np
np.random.seed(0)
n = 500
x_true = 10*np.random.randn(n) + 70 # the true, unobserved weights
y = x_true + 100 + 5*np.random.randn(n) # the observed heights
x = x_true + 10*np.random.randn(n) # the noisy, observed weights
在现实生活中,我们只被给予 x 和 y 来处理,所以这就是我们所看到的:

图片由作者提供。
如果我们不知道坏秤,或者根本不关心坏秤,我们可以对有噪声的 x 和 y 应用线性回归模型,例如:
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(x.reshape(-1, 1), y)
print(f'height = {lr.coef_[0]:.2f}*weight + {lr.intercept_:.2f}')
# Output:
# height = 0.50*weight + 134.09
嗯,看起来是个合理的结果:体重越高,身高越高。健全性检查通过。但是,回头看看我们是如何生成高度值的:
y = x_true + 100 + 5*np.random.randn(n)
真正的系数其实是 1,截距是 100,和我们运行线性回归得到的系数 0.5,截距 134 相差甚远。如果这是一个问题,取决于你,虽然。
如果你继续使用你的坏秤,你只是想预测,目前的型号*身高= 0.50 体重+ 134.09 是正确的选择,因为它完全适应这种设置。你只是这样训练它。
另一方面,如果你想知道身高和真实体重之间的真实关系,你必须比那更聪明。问题是你的斜率太小了,也就是俗称的回归稀释或者回归衰减。我们现在来看看如何解决这个问题。
用戴明回归法修复量表
即使是脏数据,也有几种方法可以检索正确的系数。最简单的一种是所谓的戴明回归,这是普通最小二乘法的一种变体,用于解释误差。
戴明回归是以威廉·爱德华·戴明博士的名字命名的,顺便说一下,他甚至没有发明这种方法,但却使它流行起来。发明者 R. J .阿德考克和 C. H .库梅尔也功不可没。
计算直线
在我们开始之前,让我们快速浏览一些符号。你可能已经看到了这些东西,但是让我们在这里让每个人都在同一页上。我们假设 x=(x₁,x₂,x₃,…,xₙ) 和 y=(y₁,y₂,y₃,…,yₙ) 是我们的数据集。然后,我们定义如下:

图片由作者提供。
在我们讨论戴明回归公式之前,让我强调一下,有了这些符号,我们也可以通过

图片由作者提供。
在 Python 中翻译成
s_xy = ((x - x.mean()) * (y - y.mean())).sum()
s_xx = ((x - x.mean())**2).sum()
print(f'slope = {s_xy / s_xx:.2f}')
# Output:
# slope = 0.50
你也可以由此计算截距,这里我省略了。我们从 sklearn 已经知道是 134.09。
现在,让我们为戴明、阿德考克和库梅尔做一些公正的事,并提出调整后的斜率和截距:

图片由作者提供。
好吧,看起来稍微复杂一点,但还是可以处理的。但是公式中的这个 δ 是什么呢?可悲的是,这是这种方法的主要缺点。这不是我们可以计算的,我们只需要知道或者猜测。
δ 是 y 的误差方差除以 x 的误差方差的比值。我的意思是这样的:回归公式 y = ax + b + ɛ* ,包含一个正态分布的随机变量 ɛ ,所以我们假设。σᵧ.的情况有些变化这就是我说的“y 中误差的方差”。
我们进一步假设我们观察到的权重 x 也是通过将高斯噪声添加到真实权重 x_true,而获得的,真实权重也具有一些方差,即“x”中误差的方差。**
在我们的例子中,我们被给予了一切。让我粘贴我们用来创建数据集的公式:
*x_true = 10*np.random.randn(n) + 70
y = x_true + 100 + 5*np.random.randn(n)
x = x_true + 10*np.random.randn(n)*
这里我们可以看到 y 中误差的方差正好是5 = 25。 x 中误差的方差为 10 = 100,因此δ= 25/100 =1/4。插上所有东西给我们
*x_mean = x.mean()
y_mean = y.mean()
s_xy = ((x - x.mean()) * (y - y.mean())).sum()
s_xx = ((x - x.mean())**2).sum()
s_yy = ((y - y.mean())**2).sum()
delta = 1/4
deming_slope = (s_yy - delta*s_xx + np.sqrt((s_yy - delta*s_xx)**2 + 4*delta*s_xy**2)) / (2*s_xy)
deming_intercept = y_mean - x_mean*deming_slope
print(f'Deming slope = {deming_slope:.2f}')
print(f'Deming intercept = {deming_intercept:.2f}')
# Output:
# Deming slope = 1.03
# Deming intercept = 97.01*
这已经足够接近真实值 1 和 100 了!当然,这不是 100%准确,但这是最大似然估计的通常处理方式。我们可以想象结果:

图片由作者提供。
我们可以再次看到,在噪声数据上训练的回归线过于平坦——所谓的回归稀释。
观察
我之前说过戴明回归是一元简单线性回归的变体。更准确的说,甚至是线性回归的推广。我的意思是:
如果特征 x 中没有错误,戴明回归和简单线性回归产生相同的结果。
对我来说,这是相当直观的,但看公式,很难看出。我不会在这里证明,但我会努力让你相信。
如果 x 没有误差,那么 x 中误差的方差是零,这意味着实际上没有我们可以计算的 δ ,因为我们会除以零。但是让我们天真地说 δ 在这种情况下是无穷大因为对于s0 我们有

图片由作者提供。
或者对非数学家来说:
一个正数除以一个极小的正数就是一个大数。
由于我们不能直接插入无穷大,所以我们使用了一个穷人的技巧,通过将 δ 设置为一个巨大的数字来模拟。**
*x_mean = x.mean()
y_mean = y.mean()
s_xy = ((x - x.mean()) * (y - y.mean())).sum()
s_xx = ((x - x.mean())**2).sum()
s_yy = ((y - y.mean())**2).sum()
delta = 99999
deming_slope = (s_yy - delta*s_xx + np.sqrt((s_yy - delta*s_xx)**2 + 4*delta*s_xy**2)) / (2*s_xy)
deming_intercept = y_mean - x_mean*deming_slope
print(f'Deming slope = {deming_slope:.2f}')
print(f'Deming intercept = {deming_intercept:.2f}')
# Output:
# Deming slope = 0.50
# Deming intercept = 134.09*
这正是我们简单的线性回归结果,好看!小心:* 这不是一张的校样,然而却给人一种美好的感觉。*
再想想另一个极端:如果特征的误差特别大,比如说无穷大会怎么样?嗯,然后 δ 变为零整个戴明斜率项也变为零,这很容易计算(做吧!).截距就是标签 y 的平均值。基本上,模型只是一条平面线。
这在直觉上也是有意义的,因为这些特性在这种情况下基本上是无用的,所以最好忽略它们,就像根本没有特性一样。而没有使平方和误差最小化的特征的最佳模型只是常数均值预测。
结论
在本文中,我们介绍了特性 X 中的错误问题,以及它们如何影响变量之间的真实关系。简单线性回归产生的斜率通常大大低于真实值,称为回归稀释。
然而,聪明人发明了另一种回归来调整这些错误:戴明回归。这使得仍然可以得出正确的结论,而不需要另一次重新运行实验,这可以节省时间、金钱和精力。
戴明回归很棒,但是猜测 δ 的正确值很棘手,这是一个主要缺点,因为回归的结果严重依赖于 δ 。尽管如此,你甚至可以在特性出错的情况下发表声明,这很好。
现在一个自然的问题是:如果我们有一个以上的特征,会发生什么?数学家为我们介绍了总体最小二乘法,这是对多个变量的戴明回归的直接推广。还有更多的型号,正如你在这里看到的。
现在你知道了,关注你所有测量中的误差,并采取相应措施尽可能地削弱它们的影响是有意义的。
我希望你今天学到了新的、有趣的、有用的东西。感谢阅读!
作为最后一点,如果你
- 想支持我多写点机器学习和
- 无论如何都要计划获得中等订阅量,
为什么不做 通过这个环节 ?这将对我帮助很大!😊
透明地说,给你的价格不变,但大约一半的订阅费直接归我。
非常感谢,如果你考虑支持我的话!
有问题就在LinkedIn上写我!
统计建模中的错误
(或者为什么要让人类参与进来)

卢克·切瑟在 Unsplash 上的照片
统计模型的准确性不仅取决于模型中包含的信息,也取决于模型中遗漏的信息。在这篇文章中,我将讨论错误的两个主要概念:可约性和不可约性,以及为什么领域知识和主题专家是最大化模型准确性的关键。
假设您希望了解组织中影响销售的因素,以便更好地预测和(希望)控制未来几个季度的业绩。你从销售部门开始,询问表现最好的人对销售驱动因素的看法。
“销售是一项复杂的工作”,他们说。“一个季度的业绩有很多变动因素,每个季度都有变化。我无法告诉你影响销售的所有因素,但根据我的经验,广告是最重要的因素”。
这为您提供了一个系统建模的起点:
销售额=广告预算+其他一切
或者,

“其他一切”(或ϵ)是不可约误差,而 f(X) 是使用广告预算数据构建的统计模型。 您可以尽可能地细化模型,但这样做不会增加广告预算提供的信息之外的任何信息。因此,不可约误差可以被认为是提供了模型预测精度的上限** 。

其中 Var (ϵ)是随机误差项的方差。(假设: f (X)独立于ϵ,均值为零)
因此,你的首要任务是识别出所有对 Y 有较大影响的可测相关回归变量( X_1,X_2,…,X_j ,表示为 X )。在上面的案例中,这些可能包括按渠道细分的社交媒体广告预算:脸书、Instagram、Twitter、抖音等。以及电视和报纸等传统媒体广告。
下一个任务是找到 f(X) ,一个表示广告预算提供的关于销售的系统信息的函数。一般来说, f 是未知的,必须使用一些观测数据点来估计。比方说,我们对 f(X) 的最佳估计是 f(X)^ ,这给出了 Y^ (对 Y 的预测),这样

因此,Y^作为 y 预测的准确性取决于两个量:
- ****可减少的误差,或者我们对 f 的估计与广告预算和销售之间的真实函数映射有多接近:

2.不可约误差,或先前确定的回归变量遗漏的信息。
想象一下,这个人忘记提到该组织在特定的播客或广播频道上做广告。或者他们提到了这一点,但也指出新闻部没有收集任何关于广播预算的数据。所有不能包含在 X 中的相关信息都收集在术语:【var(ϵ】下。
形式上,我们模型中的平均误差(或预期误差)可以写成:

统计学习导论:在 R 中的应用
其中 E(Y- Y^) 表示 Y 的预测值与实际值的平方差的平均值,即模型的平均预测误差。
为什么把错误形式化为可约和不可约?
将误差项分解成可约项和不可约项可能会被认为是不必要的,并成为引入过于复杂的数学符号的借口。然而,以这种方式形式化我们的思维是有实际价值的。当与技术利益相关者交流时,强调模型的准确性有一个上限(不可减少的误差)是有帮助的。当任务是提高系统的预测准确性时,可以依靠这些词汇来澄清问题,并在使用更复杂的模型或收集更多数据之间做出决定。最重要的是,以这种方式组织我们的理解有助于发现以前被忽视的重要数据源。在上述场景中,这可能导致包括广播和播客广告预算或收集这种数据,如果还没有的话。
**我喜欢这么想:
- 可约误差是一个基于数据的函数与真函数之间的差距。这是统计技术展示其威力的舞台,让我们尽可能接近真实的关系。**
- 不可约误差是现实和我们对它的数学表述之间的差距。这种差距更难克服,因为即使您能够捕获系统的所有相关回归变量,也总是会有随机变化(也称为随机噪声),这不会有助于您得出干净的模型。
参考文献 1。统计学习导论:在 R. (2021)中的应用。纽约斯普林格出版社。
用 A*搜索算法逃离迷宫
如何逃脱一个致命的迷宫,并避免一个有用的算法危险的敌人
A* (A-Star)搜索算法是最常用的路径规划算法之一。它主要依靠蛮力和试探法来寻找两点之间成本最低的路线。今天我们将学习如何使用它来逃离一个致命的迷宫,同时避免一些潜伏的奸诈的敌人。

我们的英雄,温文尔雅的熊猫,选择了一条成本优化的路线到达了目的地
斯坦福大学的研究人员于 1968 年首次发布了 A*搜索算法,作为机器人Shakey项目的一部分。他们想要一种改进版本的 Dijkstra 算法,该算法使用启发式函数来最小化移动成本,并为机器人规划一条路径,使其能够在建筑物的走廊和不同房间中自主导航。像某种古老的 Roomba 一样,它使用几个距离传感器和物理“保险杠”传感器来检测附近的障碍物,并建立一个本地导航地图。

摇动机器人。维基媒体基金会 CC BY-SA 3.0
与其他搜索或探路者算法相比, A搜索算法*的关键之一是使用一些启发式函数来确定或估计从起点到终点或目标的当前成本和未来成本。基于这种成本计算,算法将探索不同的路径,并选择最小化目标成本的路径。最基本的成本可以很容易地计算为到目标的距离。例如,离目标点最远的位置将比最近的位置更昂贵,然而不同的地形也将比其他位置更昂贵,例如铺设道路上的泥浆。我们还可以为地形中的高度或坡度创建一个成本函数。最后,有些障碍可能是无法逾越的,它们会影响最终的道路。
我们需要做的第一件事是建立和定义我们的世界地图,并定义一些规则,比如我们如何被允许四处移动,以及存在什么样的障碍。
我们将使用笛卡尔坐标的二维地图 x 和 y ,其中 x 是列, y 是行。在地图上的每个坐标上,都有一个位置或单元。这张地图也定义了一些我们如何移动每一步的限制。我们将定义一步向上、向下、向左或向右作为地图中可能的移动。一次一步,不要斜着走。
我们将有 4 种代表障碍的基本细胞类型,一种用于起点,另一种用于目标位置:
🌴手掌代表不可逾越的障碍,形成墙壁和地图边界
👣脚印只是空白
👹鬼兽是我们需要避开的死敌
🐥小鸡也是敌人,但没有我们稍后会发现的那么致命…
🐼温文尔雅的熊猫是我们的英雄,它也代表了地图中的起点
🛖 但是是我们从起点需要到达的目标或终点
有了前面的元素,我们可以构建相当复杂的地图,其中包含几个障碍和到达目标点的路线选项:

我们的初始地图上有障碍物,起点和终点位置。作者图片
为了构建一个地图,我只是使用一个纯文本文件作为解析的输入。我邀请你在 Github中探索文章的 库,并创建和测试你的地图。你甚至可以制造新的障碍和敌人,或者定制他们的费用,我们稍后会看到。
下一步将使用启发式函数构建地图成本。在这种情况下,我选择预处理整个地图,并为地图中的每个位置创建一个成本地图。在另一种情况下,我们可能希望避免成本高昂的预处理,并在每次发现新头寸时按需处理试探法。如果地图内容未知,并且我们需要在浏览位置的同时发现和构建地图,这将非常有用。
为了计算每个位置的成本,我们只需要两个变量:位置的本地成本和到目标的距离成本。本地成本表示穿越该位置的难度,它可以模拟真实地形(如泥地和铺面路)的成本。在泥泞中行走更加困难,成本也更高,所以我们希望尽可能避免这种情况。
在我们的例子中,敌人增加了他们的位置和周围 8 个相邻位置的成本,所以我们想避免靠近敌人一个单元格的距离。怪物比小鸡的敌人花费更多,所以在这种情况下,我们将被迫决定是否靠近它们,选择走在小鸡旁边会花费更少,我们稍后会发现。按照这种成本结构,墙也有指定的成本。但是这个代价太高,无法承担,所以我们主要忽略它们,假设墙是不可逾越的障碍。这些是每单元/位置类型的成本:
🌴城墙: 1000
👣空: 0
👹敌人: 100(并且 100 为周围的 8 个细胞)
🐥小敌人: 50(周围 8 格也是 50)
我们成本计算的第二部分将与距离有关。总的来说,朝着目标前进比离开它的代价要小。因此,我们希望尽可能选择最短的路径来实现目标。
对于这种基于距离的成本,我们可以只使用每个位置到目标位置的欧几里德距离。二维空间中两点间的欧氏距离公式为 d = √((x1-x0) + (y1-y0))。 但是由于我们不能在地图上的点之间移动角度(我们使用的是没有对角线的 4 向移动),距离成本函数可以简化为d = ABS(x1–x0)+ABS(y1-y0)在出租车几何中称为曼哈顿距离。
汇总成本、位置成本和距离成本,我们现在可以构建一个成本图:

使用距离和障碍成本的启发式函数生成成本地图。作者图片
在鲜红色,我们可以看到一些无法承受的成本超过 1000 的墙壁。然后是暗红色的 oni 怪物区域。小鸡成本区域用紫色表示。最后,带有到目标位置的距离成本的开放空间用更深的蓝色表示。
正如你所看到的,正确缩放这个成本图将是搜索算法找到从起点到目标点的最佳路线的关键。
现在已经准备好了初始地图和地图成本,我们可以开始执行 A搜索算法。在 Github 的 文章库中,我使用了一个完整的 Python 实现,不仅用于算法本身,还用于可视化和解析输入映射和成本映射。但是我在这篇文章中的目标是探索一般意义上的实现,而不是特别关注任何语言。*
我们将在 A搜索算法实现中使用的第一个数据结构是一个简单的节点列表。该节点列表将代表一条通往目标位置的路径,我们将逐节点构建该路径,以便找到更好的方法来绕过障碍物,同时最大限度地降低路径的累积成本。*
一个节点表示地图中具有累积成本的位置,并且它将引用所连接的父节点。从最后一个子节点到第一个父节点反向遍历节点将产生从起点到列表中最后一个节点的路由。
*class PositionNode: def __init__(self, parent: "PositionNode", position: tuple):
self.cost = 0
self.parent: "PositionNode" = parent
self.position: tuple = position
def __eq__(self, other: "PositionNode"):
return self.position == other.position*
如您所见, PositionNode 类有 3 个属性:
成本 与路线开始时的累计成本相关。
position为一个元组,或者以 x 和 y .
parent为参照的两个位置数组,指向的父节点对象是 PositionNode 类的另一个对象。在父中具有 null 或 None 值的对象将是路径中的根节点或起始位置。**

在链接节点列表中引用父节点的节点表示通过具有累积成本的地图中的位置的路径。作者图片
接下来,我们将把两个节点对象列表(未连接)定义为对象数组:
nodes_to_visit[] 这里,我们将添加从移动中新发现的节点/位置及其父节点和累计成本。这个未连接节点的列表实际上是节点的优先级队列,其中我们添加的第一个节点将是我们按照 FIFO 数据元素顺序弹出来处理的第一个节点。
nodes_visited[] 这里我们将把已经访问过的节点/位置从之前的列表中移走,不再需要检查。
在进入主循环之前,我们可以在 nodes_to_visit 列表中设置起始位置为第一个要访问的节点。
运行主搜索算法循环将如下所示:
1.检查列表中要访问的节点是否具有更低的开销。如果是,选择它作为当前节点。
2。将当前节点从“要访问的节点”列表移动到“已访问的节点”列表。
3。通过检查位置坐标来检查当前节点是否确实是目标位置。如果是这样,我们就完了!我们将返回应该是目标位置节点的最后一个链接节点。
4。从当前节点检查所有可能的移动并创建它们的节点,更新所有节点的当前成本
5。如果相同位置不在列表中或其累积成本低于相同位置的先前添加的节点,则仅将新节点添加到要访问的节点列表中。注意这一点很重要,因为我们可能会不止一次地访问同一个位置,但作为不同路线的一部分,累积成本较低。****
如果我们在主搜索循环中用完了要访问的节点,这意味着在地图中找不到路径,可能是因为从起始位置到目标位置是断开的,或者被一些不可逾越的障碍(如墙壁)阻挡。
成功到达目标点后,我们可以返回最后一个连接的节点,我们需要通过使用引用的父节点反向遍历节点,直到最后一个节点具有 null 或 None 父节点,来重建点的路径。颠倒这一系列的点将会给我们从开始到结束位置的正确顺序。

反转节点链表的位置后,解析带星号的路径
如果我们仔细研究选择的路径,我们会惊讶于实际优化的程度!开放空间的第一个优化是距离成本。该路径仅行走使到目标位置的距离更短的位置,同时避开昂贵的障碍物。
接下来,它试图优化(已经聚合了距离成本)怪物成本。它避免了第一个巨大的成本区域,甚至以采取一些额外的步骤为代价。第二场遭遇战更有趣,因为有两种方式:一种是短距离的,但有一个昂贵的怪物,另一种是长时间的,但有一个较小的敌人,费用较低(小鸡)。在这种情况下,绕道面对小鸡的成本比走捷径面对怪兽的成本要低。最后一场遭遇战也展示了这些基于成本的决策,有怪物时可以选择较短的路径,但要到达目标位置,最好选择没有怪物的较长路径。
现在让我们来探索这些基于成本的决策的局限性。在下一个迷宫中,所选择的路线也试图避开敌人,并选择一条明显更长的路径到达目标位置:

温文尔雅的熊猫绕了一大圈以避开小鸡。作者图片
然而,我们在同一个地图中稍微多绕了一圈(只增加了一行)。我们将到达一些成本断点,在这里绕道的成本比面对小鸡敌人和采取最短的路线更大:

温顺的熊猫面对他的第二大敌人,但提前回家了。作者图片
正如你所看到的,这种行为和决策过程完全取决于启发式函数的设计和对案例的缩放。该算法本身将访问所有邻近的允许位置,以计算出最低的路线。
您可以做一些改进和优化来调整 A搜索算法*的基本方法,以适应不同的情况。如果你想更深入地了解这个算法,我推荐你阅读 Amit Patel 关于 A* 的笔记、寻路、游戏几何等等。
我还强烈推荐您使用我们使用过的相同示例和地图解析器的完整 Python 实现以及整个 A搜索算法*来探索这篇文章在 Github 中的存储库,并尝试构建自己的地图和引入新的障碍。
有一个快乐的路径规划!
使用统一星型模式避开 SQL 陷阱
现代分析
风扇陷阱和裂缝陷阱的优雅解决方案
如果您曾经使用过关系数据库,您可能会遇到某些表组合之间的JOIN产生不正确输出的情况。在某些情况下,我们可能会马上发现这些问题,但在其他时候,这些问题会被忽略,直到用户或客户通知我们不正确的结果。
这有很高的成本,因为对数据系统的信任很难赢得,也很容易失去。
当这样的事情发生时,我们通常会争先恐后地逐个解决问题,但是如果我们暂停一会儿,我们会发现这些问题实际上是由某些模式造成的。
导致这些自残的两种常见模式是粉丝陷阱和鸿沟陷阱。
在网上做了进一步的研究后,我注意到我几乎找不到关于这些模式的高质量信息。当你发现它被提到的时候,它经常被不一致的定义,不适当的限制,或者过于复杂。最重要的是,对于这些问题,我找不到一个不依赖于任何特定软件的优雅或通用的解决方案。
幸运的是,我最近看到了弗朗西斯科·普皮尼写的一本书,书名是统一星形模式 *。在那本书里,Francesco 清楚地指出了扇形陷阱和裂缝陷阱的根本原因,并提出了一种新的和创新的建模方法来解决这两个问题,以及许多其他挑战。
在 Francesco 的祝福下,我想利用这篇文章作为一个机会来阐述他书中的观点。统一星型模式有很多优点,我觉得 fan trap 和 chasm trap 是理解这种新模式优点的很好的切入点。
什么是粉丝陷阱?
在数据建模中,当一个表中的多行引用另一个包含度量的表中的单个键时,就会出现扇形陷阱,从而导致最终连接结果中的度量重复。
“度量”只是一个数字,例如销售额或库存数量,可以使用算术方法进行聚合。然而,并不是所有的数字都是衡量标准。例如,一年是一个数字,但不是一个度量,因为在我们的分析中,我们可能永远不会将两年相加或平均。
让我们以一次销售在一段时间内有多次退货为例。Returns 表保存 Sales 表的外键。

图一。风扇陷阱示例。#代表一个度量值。|作者图片
Returns 表可以有多个引用特定销售行的行,因此会复制销售度量,如销售量。注意,Returns 表也可以有度量,比如返回的数量,但是这并不是退货和销售之间存在粉丝陷阱的必要条件。但是,如果 Returns 表成为另一个具有类似多对一配置的表的目标,形成另一个 fan 陷阱,那么它就变得相关了。

图二。退货和销售表。销售表中的销售量度量是兴趣|作者图片的度量
忽略我们的客户似乎不喜欢我们的产品这一事实,我们可以看到已经购买了 12 件商品,退回了 12 件商品。
当我们连接这些表时会发生什么?

图三。联接这两个表会导致 Sales 表中的重复度量值。|作者图片
哎呀…销售表中的行被退货表中的行展开。不幸的是,即使是有经验的分析师通常也不会意识到这种情况的发生,并继续构建关于这种不良连接的报告。大多数工具也不会在发生风扇陷阱时发出警告。
因此,报告中总的销售量看起来是 17,而不是正确的值 12。请注意,基于返回的数量的报告将是正确的。这是因为返回的数量来自一个没有被任何其他表分解的表。
扇形陷阱问题并不仅限于表中存在的数据。即使对带有度量的目标记录的引用是隐式编码的,也会发生这种情况,这在 JSON 或 XML 文档等嵌套结构中很常见。考虑这个假设的电影 API 查询返回每部电影的标签的结果。
**Movie API Results**{
"results": [
{
"Film": "F1",
"Duration": 120,
"Tags": ["Comedy", "Drama"]
},
{
"Film": "F2",
"Duration": 100,
"Tags": ["Comedy", "Horror"]
},
]
}
使用 JSON 扁平化工具将这些数据导入数据库是相当常见的,可能发生的情况是持续时间度量被标签分解。

图 4。导致不正确测量的 JSON fan 陷阱示例:显示的总数是 440,而正确的总数是 220 |图片作者
什么是鸿沟陷阱?
当一个目标表(通过外键引用)被两个或更多的其他表分解时,就会出现鸿沟陷阱,产生类似笛卡儿积的效果。由于参与的表的数量是无限的,并且它们都互相爆炸,所以与扇形陷阱相比,鸿沟陷阱会产生更多不需要的副本。
例如,考虑以下表示 Twitter 的数据模型:

图五。裂缝陷阱的例子。|作者图片
这种数据模型在应用程序的数据库中肯定是正确的,因为应用程序通常一次处理单个操作。然而,在支持分析的数据库中(例如数据仓库),这种结构存在很大的风险。SQL 开发人员可以安全地创建包含用户和 Tweets 的查询,以及包含用户和关注者的查询。但是如果查询同时涉及三个表,结果将会爆炸,并且每个度量(位于每个表中)也会爆炸,产生不正确的总计。
当 Users 表中的一行同时匹配 Tweets 表中的多行和 Followers 表中的多行时,就会出现问题。

图六。多条推文和多个关注者匹配一个用户|作者图片
让我们看看当我们SELECT FROM用户和LEFT JOIN推特和追随者时会发生什么。

图 7。由裂缝陷阱引起的爆炸。我们通过作者获得 11 行|图像
tweets 和 followers 表中匹配的每个用户都有一个笛卡尔乘积。不仅仅是用户行爆炸了。推文也是爆炸式的关注者,反之亦然。
你加入什么样的组织并不重要。如果用一个INNER JOIN代替一个LEFT JOIN,用户@qqq 和@www 会消失,但剩下的用户还是会有一个由鸿沟陷阱引起的笛卡尔爆炸。
爆炸只是与笛卡尔坐标相似。如果是完全的笛卡尔坐标(通过CROSS JOIN命令获得),有 7 条 tweets 和 5 个追随者,我们将获得 35 行。相反,我们只获得 11 行,因为笛卡尔积只在“逐个用户”的基础上发生。
但是……这个数字 11 是从哪里来的呢?
很容易推导出一个公式,计算出您可以从鸿沟陷阱中获得的记录数。您甚至可以使用这个公式来自动检查自动化数据质量管道中的裂缝陷阱。
下面,您将看到一个图表,它根据我们的示例说明了如何预先计算每个用户贡献的行数。这种计算可以在启动查询之前创建,它可以防止我们生成一个庞大的(并不真正有用的)结果表。

图 8。在我们的例子中,如何计算期望从裂缝陷阱中得到的行数
在 Twitter 示例中,如果每个表都有 N 行,那么结果表中行数的下限是 N ,上限是 N ,因为 Users 表是由 2 表定位的。一般来说,如果 Users 表被 k 个表定位,并且它们都有 N 个行,那么结果表将以一个 N^k 行的上限结束。
不难想象陷入鸿沟陷阱的代价。更糟糕的是,引用表本身可能成为其他陷阱的目标,由于嵌套效应,导致更大的爆炸。
图九。由嵌套裂缝陷阱引起的爆炸|来源: giphy
正如您所看到的,鸿沟陷阱爆炸了数据量,却没有提供额外的有用信息。这实际上会累积大量数据,导致存储冗余数据的高成本。
当表中包含度量时,情况会变得更糟。度量的重复不仅是一个系统挑战,也是一个非常严重的语义挑战,因为度量很难去重复。对于文本,您总是可以使用COUNT DISTINCT,但是对于度量,SUM DISTINCT的操作在语义上是无效的,因为它还会丢弃两个度量相同但语义独立的常见场景(例如,金额相同的不同发票)。
像 fan 陷阱一样,chasm 陷阱也扩展到了 JSON 这样的嵌套结构。
**Tweet API Results**{
"results": [
{
"User ID": "@aaa",
"Followers": [
"@bbb",
"@ccc"
],
"Tweets": [
{
"Tweet ID": "T1",
"Tweet": "Are autonomous cars data driven?"
},
{
"Tweet ID": "T2",
"Tweet": "forget data, guessing is the new oil."
}
]
},
{
"User ID": "@bbb",
"Followers": [
"@aaa",
"@www"
],
"Tweets": [
{
"Tweet ID": "T3",
"Tweet": "I just ate an avocado"
}
]
},
{
"User ID": "@ccc",
"Followers": [
"@bbb"
],
"Tweets": [
{
"Tweet ID": "T4",
"Tweet": "sleep. eat. code."
},
{
"Tweet ID": "T5",
"Tweet": "Keep calm and code"
}
]
},
{
"User ID": "@qqq",
"Tweets": [
{
"Tweet ID": "T6",
"Tweet": "I tweet but I have no followers"
},
{
"Tweet ID": "T7",
"Tweet": "I am a lonely bird!"
}
]
},
{
"User ID": "@www",
"Tweets": []
}
]
}
了解粉丝陷阱和鸿沟陷阱的定义是很重要的,因为它让你意识到这个问题。但是,下一步是什么?如何在这些麻烦的表格配置下回答我们的数据问题而不产生重复?
如何解决粉丝陷阱
您会很高兴地知道,许多(但不是全部)BI 工具,如 Tableau、Qlik 和 Power BI 为您解决了这个问题。您可以告诉 BI 工具两个表之间的关系而不是预先创建连接。BI 工具将使用该信息及时创建适当的连接,并根据可视化应用上下文逻辑。因为这两个表中的每一个都以原始粒度(没有重复)存在于内存中,所以 BI 工具能够计算并显示正确的总数。
这个解决方案是理想的,但是它只适用于某些特定的软件。如果您想检查您的工具是否支持关系(有时也称为关联,您可以使用 fan trap 运行一个快速实验,看看输出是否正确。
为了更普遍地解决这个问题,我们需要深入兔子洞,彻底转变我们的视角。
扇形陷阱源于 SQL 连接的基本属性。无论你使用INNER JOIN、LEFT JOIN, RIGHT JOIN还是FULL OUTER JOIN,你总是会以重复结束。
有没有可能在不重复的情况下合并我们的表?是的,它是!不要一个JOIN,创造一个UNION!UNION从不重复。
通常不会想到UNION,因为来自不同表的信息最初不会在同一行上对齐。然而,每个现有的 BI 工具都创建了一个自动聚合,当在聚合可视化中显示时,数字将在同一行上对齐。

图 10。展示理想行为的假设表格|作者图片
总销售量现在是正确的。
这是我们如何组合数据来回答商业智能问题的一个根本性转变。我们不依赖于 JOIN 操作符,而是使用所有数据列的 UNION 。
统一星形模式
Francesco Puppini 是最早意识到使用这种方法的好处的人之一。在他的书 The Unified Star Schema *中,他将这种模式扩展并形式化为一种针对自助式分析进行优化的新建模范式。
让我们看看如何使用统一星型模式(USS)方法构建正确版本的表。
USS 建议的一个立竿见影的效果是为键建立了一个标准的命名约定,这样每个表如何映射到其他表就变得很明显了。具有组合键的表被连接(或者散列)成一个键列。该键列以_KEY_为前缀。

图 11。将 USS 命名惯例应用于我们的原始表格|作者图片
请注意,带有_KEY_前缀的列是技术列,它们决不能包含在任何可视化中。如果最终用户需要可视化 id(Sale ID、Return ID和Product),这些 id 可以保留其原始名称,并且_KEY_列将是相同的副本。这在数据存储中产生了一点冗余,但是它清楚地划分了技术列和业务列。
为了正确地构建表格,我们需要将这两个表格映射成所需的形状。这是通过普皮尼桥完成的。
Puppini 桥是一个表,它对数据模型中实体之间的每个现有关系进行编码。这是通过从所有源表中读取所有键并用一个UNION将它们合并来实现的。Stage列告诉我们为每一行提供数据的源表的名称。

图 12 是 Puppini 桥的基本结构,充当所有记录|作者图像的关系映射层
你可以把普皮尼桥想象成一个预先连接了所有记录的中央交换机。
这很有意义,因为我们通常知道这些实体应该如何连接…如果我们可以在关系中满足 90%的用例,那么让业务用户千辛万苦地阅读关于数据关系的手册是没有意义的。
过去曾尝试过预连接所有的表,比如 SAP Business Objects 的“Universe”。这个解决方案很棒,但是很快就发现不同的需求需要不同的世界,以稍微不同的方式组织表格。
使用统一的星型模式,最终用户总是使用 Puppini 桥开始查询,然后添加他们需要的任何表。他们不再需要知道如何连接表,因为这已经由桥处理了。不再期望它们创建表链,因为桥解析所有的表链。
根据 BI 工具的不同,表将通过关系(关联)或LEFT JOIN来连接。键列总是有相同的名称,允许大多数 BI 工具默认自动匹配它们。对于绝大多数业务需求,连接表的方式保持不变。
普皮尼桥越来越像我们的目标州了!不幸的是,这还没有解决我们的粉丝陷阱问题,因为如果我们将 Sales 表LEFT JOIN到桥中,我们实际上会意外地将 Sales measures 映射到 Returns 行。
为了达到我们想要的状态,我们需要在销售阶段将销售度量映射到适当的行。我们可以通过将度量移到桥上来实现。
请注意,这种方法仅适用于有被风扇陷阱爆炸风险的表格。在我们的示例中,只有销售表需要它。然而,这种方法也可以有选择地扩展到退货。
一般来说,如果所有的度量都被移到 Puppini 桥,那么这个表就会变成一个“超级事实表”。传统上是“单一事实”的 OLAP 立方体将无缝地支持“多事实”场景。Looker 将不再需要应用非常耗费资源的“扇出公式”。每一个 BI 工具,有了“超级事实表”,将能够超越自己的极限。
将度量移到桥上
在这种方法中,我们将所有有爆炸风险的表直接嵌入到 Puppini 桥中。

图 13。将有爆炸风险的措施移至普皮尼桥|图片由作者提供
请注意,退回的数量不包括在普皮尼桥中,因为它没有爆炸的风险。该上下文可以通过 Returns 表的LEFT JOIN引入。
使用这种方法,您可以根据销售的属性过滤退货,这是传统的 Kimball 建模所不能实现的操作。
如何解决鸿沟陷阱
有了建造普皮尼桥的所有基础工作,我们可以免费得到这座桥!
为什么?
统一星型模式从定义上来说是安全的,因为我们使用一个 UNION 操作来构建 Puppini 桥,它不会产生重复。

图 14。演示普皮尼桥如何通过小心放置钥匙来解除裂缝陷阱|图片由作者提供
Followers 和 Tweets 表在桥中不指向彼此,因此避免了鸿沟陷阱。
请注意,桥中还包含了 Users 阶段,即使 Users 表没有指向任何表:这将确保所有用户都会出现在桥中,即使他们没有追随者和 tweets。这被称为“完全外部连接效应”:尽管 BI 工具中的终端用户将创建一个LEFT JOIN,但结果看起来更像一个FULL OUTER JOIN。
结论
我希望你现在对扇形陷阱和裂缝陷阱如何在你的分析中引入不必要的重复有一个明确的理解。现在,您应该能够通过查看表的连接配置来快速识别它们。
如果没有我从阅读统一星型模式 *中获得的知识,这篇文章是不可能的,并且只是你将在书中找到的一小部分。如果你想更深入地研究这种方法,我强烈建议你买一本。
这本书非常容易理解,还解释了统一星型模式如何解决其他挑战,如循环、跨多个事实表的查询以及不一致的粒度。事实上,比尔·恩门(数据仓库之父)写了这本书的前几章,这应该扩大了这些想法的范围。
如果你对这些概念有任何疑问,请随时在 LinkedIn 上联系我。我将继续更详细地讨论这个主题,所以请务必订阅时事通讯,以便获得新文章的通知。
*这本书的链接是附属链接,这意味着你不需要支付任何费用,如果你点击并购买这本书,我会赚取佣金
想进一步讨论这个吗?请在 Linkedin 上发表评论或与我联系。
📕这篇文章最初发表在 Show Me The Data 博客上,在那里我讨论了更多关于数据驱动业务的话题。
深奥的排序算法以及如何用 Python 实现它们
代码?
当快速排序太快时

卢卡斯·乔治·温迪特在 Unsplash 上的照片
W 当你听到排序算法的时候,你脑海中首先想到的是哪一个?快速排序?合并排序?冒泡排序?
回到大学,我的一位教授总是告诉全班同学,快速排序就像排序世界的苹果,因为他们非常擅长营销。通过将其命名为“quick”,人们将它们与最快的排序算法联系在一起,即使对于一般情况来说合并排序实际上更快。
撇开虚假广告不谈,我很肯定你听说过这些算法。让我们继续看最近的。
Python 使用的是 Timsort ,一种由归并排序和插入排序衍生而来的混合算法。Timsort 这个名字来自作者 Tim Peter,他在 2002 年为 Python 创建了这个名字。
对于特定的正数,我们有 基数排序 。它是计数排序和黑魔法的组合,管理复杂度为 O(d*(n+b)),其中 d 是列表中的位数,n 是列表中的元素数,b 是使用的基数,通常为基数 10。
最近在 2020 年,一种叫做 四分类 的新算法出现了。这是一个超优化合并排序的 1500 行实现,能够在多种情况下击败 Timsort。作者好心地为我们这些凡人提供了可视化和基准。
您可能认为我们将深入研究 Quadsort 的 1500 行代码,但是您错了。我们在这里看一看更有趣的排序算法。
深奥的ɛsəˈtɛrɪk 非常不寻常,只有少数人理解或喜欢,尤其是那些有专门知识的人
博戈索特
复杂度:O(n * n!)
经典的排序算法有许多别名,其中包括排列排序、愚蠢排序、慢速排序和猴子排序。
该算法的主要思想是随机打乱列表中的元素,直到它被排序。多亏了 Python 内置的random模块,我们只用几行代码就可以实现它。与实现快速排序或合并排序相比,这无疑要容易得多。
这意味着有 n 个!可能的组合,并且因为对于每一次迭代,我们将花费 O(n)时间来混洗列表,并且另外花费 O(n)时间来检查列表是否被排序,我们最终得到 O(n * n!)对于复杂性。
Bogobogosort
复杂:O(我的天)
Bogosort 的高级变体。Bogobogosort 认为最初的 bogosort 算法效率太高,这就是为什么它引入了额外的步骤,使算法更加复杂。
Bogobogosort 将使用 bogosort 对列表中的前两个元素进行排序,并继续向上排序,直到所有元素都排序完毕。在每个 bogosort 之后,它将检查下一个元素是否已经处于正确的顺序。如果顺序不正确,我们将列表随机化,并再次返回到前两个元素。
要看原描述和详细分析,可以访问这个页面。
量子 Bogosort
复杂度:O(n)
也被称为多元宇宙分类,这是最终的博戈索特变异。不幸的是,我们找不到这种变化的 Python 代码,但这里有伪代码。(来源)
理论上,创造 n!宇宙分支的成本为 O(1)。O(n)复杂度来自于检查列表是否排序。我们假设破坏包含未排序列表的宇宙的成本也是 O(1)。
奇迹排序
复杂度:O(???)
请看下面 StackOverflow 答案启发的排序算法。直接忽略讨论的标题,不相关。
https://stackoverflow.com/a/6947808
"最终,阿尔法粒子翻转内存芯片中的位应该会导致成功的排序."
我为奇迹等待时间实现了一个补偿机制,以增加在检查列表是否排序之间奇迹发生的机会。
使用上面的代码,我已经确认了这种排序算法确实有效。对于已经排序的列表,它在 O(n)时间内返回。尽管如此,我仍然在等待未排序列表的奇迹。
专业提示:实际上你可以用某种祈祷来代替"Waiting for miracle..."信息,称之为祈祷类。
斯大林式的
复杂度:O(n)
啊,一种运行在 O(n)中的高级排序算法,灵感来自非营利社交媒体乳齿象的这条“推特”。
https://mastodon.social/@mathew/100958177234287431
“……任何无序的元素都将被删除……”
该算法将遍历列表一次,并删除任何无序的元素—升序或降序。它将总是在 O(n)时间内产生一个排序列表,尽管可能会丢失一些元素。
谢天谢地,有一个 Stalin 排序的变体,它实际上将所有项目都保留在列表中。这个想法是把不按顺序的元素推到列表的前面,然后重复这个步骤,直到列表被排序。不幸的是,它没有运行 O(n)时间,这意味着它比原来的斯大林排序差。
灭霸排序
复杂度:O(n log n)
一个比较现代的排序算法,灵感来自漫威的灭霸哲学。我们实现了一个名为snap()的特殊函数来随机删除列表中的一半成员。
这个想法是检查列表是否排序。如果没有,我们将抓取列表,然后再次检查列表是否已经排序。重复此操作,直到列表真正排序完毕。
检查列表将花费 O(n)时间,由于snap()函数每次将元素减半,每次迭代将花费 O(log n)时间。考虑到这两点,该算法的复杂度为 O(n log n)。
灭霸排序的一种变体是真正的灭霸排序,在这种排序中,我们只进行一次snap()函数调用,而不检查列表是否已排序。该操作将元素数量减半,有助于将来的排序。
智能设计排序
复杂度:O(1)
智能设计是一种信仰,认为造物主通过设计创造了宇宙内部的某些事物或事件。
假设列表中的所有元素都是唯一的,那么有 n 个!可能的组合,其中 n 是列表中元素的数量。这意味着当前顺序只出现在 n 中的 1!案例。
智能设计排序认为,这种情况意味着造物主以这样一种方式排序列表,它超越了我们对升序和降序的理解。
智能设计的其他命名和哲学变化如下。
- 禅宗排序:当你意识到这个阵法,像所有的事物一样,是短暂的,它的顺序是无足轻重的,所以你就这样离开它,转而追求开悟。(来源)
- Trump Sort :数组总是排序的。任何不这么说的人都是假新闻。(来源)
睡眠排序
复杂度:O(最大(输入)+ n)
另一个经典的臭名昭著的排序算法。主要思想是通过将值分配给睡眠定时器来对列表进行排序。当计时器结束时,它会将值添加到结果列表中。
较大的值将具有较长的睡眠计时器,因此它们将被添加到较小的值之后,从而产生一个按升序排序的列表。如果我们需要一个降序列表,我们可以使用result[::-1]简单地颠倒列表顺序
缺点是这只适用于数字列表。哦,如果你试图对非常大的数字进行排序,它也可以运行好几年。
堆栈排序
复杂度:O(取决于)

作者https://gkoberger.github.io/stacksort/截图
没有,这个没有代码,但是你可以访问这个页面自己试试。现在,你可能会认为这个算法以某种方式使用了一个堆栈来根据元素的名称对元素进行排序。这是不正确的。
名称中的 Stack 是 StackOverflow 的简称。是的,没错,堆栈排序会随机抓取带有javascript和sort标签的 StackOverflow 问题的顶部答案
如果你访问堆栈排序页面,它会尝试从 StackOverflow 答案中提取一个函数,编译它,然后尝试用那个函数对输入进行排序。最精彩的部分?你不需要编码,它会告诉你实际使用的是哪个答案。
复杂程度取决于你对爬虫的运气。我很幸运地使用这个答案和另一个答案成功排序,尽管后者返回的是字符串列表而不是整数列表。一些代码成功编译,但仍然无法排序。
代码?是一系列与数据科学和编程相关的幽默。它们可能对你的日常工作没什么用,但它们可能会让你微笑。 定期更新 新故事和 成为中等会员 阅读无限故事。
https://chandraseta.medium.com/membership
咖啡篮隆隆声:机器人 vs 普尔曼 vs 体面
咖啡数据科学
一投命中的精准篮下优势!
我有几个还没有分析的浓缩咖啡过滤篮,我也更新了我的分析代码,所以这是一个好机会。
我要看看机器人、普尔曼和像样的过滤篮的孔径分布。
分析
我使用标准图像处理技术进行滤孔分析。我以前注意到,大多数对篮子进行的分析都是计算阈值以上的像素数量,但我的技术更先进,使用实际的光量来获得一些亚像素精度。
另外,我更新了算法,把所有的洞都改成了圆形。有时它们是椭圆形的。然后我应用了一个全局标准化来解决相机的任何倾斜。这种新算法考虑了大量的相机误差和镜头失真。
从底部
这是底部的视图,带有一个手绘的蓝色圆圈,用于孔洞检测算法。




上图(左:机器人;右:普尔曼)、下(左:DE 18g 右:DE 15g),所有图片作者
从头




上图(左:机器人;右:普尔曼)、下(左:DE 18g 右:德 15g)
我计算了每个过滤器的孔径。

虽然箱线图很好,但汇总表显示了一些有趣的内容。我也有孔距。就球洞大小而言,所有的篮筐都有相似的标准偏差,这很棒,因为它们都应该是精确的篮筐。

这两个像样的篮子有一个稍微不同的洞大小,特别是从顶部。我不确定这是如何影响流动的,因为孔的数量也不同。由于阈值和原始图像,孔的数量略有不同。成像过滤篮比看起来更棘手。
就有洞的总面积而言,除了机器人之外,它们都有非常相似的面积。这意味着机器人过滤器的流量会受到更多限制,但普尔曼和体面将类似地执行。
我们也可以看一下孔的平均尺寸。对于机器人来说,顶部的孔比底部的大,但对于其他三个机器人来说,顶部的孔更大。这意味着甚至咖啡出口的方式也是不同的。问题是机器人的面积比其他地方小,所以很难知道会有什么影响。

我们可以分别看所有的底值和顶值。DE 滤波器的差异很奇怪。我认为洞的大小会更接近。普尔曼有较小的孔直径,但有更多的孔。

空间分析
我用错误的颜色绘制了每张图片上的洞的大小。这是为了了解孔洞大小的分布是否是随机的,或者某些区域是否存在会导致沟道效应的或大或小的孔洞群?
为了理解下面的假色,颜色根据特定过滤器的最小和最大孔径从 0 调整到 1。

示例图像
从底部




上图(左:机器人;右:普尔曼)、下(左:DE 18g 右:德 15g)
机器人的中心有一组孔,这可能会导致中心形成更快的流动通道。普尔曼也有类似的问题。对于两个 DE 过滤器,它们都有一些簇,但是孔的大小似乎更随机。
从头




上图(左:机器人;右:普尔曼)、下(左:DE 18g 右:德 15g)
从底部来看,在空穴分布方面没有任何重大问题。DE 18g 有一些孔在顶部合并。这是由于对特定图像进行阈值处理。过滤篮是金属的,所以它们能反射很多光。因此,摄影的挑战。

总面积似乎是真正区分过滤篮的统计数据。这并不是说机器人篮子更好或更差,而是不同。它将有一个较低的流量,所以必须在两个篮子之间进行调整,以获得类似的流量。
我喜欢看这四个过滤器。这一分析的关键是有非常好的图片。我很高兴过滤器上的孔尺寸模式不是太局限。该试验的主要结论是对流量的预测。似乎机器人过滤篮的总面积较低,与流量成正比。如果同样数量的咖啡以同样的研磨速度进入篮子,那么水通过咖啡的速度就会变慢。
如果你愿意,可以在 Twitter 和 YouTube 上关注我,我会在那里发布不同机器上的浓缩咖啡视频和浓缩咖啡相关的东西。你也可以在 LinkedIn 上找到我。也可以关注我中。
我的进一步阅读:
克鲁夫:对技术状态的进一步分析
克鲁夫咖啡筛:一项分析
浓缩咖啡篮:精确与常规
咖啡数据科学
探索大多数机器的简单升级
许多浓缩咖啡机最简单的升级之一就是过滤篮。在过去的二十年里,精密过滤器改变了浓缩咖啡的领域。在这篇文章中,我看了我的杠杆机(Kim Express)的原料过滤篮和一个 Pesado 精密过滤篮。




左:金快递篮,右:佩萨多精密篮
测试本身很棘手,因为普通篮子(16g)比 Pesado (20g)小。所以我做了一些调整:
常规:16g 输入
Pesado: 21g 输入
然后,我的目标是将两者的投入产出比为 1.25,作为一个公平的比较点。
过滤孔

首先,Kim Express 篮子有大约 480 个孔,而 Pesado 篮子有 715 个孔,因此 Kim 篮子上的每个孔对性能的影响比 Pesado 上的大。Kim 篮的球洞尺寸和标准偏差比 Pesado 大得多。

Kim Express 中间有一大块不均匀的洞。对于 Pesado 过滤器,局部变化较小,但当您到达边缘时,似乎边缘上的孔变得稍大。这可能是由篮筐底部的滑动曲线引起的,但这些变化远小于金篮筐的空间变化。
镜头对比
我们可以先看看视频中的图片。每张图片时间为 5 秒。此外,我做了 30 秒的预输注,并在输注过程中使用压力脉冲。常规过滤器图像在左侧,精密过滤器图像在右侧。
常规的..精确












输液开始:




常规篮子的水流似乎更慢,但它没有精确的油炸圈饼效果那么大。然而,在输液过程中,常规篮的流量似乎更不均匀。
我们也可以看看圆盘的底部,看看暗点能说明什么关于通灵。


左:常规,右:精确
常规的篮子在右侧有很多暗点,除了左上象限(就在中心的西北方)以外,其他地方都是黑暗的。精密篮子有一些奇怪的地方,但它看起来有点干净。对于常规的篮子来说,这些孔更加明显。
绩效指标
我使用两个指标来评估技术之间的差异:最终得分和咖啡萃取。
最终得分 是评分卡上 7 个指标(辛辣、浓郁、糖浆、甜味、酸味、苦味和回味)的平均值。当然,这些分数是主观的,但它们符合我的口味,帮助我提高了我的拍摄水平。分数有一些变化。我的目标是保持每个指标的一致性,但有时粒度很难确定。
表演
从口味上看,precision 篮子显然是赢家。回想几年前我做这个改变的时候,我的品味有了很大的提高。


令人惊讶的是,精确篮的 TDS 和 EY 更低。常规篮子的最终投入产出比为 1.25,精确篮子的最终投入产出比为 1.24。这种比较的一个问题是,由于篮子的大小,剂量差异很大。
就时间而言,普通的篮子需要更长的时间来达到 10 毫升的液体。考虑到剂量差异如此之大,这很有意思。

附加数据
前段时间 Sprometheus 贴了一个关于精准篮筐的视频,他收集了一点数据。他把它显示在一个表格中,所以我把它扔进一个图表中,看能从小样本量中看出什么。我知道,当品味已经告诉你精确的篮子更好时,很难为 TDS/EY 制作更大的样品尺寸。
第一个图表是比较标准类型和精确类型的散点图。第二张是 TDS 和 EY 的对比图,有助于了解力量和提取的对比。IMS 显然做得不好。就 TDS 与 EY 而言,标准仍然与精确篮子混合在一起。我很好奇,在意大利腊肠拍摄的不同阶段,这些测量值会是怎样的。


这些数据中有一部分让我不太满意,那就是 IMS 的性能。我之前在多次拍摄和多次烘烤的情况下,比较了IMS made 篮子(Pesado)和 VST 篮子,我没有看到味道或 EY 方面的表现差异。
精确的篮子完全改变了我的浓缩咖啡体验。我会把它们推荐给任何想在品味上有大提升的人。当然,另一面是他们向你展示了你做浓缩咖啡的水平。精确的篮子隐藏不了什么,所以如果你想充分利用它们,你必须提高你的工艺水平。
如果你愿意,可以在 Twitter 和 YouTube 上关注我,我会在那里发布不同机器上的浓缩咖啡视频和浓缩咖啡相关的东西。你也可以在 LinkedIn 上找到我。也可以关注我中和订阅。
我的进一步阅读:
浓缩咖啡放屁:在断奏中检查气体
咖啡数据科学
一个简短的实验来帮助理解筛选的好处
我最近在网上参加了一个讨论,讨论水需要多大的压力才能流过咖啡。然而,不是咖啡本身,而是咖啡中的可溶物和二氧化碳气体减缓了萃取。水流过废咖啡的速度非常快,所以只有这两个其他变量的工作减缓了水流。
让我们考虑断奏的镜头。在看过数百个断奏镜头和常规镜头的视频后,我注意到断奏镜头的开始比常规镜头暗得多。我开始好奇咖啡是如何根据颗粒大小研磨德加的,因为这可能有助于解释为什么断奏镜头与普通镜头的效果如此不同。

断续浓缩咖啡准备
我在实验中发现,较粗的咖啡含有更多的二氧化碳,我怀疑通过将它们与较细的颗粒分离开来,一杯断续的咖啡会流动得更好。
通常,前 10 克的断奏酒颜色很深,这是高萃取和低 CO2 的作用。毫不奇怪,随着更多的金发出现,颜色会发生变化,但也许条纹也是由二氧化碳释放造成的。
数据
我从一些视觉数据开始。我在这里用相似的输入和相同的输出比较两个镜头。然而,断奏镜头长时间保持较暗。在我的视频数据中,我经常看到这种模式。

所有图片由作者提供
我做了一个关于单位研磨尺寸的提取率的研究,我回头看了看这些数据,看看视频中杯子的颜色差异是否与二氧化碳的释放一致。这个数据的主要问题是,该镜头是 75%的用过的咖啡,25%的新咖啡,并且咖啡没有过筛。我查看了最精细的(在小生境 0 上设置 0)和最粗糙的(设置 20):


左:设置 0,右:设置 20
两种设置都没有很多泡沫,但设置 20 的颜色更浅,有一点二氧化碳气泡,提取量更少。然而,这两种影响是交织在一起的,很难分开。
实验设计
我想要不依赖于其他变量的更明确的数据。我想要一个更好的主意,多少二氧化碳气体保留由于研磨大小,所以我筛选。然而,我不想花一大笔钱买设备来测量二氧化碳的确切排放量。我尝试这个实验,看看我是否能得到一个定性的观点,即不同粒度的研磨物释放的 CO2 是如何不同的。

我用了 400 微米和 500 微米的筛子来筛才两天的烤肉。它应该有接近最大的碳酸化。我把豆子放在壁龛的第 9 个位置。<400um, between 400 and 500um, and >500 微米的分解率分别为 40%、30%和 30%。
然后,我将一些样品放入一个冰块托盘中,每份 1 克。

我用微波炉将水加热到 90 度左右,在拍摄视频时,我用一个天平向每个样品中加入 3 克水。

越粗的咖啡泡沫越多。它稳定下来了,所以我搅拌了一下,看看会发生什么。

<400um had almost no foam or bubbles indicating that it didn’t have any more CO2 to be released. That’s probably in part because the surface area to volume is much higher for smaller particles. It had a few larger bubbles from mixing but not from CO2 in the same way as the coarser grinds.
One minute after stirring, most of the bubbles went away.

There wasn’t much of a change for the <400um samples, but the other two had a cloud of foam disappear.
Originally, I wanted to buy more complex equipment to be able to quantify the amount of CO2 released during extraction by grind size. However, I settled on this experiment because it shows a hunch was correct.
The larger story of this study is that in a staccato shot, the fine layer (<400um) sits on the bottom. Since that layer doesn’t have much CO2 released, its flow is primarily restricted by solubles, and the flow increases as more is extracted. The fine layer has a 比其他两层的提取率更高,CO2 可能会限制它们的提取,除非是更大的研磨粒度。
浓缩咖啡中的二氧化碳气泡没有得到很好的研究。我用一个制作的透明移动滤镜观察过这个问题,但这只是一个观察,与照片的其余部分没有太多联系。
这一概念提供了更多的证据,证明断奏法在提取方面具有根本优势,因为它改变了较大颗粒的二氧化碳与咖啡球中较小颗粒的相互作用方式。
如果你愿意,可以在推特、 YouTube 和 Instagram 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。也可以关注我在中和订阅。
我的进一步阅读:
个人故事和关注点
浓缩咖啡流量:预浸泡
咖啡数据科学
仔细观察预输注
当我第一次使用 Acaia Pyxis 时,我很好奇在预输注期间观察流量,并在输注期间平滑流量,以观察压力脉动。在收集了一点点数据后,我可以看到在长时间的预输注过程中,流量如何达到峰值并缓慢下降。在这篇文章中,我将回顾一些初始数据和长期指标,以帮助理解什么是好镜头的最佳预测。
总的来说,输液也出现了类似的情况,但那是由我操作控制杆的方式造成的。我用压力脉动我的镜头,我用视觉线索来帮助决定我应该多快或多慢地脉动。
让我们来看一个镜头,就提取的均匀性、最终提取率(EY)和味道而言,这是一个很好的镜头:






所有图片由作者提供
我们可以查看流量和平滑流量,其中流量在预输注和输注期间减少。这种减速并不是出现在所有的镜头中,但也许这是一个信号,表明这个镜头会拍得很好。

我们可以只关注预灌注(PI ),当液滴引起大量波纹时,最大包络和平均值正在下降。我为 TCF 和 T10 补充了一个说明:
- TCF:当过滤篮上的所有孔都有咖啡流出时,就该盖上过滤器了。
- T10:量杯中标示的达到 10ml 的时间。由于在量杯上使用视觉度量,这有更多的误差,但相对于 PI 来说,这是一个有趣的度量。

我们可以看一下另一个镜头,预输注和输注期间的流动要平坦得多。因此,这种趋势并不总是被观察到,也不知道它与性能相关。

绩效指标
我使用两个指标来评估技术之间的差异:最终得分和咖啡萃取。然后,我使用相关性来了解这两个指标与其他变量的关联程度。
最终得分 是记分卡 7 个指标(尖锐、浓郁、糖浆、甜味、酸味、苦味和回味)的平均值。当然,这些分数是主观的,但它们符合我的口味,帮助我提高了我的拍摄水平。分数有一些变化。我的目标是保持每个指标的一致性,但有时粒度很难确定。
用折射仪测量总溶解固体量(TDS),这个数字结合弹丸的输出重量和咖啡的输入重量用来确定提取到杯中的咖啡的百分比,称为提取率(EY)** 。**
相关性是衡量两个变量彼此相似程度的指标。高相关性并不意味着一个变量会引起另一个变量,而是当情况发生变化时,两个变量的涨跌幅度相同。我从一开始就假设一些分级变量会有很高的相关性,因为它们是从不同的时间点来看味道的。
预输注的更多数据
这让我对预输注期间的流速对注射有何影响产生了兴趣。所以我看了一些关于心流的数据。我用这张图来说明我在哪里计算一些指标:

从最小流量和最大流量,我们可以了解流量如何达到峰值,然后变慢。所以我查看了最大流量时间和最小流量时间。达到最大流量的时间可能与圆盘变得完全饱和有关,而最小流量是圆盘在比注入期间更低的压力下如何以稳定状态流动。
我研究了这两个流动时间指标与 TCF 以及彼此之间的关系。数据似乎非常吻合。
****
然后我从以前的实验中知道 EY 和最终分数的趋势与圆周率/TCF:

我还观察了 TCF、最小流量时间和最大流量时间之间的相关性。我特别感兴趣的是,在何时结束拍摄方面,最小或最大流量时间是否能比 TCF 更好地告诉我一些东西。

虽然它们与 TCF 和 PI 有很强的相关性,但在预测最终得分或 ey 的良好表现的指标方面,二者都不如 PI/TCF。
按镜头类型划分,TCF 仍是主要特征:

流程日志提供了一些关于拍摄进展的信息。我希望他们能更好地提示我在拍摄过程中使用它们作为衡量标准来做决定,但它似乎没有比 TCF 更有帮助。
我很好奇为什么预输注期间流速变慢。我想知道咖啡床是否压缩到流动受阻的地方。一些透明 portafilter 实验可能能够解释这一点,但在有更好的用于 58 毫米机器的透明 portafilter 之前,我不知道我能对这一特殊现象发现多少。
如果你愿意,可以在 Twitter 和 YouTube 上关注我,我会在那里发布不同机器上的浓缩咖啡视频和浓缩咖啡相关的东西。你也可以在 LinkedIn 上找到我。也可以关注我中和订阅。
我的进一步阅读:
浓缩咖啡预湿,而不是预浸泡
咖啡数据科学
预输注透明过滤器试验的残留
我正在试验一种用于浓缩咖啡的透明移动式过滤器,我发表了一些试验结果。在与乔纳森·加涅的交谈中,他想知道我是否能在毛细作用的基础上看到流量和时间之间的关系。所以我把我的图像处理技能应用到一个视频中,来帮助观察这种关系是否可见,并且它被观察到了!
预湿遵循毛细管作用的权利!
这一点很重要,因为有时我们会将预浸误认为是预湿,结果是托盘没有完全湿润,这可能会导致不好的浓缩咖啡。预浸真的是好咖啡的关键,所以我们应该每次都做好。
毛细作用
一般理论(见标题“多孔介质中的液体传输”)是当液体被多孔材料吸收时,吸收率会随着时间的推移而降低。因此,液体渗透多孔材料的垂直距离与材料与液体接触时间的平方根成正比。
垂直距离=常数*sqrt(时间)
实验装置
我用了一个 make shift 透明 portafilter(又名 Kompresso),我在里面放了一些咖啡。最初,我把热水倒进去了,我没有施加任何压力。顶部的纸过滤器减少了基于流量输入的通道,因此它就像一个淋浴帘。

所有图片由作者提供
我以 60 FPS 的速度捕捉了一个 4K 分辨率的视频。在处理视频的过程中,我专注于视频的一个区域。

然后我将每行的像素相加。我的目标是用最简单、最快的方法来计算平均咖啡线。我想如果有关系的话,我应该做些超级花哨的事情。然后我应用了一个简单的阈值。

从这里,我能够提取每帧的平均咖啡行值。我把它画在下面,然后我把它画在时间的平方根上。

当与时间的平方根比较时,存在非常强的相关性,这证实了水在预润湿期间通过圆盘的方式遵循毛细管作用预测。
一个理论是可测试的,我很喜欢看到这个关于毛细血管运动的理论解释为什么预湿不同于预灌注。这对于像 Flair 或 Kompresso 这样的机器来说是最重要的,在那里你可以倒水,你可以决定你是想做预湿还是 1 到 2 巴的预浸。
对于新的浓缩咖啡用户来说,这一点特别重要,有助于区分急需的预浸和可选的预湿。
如果你愿意,请在 Twitter 和 YouTube 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。也可以在中关注我。
我的进一步阅读:
【Kompresso 能得到 9 巴压力的浓缩咖啡吗?
浓缩咖啡圆盘分析
咖啡数据科学
咖啡失效分析的可视化数据之旅
关于投篮后冰球分析或冰球学的有用性有一些争论。通常这需要看冰球的顶部,偶尔我会看到冰球底部的照片。通常,它们很难解码。
我注意到由于断奏捣固和纸过滤器,我的镜头变得更加复杂,它们被用于更好的故障分析。我通常在照片的底部和中间使用纸质过滤器,如果有残留的咖啡渍,我可以更好地猜测通道是从哪里开始的。


咖啡上或所用的纸上的咖啡污渍或黑斑是相对于托盘中的其余咖啡的低流动性的指示。高流量区域或通道应该有较少的污点。
我们可以看一些例子,对我的数据集来说,非常引人注目的是,我有一个所有浓缩咖啡照片的视频。我希望通过这些例子,其他人能够更好地利用拍摄后的视觉线索来帮助理解问题所在。
你不能修正你观察不到的东西。
顶部过滤器的第一个例子
这一针在预输注期间开始得不太好。还有一大块地方没有咖啡流出来。通常,如果在预输注期间发生这种情况,我发现的唯一恢复的好方法是尝试低压下的压力脉动。我定期使用压力脉冲,但通过使用较低的压力,有时你可以让流量返回。




在观察圆盘时,我使用了一个顶部滤镜,两个中间滤镜,没有底部滤镜。两个中间的过滤器使得没有咖啡渣的照片更清晰。
后来,我确定顶部过滤器增加了侧通道,因为它允许水比通常情况下更快地流向两侧。在查看过滤篮上的咖啡污渍时,一个大点没有太多的咖啡,如上图所示,当其余部分变成棕色时,咖啡颜色变深。



冰球的底部也有咖啡渍,在中间的过滤器中,很明显,在水流到冰球底部之前很久就发生了沟流。大月牙标志着流量不足。



单面拍摄
我们可以从另一个角度来看如何在冰球分析中看到这些影响。





这张照片使用了一个顶部滤镜,两个中间滤镜,没有底部滤镜。中间的过滤器显示水流从两边和中间流下,停在一个甜甜圈里。中间看起来正常,因为注射准备被设计为在中间具有较低的密度,以调整杠杆机器圆环问题。






两个滤纸中间的黑色圆圈是圆盘的两半,表明通道一直通向顶部。



这个分析让我更仔细地看了看移除顶部过滤器。当时,我试图看看顶部过滤器是否可以改善结果。
用顶部过滤器夯实
在这个例子中,我使用了一个更大的过滤器,并把过滤器压在上面。然而,一个大甜甜圈出现了。





夯实没有帮助。我以为水可能在过滤器上流动,但看起来淋浴屏幕的封条正好在纸的上面。所以水穿过纸流到边缘。






较小的顶部过滤器
为了测试这个想法,我把顶部的过滤器做得更小,这样它就不会一直到边缘。通灵没有那么糟糕,但是仍然有问题。





滤纸和圆盘上的颜色不是黑色的。甜甜圈效应并不完全是由顶部滤镜造成的,但这并没有帮助。








顶部有一个较小的纸质过滤器有助于侧面保持固化,这表明有一个完整的纸质过滤器会使侧面沟道效应变得更糟。这可能不是正常夯实镜头的情况,但对于断奏夯实,顶层有一个轻得多的夯实。
拆除顶部过滤器
对于这个镜头,顶部提取更均匀。这可以通过触摸圆盘并找到较硬的位置或较密集的位置来看到。




仍然有一个黑色的甜甜圈,但提取率更高,味道更好。






这个冰球的分布很奇怪。结果,一半的咖啡豆是错误的肯尼亚咖啡豆。我用两粒咖啡豆做典型的烘焙,我将各层分开,我用肯亚豆做了两次烘焙。所以在这张照片中,肯尼亚咖啡豆比其他的要新鲜一周(一周而不是两周)。这导致了一些通灵问题。
缩小下半部分




我在底部减少到 7 克,在顶部减少到 11 克,这似乎使过滤器的提取更加均匀,但喷雾更多了。








在底部添加纸质过滤器
底部有纸过滤器,镜头提取时间长得多,但味道大大改善了提取。






冰球分析显示更好的流动,但分析更难,因为冰球不容易出来。顶部没有大的标记,因为拍摄结束和大的沟道效应,这意味着顶部的沟道效应是由底部引起的








这个镜头的流程也很有趣。似乎流量随着预输注和输注的时间而减少。流量的大峰值是由于压力脉动,这就是为什么流量被平均用于分析。

重复使用的纸质过滤器
下一张照片在提取方面是相似的,但是现在,底部的纸质过滤器造成了一个反向的环形,因为水流通过中心非常集中。这是因为纸质过滤器只需用水清洗即可重复使用。

于是,我决定在镜头的下半部分做一个平坦分布,结果真的很可怕。





EY 接近 10%,正如你在杯子和过滤篮中看到的,大量未乳化的油被提取出来。击球后冰球很湿。篮子里到处都是油。太难看了。






用力夯实
我回到了一个更密集的外环分布,并夯实底部一半稍微更难在 300 克,而不是 200 克。它回到了一个平均流量和 21% EY。





底部纸质过滤器的外侧仍然有一个有点暗的环,但我不确定这是通道还是液体在拍摄结束时沉淀的地方。镜头的上半部分均匀地分开,没有任何主要通道。



一个弱点
下一个镜头有 19%的 EY,这似乎很低。从底部看,边上有一些黑点,但在中间的过滤器上,外环没有那么暗。这意味着问题只是在下半部分。也许侧面密度不均匀。



在视频中发现了一个缓慢的斑点,它靠近底部过滤器上暗咖啡污渍的位置。







布料过滤器
我开始看布过滤器,我注意到中间的布过滤器引起了极快的流动。我被吹走了。




它们更干净,而且似乎比纸质过滤器更容易重复使用。纸质过滤器上的一个黑点可能是一些过滤孔,咖啡无法通过。

我在拍摄前/后的电子表格、拍摄期间的视频和拍摄后的冰球图像中收集了大量数据。他们很好地指导我如何制作更好的浓缩咖啡,因为我对浓缩咖啡的功能有了更深的理解。这就是为什么当人们在网上成群结队地询问如何修复他们的镜头时,我总是要求提供视频和图片。
数据越多越好。
我希望这给感兴趣的人一个起点,帮助他们学习如何提高他们的投篮。






























如果你愿意,可以在 Twitter 和 YouTube 上关注我,我会在那里发布不同机器上的浓缩咖啡视频和浓缩咖啡相关的东西。你也可以在 LinkedIn 上找到我。也可以关注我中。
我的进一步阅读:
从底部进行浓缩咖啡圆盘分析
咖啡数据科学
拍摄后分析以读取浓缩茶叶
我提取浓缩咖啡的方式可以被描述为数据收集的漩涡。最初的几个变量扩展到包括对冰球底部成像。这对于某些类型的局部通灵的镜头诊断很有帮助,但我很好奇图像细节与品味和提取的一致性有多强。
相当一部分人会在拍摄后在网上发布他们的冰球顶部,并寻求反馈。冰球的顶部不是很有用,但我发现冰球的底部可以揭示水是如何流动的。
我从经验中看到,更少的暗点意味着更少的通灵,因为暗点通常是对那些区域的低流量的指示,因此对其他区域和通灵有更高的流量。
一些黑斑在表面很深,一些则一直存在。黑点越多,流经圆盘的水流就越不均匀。
数据
几个月来,我一直在拍摄冰球底部的照片,所以我抓取了一些图像,并将其与我的数据表对齐。我最终在数据手册中找到了 175 张图片。

所有图片由作者提供
我试图保持灯光一致,但通常会有一些灯光伪像。否则,这是一个很容易捕捉到的信息。
绩效指标
我使用两个指标来评估技术之间的差异:最终得分和咖啡萃取。
最终得分 是评分卡上 7 个指标(辛辣、浓郁、糖浆、甜味、酸味、苦味和回味)的平均值。当然,这些分数是主观的,但它们符合我的口味,帮助我提高了我的拍摄水平。分数有一些变化。我的目标是保持每个指标的一致性,但有时粒度很难确定。
使用折射仪测量总溶解固体量(TDS),该数值与咖啡的输出重量和输入重量相结合,用于确定提取到杯中的咖啡的百分比。
准备图像
首先,我需要准备图像,使它们更容易处理。我用一个蓝色的环手动标注了每张照片。我用这个环来定义感兴趣的区域,其他的都涂掉了。

然后我就可以扔掉任何丢失的棋子,清楚地看到冰球。
****
我开始通过探索色调、饱和度和亮度(HSI)空间来处理它们。在下图中,这个空间用红色、绿色和蓝色(RGB)表示。在该图像中,暗点在色调平面以及强度平面上有明显的偏移。

色调和饱和度本身似乎非常有趣:

色调和饱和度
我用平均强度对图像进行了归一化,然后我观察了这些平面的一些圆形切面。我看了每一枚戒指,也看了它们的组合。
****
相互关系
相关性是衡量两个变量彼此相似程度的指标。高度相关并不意味着一个变量会引起另一个变量,而是当情况发生变化时,两个变量的涨跌幅度相同。我从一开始就假设一些分级变量会有很高的相关性,因为它们是从不同的时间点来看味道的。
查看饱和平面上这些环的均值和标准差的多个度量,我没有发现任何相关性强于-25%的东西,这是相当低的。作为参考,味道和 EY 之间的相关性通常在 70%左右。我也没有发现色调或强度平面有很强的相关性,最好的相关性是在饱和度上。
****
为了分解数据,我使用了一个我记录的度量标准来描述照片的样子。我将射束流描述为居中的、偏心的、小环形的、环形的、不均匀的或单侧的。这些照片中的大多数都是居中、偏心或小圆环,下面是一些例子:



居中、偏心、小甜甜圈
这个描述有点主观,但我想记录一些简单的东西。我最好的照片是居中或偏心的,但这是我第一次在数据分析中使用这个指标。
这里有一些突出的指标,都是 STD 指标。居中和偏心拍摄的相关性非常低(abs < 10%)。然而,小甜甜圈照片有更强的负相关性,尤其是对味道而言。
****
我绘制了累积外环 3 的散点图,它是三个最大环的组合:

似乎有一些模式,但没有很强的线性拟合。我怀疑小甜甜圈不同于居中或偏心,因为这些镜头几乎没有明显的慢点(冰球上的黑点)。他们的最终提取有点微妙,因为什么可能导致它比另一个镜头高一点或低一点。微小的圆环照片几乎总是具有较低的提取率和更不均匀的提取率。
这项研究没有显示出圆盘底部和性能指标之间的强相关性。我从经验中知道,冰球的底部是调试镜头的有用指南,很可能我没有足够的数据来看到更有趣的东西。
如果你愿意,可以在 Twitter 和 YouTube 上关注我,我会在那里发布不同机器上的浓缩咖啡视频和浓缩咖啡相关的东西。你也可以在 LinkedIn 上找到我。也可以关注我中。
我的进一步阅读:
数据科学的重要盛会
通过与贝壳相处来提高你的生产力

https://unsplash.com/@ngeshlew
作为一名数据专业人员,您可能已经了解 Python,这是最强大的通用语言之一,它的库几乎可以方便地做任何事情。那么,为什么要学习 Bash 和 Unix shell 呢?我在工作中发现的一些用例:
- 本地/远程使用文件系统
- 本地/远程监控系统资源
- 远程检查数据文件(在本地,我发现用 Pandas 启动 Python 控制台更快)
- 自动化任何使用不同命令行工具的作业
- 整合复杂的预处理步骤,包括编译软件或与数据库同步(可能通过 Docker)
- 在查询/预处理/训练/部署阶段之间建立粘合剂
- 将定制的实用程序整合在一起,这些实用程序利用了您无论如何都会从 CLI 使用的工具(例如 Git)
- Makefile 经常被用作数据科学的 DAG 框架,例如在 cookiecutter 项目中
设置
注意,在 Mac 上,默认情况下你会被 BSD 工具所困,它们没有 GNU 那么强大。然而,你可以使用brew install coreutils,然后加上前缀g,例如split变成了gsplit。
你可能还想用brew install bash更新 Mac Bash 版本。
如果有任何命令不存在,那么在 Mac 上你通常可以运行brew install command。
在 Mac 上,你可能想安装 iTerm2 而不是默认的终端。
基础
用#!/bin/bash或#!/usr/bin/env bash启动脚本(在 Mac 上,您可能有多个 Bash - which -a bash版本)。
用双引号将"$VARIABLE"括起来以保留值。
set -euox pipefail是一个很好的默认配置(在脚本中),它提供了:
- 在脚本中的任何命令失败时引发错误
- 运行命令时打印命令
- 在未设置变量的情况下引发错误
使用终端中的Ctrl+R搜索命令历史,使用history查看全部命令历史。
使用info command或man command或help command或command --help获得命令帮助。whatis command还打印来自man手册的第一行描述。
别名
别名是一种快捷方式,可用于冗长乏味的命令,例如
连接到远程机器:alias remote="ssh user@bigawesomemachine.cloud
连接到数据库:alias db="psql -h database.redshift.amazonaws.com -d live -U database_user -p 5439"
激活 Conda 环境:alias pyenv="source activate py_39_latest
甚至只是纯粹的懒惰:alias jn="jupyter notebook"
alias repo="cd /Users/john/work/team_repo"alias l="ls -lah"
alias l=”ls -lah”
将这些添加到~/.bash_profile中,以便它们在 shell 会话中保持不变。
检查文件
使用tree以树状格式列出目录的内容。
用head -n 10 file或tail -n 10 file读取文件的一端
观察动态变化的文件,如日志
tail -f file.log
在当前(嵌套)目录中查找文件
find . -name data.csv
浏览一个. csv 文件,其中“,”是列分隔符
cat data.csv | column -t -s "," | less -S
从明文配置中获取密钥值,这在 Bash 中不一定是可理解的sed -n 's/^MODEL_DATA_PATH = //p' model_conf.py
您可以使用cut对. csv 列进行 Pandas 风格的值计数,例如对分号分隔的文件中的第五列进行计数
cut -d ";" -f 5 data.csv | sort | uniq -c | sort
用diff file1 file2或cmp file1 file2比较文件,用diff <(ls directory1) <(ls directory2)比较目录。
使用grep在文件中搜索(正则表达式)模式。这个命令有很多有用的选项,所以最好读一下info grep。grep+cut 是一种强大的模式,可以根据模式过滤行并获得您关心的列,例如获得最新的提交散列:
git log -1 | grep '^commit' | cut -d " " -f 2
sort和uniq可以一起使用来处理重复,例如计算一个. csv 文件中重复的行数
sort data.csv | uniq -d | wc -l
控制结构
快速 if-then-else:
[[ "$ENV" == prod ]] && bash run_production_job.sh || bash run_test_job.sh
检查文件是否存在:[[ -f configuration.file ]] && bash run_training_job.sh || echo "Configuration missing"
数字比较:
[[ "$MODEL_ACCURACY" -ge 0.8 ]] && bash deploy_model.sh || echo "Insufficient accuracy" | tee error.log
Bash 是一种编程语言,所以它支持常规的for / while / break / continue结构。例如,要创建备份文件:
for i in ls .csv; do cp "$i" "$i".bak ; done
在 Bash 中定义函数也很简单,对于更复杂的逻辑来说,它可能是别名的有用替代物
view_map() {
open "https://www.google.com/maps/search/$1,$2/"
}
例如,上面用view_map 59.43 24.74在塔林打开了一个谷歌地图浏览器窗口
使用xargs为多个参数执行一个命令:例如删除所有已经合并到 master 的本地 Git 分支。
git branch --merged master | grep -v "master" | xargs git branch -d
IO 基础知识
添加到文件中
python server.py >> server.log
将 stdout 和 stderr 写入文件和控制台
python run_something.py 2>&1 | tee something.log
将所有输出写入 void(丢弃它)
python run_something.py > /dev/null 2>&1
操纵字符串
你可以用括号扩展生成一个字符串列表:mkdir /var/project/{data,models,conf,outputs}
Bash 内置了一些漂亮的字符串操作功能,比如
${string//substring/replacement}
对于字符串,使用转换将小写替换为大写
cat lower_case_file | tr 'a-z' 'A-Z'。
然而,还有更强大的语言,如awk和sed内置来处理任何字符串操作。您还可以将 Perl 或 Python 与正则表达式一起用于任意复杂性的字符串操作。
为了开始使用正则表达式,我发现有用的是交互式教程,如 RegexOne 和操场,如 regexr 。
操作文件
确保文件存在:
if [[ ! -f stuff/parent_of_files/files/necessary.file ]]; then
mkdir -p stuff/parent_of_files/files
touch files/necessary.file
fi
压缩和解压缩目录
tar -cvzf archive_name.tar.gz content_directory
tar -xvzf archive_name.tar.gz -C target_directory
合并两个。具有相同索引的 csv 文件
join file1.csv file2.csv
用单独的头文件合并 N .csv 文件
cat header.csv file1.csv file2.csv ... > target_file.csv
使用split将文件分成块,例如. csv 文件(保留文件头)
tail -n +2 file.csv | split -l 4 - split_
for file in split_*
do
head -n 1 file.csv > tmp_file
cat "$file" >> tmp_file
mv -f tmp_file "$file"
done
从大样本中随机抽取一个样本。csv 文件(这会将它加载到内存中):
shuf -n 10 data.csv如果您需要保留标题,但将子样本写入另一个文件:
head -n 1 data.csv > data_sample.csv
shuf -n 10000 <(tail -n +2 data.csv) >> data_sample.csv
用sedsed 's/;/,/g' data.csv > data.csv改变文件分隔符
将文件发送到远程机器
scp /Users/andrei/local_directory/conf.py user@hostname:/home/andrei/conf.py
管理资源和作业
是什么占用了我所有的磁盘空间?ncdu提供交互式视图。不需要额外安装的替代方案是du -hs * | sort -h
是什么/谁让整个机器变慢了?htop为多彩版,top否则。
uptime -不言自明。
让一个长时间运行的模型/脚本在服务器的后台执行,并将输出写到model.log nohup python long_running_model.py > model.log &这也打印出你可以用来终止进程的 PID,如果过了一段时间你可以用ps -ef | grep long_running_model.py找到 PID
向本地 Flask 服务发送带有(嵌套)有效负载的请求
curl \
-H 'Content-Type: application/json' \
-X POST \
-d '{"model_type": "neuralnet", "features": {"measurement_1": 50002.3, "measurement_2": -13, "measurement_3": 1.001}}' \
http://localhost:5000/invoke
一个进程占用一个端口吗?lsof -i :port_number
要干掉岗位,先试kill PID,最后再用kill -9 PID。
变量
基础
使用printenv查看您的环境变量。要设置一个永久的环境变量,将其添加到您的~/.bash_profile : export MLFLOW_TRACKING_URI=http://mlflow.remoteserver中,现在您可以在 Python 中访问os.environ["MLFLOW_TRACKING_URI"]。
使用env在自定义环境中运行命令:
env -i INNER_SHELL=True bash
使用local在函数中声明局部范围变量。
特殊参数
有几个,但是一些有用的
$? -最后一条命令的退出状态
$# -脚本的位置参数数量
$$ -过程 ID
$_ -外壳的绝对路径
$0 -脚本的名称
[[ $? == 0 ]] && echo "Last command succeeded" || echo "Last command failed"
变量操作
您可以使用变量的一些技巧:
如果未设置参数,则使用默认值:${MODEL_DIR:-"/Users/jack/work/models"}或将其设置为另一个值:${MODEL_DIR:=$WORK_DIR}或显示一条错误消息:${MODEL_DIR:?'No directory exists!'}
命令行参数
$# -命令行参数的数量
您总是可以使用简单的$1、$2、$3来使用与脚本一起传递的位置参数。
对于简单的单字母命名参数,您可以使用内置的getopts。
while getopts ":m:s:e:" opt; do
case ${opt} in
m) MODEL_TAG="$OPTARG"
;;
s) DATA_START_DATE="$OPTARG"
;;
e) DATA_END_DATE="$OPTARG"
;;
\?) echo "Incorrect usage!"
;;
esac
done
巴利安
Python 代码可以在管道中内联运行——这意味着如果有必要,你可以用它来替换awk / sed / perl:
echo "Hello World" | python3 -c "import sys; import re; input = sys.stdin.read(); output = re.sub('Hello World', 'Privet Mir', input); print(output)"
或者做任何事情:
model_accuracy=$(python -c 'from sklearn.svm import SVC; from sklearn.multiclass import OneVsRestClassifier; from sklearn.metrics import accuracy_score; from sklearn.preprocessing import LabelBinarizer; X = [[1, 2], [2, 4], [4, 5], [3, 2], [3, 1]]; y = [0, 0, 1, 1, 2]; classif = OneVsRestClassifier(estimator=SVC(random_state=0)); y_preds = classif.fit(X, y).predict(X); print(accuracy_score(y, y_preds))';)
向命令提供多行输入的另一种方法是使用 here 文档,例如:
python <<HEREDOC
import sys
for p in sys.path:
print(p)
HEREDOC
其中HEREDOC作为 EOF 编码,脚本仅在满足 EOF 后执行。
使用jq命令在命令行上使用 JSON。例题。
资源
初学者 Bash 指南——介绍脚本的不错资源
GNU Coreutils 手册 —了解你可以用默认工具做什么
命令行的数据科学——如果你非常喜欢 shell,想在那里做 EDA/建模:)
感谢马克·考恩的一些提示和技巧,他实际上知道一些 Bash:)
最初发表于 2021 年 6 月 1 日 https://mlumiste.com**的 。
基本的大数据、数据科学家技能:如何为 AWS EMR 集群安装 jar
基本数据科学技能
演示从哪里下载 jar 以及如何将它们安装在 AWS EMR 集群上,以便从 EMR 笔记本上访问

JESHOOTS.COM在 Unsplash 上拍照
我还没有看到关于如何将 JAR 文件放到 EMR 集群的每个工作节点上的简单而全面的指南,然而这是一个非常重要的、普遍的需求。本文解决了这些需求。以下是我从个人奋斗和无数不同的谷歌结果、堆栈溢出帖子和官方 AWS 文档中总结的要点。
背景
GitHub 上托管的 Scala 库通常有安装指令,这些指令依赖于使用名为 Maven 的程序从源代码构建 JAR 文件。但是作为一个数据科学家,我只想要 JAR 文件。上述说明也缺乏关于如何在集群环境的数百个工作节点上安装这个 JAR 的实际指导。
有些文章有很棒的代码,但是依赖于您的集群/节点可以访问互联网——对于在多个防火墙后处理大量敏感数据的用户来说,这是不可思议的。例如,您可能会看到如下教程:
// download Vegas-viz library jars from the Maven repo
import $ivy.`org.vegas-viz::vegas:0.3.11`
有时,安全层会阻止从在线 Maven 存储库中导入托管 JAR 的互联网访问。因此,需要 JAR 文件本身,以便您可以将它加载到笔记本中。
本文将教您:1)从哪里下载官方 JAR 文件,2)如何创建一个 AWS EMR 集群,将 JAR 复制到集群的每个节点,这样您就可以运行以下代码行:
import vegas._
import h2o
etc.
第 1 部分:下载 jar
我不知道为什么每一个 Scala GitHub repo 和数据科学工具都描述了如何从源代码构建一个 JAR,但是没有一个解释说你可以跳过这个多步骤的过程,直接从https://search.maven.org/下载一个 JAR。如果你知道原因,请分享。无论如何,请访问上面的网站,搜索您的软件包。首先,我找到了针对breeze的 GitHub repo,这是一个 Scala NLP 库,带有一个数据 viz 配套库:https://github.com/scalanlp/breeze。如果你只搜索“微风”,会出现太多的结果请改为搜索“scalanlp breeze”:

图片来源:作者
第 2 部分:安装 jar
创建 EMR 集群时,您希望选择Custom Jar然后选择Add Step以弹出一个对话窗口(以下截图供参考)。在这个新窗口中,相应地填写:
姓名:*
The name doesn’t matter. I just leave the default name since it isn’t referenced elsewhere/used again for our use case.
罐子位置:*
s3://us-east-1.elasticmapreduce/libs/script-runner/script-runner.jar
所有 AWS 用户都可以访问上面的 JAR,它用于运行 shell 脚本。
论据:
s3://yourDirectory/yourShellScriptName.sh
将 S3 目录用于 shell 脚本,命令复制您的 JAR 文件。下面的例子脚本进行截图。
重要提示:使用文本编辑器,如记事本++ 或记事本。AWS 技术支持和我发现,Sublime Text 默认使用一种字符编码,当集群试图运行 shell 命令时,这种编码会导致在该上下文中使用的
*.sh*脚本生成Step Failed错误。即使手动指定 Sublime 文本使用 UTF-8 字符编码,相同的代码在 Sublime 文本生成的文件中也会失败,但在用 Notepad++创建的文件中可以工作。我们从来没有弄清楚为什么,坦率地说,我们更关心的是让一些工作。

我的 shell 脚本将(cp ) jar 文件复制到每个 AWS EMR 工作节点:
代码来源:作者。
在这一步之后,您将能够创建一个 EMR 笔记本,附加到您的 EMR 集群,并导入与这些 jar 相关联的库。如果您使用了我上面的脚本,您现在可以使用一个 Python 3 内核并运行:
import h2o
此外,您现在可以在一个 Spark 内核中运行以下代码:
import vegas._
恭喜你!现在,您可以使用那些更常用于在单节点环境中处理小数据的高级工具了!
关于作者
安德鲁·杨是 Neustar 的 R&D 数据科学家经理。例如,Neustar 是一家信息服务公司,从航空、银行、政府、营销、社交媒体和电信等领域的数百家公司获取结构化和非结构化的文本和图片数据。Neustar 将这些数据成分结合起来,然后向企业客户出售具有附加值的成品“菜肴”,用于咨询、网络安全、欺诈检测和营销等目的。在这种情况下,Young 先生是 R&D 一个小型数据科学团队的实践型首席架构师,负责构建、优化和维护一个为所有产品和服务提供信息的系统,该系统为 Neustar 带来了超过 10 亿美元的年收入。在 LinkedIn 上关注 Andrew,了解数据科学的最新趋势!
更多热门文章:
参考
[1]个人的血汗
[2] AWS 技术支持(特别感谢休斯顿的 Pranay S。我们一起学到了很多!)
2021 年数据科学家必备的数据辩论技能
数据科学最基本的技能——与熊猫争论数据
使用 Pandas (Python)库处理数据的完整指南

熊猫 是数据科学工具包的核心组件之一。在过去的一年里,导入 Pandas 库已经成为我用 Python 开发的每个项目的首要步骤。加载和分析关系/标签数据从未如此简单。正如你可以从下面的图表中推断的那样,Pandas 只是下载量第二高的数据科学包,仅次于 NumPy。这是可以理解的,因为熊猫是建在 NumPy 上面的。

上个月的包下载数量。来源。
数据科学家花费 80%的时间准备和管理用于分析的数据。
在本文中,我将演示如何使用 Pandas 加载、探索和操作数据。请在 Github 上找到用于演示的数据集。为了获得技能并理解您将要学习的概念,我强烈建议您并行尝试执行我们将在本文中讨论的命令。
我将详细说明大多数命令的功能,但是如果您想重温 Python 的基础知识,我建议您阅读下面的文章。
https://link.medium.com/BhKOU360jcb
# installing pandas package
pip install pandas
# importing the pandas library
import pandas as pd
什么是数据帧?
DataFrame 是主要的 Pandas 数据结构,用于以表格格式表示数据。
让我们 使用熊猫 read_csv() 函数将mpg 数据加载到 DataFrame(DF)对象中。
import pandas as pd
# Loading the mpg data set from a local .csv file using Pandas
mpg_df = pd.read_csv('data/mpg_dataset.csv')
# Print first 5 rows of the data
mpg_df.head()

数据帧的前 5 行
有了索引和列标题,数据帧看起来很像 Excel 表格,不是吗?现在让我们从到探究到的数据。
探测
# Viewing the columns in the DataFrame
mpg_df.columns

mpg 数据集中的列
# dimensions of the DF
mpg_df.shape
>>> (398, 9)
#Summary of the DF
mpg_df.info()

mpg_df 摘要
# Viewing the data type of each column in the DF
mpg_df.dtypes

每列的数据类型
pandas 中的 describe 函数可以让您快速汇总 DF 中每个字段的数据。这包括百分比、平均值、标准偏差等细节。默认情况下。描述对象类型字段时,将返回计数、唯一值数量、模式和频率等详细信息。
# Summary of all numeric fields in DF by default
mpg_df.describe()

mpg_df 摘要
# Accessing a column from the DataFrame
mpg_df.mpg
mpg_df['mpg']

数据集中的 mpg 列
我想你现在在想三件事。让我猜猜。
- 为什么我们在同一个单元中执行两个命令?
- 为什么结果不是表格的形式?
- 如果我想要表格格式的选定列,我该怎么做?
各项问题的答案如下:
- 我放在上面的单元格中用于访问列的两个命令都返回相同的结果
- 结果不是以表格的形式,因为命令不是返回数据帧,而是返回 系列
- 从 DF 中访问列时使用双方括号,以获得 DF(表格格式)形式的结果

mpg 柱系列

作为 DF 的 mpg 列
如果这些不是你想问的问题,你可以随时留下评论,我会尽快回复你。
什么是系列?
系列是能够保存任何类型数据的一维标签数组。它有一个与每个元素相关联的索引。本质上,系列只是表格中的一列。
现在我们已经了解了 Series,我们有各种专门的函数来了解特定列中的数据。让我们继续研究这些数据。
# Describe of origin field in mpg_df
mpg_df.origin.describe()

mpg_df 中“来源”字段的汇总
解释上面的摘要:
count -列中总共有 398 个值,unique - 3 个不同的值,
top - 'usa '是列的模式,freq - 249 是模式的频率
# Values present in the column & their occurence
mpg_df.origin.value_counts()

价值 _ 原产地计数
# check all the columns if there are any missing values mpg_df.isna().sum()

缺少值计数的列
# Assigning the df to another
cdf = mpg_df
cdf.head()
这里的“=”符号不会从 mpg_df 创建一个名为“cdf”的新 DF。这意味着,我对 mpg_df/cdf 所做的任何修改都会影响另一个,因为它们将共享相同的内存地址。
id(cdf)==id(mpg_df)
>>> True
所以如果你想创建一个 DF 的副本,使用 copy() 函数。
cdf = mpg_df.copy()
id(cdf)==id(mpg_df)
>>> False
操纵
到目前为止,我们已经讨论了这些部分- 加载 和 探索 数据。我们现在将学习如何 操作 数据。
切片&划片
基本上,有 3 种分割 DF 的方法:
- 方括号-子集
- 基于位置标签的索引
- iloc -位置索引
另一个选项 ix 就像是融合了 loc & iloc 。但是我们不会讨论这个,因为它已经过时了。
首先,让我们探索一下 方括号 的做法。
# Select the row where mpg value is minimum
cdf[cdf['mpg']==cdf['mpg'].min()]

mpg 最小的行
如果您想将选定的列从其中删除,这又是一个带有列名的方括号。
cdf[['mpg','horsepower','origin']][cdf['mpg']==cdf['mpg'].min()]

从 mpg 最小的行中选择列
假设我们想基于这个条件设置一些值。我们可以这样做,但是记住这将抛出一个警告,并且不是推荐的方法。
cdf['mpg'][cdf['mpg']==cdf['mpg'].min()] = 9.001

设置值时出现警告消息

使用[]将 mpg 最小值设置为 9.001
推荐的方法是使用 loc 属性。
# Setting mpg value based on condition using loc
cdf.loc[cdf['mpg']==cdf['mpg'].min(),'mpg'] = 9.005

使用 loc 的最小 mpg 值行集
# If you want to modify multiple values based on a condition
cdf.loc[cdf['mpg']==cdf['mpg'].min(),['mpg','weight','name']] = 9.005,4733,'fordtorino'
cdf.loc[cdf['mpg']==cdf['mpg'].min()]

基于 loc 条件的多个值集
是的,我甚至在第二个命令中使用了 loc 条件,同时访问具有最小“mpg”值的行。您可以使用方括号或 loc 来访问它。但是对于修改 DF 中的值,推荐的方法是使用 loc 。
还可以指定要从数据中提取的选定列和索引,如下所示:
# Select first 5 indexes from column mpg to displacement
cdf.loc[0:5,'mpg':'displacement']

将 0 到 5 列 mpg 索引到位移
# Select first 5 indexes from column mpg to displacement
cdf.loc[[1,3,5],['mpg','displacement']]

使用 loc 选择的索引和列
iloc 可用于根据从 0 到(行数-1)分配给 DF 每行的虚拟索引位置提取行。
# Accessing row using index
cdf.iloc[[28]]

使用 iloc 的行索引 28
# Accessing the series based on index using iloc
cdf.iloc[28]

指数系列 28
# Accessing row,column based on index using iloc
cdf.iloc[28]['weight']
cdf.iloc[28,4]
>>> 4733
第一个命令显式地访问“weight ”,指定字段名,而第二个命令指向列索引 4 (weight)来获取值。
由于将涉及多个 DF,请在以下部分中小心遵循 DF 名称。
设置指标
set_index() 函数帮助使用一个或多个现有列设置 DF 索引(行标签)。有一个参数'代替',默认设置为 False。如果命令不应返回结果 DF,而是直接在调用 DF 中进行更改,则可以将其设置为 True。
abc_df = cdf.set_index("name")
abc_df.head()

使用名称作为索引从 cdf 创建 DF abc
loc 在使用基于标签的索引访问行时起着重要的作用。
abc_df.loc['buick skylark 320']

使用 loc 的基于标签的选择
复位索引
我们有包含列名的 abc_df 作为索引。为了理解 reset_index 是如何工作的,我将把“cdf”中的行子集化到一个名为“rdf”的新 df 中。
rdf = cdf.loc[cdf['mpg']>17]
rdf.head()

mpg>17 的 rdf 子集 df
要删除索引并将 DF 索引重置回默认数字索引或将现有数字索引从 0 重置为(行数-1),我们使用reset _ index()函数。
# inplace=True makes the changes directly in rdf
rdf.reset_index(inplace=True)
rdf.head()

rdf 索引重置
您可以在新列“index”中看到初始索引值。您可以通过将参数 drop 设置为 True 来删除它。例如,在当前将名称作为索引的 abc_df 中,让我们重置索引,将 drop 参数设置为 True。

以名称为索引的 abc_df
abc_df.reset_index(inplace=True,drop=True)
abc_df.head()

abc_df 索引重置-名称删除
创建新列
只有当值的长度与索引的长度匹配,或者在 DF 列中设置了单个值时,才能创建新列。
# Creating a column name set to 'xxx'
abc_df['name'] = 'xxx'# Creating a column based on existing columns
abc_df['calc'] = abc_df['cylinders']*abc_df['mpg']
abc_df.head()

添加了新列-名称,计算
扔下一列
可以使用 drop() 函数删除 DF 中的一列或多列。或者要删除单个列,您也可以使用 del() 函数。
# Drop a single column - index
del(rdf['index'])
# Drop multiple columns
rdf = rdf.drop(columns=['displacement','weight'])
rdf.head()

从 rdf 中删除的列
重命名一列
使用 rename() 函数重命名一个或多个 DF 列。
# Renaming the columns of DF
rdf = rdf.rename(columns={
'acceleration':'acc',
'model_year':'year'
})
rdf.head()

重命名的列
按列排序
使用sort _ values()函数对基于列的 DF 进行排序。默认情况下,升序参数为真。如果设置为 False,DF 将按降序排列。
weight_sorted_df = mpg_df.sort_values(by=['weight'],ascending=False)
weight_sorted_df.head()

DF 按 desc 顺序按重量排序
执行字符串操作
我发现非常有用且最常用的字符串操作:
- strip()修剪空白
- 大写的 upper()
- 小写的 lower()
- 包含()来检查字符串是否存在
# String operations
# Trimming whitespaces and making origin capital
kdf = mpg_df.copy()
kdf['origin'] = kdf['origin'].str.strip()
kdf['origin'] = kdf['origin'].str.upper()
kdf.origin.unique()

字符串操作后的唯一值
# Rows where origin contains string 'eur' irrespective of the case
kdf[kdf['origin'].str.contains('eur',case=False)].head()

原产地为 str 'eur '
有关字符串操作技术的更多信息,请参考 Geeksforgeeks 。
创建数据帧
# Creating a DF using a dictionary with list of prices & country
price_dict = {'country': ['usa','japan','europe'],
'price': [50000,85000,45000]}
price_df = pd.DataFrame(price_dict)
print(price_df)

创建的价格 DF
创建 DF 有许多方法。我只提到其中之一。要查看创建 DF 的所有不同方法,请参考 Geeksforgeeks 。
追加到数据帧
【append()】函数可用于将行追加到 DF 对象的末尾。参数‘ignore _ index’默认为 False,这意味着追加的 DF 的索引将保持在追加前的状态。
考虑一个名为“pk”的新 DF,如下所示,追加到 mpg_df 中

要追加的新 DF 'pk '
mpg_df.append(pk)

附加到 mpg_df 的新 DF
mpg_df.append(pk,ignore_index=True)

追加新 DF 时忽略索引
串接数据帧
使用 concat() 函数可以将行或列与现有的 DF 连接起来。“轴”参数决定要连接的轴。轴 0-索引(默认),1 列。
# Concatenating the new DF pk and mpg_df
pd.concat([pk,mpg_df],ignore_index=True) # axis 0 by default

串联“主键”& mpg_df
类似地,使用 axis=1 进行连接将在现有 DF 的右侧创建新列。
填充缺失 NA 值
fillna() 函数可用于填充 DF 中出现的所有 NaN 或 NULL(缺失)值。
# Filling the mpg_df missing country price values with 0
mpg_df['price'] = mpg_df['price'].fillna(0)# Or filling missing values in multiple numeric columns in DF
mpg_df[['price','horsepower']] = mpg_df[['price','horsepower']].fillna(0)# For filling entire DF missing values with 'Missing'
mpg_df = mpg_df.fillna('Missing')
删除缺失值的行/列
【dropna()函数可用于删除缺失值的行/列。
# Dropping rows with any missing values
tmp_df = mpg_df.dropna(axis=0,how=’any’,subset=None,inplace=False)'''Dropping columns inplace with all values missing. subset checks dropna only for the mentioned columns'''mpg_df.dropna(axis=1,how=’all’,subset = [’mpg’,’price’,’origin’],inplace=True)
解决问题的时间
假设我们必须根据每个原产地设置一个“价格”属性。有许多方法可以做到这一点,我将告诉你如何做。
- for 循环
- 应用
- 合并
- 通信线路(LinesofCommunication)
- 绘图
为循环
for i in cdf['origin']:
if i == 'usa':
cdf['price'] = 50000
elif i == 'japan':
cdf['price'] = 85000
elif i == 'europe':
cdf['price'] = 45000
应用
在 DF 上调用 apply() 上的函数将对所有行/列执行该计算。当涉及复杂操作时,被认为比循环更快。
# Function defined to calculate the price
def calc_price(df):
if df['origin'] == 'usa':
df['price'] = 50000
elif df['origin'] == 'japan':
df['price'] = 85000
elif df['origin'] == 'europe':
df['price'] = 45000
return df
weight_sorted_df = weight_sorted_df.apply(calc_price,axis=1)
合并
合并 DFs 是处理数据时最重要的功能之一。Pandas 支持所有可以在基于列或索引的 DataFrame 对象之间执行的数据库连接操作。这就是一个 merge() 函数默认的样子。
pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None)
- “左”和“右”指的是测向对象
- 如果两个 DFs 有共同的列,则“on”指的是列列表
- 如果我们没有公共列,我们使用“left_on”和“right_on”来指定特定于 DF 对象的列名
- “how”指的是连接操作-(左、右、内、外)

解释了合并方法。来源。
现在让我们合并 rdf 和 price_df 数据帧对象。
rdf = pd.merge(
rdf,
price_df,
left_on = 'origin',
right_on = 'country',
how = 'left'
)
rdf.head()

rdf & price_df 已合并
loc
创建新的列价格,并使用 loc 根据条件设置列值。这种方法肯定比循环或应用快。
abc_df.loc[abc_df['origin']=='usa','price'] = 50000
abc_df.loc[abc_df['origin']=='europe','price'] = 45000
abc_df.loc[abc_df['origin']=='japan','price'] = 85000
映射
使用 map() 函数根据字典的键映射值并分配相应的值。
p_dict = {'usa':50000,'europe':45000,'japan':85000}
mpg_df['price']=mpg_df['origin'].map(p_dict)
mpg_df.head()
在我们宣布我们已经掌握了 Pandas 中用于数据争论的所有基本功能之前。有一个重要的概念仍然悬而未决:
分组依据
groupby() 函数用于将数据分组。它有助于以一种易于解释和计算的方式聚合数据。
比方说,我想找出基于“原点”的所有值的平均值。
mpg_df.groupby('origin').mean()

按原点分组的所有值的平均值
'''Grouping based on multiple columns & calculate sum of cylinders used in each origin, will return a series'''mpg_df.groupby(['origin','price'])['cylinders'].sum()

按产地和价格分组的钢瓶总数
正如我们已经知道的,为了得到 DF 形式的结果而不是级数,我们使用了双方括号。
mpg_df.groupby(['origin','price'])[['cylinders']].sum()

分组依据 DF
mpg_df.groupby(['origin','name'])['name'].count()

按来源分组的名称
Groupby 可以在不同的语境下有效使用,获得不同的结果。根据应用程序,可以结合使用不同的参数和函数。
Groupby 本身可以成为一篇文章的主题。因此,要想更详细地了解 groupby,你可以浏览以下我觉得有趣的媒体文章&涵盖了所有的基础:
- 熊猫团比解说和 11 个团比的例子由Soner yld rum
- 高效数据汇总&分析由拉希达·纳斯林·萨克斯利
- 斯凯勒·戴尔的的《掌握群体指南》
是的!你现在已经学会了与熊猫争论数据的艺术。Pandas 中的大多数命令&功能都很简单并且容易实现,只要我们清楚我们想要用手头的数据实现什么。
总结
我们在本文中涉及的概念:
- 将 数据加载到数据帧中
- 探索 数据
- 使用不同的方法操纵 数据
在几个数据集上练习和做一些 StackOverflow 搜索都是这个过程的一部分。
你挖得越深,发现的就越多
包装完毕
感谢到目前为止的所有人,我希望这篇文章对你有所帮助。请在评论中分享您的反馈/疑问。现在,是时候使用您的数据争论技能了。祝你好运!
如果你觉得这篇文章很有趣,并且对数据科学/机器学习充满热情,点击 关注 并随时在LinkedIn上添加我。
References:
1.[https://pypistats.org/](https://pypistats.org/)
2.[https://www.tutorialspoint.com/python_pandas/python_pandas_merging_joining.htm](https://www.tutorialspoint.com/python_pandas/python_pandas_merging_joining.htm)
3.[https://pandas.pydata.org/docs/reference/frame.html](https://pandas.pydata.org/docs/reference/frame.html)
Python 程序员必备的字典突变
回顾 Python 中字典数据类型的一些最重要的特性。

(src =https://pixabay.com/images/id-2771936/
介绍
T 字典数据类型可以说是 Python 编程语言中最重要的数据结构。对于数据科学家来说更是如此,他们经常处理像字典一样工作的带标签的数据。在许多方面,数据框只是一个简单的表格视图,其功能与字典完全相同,并具有一些有助于数据处理的功能。
数据结构也是 JSON 的基础,JSON 是一种非常流行的数据格式,用于存储和通过请求传输。Python 有一个非常棒的字典数据结构的实现,数据结构有很多现成的功能。不用说,了解字典数据结构的来龙去脉将在数据科学工作中派上用场。有很多东西需要学习,但今天我想回顾一些我认为非常特别和有用的字典方法,尤其是对于数据科学。
对词典进行分类
我们都听说过对熊猫系列和数据框进行排序,但是你知道你可以用字典做本质上相同的事情吗?我们既可以按字母排序,也可以按数值排序。这很方便,因为它允许我们同时对所有的键及其配对进行排序。这也可以用元组和其他数据类型来完成,但是现在我们将坚持使用字典。考虑下面的字典:
cities: dict = {
"Detroit": 3619342323,
"Las Vegas": 99870,
"Las Angeles": 9525067,
"Boulder": 17098246,
"Atlanta": 2780400,
"Paducah": 752612,
"Tampa": 9596961,
}
我们可以使用 sorted()方法和 dict cast 对这个字典进行排序。对于键,我们将使用字典中的数值。为了对每个键都这样做,我们需要使用一个 lambda 表达式来计算每个值的键。如果你想了解更多关于 lambda 的知识和它的用途,我有一篇关于它的文章,我认为值得一读!你可以在这里找到这篇文章:
让我们继续整理字典:
citysorted = dict(sorted(cities.items(), key = lambda kv: kv[1], reverse=False))
现在我们的字典是这样的:
{'Las Vegas': 99870,
'Paducah': 752612,
'Atlanta': 2780400,
'Las Angeles': 9525067,
'Tampa': 9596961,
'Boulder': 17098246,
'Detroit': 3619342323}
为了证明我们也可以用字母特性做到这一点,我现在将按键的[0]位置对其进行排序:
{'Atlanta': 2780400,
'Boulder': 17098246,
'Detroit': 3619342323,
'Las Angeles': 9525067,
'Las Vegas': 99870,
'Paducah': 752612,
'Tampa': 9596961}
合并词典
合并是 Pandas 模块提供的工具之一,它不同于仅仅使用字典数据结构。为了将字典合并在一起,我们不一定需要编写函数,因为我们可以简单地使用包含在 dict 类中的 update 函数。然而,我喜欢编写一个函数来这样做,以便复制字典,而不是改变另一个,因为我在编程时经常专注于保留数据的形式。也就是说,这是一个相对简单的操作,主要涉及对字典类型中已经提供的函数的调用。为了编写这个合并函数,我们要做的第一件事是考虑我们的参数和输出,我们想要提供两个字典,并得到一个字典。让我们把它放到一个函数调用中:
def merge_dicts(one: dict, two: dict):
现在我们想要复制字典,这样我们就不会改变原始类型,因为如果我们在第一次尝试中意外地破坏了数据,那将是灾难性的——尤其是如果我们第一次尝试就搞砸了这个函数。因为我们不处理字典中包含的单个数据类型,所以没有必要进行深度复制,所以我们可以很容易地使用浅层复制。
temp: dict = one.copy()
newdict: dict = one.copy()
最后,我们将调用 update 函数并返回我们的新字典:
newdict.update(two)
return(newdict)
最终结果如下所示:
def merge_dicts(one: dict, two: dict):
newdict = one.copy()
newdict.update(two)
return(newdict)
现在我们将创建一个新的字典来调用它:
additional_cities: dict = {
"Dover": 8323,
"Colorado Springs": 4990,
"Seattle": 95067,
}
现在让我们通过函数传递我们的两个字典!
allcities = merge_dicts(additional_cities, cities)
这就是结果:
{'Dover': 8323,
'Colorado Springs': 4990,
'Seattle': 95067,
'Detroit': 3619342323,
'Las Vegas': 99870,
'Las Angeles': 9525067,
'Boulder': 17098246,
'Atlanta': 2780400,
'Paducah': 752612,
'Tampa': 9596961}
酷!
默认
Python 中经常使用字典来存储数据。随着时间的推移,这些数据会不断增加,有时可能会有丢失的值或键,如果我们没有适当的方法来处理它们,就会产生问题。对于这个问题,解决方案是使用 dictionary 类中包含的 setdefault()函数。在下面的例子中,我将这样做,并默认输入键没有被分配给我们的数据的平均值的对:
allcities.setdefault("Unknown", sum(allcities.values()) / len(allcities.values()))
这将把默认值追加到我们的字典中,同时也使传入的数据默认为这两个值。
将 CSV 读入字典
正如我之前提到的,JSON 数据格式在发送、接收甚至存储 web 开发和数据科学的数据时非常流行。然而,对于数据库之外的真实数据存储,最流行的格式之一可能是逗号分隔值或 CSV 文件。这些文件很棒,因为它们包含的语法字符比 JSON 之类的格式少得多。使用 CSV 模块,我们可以获得一个 CSV 阅读器,它会将我们的 CSV 文件转换为数据!让我们导入并试用它!:
import csv
现在我们可以通过调用 csv 来创建一个新的 reader 对象。用 open()方法调用 DictReader()类,以便将 CSV 文件转换成字符串:
reader = csv.DictReader(open("spreader.csv", "r"))
现在我们可以迭代该读取器类型,为我们提供键和相应的值:
for row in reader:
print(row){'hello': 'how', 'medium': 'is'}
{'hello': 'everyone', 'medium': 'doing?'}
最后,我们可以使用之前使用的 update()函数将这些内容推入字典:
for row in reader:
z[row["Code"]] = row
用标记可视化字典

(图片由作者提供)
大约一个月前,我写了一篇非常酷的文章,讲述了如何以表格的形式可视化字典,就像可视化数据框一样。因为这是一个很酷的技巧,我决定把它加在这个列表的末尾,这样字典征服者就可以找到更好的方法来查看他们的数据。如果你有兴趣阅读这篇文章,你可以在这里查看:
结论
字典数据结构对于软件工程工作几乎是必不可少的,对于数据科学工作更是至关重要。该数据结构使得一次处理多个对应的列表变得容易得多,并且在便利性和功能上是该语言打包的其他数据结构所无法比拟的。我认为,知道如何有效地使用字典可以让你与众不同,并对你的工作产生重大影响,使用其中的一些技巧肯定可以让字典的使用变得更容易一些。非常感谢你看我的文章!
Excel 中用于数据预处理的基本函数
本文讨论了 Microsoft Excel 中用于数据预处理的几个基本函数以及几个示例。这些函数简化了数据预处理的过程。
本文假设读者对 Excel 函数有基本的了解。

对于非程序员来说,Microsoft Excel 是预处理和处理结构化数据的绝佳工具。Excel 有一些功能和技术可以使清理结构化数据变得更加容易。我们将讨论许多函数中的几个,并给出几个例子。在继续之前,我们将讨论几个基本函数,它们将是本文后面一个更大的公式的一部分。
基本功能
- 如果
这个函数检查一个条件,并相应地返回一个指定的值。在下面的示例中,该函数检查条件“是 2 大于 3”。如果条件为真(事实并非如此),则函数返回“2 > 3 ”,否则返回“2 < 3”.
=IF(2>3,"2 > 3","2 < 3")
Since, 2 isn’t greater than 3, so the condition is FALSE and the value returned will be “2 < 3”.
2. IFERROR
该函数有助于错误处理。在 Excel 中,最常见的错误是#N/A,#VALUE!,#DIV/0!,#REF!,#姓名?,#NUM!,不一而足。让我们来看一个错误处理的例子,我们试图用 1 除以 0。
=IFERROR(1/0,-1000)
在上面的例子中,我们将 1 除以 0。此计算返回一个错误(#DIV/0!).IFERROR 函数将计算/公式作为输入,如果计算结果不是错误,则返回计算结果。如果结果是错误的,则返回我们指定的值(在上例中为-1000)。
如果不处理,包含错误计算的公式也会返回错误。例如,如果我们添加 10 个数字(使用 SUM 函数),其中#DIV/0!是 10 个数字之一,那么 SUM 函数也将返回#DIV/0!。
3.左
这个函数返回字符串的前 n 个字符。我们来看一个例子,提取字符串“Excel”的前三个字符。该函数的输出将是“Exc”。
=LEFT("Excel",3)
4。右侧
这个函数返回字符串的最后 n 个字符。我们来看一个例子,提取字符串“Excel”的后三个字符。该函数的输出将是“cel”。
=RIGHT("Excel",3)
5。中旬
如果我们输入起始数和要返回的字符数,这个函数将返回一个字符串的子串。让我们看一个从字符串“Excel”中提取子字符串“xce”的例子。在本例中,起始数字是 2,要返回的字符数是 3。
=MID("Excel",2,3)
6。找到
此函数返回子字符串/字符在字符串中第一次出现的位置。我们来看一个例子,求子串“ce”在字符串“Excel”中的位置。该函数返回 3,因为它是第一个也是唯一一个“ce”出现的位置。
=FIND("ce","Excel")
查找功能还使用户能够控制搜索的起点。我们来看一个例子,找出“a”在“abca”中第二次出现的位置。我们将添加一个起点参数 2(因为我们想从搜索中排除第一个“a”)。该函数返回 4。
=FIND("a","abca",2)
FIND 中的搜索是区分大小写的,还有一个名为 search 的函数和 FIND 一样,但是它的搜索是不区分大小写的。查找和搜索函数返回#VALUE!如果在字符串中找不到指定的子字符串/字符,则出现错误。
7。ISNUMBER
如果给定的输入是一个数字,这个函数返回 TRUE,否则返回 FALSE。让我们看一个例子,在这个例子中,我们将“abc”和 123 输入到函数中。
=ISNUMBER("abc")
=ISNUMBER(123)
在上面的示例中,第一个公式返回 FALSE,因为“abc”不是数字。第二个公式返回 TRUE,因为 123 是一个数字。
8。ISTEXT
如果给定的输入是字符串/文本,该函数返回 TRUE,否则返回 FALSE。让我们看一个例子,在这个例子中,我们将“abc”和 123 输入到函数中。
=ISTEXT("abc")
=ISTEXT(123)
在上面的示例中,第一个公式返回 TRUE,因为“abc”是一个字符串/文本。第二个公式返回 FALSE,因为 123 是一个数字。
9。替换
此函数用替换字符串替换字符串中第 n 次出现的子字符串。此函数区分大小写,默认情况下“n”为 1。我们来看一个例子,将字符串“Excel 很酷”中的“Excel”替换为“MS Excel”。
=SUBSTITUTE("Excel is cool","Excel","MS Excel")
让我们看另一个例子,在字符串“Excel 很酷”中用“它”替换第二次出现的“Excel”。Excel 是微软开发的”。该公式返回“Excel 很酷。它是由微软开发的”。
=SUBSTITUTE("Excel is cool. Excel is developed by Microsoft","Excel","It",2)
10。修剪
这个函数从字符串中删除前导和尾随空格。
=TRIM(" a ") returns "a"
11。计数
该函数返回一组单元格中非空单元格的计数。
=COUNTA(A1:A10)
12。COUNTBLANK
该函数返回一组单元格中的空单元格数。此函数有助于查找列中缺失值的计数。
=COUNTBLANK(A1:A10)
13。COUNTIF
该函数返回一组单元格中指定值的计数。这对于查找被 999,-1000,-等值屏蔽的缺失值的计数很有用。等。让我们来看一个例子,计算一系列单元格中出现“-”的次数。我们还可以指定包含搜索条件的单元格地址,而不是“-”。
=COUNTIF(A1:A10,"-")
=COUNTIF(A1:A10,B1)
几个数据预处理示例
- 找出一个单词在一个字符串中出现的次数
我们可能需要计算一个单词/字符在一个字符串中出现的次数。让我们看一个例子来计算单词“is”在字符串中出现的次数。

作者图片
=(LEN(A2)-LEN(SUBSTITUTE(LOWER(A2),"is","")))/LEN("is")
让我们将公式分解成几个部分,以便更好地理解它,并将其应用于上图的单元格 A2 中的名称。
- LEN(A2)计算字符串的长度。这将返回 44。
- LEN(SUBSTITUTE(LOWER(A2)," is ",""))从字符串中删除单词" is "并计算其长度。这会返回 40。A2 中的字符串被转换为小写,以使替代函数不区分大小写。
- 我们从原始字符串的长度中减去不带“is”的字符串的长度。这给出了单词“is”在字符串中出现的次数(即 2)乘以单词“is”的长度(即 2),因此,我们得到 2 * 2 = 4。因此,我们需要将结果除以搜索字符串的长度(即“is”的长度为 2),最终得到 4 / 2 = 2。
2。统计字符串中的单词数
我们可能需要找出一个字符串中的单词数。让我们来看一个来自泰坦尼克号数据集的例子,来计算一个乘客名字中的单词数。

作者图片
=LEN(A2)-LEN(SUBSTITUTE(A2," ",""))+1
让我们将公式分解成几个部分,以便更好地理解它,并将其应用于上图的单元格 A2 中的名称。
- LEN(A2)计算字符串的长度。这将返回 23。
- LEN(SUBSTITUTE(A2," ",""))从字符串中删除空格,并计算字符串的长度。这会返回 20。
- 我们从原始字符串的长度中减去不带空格的字符串的长度。这给了我们字符串中的空格数,即 3。将空格数加 1 得到单词数,即 4。
3。提取字符串的第一个单词
在某些情况下,我们可能需要从现有特征创建附加特征。让我们看一个从人名中提取人名的例子。我们假设一个名字的第一个单词就是标题。没有空格的名字被认为没有头衔。

作者图片
=LEFT(A6,IFERROR(FIND(" ",A6)-1,0))
让我们将公式分解成几个部分,以便更好地理解它,并将其应用于上图的单元格 A2 中的名称。
- FIND 返回名称中第一个空格的位置,即 4。我们从位置中减去 1 来排除空间本身。现在变成了 3。
- 如果名称中没有空格,IFERROR 将处理 FIND 返回的错误。如果名称没有空格,IFERROR 返回 0。在这种情况下,名称中有一个空格。因此,FIND 不会返回任何错误。
- LEFT 返回 FIND 返回的前 n 个字符,即前 3 个字符。如果名称中没有空格(如上图中的单元格 A6 ),则 IFERROR 返回 0,LEFT 返回前 0 个字符,即 nothing。
4。提取一个字符串的最后一个单词
让我们看一个从名字中提取姓氏的例子。我们假设一个名字的最后一个字是姓氏。当名称中没有空格时,名称将按原样返回。

作者图片
=MID(A2,IFERROR(FIND("~",SUBSTITUTE(A2," ","~",LEN(A2)-LEN(SUBSTITUTE(A2," ",""))))+1,1),LEN(A2))
把公式分解成几部分来解释可能会使事情变得复杂。所以,我们只讨论其中涉及的过程。
- 通过查找字符串中的空格数,可以找到空格最后出现的位置。
- 最后一个空格被替换为“~”(可以是不属于字符串的任何字符)。
- 我们找到了字符串中“~”的位置,这又是空格在字符串中最后一次出现的位置。加上 1 会使位置增加 1,因此不包括空格。
- 如果字符串中没有空格,IFERROR 返回原始字符串的长度。
- MID 返回从最后一个空格开始直到字符串结束的子字符串。由于要提取的字符数量未知,我们可以给出最大可能数量,即原始字符串的长度。
5。提取字符串的第 n 个单词
让我们看一个例子,从 Titanic 数据集中的人名中提取人名。名字的第二个字是头衔。因为第二个单词是大多数名称中的标题,所以建议使用 Excel 的文本到列功能。然而,我们也将讨论公式。

作者图片
=SUBSTITUTE(LEFT(MID(A2,IFERROR(FIND("~",SUBSTITUTE(A2," ","~",1))+1,1),LEN(A2)),IFERROR(FIND(" ",MID(A2,IFERROR(FIND("~",SUBSTITUTE(A2," ","~",1))+1,1),LEN(A2)))-1,LEN(A2))),".","")
在 SUBSTITUTE(A2," "," ~ ",1)中用' n-1 '替换 1,提取字符串的' n '个单词。在上面的例子中,我们提取了字符串的第二个单词,所以它是 1。下面的公式提取字符串的第三个单词。
=SUBSTITUTE(LEFT(MID(A2,IFERROR(FIND("~",SUBSTITUTE(A2," ","~",2))+1,1),LEN(A2)),IFERROR(FIND(" ",MID(A2,IFERROR(FIND("~",SUBSTITUTE(A2," ","~",2))+1,1),LEN(A2)))-1,LEN(A2))),".","")

作者图片
把公式分解成几部分来解释可能会使事情变得复杂。所以,让我们只讨论提取第 n 个单词的过程。
- 最后出现的“n-1”个空格被替换为“~”(可以是不属于字符串的任何字符)。
- 我们找到了字符串中“~”的位置,这又是空格在字符串中最后一次出现的位置。加上 1 会使位置增加 1,因此不包括空格。
- 如果字符串中没有空格,IFERROR 返回原始字符串的长度。
- MID 返回从最后一个空格开始直到字符串结束的子字符串。由于要提取的字符数量未知,我们可以给出最大可能数量,即原始字符串的长度。
- 提取结果字符串的第一个单词,如示例 3 所示。
- 替换句点(。)在标题的末尾加上一个空字符串(" ")。
6。最小-最大标准化
最小-最大归一化对数值特征进行归一化,使其保持在范围[0,1]内。

作者图片
=(A2-MIN($A$2:$A$9))/(MAX($A$2:$A$9)-MIN($A$2:$A$9))
我们可以使用下面的公式在范围[new_min,new_max]内对数据进行归一化。在下面的公式中,new_max = 10,new_min = 1。因此,数据将被归一化,以使它们保持在范围[1,10]内。
=(((A2-MIN($A$2:$A$9))/(MAX($A$2:$A$9)-MIN($A$2:$A$9)))*(10-1))+1

作者图片
7 .。离散化
离散化将数值特征转换为分类特征。它将数字特征转换成类区间,如 0–10、11–20 等。一旦确定了类间隔/箱,我们就可以使用 Excel 公式来离散化数据。在下面的例子中,我们将数值离散化为 4 个类区间,即“0 到 30”、“31 到 60”、“61 到 90”和“91 到 100”。任何大于 100 的值都被指定为“未定义”类。
=IF(AND(A2>90,A2<=100),"91 to 100",IF(A2<=30,"0 to 30",IF(AND(A2>=31,A2<=60),"31 to 60",IF(AND(A2>=61,A2<=90),"61 to 90","Undefined"))))

作者图片
这是一些使用 Excel 函数的数据预处理技术。还有其他预处理/清理技术可以在不使用函数的情况下在 Excel 中完成,这些技术不在本文中讨论。如果你有编程背景,建议使用 Python 或 r 等编程语言,对于非程序员来说,Excel 是处理结构化数据的最佳选择之一。
每个程序员都应该知道的基本 Git 命令
学习 Git 的基础知识

在我们进入 Git 之前,让我们简单地讨论一下版本控制。版本控制或源代码控制系统赋予我们协作、管理和跟踪代码历史的能力。该系统将元数据存储在一个称为存储库的数据结构中,这允许我们使用像 revert 这样的特性来撤销代码。因此,它使软件开发更容易和更快。有两种类型的版本控制系统,集中式和分布式。所以,Git 是一个分布式版本控制系统。它是 Linus Torvalds 在 2005 年创建的最常用的源代码控制系统之一。通过这篇文章,您将了解一些常用的 Git 命令。
设置
对于这个设置,我将使用 Git 命令行工具。此外,有多个 Git GUI 客户端可用于不同的操作系统。我将把 GitHub 用于存储库托管服务,它带有大量很酷的特性。
Git 仓库
有两种方法可以启动 Git 存储库,本地和远程。
本地知识库
我们可以使用 Git 命令git init将本地目录转换成 Git 存储库。本地 repo 的 Git 工作流将创建一个本地 repo,并最终将其推送到一个远程存储库,以便与其他人协作。
[start] the working directory → git add → staging area → git commit →
local repository → git push → remote repository [end]
现在,我们可以使用 Git 命令git pull从远程 repo 中提取任何更改来更新我们的本地存储库。
[end] the working directory ← git pull ← remote repository [start]
Git 命令
首先,让我们创建一个本地存储库。
cd Desktop
git init neptune
cd neptune
touch README.md
echo TODO > README.md
git add README.md
git commit -m "README"
git push
如果要将现有目录转换为本地存储库,请使用以下方法。
cd Desktop
cd neptune
git init
touch README.md
echo TODO > README.md
git add README.md
git commit -m "README"
git push
git 初始化
git init命令将创建一个.git目录,您可以使用下面的命令看到这个目录。它还将创建一个默认的或称为 master 的主分支。
ls -a
# . .. .git README.md
Git 管理.git目录中的源代码控制。
git 添加
git add fileName
git add *
它会将一个或多个文件添加到临时区域。
git 提交
git commit -m "commit message"
它会将一个或多个文件提交到本地存储库。如果 Git 已经在跟踪您的文件,您可以使用一个命令添加和提交。所以它对修改过的文件有效,但对新文件无效。
echo 1\. TODO_1 >> README.md
git commit -am "add and commit"
git 推送
现在访问 GitHub 来创建一个远程存储库,这样我们就可以与其他人协作。


在 GitHub 上创建一个远程资源库
# add a remote repository
# where the origin is the remote location name
# one-time setup
git remote add origin git@github.com:lifeparticle/neptune.git
# rename the current branch into main, one-time setup
git branch -M main
# push all the local changes to the repository
# the -u parameter is needed only for the first push
git push -u origin main
远程存储库
我们可以使用 Git 命令git clone获得一个现有的 Git 存储库。远程 repo 的 Git 工作流是将一个远程 repo 从 GitHub 克隆到我们的本地机器上。
[end] the working directory ← git clone ← remote repository [start]
现在,我们可以像以前一样将本地更改推送到远程存储库。
[start] the working directory → git add → staging area → git commit →
local repository → git push → remote repository [end]
Git 命令
首先,让我们删除之前创建的本地存储库。从机器中移除文件夹时,请务必仔细检查。
sudo rm -r neptune
git 克隆
首先,从 GitHub 克隆远程存储库。
git clone git@github.com:lifeparticle/neptune.git
因为我们有一个链接到远程存储库的本地存储库,所以我们可以添加、提交并最终将我们的更改推送到远程存储库。此外,通过使用 Git 命令git pull,我们可以从远程 repo 中提取任何更改来更新我们的本地存储库。
其他重要的 Git 命令
下面是一些更常用的基本 Git 命令。
从远程存储库中获取所有最近的更改
我们可以使用git pull命令将任何更改合并到我们的本地存储库中。在我们推送之前,这一步是必要的,以避免任何推送拒绝,因为在我们上次推送之后,另一个合作者可能已经对远程存储库进行了更改。对于这个例子,我对 GitHub 的 README.md 进行了修改。
git pull
显示分支历史
我们可以使用git log来查看分支历史。在这里,我们正在查看主分支的历史。
git log main
显示存储库的当前状态
git status命令将显示我们当前在哪个分支上。如我们所见,我们在主干道上。此外,它还会显示与存储库相关的其他数据。
git status
# On branch main
# nothing to commit, working tree clean
创建新分支
我们可以使用git branch来创建一个新的分支。
git branch dev-007
切换到另一个分支
我们可以用git checkout切换到一个分支。
git checkout dev-007
删除分支
我们可以使用git branch -d删除一个分支。但首先,你需要切换到不同的分支。
git checkout main
git branch -d dev-007
显示文件之间的差异
我们可以使用git diff命令来比较文件之间的差异。
echo 4\. TODO_4 >> README.md
git diff
在存储库中移动文件
我们可以使用git mv命令在存储库中移动文件。
echo Hello world > index.html
git add .
git commit -m "index file"
mkdir web
git mv index.html web
git commit -m "move index file"
从存储库中删除文件
我们可以使用git rm命令从存储库中删除任何不必要的文件。
echo 1\. Make space robot > todo_today.txt
git add .
git commit -m "todo"
git rm todo_today.txt
git commit -m "remove todo file"
我希望看完这篇文章后,你已经对 Git 有了基本的了解。Git 是软件开发中必不可少的一部分,所以任何程序员都必须尽早了解和学习 Git 版本控制。只管做 Git!
相关职位
</17-terminal-commands-every-programmer-should-know-4fc4f4a5e20e> [## 每个程序员都应该知道的 17 个终端命令
towardsdatascience.com](/17-terminal-commands-every-programmer-should-know-4fc4f4a5e20e)
在一行 Python 代码中估算缺失值的基本指南
使用随机森林和基于 k-NN 的插补预测缺失数据

图片来自pix abayWilli Heidelbach
现实世界中的数据集通常会有许多缺失记录,这可能是由于数据损坏或未能记录值而导致的。为了训练健壮的机器学习模型,在特征工程流水线期间,丢失值的处理是必不可少的。
有多种插补策略可用于对分类、数字或时间序列特征的缺失记录进行插补。你可以参考我以前的一篇文章,我已经讨论了处理数据集中缺失记录的 7 种策略或技术。
在本文中,我们将讨论一个开源 Python 库— missingpy 的实现,它使用基于随机森林和 k-NN 的模型来预测数字特征中的缺失值。
缺少副本:
missingpy 是一个开源 python 库,它使用基于预测的插补策略来插补缺失数据。它有一个类似于 scikit-learn 的 API,所以开发人员可以找到熟悉的界面。截至目前,missingpy 仅支持基于随机森林和 k-NN 的插补策略。
我们将使用 Kaggle 的信用卡欺诈检测数据集中的一些特征来估算缺失的记录,并比较 missingpy 库的性能。
安装:
missingpy 可以从 PyPI 安装,使用:
**pip install missingpy**
KNNImputer()和MissForest()是 missingpy 包中的两个 API。
用法:
我们将仅使用信用卡检测数据集中的 8 个特征和 25,000 个实例进行进一步演示。由于数据集没有缺失记录,我们将创建一个‘Amount’要素的副本,并用 NaN 值替换实际值。
准备好数据后,‘Amount’特征‘Amount_with_NaN’的副本在总共 25000 条记录的样本数据中有 4512 条空记录。

(图片由作者提供),缺少记录编号
MissForest 基于森林的随机插补:
missingpy 附带了一个基于随机森林的插补模型,可以使用**MissForest()**函数在一行 Python 代码中实现。
**from missingpy import MissForest****imputer = MissForest()
df_new = imputer.fit_transform(df)**
实例化 MissForest 模型后,拟合具有缺失记录的数据集。该方法返回数据集和估算值。
现在,让我们将估算值与“金额”特征的真实值进行比较,并查看估算的偏差。


(图片由作者提供),左:实际金额特征与估算值的 PDF 图,右:实际值与预测值偏差分布的 PDF;对于 MissForest 估算器
从上面的情节可以得出结论,
- 左图:我们可以观察到预测缺失数据和实际数量有相似的 PDF 图。
- 右图:实际值和预测缺失记录之间的偏差的 PDF 图是倾斜的,峰值为 0。我们有很长的尾巴,代表很少的预测偏离超过 100 个值。
现在,让我们来看一些统计数字,以观察缺失森林插补的表现。

(图片由作者提供)、实际数量的统计数字、使用 MissForest 预测的缺失数量及其偏差
KNNImputer —基于 kNN 的插补:
KNNImputer 的实现与 MissForest 插补的实现相同。现在让我们直接跳到实现和生成 PDF 图。
**from missingpy import KNNImputer****imputer = KNNImputer()
df_new = imputer.fit_transform(df)**


(图片由作者提供),左:实际金额特征与估算值的 PDF 图,右:实际值与预测值之间的偏差分布的 PDF;对于 KNNImputer

(图片由作者提供)、实际数量的统计数字、使用 KNNImputer 预测的缺失数量及其偏差
结论:
MissForest(基于随机森林的插补)和 KNNImputer(基于 k-NN 的插补)的表现几乎相似,给出了类似的插补值和实际值偏差的统计数字。就 RMSE 指标而言,KNNImputer 似乎表现得更好一些。
missingpy 库是一个非常方便的工具,可以在几行 Python 代码中预测丢失的数据。该包的 API 实现类似于 scikit-learn 的 API 实现,这使得开发人员熟悉该接口。
阅读我以前的文章,了解处理数据集中缺失值的 7 种技巧
</7-ways-to-handle-missing-values-in-machine-learning-1a6326adf79e>
参考资料:
[1]缺少文件副本:https://pypi.org/project/missingpy/
感谢您的阅读
生产中机器学习模型监控的基本指南
检测数据漂移的技术

图片由 Mediamodifier 来自 Pixabay
模型监控是端到端数据科学模型开发管道的重要组成部分。该模型的稳健性不仅取决于特征工程数据的训练,还取决于该模型在部署后的监控程度。
通常,机器学习模型的性能会随着时间的推移而下降,因此检测模型性能下降的原因至关重要。其主要原因可能是独立或/和从属特征的漂移,这可能违反模型的假设和数据分布。
在本文中,我们将讨论各种技术,以检测数据漂移独立或独立的特征,在生产推断数据。
为什么需要模型监控?

(图片由作者提供)、模型训练、验证和监控工作流
模型的性能随着时间的推移而下降有多种原因:
- 推理模型性能
- 推断数据分布不同于基线数据分布
- 业务 KPI 的变化
上面提到的是模型性能随时间下降的主要原因。在部署之后,需要监控已部署的模型,以测量模型性能和数据分布。在确定了模型衰减的原因之后,用更新的数据集重新训练现有的模型。
如何进行模型监控?
推断数据的实际目标类标签通常不会在前面出现。因此,很难使用标准的评估指标(如精确度、召回率、准确度、日志损失等)来衡量模型的性能。
有时,直到实际的目标类标签可用需要时间。但是也可以通过观察数据分布来衡量模型的稳健性。有各种技术来测量独立和从属特征中的数据漂移。
测量独立特征的漂移:
有各种方面来监控独立特征的漂移。
1.监控每个特征的分布:
如果我们观察到推理数据的工程特征或原始特征的分布发生了变化,我们可以预期模型性能会下降。测量偏差的一些流行的统计技术有:
- KL (Kullback Leibler)散度检验
- 科尔莫戈罗夫-斯米尔诺夫试验
- 卡方检验
2.监控统计特征:
我们需要监控推断和基线数据的统计特征,以观察数据集中的差异。一些统计特征是:
- 可能值的范围(分位数、平均值、最大值、最小值)
- 缺失值或空值的数量
- 数字特征的直方图分布
- 分类特征的不同值
3.监控多元特征的分布:
机器学习模型开发特征之间的一些交互来进行预测。如果特征之间的模式或分布发生变化,则可能导致模型性能下降。检测多元特征分布的技术是:
- 克拉默 Phi 试验
测量从属特征的漂移:
推理目标类的相关特征(目标标签)可能不会在生产中提前出现。一旦存在相关特征,就有各种技术来测量漂移,并得出模型性能是否恶化的结论。
1.目标类别的分布:
对于分类任务,目标类标签本质上是分类的。其思想是比较推理数据和基础数据之间目标类标签的分布。
对于回归任务,直方图或连续目标标签的统计特征可用于测量数据中的漂移。
2.监控推理模型性能:
一旦实际的目标类标签可用,那么可以通过评估和比较模型在标准度量上的性能来检测模型漂移。如果模型度量显示少于预期的数字,则需要重新训练模型。
结论:
在本文中,我们讨论了在产品中部署模型之后,检测推理数据集中的偏差的各种技术。随着时间的推移,模型漂移可能导致模型性能恶化。因此,在生产后监控模型的性能是很重要的。
参考资料:
[1] Vivek Kumar YouTube 播放列表:https://www.youtube.com/playlist?list = PLM pree 1k byuk 348 hs 7w 2 ezuywtgcsn 54j
喜欢这篇文章吗?成为 中等会员 继续无限制学习。如果你使用下面的链接,我会收到你的一小部分会员费,不需要你额外付费。
https://satyam-kumar.medium.com/membership
感谢您的阅读
Python 中多类和多输出算法的基本指南
如何训练多学习任务的 ML 模型

人们使用的大多数分类或回归涉及一个目标类别标签(因变量)和多个独立特征。一些机器学习分类或回归任务可能有两个或两个以上的因变量。标准的机器学习算法,如决策树、逻辑回归、SVM 等,并不是为预测多个输出而设计的。
在本文中,我们将讨论如何训练机器学习模型来预测分类和回归任务的多输出。在实现算法之前,让我们讨论一下什么是多类多输出算法。
多类多输出算法?
对于分类和回归任务,存在 1 个因变量或 1 个以上因变量的可能性。根据因变量的数量,我们可以定义多类和多输出算法:

(图片由作者提供),基于目标类标签的问题陈述类型
Scikit-learn 包提供了各种函数来实现多类分类、多输出分类和多输出回归算法。

(来源),sci kit-多类和多输出实现的学习函数
sklearn 包中的两个模块 multiclass 和 multioutput 涵盖了开发多学习任务模型的所有实现。上面提到的图表演示了可用于训练相应数据集的元分类器。
多类分类:
对于多类分类任务,数据有 1 个因变量,目标类有 2 个以上基数。鸢尾物种数据集是多类数据集的一个例子。大多数机器学习算法仅限于 2 类分类,无法处理多类数据集。scikit-learn 包中的多类模型实现了 3 个函数来训练这样的数据:
- One-vs-rest 分类器:这种策略适合每个类一个二元分类器。对于每个估计量,分类器与所有其他类别相匹配。
- 一对一分类器:这种策略适合每对类一个二元分类器。在预测时,选择获得最多投票的一对类别。
- 输出代码分类器:该策略将每个目标类转换成二进制代码。一个二进制分类器适用于二进制代码的每一位。使用这种策略,用户可以控制分类器的数量。
多标签分类:
对于多标签分类,数据有 1 个以上的独立变量(目标类),每个类的基数应为 2(二进制)。 Stackoverflow 标签预测数据集是多标签分类问题的一个例子。在这种类型的分类问题中,有不止一个输出预测。
大多数分类机器学习算法不能够处理多标签分类。需要使用机器学习算法的包装器来训练多标签分类数据。Scikit-learn 提出了两种包装器实现:
- MultiOutputClassifier :该策略适合每个目标一个二元分类器。该包装器可用于不支持多目标分类的估计器,如逻辑回归。
- 链式分类器:当分类目标相互依赖时,可以使用该策略。在这种策略中,二元估计器链是用独立特征以及最后一个估计器的预测来训练的。
多输出回归:
多输出回归类似于多标签分类,但这仅适用于回归任务。在这种条件问题语句中,数据有 1 个以上的连续目标标签。一些回归算法,如线性回归和 K-NN 回归器处理多输出回归,因为它们本质上实现了直接回归器。
对于不处理多输出回归的回归算法,scikit-learn 包提供了可用于此类任务的包装器:
- MultiOutputRegressor :这个策略适合每个目标类的一个回归估计量。这种策略可用于不处理多输出回归的回归估计器,如 LinearSVC 。
- RegressorChain :当回归目标特性相互依赖时,可以使用该策略。在回归量的这个策略链中,估计量是用独立的连续特征以及最后一个估计量的预测来训练的。
结论:
在本文中,我们讨论了 scikit-learn 包提供的用于训练多学习任务模型的功能。它提供了可与分类和回归估计器一起使用的包装器,用于训练多类分类、多标签分类和多输出回归问题。
参考资料:
[1]sci kit-学习文档:https://scikit-learn.org/stable/modules/multiclass.html
喜欢这篇文章吗?成为 中等会员 继续无限制学习。如果你使用下面的链接,我会收到你的一小部分会员费,不需要你额外付费。
https://satyam-kumar.medium.com/membership
感谢您的阅读
使用决策树模型执行功能宁滨的基本指南
如何为数字特征宁滨找到最佳存储桶

特征工程是机器学习模型开发管道的重要组成部分。机器学习模型只理解数字向量,因此数据科学家需要设计这些特征来训练一个健壮的机器学习模型。
宁滨或离散化用于将连续变量或数值变量编码成分类变量。有时,数字或连续特征不适用于非线性模型。因此,连续变量的宁滨会在数据中引入非线性,并有助于提高模型的性能。
有各种技术来执行特征宁滨,包括宁滨的非监督和监督方法。

(图片由作者提供),宁滨表演技巧
阅读我以前的文章,深入了解特性编码策略
在本文中,我们将讨论使用决策树模型绑定数字特征的最佳策略之一。
宁滨的特色是什么?
特征宁滨指的是将数字或连续特征转换或存储为分类变量的技术。
Pandas 提出了一个**pd.qcut(x, q)** 函数,该函数将连续特征分成**q**个桶,具有相等的百分位数差异。
**df['Sepallength_quartle'] = pd.qcut(df['SepalLengthCm'], 4)**

(图片由作者提供),分桶结果示例
或者也可以使用**pd.cut(x, bins, labels)** 函数,并传递自定义的箱柜和标签进行装箱。
在计算仓位时,这些类型的分桶策略不涉及目标变量。因此,箱夜与目标变量没有任何关联。特征宁滨的目的是在数据中引入非线性,这可以进一步改善模型的性能。
想法:
通过执行随机、计数或基于四分位数的存储桶,无法实现功能宁滨的主要目的。想法是使用决策树模型找到最佳的桶或箱集合,该模型将涉及与目标变量的相关性。
数据来源:我们将对从 Kaggle 下载的虹膜数据集中的“萼片长度”特征进行宁滨处理。
按照以下步骤确定最佳“萼片长度”箱:
- 用特征“萼片长度”作为训练数据,用“物种”作为目标变量,训练一个最适合的决策树模型。
**X = df[['SepalLengthCm']]
y = df['Species']****params = {'max_depth':[2,3,4], 'min_samples_split':[2,3,5,10]}
clf_dt = DecisionTreeClassifier()
clf = GridSearchCV(clf_dt, param_grid=params, scoring='accuracy')
clf.fit(X, y)****clf_dt = DecisionTreeClassifier(clf.best_params_)**
- 使用 Graphviz 绘制决策树。
**tree.plot_tree(clf_dt, filled=True, feature_names = list(X.columns), class_names=['Iris-setosa', 'Iris-versicolor', 'Iris-virginica'])
plt.show()**
- 通过观察图节点来决定特征桶。

(图片由作者提供),使用 Graphviz 的决策树表示
从决策树模型的上述图形表示中,我们可以得出结论,鸢尾属的大多数点的萼片长度≤ 5.45,而鸢尾属的大多数数据点的萼片长度> 6.15。大多数鸢尾-杂色花数据点的萼片长度在 4.86-6.15 之间。
利用上述观察,我们可以准备桶来收集“萼片长度”连续特征。
bins = [0, 5.45, 6.15, 10]
labels = ['Less Than 5.45','4.86 - 6.15', 'Above 6.16']
df['SepalLengthBucket'] = pd.cut(df['SepalLengthCm'], bins=bins, labels=labels)

(图片由作者提供),分桶连续特征(“SepalLengthBucket”)和目标变量(“物种”)之间的相关性
分桶萼片长度特征与目标变量有很好的相关性,并且将比随机、计数或基于四分位数的分桶策略更好地训练稳健得多的模型。
结论:
连续变量的宁滨会在数据中引入非线性,并有助于提高模型的性能。在执行要素宁滨时,基于决策树规则的分桶策略是决定要挑选的最佳要素分桶集的一种简便方法。
必须记住,不要训练深度较大的决策树模型,因为解释特征桶会变得很困难。
参考资料:
[1] Scikit-learn 文档:【https://scikit-learn.org/stable/modules/tree.html
感谢您的阅读
有志 ML 从业者的基本行话
从层类型到功能,一个快速列表可以帮助你在开始机器学习时保持方向。
每个行业都有自己的行话,机器学习也不例外。这里有一个关键术语的简要清单,所有有抱负的 ML 开发人员在构建模型时都应该熟悉这些术语。

图层和模型类型
在 ML 中,深度学习(DL)是当前的艺术状态。今天所有的 DL 模型都是基于神经网络。神经网络包括神经元(又名张量 ) 的层,其存储某物的状态(例如,像素值),并且连接到存储更高级实体(例如,像素组)的状态的后续层中的神经元。在这些层之间是 权重 ,这些权重在训练期间被调整,并最终用于进行预测。
下面是要熟悉的神经网络层的常见类型:
- 输入层:代表输入数据(如图像数据)的神经网络的第一层。
- 输出层:神经网络的最后一层,包含给定输入的模型结果(如图像分类)。
- 隐藏层:位于神经网络的输入和输出层之间的层,对数据进行转换。
- 密集(又名全连接层):其所有输出都连接到下一层所有输入的层。
- 卷积:构建卷积神经网络 (CNN)的基础。
- 递归:提供循环功能,其中层的输入包括要分析的数据和对该层执行的先前计算的输出。递归层构成了递归神经网络 (RNNs)的基础。
- 残差块:残差神经网络(ResNets)将两层或更多层组合在一起作为残差块,以避免消失梯度问题。
ML 从业者使用不同类型的层以不同的方式构建神经网络。常见的神经网络示例包括 VGG16、ResNet50、InceptionV3 和 MobileNetV2。请注意,这些模型中有许多是由 Keras 应用提供的,并且可以通过 PerceptiLabs 中的组件获得,允许您使用迁移学习快速构建模型。
基础知识
模型的层和互连构成了模型的 内部 或 可学习的参数,这些参数在训练过程中调整。关键参数包括:
- 权重:一个给定的神经元影响它在下一层所连接的张量的程度。
- 偏差:向上或向下移动结果,使其更容易或更难激活神经元。
在训练期间,底层 ML 引擎通过调整其权重和偏差来优化模型。为了做到这一点,采用了以下算法:
- 优化器:更新模型,帮助它学习复杂的数据模式。常见的算法有: ADAM ,随机梯度下降(SGD) , Adagrad , RMSprop 。
- 激活函数:给神经元输出增加非线性的数学函数。例子包括:乙状结肠、 ReLU 和 Tanh 。
- 损失函数(又名误差函数):衡量优化器对单个训练样本的调整是否会产生更好的性能(即,指示给定一组权重和偏差值时模型的预测效果)。常见的损失函数有交叉熵、二次和骰子。
- 成本函数:衡量优化器对所有训练样本的调整是否会产生更好的模型性能。
训练通常包括将数据分成三组:
- 训练数据:用于通过提供地面实况数据(例如,具有已知正确的相应标签的图像)来优化模型的权重和偏差。
- 验证数据:用于测试模型在训练时的准确性。
- 测试数据:用于测试最终训练好的模型在以前没有见过的数据上的性能。
重要的是要避免在训练期间 欠拟合 (即训练一个太简单而不能很好地从数据中学习的模型),以及过拟合(即训练一个太复杂而不能根据新的、看不见的数据准确预测的模型)。ML 从业者可以调整许多用户可控的设置,称为 超参数 ,以开发出尽可能好的模型:
- 批量大小:优化器更新模型参数之前要训练的训练样本数。
- 时期:对整个训练数据集(包括所有批次)进行的次数。
- 损失:要使用的损失函数算法。
- 学习率:模型参数更新后,根据计算的误差,改变模型参数的多少。
- 优化器:要使用的优化器算法。
- Shuffle :随机改变训练数据顺序,使模型更健壮。
- Dropout :通过忽略随机选择的层输出来减少过拟合的正则化技巧。然后,对层的每次更新都在该层的不同视图上有效地执行。
这个清单当然不是详尽无遗的——我们可以很容易地写出一整套关于基础知识的书!然而,这些关键要素是一个良好的开端。
对于那些想在 Perceptilabs 中尝试这些基础知识的人,请务必查看 PerceptiLabs 文档和我们的入门指南。
计算机视觉研究人员的基本 Linux 命令行技巧
在本帖中,我们将介绍一些有用的命令行技巧,它们可以帮助计算机视觉工程师加快日常研发工作。

由 ThisisEngineering RAEng 在 Unsplash 拍摄的照片
介绍
我永远不会忘记我第一天使用测试服务器的情景,我不小心在根目录上运行了rm -r *,搞砸了,让 DevOps 的人哑口无言!!

DevOps 的人想知道发生了什么?(gif 来自Giphy.com)
经过这次实验,我学到了两件重要的事情:
- 第一,打字的时候要抬头!
- 第二,我需要更好地使用命令行。
L inux 在数据科学社区,尤其是深度学习工程师中非常受欢迎。因为每当涉及到训练像 YOLO、RCNN 或 BERT 这样的深度学习模型时,他们都必须通过 MLOps 来确保安装的软件包与操作系统和硬件非常好地集成。但是在它所有的酷功能中,我认为它的命令行是另一回事。它的简单性、工具多样性、易于与最先进的技术、、和敏捷的本质使它在数据科学的研发方面非常独特。
耶鲁安·扬森斯在他的书《数据 命令行中的科学》中对命令行的优势进行了很好的分类:
https://www.datascienceatthecommandline.com/
命令行是敏捷的:这个特性使它非常适合数据科学的探索性质。因为你想要及时的结果。
命令行增加:与其他技术很好地集成。(例如 Jupter Notebook、Colab 等)
命令行是可扩展的:因为您没有使用 GUI,而是实际键入命令,并且每个命令都有许多参数,所以它可以很容易地满足您的需要。
命令行是可扩展的:命令行本身是语言不可知的。所以你可以开发和扩展它的功能而不用担心语言。
命令行无处不在:任何类似 Unix 的操作系统都有命令行。大多数超级计算机、嵌入式系统、服务器、笔记本电脑、云基础设施都使用 Linux。
在这篇文章中,我想介绍一些我在项目中经常使用的 Linux 命令的技巧,主要是因为我发现必须为每个过程编写一个 python 脚本。我将命令分为两大类:
- 第一类包括使用远程服务器和文件时有用的命令
- 第二个包括帮助图像/视频操作的命令。
使用远程服务器和文件
1.检查图像的尺寸
有时在远程服务器上,您正在处理数据,并且没有任何 GUI 来查看图像尺寸。您可以使用以下命令检查图像尺寸:
`**linux@user-pc:~/some/path$ identify image_name.png
image_name.png PNG 1920x1080 8bit sRGB 805918B 0.000u 0:00.000**`
如果你想把它用在一个目录中的几个图片上(也许你想把它们组合成一个剪辑,但是其中一些和其他的尺寸不一样!),用*.png代替image_name.png
我曾经在 python 中使用过这个脚本:
2.清点目录上的图像
在某个目录下创建数据集后,我经常检查图像和标签的数量是否相等。(由于我使用 jupyter notebook,有时会有一个名为.ipynotebook的隐藏目录,当您使用ls命令时它不会出现,这在数据生成器开始从该目录获取数据时会产生问题)。
- 对所有图像进行计数:
**ls path/to/images | wc -l** - 仅检查
png格式文件:
**find -name *.png -type | wc -l** - 查看目录的存储大小:
**du -lh --max-depth=1** - 您还可以使用
**tree -du -h Path/to/images**来检查图像及其消耗的磁盘存储。
命令**tree**的输出更加直观,如下所示:

tree 命令的输出示例(图片由作者提供)
3.将文件从本地电脑复制到远程服务器或从远程服务器复制到本地电脑
我们经常需要将数据上传到远程服务器(或者从远程服务器下载数据)。为此,你应该这样使用**scp**命令:
- 首先我建议你用这个命令压缩文件夹:
**zip -r output.zip path/to/images path/to/labels** - 要下载,你应该在本地电脑上使用这个命令:
**scp remote_user@remote_host:remote_path local_path**
例如:
**scp user_1@111.222.333.444:/home/user_1/path /home/local_user/path** - 要上传,切换来源和目的地:
4.在远程服务器上运行 jupyter 笔记本
jupyter notebook 帮助研究人员编写脚本和可视化。
为了在本地 PC 上使用它,同时在远程主机上运行,我们在本地机器和远程主机上使用以下命令将 jupyter 笔记本端口转发到本地:
- 在远程服务器上运行 jupyter 笔记本:
**jupyter notebook --no-browser --port=XXXX** - 端口转发本地端口到远程端口:
**ssh -N -f -L localhost:YYYY:localhost:XXXX remote_user@remote_host**

作者创造的形象
5.与 TMUX 一起进行 ML 模型培训/评估
我曾经在 ML 模型训练会议期间遭受了很多网络中断的痛苦,这有时使我回到起点,从头开始再次训练模型!因此,我发现**tmux**实用程序是一个非常吸引人的解决方案,当你想将进程从它们的控制终端上分离时,它允许远程会话保持活动而不被看到。
使用 tmux 真的很容易。
- 要启动 tmux 会话,只需键入
**tmux**。 - 要重命名会话,请键入
**rename-session -t some_random_name**(仅当您已激活会话时)。 - 然后,您可以运行您的流程,并通过按下
**CTRL+B**和**D**将其分离。 - 要附加会话,您可以使用
**tmux attach -t some_random_name**。 - 要终止会话,执行
**tmux kill-session -t yolo**
你可以在这段精彩的 youtube 视频中了解更多细节:
HackerSploit |完整 tmux 教程| 2020 年 4 月 9 日
6.观看您的培训课程和 GPU 资源
如果你在一个团队工作,你可能经历过与同事争夺 GPU 资源的竞争!!要了解 GPU 的使用情况(意识到它何时不被其他人使用),请使用以下命令:
**watch -n nvidia-smi**

作者图片
**nvtop**(更好的可视化):

运行训练和 GPU 内存分配监控——图片由作者提供
**gpustat**(比较适合管理员同时看多个 GPU)

作者图片
有时,当您开始训练 ML 模型时,您会将结果记录在某个文本文件中。例如,Detectron2框架或 YOLOV5 日志度量(准确性、损失等)。)在一些 txt 文件上。
因此,如果我无法访问tensorboard,我将使用此命令检查最后 5 行的结果,这些结果将每 100 秒更新一次:
**watch -n 100 tail -n 5**
我根据损失和精度值保存检查点,并用相应的损失和精度命名权重。因此,我可以在命令行中对它们进行排序,并使用以下命令检查最后一个检查点的准确性:
**ls checkpoints/* | sort | tail -n 1**
6.创建 gif 图来显示时间序列图像
我使用 GradCam 算法,使用tf-explain模块在测试图像上可视化激活层特征提取热图。我经常从图中生成 gif,以了解训练过程如何影响模型的准确性。要将目录中的图像转换为绘图,可以使用此命令:
**convert -delay 10 -loop 0 *.png animation.gif**
并导致:

GradCam 输出的项目图像颜色分类。图片由作者提供。
Kaggle 上的完整项目:
https://www.kaggle.com/masouduut94/tensorflow-a-comprehensive-image-classification
如果你想把视频转换成 gif,你可能会注意到输出的 gif 文件会变得非常大。所以减小 gif 文件大小的最好方法是从视频中取样。为此,使用采样 FPS 的-r:
ffmpeg -i video.mp4 -r 10 output.gif
视频/图像处理
是计算机视觉工程师的必备技能,因为她/他必须处理视频/图像数据。**ffmpeg**有很多窍门,我在这里只分享其中的几个。如果你想了解更多关于ffmpeg的信息,请看看这些书:
https://www.amazon.com/FFmpeg-Basics-Multimedia-handling-encoder/dp/1479327832 https://ffmpegfromzerotohero.com/
1.检查视频持续时间
使用此命令:
**ffmpeg -i file.mp4 2>&1 | grep “Duration”**
输出将是这样的:
*Duration: 00:05:03.05, start: 0.00000, bitrate:201 kb/s*
2。转换视频格式:
为此:
**ffmpeg -i video.mp4 video.avi**
如果您只需要视频中的音频:
**ffmpeg -i input.mp4 -vn output.mp3**
3.从视频生成数据集
有时,我们可能需要从视频中生成一个数据集,其中一些动作很少发生,如检测罕见的动作或罕见的对象,这可以称为“异常检测”。为此,我们需要浏览长达数小时的录像。我使用这个命令来剪切视频的某些部分:
**ffmpeg -ss 00:10:00 -i input_video.mp4 -to 00:02:00 -c copy output.mp4**
在该命令中:
**-ss**:开始时间**-i**:输入视频**-to**:时间间隔如 2 分钟。**-c**:输出编解码器
如果您想要没有音频的视频:
**ffmpeg -i input_video.mp4 -an -c:v copy output.mp4**
**-an**用于无音频输出。
4.为 ConvLSTMs 或 3d CNNs 生成一系列帧
ConvLSTM 和 3d-CNN 网络设计用于提取视频序列的时空特征。所以为了和这些网络一起工作,我们必须为它们的输入张量产生一系列的框架。使用此命令从视频中生成 20 秒的图像:
**ffmpeg -ss 00:32:15 -t 20 -i videos.ts ~/frames/frame%06d.png**
您可以添加**scale**来重新调整图像尺寸。我主要是在**1028x540**得到视频,它的宽度必须减半。执行此操作的命令如下:
**ffmpeg -ss 00:10:00 -t 20 -i video.ts -vf scale=iw/2:ih output_path/frame%06d.png**
或者你可以简单地把维度:
**ffmpeg -ss 00:10:00 -t 20 -i video.ts -vf scale=960x540 output_path/frame%06d.png**
5.裁剪视频的边界框
这就是你如何简单地裁剪一盒视频剪辑:
**ffmpeg -i input.mp4 -filter:v "crop=w:h:x:y" output.mp4**
6.将视频相互堆叠在一起
在 TVConal 初创公司,我从事体育分析项目,有时我们想要检查不同的视频馈送是否彼此同步。我刚刚发现这个惊人的 StackOverflow 答案与这个问题如此相关,我在这里分享的是从提到的帖子中得到的见解。
由于我是排球的超级粉丝(我最喜欢的球员是二传手之王“赛义德·马鲁夫”),我选择在排球的视频片段上测试命令。视频名称为a.mp4 — b.mp4 — c.mp4 — d.mp4,荣誉归于 Pexels 的 Pavel Danilyuk 。


a.mp4-b.mp4 视频剪辑(视频由帕维尔·丹尼柳克从派克斯拍摄)


clips 和 clips 的视频剪辑(来自 Pexels 的 Pavel Danilyuk 的视频)
6.1 —水平堆叠视频:
要沿水平轴堆叠提要,我们使用以下命令:
**ffmpeg -i a.mp4 -i b.mp4 -filter_complex hstack output.mp4**

FFmpeg 水平堆叠的输出结果视频
6.2 —垂直堆叠视频:
要沿垂直轴堆叠提要,我们使用以下命令:
**ffmpeg -i a.mp4 -i b.mp4 -filter_complex vstack=inputs=2 end_vstack.mp4**

FFmpeg 垂直堆叠的输出结果
6.3 —使用 xstack 的 2x2 网格堆叠
现在让我们试试更复杂的东西:
**ffmpeg -i a.mp4 -i b.mp4 -i c.mp4 -i d.mp4 -filter_complex "[0:v][1:v][2:v][3:v]xstack=inputs=4:layout=0_0|w0_0|0_h0|w0_h0[v]" -map "[v]" 2x2.mp4**

FFmpeg xstacking 的输出结果
结论
我相信命令行和 python 将为数据科学社区提供一套强大的工具。我们都知道,在 R&D 和快节奏的发展中,时间管理是非常关键的。所以使用如此强大的工具可以让我们在白天简单的任务上浪费更少的时间。我不否认还有很多更有用的命令行技巧我没有包括在本文中,比如在处理 DevOps 时很方便的**Docker**,但是我想在另一篇文章中写一下 Docker 。
我也感谢那些知道更多对计算机视觉社区有用的命令行并能与我分享的人。
我的在线个人资料:
参考
- https://ljvmiranda 921 . github . io/notebook/2018/01/31/running-a-jupyter-notebook/
- https://en.wikipedia.org/wiki/Tmux
- https://ostechnix.com/20-ffmpeg-commands-beginners/
- https://UNIX . stack exchange . com/questions/233832/merge-two-video-clips-into-one-placing-them-next-to-other
- https://askubuntu . com/questions/648603/how-to-create-a-animated-gif-from-MP4-video-via-command-line
- https://stack overflow . com/questions/11552565/vertically-or-horizontal-stack-mosaic-some-videos-using-ffmpeg/33764934 # 33764934
数据科学基础数学:基础和基础的变化
数据科学的基本数学
理解对特征分解和奇异值分解有用的基的线性代数概念

(图片由作者提供)
在本文中,您将了解基的概念,这是理解矩阵分解方法(如特征分解或奇异值分解(SVD))的一种有趣方式。
定义
基是一个用来描述向量空间(向量组)的坐标系。它是一个参考,用于将数字与几何向量相关联。
作为基础,一组向量必须:
- 线性无关。
- 跨越空间。
空间中的每个向量都是基向量的唯一组合。空间的维数被定义为基集的大小。例如, ℝ 中有两个基矢(对应笛卡尔平面中的 x 和 y 轴),或者 ℝ 中有三个基矢。
如数据科学基础数学第 7.4 节所示,如果一个集合中向量的个数大于空间的维数,它们就不可能线性无关。如果一个集合包含的向量少于维数,这些向量就不能跨越整个空间。
矢量可以表示为从原点到空间中一点的箭头。这个点的坐标可以存储在一个列表中。笛卡尔平面中矢量的几何表示意味着我们采用一个参考:由两个轴 x 和 y 给出的方向。
基本矢量是对应于该参考的矢量。在笛卡尔平面中,基向量是正交的单位向量(长度为 1),一般表示为 i 和 j 。

图 1:笛卡尔平面中的基本向量。(图片由作者提供)
例如,在图 1 中,基矢 i 和 j 分别指向轴 x 和 y 的方向。这些向量给出了标准基础。如果你将这些基向量放入一个矩阵,你会得到以下单位矩阵(关于单位矩阵的更多细节,参见数据科学基础数学中的 6.4.3):

于是, 的列我 ₂ 跨度 ℝ 。同理,各列的 I ₃ 跨度 ℝ 等等。
正交基
基向量可以是正交的,因为正交向量是独立的。然而,反过来就不一定了:非正交向量可以是线性无关的,从而形成一个基(但不是标准基)。
你的向量空间的基非常重要,因为对应于向量的坐标值依赖于这个基。顺便说一下,你可以选择不同的基向量,比如图 2 中的那些。

图二。二维空间中的另一个基础。(图片由作者提供)
请记住,向量坐标取决于基本向量的隐式选择。
基本向量的线性组合
你可以把向量空间中的任何向量看作是基向量的线性组合。
例如,取下面的二维向量 v :


图 3。矢量𝒗.的分量(图片由作者提供)
矢量 v 的分量是在 x 轴和 y 轴上的投影( v_x 和 v_y ,如图 3 所示)。向量 v 对应其分量之和:v= v _ x+v _ y,你可以通过缩放基向量得到这些分量:v _ x = 2I和v _ y =-0.5j。因此,图 3 所示的矢量 v 可以认为是两个基矢量 i 和 j 的线性组合:

其他基地
单位矩阵的列不是线性无关列向量的唯一情况。有可能在 ℝⁿ 中找到其他组 n 向量线性无关。
例如,让我们考虑一下 ℝ 中的以下向量:

和


图 4:二维空间中的另一种基。(图片由作者提供)
从上面的定义来看,向量 v 和 w 是一个基础,因为它们是线性独立的(你无法从另一个的组合中获得其中的一个),并且它们跨越空间(所有的空间都可以从这些向量的线性组合中到达)。
重要的是要记住,当你使用向量的分量时(例如向量的 v_x 和 v_y 、 x 和 y 分量),这些值是相对于你选择的基的。如果使用另一种基础,这些值将会不同。
您将在数据科学基础数学的第 9 章和第 10 章中看到,改变基底的能力是线性代数的基础,也是理解特征分解或奇异值分解的关键。
向量是相对于基来定义的
您已经看到,要将几何向量(空间中的箭头)与坐标向量(数字数组)相关联,您需要一个引用。这个参考是你的向量空间的基础。由于这个原因,向量应该总是相对于基来定义的。
让我们取下面的向量:

x 和 y 分量的值分别为 2 和-0.5。未指定时,使用标准基准。
你可以写 Iv 来指定这些数字对应于相对于标准基的坐标。这种情况下, I 称为基矩阵的变化。

您可以使用不同于 I 的另一个矩阵来定义相对于另一个基的向量。
基本向量的线性组合
向量空间(可能向量的集合)的特征是关于一个基。把一个几何向量表示成一组数字意味着你要选择一个基。用不同的基,同一个向量 v 关联不同的数。
你看到了基是一组跨越空间的线性独立的向量。更准确地说,如果空间中的每个向量都可以描述为基的分量的有限线性组合,并且向量集是线性独立的,那么向量集就是一个基。
考虑下面的二维向量:

在 ℝ 笛卡尔平面中,可以把 v 看作标准基向量 i 和 j 的线性组合,如图 5 所示。

图 5:向量 v 可以描述为基向量 i 和 j 的线性组合。(图片由作者提供)
但是如果使用另一个坐标系, v 则与新的数字相关联。图 6 显示了向量 v 在新坐标系下的表示( i ' 和 j ' )。

图 6:矢量 v 相对于新基准的坐标。(图片由作者提供)
在新的基础上, v 是一组新的数字:

基矩阵的变化
您可以使用基矩阵变化从一个基转到另一个基。要找到新基向量对应的矩阵,可以将这些新基向量(I’和j’)表示为旧基( i 和 j )中的坐标。
让我们再看一下前面的例子。你有:

和

这如图 7 所示。

图 7:新基础向量相对于旧基础的坐标。(图片由作者提供)
由于是基向量, i ' 、 j ' 可以表示为 i 和 j 的线性组合。:

让我们将这些方程写成矩阵形式(关于方程组的矩阵形式的更多细节在数据科学基础数学的第 8 章中):

要使基本向量成为列,需要转置矩阵。您将获得:

这个矩阵叫做基变换矩阵。姑且称之为 C :

正如你所注意到的,基变换矩阵的每一列都是新基的一个基向量。接下来您将看到,您可以使用基变换矩阵 C 将向量从输出基转换为输入基。
基变换 vs 线性变换
基的变化和线性变换的区别是概念上的。有时,将矩阵的效果视为基的变化是有用的;有时,当你把它看作线性变换时,你会得到更多的见解。
要么移动向量,要么移动其参考。这就是为什么旋转坐标系与旋转矢量本身相比具有相反的效果。
对于特征分解和奇异值分解,这两种观点通常被放在一起,这在一开始可能会引起混淆。在本书的结尾,牢记这一区别将会很有用。
两者之间的主要技术差异是基的变化必须是可逆的,而线性变换不需要这一点。
求基矩阵的变化
基变换矩阵将输入基映射到输出基。让我们称输入基 B ₁ 用基向量 i 和 j ,输出基 B ₂ 用基向量**’和j你有:****

和

根据基变化的等式,你有:

如果你想求给定 B ₁ 和 B ₂ 的基矩阵的变化,你需要计算 B ₁ 的逆来分离 C :

换句话说,你可以通过将输入基矩阵的逆矩阵(b₁^{-1},包含输入基向量为列)乘以输出基矩阵(b**,包含输出基向量为列)来计算基矩阵的变化。**
Converting vectors from the output to the input basis
注意,这个基矩阵的变化允许你将向量从 B ₂ 转换到 B ₁ 而不是相反。直觉上,这是因为移动对象与移动参考是相反的。由此可见,要从 B ₁ 到 B ₂ ,就必须使用基矩阵c**^{-1}的逆矩阵变化。**
注意,如果输入基是标准基(b₁=I),那么基的变化矩阵就是简单的输出基矩阵:

基矩阵的可逆变化
由于基向量是线性无关的, C 的列也是线性无关的,因此,如数据科学基础数学、 C 的 7.4 节所述,是可逆的。
示例:更改向量的基
让我们改变矢量 v 的基础,再次使用图 6 所示的几何矢量。
注释
您将把 v 的基础从标准基础更改为新基础。让我们把标准基记为 B ₁ ,新基记为 B ₂ 。记住,基是一个矩阵,包含列形式的基向量。你有:

和

我们把向量*相对于基 B ₁ 表示为[v*****b₁:

目标是找到 v 相对于基 B ₂ 的坐标,记为[vb₂。**
方括号符号
为了区分用来定义一个矢量的基,你可以把基名(像 B ₁)放在用方括号括起来的矢量名后面的下标中。然而,由于介质下标的限制,我们将在文本中写例如,[v]b₁来表示相对于基 B ₁.的矢量 v 相对于₁.,也称为的*表示*****
使用线性组合
让我们将向量 v 表示为输入和输出基向量的线性组合:

标量 c₁ 和 c₂ 对输入基向量的线性组合进行加权,标量 d₁ 和 d₂ 对输出基向量的线性组合进行加权。你可以合并这两个方程:

现在,让我们用矩阵形式写出这个方程:

包含标量 c₁ 和 c₂ 的向量对应于[vb₁包含标量 d₁ 和 d₂ 的向量对应于[vb₂你有:**

很好,这是一个带有你要找的项的方程:[vb₂。你可以把每边乘以b**₂^{-1}来隔离它:**

您还拥有:

*****b₂{-1}****b****₁*是***b****₁b*₂,这是基阵 C 的变化这说明 C ^{-1} 允许您将向量从输入基 B ₁ 转换为输出基 B ₂ 和 C 从 B
在这个例子的上下文中,由于b**₁是标准基础,它简化为:**

这意味着,应用矩阵b₂^{-1}到vb_ 1可以让你将其基础改为b₂。**
让我们编码如下:
**array([ 0.86757991, -1.00456621])**
这些值是矢量 v 相对于基b₂的坐标。这意味着如果你去0.86757991I'—1.00456621j'你就到达了标准基中的位置(2,1),如图 6 所示。**
结论
理解基的概念是接近矩阵分解的好方法,如特征分解或奇异值分解(SVD)。在这些方面,你可以认为矩阵分解是寻找一个基,其中与变换相关的矩阵具有特定的属性:因式分解是一个基矩阵的变化,新的变换矩阵,最后是基矩阵变化的逆,以返回到初始基(更多细节在数据科学基础数学的第 09 章和第 10 章)。
这篇文章是我的书数据科学基础数学的样本!**

数据科学基础数学:特征向量及其在 PCA 中的应用
数据科学的基本数学
理解特征向量和特征值以及它们与主成分分析(PCA)的关系

(图片由作者提供)
矩阵分解,也叫矩阵分解是将一个矩阵拆分成多个块的过程。在数据科学的背景下,你可以使用它来选择数据的一部分,目的是在不丢失太多信息的情况下减少维度(例如在主成分分析中,你将在本文后面介绍)。一些运算在分解得到的矩阵上也更容易计算。
在本文中,您将学习矩阵的特征分解。理解它的一种方式是将其视为一种特殊的基础变化(关于基础变化的更多细节,请参见我的上一篇文章】)。您将首先学习特征向量和特征值,然后了解它们如何应用于主成分分析(PCA)。主要思想是将矩阵的特征分解视为基的变化,其中新的基向量是特征向量。
特征向量和特征值
正如您在数据科学基础数学的第 7 章中所看到的,您可以将矩阵视为线性变换。这意味着,如果你取任意一个矢量并将矩阵应用于它,你就获得了一个变换后的矢量 v 。**
以...为例:

和

如果将 A 应用于矢量(矩阵-矢量乘积),就会得到一个新的矢量:

让我们画出初始向量和变换向量:

图 1:将矢量由矩阵 A 转化为矢量 v 。****
请注意,正如您所料,变换后的矢量 v 与初始矢量的运行方向不同。这种方向的改变表征了你可以通过变换的大多数向量。****
然而,以下面的向量为例:
********
图 2:矩阵对特殊向量 x 的变换。****
在图 2 中可以看到,向量 x 与矩阵 A 有着特殊的关系:它被重新缩放(带有负值),但初始向量 x 和变换后的向量 y 都在同一条线上。
向量 x 是的一个特征向量。只按一个值缩放,这个值叫做矩阵的一个特征值*。矩阵 A 的特征向量是由矩阵变换时收缩或伸长的向量。特征值是向量收缩或伸长的比例因子。*****
数学上,向量 x 是 A 的一个特征向量,如果:

其中 λ (读作“λ”)是对应于特征向量 x 的特征值。
特征向量
矩阵的特征向量是非零向量,仅当矩阵应用于它们时才被重缩放。如果比例因子为正,则初始向量和变换向量的方向相同,如果为负,则它们的方向相反。
特征向量的数量
一个 n -by- n 矩阵最多有 n 个线性无关的特征向量。然而,每个特征向量乘以一个非零标量也是一个特征向量。如果您有:

然后:

用 c 表示任何非零值。
这排除了零向量作为特征向量,因为你会有

在这种情况下,每个标量都是一个特征值,因此是未定义的。
动手项目:主成分分析
主成分分析(PCA)是一种算法,可以用来降低数据集的维度。例如,减少计算时间、压缩数据或避免所谓的“维数灾难”是很有用的。它对于可视化也很有用:高维数据很难可视化,减少绘制数据的维数会很有用。
在这个动手项目中,您将使用在《数据科学的基本数学》一书中学到的各种概念来理解 PCA 是如何工作的,如基的变化(第 7.5 和 9.2 节,这里的一些示例)、特征分解(第 9 章)或协方差矩阵(第 2.1.3 节)。
在第一部分中,您将了解投影之间的关系,解释方差和误差最小化,首先是一些理论,然后通过对啤酒数据集(啤酒消费量作为温度的函数)进行 PCA 编码。在第二部分中,您将使用 Sklearn 对音频数据使用 PCA 来根据音频样本的类别可视化音频样本,然后压缩这些音频样本。
在后台
理论语境
PCA 的目标是将数据投影到低维空间,同时尽可能多地保留数据中包含的信息。这个问题可以看作是一个垂直最小二乘问题,也称为正交回归。**
您将看到,当投影线对应于数据方差最大的方向时,正交投影的误差最小。
差异和预测
首先要理解的是,当数据集的要素并非完全不相关时,某些方向比其他方向具有更大的方差。

图 3:向量 u 方向的数据方差(红色)大于向量 v 方向的方差(绿色)。(图片由作者提供)
将数据投影到低维空间意味着您可能会丢失一些信息。在图 3 中,如果您将二维数据投影到一条线上,投影数据的方差会告诉您丢失了多少信息。例如,如果投影数据的方差接近零,这意味着数据点将被投影到非常接近的位置:您会丢失大量信息。
为此,PCA 的目标是改变数据矩阵的基,使得具有最大方差的方向(图 3 中的 u )成为第一个主分量。第二个分量是方差最大的方向,它与第一个分量正交,依此类推。**
当你找到主成分分析的组成部分,你改变你的数据的基础,使组成部分是新的基础向量。此变换后的数据集具有新的要素,这些要素是初始要素的组成部分和线性组合。通过仅选择一些组件来减少维度。

图 4:改变基底,使得最大方差在 x 轴上。(图片由作者提供)
作为一个例子,图 4 显示了改变基础后的数据:最大方差现在与 x 轴相关联。例如,您可以只保留第一维度。
换句话说,用基的变化来表示 PCA,它的目标是找到一个新的基(它是初始基的线性组合),其中数据的方差沿着第一维被最大化。
最小化误差
寻找最大化方差的方向类似于最小化数据与其投影之间的误差。

图 5:方差最大化的方向也是误差最小的方向(用灰色表示)。(图片由作者提供)
您可以在图 5 中看到,左图显示了较低的误差。由于投影是正交的,与投影线方向相关的方差不会影响误差。
寻找最佳方向
更改数据集的基础后,要素之间的协方差应该接近于零(如图 4 所示)。换句话说,您希望变换后的数据集具有对角协方差矩阵:每对主成分之间的协方差等于零。
你可以在数据科学基础数学的第 9 章中看到,你可以使用特征分解来对角化矩阵(使矩阵对角化)。因此,您可以计算数据集协方差矩阵的特征向量。他们会给你新基的方向,其中协方差矩阵是对角的。
总之,主成分是作为数据集协方差矩阵的特征向量来计算的。此外,特征值给出了相应特征向量的解释方差。因此,通过根据特征值按降序对特征向量进行排序,您可以按重要性顺序对主成分进行排序,并最终删除与较小方差相关的成分。
计算 PCA
数据集
让我们来说明 PCA 是如何处理啤酒数据集的,该数据集显示了 2015 年巴西圣保罗的啤酒消费量和温度。
让我们加载数据,并将功耗绘制为温度的函数:

图 6:啤酒消费量与温度的关系。
现在,让我们用两个变量创建数据矩阵 X :温度和消耗。
***(365, 2)***
矩阵 X 有 365 行和两列(两个变量)。
协方差矩阵的特征分解
如您所见,第一步是计算数据集的协方差矩阵:
***array([[18.63964745, 12.20609082], [12.20609082, 19.35245652]])***
记住你可以这样理解:对角线上的值分别是第一个和第二个变量的方差。两个变量之间的协方差在 12.2 左右。
现在,您将计算这个协方差矩阵的特征向量和特征值:
***(array([ 6.78475896, 31.20734501]), array([[-0.71735154, -0.69671139], [ 0.69671139, -0.71735154]]))***
可以将特征向量存储为两个向量 u 和 v 。
让我们用数据绘制特征向量(注意,您应该使用居中的数据,因为它是用于计算协方差矩阵的数据)。
你可以通过特征向量对应的特征值来缩放特征向量,也就是解释的方差。出于可视化的目的,让我们使用三个标准差的向量长度(等于所解释方差的平方根的三倍):

图 7:特征向量 u (灰色)和 v (红色)根据解释的方差缩放。
从图 7 中可以看出,协方差矩阵的特征向量给出了数据的重要方向。红色的向量 v 与最大特征值相关联,因此对应于最大方差的方向。灰色的向量与 v 正交,是第二主成分。**
然后,你只需要用特征向量作为新的基向量来改变数据的基。但是首先,你可以按照特征值降序排列特征向量:
***array([[-0.69671139, -0.71735154], [-0.71735154, 0.69671139]])***
现在,您的特征向量已经排序,让我们改变数据的基础:
您可以绘制变换后的数据,以检查主成分现在是否不相关:

图 8:新基础中的数据集。
图 8 显示了新基础上的数据样本。您可以看到,第一个尺寸(x-轴)对应于方差最大的方向。**
在这个新的基础上,您可以只保留数据的第一部分,而不会丢失太多信息。
协方差矩阵还是奇异值分解?
使用协方差矩阵计算 PCA 的一个注意事项是,当有许多特征时,可能很难计算(就像音频数据一样,就像本实践的第二部分)。为此,通常最好使用奇异值分解(SVD)来计算 PCA。
这篇文章是我的书数据科学基础数学的样本!

***在这里获取书籍:【https://bit.ly/2WVf4CR! ***
数据科学基础数学:线性方程组介绍
数据科学的基本数学
理解和想象线性方程组的行列图

(图片由作者提供)
在这篇文章中,你将能够运用你所学的关于向量(见这里的)、矩阵(见这里的)和线性变换(这里的)的知识。这将允许你把数据转换成线性方程组。您还可以查看数据科学基础数学,了解如何使用方程组和线性代数来解决线性回归问题。
线性方程是变量之间关系的形式化。以两个变量 x 和 y 之间的线性关系为例,由以下等式定义:

您可以在笛卡尔平面中表示这种关系:

图 1:方程 y=2x+1 的曲线图。
记住直线上的每个点都对应这个方程的一个解:如果你用这个方程中直线上的一个点的坐标代替 x 和 y ,等式就满足了。这意味着有无限多的解(直线上的每一点)。
也可以考虑使用相同变量的多个线性方程:这是一个方程组。
线性方程组
方程组是描述变量之间关系的一组方程。例如,让我们考虑下面的例子:

你有两个线性方程,它们都描述了变量 x 和 y 之间的关系。这是一个有两个方程和两个变量的系统(在本文中也称为未知数)。
您可以将线性方程组(系统的每一行)视为多个方程,每个方程对应一条线。这叫排图。
您也可以将系统视为不同的列,对应于缩放变量的系数。这叫做列图。让我们来看看这两张图片的更多细节。
行图片
对于行图,系统的每一行对应一个方程。在前面的例子中,有两个方程描述了两个变量 x 和 y 之间的关系。
让我们用图形表示这两个方程:

图 2:我们系统中两个方程的示意图。
拥有一个以上的等式意味着 x 和 y 的值必须满足更多的等式。请记住,第一个等式中的 x 和 y 与第二个等式中的 x 和 y 相同。
蓝线上的所有点满足第一个方程,绿线上的所有点满足第二个方程。这意味着只有两条线上的点满足这两个方程。当 x 和 y 取直线交点坐标对应的值时,方程组求解。
在本例中,该点的 x 坐标为 0.8,而 y 坐标为 2.6。如果您在方程组中替换这些值,您将得到:

这是一种解方程组的几何方法。线性系统求解为 x=0.8 和 y=2.6 。
柱形图
将系统视为列被称为列图:你认为你的系统是缩放向量的未知值( x 和 y )。
为了更好地理解这一点,让我们重新排列方程,让变量在一边,常数在另一边。首先,您有:

第二点:

现在,您可以将系统写成:

您现在可以查看图 3,了解如何将这两个方程转换成一个单独的向量方程。

图 3:将方程组视为由变量 x 和 y 缩放的列向量。(图片由作者提供)
在图 3 的右边,你有矢量方程。左侧有两个列向量,右侧有一个列向量。正如您在数据科学基础数学中看到的,这对应于以下向量的线性组合:

和

使用柱形图,您可以用一个向量方程替换多个方程。在这个透视图中,您希望找到左侧向量的线性组合,从而得到右侧向量。
柱形图中的解决方法是一样的。行和列图片只是考虑方程组的两种不同方式:

它是有效的:如果你使用几何学上找到的解,你会得到右边的向量。
让我们把方程组看作向量的线性组合。让我们再看一次前面的例子:

图 4 显示了等式左侧的两个向量(图中蓝色和红色为您想要组合的向量)和右侧的向量(图中绿色为您想要从线性组合中获得的向量)的图形表示。

图 4:由 x 和 y 缩放的向量的线性组合给出了右边的向量。(图片由作者提供)
您可以在图 4 中看到,通过组合左侧向量,可以得到右侧向量。如果用值 2.6 和 0.8 缩放向量,线性组合会得到等式右侧的向量。
解决方案的数量
在一些线性系统中,没有唯一的解。实际上,线性方程组可以有:
- 无解。
- 一个解决方案。
- 无限多的解决方案。
让我们考虑这三种可能性(用行图和列图)来看看一个线性系统怎么可能有不止一个解和少于无穷多个解。
让我们采用下面的线性方程组,仍然有两个方程和两个变量:

我们将从表示这些方程开始:

图 5:平行线。
如图 5 所示,没有一个点同时位于蓝线和绿线上。这意味着这个方程组无解。
也可以通过柱形图图形化的理解为什么无解。让我们把方程组写成如下:

把它写成列向量的线性组合,你有:


图 6:无解线性系统柱形图。(图片由作者提供)
图 6 显示了系统的列向量。你可以看到,通过结合蓝色和红色向量,不可能到达绿色向量的端点。原因是这些向量是线性相关的(更多细节见数据科学基础数学)。要到达的向量超出了组合向量的范围。
您可能会遇到另一种情况,即系统有无限多种解决方案。让我们考虑以下系统:


图 7:方程线重叠。
因为方程是相同的,无限多的点在两条线上,因此,这个线性方程组有无限多的解。这例如类似于单个方程和两个变量的情况。
从柱形图的角度来看,您有:

用向量符号表示:


图 8:有无穷多个解的线性系统的柱形图。(图片由作者提供)
图 8 显示了图形表示的相应向量。您可以看到,通过蓝色和红色向量的组合,有无数种方法可以到达绿色向量的端点。
因为两个向量的方向相同,所以有无限多的线性组合可以让你到达右边的向量。
总结
总而言之,您可以有三种可能的情况,如图 9 中的两个方程和两个变量所示。

图 9:总结了两个方程和两个变量的三种情况。(图片由作者提供)
不可能有两条直线交叉不止一次而少于无限次。
这个原则适用于更多的维度。例如, ℝ 中的三个平面,至少两个可以平行(无解),三个可以相交(一个解),或者三个可以重叠(无限多个解)。
用矩阵表示线性方程
现在,您可以使用列图编写向量方程,您可以进一步使用矩阵来存储列向量。
让我们再来看看下面的线性系统:

你可以在数据科学基础数学中看到,你可以将线性组合写成矩阵向量乘积。该矩阵对应于从左侧连接的两个列向量:

并且向量对应于加权矩阵的列向量的系数(这里, x 和 y ):

你的线性系统变成下面的矩阵方程:

这导致了下面广泛用于编写线性系统的符号:
Ax=b
用 A 矩阵包含列向量、 x 系数向量和 b 结果向量,那我们就称之为目标向量。它允许你从单独考虑方程的微积分学到线性代数,线性系统的每一部分都用向量和矩阵表示。这种抽象是非常强大的,并带来了向量空间理论来解决线性方程组。
对于柱形图,您希望找到等式左侧的列向量的线性组合的系数。只有当目标向量在它们的跨度内时,解才存在。
这篇文章是我的书数据科学基础数学的样本!

在这里得到这本书:https://www.essentialmathfordatascience.com/
奇异值分解的可视化介绍
数据科学的基本数学
使用矩阵变换的可视化来了解 SVD

(图片由作者提供)
在本文中,您将了解奇异值分解(SVD),这是线性代数、数据科学和机器学习的一个主要主题。例如,它用于计算主成分分析(PCA)。
你需要了解一些线性代数的基础知识(请随意查阅之前的文章和书数据科学的基本数学)。
您只能对方阵应用特征分解,因为它使用基矩阵的单一变化,这意味着初始向量和变换向量相对于同一基。你用 Q 去另一个基做变换,你用q^-1 回到初始基。
作为特征分解,奇异值分解(SVD)的目标是将矩阵分解成更简单的分量:正交矩阵和对角矩阵。
你也看到了,你可以把矩阵看作线性变换。矩阵的分解对应于将变换分解成多个子变换。在 SVD 的情况下,转换被转换成三个更简单的转换。
这里有三个例子:一个是二维的,一个是比较 SVD 和特征分解的变换,一个是三维的。
二维示例
您将看到使用定制函数matrix_2d_effect()的这些转换的动作(您可以在这里找到这个函数)。该函数绘制单位圆(你可以在数据科学基础数学的第 05 章中找到更多关于单位圆的细节)和矩阵变换的基本向量。
为了表示单位圆和变换前的基向量,让我们用单位矩阵来使用这个函数:

图 1:单位圆和基本向量。
现在让我们用函数来看看下面这个矩阵的效果。

它将绘制单位圆和由矩阵变换的基向量:

图 2:矩阵 A 对单位圆和基矢的影响。
图 2 展示了 A 在二维空间中的效果。让我们将其与 SVD 矩阵相关的子变换进行比较。
可以用 Numpy 计算出T3A的 SVD:
记住矩阵 U 、σ、* 和 V 分别包含左奇异向量、奇异值和右奇异向量。可以考虑将 V ^T 作为基矩阵的第一次变化,σ作为这个新基中的线性变换(这个变换应该是一个简单的缩放既然σ是对角的),以及基矩阵的另一次变化。您可以在数据科学基础数学的第 10 章中看到,SVD 约束基矩阵和v^t 的变化是正交的,这意味着变换将是简单的旋转。***
综上所述,矩阵 A 对应的变换分解为一个旋转(或一个反射,或一个回折),一个缩放,另一个旋转(或一个反射,或一个回折)。
让我们依次看看每个矩阵的效果:

图 3:矩阵v^t对单位圆和基矢的影响。**
在图 3 中可以看到,基向量已经被矩阵v**^t 旋转了**

图 4:矩阵的作用v^t和σ。**
然后,图 4 示出了【σ】的效果是单位圆和基向量的缩放。

图 5:矩阵v^t、σ和u的效果。**
最后,由进行第三次旋转。您可以在图 5 中看到,这个转换与矩阵 A 相关的转换是相同的。您已经将变换分解为一个旋转、一个缩放和一个旋转反射(查看基础向量:反射已经完成,因为黄色向量在绿色向量的左侧,最初并不是这样)。
与特征分解的比较
由于矩阵 A 是正方形的,您可以将这种分解与特征分解进行比较,并使用相同类型的可视化。你会对这两种方法的区别有更多的了解。
请记住数据科学基础数学的第 09 章,矩阵 A 的特征分解由下式给出

让我们用 Numpy 来计算矩阵 Q 和λ(读作“大写λ”):
注意,由于矩阵 A 不是对称的,所以特征向量不是正交的(它们的点积不等于零):
**-0.16609095970747995**
让我们看看q**^-1 对基矢和单位圆的影响:**

图 6:矩阵效果q^-1。**
在图 6 中可以看到q**^-1 旋转并缩放单位圆和基矢。非正交矩阵的变换不是简单的旋转。**
接下来就是应用λ。

图 7:矩阵效果q^-1λ。**
λ的效果,如图 7 所示,是通过 y 轴的拉伸和反射(黄色矢量现在在绿色矢量的右边)。

图 8:矩阵效果q^-1、 、 和 Q 。**
图 8 所示的最后一个变换对应于将基变换回初始基。你可以看到它导致了与关联于的变换相同的结果:两个矩阵 A 和qλq^-1 是相似的:它们对应于不同基中的相同变换。****
它强调了特征分解和奇异值分解之间的区别。使用 SVD,有三种不同的变换,但其中两种只是旋转。使用特征分解,只有两个不同的矩阵,但是与 Q 相关的变换不一定是简单的旋转(只有当 A 对称时才是这种情况)。
三维示例
因为 SVD 可以用于非方阵,所以在这种情况下观察变换是如何分解的是很有趣的。
首先,非方阵映射两个具有不同维数的空间。请记住, m 乘 n 矩阵映射一个 n 维空间和一个 m 维空间。
让我们以一个 3 乘 2 的矩阵为例,将一个二维空间映射到一个三维空间。这意味着输入向量是二维的,输出向量是三维的。取矩阵 一个 :
为了可视化的效果,您仍将使用二维单位圆,并计算该圆上点的输出。每个点被认为是一个输入向量,你可以观察对这些向量的影响。功能matrix_3_by_2_effect()可以在这里找到。****

图 9:矩阵 A 的作用:它把单位圆上的向量和基向量从二维空间变换到三维空间。
正如所料,如图 9 所示,二维单位圆被转换成三维椭圆。请随意使用笔记本,以便能够移动情节,并对三维形状有更好的想法。
你可以注意到,输出向量都落在一个二维平面上。这是因为 A 的秩是 2(关于矩阵秩的更多细节在数据科学基础数学的第 7.6 节)。
现在,您已经知道了通过进行变换的输出,让我们计算的 SVD,并查看不同矩阵的效果,就像您对二维示例所做的那样。****
左奇异向量( U )的形状是 m 乘 m 而右奇异向量(v^t)的形状是 n 乘 n 。矩阵中有两个奇异值σ。**
与 A 相关联的变换被分解成在 ℝⁿ 中的第一旋转(与v^t 相关联,在该示例中为 ℝ ),一个从 ℝⁿ 到 ℝᵐ 的缩放(在该示例中,从 ℝ 到 ℝ )
下面开始考察v^t 对单位圆的作用。在这一步你停留在二维空间:

图 10:矩阵的效果v^t:到了这一步,你还是在二维空间。**
您可以在图 10 中看到,基本向量已经旋转。
然后,需要对【σ】进行整形,因为函数np.linalg.svd()给出了包含奇异值的一维数组。你想要一个与形状相同的矩阵:一个从 2D 到 3D 的 3 乘 2 矩阵。这个矩阵包含作为对角线的奇异值,其他值为零。**
让我们创建这个矩阵:
****array([[9.99274669, 0\. ], [0\. , 4.91375758], [0\. , 0\. ]])****
您现在可以添加【σ的变换,在图 11 中以 3D 形式查看结果:**

图 11:矩阵v^t和σ:由于σ是一个三乘二的矩阵,它将二维向量转化为三维向量。**
最后,你需要操作基差的最后一个变化。你留在 3D 是因为矩阵是一个 3 乘 3 的矩阵。**

图 12:三个矩阵v^,σ和 U 的效果:变换是从三维空间到三维空间。**
您可以在图 12 中看到,结果与矩阵 A 相关的转换相同。
摘要

图 13:二维奇异值分解。(图片由作者提供)
图 13 总结了一个矩阵 一个 分解成三个矩阵。与相关的变换由三个子变换完成。符号与图 13 中的相同,说明了 SVD 的几何透视图。**
例如,奇异值分解可用于寻找近似矩阵的变换(参见数据科学基础数学第 10.4 节中的低秩矩阵近似)
这篇文章是我的书数据科学基础数学的样本!**

在这里拿书:https://www.essentialmathfordatascience.com/!
2021 年每个数据科学家都应该知道的基本熊猫
提高你的熊猫技能 10 倍!

照片来自 Pexels
Pandas 是一个图书馆,作为一名数据科学家,我们每天都会在某个时候偶然发现它。快速重温一下,绝对感觉像是在炎热的夏天啜饮一杯莫吉托鸡尾酒,或者在下雪的时候喝一杯咖啡。让我们开始吧。
进口
import pandas as pd
谁想出了 pd,没人知道。但是,这是你在所有代码中看到的熊猫的脸。
数据框架是一种工具,你可以用它来构建你的数据,并从中获取价值,无论是以模型的形式还是以你所获得的分析/可视化的形式。
把自己想象成一个摄影师,以完美的角度拍摄照片。
完美的角度将是你应用于数据的变换,而镜头将是你的最终数据帧,它将被你的可视化或模型摄取。
现在,如何为您的数据获取一个?
要激活数据帧
数据帧可以作用于各种形式的数据。csv 到 Excel 文件。除此之外,数据帧可以由元组列表或字典创建。让我们看一些例子。
df = pd.DataFrame(data={'col1': [1, 2], 'col2': [3, 4]}) col1 col2
0 1 3
1 2 4df = pd.DataFrame(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), columns=['a', 'b', 'c'])a b c
0 1 2 3
1 4 5 6
2 7 8 9
以上是如何通过调用 pd.DataFrame. 在内部创建数据帧的方法。另一种方法是使用结构化 ndarray、字典列表或元组列表以及列名。
# Using structured ndarraydata = np.array([(3, 'a'), (2, 'b'), (1, 'c'), (0, 'd')], dtype=[('col_1', 'i4'), ('col_2', 'U1')])pd.DataFrame.from_records(data) col_1 col_2
0 3 a
1 2 b
2 1 c
3 0 d
如果你想知道什么是 i4 和 U1,那么参考这里的。
# Using list of dictionaries data = [{'col_1': 3, 'col_2': 'a'},
{'col_1': 2, 'col_2': 'b'},
{'col_1': 1, 'col_2': 'c'},
{'col_1': 0, 'col_2': 'd'}]pd.DataFrame.from_records(data) col_1 col_2
0 3 a
1 2 b
2 1 c
3 0 d
每个字典代表数据帧中的一行数据。
# Using list of tuples data = [(3, 'a'), (2, 'b'), (1, 'c'), (0, 'd')]pd.DataFrame.from_records(data, columns=['col_1', 'col_2']) col_1 col_2
0 3 a
1 2 b
2 1 c
3 0 d
最后,让我们看看如何从文件中获取数据。熊猫支持你能想到的大部分文件。如果您的文件类型可用,请在此处选择。我们将看到一个阅读的例子。csv ,大多数数据通常伪装的格式。
pd.read_csv('data.csv')
景色
让我们看看如何查看我们的熊猫数据帧。
让我们以数据帧为例:
df
0 1 2 3
0 0.678864 0.657119 0.308116 0.378025
1 0.416279 0.207325 0.789917 0.017392
2 0.973793 0.611060 0.826773 0.626822
头和尾巴
作为一种视觉手段来记住这一点,我们将使用一枚硬币。现在,它的两边都是我们需要的最基本的熊猫功能。
df
0 1 2 3
0 0.678864 0.657119 0.308116 0.378025
1 0.416279 0.207325 0.789917 0.017392
2 0.973793 0.611060 0.826773 0.626822df.head(1) 0 1 2 3
0 0.678864 0.657119 0.308116 0.378025df.tail(1) 0 1 2 3
2 0.973793 0.61106 0.826773 0.626822
函数头和尾允许分别查看前 n 行和后 n 行,其中 n 是您想要的行数。
每当一个人看到一些数据时,他都希望有秩序。因此,Pandas 提供了对索引和值进行排序的能力。这里可以使用的内存设备是访问一个数组的行为(你需要一个索引,你将得到这个值)。
df 0 1 2 3
0 0.678864 0.657119 0.308116 0.378025
1 0.416279 0.207325 0.789917 0.017392
2 0.973793 0.611060 0.826773 0.626822temp.sort_index(axis=0) 0 1 2 3
0 0.678864 0.657119 0.308116 0.378025
1 0.416279 0.207325 0.789917 0.017392
2 0.973793 0.611060 0.826773 0.626822df.sort_index(axis=0, ascending=False) 0 1 2 3
2 0.973793 0.611060 0.826773 0.626822
1 0.416279 0.207325 0.789917 0.017392
0 0.678864 0.657119 0.308116 0.378025df.sort_index(axis=1, ascending=False) 3 2 1 0
0 0.378025 0.308116 0.657119 0.678864
1 0.017392 0.789917 0.207325 0.416279
2 0.626822 0.826773 0.611060 0.973793
在 sort_index 中,升序默认为真。除此之外,轴 0 和 1 对应于列和行。这个和 Numpy 一样。记住这一点的一个很好的工具是 CR ( 克里斯蒂亚诺罗纳尔多 ) {C=0,R=1}或者更有帮助的、令人敬畏的 CR {Axis,C=0,R=1}。

来源:维基百科
移动到 sort_values。
df0 1 2 3
0 0.678864 0.657119 0.308116 0.378025
1 0.416279 0.207325 0.789917 0.017392
2 0.973793 0.611060 0.826773 0.626822df.sort_values(by=1) 0 1 2 3
1 0.416279 0.207325 0.789917 0.017392
2 0.973793 0.611060 0.826773 0.626822
0 0.678864 0.657119 0.308116 0.378025df.sort_values(by=1, ascending=False) 0 1 2 3
0 0.678864 0.657119 0.308116 0.378025
2 0.973793 0.611060 0.826773 0.626822
1 0.416279 0.207325 0.789917 0.017392df.sort_values(by=1, axis=1) 3 1 0 2
0 0.378025 0.657119 0.678864 0.308116
1 0.017392 0.207325 0.416279 0.789917
2 0.626822 0.611060 0.973793 0.826773df.sort_values(by=1, ascending=False, axis=1) 2 0 1 3
0 0.308116 0.678864 0.657119 0.378025
1 0.789917 0.416279 0.207325 0.017392
2 0.826773 0.973793 0.611060 0.626822
在 sort_index 中,默认升序为真轴默认为 0。
破坏您的数据
在进行探索性分析或特性工程时,您不需要所有的数据。有时候你只是需要一些列。你是怎么得到它们的?
df = pd.DataFrame(np.random.rand(3, 4), columns=['A', 'B', 'C', 'D'])
A B C D
0 0.391324 0.806291 0.944394 0.868385
1 0.917360 0.108644 0.693635 0.070426
2 0.222674 0.578004 0.149174 0.813082df['A']
0 0.391324
1 0.917360
2 0.222674df.A
0 0.391324
1 0.917360
2 0.222674df = pd.DataFrame(np.random.rand(3, 4))
0 1 2 3
0 0.784208 0.987607 0.799535 0.032508
1 0.026126 0.326865 0.225918 0.929332
2 0.451035 0.965493 0.735323 0.991792df[0]
0 0.784208
1 0.026126
2 0.451035df.0
Error!
要取出一列,你可以调出该列的名称,或者使用列索引直接访问它,即假设你的数据帧有列名。如果你的数据框架没有列名,你只能通过列索引来调用。
Loc 和苹果的 iLoc🍎
有两个函数可以用来获取数据:loc 和 iloc。现在 iloc 第一眼看上去就是苹果的版本,但是 i 居然指出可以直接用 index(I = index)。
我们熊猫只有两样东西,指数和标签。因为 iloc 允许使用索引访问数据,所以 loc 函数将允许使用 _____ 进行访问?
答案是标签!
让我们看一些例子。
df = pd.DataFrame(np.random.rand(3, 4), columns=['A', 'B', 'C', 'D'])
A B C D
0 0.187429 0.565957 0.611477 0.747578
1 0.359590 0.548627 0.895516 0.381595
2 0.229124 0.702323 0.878924 0.483650df.loc[:, 'A']
0 0.187429
1 0.359590
2 0.229124df.loc[:, ['A', 'B']]
A B
0 0.187429 0.565957
1 0.359590 0.548627
2 0.229124 0.702323df.loc[0, ['A']] # same as df.loc[0, 'A']
A 0.187429df.loc[0, ['A', 'B']]
A 0.187429
B 0.565957df.iloc[2]
A 0.229124
B 0.702323
C 0.878924
D 0.483650df.iloc[0:2, 0:2]
A B
0 0.187429 0.565957
1 0.359590 0.548627df.iloc[:, 0:2]
A B
0 0.187429 0.565957
1 0.359590 0.548627
2 0.229124 0.702323df.iloc[0:2, :]
A B C D
0 0.187429 0.565957 0.611477 0.747578
1 0.359590 0.548627 0.895516 0.381595df.iloc[0:2, [0,2]]
A C
0 0.187429 0.611477
1 0.359590 0.895516df.iloc[[0,2], 0:2]
A B
0 0.187429 0.565957
2 0.229124 0.702323
NumPy 的遗产
布尔索引,使用可以为数据帧中的每个值设置真或假的条件,你可以把它们取出来。
这是第一次进入 NumPy,因此,继承。这种索引将是科学家们使用最多的数据。它是你清理数据时最好的朋友。
让我们快速看一些例子。
df = pd.DataFrame(np.random.rand(3, 4), columns=['A', 'B', 'C', 'D'])
A B C D
0 0.187429 0.565957 0.611477 0.747578
1 0.359590 0.548627 0.895516 0.381595
2 0.229124 0.702323 0.878924 0.483650df[df > 0.3]
A B C D
0 NaN 0.565957 0.611477 0.747578
1 0.35959 0.548627 0.895516 0.381595
2 NaN 0.702323 0.878924 0.483650df[~df > 0.3]
Error!df[~(df > 0.3)]
A B C D
0 0.187429 NaN NaN NaN
1 NaN NaN NaN NaN
2 0.229124 NaN NaN NaNdf[df > 0.3 & df < 0.5]
Error!df[(df > 0.3) & (df < 0.5)]
A B C D
0 NaN NaN NaN NaN
1 0.35959 NaN NaN 0.381595
2 NaN NaN NaN 0.483650df[df < 0.3 | df < 0.5]
Error!df[(df < 0.3) | (df < 0.5)]
A B C D
0 0.187429 NaN NaN NaN
1 0.359590 NaN NaN 0.381595
2 0.229124 NaN NaN 0.483650
~、&和|对应于逻辑 NOT、and 和 OR,用于布尔索引。记住在使用多个条件时一定要使用 Paran themes(你肯定会这么做)。
生存还是毁灭——缺失的价值观
现实重拳出击!
完美数据是不存在的!
欢迎来到现实世界。糟透了。你会喜欢的。
你会得到缺失的值,你必须处理它们。别担心,熊猫会保护你的。
df = pd.DataFrame(np.random.rand(3, 4), columns=['A', 'B', 'C', 'D'])
A B C D
0 0.187429 0.565957 0.611477 0.747578
1 0.359590 0.548627 0.895516 0.381595
2 0.229124 0.702323 0.878924 0.483650
df[~(df > 0.3)]
A B C D
0 0.187429 NaN NaN NaN
1 NaN NaN NaN NaN
2 0.229124 NaN NaN NaNdf[~(df > 0.3)].dropna(how='any')
Empty DataFramedf[~(df > 0.3)].dropna(how='all')
A B C D
0 0.187429 NaN NaN NaN
2 0.229124 NaN NaN NaNdf[~(df > 0.3)].dropna(how='any', axis=1)
Empty DataFramedf[~(df > 0.3)].dropna(how='all', axis=1)
A
0 0.187429
1 NaN
2 0.229124
dropna()函数允许您删除数据帧中的行或列,这些行或列要么在整个行/列中包含 NaN,要么在行/列中只包含一个值作为 NaN。
默认情况下,how = 'any '(如果行/列中有单个 NaN,则会被丢弃),axis = 0(即 row)。
两者的其他选项都是 how = 'all '(行/列中的所有值都应该是 NaN)和 axis = 1(即列)。
df = pd.DataFrame(np.random.rand(3, 4), columns=['A', 'B', 'C', 'D'])
A B C D
0 0.187429 0.565957 0.611477 0.747578
1 0.359590 0.548627 0.895516 0.381595
2 0.229124 0.702323 0.878924 0.483650
df[~(df > 0.3)]
A B C D
0 0.187429 NaN NaN NaN
1 NaN NaN NaN NaN
2 0.229124 NaN NaN NaNdf[~(df > 0.3)].fillna(0)
A B C D
0 0.187429 0.0 0.0 0.0
1 0.000000 0.0 0.0 0.0
2 0.229124 0.0 0.0 0.0df[~(df > 0.3)].fillna(0, limit=1)
A B C D
0 0.187429 0.0 0.0 0.0
1 0.000000 NaN NaN NaN
2 0.229124 NaN NaN NaNdf[~(df > 0.3)].fillna(method='ffill')
A B C D
0 0.187429 NaN NaN NaN
1 0.187429 NaN NaN NaN
2 0.229124 NaN NaN NaNdf[~(df > 0.3)].fillna(method='pad')
A B C D
0 0.187429 NaN NaN NaN
1 0.187429 NaN NaN NaN
2 0.229124 NaN NaN NaNdf[~(df > 0.3)].fillna(method='bfill')
A B C D
0 0.187429 NaN NaN NaN
1 0.229124 NaN NaN NaN
2 0.229124 NaN NaN NaNdf[~(df > 0.3)].fillna(method='backfill')
A B C D
0 0.187429 NaN NaN NaN
1 0.229124 NaN NaN NaN
2 0.229124 NaN NaN NaNdf[~(df > 0.3)].fillna(method='ffill', axis=1)
A B C D
0 0.187429 0.187429 0.187429 0.187429
1 NaN NaN NaN NaN
2 0.229124 0.229124 0.229124 0.229124
fillna()函数用于替换数据帧中的 NaN 值。它接受一个值来替换每个 NaN。以下是关于上述例子的一些揭示:
- 回填相当于 bfill。我知道你想通了。
- pad 相当于 ffill。我希望你明白这一点。
- axis = 1 对列应用了函数。
让我们谈谈我们的选择。
limit 告诉 Pandas 每行/每列要替换多少个值。
方法告诉熊猫如何替换南值。ffill/pad 将从行/列的开头开始填充。b 填充/回填将从行/列的末尾开始填充。
轴告诉熊猫是在行还是列上操作。
结论
在这篇文章中,我们了解了如何在熊猫中生存并创造一些可操作的输出。与此同时,我们也看到了一些记忆设备来记住所有这些东西。
阿布舍克·维尔马
数据科学面试的基本 Python 编码问题
破解数据科学面试
Python 中的数据操作和字符串提取

拉娅·索德伯格在 Unsplash 上的照片
随着工程文化的不断发展,数据科学家经常与其他工程师合作,构建管道并执行大量软工程工作。求职者预计会面临 R/Python 和 SQL ( 基本和棘手 SQL)方面的大量编码挑战。
从我过去的面试经历来看,仅仅会编码是远远不够的。有经验的程序员和经过代码训练营训练的初学者的区别在于,他们有能力将大问题分解成小问题,然后编写代码。
这是我的“啊哈”时刻。
在过去的几个月里,我一直在刻意练习剖析代码,并遍历我的思维过程,如果你跟踪我的进展,你可能会注意到这一点。今天的内容我也会这么做。
数据操作和字符串提取是数据科学访谈的重要组成部分,无论是作为一个单独的主题还是与其他主题相结合。我在之前的一篇文章中阐述了数据科学家的 6 个 Python 问题,并提供了主要科技公司提出的其他真实面试问题:
</6-python-questions-you-should-practice-before-coding-interviews-f958af55ad13>
在这个领域有一个普遍的误解,那就是使用野蛮的强制方法是不值得推荐的,因为它效率低并且占用内存。
在这里,我倾向于不同的观点。这对新的 Python 程序员非常有帮助,因为野蛮力量一步一步地解决了问题,并加强了我们的逻辑推理技能,也就是对问题的更好理解和更好的“想法实现”能力。
事不宜迟,我们开始吃吧
问题 1:反转字符串
-编写一个反转字符串的函数。
- sentence = "我喜欢用 Python 编程。你呢?”
穿过我的思维
这是一个热身问题,但将其嵌入到一个更大的问题中后会变得复杂。无论如何,反转一个字符串最直接的方法是向后访问它。
解决方案 1:字符串[::-1]
'?uoy tuoba woH .nohtyP ni gnimmargorp evol I'
解决方案 2:列表理解
'?uoy tuoba woH .nohtyP ni gnimmargorp evol I'
如果问我另一种方法,我会推荐列表理解,因为它很简单。
问题 2:斐波那契数,FAANG
通常表示为 F(n)的斐波纳契数列形成了一个序列,称为斐波纳契数列,使得每个数字都是前面两个数字的和,从 0 和 1 开始。
也就是说,
F(0) = 0,F(1) = 1,
F(n) = F(n -1) + F(n-2),对于 n > 1。
-给定 n,计算 F(n)。
穿过我的思维
这就是“如果你知道你知道的问题类型。面试考生要么爱,要么恨,就看站在哪一边了。如果他们熟悉快捷方式,这几乎是一行代码。否则,它可能是不必要的繁琐,甚至无法解决。
首先,在提出更好的解决方案之前,我会使用暴力说出我的想法。作为一个经验法则,如果我们用简单的方法编码,列出一些斐波纳契数并尝试找出任何一致的模式是一个好主意。
F(1) = 1。
F(2) = F(1) +F(0) = 1+0 = 1
F(3) =F(2) + F(1) = 1+1 = 2
F(4) = F(3) + F(2) = 2+1 = 3
F(5) = F(4) + F(3) = 3+ 2 = 5
…
你发现什么有用的模式了吗?
事实证明,这是一个迭代的过程。在每一步,我们必须连续更新三个值,并为下一次迭代准备值。有关详细解释,请参见以下代码。
解决方案 1:暴力
2
正如所看到的,当一个简单的解决方案存在时,它变得不必要的乏味。很有可能,你的面试官会要求你改进它。
解决方案 2:递归
2
实际上,斐波那契数是采用递归的理想场景。我们指定当 n ≤1 时的基本条件,并将函数应用于自身两次。
简洁的解决方案!
你的面试官现在很开心。
问题 3:检查两个字符串数组是否等价,脸书
-给定两个字符串数组 word1 和 word2,如果这两个数组表示相同的字符串,则返回 true,否则返回 false。
-如果按顺序连接的数组元素形成字符串,则字符串由数组表示。
-https://leet code . com/problems/check-if-two-string-arrays-are-equivalent/
穿过我的思维
问题问两个字符串数组是否相同。要相同,它们必须包含相同的元素和顺序。例如,以下两个数组由于元素顺序不同而不同:
word1 = ["a "," cb"],word2 = ["ab "," c"]
残酷的方法表明,我们可以创建两个新的字符串,并将每个数组中的元素分别迭代到空字符串中,并判断它们是否相同。
解决方案 1:暴力
True
这不是一个最佳的解决方案,因为它创建了两个新的对象,占用了额外的空间,并且 for 循环通常在速度方面效率不高。如果数据很大,解决方案 1 会导致延迟。
你的面试官不满意,要求改进。
好吧,让我们做解决方案 2。
解决方案 2: join and ==
True
join()方法是一个强大的工具。我们可以将两个数组中的元素连接到空字符串中,并检查它们是否相同,而无需创建任何额外的内容。
太棒了。你通过了面试。

在 Unsplash 上由 Nelly Antoniadou 拍摄的照片
问题 4:矩阵对角和,Adobe
-给定一个正方形矩阵 mat,返回矩阵对角线的和。
-仅包括主对角线上的所有元素和次对角线上不属于主对角线的所有元素的总和。
-https://leetcode.com/problems/matrix-diagonal-sum/
穿过我的思维
TBH,我因为两个原因被困了足足 20 分钟。首先,我没有意识到,我不必扣除中心元素,但只有当矩阵长度是奇数。其次,我不确定如何在第二对角线上添加元素。在探索和检查了其他人的 Leetcode 解决方案之后,我想出了下面的代码。
解决方案 1
8
根据 Leetcode 的说法,运行时间和内存使用率都在 80%以上。问题 4 有没有更好的解决方法?如果你知道一个,请让我知道。
(2021 年 1–14 日更新。多亏了 Nebadita Nayak,我们现在有了一个更好的解决方案。)
向内巴蒂塔·纳亚克致谢:
解决方案 2
问题 Dream11 商店中特殊折扣的最终价格
-给定数组价格,其中 prices[i]是商店中第 I 件商品的价格。
-商店中的商品有特殊折扣,如果您购买第 I 件商品,那么您将获得相当于 prices[j]的折扣,其中 j 是最小指数,使得 j > i 和 prices[j] < = prices[i],否则,您将不会获得任何折扣。
-返回一个数组,其中第 I 个元素是考虑到特殊折扣,你将为商店的第 I 件商品支付的最终价格。
-https://leet code . com/problems/final-prices-with-a-special-discount-in-a-shop/
穿过我的思维
我在第一次审判中犯了一个错误。我认为 j 应该在 I 的下一个位置。j = i+1。事实证明,j 可以在任何地方,只要它比 I 大。我没有得到它,直到交叉检查我的提交与正确的输出。
同样,一旦我们找到一个元素,我们必须中断for 循环;否则,for 循环将遍历整个数组。
解决方案:两个 for 循环
嵌套的 for 循环占用大量时间,运行时间为 O(N),可以使用另一种数据类型 stack ( link )来改进。我会再写一篇关于这个话题的文章。
问题 6:颠倒字符串中的单词 III,亚马逊
-给定一个字符串,您需要颠倒句子中每个单词的字符顺序,同时仍然保留空白和初始单词顺序。
-https://leet code . com/problems/reverse-words-in-a-string-iii/
穿过我的思维
这个问题有几种变体。在阅读以上信息的同时,我有了一些大致的方向。为了颠倒每个字符但保持原来的顺序,我可以使用空格作为分隔符来分割字符串。然后,通过单词[::-1]反转单词,并在单词之间添加空格。包括 rstrip()方法来去除尾部的空白。
然而,除了返回的结果包含空格(这会阻止我正确提交代码)之外,所有其他步骤都可以工作。
不正确的代码:
def reverse_words(s):
result = “”
new = s.split(“ “)
for word in new:
word = word[::-1]+” “
result += word
**result.rstrip() # don't put it here** return result ** # instead, put it here**
如果我将 rstrip()放在 return 命令之前,它将无法正常工作。它生成以下带有尾随空格的结果。
"s'teL ekat edoCteeL tsetnoc " # trailing whitespace
除了最后一行,每一行代码都运行良好。真扫兴。在四处搜索之后,我发现 rstrip()必须在开头或结尾。如果在中间就不行了。
修复故障后,我们开始。
解决方案:for 循环
"s'teL ekat edoCteeL tsetnoc"
同样,rstrip()方法必须在末尾或开头!
完整的 Python 代码在我的 Github 上有。
外卖食品
- 我喜欢外卖,因为这是我反思过去错误并从中吸取教训的部分,也是今天的第一课。
- 虽然不建议使用暴力,但它有助于我们更好地理解潜在的逻辑并记录我们的想法。
- 没有好的编码这种东西,因为总有更好的解决方案,这就是为什么我们的采访不断要求改进。尝试另一种数据类型/结构?
Medium 最近进化出了它的 作家伙伴计划 ,支持像我这样的普通作家。如果你还不是订户,通过下面的链接注册,我会收到一部分会员费。
https://leihua-ye.medium.com/membership
我的数据科学面试序列
统计和编程
</5-python-coding-questions-asked-at-faang-59e6cf5ba2a0> </6-python-questions-you-should-practice-before-coding-interviews-f958af55ad13>
喜欢读这本书吗?
还有,看看我其他关于人工智能和机器学习的帖子。
2021 年每个数据科学家都应该知道的基本 Python
将您的 Python 游戏提升 10 倍!

对于数据科学家来说,Python 就像空气一样重要。我们整天走路,聊天,思考 Python。但是,Python 是一个浩瀚的海洋,我们有时肯定会迷路。本文旨在成为一座灯塔,让您回忆起自己在 Python 海洋中的位置。这里是对所有数据科学家的 Python 复习。
目录

创造
l = [] # initialize a listl = [0] * n # initializes a list of 0s with n elements
分配
l[i] = val # assignment of ith valuel[i:j] = newlist # replace ith to jth-1 elements with all elements of newlist
检查东西
x in l # checks if x is in lx not in l # checks if x isn’t in l
发现事情
len(l) # returns length of listmin(l) # returns minimum element of listmax(l) # returns maximum element of listsum(l) # return sums of elements in listl.index(x) # returns index of first occurence of x in ll.index(x, s) # returns index of first occurence of x in l starting from sth positionl.index(x, s, e) # returns index of first occurence of x in l from sth to eth — 1 positionl.count(x) # returns number of total occurences of x in l
把东西拿出来
l[i] # return ith elementl[i:j] # returns ith to jth — 1 element l[i:j:k] # return list from ith to jth — 1 element with step kl[i:] # returns list after ith element l[:j] # returns list until jth — 1 elementl[-1] # returns last elementl[:-1] # returns all but the last element
扔东西
l.pop() # returns and removes last element from the listl.pop(i) # returns and removes i-th element from the listl.remove(i) # removes the first item from the list whose value is i
破坏东西
del l[i] # deletes ith elementdel l[i:j] # deletes ith to jth — 1 elementsdel l[i:j:k] # deletes ith to jth — 1 elements with step kdel l # deletes list ll.clear() # deletes all elements in l
放东西进去
l.append(item) # adds item to the last of list ll.insert(ind, item) # add item at index ind in list l l.extend(m) # add elements of m to the end of list l l += m # add elements of m to the end of list l
一些就地(没有额外的内存使用)的东西
l.reverse() # reverses the elements of the listl.sort() # sorts list l
一些额外的技巧
list(set(l)) # returns list of unique elements in lzip(list1, list2) # returns list of tuples with n-th element of both list1 and list2sorted(l) # returns sorted copy of listsorted(l, key=lambda x: x[0]) # works on list of lists or list of tuples, returns copy of l sorted according to first element of each element list(reversed(l)) # returns reversed list l",".join(l) # returns a string with list elements seperated by commalist(enumerate(l)) # return a list of tuples (index, value at index)from collections import Counter; Counter(l) # return dictionary with counts of each unique elements in l
词典

创造
d = {} # creates empty dictionaryd1 = {‘a’: 1, ‘b’: 2, ‘c’: 3} # just give keys and valuesd2 = dict(zip([‘a’, ‘b’, ‘c’], [1, 2, 3])) # give two list of equal length and zip them d3 = dict([(‘a’, 1), (‘b’, 2), (‘c’, 3)]) # give a list of pair tuples# d1, d2, d3 yield the same output
把东西拿出来
d[k] # returns value against key k in dd.get(k) # returns value aginst key k in dd.keys() # returns keys of dd.values() # return values in dd.items() # return tuples of key-value pairsiter(d) # returns an iterator over keys in dreversed(d) # returns a reversed iterator over keys in dlist(d) # returns list of keys in dlen(d) # return number of items in d
改变事物
d[k] = v # reassigns value of key k to vd.update(new) # updates d according to dictionary or list of pair tuples newd |= new # updates d according to dictionary or list of pair tuples new (in Python 3.9)
d | new # merges d and new, values in new take precedence (in Python 3.9)d.setdefault(k) # return value of key k if it exists, else adds key k to d with value Noned.setdefault(k, v) # return value of key k if it exists, else adds key k to d with value k
破坏东西
del d[k] # deletes key k from d d.pop(k) # return value against key k and remove key k from dd.popitem() # removes and returns a (key, value) pair from d (LIFO {Last In First Out} order)
检查东西
k in d # checks if key k is in dk not in d # checks if key k is not in d
额外奖励
d = {v: k for k, v in d.items()} # interchanges keys with values in dictionary
线

创造
s = ''s = ""
把东西拿出来
l[i] # return ith elementl[i:j] # returns ith to jth — 1 element l[i:j:k] # return list from ith to jth — 1 element with step kl[i:] # returns string after ith element l[:j] # returns string until jth — 1 elementl[-1] # returns string elementl[:-1] # returns all but the last element
你需要的常量
import string # mandatory for everything in this sectionstring.ascii_letters # 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'string.ascii_lowercase # 'abcdefghijklmnopqrstuvwxyz'string.ascii_uppercase # 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'string.digits # '0123456789'string.punctuation # '!”#$%&\’()*+,-./:;<=>?@[\\]^_`{|}~'string.whitespace # ' \t\n\r\x0b\x0c'; \x0b is \v (vertical tab), \x0c is \f (form feed)string.printable # = digits + ascii_letters + punctuation + whitespace
美化你的琴弦
s.capitalize() # captilizes (first character becomes uppercase) the strings.lower() # all characters become lowercases.casefold() # more rigorous lowercase (languages other than English are covered)s.upper() # all characters become uppercase
寻找东西
s.count(sub) # count occurences of substring sub in ss.count(sub, start) # count occurences of substring sub starting from start position in string ss.count(sub, start, end) # count occurences of substring sub from start to end — 1 position in string ss.find(sub) # returns index of first occurence of substring sub in s, return -1 if not founds.find(sub, start) # returns index of first occurence of substring sub starting from start position in string s, returns -1 if not founds.find(sub, start, end) # returns index of first occurence of substring sub from start to end — 1 position in string s, return -1 if not founds.index(sub) # returns index of first occurence of substring sub in s, raises error if not founds.index(sub, start) # returns index of first occurence of substring sub starting from start position in string s, raises error if not founds.index(sub, start, end) # returns index of first occurence of substring sub from start to end — 1 position in string s, raises error if not foundlen(str) # returns length of string
检查东西
s.startswith(prefix) # checks if s starts with prefixs.startswith(prefix, start) # checks if s starts with prefix starting from start positions.startswith(prefix, start, end) # checks if s starts with prefix starting from start position until end — 1 positions.endswith(suffix) # checks if s ends with suffixs.endswith(suffix, start) # checks if s ends with suffix starting from start positions.endswith(suffix, start, end) # checks if s ends with suffix starting from start position until end — 1 positions.isalnum() # checks if string is alphanumerics.isalpha() # checks if string contains only alphabetss.isnumeric() # checks if string contains only numberss.islower() # checks if all alphabets in string s are lowercases.isupper() # checks if all alphabets in string s are uppercases.isspace() # checks if s is a space character
改变事物
s.replace(old, new) # replaces substring old with substring news.replace(old, new, count) # replace substring old with substring new for count number of times starting from left side of string ss.ljust(width) # puts width — len(s) spaces on the right side of string ss.ljust(width, fillchar=c) # puts character c width — len(s) times on the right side of string s s.rjust(width) # puts width — len(s) spaces on the left side of string ss.rjust(width, fillchar=c) # puts character c width — len(s) times on the left side of string ss.strip() # all spaces gone left and right both sides of string ss.lstrip() # all spaces gone on left side of string s s.rstrip() # all spaces gone on right side of string ss.strip(k) # all substrings k gone left and right both sides of string ss.lstrip(k) # all substrings k gone on left side of string ss.rstrip(k) # all substrings k gone on right side of string ss.split(‘,’) # splits the string by ‘,’; returns a lists.split(‘::’) # splits the string by ‘::’; returns a list s.split(‘ ‘) # splits the string by ‘ ‘; returns a lists.zfill(width) # adds width — len(s) zeros on the left side; if a +/- sign is there then zeros are added after it
奖励诡计
s.join(l) # joins a list or string l with substring s
结论
Python 是 Google 和 Guido von Rossum 送给我们的健康礼物。还有很多东西有待探索。本文提供了一些现成的复习资料和技巧,帮助您进一步加深对 Python 的了解。
阿布舍克·维尔马
- 如果你喜欢这个, 在 Medium 上关注我 获取更多
- 我们来连线上 LinkedIn
Python 中的主标注处理
关于在 Python 中处理字符串类型数据的完整教程。

介绍
在数据科学和计算机编程的广阔世界中,有许多不同的数据类型都需要以某种方式来处理。在计算中,我们非常习惯于频繁地与数字和数学打交道来完成目标。然而,虽然计算机可能会用数字说话,但人类不会,包含字符的数据在计算机编程中被称为字符串数据类型。
字符串数据类型可用于表示任何类型的值。这可以是整个段落,例如元代码,或者甚至只是一个单词或字符。也就是说,string 数据类型对于促进这类数据是完全必要的,这类数据是数据生态系统的重要组成部分。通常,在处理数据时,我们需要大规模地操纵数据,以便实际使用数据,这一点在字符串的例子中最为明显。
幸运的是,Pythonic 的 str 类实际上非常容易使用,甚至可以使最复杂的操作看起来简单!在编程中,字符串和其他数据类型的一个很大区别是,计算机本身并不真正理解字符串。计算机在数字中工作,所有的字符串都是在没有这些数字的高层次上呈现的,但在幕后,我们可以使用很多技术来转换这些字符串,使其更容易被计算机解释。
str 类
str 类是 Python 命名字符串的奇怪方式,它提供了一些很好的功能来简化字符串操作。重要的是要记住,Python 语言的一大优势是其数据和脚本的高级接口,这一点从第一天起就备受推崇。当然,为了打开文件、编写文本和处理数据,数据类型必须便于适当地处理字符串和 IO。因此,str 类实际上非常善于管理其中的字符数据。然而,为了正确理解 str 类,对它进行一点解构可能是个好主意。
创建字符串
每当我们在数据上使用断言操作符时,Python 都会检查它的基本数据类型,以便为所提供的数据找到准确的匹配。这当然是 true 或 string,可以这样构造:
string = "Hello, my name is Emmett :)"
现在让我们来看看这个新字符串的类型:
type(string)

(图片由作者提供)
循环
首先,需要注意的是,这种类型实际上是可迭代的。一个字符串只是一个按顺序显示的字符列表,和列表一样,我们可以称之为字符串的索引。这也意味着我们可以使用将 iterable 作为 str 对象的参数的方法,例如 len():
string[1]
string[0]
len(string)

(图片由作者提供)
这也意味着我们可以在常规的 for 循环中使用这个对象。
for char in string:
print(char)

(图片由作者提供)
功能
如您所料,str 类还包含许多函数,可以非常有效地用来操作字符串中包含的字符。当然,可能要花很长时间才能真正看完它们,但我会展示我发现最有用的几个!列表中第一个是 strip()函数。这个函数用于从字符串中删除前后的空格。考虑以下字符串:
h = " This string starts and ends with spaaaace "
这个字符串本身前后都有很多空格,如果我们试图使用这个字符串来处理一个模型,这可能会很成问题!通过使用 strip()函数删除所有多余的空白,我们可以非常简单快速地解决这个问题。
print(h.strip())

(图片由作者提供)
这个类中另一个很酷的函数是 replace()函数。这用于将某些字符组合替换为其他字符组合。看看这个:
ex = "I love Data Science"
ex.replace("Data", "Biological")

(图片由作者提供)
isalnum()和 isalpha()分别是一组方便的方法,可以用来探索字符串类型的数据。这两个函数可用于返回一个布尔值,该值将区分字符串是否包含数字。还有一些类似的方法可以用来判断一个字符串实际上是浮点数还是整数,但是现在,让我们来看看这两种方法的用法:
numbers = “2”
nonumbs = “two”
print(numbers.isalpha())
print(nonumbs.isalpha())
print(numbers.isalnum())
print(nonumbs.isalnum())

(图片由作者提供)
我发现自己经常使用的另一个很酷的功能是 rfind()。这个函数返回一个字符串的索引,在这个字符串中找到一组给定的字符。考虑下面的例子,它详细描述了我如何假设我可能会喜欢一种我从未尝试过的食物:
txt = "I like fish. I like tacos. So I like fish tacos!"x = txt.rfind("fish")
z = txt.rfind("like")
print(x, z)

(图片由作者提供)
Python 类中另一个很棒的字符串函数的例子是 partition()方法。这个函数可以用来根据一个逻辑中心将一个字符串分成三个独立的部分。在我们的鱼玉米卷的例子中,我把这个应用到中间的句子,我喜欢玉米卷,让我们看看产生了什么结果:
fish1, fish2, fish3 = txt.partition("I like tacos.")
print(fish1)
print(fish2)
print(fish3)

(图片由作者提供)
使用 index()函数,我们还可以获得任何给定字符集的索引。在本例中,我使用它分别打印出 fish 的字符,方法是在基于这两个值对字符串进行索引之前获取索引和长度,以便只接收我们最初索引的字符串:
searchingfor = "fish"
print(txt.index("fish"))
start = txt.index("fish")
sl = len(searchingfor)
for i in range(start, start + sl):
print(txt[i])

(图片由作者提供)
一张便条
我们刚刚看到的所有这些函数都很棒,但是在数据科学中,我们实际上使用单个大字符串的频率有多高呢?正确的答案是根本不经常,所以知道如何将这些技术应用于整个字符串列表而不仅仅是单个字符串是很重要的。为此,我们通常使用 Pandas apply()函数,该函数将获取一个 lambda 表达式,并将其应用于整个系列或数据帧类型。
import pandas as pd
listofstr = ["Hello", "Hi", "Hello", "Hi"]
nums = [5, 10, 15, 20]
df = pd.DataFrame({"A" : listofstr, "B" : nums})
df["A"] = df["A"].apply(lambda x: x.replace("H", "W"))
df.head(5)

(图片由作者提供)
正则表达式
关于 str 类和字符串,我想谈的另一件事是正则表达式的概念。正则表达式是编译器的上下文调用,允许程序员扩展字符串的功能。也就是说,它们在某种程度上对于用字符串编程的坚实基础是必不可少的,但只是作为一个例子,我将演示\n,\n 是一个用于创建新行的正则表达式。有很多正则表达式可以放在字符串中来改变它们的行为方式,当然这只是处理字符串的冰山一角。
poem = """I tried a career called data science,\n Then befriended a colony of ants\n
The ants ate my sugar,\n
so i took my cooler,\n
and flooded, so harshly, their pants"""
print(poem)

(图片由作者提供)
编码
字符串是一种很好的数据形式,但是就计算机语言而言,它们对计算机来说毫无意义。计算机将字符和文本解释为用 Unicode 或 ANSI 表示的任意浮点数。由于这些字符对计算机来说是完全任意的,对我们来说是主观的,为了实际使用这些数据进行机器学习,甚至统计,我们需要对其进行编码。编码是将任意字符转换成没有任何意义的真实数据的好方法。有许多类型的编码器,但主要使用的三种是标签编码器,普通编码器和一个酒店编码器。
Onehotencoding 通过将每个单独的类别作为布尔型特征来扩展字符串的维度。这可能是积极的,也可能是消极的,取决于应用程序,因为有时这种更高的维度会导致一些严重的问题,但有时它会非常有帮助。如果您的要素是一个很小的类子集,那么这一点尤其正确,在这种情况下,维度的权衡将不会像仅使用少量维度那样明显。我们可以使用 Sklearn 附带的 Onehotencoder 来实现这一目标:
from sklearn.preprocessing import OneHotEncoder
现在让我们用一些基于类的字符串制作一个数据框架:
df = pd.DataFrame({"Danger_Level": ["A", "A", "C", "D", "C", "A", "B"],
"Layer_Count": [5, 6, 12, 19, 13, 6, 9]})
现在,我们将通过调用不带参数的构造函数来初始化编码器:
encoder = OneHotEncoder()
接下来,我们需要让我们的单个特征改变它的形状。这是因为 SkLearn 更喜欢我们处理矩阵,而不是数据帧中的一维数组。通常,在矩阵中,特征是通过垂直方向的观察而不是水平方向的观察列出的。因此,我们的特性看起来像是一堆不同的特性,每个特性只有一个样本——但事实并非如此。为了解决这个问题,我们需要将它转换成一个 NumPy 数组,然后对它进行整形:
import numpy as np
dl = np.array(df["Danger_Level"]).reshape(-1, 1)
现在,我们可以在编码器中调用 fit_transform()方法,以便对新的 dl NumPy 数组进行编码:
output = encoder.fit_transform(dl)
普通编码器也可以这样做。OrdinalEncoder 不同于 OneHot 编码器,它保留了我们的特征的维度。编码器只是将类映射到整数值,然后在匹配时替换数据中的这些值:
from sklearn.preprocessing import OrdinalEncoder
encoder = OrdinalEncoder()
encoder.fit_transform(dl)
我们要看的最后一种编码器是浮点或标签编码器。这种编码器通常用于矢量化和自然语言处理,但当应用于分类问题时,本质上与顺序编码器一样,只是使用字符中的浮点组合作为映射数。
from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()
encoder.fit_transform(dl)
结论
字符串可能是一种有趣的数据类型,因为它们对人类来说是主观经验,而对计算机来说是不存在的,但尽管如此,它们对数据科学家经常处理的许多数据来说肯定非常重要。幸运的是,Python 拥有处理这些数据类型的非常坚实的基础,了解这方面的正确方法可以节省大量时间!非常感谢你的阅读,祝你好运!
机器学习工程师的基本技能
打造机器学习工程师

在 Unsplash 上拍摄的 ThisisEngineering RAEng
你喜欢开发软件,但是对数据科学非常感兴趣吗?如果是这样,你可能要考虑机器学习工程师的角色。机器学习工程师位于软件工程和数据科学的交汇点——这意味着如果你真的想出类拔萃,你需要这两种技能。
数据科学家的重点是将分散的数据转化为可操作的见解。另一方面,机器学习工程师专注于开发利用数据的工作软件以及自动化预测模型。
[## 数据相关角色概述
不同视角的数据](/overview-of-data-related-roles-70ca48f8deed)
以下是所需技能的总结:
软件工程
计算机科学基础对机器学习工程师至关重要;很好地掌握数据结构和算法,如多维数组、数组、堆栈、队列、树等。ML 工程师还应该能够编写能够搜索、排序和优化代码的算法。进一步补充,对可计算性、复杂性和计算机架构的理解都是必要的。
由于机器学习工程师的最终输出通常是可交付的软件,ML 工程师必须很好地了解软件的各个不同部分如何工作和通信,以便为您的组件构建合适的接口。
数据科学
数据科学家通常非常依赖编程语言,如 Python、R、SQL、Java 等。他们在概率和统计方面也有坚实的基础——主题包括:
注:这绝不是一份详尽的清单。更多阅读我的文章,2021 年学习数据科学的课程
此外,ML 工程师应该有娴熟的数据建模和评估技能。数据建模是训练学习算法以预测给定一组特征的标签的过程。
建模的目标是识别有用的模式,这些模式最好地允许模型推广到新的看不见的实例——这就是评估发挥作用的地方。在机器学习项目的开始,将使用任务的最合适的评估度量来确定算法执行得有多好。
机器学习
许多广泛使用的机器学习算法可以通过第三方库实现,如 Scikit-Learn、Keras、TensorFlow、PyTorch、MLlib 等。然而,有效地应用这些算法包括选择适合手头问题的模型、优化方法以及了解超参数对学习的影响。
此外,ML 工程师应该擅长超参数调整。超参数是用于控制学习过程的参数值,因此超参数调整可以描述为为学习算法选择一组最佳超参数的问题。
ML 工程师可能需要了解的一些其他工具(取决于他们工作的公司)包括:
- Spark 和 Hadoop
- 阿帕奇卡夫卡
- 谷歌云 ML 引擎
- 亚马逊机器学习
- Azure 机器学习
- IBM 沃森
机器学习工程师自学路径
和数据科学一样,成为一名机器学习工程师需要学习的东西也很多。假设我们已经学习了基础数学要求,下面我会留下一些好的资源来帮助你练习 ML 工程师技能集的各个部分:
软件工程&编程
https://leetcode.com/problemset/all/ https://www.hackerrank.com/dashboard
数据科学&机器学习
https://www.kaggle.com/ https://datahack.analyticsvidhya.com/?utm_source=main-logo
课程
包裹
随着数据科学继续从研究转向生产,对 ML 工程师的需求一直在快速增长。如果你有构建优秀软件的诀窍,但仍然热爱数据科学,ML 工程师之路可能是适合你的。
感谢您的阅读!
如果你喜欢这篇文章,请通过订阅我的免费 每周简讯与我联系。不要错过我写的关于人工智能、数据科学和自由职业的帖子。
</5-laws-for-self-taught-data-professionals-4bf351ac5c24> [## 自学成才的数据专业人员的 5 条法则
towardsdatascience.com](/5-laws-for-self-taught-data-professionals-4bf351ac5c24)
关于 F1-Score 你需要知道的重要事情
了解 F1 分数的关键基础知识,F1 分数是机器学习中最重要的评估指标之一。

一级方程式赛车(照片由斯潘塞·戴维斯在 Unsplash 上拍摄)
目录
- 简介
- 精确和召回:一个回顾
- 假阳性和假阴性哪个更糟糕?
- F1 得分背后的动机
- 什么是 F1-Score?
- 精准、召回还是 F1 评分?
- 为什么谐音是指?
- 结论
1.介绍
F1 值是机器学习中最重要的评价指标之一。它通过结合两个相互竞争的指标——精确度和召回率,很好地总结了模型的预测性能。这篇文章是我前两篇关于准确度、精确度和召回的文章的延伸,我鼓励你去看看!
在这篇文章中,我将涵盖所有你需要知道的关于 F1 分数的重要事情。我将通过解释何时使用 precision 或 recall 来设置上下文。接下来,我将定义 F1-score,并解释何时应该使用它。最后,我将比较两个数字平均的不同类型——包括调和平均数、几何平均数和算术平均数——并讨论为什么 F1 得分基于调和平均数。
2.精确度和召回率:回顾
如果不首先设定精确和回忆的上下文,就不可能讨论 F1 分数。简言之,精确度和召回率是帮助我们评估分类模型对特定兴趣类别(也称为正面类别)的预测性能的指标。
- 精度:在所有积极的预测中,有多少是真正积极的?
- 回忆:在所有真实阳性病例中,有多少是预测阳性?
形式上,精确度和召回率被定义为:


3.假阳性和假阴性哪个更糟糕?
那么,什么时候应该使用精确,什么时候应该使用回忆呢?仔细想想,精确度和召回率都衡量模型的预测性能,但方式不同。精确度测量由假阳性(FPs)引起的错误的程度,而召回测量由假阴性(FNs)引起的错误的程度。因此,为了决定使用哪个度量,我们应该评估这两种类型的错误对我们用例的相对影响。因此,我们应该问的关键问题是:
"对于我们的用例,哪种类型的错误——FPs 或 FNs——更不受欢迎?"
让我们通过回顾我们的癌症预测例子来理解这一点。图 1 显示了总结假设预测结果的混淆矩阵。在这四种情况中,情况#2 和#3 是不可取的。

图 1:癌症预测的混淆矩阵(图片由作者提供)
- 场景#2 代表 FPs 。在 900 名真正没有患癌症的病人中,模型显示其中 80 人患了癌症。这 80 名患者可能会接受昂贵和不必要的治疗,以牺牲他们的健康为代价。
- 场景#3 代表 FNs 。100 个真正患有癌症的病人中,模型显示其中 20 个没有。这 20 名患者将无法确诊,也无法接受适当的治疗。
在这两种情况之间,哪个更不可取?我们可以说这是第三种情况。不接受任何可能危及生命的治疗可能比接受不必要的治疗更糟糕。由于 FNs 造成的误差影响被评估为更为显著,因此选择具有尽可能少 FNs 的模型是有意义的。换句话说,我们应该用回忆代替精确。
那么,什么时候应该使用精度呢?许多真实世界的数据集通常没有标记,即我们不知道每个观测值属于哪个类。电子邮件是垃圾邮件还是火腿?一篇文章到底是不是假新闻?客户会流失吗?在这种用例中使用机器学习进行分类的一个关键好处是减少所需的人力。因此,对于模型预测为正的所有观察值,我们希望它们中有尽可能多的真的为正。换句话说,我们希望我们的模型尽可能精确。在这种情况下,精度应该比召回更重要。
4.F1 分数背后的动机
对于您的用例,您也有可能认为 FPs 和 FNs 引起的误差(几乎)同样不可取。因此,您可能希望模型的帧数和帧数尽可能少。换句话说,你可能希望最大化精确度和召回率。在实践中,由于精确度和召回率之间的权衡,不可能同时最大化精确度和召回率。
提高精度会降低召回率,反之亦然。
因此,给定不同模型的精度和召回值对,您将如何比较并决定哪一个是最好的?答案是——你猜对了——F1——分数。
5.F1-Score 是什么?
根据定义,F1 分数是精确度和召回率的调和平均值。它使用以下公式将精度和召回率合并为一个数字:

这个公式也可以等价地写成:

请注意,F1-score 同时考虑了精确度和召回率,这也意味着它同时考虑了 FPs 和 FNs。精确度和召回率越高,F1 分数越高。f1-分数范围在 0 到 1 之间。越接近 1,模型越好。
6.精确度、召回率还是 F1 分数?
既然我们已经讨论了基础知识,让我们来看看在精确度、召回率和 F1 分数之间进行选择的思考过程。假设我们训练了三个不同的癌症预测模型,每个模型都有不同的精度和召回值。

图 2:使用精确度、召回率或 F1 分数的模型选择(作者的动画 GIF)
- 如果我们评估 FPs 引起的误差(图 1 中的场景# 2)更不可取,那么我们将基于精度选择模型,并选择模型 c
- 如果我们评估 FNs (图 1 中的场景# 3)导致的错误更不可取,那么我们将基于召回选择一个模型,并选择模型 b。
- 然而,如果我们评估两种类型的错误都是不期望的,那么我们将基于 F1 分数选择一个模型并选择模型 a。
因此,这里的要点是您选择的模型在很大程度上取决于您选择的评估指标,而评估指标又取决于您的用例中 FPs 和 FNs 误差的相对影响。
7.为什么调和平均?
我简单提到过 F1 分数是“精确度和召回率的调和平均值”。谐音表示是什么意思?当然,还有其他方法可以将两个数字合并成一个数字……例如算术平均值或几何平均值(它们的数学公式见图 3)。

图 3:两个数的调和、算术和几何平均值(图片由作者提供)
关于调和、算术和几何平均的更多详细信息,我推荐下面这个由丹尼尔·麦克尼克写的帖子。
如果你在谷歌上搜索 F1-score 为什么要用调和平均值,你会找到类似“调和平均值更多地惩罚不相等的值”和“调和平均值惩罚极值”这样的答案。一开始我很难理解他们。因为我喜欢简化概念,所以我在图 4 中创建了一个交互式 3D 散点图来帮助我更好地理解。实际上,这个 3D 散点图比较了谐波、算术和几何平均值如何随着不同组的精度和召回值而变化。请随意使用它!
图 4: 3D 互动图表,说明调和、算术和几何平均的各自行为(图片由作者提供)
另外,我不会仔细检查用于生成图 4 的代码,因为那超出了本文的范围,但是可以在我的GitHub repo查看。
从图 4 中我们可以得出一些有趣的观察结果:
- 当且仅当精度和召回率相等时,三种类型的方法是相同的。请注意红色散点如何仅沿精度-召回轴的对角线与蓝色和绿色散点相交(即当精度=召回时)。
- 当精度和召回率不相等时,调和平均值和几何平均值开始远离算术平均值。红色散射点形成平面,而蓝色和绿色散射点形成曲面。
- 精度和召回值越不相等,调和平均值越低,超过几何和算术平均值。调和平均值的蓝色散点图所代表的平面比几何和算术平均值的平面更“弯曲”。

我们如何以直观的方式理解这些观察结果?让我们想象自己站在图 4 的最高点,这里精度和召回率都等于 1,三类均值都是 1。现在,假设我们沿着精度轴“走”平面的斜率,即我们将 recall 固定为 1,同时将精度从 1 降低到 0.95、0.90、0.85 等等,直到达到 0.05。当我们行走时,精确度降低,所有三种类型的平均值也降低。
然而,对于调和平均值,它下降得最厉害,因为它的平面是最“弯曲”的。在精度为 0.05 的点上,如果我们在代表调和平均值的平面上,我们会发现自己处于最低点。这里算术平均值是 0.525,几何平均值是 0.224,但是调和平均值只有 0.095!现在,对我(希望对你也是)来说,调和平均值“更多地惩罚不平等值”或“惩罚极端值”的事实的真正含义更有意义。
那么,F1-score 为什么是基于调和平均值的呢?很明显,调和平均值不鼓励极大的不平等值和极低的值。我们希望 F1 分数在精确度或召回率较低时给出合理的低分,并且只有调和平均值才能做到这一点。例如,当 recall 为 1 而 precision 为 0.05 时,算术平均值为 0.525 或几何平均值为 0.224 可能不足以传达精度非常低的事实,而调和平均值为 0.095 则足以传达这一事实。此外,如果精度或召回率为 0,使用调和平均值意味着 F1 得分将为 0。
8.结论
恭喜你!您已经了解到,选择精度、召回率还是 F1 分数来评估模型取决于 FPs 和 FNs 在您的使用案例中的相对影响。特别是,如果两种类型的错误都是不可取的,F1-score 会更合适。此外,你对 F1 分数基于调和平均值的原因有了更好的直觉。当然,机器学习中还有其他评估指标,但我特意将这篇文章主要集中在 F1 分数上。那好吧..请继续关注我的下一篇文章!
你好!我是泽雅。我喜欢将数据科学的概念分解成简单的小块,并给出清晰直观的解释。毕竟,这是我发现自己最有效的学习方式。通过分享我如何简化概念,我希望帮助人们降低学习数据科学的门槛。如果你觉得这篇文章有用,请在评论中告诉我。我也欢迎讨论、问题和建设性的反馈。请随时通过媒体关注我,或者通过 LinkedIn 或 Twitter 联系我。祝您愉快!
参考
- 比尔·威尔逊(2021 年 11 月 18 日)。理解调和平均数。http://groups.di.unipi.it/~bozzo/The%20Harmonic%20Mean.htm
建立因果关系:第 1 部分
随机实验的黄金标准

图片由作者提供。
能够建立因果关系是强大的。它赋予你在对话中使用“因为”这个词的权利。我们的销售额增加了,因为我们改变了网站布局。犯罪率下降了,因为新的预防政策已经出台了。准确定位因果关系对于数据驱动的决策至关重要,无论是在企业中优化公司运营,还是在政府中确保我们的税收以最有效的方式支出,政策有效运作。在这一系列文章中,我讨论了四种统计工具,它们为说“因为”提供了科学依据。
只有以严格的方式建立因果关系,你才有权利使用“因为”这个词。
我们要研究的四种因果关系估计方法是:
这个系列的第一部分集中在科学的黄金标准:随机实验。

相关性并不意味着因果关系
你可能以前听过这个。两件事同时发生的事实并不意味着其中一件是导致另一件的。看看这个臭名昭著的,在泳池中溺死的人数和尼古拉斯·凯奇电影上映数量之间近乎完美的相关性。

资料来源:https://www.tylervigen.com/spurious-correlations(抄送 4.0)
我们觉得这是一种虚假的关联。这两个变量之间的任何因果关系似乎都是荒谬的。但是撇开这个考虑,你怎么说是什么导致了什么呢?尼古拉斯·凯奇的电影很糟糕吗,人们看完之后会淹死自己吗?《溺水者》是否能以某种方式让凯奇发行更多电影?或者可能存在一个导致这两者的外部因素:糟糕的经济可能会使一些人陷入绝望,迫使凯奇出演更多的电影来增加他的收入。

资料来源:xkcd(https://xkcd.com/552/)
为了回答因果关系相关的问题,我们需要严格的统计工具。最简单也是最理想的方法是随机实验。

潜在结果模型
在跳到随机实验之前,让我先介绍一下用于分析因果关系的框架,称为潜在结果模型。它的大部分词汇来自医学研究。实际上,因果关系估计也被称为治疗评估。这是因为该框架的早期应用侧重于评估为患者提供一些治疗是否会导致他们的健康状况改善。
让我们从一些符号开始。潜在结果模型中有两个感兴趣的变量:
- Cᵢ = {0,1},表示人 i 是否暴露于治疗,
- Y ᵢ,人的结局我的。
治疗 C = 1 字面意思是用某种药物治疗病人,在这种情况下 Y 可以表示病人体内病毒的数量。但是 C = 1 也可以意味着向用户显示新的网站布局,而不是旧的布局( C = 0),和 Y 可以测量用户在网站上花费的时间。无论我们想确定药物是否会改善健康,或者网站布局的改变是否会增加粘性,同样的框架也适用。
现在,单个患者 Y ᵢ 的结果可以取两个可能值中的一个,称为潜在结果:
- Y₁ ᵢ 表示人 i 被治疗的结果(当 Cᵢ = 1 ),
- Y₀ ᵢ 表示如果人 i 没有被治疗的结果(当 Cᵢ = 0 )。
自然,一个人不可能既被治疗又不被治疗,这就是为什么我们只观察两个价值中的一个。另一种被称为反事实。想象用户 i 被展示了新的网站布局,他们在网站上花了 50 秒,所以 Y₁ ᵢ = 50。我们不知道反事实的 Y₀ ᵢ 测量这个用户会在网站上花多长时间,如果他们看到旧的布局。
但是想象一下,我们可以在不同的现实之间移动,可以观察同一个人在两种情况下的结果:当他们接受治疗时和不接受治疗时。然后,我们可以简单地将这两个结果相减,得到人 i 的治疗效果:
TEᵢ = Y₁ ᵢ - Y₀ ᵢ
如果我们的网站用户在旧布局下只花了 Y₀ ᵢ = 30 秒,那么对于这个特定用户来说,切换到新布局的治疗(或因果)效果将达到 50 - 30 = 20 秒。
如果我们有许多人的数据,我们可以将他们平均计算出平均处理效果(ATE) 来估计网站布局变化的平均影响 :
E[Y₁ᵢ - Y₀ᵢ]
然而,在大多数情况下,我们对 ATE 并不真正感兴趣。通常,研究的目标是在证明有效的情况下,将治疗推广到更大的人群。例如,如果我们发现药物能使病人康复,我们会把它分发给每个人。如果新的网站布局能让用户更长时间地使用它,我们会向所有用户推广。因此,我们实际上感兴趣的量是对被治疗者的平均治疗效果的,或 ATET 。这只是治疗对接受治疗的个体的平均影响:
E[Y₁ᵢ - Y₀ᵢ | Cᵢ = 1]
如果我们能够计算出 ATET,我们就能平均得出治疗的因果影响。但我们不是,因为我们无法知道公式中存在的反事实。我们能做些什么呢?

幼稚的比较?
如果我们只看治疗组和未治疗组之间平均结果的差异呢?
天真的比较= E[Y₁ᵢ | Cᵢ = 1] - E[Y₀ᵢ | Cᵢ = 0]
平均而言,服用药物的患者每 1cm 组织中有 90 个病毒细胞,而没有服用药物的患者有 140 个。这是否证明了药物对病毒的因果影响?可惜没有。看看为什么让我们增加和减少接受治疗的患者的反事实 Y₀ᵢ。通过加减相同的东西,我们不会打破等式,现在等式是:

添加这两个相互抵消的项(用橙色标记)让我们注意到,这样一个天真的比较实际上是 ATET 加上其他一些项。另一个术语被称为选择偏差。如果选择偏差为零,那么这样一个简单的比较将会产生治疗的因果效应。
因果效应估计完全是为了摆脱选择偏差。
然而,通常它不是零。选择偏倚与个体选择治疗的原因有关,消除它是建立因果关系的主要挑战。幸运的是,有一个银弹。

进入随机实验
银弹被称为随机实验,非常简单。你所需要做的就是将这些人随机分成两组:治疗组,他们将接受治疗,而对照组,则不会。
这背后的想法是,如果样本足够大,并且分割是真正随机的,那么所有其他与治疗无关但可能影响结果的特征,将在两组中分散开来。两组都将包含高个子和矮个子、聪明的和愚蠢的、年轻的和年老的、有抵抗力的和易受影响的人,比例相似。治疗组和对照组之间的唯一区别在于治疗。
另一种说法是,在没有治疗的情况下,两组是相同的。从统计学上来说:
E[Y₀ᵢ | Cᵢ = 0] = E[Y₀ᵢ | Cᵢ = 1],
或者:如果没有进行治疗,治疗组的平均结果将与对照组相同。你可能已经注意到,这两项,现在假设是相等的,是在前面的公式中形成选择偏差的(用粉色标记)。因为它们相等,所以相减后就抵消了。因此,在一项随机实验中,治疗组和未治疗组之间平均结果的简单差异就是 ATET 或治疗对治疗组的因果影响!
在一项随机实验中,治疗和未治疗之间的平均结果差异产生了因果效应!

美中不足
随机实验很酷。他们只需要正确的实验设计,足够大的样本,真实可信的随机化,瞧,我们得到了因果效应。我们可以说“因为”。我们有。在医学研究中,药物正在随机实验中进行测试,这使得它们能够获得必要的批准。随机实验设计确保患者因服药而恢复。在市场营销和网络广告中,A/B 测试是一个标准。它们也是随机实验。一个随机的用户样本暴露于一些 UX 的变化,这些变化后来可以说是导致用户在网站上花更多的时间或者点击更多的广告。
然而,美中不足的是。随机实验并不总是可行或合乎道德的,尤其是在经济学和其他社会科学领域。税收改革会降低失业率吗?人们不可能真的对随机抽样的公民征收新税。是不是小时候被欺负过的人成年后就因为这个不太成功?我们不应该指派孩子被欺负,等他们长大了再看会发生什么。在这种情况下,为了估计因果影响,我们需要求助于随机实验以外的其他方法。
接下来,我们来看看工具变量法。

感谢阅读!
如果你喜欢这篇文章,为什么不订阅电子邮件更新我的新文章呢?并且通过 成为媒介会员 ,可以支持我的写作,获得其他作者和我自己的所有故事的无限访问权限。
需要咨询?你可以问我任何事情,也可以在这里 为我预约 1:1 。
你也可以试试我的其他文章。不能选择?从这些中选择一个:
</6-useful-probability-distributions-with-applications-to-data-science-problems-2c0bee7cef28> [## 贝叶斯数据分析最温和的介绍
towardsdatascience.com](/the-gentlest-of-introductions-to-bayesian-data-analysis-74df448da25) </8-tips-for-object-oriented-programming-in-python-3e98b767ae79>
建立因果关系:第 2 部分
通过工具变量加强随机性

图片由作者提供。
每当有人说“因为”这个词时,他们都暗示着一种因果影响。某件事导致另一件事发生。否则就不会发生了。这样的“因为”陈述相当大胆,因为需要一些统计技巧来正确地建立因果关系。在这一系列文章中,我讨论了四种统计工具,它们为说“因为”提供了科学依据。
我们要研究的四种因果关系估计方法是:
如果您还没有阅读关于随机实验的第 1 部分,请考虑绕道而行。它介绍了我们正在使用的行话,将使我们更容易理解我们从这里开始。
这个系列的第二部分集中在随机实验不可行的情况下,人们不得不求助于另一种技术:工具变量。

随机实验与观察研究
在本系列的第 1 部分中,我们已经看到了随机分配到治疗组或对照组是如何消除选择偏差的,这使我们能够比较治疗组和未治疗组的平均结果,以获得治疗的因果影响。例如,这在在线营销和广告中运行良好,人们可以将成千上万的用户无缝地分成实验组。然而,在其他领域,进行随机实验会带来一系列问题:
- 它们很贵。在医学研究中,药物对健康的因果影响必须在允许进入市场之前得到证明,制药公司别无选择,只能在随机试验上多花一些钱。在其他领域,资金可能不足。
- 如果要进行一些实验,让某些人而不是所有人受益,那么这种治疗可能会被认为是不道德的(想想把普遍基本收入作为一种治疗发放给随机选择的个人,看看它是否会影响失业水平)。
- 未接受治疗的个体倾向于寻求替代治疗。在医学实验中,被分配到对照组的病人可能非常渴望康复,以至于他们会服用其他药物,从而掩盖了所检查药物的因果效应。
- 约翰·亨利效应:当人们知道自己是实验的一部分时,他们的行为会有所不同。
在这种情况下,当不可能进行随机实验时,人们必须依靠所谓的观察研究。在这些研究中,我们观察治疗的效果,而不选择谁接触或不接触它。
在观察性研究中,我们不控制谁暴露于治疗,我们只是观察结果。
考虑两个例子,我们将在后面提到:
- 加州当局担心来自贫困家庭的学生的学习成绩,尤其是那些家里没有电脑的学生。他们提出了一项政策计划,给这些家庭提供电脑。为了验证这是否是一个好主意,他们需要知道计算机是否会导致孩子们在学校的表现提高。作为实验的一部分,向大量随机抽样的家庭提供电脑会过于昂贵,而且可能会被公众认为不公平。
- 学历对工资的因果关系是什么?经济学家用来解决这个问题的一个典型框架,被称为明瑟方程,是回答以下问题:一个人平均多受一年教育能多挣多少钱?没有人能真正强迫学校内外的人进行随机试验。
为了估计这些场景中的因果关系,我们需要借助一个聪明的技巧。但是在我们开始之前,我们首先需要再看一看随机试验。

回归和因果关系
在本系列的前一篇文章中,我们已经看到,给定治疗组和对照组的真正随机分配,我们可以简单地计算两组的平均结果,减去这两个数字,瞧,我们有因果关系。
理解这与拟合这个简单的线性回归模型完全相同是至关重要的:
y = α₀ + α₁ x
其中 y 是结果 x 是处理。一旦我们估计了模型的参数,我们会发现α₁等于治疗组和未治疗组的平均结果之差。所以,α₁是 x 对 y 的因果影响
在随机试验中,使用虚拟治疗指标的线性回归估计其对结果的因果影响。
你可能想知道为什么。为什么简单的线性模型会产生因果影响?在不深究公式和数学的情况下,让我试着给你一些基本的直觉。
我相信有三种不同的方式来理解和解释线性回归。
- 如果你来自计算机科学或机器学习,你可能会将其视为一个简单的监督预测模型,其参数通过梯度下降进行优化,由于其简单性,可能会对数据进行欠拟合。
- 如果你是一名统计学家,你可能会将回归视为一种描述性模型,旨在解释数据中的关系,在大量假设下,通过普通最小二乘法估计参数。
- 最后,从计量经济学家的角度来看,回归是一把刀,沿着预测变量将数据切成碎片,并计算每个碎片的平均 y 值。 x 处的系数是 x. 的每个值的平均值 y 之间的差值
回归是一把将数据分割成小块并计算每一块的平均值的刀。
最后一种说法可以用数学方法证明,但是,让我给你看一个 Python 模拟。让我们创建一个有 100 个观察值的想象数据集,其中 50 个观察值将以 100 左右的随机结果进行处理,另外 50 个观察值将以 200 左右的随机结果进行处理。然后,我们来比较一下均值计算和回归系数的区别。

Difference in means: 100.12765658545278
Regression coefficient: 100.12765658545275
要点:我们可以使用线性回归来估计随机实验中的因果影响。但是观察研究呢?

观察研究中的回归
在观察性研究中,除了影响结果的治疗外,个体之间可能存在差异。例如,回想一下估算教育对工资影响的尝试。还有许多其他因素影响着工资,比如居住地、职业、年龄等等。假设我们将它们列出来,并用控制变量进行回归拟合:
y = α₀ + α₁ x + β₁ c₁ + … + βₙ cₙ
其中 y 为工资, x 为受教育年限,Cs 为刚才讨论的控制变量。这行得通吗?可惜没有。主要是因为我们不能控制一切。有一些影响工资的变量我们没有数据,不知道它们有多重要,或者根本无法衡量(想想“技能”)。这种现象被称为省略变量偏差。忽略重要的变量会使模型将它们的影响归因于被包含的变量,从而使对治疗的因果影响的估计产生偏差。
省略变量偏差使得回归系数在预测因子的因果影响方面不可信。
那么,如何解决观察性研究呢?如果随机化是不可能的,我们需要…无论如何都要实施它!算是吧。

工具变量
我们需要找一个仪器!工具是与感兴趣的因果变量相关但与结果的任何其他决定因素不相关的一些变量。
如果我们找到这样一个工具变量 z ,我们可以运行一对回归,这一过程被称为两阶段最小二乘法(2SLS)。在第一阶段,我们用工具解释感兴趣的因果变量。在第二阶段,我们使用第一阶段模型的预测来解释结果:
第一阶段:x = α₀ + α₁ z
第二阶段:y = β₀ + β₁ x̂
实际上,没有必要拟合两个模型,因为感兴趣的结果可以使用封闭形式的公式来计算。我们可以计算治疗 x 对结果 y 的因果影响
β₁=(e[y | z = 1]—e[y | z = 0]/(e[x | z = 1]—e[x | z = 0])。
工具变量(IV)方法实际上随机创建了组,其中工具的每个值代表一个组。然后,它简单地比较组均值( Z=0 vs Z=1 )。
工具变量法随机创建组,其中工具的每个值代表一个组,这使得它几乎与随机实验一样好。
注意,因果变量 X 和仪器 Z 都用于估算。如果仪器满足某些条件,这就相当于随机分配!我们说的是什么条件?有四个。
- 独立。该工具应该像彩票一样,这样它创建的群体在潜在治疗和结果方面是相似的。
- 排除。仪器只能对治疗状态产生影响,并且只能通过这种影响来影响结果。
- 第一阶段。仪器必须与治疗状态相关联。
- 单调性。改变个人的仪器值将导致其治疗状态的相应改变。
如果这四个成立,IV 可以估计因果影响。这听起来可能很抽象,让我们看一些例子。

寻找仪器
寻找好的仪器既是科学也是艺术。之前提到的两项研究(计算机对学校表现的影响和教育对工资的影响)是展示在哪里寻找它们的有趣例子。
在他们最初的关于计算机和学校表现的研究中,fair lie Robinson 实际上进行了一项大型且昂贵的随机实验,但是假设这是不可行的。假设某个慈善组织已经随机向一小部分家庭分发了电脑。我们有工具指标:该家庭是否从该组织收到了计算机。它与感兴趣的因果变量(拥有一台计算机)相关,与学校表现的任何其他决定因素无关(因为它是随机的)。结论:可以在小范围内模拟随机分配,并使用 IV 来估计因果影响。顺便说一句,作者发现使用电脑根本不能转化为更好的学校表现!
工资和教育的例子更加有趣。在的论文中,安格里斯特和克鲁格提出了一个聪明的工具。他们注意到,美国大多数州要求学生在年满六岁的那一年入学。一年中晚出生的相对年级来说年龄小,早出生的相对年龄大。与此同时,义务教育法通常要求学生在 16 岁生日之前留在学校。这样一来,1 月份出生的学生将与 12 月份出生的学生处于不同的年级,而 12 月份出生的学生在法律上是允许退学的。这创造了一个自然的实验,在这个实验中,年轻人根据他们的出生季节被迫上学不同的时间。因此,作者使用出生的四分之一作为教育的工具。结论:有时法律提供了研究人员可以利用的自然实验。
然而,有时找到一个好的乐器是困难的,甚至是不可能的。幸运的是,在有利的情况下,我们也许可以使用剩下的两种方法之一来建立因果关系:回归不连续性或差异中的差异。
接下来,让我们看看回归不连续设计。

感谢阅读!
如果你喜欢这篇文章,为什么不订阅电子邮件更新我的新文章呢?并且通过 成为媒介会员 ,可以支持我的写作,获得其他作者和我自己的所有故事的无限访问权限。
需要咨询?你可以问我任何事情,也可以在这里 为我预定 1:1 。
你也可以试试我的另一篇文章。不能选择?从这些中选择一个:
</6-useful-probability-distributions-with-applications-to-data-science-problems-2c0bee7cef28> [## 贝叶斯数据分析最温和的介绍
towardsdatascience.com](/the-gentlest-of-introductions-to-bayesian-data-analysis-74df448da25) </8-tips-for-object-oriented-programming-in-python-3e98b767ae79>
建立因果关系:第 3 部分
回归不连续设计

图片由作者提供。
“因为”这个词很容易被过度使用。我们常常意识不到它的意义的力量。“因为”意味着因果关系——原因和结果之间的关系,这需要一些统计技巧来建立。
这是我讨论四种统计工具的系列文章中的第三篇,这四种统计工具为说“因为”提供了科学依据。我们正在研究的四种因果关系估计方法是:
如果你还没有看完前两部分,请考虑绕道。他们引入了术语,这将使我们更容易理解我们从这里开始。
在这个系列的第三部分,我们将着眼于不连续性,或截断点,人们可以利用它来估计因果关系。

介绍
在本系列的第 1 部分中,我们已经看到了随机实验如何让我们通过简单地比较治疗组和对照组之间的平均结果来计算治疗的因果影响。这是可能的,这要归功于随机化的魔力:如果分配到两个组确实是随机的,那么每个组中的个体仅在他们是否暴露于治疗中有所不同,因为所有其他的差异都被平均掉了。
在这两种情况下,我们实际上是在比较那些仅在治疗状态上有所不同的群体。有时,这种组作为阈值的结果出现。

中断
在某些情况下,治疗是由一些观察到的变量是否超过一个已知的分界点决定的。想想给最好的学生现金奖励。他们会让你以后有更好的学习成绩吗?如果奖金颁发给所有 GPA 高于某个阈值的学生 C,我们应该能够使用回归不连续技术来回答这个问题。
我们不能简单地比较获奖学生和未获奖学生的学业表现,因为这两组学生在奖项之外的更多方面存在差异,比如知识和学习意愿。
然而,想想那些刚刚低于阈值 C、的和那些刚刚高于阈值的。这两个群体应该非常相似,由(坏)运气决定他们 GPA 的最后一个小数点,从而决定他们是否通过了门槛并获得了奖励。

回归不连续性
回归不连续性(RD)背后的思想是估计接近临界值两边的个体结果的差异。
回归不连续性比较了刚高于和刚低于决定治疗状态的阈值的个体。
设 Y 为结果,例如学习成绩,XGPA,C 为治疗分界点,高于该分界点则给予奖励。我们希望计算那些 GPA 刚刚超过 C、的学生和那些 GPA 刚刚低于它的学生之间的平均结果差异、,其中ε表示实际 GPA 和阈值之间的差异,我们希望阈值无限小,即收敛到零。

这种差异被称为截止时的平均治疗效果(ATE)。通过运行回归可以很容易地对其进行估计,在回归中,我们用虚拟治疗指标(是否获奖,姑且称之为 D )和建立阈值的变量(在本例中为 GPA)来解释结果:

这里的 α1 是想要的待遇效果,也就是奖金对以后学习成绩的影响。
包括治疗指标和设定临界值的变量允许我们估计治疗对结果的因果影响。

假设和有效性
和往常一样,还是有一些问题。首先,RD 假设 X 是 D. 在我们这里的例子中,应该是这样的:GPA 是奖金是否授予的唯一决定因素。然而,在其他情况下,情况不一定如此。这一假设至关重要,因为它确保了 D 与回归误差项不相关,否则将导致内生性问题。
确保治疗仅由是否超过临界值决定,并且个人无法控制治疗状态。
第二,如果个人能够控制治疗**【D】状态,并且从治疗中获得预期收益,那么临界值两边的群体可能是不同的。为了验证这是否是一个问题,看一下 X 的密度是有用的:在 C 的跳跃暗示着一个问题。

清晰模糊的设计
到目前为止所讨论的方法被称为 Sharp RD 设计,在该设计中,治疗状态正好在临界值处改变。另一种可能性是模糊 RD,其中治疗的概率在截止点发生变化。
在急变研发中,治疗状态在临界值处发生变化。在模糊 RD 中,治疗的概率在截止点发生变化。
作为一个具体的例子,考虑英国司法部的这项研究,旨在评估提前释放囚犯对累犯的影响。只有刑期超过三个月的罪犯才有资格提前释放,而且只有其中一些人获得了提前释放。因此,超过三个月监禁的门槛增加了获得提前释放的可能性,但并不能保证这一点。
在模糊研发中,需要工具变量回归来估计治疗对结果的因果影响。
在这种情况下,普通的回归是不够的。我们将需要求助于工具变量回归,其中我们将使用通过阈值的指标 I、作为处理的工具:

接下来,我们再来看差异中的差异法。

感谢阅读!
如果你喜欢这篇文章,为什么不订阅电子邮件更新我的新文章呢?通过 成为媒介会员 ,你可以支持我的写作,并可以无限制地访问其他作者和我自己的所有故事。
需要咨询?你可以问我任何事情,也可以在这里 为我预约 1:1 。
你也可以试试我的其他文章。不能选择?从这些中选择一个:
* </6-useful-probability-distributions-with-applications-to-data-science-problems-2c0bee7cef28> *
建立因果关系:第 4 部分
提示和技巧
利用差异中的差异的政策转变

图片由作者提供。
人们倾向于以因果的方式思考。每当我们观察到一个结果,我们的大脑会立即提示最可能的原因。很自然,感觉很轻松。然而,用统计学上合理的方法来量化原因对结果的影响要困难得多。在某种程度上,我们可以有把握地说,某件事的发生是因为某个其他事件的 。
这是我讨论四种统计工具的系列文章中的第四篇也是最后一篇,这四种统计工具为说“因为”提供了科学依据。我们正在研究的四种因果关系估计方法是:
如果你还没有看完前两部分,请考虑绕道。他们引入了术语,这将使我们更容易理解我们从这里开始。第 3 部分不是阅读本文的必要部分,但尽管如此,我还是鼓励您去看看——它描述了一种简单而又强大的因果关系估计方法。
在这个系列的最后一部分,我们将研究一些外部政策变化创造准实验环境的情况。在这些情况下,我们可以使用差异中的差异方法来利用这种准实验。

介绍
在本系列的第一部分中,我们已经看到了确定因果影响的最佳方式是进行一个随机实验。只是将人们随机分成两组,其中只有一组接受治疗,并比较他们之间的平均结果。这种方法的成功要归功于随机分组的魔力,它平均掉了两组之间除治疗以外的所有差异。然而,有时随机实验成本太高,不道德,或者我们不得不使用观察性研究的数据(在这些研究中,我们观察治疗的效果,而不选择谁接触或不接触它)。
在第 2 部分中,我们已经看到,比较随机实验的平均结果与运行线性回归解释治疗指标的结果是一样的。我们还看到,在观察性(相对于随机)研究中,选择偏差阻止了这种基本回归产生正确的因果关系估计。绕过它的一个方法是找到一个工具变量,一个随机但与治疗相关的变量,然后运行工具变量回归 (IV)。
在第三部分中,我们看到了通过一些观察变量是否超过一个已知的分界点来决定治疗的情况。在这样的场景中,回归不连续 (RD) 方法就派上了用场。他们基本上是比较接近分界点两边的个体在结果上的差异,假设他们的确切分数,以及他们所处的分界点的一边,受到随机性的影响。
随机实验是金标准。在静脉注射和研发中,我们实际上是在试图创造一个尽可能接近随机实验的环境,这样我们就可以比较那些只有治疗状态不同的组。有时,这种团体的出现是政策变化的结果。

政策转变
在某些情况下,治疗是由政策或经济环境的一些外部变化决定的。一个著名的例子是卡和克鲁格对最低工资和失业的研究。根据一些经济理论,最低工资的提高应该会导致失业激增,因为无法承受更高雇佣成本的雇主被迫解雇最廉价的员工。但是我们如何用一种统计上严格的方法来估计这种因果关系呢?
美国新泽西州于 1992 年 4 月通过了一项法案,将最低工资从 4.25 美元提高到 5.05 美元。卡德和克鲁格利用了这一政策变化。他们测量了该州同年 2 月和 11 月的就业水平,即该法律生效前后的就业水平。他们用全时当量(fte)表示就业情况,发现 2 月份为 20.44,11 月份为 21.03。这产生了 0.59 的正差值,表明最低工资提高后就业实际上略有增加。
然而,这种天真的比较不会产生有效的因果关系估计,因为除了最低工资增长之外,可能还有许多因素影响就业激增。例子有经济状况、天气或简单的季节性——就业根据几个月的季节性模式而变化。

差异中的差异
理想情况下,我们希望将最低工资上调后新泽西州的就业情况与最低工资没有上调时该州的就业情况进行比较。在数学符号中,这种预期的因果关系是:

其中 t 表示时间步长(0 表示政策变化前,1 表示政策变化后),Y₀是没有政策变化时的就业,Y₁是有政策变化时的就业。显然,我们不能观察第二个术语,因为它是一个反事实:没有政策变化的就业,在变化之后。
我们唯一能观察到的是变化前后就业的简单差异:

但是,正如我们已经提到的,由于遗漏变量的偏差,这不是一个有效的因果关系估计。只有当我们假设,它才等于我们之前定义的真正因果效应

这意味着在没有政策变化的情况下,随着时间的推移,结果不会有什么不同。在大多数情况下,这很难保持。
这就是差异中的差异(DiD)方法发挥作用的地方。它通过将受变化影响的一组(治疗组)在变化前后的结果与未受影响的一组(对照组)的相应差异进行比较,放松了这种不可信的假设:

这里,表示对照组的结果。正如你所看到的,我们计算了两个随时间变化的差值:一个是经过治疗的,另一个是未经治疗的。让我们通过回到卡片和克鲁格的研究来更具体的说明一下。
差异中的差异将治疗前后的结果与未治疗的相应差异进行比较。
对他们来说,新泽西州 0.59 的就业变化是接受治疗者的超时差异。但是他们需要控制组:与新泽西公民相似的人,但是不受新政策的影响。他们选择了邻近的宾夕法尼亚州,在大多数经济指标和天气方面与新泽西州相似。通常,这两个州的就业水平遵循相似的模式。他们测量了宾夕法尼亚州相应月份的就业差异,为-2.16,这意味着那里的就业水平显著下降。这是他们的对照组超时差异。

卡德和克鲁格的 FTE 测量。来源:https://en . Wikipedia . org/wiki/Difference _ in _ differences # Card _ and _ Krueger (1994) 示例
最后,他们计算了差异中的差异:0.59–2.16 = 2.75。这使他们得出结论,新泽西最低工资 0.80 美元的增长导致了 FTE 2.75 美元的就业增长,这与一些经济理论相反。

假设
通常情况下,统计方法会附带一些假设,DiD 也不例外。
核心 DiD 假设是共同(或平行)趋势假设。它指出,在没有政策变化的情况下,对照组和治疗组的平均结果变化是相同的。这可以用图表清楚地描述出来:

差异中的差异方法依赖于共同趋势假设。图片由作者提供。
我们假设,如果没有进行治疗,治疗组和对照组会显示出相同的趋势。自从宾夕法尼亚州的就业人数减少了 2.16。我们假设,如果没有政策变化,新泽西州的死亡率也将下降相同的幅度,从 2.44 降至 18.28。这一假设值与观察到的就业率之间的差异(21.03–18.28 = 2.75)归因于治疗,即政策变化。
平行趋势假设:在没有政策改变的情况下,随着时间的推移,对照组和治疗组的结果变化是相同的。
共同趋势假设可能看起来相当严格,但如果对照组确实与治疗组相似,就像这两个相邻的州一样,在实践中就不应该违反。然而,在选择对照组时,这一点需要记住。

作为线性回归的差异中的差异
就像我们在本系列中讨论的所有其他因果关系估计方法一样,DiD 可以被视为一个回归模型。该模型应该用三个预测因子来解释结果:
- 虚拟时间步长指示器 T,
- 虚拟治疗指示器 P ,
- 上述两者的乘积,即在政策改变后(当 T =1 时),仅用于治疗个体的观察值为 1 的虚拟值。

在这样的模型中,β₃系数是治疗对结果影响的估计值。
让我们在模拟研究中看看这是不是真的。首先,我对一个有 200,000 个观察值的数据集进行采样,其中 50,000 个观察值在时间 T=0 时被处理,另外 50,000 个观察值在时间 T=1 时被处理,类似的还有未处理的观察值。对于每条记录,我模拟了一个接近 Card 和 Krueger 的就业人数的随机值。
让我们通过手动计算差异来计算 DiD 估计值。
2.751098003538063
现在,让我们试试回归法。
[-2.16443239 -2.88931896 2.751098]
模型系数中的第三项是上述模型方程中的β₃系数,您可以验证它与手动计算差异时的系数相同。

感谢阅读!
如果你喜欢这篇文章,为什么不在我的新文章上 订阅电子邮件更新 ?而通过 成为媒介会员 ,可以支持我的写作,可以无限制的获取其他作者和我自己的所有故事。
需要咨询?你可以问我任何事情,也可以在这里 为我预约 1:1 。
你也可以试试我的其他文章。不能选择?从这些中选择一个:
* *
使用神经网络超越单点估计
用 Python 估算未知数据生成过程的观测数据的 PDF。
机器学习算法通常估计未知数据生成过程的第一矩(即均值)。然而,超越分布拟合一阶矩的应用可以在各种不同的领域找到,如经济[2],工程[3]和自然科学[4]。例如,观察金融市场并试图指导投资者的神经网络可以计算股票市场运动的概率以及运动的方向。为此,可以使用概率密度函数来计算连续随机变量范围出现的总概率。
用于密度估计的传统方法(例如这里的和)是基于函数的统计模型的初步选择和随后对其参数的拟合。例如,神经网络输出的均值(μ)和标准差(σ)参数足以描述正态分布的密度函数:

然而,什么 id。没有关于数据生成过程的先验知识?Aristidis Likas [1]在早期工作中首次提出了一种基于贝叶斯统计的替代方法。这种简单但有效的方法不需要对可用数据进行任何假设,而是从神经网络的输出中提取概率密度函数,该神经网络用合适的数据库进行训练,该数据库包括原始数据和一些具有已知分布的特别创建的数据。不幸的是,最初的实现并不适合现代计算环境,因此,在本文中,我将演示如何在 Python 和 TensorFlow 的上下文中使用该方法。
设计数据生成流程
为了便于说明,我们可以创建一些简单的数据生成过程,如下图所示,其中使用单位指数 Xs 的条件正态分布生成结果。因此,每个个体都将具有以 x 为条件的唯一概率密度函数(PDF)。
def hi_sample(N):
fx = lambda x: np.random.normal(loc = np.mean(x[:,0:-1],1), size=N)
X1 = np.random.exponential(1, size=N)
X2 = np.random.exponential(1, size=N)
Y = fx(np.array([X1,X2]).T)
hi_data = [X1, X2, Y]
return np.array(hi_data).T
train_array = hi_sample(5000)
生成的 Y 将具有以下分布:

作者图片
概率密度估计的基本方法
在这里,我们不讨论原著中的所有数学术语,而是强调用 python 实现所需的基本步骤:
- 用已知的数据生成过程(创建先验分布)扩充原始数据集,该过程来自参考 PDF: P(ref) 。这里,我们假设扩充数据集中所有个体的结果将从标准正态分布中生成:
train_array_0 = train_array.copy()
train_array_0[:,-1] = metropolis_hastings_normal(train_array_0[:,-1]) #regenerate the outcome f(x) using metropolis_hastings simulators with a standard normal distribution.
train_data = [train_array,train_array_0]
2.用二进制判别神经网络拟合扩充的数据集,该网络估计从 pr(X) 或从目标 PDF: p(X) 生成观察对 X:=(x,f(x)) 的概率y(X)。它可以是这样一个简单的网络:
def simple_nn(dim_x):
input_x = tfkl.Input(shape=(dim_x+1))
combined_layer = tfkl.Dense(dim_x)(input_x)
for i in range(5):
combined_layer = tfkl.Dense(dim_x)(combined_layer)
combined_layer = tfkl.Dropout(0.1)(combined_layer)
out = tfkl.Dense(1, activation='sigmoid')(combined_layer)
model = tf.keras.Model(inputs = input_x, outputs=out)
model.compile(loss = 'binary_crossentropy',
optimizer=tf.keras.optimizers.RMSprop(lr=0.001))
return model
3.使用贝叶斯理论计算目标概率密度函数:

因此,用特意扩充的数据集训练 NN 产生了神经函数 y(X) ,通过使用中间的任意 PDF pr(X) ,可以从该神经函数中推断出未知的 PDF p(X) :
conditional_y = pdf_function_i(x).numpy()
reference_dense = norm.pdf(x[:,-1]).reshape(-1, 1) #norm(loc = np.sum(x[:,0:-1],1)).pdf(x[:,-1]).reshape(-1, 1)
conditional_dense = (conditional_y / np.clip(1 - conditional_y, a_min=1e-3, a_max=1.0)) * reference_dense
现在让我们用真实的 PDF 来绘制估计的 PDF (conditional_dense ):

条件正态分布的估计。作者图片
注意这里每个单独的点都有一个以给定的 x 为条件的正态分布。
让我们看看从标准密度函数导出的其他几个条件分布的估计:

条件均匀分布的估计。作者图片

条件指数分布的估计。作者图片
使用相同的标准正态先验,我们能够从不同的原始数据生成过程中获得相当准确的单个 pdf 的估计。我们能进一步提高估计值吗?是的。在 Leonardo Reyneri 等人[5]的另一项工作中,作者建议使用初始估计的估计 PDFp’(X)作为调整估计的先验。也就是说,我们重复第 1 步到第 2 步,用p’(X),代替标准的正态先验,在第三步中,我们估计以下量:

因此,我们可以得到 PDF 的原始估计的一步调整。这背后的直觉类似于自动编码器,其中如果神经网络不能区分估计的 PDF 和先前的 PDF,则估计的 PDF 将是最佳的。
参考
[1] Likas,A. (2001 年)。用人工神经网络估计概率密度。计算机物理通信, 135 (2),167–175。
[2] Combes,c .,Dussauchoy,a .:用于拟合股票交易中开盘/收盘资产价格和收益的广义极值分布。施普林格运营研究 6(1),3–26(2006)
[3] Zaharim,a .,Razali,A.M .,Abidin,R.Z .,Sopian,k .:马来西亚风速数据的统计分布拟合。欧洲科学研究杂志 26(1),6–12(2009)
[4] Wendell Cropper Jr .,p .,Anderson,P.J .:热带棕榈的种群动态:使用遗传算法进行逆参数估计。生态模型 177,119–127(2004)
[5]雷诺里、科拉和范努齐(2011 年 6 月)。通过神经网络估计概率密度函数。在人工神经网络国际工作会议上(第 57-64 页)。斯普林格,柏林,海德堡。
用因果影响估计时间序列的因果效应干预
推断对时间序列数据的因果干预效应

在 Unsplash 上由 Fabrizio Verrecchia 拍摄的照片
想象一下,作为一名数据科学家,在一家销售糖果的公司工作,公司的销售部门就他们最新的活动请求帮助。销售部门想知道他们的新活动从长远来看是否有助于提高公司收入,但由于活动的成本很高,他们无法进行任何 A/B 测试;这意味着你只有当前的实验数据来验证现实世界的影响。我们应该采取什么方法?
解决这个问题的替代方法很少,例如,活动前后的描述性收入比较或实验性假设检验。这些方法都是可行的,但无法衡量实际的影响,因为许多特征会影响结果。
为了解决这个问题,谷歌的团队使用贝叶斯结构时间序列开发了一个开源包,对 R 的因果影响。Python 中也有因果影响包,我们稍后会用到。
在这篇文章中,我想概述因果影响是如何工作的,以及这个包如何帮助我们一个数据科学家。
让我们开始吧。
因果影响
概念
因果影响概念是为了解决干预或行动对时间序列数据产生影响的问题。在现实世界中,有很多情况下,我们拥有的唯一数据是我们的实验结果,而不是我们没有做任何实验或干预的数据。
让我用下面的例子来说明我在上面的文章中的意思。

作者创造的形象
回到我们以前的想象,当我们有一个新的活动来增加公司收入时,我们唯一的数据将是活动后的收入,而不是活动没有发生时的收入。
活动结束后,收入可能会有变化,但我们能确定这是因为活动而不是其他原因吗?毕竟,如果这场运动没有发生,我们就没有数据。
这就是因果影响可以帮助我们评估我们的假设。因果影响处理问题的方式是通过使用贝叶斯结构时间序列 来拟合最佳解释【预干预】数据(在我们的示例中是活动发生之前的数据)的模型。最佳模型用于“后干预”(活动后)数据,以预测如果干预从未发生会发生什么。
因果影响提到,模型将假设响应变量(预期目标,如收入)可以通过线性回归与不受时间干预影响的其他变量(“协变量”或 X)一起建模。例如,在我们的例子中,我们想评估一个活动对收入的影响,那么它的每日访问量不能作为协变量,因为访问量可能会受到活动的影响。
然后,评估因果影响的推断将基于观察到的响应与预测的响应之间的差异,这将产生干预对数据造成的绝对和相对预期影响。
如果我在一个图像中总结因果影响过程,它将如下图所示。

作者图片
编码示例
我们可以用数据集例子来尝试因果影响分析。我们用下面的代码试试吧。
import numpy as np
import pandas as pd
from statsmodels.tsa.arima_process import ArmaProcess
from causalimpact import CausalImpact#Creating Random Generated time data
np.random.seed(12345)
ar = np.r_[1, 0.9]
ma = np.array([1])
arma_process = ArmaProcess(ar, ma)
X = 100 + arma_process.generate_sample(nsample=100)
y = 1.2 * X + np.random.normal(size=100)
y[70:] += 5#Data Frame with time index
data = pd.DataFrame({'y': y, 'X': X}, columns=['y', 'X'])
dated_data = data.set_index(pd.date_range(start='20180101', periods=len(data)))#Time period for pre-intervention and post-intervention
pre_period = ['20180101', '20180311']
post_period = ['20180312', '20180410']dated_data.head()

作者创建的图像
现在,我们有一个包含两列的数据帧, y 和 X 。使用因果影响,要评估的预期目标应该重命名为 y,其他列将被视为协变特征。
要将时间分为干预前数据和干预后数据,您应该使用 DateTime 对象设置数据的索引,并创建两个包含时间范围的列表(干预前和干预后)。
让我们尝试使用所有这些信息进行因果影响分析。
#Using Causal Impact analysis, the three parameter you should fill (data, pre-intervention time, post-intervention time)
ci = CausalImpact(dated_data, pre_period, post_period)
ci.plot()

作者创建的图像
当我们绘制结果时,有三个图作为默认输出:
- 上面部分是原系列对其预测的一个
2.中间部分,也就是点特效剧情;原始序列和预测序列之间的差异
3.底部是累积效果图。这是随着时间累积的点效应的总和。
如果你不确定这个情节在告诉我们什么,我们可以通过观察累积效应,看到一个因为时间干预事件而增加的效应。
出于考虑,因果影响建议我们将先验参数设置为None,就像下面的代码一样,让模型进行所有的优化。
#Add prior_level parameter
ci = CausalImpact(dated_data, pre_period, post_period, prior_level_sd=None)
ci.plot()
如果您想查看所有因果影响的统计结果,您可以运行下面的代码。
print(ci.summary())

作者创建的图像
对于上面的例子,我们可以看到绝对效应是 4.78,其中预测可以在 95%置信区间内从 4.17 变化到 5.38。这意味着干预发生时增加了 4.78 个单位。虽然,在做任何结论之前,不要忘记检查 p 值。
如果您需要更详细的结果,您可以运行报告摘要代码。
print(ci.summary(output='report'))

作者创建的图像
在这种情况下,您将获得关于时间干预效果的所有信息。
如果您需要更深入的分析,您可以运行这段代码来获取每个时间点的预测信息。
ci.inferences.head()

作者创建的图像
这里,所有预测信息都汇总到一个数据框对象中。如果您想摆弄这些数据,您可以创建自己的可视化。
另外,如果您想要分析模型,您可以运行这段代码。
ci.trained_model.params

作者创建的图像
如果您觉得模型参数无法帮助模型预测响应变量,您可能需要评估该参数。
结论
作为数据科学家,我们可能需要评估事件对时间序列数据的影响。虽然可以使用简单的统计数据来评估,但具有挑战性的部分是确认事件是否实际影响了结果。
在这种情况下,我们可以使用称为因果影响的分析来解决影响问题。这个软件包易于使用,如果干预影响了响应变量,它会产生所有的结果来支持你的假设。
如果您喜欢我的内容,并希望获得更多关于数据或数据科学家日常生活的深入知识,请考虑在此订阅我的简讯。
如果您没有订阅为中等会员,请考虑通过我的推荐订阅。
单应估计

单应概念可视化【作者图片】
[让我们知道系列] — #1
这篇短文描述了估计一个 3 × 3 单应矩阵的方程。我们首先讨论从内部和外部摄像机参数的计算;必要时,将公式与现实世界的相机规格联系起来。接下来,我们提出使用对应点的二元组的计算方法,这些点在它们各自的平面中是共面的,并且避免了共线退化。
1 ⌉ 来自摄像机参数的单应
a .基本设置
让我们把 3D 世界视图空间中的一个点看作一个三元组

3D 世界视图通过放置在世界视图坐标中的照相机被捕获到 2D 图像上。
然后,我们可以将这个 3D 点映射到任意空间中的一个点,如下所示:

情商。(1)
其中C_ int分别是内在和C_ ext是外在相机矩阵。任意空间中的点(_ a,y_ a,z*_ a),可以通过涉及如下比例因子映射到*图像空间:**

情商。(2)
因此,一旦我们在任意空间中有了一个点,我们可以简单地缩放它的坐标,以获得(捕获的)图像空间中的 2D 坐标。
b .内禀矩阵
现在让我们看看C_ int的形式。假设一台相机,其焦距为 f (单位为 mm),实际传感器尺寸为(x_ S,y_ S)(单位为 mm),捕获图像的宽度和高度(有效传感器尺寸)为( w,h )
相机的光学中心(o_x,oy)然后是(w/2,h 【T 我们现在的状态指定C*** int如下:*****

情商。(3)
由此可以观察到, C _int 中的所有条目都是以像素为单位的。以下可视为分别在 x 和 y 方向上的有效焦距,单位为像素

实际上,它们是相同的,因为当从相机规格计算时,它们有非常微小的差异(≈ 0.2 到 0.5 %)
c .非本征矩阵
C _ext 由旋转矩阵 R 和平移矩阵 T 组成,如下:

情商。(4)
元组(T_ x,T_ y,T_ z)表示摄像机在世界空间坐标中的平移。通常情况下,我们可以认为摄像机没有 x 和 y 平移(T_ x=T_ y= 0),摄像机位置离地面的高度(单位为 mm)等于 T****
如果 θ , φ , ψ 分别是相机相对于 x , y 和 z 轴的方位(以弧度为单位的角度),我们可以得到 r _ ij i , j ∈ {1,2,3}如下:

情商。(5)
d .单应
要估计的单应矩阵 H 是一个 3 × 3 的矩阵,并且包含如下的内在和外在摄像机矩阵的部分:

情商。(6)
以上可以直接成立的事实是,当我们在 world-view 中寻找一个平面来计算单应性时,Z_ w= 0,因此,

情商。(7)
因此,单应性H* 将在任意空间中映射一个世界观点。如果我们只需要计算任意两点之间的距离,这个空间就足够了。然而,实际上,像素空间中的坐标将通过考虑等式 1 中指定的比例因子来计算。(2).*
共面点的 2⌉单应
a.基本设置
单应性让我们将两个观看同一平面的相机联系起来;相机和它们观察(生成图像)的表面都位于世界视图坐标中。换句话说,两个 2D 图像通过一个单应* H 相关,如果两者从不同的角度观察同一平面*。单应关系不依赖于正在观看的场景。**
考虑两幅这样的图像在世界视图中观看同一平面。
设(x_1,y_1)为第一幅图像中的一点,(xˇ_ 1,yˇ_ 1)为第二幅图像中的对应点。然后,这些点通过估计的单应 H 相关联,如下:

情商。(8)

情商。(9)
因此,第一图像中的任何点都可以通过单应映射到第二图像中的对应点,并且该操作可以被视为图像扭曲的操作。
b .单应性
让我们将 3 × 3 单应矩阵 H 参数化如下:

情商。(10)
因此, H 的估计需要估计 9 个参数。换句话说, H 有 9 个自由度。如果我们选择两个元组的对应点,**【☟】**【共面】**在它们各自的平面上,如下:

情商。(11)
*[**co-planar**] The homography relation is provable only under the co-planarity of the points, since everywhere, we are assuming that the *z*-coordinate of any point in any image is 1\. In practice, for instance, one may thus choose four points on a floor, or a road, which indicate a *nearly* planar surface in the scene.*
然后,从 Eq。(8,9,10),我们可以解出下面的估算值😗*

情商。(12)
其中(xˇ_I,yˇI)∈Tˇ 1 和(x_ I, 【T29)然后,这将转化为要求解的以下方程组:**

情商。(13)
我们现在有 8 个方程,可以用来估算(除了H_33)的 8 个自由度。为此,我们要求上面的 8 × 8 矩阵具有满秩(没有冗余信息),也就是说,没有一行是线性相关的。这暗示着在 T _1 或者Tˇ_ 1 中没有三个点应该是共线的。**
我们接下来需要解决的是 h _33。请注意,在等式中。(13)如果 h _33 被预先等同于 1,我们将简单地把整个一组h_ ij超平面移动到另一个参考系,但是它们的方向不会改变。实际上,我们将因此简单地看到不同的z_ a值,同时根据 Eq 映射一个2D* 图像坐标。(8),它随后会在等式中被分开。(9).于是,我们在*中保持 h _33 = 1,而 Eq。然后可以使用最小二乘估计来求解(13)。**
在 OpenCV 中,可以使用函数find homo graphy,它的功能与上面描述的完全相同。它接受四个对应点的二元组,用_ 33 计算单应性 H 始终且严格为 1。任何一个 2D 图像点都会被映射到一个z_ a**放大版的另一个平面上的对应点上。**
c .假设摄像机的单应性
在各种应用中,例如虚拟广告、用于智能城市规划的绝对距离测量,需要假设存在假设的摄像机【C】,并计算单应矩阵,该单应矩阵可以将观察到的场景中的任意点投影到由 C 捕获的图像的平面上。
想象鸟瞰图(俯视图)中的 C 是一个☟热门选择**在这种情况下,可以选择在被观察场景中具有四个共面点的_ 1,而对应的元组tˇ_ 1 可以简单地具有四个点作为假想矩形的角场景中的任何一点都可以映射到它的鸟瞰图上,也就是说,从上面看它会是什么样子。
**[**popular choice**] There has been a recent surge of research papers, which exploit the bird's eye view (BEV) for behavioural prediction and planning in autonmous driving.**
请注意,基于单应性的映射只是观察到的图像的扭曲版本,并且场景中没有新的信息被合成。例如,如果我们只观察了场景中一个人的正面视图,它的鸟瞰视图,不会真正开始告诉我们这个人的头发是如何从顶部开始的;但这只会扭曲他头部的正面可视部分,使其看起来像俯视图。
d .单应投影中的负值
注意,在求解 H 时,没有约束任意空间中的投影点必须为正,即x_a、ya和z【T47 _a可能为负。一旦被za缩小,这将意味着一个映射点(xˇ_ I、yˇ_ I)可能是负数。****
这从直觉上看可能是不期望的,因为图像坐标通常被认为是正的。然而,这可以被看作仅仅是参考轴偏移,并且在映射整个图像之后,可以适当地决定偏移量。
****💡 Remark** - The treatment presented here, *may not be* akin to *3D* reconstruction procedures, which may involve estimation of multiple-view homographies', sometimes via a hypothesized view projection. Multi-view homographies, have been shown to possess specific algebraic structures, but *3D* reconstruction from *2D* scenes largely remains an unsolved problem.**
根据纽约市自行车份额数据估计自行车的可用性
使用熊猫来重组花旗自行车的行程记录,以估计一个车站全天可用的自行车数量
自行车共享系统要想取得成功,会员必须随时随地都可以使用自行车。对于像纽约市的 Citi Bike 这样使用带码头的固定站点的系统,在骑行结束时必须有空码头,以便会员可以归还自行车并上路。
自行车和空码头的可用性是任何自行车共享系统的有用指标。虽然 Citi Bike 没有提供这方面的信息,但可以根据现有的出行数据进行估算。在本文中,我将展示如何做到这一点。

宾州车站附近的自行车站——图片由作者提供
如何确定自行车(和码头)的可用性?Citi Bike 以网络服务的形式提供了一个关于站点状态的实时反馈,包括可用自行车和停靠站的数量。在花旗自行车系统数据页面上可以找到描述和 URL 的链接。
获得历史数据更成问题。使用电台状态信息需要编写一个程序,定期查询节目源并保存信息。这在过去已经完成并可用,例如在theopenbus.com,但自 2019 年 4 月以来一直没有更新。
然而,Citi Bike 每月提供 tripdata 文件,记录每次行程,包括起止时间和站点。使用这些数据来估计自行车的可用性是可能的。
本文是探索花旗自行车提供的自行车份额数据系列的一部分。其他文章包括:
探索纽约市自行车共享数据 —介绍使用 Jupyter、Python、Pandas 和 Seaborn 进行数据准备和分析。
内插纽约自行车共享数据以发现再平衡运动-解释如何使用花旗自行车为每次骑行提供的数据来发现自行车何时在骑行之间移动以进行再平衡。
估计自行车和码头的可用性
在每次旅行中,一辆自行车在一个车站上车,在另一个车站下车。当一辆自行车被取走时,起点站就少了一辆自行车;当它被放下时,在终点站还有一辆自行车。**
为了表示这个活动,我首先阅读一个月的 tripdata 文件。我把它分成两个数据帧:一个用于接送,一个用于接送。然后,我将两个数据帧连接起来,并按照旅程开始的时间进行排序。这提供了每个站点的所有活动,因此我可以看到自行车数量如何波动。
然而,这种方法有两个问题:
- 月初车站里自行车的数量还不知道。
- 它没有考虑到重新平衡、因维修而移除的自行车或添加到系统中的自行车。
但这些都不是不可克服的问题。
下载数据和导入库
从下载并解压一个月的数据开始。Tripdata 文件可以从上面链接的数据页面下载,该页面还包括可用列的描述。在这篇文章中,我使用了 2020 年 9 月的数据。
在 Linux 命令提示符下输入以下命令:
mkdir bikeshare && cd bikeshare
wget [https://s3.amazonaws.com/tripdata/202009-citibike-tripdata.csv.zip](https://s3.amazonaws.com/tripdata/202009-citibike-tripdata.csv.zip)
unzip 202009-citibike-tripdata.zip
rm -f 202009-citibike-tripdata.zip
在您的bikeshare目录中创建一个 Jupyter 笔记本。这里用到的所有代码都可以在 GitHub 上找到,名为avail ability . ipynb。
导入所需的库:
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter
将 tripdata 加载到数据帧中。九月对花旗自行车来说是一个繁忙的月份,实际上是迄今为止最繁忙的月份,有近 200 万次骑行,所以为了减少数据帧的大小,我只阅读我需要用usecols的列。我希望开始和结束时间值被读取为datetime值,所以对它们使用parse_date。
df = pd.read_csv('~/bikeshare/202009-citibike-tripdata.csv',\
usecols=['starttime','start station id',\
'stoptime','end station id'],\
parse_dates=['starttime','stoptime'])
df.info()

调整资金组合
为了确保在需要时有自行车和码头可用,Citi Bike 通过将自行车从有剩余的车站转移到需要的地方来“重新平衡”自行车。虽然这些运动中的大部分不会出现在 tripdata 文件中,但是可以通过在系统中骑自行车来推断它们。当一辆自行车似乎从一个站点移动到另一个站点而没有骑手时,我认为这是一次重新平衡(或维护)的移动。
参见我的文章内插纽约市自行车共享数据以发现再平衡运动,了解确定这些运动发生的时间和地点的方法。我创建了一个拼花文件,其中记录了每次移动,格式与实际乘坐相同。
要从 Python 中读取一个 Parquet 文件,请安装 pyarrow 库:
*conda install -c conda-forge pyarrow*
您可以按照那篇文章中的描述创建文件,并在本地读取它,或者从我的 Github 存储库中读取一个保存的副本:
dfrebal = pd.read_parquet \ ('[https://github.com/ckran/bikeshare/blob/main/202009-citibike-reblance.parquet](https://github.com/ckran/bikeshare/blob/main/202009-citibike-reblance.parquet)')
该文件具有与 tripdata 文件相同的列和 39,560 个条目,这表示找到的重新平衡移动的数量。

将 tripdata 文件中的游乐设备与再平衡运动连接起来,使它们都在一个数据帧中。
df = pd.concat([df,dfrebal])
df.reset_index(drop=True, inplace=True)
df

从旅行到行动的重组
通过创建两个数据帧,将一行中的每个行程(包括开始和结束时间及站点)拆分为两行,并增加一列act来指示发生的动作:
dfs(拾音器)starttime、start station id和act设置为-1dfe(下降)stoptime、end station id和act设置为+1
dfs=df[['starttime','start station id']].assign(act=-1)
dfe=df[['stoptime','end station id']].assign(act=1)
重命名中的列,使它们匹配并将两个数据框架连接成一个新的数据框架。
dfs.columns=['docktime','stationid','act']
dfe.columns=['docktime','stationid','act']
dfse=pd.concat([dfs,dfe])
按时间排序
最后,按docktime对数据帧进行排序;删除并重新创建索引。
dfse.sort_values(by=['docktime'], inplace=True)
dfse.reset_index(drop=True, inplace=True)
dfse.head(100)
现在,所有的接送都按照发生的顺序进行:

车站开始和停止
可视化车站流量
使用花旗自行车时,唯一真正重要的自行车(或码头)可用性是你所站的车站。这种分析对单个站最有用。
以前,我在一家软件公司工作,办公室就在宾夕法尼亚车站的正上方。我最熟悉的花旗自行车站在第七大道对面,名为 W 31 St & 7 Ave. 在早高峰期间,它基本上是空的,因为通勤者会在佩恩站从他们的火车上下来,拿起自行车,很快清空了所有的码头。白天,骑手们来来往往,但通常只有几辆自行车。在下午高峰期间,花旗自行车站会挤满人,因为通勤者会在进入火车站之前归还他们的自行车。事实上,当我下班后去那里取自行车时,总有人在等着码头开放。我想看看那个车站的用法图是否能反映我的经历。
获取电台名称
虽然 tripdata 文件包含每次旅行的车站名称,但我没有将它们包含在 dataframe 中,因为这样做会使 data frame 非常大,因为几乎有 200 万行,但现在我需要它们。
要在我的图表中包含站点名称,请创建一个数据框架作为查找表,每个站点占一行(大约只有一千个站点)。
dfstations = \
pd.read_csv('~/bikeshare/202009-citibike-tripdata.csv',\
usecols=['start station id','start station name']).\
drop_duplicates()
dfstations.columns=['stationid','station name']
dfstations.set_index('stationid',drop=True, inplace=True)

电台 ID 和名称查找表
为一个站创建一个数据帧
首先从dfstations数据帧中获取感兴趣站点的站点 ID。然后创建一个新的数据帧,只包含该站的行。
sid = dfstations.loc[dfstations['station name']\
=='W 31 St & 7 Ave'].index[0]dfstation = dfse.loc[(dfse.stationid==sid) ]
dfstation.reset_index(drop=True, inplace=True)
计算一个站点的自行车数量
通过计算act列的累计和,我可以看到车站自行车数量的增加和减少:
dfstation = dfstation.assign(cnt = dfstat.act.cumsum())
dfstation.head(10)

运行总和的车站活动
从 9 月 1 日午夜开始,它显示自行车的数量随着骑车人放下自行车而增加,随着他们捡起自行车而减少。问题是cnt从零辆自行车开始,这是不准确的。随着越来越多的自行车被捡起来,数量将下降到零以下,这当然是不可能的。
我可以通过计算曾经达到的最小数字并将其添加到第一个单元格来避免这种情况。这个数字是猜测,但总比没有强。然后重新计算。
dfstation.at[0, 'act'] =+ abs(dfstation.act.cumsum().min())dfstation = dfstation.assign(cnt = dfstat.act.cumsum())
dfstation.head(10)

该月的绘图可用性
现在,我可以创建一个图表,显示这个站点九月份的可用自行车数量。每当地块触底时,车站是空的,当它触顶时,车站是满的。
sns.set(font_scale = 1)
plt.figure(figsize=(20,5))
ax=sns.lineplot(data=dfstation , x='docktime', y='cnt' )
ax.set_xlabel('Day')
ax.set_ylabel('Available Bikes') ;

W 31 St&7 Ave2020 年 9 月使用——图片由作者提供
月图显示了可用性的变化,但它太压缩了,我看不出这是否是我所期望的。一天一天地看可能更好。
绘制一天的可用性
我选择了 9 月 8 日,那天是劳动节的第二天。我使用DateFormatter将日期值格式化为一天中的小时。
sns.set(font_scale = 1)
plt.figure(figsize=(20,8))
ax=sns.lineplot(data=dfstation.loc[dfstation.docktime.dt.day == 8],\
x='docktime', y='cnt' )
ax.set_ylabel('Available Bikes')
ax.set_xlabel('Hour of Day')
ax.set_title('Citi Bike station - '+station+' - September 8, 2020')
ax.xaxis.set_major_formatter(DateFormatter("%H")) ;

一个站点一天的站点使用量
这显示了车站在一天中的大部分时间都是空的或接近空的,直到下午它经常是满的。这是我所期望的。
然而,这个车站靠近一个交通枢纽。位于商业区或居民区的车站呢?
其他站点的可用性
要查看其他站,请将代码放入函数中:
def availabilty (station,day):
# inputs: station name, day
# requires: dfstations, dfse
sid = dfstations.loc[dfstations['station name']==station]\
.index[0] # lookup station id
dfstation = dfse.loc[(dfse.stationid==sid) ]
dfstation.reset_index(drop=True, inplace=True)
dfstation = dfstation.assign(cnt = dfstation.act.cumsum())
dfstation.at[0, 'act'] =+ abs(dfstation.act.cumsum().min())
dfstation = dfstation.assign(cnt = dfstation.act.cumsum())
# Create chart
sns.set(font_scale = 2)
plt.figure(figsize=(20,8))
ax=sns.lineplot(data=dfstation.\
loc[dfstation.docktime.dt.day == day],x='docktime',y='cnt' )
ax.set_ylabel('Available Bikes')
ax.set_xlabel('Hour of Day')
ax.set_title\
('Citi Bike Station - ' + station +' - September ' + str(day))
ax.xaxis.set_major_formatter(DateFormatter("%H")) ;
首先,我将在洛克菲勒中心的一个电视台播放,这是一个大型会议酒店附近的大型办公楼群。加油站在早上六点到九点之间加满油,一天的大部分时间都是满的,下午五点后很快就空了,一天的其余时间几乎都是空的。

洛克菲勒中心的花旗自行车——作者图片
接下来,我将查看一个大型公寓楼附近的一个车站的主要居民区。那个车站一夜爆满,在早高峰和全天都是空的,然后在晚高峰又被填满。

施托伊弗桑特镇附近的花旗自行车——图片由作者提供
结论
尽管 Citi Bike 不提供有关自行车和码头可用性的历史数据,但可以从反映实际站点使用情况的 tripdata 文件中得出估计值。
用因果影响 BSTS 估计金融时间序列的因果影响
简要介绍 Google 的 Python 因果影响库及其在估计金融时间序列因果影响中的效用。

作者图片
注来自《走向数据科学》的编辑: 虽然我们允许独立作者根据我们的 规则和指导方针 发表文章,但我们不认可每个作者的贡献。你不应该在没有寻求专业建议的情况下依赖一个作者的作品。详见我们的 读者术语 。
介绍
量化和解释已知事件对因变量的影响的能力是一种数据科学技能,其效用适用于无数学科。然而,这种分析的影响及其影响商业决策的能力(尽管分析本身是真实的!)的能力与数据科学家/分析师/定量分析师合理选择模型及其决策的能力,以及必要的领域专业知识和对因变量的理解一样好。
谷歌的因果影响库(用 R 和 Python 实现)可以帮助我们在很短的时间内完成这样的任务,同时提供方法让用户能够充分解释底层建模过程和模型的决定。
在本文中,我们将简要探讨如何实施因果影响模型来估计淡水河谷大坝坍塌对铁矿石现货价格的影响。你可以在这里找到本文中的所有代码。
什么是因果影响?
Google 的因果影响库提供了一个非常简单的结构化时间序列模型的实现,该模型可以估计“设计”干预对目标时间序列的影响。通过分析预期和观察到的行为之间的差异来衡量这种影响——具体而言,该模型生成一个预测反事实,即预期观察值,说明如果事件未发生,因变量可能会如何在事件*之后演变。*****
它是如何工作的?
最初是作为 R 包开发的,因果影响通过将贝叶斯结构时间序列(BSTS)模型拟合到一组目标和控制时间序列观察值来工作,并随后对反事实进行后验推断。
作为参考,结构化时间序列模型是时间序列数据的状态空间模型,可以根据以下一对等式来定义:

结构时间序列模型定义。作者图片
在上面的表达式中:
- Eq 1。就是观测方程。这将观测数据 y_t 与一个潜在的 d 维状态向量α_t 联系起来。
- 等式 2。是状态方程;它决定了状态向量αt 随时间的演化。换句话说,阿尔法变量指的是时间序列的“状态”, y_t 是状态的线性组合,加上一些解释协变量 x 的线性回归,加上一些噪声的ε,ϵ,正态分布在平均值 0 附近。
- ε_ t和η_ t独立于所有其他未知量。**
在这个阶段,值得注意的是 BSTS 模型和传统统计/深度学习变体之间的一个关键差异:
传统的时间序列预测架构,如线性回归模型,通过最大似然估计来估计其系数,或者,在更强大的一端,LSTM 学习一个函数,该函数将一系列过去的观察值作为输入映射到输出观察值。另一方面,BSTS 模型采用概率方法来模拟时间序列问题,即,它们返回一个后验预测分布,我们可以对其进行采样,不仅提供预测,还提供一种量化模型不确定性的方法。****
这为什么有用?
贝叶斯结构时间序列模型是特别强大的模型,因为它们可以推广到一个非常大的时间序列模型类,如 ARIMA,萨里马克斯,霍尔特温特斯等。在上述表达式中,您可以观察到我们如何通过改变矩阵 X、Z、T、G 和 R 来实现这一点,以便在观察到的时间序列中模拟不同的行为和结构,以及添加线性协变量βX,这也可能是因变量的预测。
示例——二阶自回归过程:
考虑一个例子,我们想要对一个时间序列的时间结构(自相关)进行建模。我们可以将时间序列建模为二阶自回归过程 AR(2),通过调整上面的表达式!:
AR(2)过程的定义:

二阶自回归过程定义。作者图片
状态空间形式:

状态空间形式的 AR(2)过程。作者图片
鉴于这种选择任何时间序列模型来拟合数据的灵活性,人们可以很快看到这些模型有多么强大。
但是这听起来工作量很大?
嗯,是的,但是不要害怕。关于因果影响的奇妙之处在于,如果你需要一个权宜的初步分析,你不必明确定义模型的任何结构组件。
如果确实是这种情况,并且您没有在输入中指定模型,则默认情况下会构建一个本地级别的模型,该模型会为您估计时间序列的主要结构组件。对于局部水平模型,目标时间序列 y 被定义为:

本地级模型。作者图片
这里,给定时间点被建模为随机游走分量 mu_t(也称为局部水平分量)。趋势和季节性成分、gamma_t 被建模为未观察到的成分。趋势被建模为固定截距和季节分量,使用具有固定周期和谐波的三角函数。关于更详细的数学解释,可以参考因果影响文档 此处 ,以及 S tatsmodels 状态空间季节性文档 ,其逻辑遵循因果影响的 Python 变体的逻辑。
总之,我们的实现(在 Python 中)因此被简化为一行表达式!:
ci_model = CausalImpact(target, pre_period, post_period)
对于那些对如何使用 TensorFlow 详细构建贝叶斯结构时间序列感兴趣的人,你可以在本文中看到如何:****
否则,我们来看看行动中的因果影响。
问题是
我们将探讨如何在估算淡水河谷大坝坍塌对铁矿石现货价格的影响时实施因果影响。虽然这一事件并不构成“设计好的”干预,但在金融界,在提供对未来类似性质事件的价格变动的估计方面,效用仍然存在。此外,这是为了展示谷歌的因果影响包在估计一个事件对反应时间序列的影响的效用。提醒一下,Python colab 笔记本的链接可以在这里 找到 。
淡水河谷大坝事件
在这种情况下,我们正在估算 2019 年 1 月 25 日发生在淡水河谷(全球最大的铁矿石生产商)córrego do feijo 矿的淡水河谷大坝事件对铁矿石现货价格的影响。
****警告:虽然对任何金融时间序列的结果进行建模通常是一个高度复杂的非线性问题,需要比我们正在演示的更多的考虑和技术应用,但本演示的目的是说明 CI 可以执行这种分析的效用和便利性,及其可解释性——这是任何商业环境中的先决条件。
铁矿石现货价格数据
我们首先获取铁矿石现货价格数据,然后绘制铁矿石现货时间序列的收盘价。我们还创建了 21 天和 252 天的滚动平均值,为我们提供当前价格变动的方向指引:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pdfrom causalimpact import CausalImpact
from statsmodels.tsa.seasonal import seasonal_decompose# Get spot prices
df = pd.read_excel(
'/Users/CMP/OneDrive/PriceCurves/IO_Spot_2015010120200501.xlsx',
index_col='date'
)
# Plot close price, 21d and 252 roll avg.
df['close'].plot(lw=2., figsize=(14,6), label='Close Price',c='royalblue')
df['close'].rolling(21).mean().plot(lw=1.5, label='Roll_21d',c='orange')
df['close'].rolling(252).mean().plot(lw=1.5, label='Roll_252d', c='salmon')
plt.title('Spot Iron Ore Historical ($/MT), 2015-2020')
plt.ylabel('Close Price, $/MT')
plt.grid(); plt.legend()

铁矿石现货价格和 2015-2020 年 21d/252d 滚动平均价格。作者图片
淡水河谷大坝事件
到目前为止一切顺利。在定义事件前和事件后的时间段之前,我们可以通过标记事件日期并绘制图表来更好地了解事件本身的规模:

铁矿石现货收盘价注释。作者图片
在上图中,您可以观察到两个事件:
- ****第一个数据点表示 Vale dam 崩溃的日期。
- 第个数据点/事件是淡水河谷对其一座尾矿坝稳定性的警告。
开始之前—模型假设:
与所有预测问题一样,在将任何模型应用于我们的问题之前,我们必须充分考虑它所做的假设。
- 控制集合**:在有因果影响的情况下,模型假设存在一个集合控制时间序列,它是受事件影响的 而不是 本身。在我们问题的背景下,这个假设并不严格相关。
- 外部协变量:另外一个假设是与相关,然而,该模型假设外部协变量和目标时间序列之间的关系,如前期所建立的,在后期保持稳定。在我们的问题背景下,很有可能的是,可以预测铁矿石价格的特性也很可能受到该事件的影响。
- ****模型先验:未观测状态的先验分布会有所不同,这取决于您是否选择在 R 或 Python 中实现因果影响。因果影响的 R 变量基于目标系列的第一次观察和数据集的方差来设置未观察状态的先验。另一方面,Statsmodels 使用漫射(均匀)先验。这不应该导致两者之间的任何重大差异,但是值得记住(参见观察、批评&对 R 和 Python 包之间的关键差异的进一步分析)。
默认模型
有了上面的基本信息,让我们将基本模型拟合到我们的现货价格数据,并检查输出。实施简单明了:
# Define training data - period prior to the event
pre_period = ['2016-01-04', '2019-01-24'] # Define post-event period - i.e. time AFTER the event occurred.
post_period = ['2019-01-25', '2019-08-01'] # Instantiate CI model.
ci = CausalImpact(df['close'], pre_period, post_period)
注意:在指定事件前和事件后期间的界限时,因果影响可以(方便地)接受日期字符串。
将因果影响模型拟合到我们的数据会返回一个对象,调用该对象的方法时,我们可以检查模型结果、拟合的参数等。现在,我们将绘制结果:

因果影响结果,包括预测的反事实、逐点和累积影响。图片作者。
正如您在上面的图表中所看到的,通过调用 CI 对象上的.plot()方法,我们可以访问拟合过程的结果。默认情况下,plot 方法呈现三个独立的图表:
- ****观察到“事后”时间序列,并拟合模型的预测反事实
- 逐点 因果效应,由模型估计。这是观察结果和预测结果之间的差异。
- ****累积效应。
此外,通过调用 CI 对象的.summary()方法,我们可以生成一个方便的摘要报告:
print(ci.summary())

因果影响模型:总结报告。图片作者。
结果评估
对上述输出的检查揭示了拟合模型的结果:
- ****平均栏是指干预后期间的平均价格(在一段时间内)。
- ****累积列是个人每日观察的总和——在我们的例子中不是很有用,但是如果因变量是一个度量标准,其累积总和是您实验的一部分,则非常有用;额外的销售、点击等。
粗略地看一下预测的反事实和逐点效应表明,这种规模的事件对现货价格产生了重大影响。事实上,该模型自信地断言,淡水河谷大坝事件的绝对因果效应为 21 美元,从 18.04 美元到 23.98 美元不等。注意这里的 p 值也很重要(< .05) to understand whether the observed behaviour is statistically significant or simply occurred by chance.
拟合模型诊断
然而,显而易见的是,我们的默认模型的预测反事实看起来不太令人信服。在很大程度上,它看起来是方向准确的,但是,它显然没有捕捉到显著的价格变动,并且预测似乎落后于观察到的现货价格。
我们可以检查拟合模型的参数和诊断,以评估模型是否符合其基本的统计假设:
ci.trained_model.summary()

图 1:拟合的模型结果和参数。图片作者。
# Plot residuals diagnostics
_ = ci.trained_model.plot_diagnostics(figsize=(14,6))

图 2:拟合模型的残差。图片作者。
对上面图 1 的检查显示了拟合模型的参数。对单个结果的完整解释超出了本文的范围,但是突出的要点,即模型组件;sigma2 .不规则和 sigma2 .水平以及它们的系数表明,它们对我们的目标铁矿石现货价格的预测是多么微弱。
事实上,如果我们考虑图 2 和残差图,我们可以检查模型误差的大小,“c 的标准化残差”。我们还可以观察到误差遵循明显的非正态分布,并表现出很强的自相关性。然而,我们已经有效地建立了一个基线模型来估计事件对我们目标变量的影响。
定制模型
我们刚刚实施了第一个因果影响模型,并估算了淡水河谷大坝事件对我们的铁矿石现货数据的因果影响。到目前为止,我们已经让软件包决定如何为我们的现货价格数据构建一个时间序列模型,并且发现,作为拟合模型的诊断结果,我们不能确信推断的效果。
如上所述,结构性时间序列模型的一个有用的特性是它们的模块化,为我们提供了建模时间序列的个体行为动态的灵活性,例如季节性。因果影响模块提供了几个选项,使我们能够实现这一点,我们将利用这些选项来改善我们推断的反事实。
季节性模型&具有外生特征(协变量)的模型
为了改进我们的模型和预测反事实,我们将利用我们的专业知识并调整我们的模型,以包括一个经常出现在现货铁矿石价格行为中的已知季节性因素,并纳入两个与铁矿石现货价格呈现已知线性相关性的特征:现货废钢和中国国产钢筋(螺纹钢):

铁矿石现货价格曲线增加了两个新特征(协变量);螺纹钢和废钢。图片作者。
添加季节性成分
我们将开始尝试在我们的模型中添加一个已知的季节性组件。对于许多大宗商品来说, 价格往往会在中国的夏季和冬季赶在春季和秋季建设高峰期之前上涨。在铁矿石中通常可以观察到同样的行为。作为参考,季节分量可以描述为周期性重复的模式,即信号是周期性的。****
通过将我们的时间序列分解成它的组成结构部分,我们观察了上述季节性对现货价格的影响程度:
**s_dc = seasonal_decompose(df['close'], model='additive', period=252).plot()**

分解的现货铁矿石时间序列。图片作者。
注意:Statsmodels seasonal_decompose 对我们的时间序列进行了简单的分解——更复杂的方法应该使用,特别是当我们的时间序列是金融时间序列时。然而,对于本文的目的以及演示如何将它们作为组件添加到我们的模型中,这就足够了。
隔离几个例子证实了我们先前的信念/领域专业知识;我们可以看到季节分量的频率和幅度与中国的夏季和冬季相对应;

2015 年和 2016 年孤立的季节性成分观测。图片作者。
看起来这个信号大概有 146 天的周期,谐波为 1,尽管这些都是粗略的解释。我们可以观察到,在两个例子中,冬季峰值的幅度小于夏季峰值的幅度,因此信号本身不是严格对称的。现在,出于演示的目的,我们将继续我们的假设。
将季节性因素纳入因果影响非常简单:因果影响类接受一个字典列表,其中包含每个季节性信号的周期性,以及已知的谐波:
**# Example - Adding seasonal components to a CI modelci = CausalImpact(
df['close'],
pre_period,
post_period,
nseasons=[{'period': 146, 'harmonics': 1}]
)**
添加外源协变量
我们现在可以将外部协变量添加到我们的模型中,即现货废钢价格和中国国内钢筋。
同样,将外部协变量添加到我们的因果影响模型很简单。这些可以作为 Pandas 数据帧与我们的目标变量一起传递。根据源代码,因果影响期望目标/标签列在第一个索引(0)中。所有后续列都注册为外部协变量:
**df_1 = pd.concat([df, steel_scrap_df, rebar_df], join='inner', axis=1)**

我们的价格曲线数据框。图片作者。
然后,我们将这个数据框架传递给我们的因果影响模型,以及相同的前期和后期事件定义和我们的季节性组件。请注意,我们不会指定季节分量的谐波:*
**# Fit new seasonal + beta coef variant
ci_1 = CausalImpact(df_1, pre_period, post_period, nseasons=[{'period': 146}])# Plot result
ci_1.plot(figsize=(12, 6))**
*模型默认将此计算为 math.floor(periodicity / 2))

预测反事实:季节性+外部协变量 CI 模型变量。图片作者。
在检查我们新模型的反事实时,我们可以观察到一个更可信的结果。看起来似乎我们仍然需要考虑时间序列中的时间(自相关)结构,并且可以将其包括在随后的模型中,但是结果对于第一遍来说似乎是可信的。在检查拟合模型的参数时,我们可以看到我们的季节成分及其定义需要重新评估,而我们的外部协变量解释了我们的响应变量中观察到的许多情况:

季节性+外部协变量模型—拟合参数。图片作者。
最后,我们可以让我们的模型使用有用的方法.summary('report')来估计 Vale dam 事件对我们目标的影响,该方法返回了对观察到的影响的详细解释:

季节性+外部协变量模型-报告。作者图片
在这里,我们可以看到,该模型得出的重要结论是,该事件导致铁矿石现货价格上涨+33%,因此,从逻辑上讲,如果该事件没有发生,那么现货价格在此期间将下跌 33%。
结论
在这篇文章中,我们已经了解了如何使用 Google 的因果影响包来估计干预对观察到的时间序列的因果影响。具体来说,我们实施了一个 CI 模型来估计淡水河谷大坝事件对铁矿石现货价格的影响。
此外,我们了解到:
- 什么是贝叶斯结构时间序列模型,它们的能力,以及在某种程度上,它们的局限性(见下文)。
- 谷歌的因果影响库是什么,它做什么。
- 如何实现基本/默认因果影响模型。
- 如何实现一个更复杂的变量,通过定义和添加已知的季节性因素和线性相关的外生变量作为线性协变量。
- 如何评价我们拟合模型的结果?
观察、批评和进一步分析
- 参数估计:根据您是在 R 还是 Python 中实现因果影响,您可能会发现您的模型返回完全不同的结果。这是各个库采用不同估计方法的结果:Python 变体采用 Statsmodels 不可观测组件实现来对目标时间序列建模,并且使用最大似然估计其参数。R 变体使用 BSTS 库,其参数用贝叶斯 MCMC 估计。
- 季节性:参见上述“添加季节性成分”下的限制
- 对架构能力建模:在干预之前,通过在人为干预上运行
CausalImpact()来检查目标变量能够被预测到多好是一个好主意。这是对模型捕捉假想干预后数据中相关结构的能力的健全性检查。从逻辑上讲,我们希望而不是发现显著影响,即反事实估计值和实际数据应该相当接近。**** - 模型假设:参见上面的“模型假设”。进一步注意,我们没有为几乎所有金融时间序列固有的自相关建模。
- 定制模型:请注意,在本文中没有介绍如何定义定制模型,这将是冗长的,超出了范围。然而,因果影响类的一个参数
model使我们能够将任何 Statsmodels 状态空间模型作为参数传递,这展示了 CI 库的强大和灵活性。
非常感谢您花时间阅读这篇文章,一如既往,我欢迎所有建设性的批评和意见。
参考
- 因果影响 1.2.1,布罗德森等人,《应用统计学年鉴》(2015),http://google.github.io/CausalImpact/
2。https://www . ft . com/content/8452 e078-7880-11e 9-bbad-7c 18 c 0 ea 0201
3.https://www . business insider . com . au/铁矿石-价格-季节性-2018-1
估计咖啡萃取物的粒度
咖啡数据科学
咖啡数据之路的更多探索
最近,我用用过的咖啡渣做了一个实验,以更精确地测量不同研磨设置的提取潜力。我还试图通过使用表面积体积比来估计提取潜力,知道颗粒尺寸分布。现在,我想我可以把这两条信息结合起来,看看我们能从不同粒度提取中了解到什么。
原始数据
首先,我在一个利基零研磨机上有 5 个设置,我使用一种成像技术测量它们的粒子分布。有一个设置似乎有点偏离或者容易出错,那就是设置 10。我对此没有很好的解释,但我们可以承认这种差异是一个警告。在数据收集期间,我必须回忆分布情况,因为照明问题可能是导致问题的原因。

在我的实验中,我确定了随着时间的推移,分成大致相等的 3 杯的弹丸的提取率(EY)。然后,我通过了解废磨粒控制样品的 EY 来修正测量值。我鼓励任何有问题的人首先阅读这篇文章。

将提取率应用于颗粒分布
之前,我研究过使用表面积与体积(S/V)的比率作为粒子提取速度的指标。无论粒子形状如何(球形、方形等),S/V 都是半径的一个因子。我还可以假设最大提取水平是 32%,因为这是我从设置 0 获得的最大提取。
然后,我用最大可能提取量计算了分布中每个箱的提取量。从那里,我得到了每个颗粒尺寸箱对最终提取的贡献(如下)。不足为奇的是,大部分估计的提取量来自较小的粒子。

我注意到一些有趣的模式,所以我用最大提取量来归一化累积估计提取曲线。由此看来,似乎区分提取的仅仅是颗粒大小。

然后,我查看了每个颗粒尺寸箱的估计 EY,并用粗线绘制了过度提取和提取不足的估计值。

需要对颗粒大小有更好的了解。用筛分过的颗粒尺寸代替研磨设置再做一次实验不会有什么坏处,这样可以增加另一个级别的控制,而且我认为这将有助于告知基于研磨机或研磨分布与其他圆盘准备或机器参数相比可以在哪里进行改进。
如果你愿意,可以在 Twitter 和 YouTube 上关注我,我会在那里发布不同机器上的浓缩咖啡视频和浓缩咖啡相关的东西。你也可以在 LinkedIn 上找到我。也可以关注我中。
我的进一步阅读:
用蒙特卡罗模拟法估计圆周率
如何只用几行代码模拟圆周率?

Taso Katsionis 在 Unsplash 拍摄的照片
说明如何使用蒙特卡罗模拟的一个常见例子是通过估计圆周率。本文将向您展示如何使用 r 用几行代码完成这一模拟。在编写代码之前,让我们先来看一下我们将用来估计圆周率的数学公式。

作者图片
找出圆周率的表达式
圆周率是一个数学常数,大约为 3.14159,表示圆的周长与其直径的比值。我们如何在模拟中找到一个表达式来计算圆周率的近似值?
我们知道圆的面积是由π* r 计算出来的,包围正方形的面积是(2r) = 4r。用圆的面积除以正方形的面积,我们得到两个面积的比值:

这意味着我们可以使用以下公式估算圆周率:

通过在这个正方形内模拟随机数,我们得到了圆形面积和正方形面积的近似值。因此,我们可以通过取落在圆内的模拟点与模拟点总数的比率并乘以 4 来估计圆周率。
为了识别哪些点落在圆内,我们使用圆的等式:

在我们的示例中,圆心为(0,0),半径为 1,因此满足以下标准的模拟点位于圆内。

为模拟编写函数
我们的函数将采用两个参数,种子用于再现性,以及迭代以便我们可以选择我们想要生成多少个模拟点。
该功能经历以下步骤:
- 设定种子。
- 从均匀分布中生成选定数量的随机点,最小值和最大值被设置为我们的域边界,这里为-1 和 1。这些随机数存储在两个向量中,一个用于 x 坐标,一个用于 y 坐标。
- 计算每个(x,y)点的 sqrt(x +y ),并将它们存储在一个向量中。
- 通过计算 sqrt(x +y ) ≤1 来计算圆内有多少个点。
- 用圆内的点数与总点数之比乘以 4 来估算 pi
- 返回估计圆周率的值
调用我们的 simulate_pi() 函数将返回种子 pi 的近似值以及传递给其参数的点数。请注意,由于函数中没有种子,由于生成了不同的随机数,模拟每次运行将产生不同的估计值。
将 seed 设置为 28 并使用 10,000 个随机点,得到的估计值为 3.1724。相当接近圆周率,但可能还不够接近到让我们满意的程度。让我们看看如何改进这一估计。
增加生成点的数量将改善我们的估计
我们可以使用 sapply()在一次调用中为不同数量的生成点估计 pi。
运行下面的代码给出 10,100,1,000,10,000,100,000 和 1,000,000 个模拟点的 pi 估计值。
正如我们所看到的,pi 的估计值随着点数的增加越来越接近 pi 的真实值。这说明了蒙特卡罗模拟的一个重要方面;仅使用少量随机点通常会给出较差的近似值。平均而言,随着点数的增加,近似值将会提高。
绘制估计的 pi 值
为了说明这一点,我们可以绘制圆周率的估计值,看看它是如何随着点数的增加而变化的。为了绘制该图,我们需要将每个估计值存储在一个数据框中。我们可以通过如下修改函数来做到这一点。
现在我们有了一个生成所需数据的函数。让我们
生成数据,并绘制从第一个点到 100,000 个点的 pi 近似值。

正如我们所看到的,pi 的估计值在第一次模拟中相当不准确,但随着模拟次数的增加,它越来越接近 pi 的实际值。
本文中使用的代码可以在这个 GitHub repo 中找到。
如果你喜欢阅读这样的故事,并且想支持我成为一名作家,考虑注册成为一名媒体会员。每月 5 美元,你可以无限制地阅读媒体上的故事。如果你注册使用我的链接,我会赚一小笔佣金。
https://medium.com/@andreagustafsen/membership
估计邻居级别的政治关系
没有电话,没有调查,没有网上投票——只有好的数据。

由 Unsplash 上的 Element5 数码拍摄
这篇文章提供了一个在邻里层面估计政治倾向的高层次概述。如需更深入的讨论,请查看随附的 Google Colab 笔记本 或最近的 Meetup 演示文稿 。
免责声明:我是 SafeGraph 公司的数据科学承包商,该公司的数据用于本项目。
介绍
政治派别是一个强大的变量,它与宗教极端主义、疫苗拒绝、对体罚的立场等因素相关联。研究人员用它来理解各种现象。企业用它来了解他们的市场。政治运动用它来赢得选举。
不幸的是,政治党派数据通常只能在县或区一级获得。虽然收入、种族、人口和教育等其他人口统计变量可以通过美国人口普查局在人口普查小组(CBG)一级获得,但政治派别却不能。注:平均而言,每个州由 67 个以上的社区团体组成。
因此,政治党派变量可能会忽略一个县或地区内的重要变化。例如,查看下图。当在县一级进行估计时,科布县(佐治亚州)稍微倾向于民主党(蓝色)。当在 CBG 层面进行估计时,很明显科布县的许多地方都倾向于共和党(红色)。

来自《纽约时报》的县级地图(左)。本文作者提供的人口普查区块组级地图(右)。
有了正确的数据和工具,我们可以很容易地做出这些估计。
材料
步行交通模式+投票地点/结果=政治派别
我们使用三个数据集和一个 API 来进行这些估计。对于本教程,代码和示例数据已在 my GitHub 上提供。
数据
- 投票地点(科布和佐治亚州福塞斯县):2020 年美国大选投票地点地址
- 选举结果(格鲁吉亚):2020 年美国大选投票地点结果
- 安全图模式:指示给定时间段内每个兴趣点(POI)的访客的家庭 CBG 的步行交通数据

使用 SafeGraph 模式,我们可以看到哪些人口普查区块组(cbg)的居民最常去威斯康星州戴尔的沃尔玛。
当我们将安全图模式与选举结果相结合时,我们可以将选举结果与 cbg 相关联。
一个问题是:由于地址格式不一致,连接 POI 数据很困难。幸运的是,我们可以使用 Placekey 来解决这个问题。

在两个不同的数据集中,相同的地址通常具有不同的格式。上面的行对应于同一个 POI,但是地址的格式不同。这使得在没有开放、唯一标准的情况下连接 POI 数据集变得极其困难。
连接数据
- Placekey :大规模连接兴趣点数据的免费工具,非常有用,因为大多数兴趣点数据集没有标准化的地址列(“AVE”vs“AVE .”vs“Avenue”,“NW”vs“n . w .”vs“northwestern”vs“NW”等)

位置键看起来像什么?
Placekey 是美国(以及加拿大,很快会有更多国家)每个 POI 的开放、唯一的标准标识符。当给定位置参数(地址数据或坐标/位置名称)时,Placekey API 返回一个 Placekey。
我们希望按选区将 Patterns visitor_home_cbgs列与选举结果对齐。然后我们可以估计每个 CBG 的政治倾向。
看看下面的图片,记住以下几点:
- 红色:选区 ID 列,用于合并选举结果和选区位置。
- 黄色 : Placekey 列,用于将辖区位置与 SafeGraph 模式合并。
- 蓝色:选举结果和访客住宅人口普查区块组列。所有这些合并的目标是将
visitor_home_cbgs与选举结果相匹配。

我们的三个数据集和用来连接它们的列。“选区选举结果”将与“选区位置”一起出现在“选区”栏(红色)。“辖区位置”将与“地点键”栏(黄色)上的“安全图表每周模式”相结合。我们的目标是使用相应的位置将模式‘visitor _ home _ cbgs’列与选举结果(蓝色)相匹配。 注意:必须使用 Placekey API 将“placekey”列添加到“分局位置”数据集中。
例如,给定下表中的选区投票总数,假设 CBG 123456789012是 A 区 50%的访问者、B 区 20%的访问者和 c 区 10%的访问者的家

虚拟选区的虚拟数据。
然后我们估计 CBG123456789012的总票数为:
Trump = .50*100 + .20*25 + .10*60 = 50 + 5 + 6 = 61Biden = .50*50 + .20*25 + .10*150 = 25 + 5 + 15 = 45
步伐
提醒: 详细讨论和代码在 GitHub 上。用附带的谷歌 Colab 笔记本再创作超级容易!
1)读入数据
相当简单——将 CSV 数据读入熊猫数据帧。我们在上面看到了三个数据集(选举结果、投票地点和安全图模式)的例子。
2)将投票数据与模式数据相结合
将选举结果与投票地点联系起来。 如上图所示,将选举结果数据与Precinct列的投票地点数据连接起来。
b. 向投票数据添加 Placekey 列。 使用 Placekey API ,为来自步骤 2a 的投票数据中的每个地址请求 Placekey。使用 Python 库处理 Placekeys 使这一步变得简单。
下面是一个查询示例:
place = {
"query_id": "0",
"location_name": "Twin Peaks Petroleum",
"street_address": "598 Portola Dr",
"city": "San Francisco",
"region": "CA",
"postal_code": "94131",
"iso_country_code": "US"
}
pk_api.lookup_placekey(**place, strict_address_match=True)Output:{‘query_id’: ‘0’, ‘placekey’: ‘227-222@5vg-82n-pgk’}
成功返回查询后,我们将 Placekey 添加到投票数据中的相应行。
c. 在 Placekey 上加入模式和投票数据。 SafeGraph 模式带有内置的 Placekey,因此我们可以简单地将模式数据帧与placekey上的投票数据帧连接起来。这应该是一个“内部”连接,因为只有一小部分模式行对应于轮询位置。此外,一些轮询位置可能没有被 SafeGraph 模式覆盖。
结果示例:

现场选举结果和 SafeGraph 步行交通数据是一致的。
3)被游客之家 CBG 引爆
您会注意到visitor_home_cbgs列是 JSON 格式的,其中的键是一个 CBG,值是来自该行所在区域的 CBG 的访问者数量。我们需要垂直展开visitor_home_cbgs列来提取这些数据。幸运的是,SafeGraph 有一个 Python 库来让这个过程变得不那么痛苦。
下面是仅过滤到分区 3 的结果数据帧。请注意,in_person_trump、in_person_biden和in_person_jorgensen列在每一行中都是相同的,因为每一行都对应于 Prccinct 3。cbg列包括分区 3 的visitor_home_cbgs JSON 对象中的每个键。visitors列是 JSON 对象中键的对应值。

让我们再次查看这个相同的数据帧,但是这次过滤到所有行,其中cbg是‘131171301011’。

来自 CBG 的游客 131171301011。
6)计算估计的 CBG 政治归属
接下来,我们计算在每个辖区看到的来自每个 CBG 的访问者的比例。例如,从' 131171301011 '看到的访问者总数是 36(visitors列的总和)。那么portion就是visitors / total_visitors。

然后,我们有以下 CBG 的“131171301011”:
in_person_trump = sum( in_person_trump * portion ) = 321*.11111 + 779*.305556 + 513*.277778 + 409*.138889 + 294*.166667 = 522in_person_biden = sum( in_person_biden * portion ) = 283*.11111 + 174*.305556 + 94*.277778 + 120*.138889 + 117*.166667 = 146.9in_person_jorgensen = sum( in_person_jorgensen * portion ) = 20*.11111 + 37*.305556 + 23*.277778 + 18*.138889 + 13*.166667 = 24.6
我们感兴趣的是与每个候选人相关的部分(而不是实际的投票数——如果我们想计算投票数,我们必须归一化 SafeGraph 的访问者计数,因为计数是基于样本的)。为了计算 CBG“131171301011”的从属关系,我们有:
trump = in_person_trump / (in_person_trump + in_person_biden + in_person_jorgenson) = **.753**biden = in_person_biden / (in_person_trump + in_person_biden + in_person_jorgenson) = **.212**jorgensen = in_person_jorgensen / (in_person_trump + in_person_biden + in_person_jorgenson) = **.035**
以下是该数据集中的一些其他 cbg:

注:附带的 Google Colab 笔记本 中的实际估计值可能会因增加的标准化步骤而略有不同。
7)地图估计
最后一步,我们可以通过映射隶属关系来可视化我们的估计。蓝色表示倾向拜登,红色表示倾向特朗普,白色表示五五开。很明显,一些 cbg 丢失了,这将在下面简要说明。CBGs 的右上分组对应于 Forsyth 县,而左下分组对应于 Cobb 县。福塞斯县相当同质,但科布县有一些变化。总的来说,科布县似乎越靠近亚特兰大越蓝,但也有一些例外。

限制
- 客流量数据对应于选举日的整个星期。人们可以尝试通过删除基于前几周或后几周的“预期流量”来纠正这一点。
- 我们仅使用现场投票进行估计,这是有问题的,因为(a)许多选票是邮寄来的,因为选举是在疫情期间进行的,(b)邮寄来的选票往往倾向于民主党。人们可以尝试在县一级纠正这种情况,或者在了解县选举规则和选民登记数据的情况下,在 CBG 一级纠正这种情况。
- 一些 CBG 的估计完全不见了。根据分析的需要,缺失的估计值可以完全忽略,用中间值/平均值插补、最近邻估计值插补或最近投票点插补进行估计。
结论
虽然这种方法有一些局限性,但由此产生的估计值为研究人员、企业和政治活动提供了更多的价值。有了 CBG 级别的估计,政治倾向可以前所未有地与美国人口普查变量(如收入、种族、教育、年龄等)相关联地进行分析。Placekey 支持大规模的 CBG 级别的政治关联评估,这反过来又支持强大的洞察力。
这项工作的未来方向可能是分析诸如投票率、选民压制等等。安全图模式包括停留时间和行驶距离的值。在分析这些东西之前,您需要对原始模式数据进行标准化,因为 SafeGraph 的数据是整个美国人口的样本,并且基础面板会随着时间的推移而变化。缩放步骤超出了本教程的范围,但是在 GitHub 中有所涉及。
资料和笔记本都是给你用的!你能发现哪些真知灼见?!
评估数据的价值
一个新颖的框架来一致地度量数据资产的价值。
David R. Hardoon 博士是新加坡管理大学 Sim Kee Boon 金融经济研究所的客座教授,曾任新加坡货币管理局首席数据官。
新加坡管理大学沈基文金融经济研究所助理研究员。
Sri Siva Shankar R,CFA,新加坡管理大学沈基文金融经济研究所研究助理。
— — — -

泰勒·维克根据 Unsplash 授权拍摄的图片
有没有想过您的数据资产值多少钱?放心,你不是一个人。如今,组织意识到数据是一项关键资产,有助于实现在不断发展的数字现实中释放新价值、新收入流和新机遇的雄心。目前,可能只能在数据获取和投资之后估计投资回报。这种方法是有价值的。然而,作为一个试图最大化股东价值的公司管理层,或者评估是否购买、建造或投资一项资产的外部投资者,甚至作为一个在国际数据流中运作的国家政府,我们可能会受益于对我们资产价值的先验估计。
目前,数据资产是使用评估其他有形资产时使用的传统方法进行估价的。这些方法,由于它们的设计,在应用于数据资产时会带来一定的挑战。具体来说,它们对数据资产价值的估计不完整,因为它们忽略了没有两个数据资产是相同的这一事实,并且没有考虑到数据资产可能具有其他无形资产中没有的某些特征。
此外,由于 Covid 的存在,数据的重要性被放大了,而由于需要,对数字的依赖性呈指数增长。新用户上线;在线交易正以更快的速度发生,在家工作成为新的标准。组织正在经历加速的数字化转型,以提升其远程工作、云计算和电子商务能力。因此,不仅有更多的数据可用,而且还可以收集更多的数据。因此,“我的数据资产值多少钱?”现在比以往任何时候都更相关,我们必须找到一种方法来回答这个问题。
像大多数当代经济模型一样,实现这一点的一种方法是建立在现有的方法/框架之上。估计无形资产价值的现有成本和收入方法提供了一个很好的起点,因为这些方法计算的是资产的可观察价值或已实现价值。这些方法的挑战在于,它们不是对您的数据资产相对于实现价值的价值的完整先验估计。数据资产还具有未来的潜在价值,需要加以考虑。
打个简单的比方,想象你参加了一个 21 天的健身计划。这包括 200 美元的项目费,每天 2 小时的锻炼,以及努力克制自己不吃不健康的食物。在计划结束时,你已经减了 10 磅,腰围减了 2 英寸,还收到了成吨的赞美。所有这些都是可以观察到的“成本”和“收益”,因此构成了健身计划的实现价值。然而,这就是健身计划的全部价值吗?不会。不太可能增加体重、不太可能感染慢性疾病等等会有额外的价值。这些额外的成本和/或收益发生在未来,在当前时间点无法观察到。这些构成了健身计划的潜在价值。

斯科特·韦伯的图片授权给 Unsplash
与 fitness 计划类似,从先验的角度来看,数据资产的完整价值是一分为二的,既有实现的(可观察的)部分,也有潜在的(不可观察的)部分。如果潜在价值不可观察,我们如何确定数据资产的价值?此外,在这样做的过程中,我们如何考虑数据资产极其异构的事实,即没有两个数据资产是相同的,事实上,它们的价值会随着时间、用户、地理位置等动态变化。?一种可能的方法是将数据资产的价值视为数据资产的一组特征(例如质量)、使用该资产的组织的能力(例如技术能力)和外部环境(例如法律和法规)之间的相互作用。
举个例子,假设有一个高质量的数据资产,可以很容易地从中提取见解,但是使用该数据资产的公司不具备处理该数据所需的技术知识。在这种情况下,数据资产对于使用它的公司来说可能一文不值,但是对于拥有使用数据的知识的公司来说可能价值不菲。或者,想象一下这样的场景:数据资产质量很高,公司拥有提取洞察力所需的知识,但是通过了一项新的法律,禁止公司使用数据。对所有公司来说,数据资产的价值降低到零。
数据异构性也可以通过共同构成数据资产的一组特征来理解。这些因素与特定数据的相关程度以及它们之间的相互关系是数据资产不同于任何其他数据资产的原因,这种微妙之处也是数据资产估值复杂的原因。一个能够捕捉这些细微差别的框架将能够对数据资产进行先验评估。
剩下的唯一问题是:我们如何着手创建一个在这种相互作用中发挥作用的特征的详尽列表,以及我们如何评估每个这样的特征的定量价值和相关性。也许机器学习可以为这些提供答案。例如,合并结构性中断(在数据资产的实现中)并分析结构性中断前后组织绩效的变化可能是有用的。然后,可以使用机器学习技术将性能归因于不同的特征,并且可以从中推导出相对价值。然而,这是一个需要持续和集中研究的领域。
然而,在现有方法的基础上构建将提供一个健壮的概念工具和起点,以获得数据资产的定量估计,从而回答“我的数据资产值多少钱?”
估计合同终止前的时间——生命线生存分析
估计生命线特定事件发生的时间

卢克·切瑟在 Unsplash 上的照片
我敢肯定,每个人在数据职业生涯的某个阶段都会遇到这样的情况,当你想要估计某个特定事件将要发生时,例如,“什么时候有人会流失?”或者“保险的死亡索赔何时发生?”。我们通常会使用时间序列方法来解决这个问题,但生存分析是典型的方法。
在这篇文章中,我想向大家介绍什么是生存分析及其在 Lifelines Python 包中使用现实生活中的合同终止数据的应用。让我们开始吧。
生存分析
简介
什么是生存分析?生存分析是一种统计分析,旨在测量个体的寿命。比如“这个种群活多久?”。这可以用生存分析来回答。
虽然最初它被用来衡量个人的寿命(生或死),但这种分析足以回答各种问题。您可能对人们购买保险到索赔的时间间隔感兴趣,或者对人们进入主题公园到离开公园的时间间隔感兴趣。重要的是数据具有事件的持续时间。
审查
在我上面的解释之后,您可能会有一个后续问题“我可能有数据,但如果我没有我想要的事件持续时间的所有数据,该怎么办?”例如,你可能需要等到 60 年,直到你分析的所有个人都要求他们的死亡保险——但你想在更短的时间内做出决定(可能是现在)。
这就是为什么我们有权利审查的概念。这个概念定义了排除非发生事件寿命(或持续时间)的个体,我们仅有的信息是当前的持续时间。
为了说明这一点,我们将使用来自 Kaggle 的数据集示例,该示例涉及合同终止时的员工流失。
import pandas as pd
df = pd.read_csv('MFG10YearTerminationData.csv')
df[['length_of_service', 'STATUS']]

作者创建的图像
我们可能会从数据中问这个问题“员工合同终止前的持续时间是多长?”。因为我们对终止时间感兴趣,所以我们需要员工数据及其合同状态(有效或终止)。
如果你看到上面的数据,我们就会有服务年限。这是员工到目前为止或合同终止之前在公司服务的时间(以月为单位)。如果状态仍然有效,那么我们已经对数据进行了右删截,因为我们仍然不知道他们的雇佣合同将于何时终止(我们将有效状态的服务年限填写为从雇佣时间到当前时间之间的月数)。
审查也是为了缓解某些个人不同的进入时间的问题。我们现有的数据肯定不会同时雇用该员工;有些是很久以前雇佣的,有些是最近才加入的。
为什么我们需要了解右删?这是因为生存分析被开发来处理使用右删失数据的估计。我们所有的估计都是基于我们右删的数据。
如果您仍然对这个概念不满意,让我们使用 Lifelines Python 包进入应用程序。
生命线
我将跳过生存分析数学部分,因为本文的中心部分是使用生命线包来估计直到合同终止的时间。如果你对生存分析的深入数学解释感兴趣,你可以访问生命线包这里。
安装
为了做好准备,让我们安装生命线包。
#install with pip
pip install lifelines
或者
#install via conda
conda install -c conda-forge lifelines
Kaplan-Meier 估计下的生存函数
安装了这个包之后,我们现在可以用生命线包进行生存分析了。作为开始,我们将使用卡普兰-迈耶估计来估计生存函数。
from lifelines import KaplanMeierFitter
kmf = KaplanMeierFitter()
数据集准备就绪后,我们将进行一项功能工程,将“状态”变量转换为 0 或 1,而不是文本(该功能将被称为“观察到的”)。
df['Observed'] = df['STATUS'].apply(lambda x: 1 if x == 'TERMINATED' else 0)
现在我们有了所有需要的数据;持续时间和观察到的事件。让我们从创建生存函数开始。
#Kaplan-Meier estimation
kmf.fit(df['length_of_service'], df['Observed'])
如果成功,此通知将显示在您的笔记本中。

作者创建的图像
接下来,我们可以尝试绘制生存函数,以查看整个时间内的生存概率(员工合同终止未发生的概率)。
import matplotlib.pyplot as plt
plt.figure(figsize = (8,8))
plt.title('Employee Contract Termination Survival Function')
kmf.plot_survival_function()

作者创建的图像
上图显示了雇佣合同不会随时间终止的概率。显示有趣信息的情节;在前 23 个月,你的合同被终止的可能性相对较低,但当你进入第 24 个月和第 25 个月时,你的生存概率(合同未被终止)变得显著降低。这意味着大多数的终止发生在你合同时间的第 24 或 25 个月。
如果您对实际数字比图中显示的更感兴趣,您可以通过运行以下代码来显示生存函数估计。
kmf.survival_function_

作者创建的图像
或者,如果您对终止概率的累积密度更感兴趣,您可以运行下面的代码。
kmf.cumulative_density_

作者创建的图像
比较两种不同的 Kaplan-Meier 估计
我们可能会有另一个问题;"合同终止时间在性别上有差异吗?"。为了回答这个问题,我们可以建立两个不同的数据集和生存函数。
#Separate the Male and Female data
df_m = df[df['gender_short'] == 'M']
df_f = df[df['gender_short'] == 'F']#Prepare the survival function
kmf_m = KaplanMeierFitter()
kmf_f = KaplanMeierFitter()#Estimate both dataset to acquire the survival function
kmf_m.fit(df_m['length_of_service'], df_m['Observed'], label = 'Male')
kmf_f.fit(df_f['length_of_service'], df_f['Observed'], label = 'Female')#Produce the survival function plot
plt.figure(figsize = (8,8))
plt.title('Employee Contract Termination Survival Function based on Gender')
kmf_m.plot_survival_function()
kmf_f.plot_survival_function()

作者创建的图像
这里我们有了另一个令人兴奋的结果。该图显示,雌性的生存概率远低于雄性。在第 25 个月,与雄性相比,雌性的存活概率显著下降。
如果男性和女性的合同终止时间存在差异,我们可以使用统计假设检验来评估我们的假设。要使用的标准测试是对数秩测试,生命线在包内提供该测试。
from lifelines.statistics import logrank_testresults = logrank_test(df_m['length_of_service'], df_f['length_of_service'], df_m['Observed'], df_f['Observed'], alpha=.95)results.print_summary()

作者创建的图像
从测试结果来看,我们获得了小于 0.05 的 P 值,这意味着我们拒绝零假设,并接受男性和女性在合同终止时间上存在差异。这是一件令人不安的事情,但是让我们把结论放在一边,直到我们进一步分析事情。
危险函数使用尼尔森-艾伦
之前我们讨论了生存函数,但您可能对在 t 发生的死亡(合同终止)事件的概率更感兴趣。这是风险函数的内容,估计死亡概率。
我们将使用 Nelson-Aalen 估计作为估计风险函数的方法。幸运的是,生命线软件包已经提供了这个功能。
from lifelines import NelsonAalenFitter
naf = NelsonAalenFitter()naf.fit(df['length_of_service'], df['Observed'])
plt.figure(figsize = (8,8))
plt.title('Employee Contract Termination Hazard Function')
naf.plot_cumulative_hazard()

作者创建的图像
风险函数感觉像是生存函数的逆函数。从图中,我们可以看到,它开始缓慢,并在第 24 和 25 个月结束时走高。尽管如此,你在图中看到的是累积风险率,我们通过观察时间 t 的风险率来解释结果。
如果我们想得到实际的次数 t ,可以运行下面的代码。
naf.cumulative_hazard_

作者创建的图像
随着时间的推移,合同终止事件发生的时间概率越大,风险率越大。
Cox 比例风险模型下的生存回归
通常,除了您认为会影响生存分析的持续时间之外,您还会有其他数据。这种技术就是我们所说的生存回归。
在生存回归中有一些流行的模型,但是 Cox 的模型最常用于这个问题。让我们用之前拥有的数据集来尝试这个模型。
我假设只有年龄、性别和业务单位会影响员工合同的期限。
training = df[['age', 'gender_short', 'BUSINESS_UNIT', 'length_of_service','Observed']].copy()
根据这些数据,我们需要为机器学习做好准备。这意味着我们需要一次性编码分类变量。
training = pd.get_dummies(data = training, columns = ['gender_short', 'BUSINESS_UNIT'], drop_first = True)training.head()

作者创建的图像
这是我们将用于考克斯模型的数据集。让我们看看这些变量是否在影响员工合同终止时间。
from lifelines import CoxPHFittercph = CoxPHFitter()
cph.fit(training, duration_col='length_of_service', event_col='Observed')
cph.print_summary()

作者创造的形象
从上面的模型总结中,我们可以看到,BUSINESS_UNIT_STORES 是唯一不影响持续时间的变量,因为 P 值大于 0.05,而其他变量具有显著的结果。
为了解释,我们可以评估变量的系数。就拿年龄系数来说吧,是-0.01。这意味着,年龄每增加一个单位,基线风险函数就会降低一倍(-0.01)或 0.99。你可以这样解释,随着年龄的增长,合同终止的几率会降低 1%左右。
在拟合模型之后,我们可以使用这个模型来预测新数据的生存函数。
cph.predict_survival_function(training[['age', 'gender_short_M', 'BUSINESS_UNIT_STORES']].loc[0]).plot()

作者创建的图像
我们提供给模型的数据产生了以下生存函数,其中生存概率在第 25 个月显著下降(约 50%)。
为了更好地理解这个模型,我们可以绘制生存曲线,看起来就像我们改变了一个变量,而保持其他一切不变。这用于了解给定模型时变量的影响。
我们想了解年龄变量对我们模型的影响;我们可以使用下面的代码绘图。
cph.plot_partial_effects_on_outcome(covariates='age', values=[20,30, 40, 50], cmap='coolwarm', figsize = (8,8))

作者创建的图像
给定年龄,我们可以看到生存概率随着年龄的增长而增加。这意味着我们可以假设老年人比年轻人更不容易被终止合同。
结论
生存分析是一种试图用一个问题来解决问题的分析,比如“某人什么时候会流失?”或者“保险的死亡索赔何时发生?”。
使用 Lifeline 包,我们可以更容易地进行生存分析,正如我们从合同终止数据中看到的那样。通过生存分析,我们发现,当员工工作时间达到 24 和 25 个月时,终止合同的可能性最高。此外,女性和年轻人终止合同的可能性更高。
如果您喜欢我的内容,并希望获得更多关于数据或数据科学家日常生活的深入知识,请考虑在此订阅我的简讯。
如果您没有订阅为中等会员,请考虑通过我的介绍订阅。
根据 OpenStreetMap 估算出行成本
可以使用建筑物/道路和 LULC 图层模拟子区域通勤的路面成本

OSM 大楼/里斯本高速公路覆盖在谷歌地图上。来源:谷歌地图和 OSM
交通数据以其产生的强度存储是不可行的,甚至按月存储也是不可行的,它更适用于实时用例,正如我们所知的大多数日常通勤应用程序使用它的情况一样。
然而,有些情况下,我们需要对一些研究或商业项目进行静态分析,这时我们才意识到并没有这样的数据来源。很少有公司提供这种服务,但首先,它仅限于某些地区/城市,而且,这当然会让我们付出代价。
这篇简短的文章解释了如何将开源数据用作替代资源,以及如何从中构建有意义的东西。
OSM 倡议
众所周知,OSM 是一个众包平台,为社区提供和构建开源地图。多年来,它提供了巨大的价值,因为现在大多数商业应用程序都使用 OSM 来提供大部分底图和与路线相关的服务。
有多种平台/API 可用于接收数据,下面列出了其中一些:
- https://download.geofabrik.de/
- 【https://overpass-turbo.eu/ 号
- ArcGIS OSM 编辑器
- QGIS QuickOSM 插件
有多种类别的键/值对或数据集被分为多个类,对于这个练习,我们只需要建筑物和高速公路。
以葡萄牙里斯本为例,这里有几张 2015 年和 2021 年的矢量多边形快照




左边是 2015 年的建筑/道路,右边是 2021 年的地图。来源:OSM 的地图和矢量数据
从视觉上,我们可以推断出两件主要的事情:
- 在过去的 6 年里,建筑物的数量增加了
- 道路网络也有所改变
这也可能是因为 OSM 储存库中没有 2015 年的数据。
ESRI 2020 LULC 地图
另一个有用的来源是 2020 年发布的 ESRI LULC 地图。使用分辨率为 10m 的全球地图范围,我们可以推断出小区域级别的类别,并尝试估计预定义子区域的表面成本。
方法很简单,我们可以执行以下步骤:

里斯本的谷歌地图图像上覆盖的网格。来源:谷歌图片
通过创建更小的子区域来数字化里斯本,这也可以在一个教区(官方子区域)级别完成。
然而,对于这个实验,我们将实现简单的基于网格的分离。这些多边形的形状会影响我们推断最终结果的方式,通常建议使用六边形而不是正方形,因为它们更有可能代表真实的地理位置。
这些图像显示了基于正方形的栅格线的示例,但是在下一步中使用了六边形栅格。

建筑多边形上覆盖的网格。来源:OSM 矢量多边形
叠加 OSM 的建筑多边形会得到这样的结果。
现在,我们可以简单地计算每个方块中建筑物的平均数。这可以通过交叉或分区统计来实现。不同的工具有自己的方式来定义不同算法的名称。
在运行统计数据时,我们可能有两种选择:
- 每个多边形中建筑物的平均数量
- 每个多边形中建筑物的平均面积
获得的平均面积更有意义,因为较大的建筑可能意味着较高的人口密度,而相比之下,有几栋住宅建筑可能人口密度较低。一旦我们运行这个,我们得到类似下图的东西。

生成的每个多边形中建筑物平均面积的地图。来源:ESRI 底图
现在我们有了一个基础设施的一般概念,城市中的大多数人将聚集在这里,因此交通也将主要集中在这些地区。然而,这并没有告诉我们从六角形 A 到六角形 x有多困难
这可以从 2020 年 ESRI 提供的一张 LULC 地图中推断出来。我们可以将 LULC 地图重新分类为成本表面地图。例如,这意味着,水的数字较高,因为它是理想世界中最不喜欢的出行方式,同样,建筑面积类的数字最低,因为我们假设该类还包括可行驶的道路。
最后,我们可以建立一个这样的表。

每个类别的地面通勤复杂性权重。
这是我们得到的 ESRI 地图样本。由于地图的分辨率较低,目视判读最多只能识别 3-4 个类别,因此地图用处不大。

来自 ESRI 2020 的原始 LULC 地图。资料来源:ESRI
重新分类后,我们得到了以下描述通勤表面韧性的图像

重新分类的 LULC 栅格。地图现在减少到只有 8 个类。NODATA 已被添加到被视为不可替换的类中。来源:ESRI 底图/ArcGIS Pro
为了最终为我们的道路创建成本表面模型,我们将开发一个简单的公式,为两个数据源分别赋予不同的权重。LULC 地图的权重较低,因为信息质量不如我们从建筑物图层获得的信息精确。

以下地图是 LULC 和建筑物数量的混合栅格。一个地区可能有较低的建筑数量,但更难穿越的地表地形或看起来更像土路的糟糕道路,因此,在这些地方成本会更高。

加权栅格输出。来源:ESRI ArcGIS Pro
在 ArcGIS Pro 中运行路径距离分配工具后,我们提供道路网络作为输入源,道路最终被栅格化,每条道路线显示了通过该区域的出行成本。

道路网络及其相关成本的最终结果。来源:ESRI ArcGIS Pro
这是一个覆盖了道路网络的放大版本

最终地图的放大视图。来源:ESRI ArcGIS Pro
在哪些数据变量可用于提供更好的估计方面仍有很大的改进空间,但这可以作为数据不容易获得的交通相关应用的开端。
请随时联系:jaskaranpuri@gmail.com
使用 CatBoost 分类器估计不确定性
如何利用模型集检测未知的网络入侵

数据不确定性示例(图片由 Yandex 提供)
这是我们关于梯度推进决策树模型(GBDT)中不确定性估计的教程的第二部分。第一部分讨论回归问题中的不确定性估计。在这篇文章中,我们将展示如何解决分类问题,以及不确定性在实际任务中的应用。包含这篇文章中描述的实验的 GitHub 知识库可以在这里找到。
为什么不确定?
在机器学习的高风险应用中,重要的是检测系统何时不确定并且可能出错。这些应用包括医疗诊断、金融预测等等。数据通常以表格形式表示,具有不同性质和规模的特征。对于这样的数据,最好的结果通常是用 GBDT 模型实现的,例如 CatBoost。
回想一下,不确定性有两个主要来源:数据和知识。数据的不确定性是由于数据中的噪声(目标值的高方差或重叠类别)而产生的。当模型被给予远离训练数据的输入时,知识不确定性出现。由于模型对这个地区知之甚少,它可能会出错。因此,知识不确定性可用于检测异常实例。
KDD 入侵检测
我们将通过一个实例演示如何使用 CatBoost 进行异常检测。为此,我们考虑 KDD-99 入侵检测数据集。任务是检测网络入侵,以保护计算机网络免受未经授权的用户。模型被训练为将网络活动分类为正常或几种入侵类型之一。至关重要的是,测试集包含了几种在训练集中没有观察到的攻击类型——这与真实的实际场景非常吻合。对于 ML 系统来说,能够检测异常行为是很重要的——用户行为会随着时间的推移而漂移,恶意黑客总是试图找到攻击系统的新方法。
因此,我们的目标是:
- 区分正常和恶意的网络活动
- 将恶意网络活动分为已知的攻击类型
- 检测以前未发现的攻击
数据集和预处理描述
对于这个实验,我们按照[3]中的描述对数据进行预处理。我们将原始测试数据分成两个子集:一个称为“域内测试”的子集包含训练数据中存在的攻击类型,另一个称为“域外测试”的子集仅包含未知的攻击类型。然后,任务是基于不确定性的度量来分类输入样本是属于域内还是域外测试集。我们在训练数据上训练 10 个 SGLB catboost 模型的集合。在 GitHub 上有一个完全重新创建这个设置的脚本。
分类的不确定性估计
给定 SGLB 分类模型的集合,可以如下计算不确定性。每个模型产生已知类别的分布。我们可以通过将所有模型的预测分布平均在一起来组合所有模型,以获得预测后验:

作者图片
预测后验的熵是总不确定性的集合。总不确定度是上述数据和知识不确定度的总和(详见【1,2】)。我们可以通过考虑集合多样性的度量,例如互信息,将总不确定性分离成其组成部分,这产生了知识不确定性的估计:

作者图片
为什么集合多样性度量知识不确定性?我们假设模型集合将对熟悉的数据产生非常相似、几乎相同的预测,并且随着输入距离训练数据越来越远,将产生越来越多样化的预测。我们可以考虑多样性的其他度量方法,这些方法都会产生对知识不确定性的估计,但这超出了本文的范围。
结果
现在让我们看看我们在 KDD 入侵检测数据集上获得了什么。首先,我们在多类机制中训练单个 SGLB CatBoost 模型。预测网络活动类型的误差率为 4%。然后,我们评估我们检测异常(未知)攻击类型的能力。为此,我们用预测的熵来衡量不确定性。我们通过 ROC 曲线下的面积来评估质量[2]。获得的 ROC-AUC 为 92.8。
然后,我们通过使用 SGLB CatBoost 模型的集成来改进结果[1]。为此,我们训练了 10 个独立的 SGLB 模型(用不同的种子)。首先,我们观察到平均集合预测将错误率提高到 3.6%。然后,我们比较两种不确定性度量:总不确定性,作为预测后验熵(平均预测)度量,知识不确定性,作为互信息度量。正如预期的那样,使用达到 94.2 ROC-AUC 值的知识不确定性获得了最好的结果。

10 个 SGLB CatBoost 模型的 Ensembe 结果
带回家
这个小实验表明,我们可以使用梯度增强模型的集合来实现两个目标:
- 即使在不平衡的数据集中,也能提高分类精度
- 改进域外/异常输入检测
这证明了使用 GBDT 模型集合的好处。
[1] A. Malinin,L. Prokhorenkova 和 A. Ustimenko,ICLR 2021 会议论文集中通过系综提高梯度的不确定性。
[2]一种马林宁。深度学习中的不确定性估计及其在口语评估中的应用。博士论文,2019。
[3] Divekar 等人,基于异常的网络入侵检测基准数据集:KDD 杯 99 备选方案ICCCS 2018 论文集。
人工智能监控和面部识别技术打击犯罪的伦理问题
在围绕人工智能在打击犯罪中的应用的辩论中,出现了两个突出的问题。独裁政府利用人工智能监控和面部识别技术的偏见。

简介
人工智能(AI)在全球范围内迅速发展,每天都有新的应用被发现。虽然人工智能在许多领域都有应用,但它通常被用于人工智能监控和面部识别技术以打击犯罪。截至 2019 年,全球至少有 75 个国家正在积极使用人工智能技术进行监控,包括智能城市/安全城市平台、面部识别系统和智能警务计划(Feldstein 2019: 1)。然而,以打击犯罪的名义广泛使用 AI 并不是没有代价的;在过去几年中,出现了多种道德问题,这质疑了实施人工智能技术打击犯罪的可行性。本文将研究人工智能在打击犯罪方面的两个突出的伦理问题:面部识别技术的偏见和威权政府以公共安全的名义利用人工智能监控。
面部识别技术中的偏见

由 Perchek Industrie 在 Unsplash 上拍摄的照片
在人工智能新研究的推动下,面部识别技术变得比以往任何时候都更受欢迎;然而,它的发现并不总是准确的。根据国家标准和技术研究所(NIST)的研究杂志最近进行的一项研究,面部识别软件在种族、年龄和性别方面存在一定的偏见。NIST 计算机科学家 Patrick Grother 领导了这项同类研究中的第一项。格罗特和他的团队评估了来自 99 名开发者的 189 种软件算法,以衡量这些算法是否表现出人口统计学差异,这是一个衡量算法匹配图像的能力是否因不同人口统计学群体而异的术语(NIST 2019)。使用由各个政府机构提供的包含 849 万人的 1827 万张图像的四组照片,该团队评估了这些算法在人口统计因素方面的匹配能力。结果令人震惊;尽管不同算法的误差水平不同,但大多数算法都表现出了人口统计学差异。特别是,格罗特指出,与白种人相比,亚裔、非裔美国人和土著群体被错误识别的可能性要高 10 到 100 倍。此外,算法还难以识别女性与男性的对比,以及老年人与中年人的对比(NIST 2019;Grother,Ngan 和 Hanaoka 2019)。这些发现至关重要,因为它们暴露了面部识别系统中阻碍这些技术安全实施的偏见。美国公民自由联盟(American Civil Liberties Union)的分析师杰伊·斯坦利(Jay Stanley)说:“一次错误的匹配可能导致错过航班、漫长的审讯、监视名单的放置、紧张的警察遭遇、错误的逮捕或更糟,”(Stanley in Singer and Metz,2019)。这种来自固有歧视性人工智能面部识别系统的广泛人口差异的现实仍然是一个需要解决的首要伦理问题。
有缺陷的技术导致不公正
不幸的是,面部识别技术的偏见已经导致了美国的不公正。第一个已知的例子是罗伯特·威廉的案件,一名非洲裔美国人在面部识别系统错误地将他的照片与一名小偷匹配后被捕(波特 2020)。威廉姆斯最终被拍了面部照片,采集了指纹和 DNA,并被关押了一夜(波特 2020)。当一名侦探向他展示监控录像中的一幅图像时,威廉说:“不,这不是我,你认为所有的黑人看起来都一样吗?”(希尔 2020)。虽然威廉最终被释放,但他的经历一直是创伤性的,他周围的人,包括他五岁的女儿,永远无法看到他被戴上手铐带走(波特 2020)。罗伯特·威廉斯的故事有力地证明了有缺陷的面部识别技术对社会的危害。
独裁政府利用人工智能监控

Alec Favale 在 Unsplash 上的照片
虽然美国等自由民主国家正在努力利用人工智能来进一步保护社会,但在以打击犯罪为名利用人工智能监控的威权政府中,伦理问题也出现了。中国就是这样一个国家;通过“新一代人工智能发展计划”(AIDP),该国描绘了一个总体目标,即使中国成为人工智能的世界领导者。AIDP 表明中国打算将人工智能用于国防、社会福利和发展道德标准(Robert et al . 2020:1–2)。然而,了解了中国腐败和压制的政治文化,一些人,如《大西洋月刊》的副主编罗斯·安德森,认为中国关于人工智能的声明有着险恶的边缘。安德森认为,中国希望利用人工智能建立一个全方位的社会控制数字系统,这将把中国推向监控的前沿(安德森 2020)。这种由基于人工智能的监控推动的全知系统的可能性提出了伦理问题,因为它以牺牲公民自由为代价授予政府绝对控制权。
利用人工智能监控进行种族定性

安德森对政府控制使用全知数字系统的担忧肯定不是没有原因的。根据《纽约时报》驻香港记者 Paul Mozur 的说法,中国政府使用人工智能监控来描述维吾尔人,这是中国的一个主要穆斯林少数群体(Mozur 2019)。根据 Mozur 的说法,这种类型的监控是政府有意使用人工智能进行种族定性的第一个例子。通过人工智能监控,政府专门根据外表寻找维吾尔人,并记录他们的日常活动。这些信息被用来监视中国新疆省的 1100 万维吾尔人。由于人工智能技术的广泛整合,当局已将 100 万名维吾尔人因涉嫌恐怖主义和其他涉嫌犯罪而关进拘留营(Mozur 2019)。乔治城大学法律隐私和技术中心的助理克莱尔·加维(Clare Garvie)指出,人们将使用人工智能技术中最危险的部分:“如果你制造一种可以按照种族对人们进行分类的技术,有人会用它来压制这种种族。”(Mozur 2019 中的 Garvie)。政府采用的大规模人工智能监控的能力和实施仍然是全球人类活动家和领导人面临的一个紧迫的伦理危机。
人工智能技术的好处

斯蒂芬·菲利普斯-Hostreviews.co.uk 在 Unsplash 上的照片
虽然解决人工智能在打击犯罪中的伦理问题至关重要,但承认人工智能带来的一些好处也很重要。根据 Oliver Wyman Risk Journal, AI 用于检测员工盗窃、网络欺诈、假发票、洗钱和恐怖分子融资等犯罪(Quest et al. 2018)。这些人工智能应用战胜了金融犯罪。具体来说,银行在利用人工智能驱动的工具跟踪犯罪分子的同时,错误警报减少了 50%(Quest et al . 2018)。此外,如果使用得当,人工智能的应用范围实际上是无限的。未来的应用包括检测和跟踪非法商品、恐怖活动和人口贩运(Quest et al. 2018)。快递公司可以使用人工智能评估包含非法商品的包裹,商店可以使用人工智能识别异常购买,执法部门可以使用人工智能打击人口贩运(Quest et al. 2018)。所有这些人工智能应用都展示了在全球范围内增强社会安全的潜力。
结论
利用人工智能技术打击犯罪所涉及的道德问题仍将是研究人员、政府当局和普通民众争论的一个关键问题。虽然人工智能在打击犯罪和提高全球公民安全方面具有潜力,但不可否认的是,在打击犯罪方面实施人工智能存在伦理问题。关键问题包括极权政权对人工智能监控的滥用,以及任何政府对带有根本性偏见的面部识别系统的使用。作为对人工智能技术新兴问题的回应,近年来发布了多项指导方针。艾伦图灵研究所的大卫莱斯利博士发表了一套这样的指导方针。该报告强调了人工智能道德的重要性,并探讨了负责任地交付人工智能技术的平台(Leslie 2019: 3)。随着人工智能成为一种看门人技术,人类最终可以选择它的发展方向,无论是人类福祉的指数级进步还是重大风险的可能性(Leslie 2019: 73)。人工智能在全球范围内的日益融合不可避免地导致了重大的伦理问题,但如果领导者和研究人员愿意打击不道德的行为,并遵循适当的指导方针,人工智能可以成为走向更美好未来的不可战胜的力量。
[1]:“AI 是数字计算机或计算机控制的机器人执行通常与智能生物相关的任务的能力”(Copeland 2020)。
参考文献
安德森河(2020 年 9 月 9 日)。圆形监狱已经在这里了。大西洋。https://www . theatlantic . com/magazine/archive/2020/09/China-ai-surveillance/614197/
人工智能在达特茅斯诞生。(未注明)。达特茅斯。https://250 . Dartmouth . edu/highlights/artificial-intelligence-ai-coined-Dartmouth
B. J .科普兰(未注明)。人工智能(AI)。大英百科全书学派。2020 年 12 月 23 日检索,来自https://school . EB . com/levels/high/article/artificial-intelligence/9711
费尔德斯坦,S. (2019)。人工智能监控的全球扩张。卡内基国际和平基金会。JSTOR。https://www.jstor.org/stable/resrep20995.1?seq = 1 # metadata _ info _ tab _ contents
Grother,p .,Ngan,m .,& Hanaoka,K. (2019)。人脸识别供应商测试(FRVT)第 3 部分:人口统计学效应。美国国家标准与技术研究院研究杂志。https://doi.org/10.6028/NIST.IR.8280
k .希尔(2020 年 6 月 24 日)。被算法错误地指控。纽约时报。https://www . nytimes . com/2020/06/24/technology/face-recognition-arrest . html?登录=电子邮件&验证=登录-电子邮件
莱斯利博士(2019)。理解人工智能伦理和安全:公共部门负责任的人工智能系统设计和实施指南。艾伦图灵研究所。https://doi.org/10.5281/zenodo.3240529
Mozur,P. (2019 年 4 月 14 日)。一个月,500,000 次面部扫描:中国如何使用人工智能描绘少数民族。《纽约时报》。https://www . nytimes . com/2019/04/14/technology/China-surveillance-artificial-intelligence-race-profiling . html
NIST 研究评估种族、年龄、性别对人脸识别软件的影响。(2019).美国国家标准与技术研究所研究杂志。https://www . NIST . gov/news-events/news/2019/12/NIST-study-evaluates-effects-race-age-sex-face-recognition-software
波特,J. (2020 年 6 月 24 日)。一名黑人因为面部识别而被错误逮捕。濒临绝境。https://www . the verge . com/2020/6/24/21301759/面部识别-底特律-警察-错误逮捕-罗伯特-威廉姆斯-人工智能
Quest,l .,Charrie,a .,和 Roy,S. (2018 年)。使用人工智能检测犯罪的风险和好处。奥纬风险杂志, 8 。https://www . Oliver Wyman . com/our-expertise/insights/2018/dec/risk-journal-vol-8/re thinking-tactics/the-risks-and-benefits-of-use-ai-detect-crime . html
Roberts,h .,Cowls,j .,Morley,j .,m .,Wang,v .,& Floridi,L. (2020)。人工智能的中国方式:政策、伦理和监管分析。 AI 与社会。https://doi.org/10.1007/s00146-020-00992-2
n . singer & Metz,C. (2019 年 12 月 19 日)。美国研究称,许多面部识别系统存在偏见。《纽约时报》。https://www . nytimes . com/2019/12/19/technology/face-recognition-bias . html
道德数据分析——每个企业都需要知道的

每个公司都有客户数据。问题是,他们能用它做什么?哪些行为符合法律和道德规范,哪些不符合?如果说公司在历史上没有注意过如何利用客户数据,那么现在它们肯定注意到了。许多新的全球立法(如欧洲的 GDPR)已经将这个问题列为每一家存储客户数据并使用它来发送营销电子邮件的公司的首要考虑因素。
无论您的公司是否足够大,拥有一支使用自学算法的数据科学家团队,还是只有一名分析师在密室中切割数据,数据分析都可以帮助您做出关于关键业务流程的数据驱动型决策,从而提高业务绩效。但无论大小,如果你的公司利用客户数据做出面向客户的决策,就有伦理问题需要考虑。
当“了解你的客户”走得太远
基于客户数据的任何决策或行动都可能导致潜在的破坏性个人后果,从仅仅是不舒服到改变生活。到 2012 年,美国零售巨头塔吉特(Target)已经找到了如何通过数据挖掘进入女性子宫的方法,了解到一位顾客在需要开始购买尿布之前很久就已经怀孕了。
如果这种见解 1)对一个人的生活有深远的影响(比如银行业),或者 2)对大多数人有很浅的影响(比如社交媒体),那么这种见解的影响可能特别有害。首先,银行使用数据分析来做出决定,例如,谁可以获得抵押贷款以及利率如何,可能会对某人的生活产生持久且潜在的有害影响。在第二个场景中,2016 年的脸书/剑桥分析公司政治影响丑闻为应用数据分析如何影响投票箱中的大众行为提供了一个清晰的路线图。
无论决策是否会对另一个人的生活产生直接影响,每个公司都需要考虑他们的算法中可能出现的无意识偏见,因为这可能会对品牌产生负面影响。几年前,一个谷歌图像识别人工智能错误地将人类表示为大猩猩,微软的聊天机器人 Tay 在 24 小时内变得对 Twitter 太无礼,亚马逊的招聘算法产生了明显的性别偏见。
出于所有这些原因,一份最近在网上流传的泄露提案透露,欧盟正在考虑禁止出于多种目的使用人工智能,包括大规模监控和社会信用评分。然而,值得注意的是,GDPR 已经强烈坚持透明度,因为消费者对于强加给他们的自动决策有“解释的权利”。
要考虑的道德框架
无论存在(或最终出现)什么样的法规来减轻不公平的算法行为,数据科学家都站在关于数据分析的道德思考的前沿。幸运的是,有许多公共框架可供数据科学家或公司中希望避免任何法律或道德纠纷的任何人参考,这些纠纷可能因使用自动化和/或数据驱动的决策而产生。
最重要的是,行为合乎道德意味着不直接或间接地伤害他人或对他人造成不利。正如上面无意识偏见的例子一样,即使公司试图以客观的方式做好事,也可能在道德问题上站在错误的一边,并受到公开谴责。因此,在开始任何数据分析使用案例之前,建议后退一步,考虑案例的道德相关因素。
大型(政府和非政府)组织,如谷歌、欧盟和联合国已经发布了使用数据/AI/ML(机器学习)的指南,可以在处理数据和分析时提供道德指导。但是为了避免你不得不查阅所有这些,一项针对 39 位不同指南作者的元研究显示了以下主题的强烈重叠:1)隐私;2)安全和安保;3)透明度、问责制和可解释性;4)公平和不歧视。
避免道德失误的实用指南
在我们公司,我们已经将这些指南提炼为道德风险快速扫描,突出需要特别关注的领域,并可以帮助您评估特定用例可能产生的风险。扫描由一系列问题组成,这些问题涉及诸如谁将受到所做决策的影响、这些决策将如何受到影响以及影响的程度如何等。
例如:您是否会使用这些数据来做出可能会对被视为弱势群体的财务状况产生影响的决策?如果是,他们的个人行为会受到什么影响?在你能够衡量算法是否会导致不良影响之前,需要多长时间?
这是伦理数据分析系列的第一篇文章。在下一部分中,我们将更深入地探讨道德风险快速扫描,并描述 IG & H(我工作的公司)用来帮助企业减轻扫描结果中出现的潜在问题的框架。
这篇文章也被发表到 IG & H 的网站上。这是三篇系列文章中的第一篇,你可以在这里找到这些文章: 第二部分 和 第三部分
道德数据工作:技术数据保护的教训
一个初学者友好的教程,探索伦理数据科学——阐明一个人如何在保护敏感、私人信息的同时处理数据。

利亚姆·塔克在 Unsplash 上拍摄的照片
来自《走向数据科学》编辑的提示: 虽然我们允许独立作者根据我们的 规则和指导方针 发表文章,但我们无法验证每个作者的贡献。这篇文章的作者重申,他不是在提供法律建议。详见我们的 读者术语 。
介绍
作为天生好奇的数据科学家,我们的工作就是疯狂处理数据。我们真的被它迷住了。日复一日,我们的任务是清理凌乱的桌子,建立因果关系模型,开发防弹预测。我们大多数人都满怀激情地这样做。
然而,无论我们的任务是什么,无论是复杂的还是平凡的,最终都与数据洞察力有关。事实上,我们所做的一切都是为了获取有价值的商业优势,在学术界,是为了推进科学前沿。因此,作为一群数据专业人员,我们有一个共同点:我们每个人都利用他们的技能来释放数据的全部潜力。
但是这种对价值释放的执着并不是在每种情况下都是有益的。如果没有任何限制,我们对洞察力的狂热追求会造成巨大的伤害。每一项缺乏平衡的努力都是如此。这就是为什么我正在写你刚才正在阅读的这篇文章,探索公司和学者在利用数据时必须面对的限制。实际上,隐私受到数据保护法规的保护,本质上是定义数据工作边界的法律约束。
数据保护:技术实施
公司需要遵守各种规则和法规,以避免违反数据保护。其中一些很容易导致巨额罚款和持久的声誉损害,尤其是在数据盗窃的情况下。
为了实现这一点,他们需要实现一些机制,让个人能够完全控制存储在他们身上的数据。除此之外,限制敏感信息的收集和保留也很重要。“这是怎么做到的?”你可能会想。以下段落给出了明确的答案。
技巧 1:简单抑制
每当我们希望匿名化一个数据集时,我们首先必须获得一些关于原始数据的统计特征的信息。因此,获得感兴趣的一列(或多列)的概率分布是必要的,这样我们就可以创建原始数据的匿名副本。
一个很好的实验资源是 mocker oo(https://www.mockaroo.com/),它允许软件开发者和数据科学家创建人工数据集。人们甚至不会注意到数据集不是真实的。然而,最重要的是,它们是完全可定制的。因此,如果您需要一些模拟数据来开始构建您的应用程序,或者只是尽可能地模仿真实数据,Mockeroo 是您的最佳选择。

Mockeroo 的摘要信息。作者照片。
使用 Mockeroo,我收集了一个虚构的数据集,包含姓名、年龄、性别、地址和信用卡信息。

表 1:人工数据集。作者照片。
如上图所示,我们有敏感的 PII,比如人名,以及他们的财务信息。然而,这还不是全部。包括国家、城市和州的非敏感 PII,如性别和地址信息也是可见的。这种碎片足以使怀有恶意的公司或个人造成伤害。因为他们可以创建与地理位置相关的用户资料来操纵选民意图,就像剑桥分析丑闻中所做的那样。让我们看看如何匿名化这个假设真实的数据集。

表 2:人工数据集。作者照片。
当然,模式可能会更复杂,并且实际上剥离的细节水平会有所不同。但是像这样简单的隐藏策略可能有助于遵守数据保护法。
现在让我们转向下一组技术。
技巧 2:用抽样代替数字
在我们希望匿名化数据集的任何情况下,我们都必须获得一些关于原始数据的统计属性的信息。获取感兴趣的列的概率分布是必要的,这样我们就可以创建具有相同统计属性的匿名版本。
下面的函数使用 Scipy 库从我们的原始数据中绘制一个偏斜的正态分布,以创建一个具有相同统计模式的副本。这确保了匿名性,同时也使我们在获取见解的过程中不会丢失数据。
参数 a 根据底层数据表现为右倾斜还是左倾斜而变化。当参数 a=0 时,我们处理的是好的旧的正态分布。下面我们可以看到左边是原始数据,右边是匿名的复制工资数据。您可以使用 alpha 来实现底层发行版的最佳副本。

图表 1:作者对比直方图照片。
技术 3:用 Faker 替换敏感数据
现在转向一个不可或缺的伦理数据科学工具,我们将探索名为 Faker 的伪数据生成 Python 库。根据文档,Faker 是一个 Python 库,可以为您生成假数据。从姓名、地址、信用卡详细信息到更复杂的数据,如地理数据,你会发现这个包非常有用,很可能把它添加到你的书签中。
你可能想知道它是如何工作的?在我的虚构数据集中,我用它来生成基于性别栏的真实姓名。
我发现有趣的是,我小说中的一些人有博士头衔,尽管如果这能以某种方式反映在薪水上,那就更棒了。无论如何,您可能希望使用 Faker 编写一些实用函数,用虚构数据替换敏感数据。
技术 4:汇总到箱中
数据的概括是通过应用聚合操作(如宁滨、舍入和更广泛的分类)来实现的,以便用不太精确的值替换非常精确的值。从本质上说,这就好像我们稍微模糊了一个人的脸,而我们仍然可以从广义上看到他们的特征。通过这样做,敏感数据和个人标识符被删除,而数据仍然对分析有用。
这里我们使用 Pandas cut 函数来将年龄分组到分类切片中。

表 3:宁滨之后的人工数据集。作者照片。
结论
直到不久前,科学家们还在不顾伦理考虑的情况下,将人类作为实验品。为了从数据中获得尽可能多的洞察力,现代数据科学家可能也会这样做。不是人体,而是私人的敏感信息。正因为如此,各国政府制定了 GDPR 和 2018 年数字个人数据法案等法律。因此,我们有责任采用上述技术以符合道德的方式处理个人数据。
这些技术对索马里等发展中国家的数据专业人员特别有价值。在这样的环境中运行,数据的所有权和生产仍然不清楚,巨大的挑战摆在他们面前。其中之一是缺乏有效的国家机构,这导致了一个基本上不受监管和限制的数据生态系统的出现。匿名化提供了技术解决方案。
在本文中,我们研究了一些有趣的技术,这些技术将帮助伦理数据科学家保护隐私,而不会丢失原始数据中一些具有统计学洞察力的宝石。这些技术只是少数。还有很多其他的。但这是另一个帖子的故事。
如果你能跟随我的媒介,我将不胜感激。如果你能在我的博客上订阅时事通讯就更好了,我正在那里为未来的出版物建立读者群。我的推特账号是 @warsame_words ,我欢迎反馈和建设性的批评——对于后者来说, LinkedIn 是一个受欢迎的渠道。谢谢你陪我走完这段旅程。
参考
https://datacated.medium.com/general-data-protection-regulation-gdpr-b7745c5a69ff https://somalipublicagenda.org/who-owns-data-in-somalia/
推荐系统的伦理挑战
播客
Silvia Milano 谈到了我们需要探索的难题,以构建有益的算法
要选择章节,请访问 Youtube 视频这里。
编者按:这一集是我们关于数据科学和机器学习新兴问题的播客系列的一部分,由 Jeremie Harris 主持。除了主持播客,Jeremie 还帮助运营一家名为sharpes minds的数据科学导师初创公司。可以听下面的播客:
生活在这样一个世界里,我们可能想要的各种数据都唾手可得,其中一个后果就是,我们可用的数据比我们可能查看的要多得多。想知道你应该进入哪个大学项目?你可以访问十万个网站中的任何一个,每个网站都提供有用的见解,或者看看数百所不同大学网站上的一万个不同的项目选项。唯一的障碍是,当你完成那个复习的时候,你可能已经毕业了。
推荐系统允许我们从每天为我们指明方向的信息水龙带中有控制地啜饮一口,从庞大的目录中突出少量特别相关或有价值的项目。虽然它们是非常有价值的技术,但它们也有一些严重的道德失败模式——其中许多是因为公司倾向于建立推荐器来反映用户反馈,而没有考虑这些系统对社会和人类文明的更广泛影响。
这些影响是显著的,并且正在快速增长。Twitter 和谷歌部署的推荐算法定期就我们这个时代的关键道德问题塑造公众舆论——有时是有意的,有时甚至是偶然的。因此,与其让社会被这些强大的算法重塑,也许是时候我们问一些关于我们想要生活在什么样的世界的大问题了,并倒推找出我们的答案对我们评估推荐引擎的方式意味着什么。
这正是我想和本期播客的嘉宾西尔维娅·米兰诺谈谈的原因。Silvia 是推荐系统伦理的专家,也是牛津人类未来研究所和牛津互联网研究所的研究员,她在那里参与了旨在更好地理解推荐算法的隐藏影响以及如何减轻其更负面影响的工作。我们的谈话引导我们思考复杂的问题,包括身份的定义、人权自决权以及政府与科技公司的互动。
以下是我最喜欢的外卖:
- Silvia 指出了现代推荐系统中的四个利益相关者,每个人都有不同的兴趣和理由关心推荐质量的不同方面。首先是最终用户——直接消费推荐的人(亚马逊购物者、网飞账户持有人和投机者等)。其次是系统本身,它需要平衡即时用户满意度和让用户测试尚未评级的产品的机会(有时亚马逊可能有一个新产品需要评估,它可能会向你推荐,尽管它的算法怀疑另一个选项更有可能更好地满足你的即时需求)。第三,运营这些系统的公司分别关心长期用户满意度,以及可能的其他考虑,如监管压力。最后,基于推荐者影响人们偏好的方式,整个社会会随着时间而改变。因此,Twitter 的用户并不是唯一对 Twitter 推荐系统的完整性和质量感兴趣的人——每个人都是。
- 推荐者对于对他们的用户重要的时间范围做出隐含的选择。例如,Twitter 旨在尽可能长时间地垄断用户的注意力,引诱你去做在很短的时间内让你开心的事情。但是在短期内感觉良好的决定在长期内并不总是感觉良好。当你最终注销时,你可能很容易觉得你的时间被浪费了,即使从一秒到下一秒,你只是在执行你当时“想要”做的动作。其他推荐者有不同的时间范围(例如,亚马逊关心你是否信任它的评论,这要求你购买产品并在留下反馈前进行测试),但当涉及到决定他们希望评估他们与软件交互的质量时,用户通常没有什么选择。不同的时间范围意味着不同的产品开发选择,所以这是一个非常有影响力的自由度,目前不在消费者自己的手中。
- 在我们希望通过与人工智能系统(如推荐者)的互动来“改进”和“成长”的愿望,以及我们希望保留代理并控制我们成长和改进的轨迹的愿望之间,有一种自然的紧张关系。例如,Twitter 不仅仅向用户提供推荐的文章和帖子,它还主动塑造用户的偏好,包括他们对时事的看法。在这个过程中,Twitter 用户转变成了不同的人。如果你在与推荐系统的互动中可以改变自己,你对这种改变的程度和方向有多大的发言权?这仍然是推荐系统伦理中一个悬而未决的问题——也是一个大问题。
- 由于推荐系统的影响远远超出其直接用户群,一个自然的问题是:政府应该在监管中发挥作用吗?不幸的是,每个推荐者都是不同的,政府不太可能有技术能力、理解或带宽来负责任地逐案监管每个推荐者。因此,Silvia 建议,他们的作用可能会变成促进行业规范和标准的形成,并通过玩更多的元游戏进行行业监督,包括通过法律,使监督团体更容易监控和采取行动违反某些道德规范的做法。
你可以在推特上关注西尔维娅,或者在推特上关注我。
播客中引用的链接:
- 人类未来研究所的网站。
- 西尔维娅在牛津互联网研究所的网站,或者她在人类未来研究所网站上的简介。

章节:
- 0:00 介绍
- 1:13 西尔维娅的背景
- 5:11 推荐系统的伦理限制
- 权利的量化
- 10:02 后果主义评价
- 11:58 结果主义评估的领先替代方案
- 23:06 人格同一性的一般问题
- 27:34 用户、提供商和系统
- 30:34 实施方面的问题
- 37:16 跨期选择
- 39:53 系统设计
- 51:23 政府的作用
- 59:34 总结
请查看下面的文字记录:
杰里米(00:00:00):
大家好,杰里米,欢迎回到播客。今天我们与 Silvia Milano 交谈,她是牛津人类未来研究所的研究员,也是牛津互联网研究所的研究员,她在那里从事人工智能伦理学的工作,特别是,她最近一直专注于推荐系统的伦理学。所以我们将会谈论很多关于推荐系统的内容,特别是,我们将会关注的一个关键领域,就是现代推荐系统中的利益相关者是谁的问题。那么,这仅仅是关于最终用户吗?仅仅是关于与 Twitter 应用程序交互的人吗?还是和网飞一起?或者不管是什么?或者,我们是否必须开始缩小一点范围来思考公司、思考系统,甚至思考这些推荐系统及其用户所处的社会和文明?
Jeremie (00:00:45):
西尔维亚的观点是,是的,事实上,我们有。当我们意识到这一事实时,这些问题就变得非常有趣,既道德又实际。因此,我认为可以公平地说,我们正在进行一系列非常有趣的对话,从个人身份到公司和政府在非常强大的推荐系统中的责任。我真的很喜欢这次谈话,希望你也一样。西尔维亚,非常感谢你参加我的播客。
Silvia (00:01:11):
谢谢你邀请我。
Jeremie (00:01:13):
你能来我真的很高兴。我们之前已经简短地谈过一次,但我真的很期待在这里深入一点,特别是关于你在推荐系统方面的工作,这是一个非常热门的话题。推荐系统的伦理问题,我认为是我们仍在努力解决的问题。我们才刚刚开始,真的,但是在我们进入正题之前,我想先问问你。当初是什么让你一头扎进这个领域的?你是如何对推荐系统感兴趣的?你是如何结束你现在所做的研究的?
Silvia (00:01:43):
嗯,这是个好问题。这是多种因素的结合。我对人机交互很感兴趣,对这个领域的普遍兴趣也很感兴趣,我发现我读到的关于推荐系统以及这个系统如何构建我们的在线交互以及我们的数字生活的文章并不多。这就是我第一次意识到这是一个大话题的原因。但是目前有太多的问题没有得到解答,所以我想,我真的想深入研究一下。从认识论的角度来看,这很有趣,因为,嗯,有各种各样的问题。例如,我们如何知道某件事何时与用户相关?我们如何知道某件事是正确的建议?从伦理的角度来看,很明显,推荐东西看起来像是非常私人的、有针对性的个人行为,但实际上有各种各样的社会影响。所以从伦理上来说,我认为尝试去理解和剖析这些东西是非常有趣的,在我们的社会中,推荐系统的功能是什么?
Jeremie (00:03:07):
是的,每次我想到这个问题时,我都会想到,尽管存在所有这些问题,但推荐人显然会一直存在,他们非常重要,真的没有办法绕过他们。我内心的勒德分子想说,“好吧,我们有一些推荐者,可以说他们正在撕裂我们的社会结构,让我们摆脱他们吧。”但实际情况是,无论存在什么问题,普通人都没有时间去收集《纽约时报》级别的新闻报道,我们也没有时间收集这种产品信息,从这个意义上来说,这几乎就像新闻业。看起来我们要解决的就是这件事,对吧?
西尔维亚(00:03:49):
对,没错。我想说不仅仅是一个正常人。任何人,你不可能在每个话题上都是专家。也就是说,在这一点上,我们可以不需要推荐系统,也就是说,我们可以不需要互联网上所有的数据和信息。
Jeremie (00:04:07):
是啊。
西尔维亚(00:04:08):
是的,我们可以。我们做到了。我们过去常常把它留在那个场景中,但显然,它有如此大的价值,我们不想放弃它。而是将所有这些可用的数据和信息转化为实际可用的东西,并与我们的日常生活相关,转化为推理的方式。我们确实需要推荐系统,它能帮助我们克服信息过载,找到一条路,找到一条穿过所有这些信息的路。
Jeremie (00:04:43):
是的,正如你在所有研究中指出的那样,它们的成本很高。我认为很多人几乎都是本能地意识到这种成本是多少。他们已经看到了他们的行为是如何受到与 Twitter,甚至亚马逊上的产品推荐的交互的影响。但是你能不能花一点时间,解释一下你在伦理上对推荐系统可能产生的影响以及它们可能产生的更广泛的外部性的一些具体担忧。
Silvia (00:05:11):
我提出了一个分类法来思考推荐系统的伦理限制。我目前正在建立分类法来回答你提出的这类问题。因此,从这个角度来看,我们应该区分推荐系统对不同利益相关者效用的影响。所以向用户推荐某些商品和信息的结果。这将如何影响效用,比如说,消费商品或信息的用户本身,或编写信息的提供商,系统本身,托管推荐系统的平台或更广泛的社会。所以这就是,我们可以从效用的角度来考虑,或者从这些不同的利益相关者的角度来看,如何评估后果,我们可以这么称呼他们。这是评估的一个方面。
西尔维亚(00:06:19):
如果你愿意,另一个维度可能更符合道义论,与系统本身的行为有关。因此,这些行为是否侵犯了一些权利,例如,用户的权利,是否侵犯了用户自主权,是否侵犯了一些权利,这是我们可能无法根据建议的后果来兑现的事情,但可能行为本身是有问题的。因此,我建议从这两个大的方面来看待[相同的 00:06:50]影响。我马上想到了几个挑战。一旦我们以这种方式看待事物,我们就能立即看到,例如,在效用方面,在结果论的评价方面。
Silvia (00:07:10):
还有其他事情要做,例如,这些建议有多准确,它们是否促进了效用和利润。推荐项目的提供者和用户自己的平台。所以这些是非常广泛的考虑因素。此外,还有更多与公平有关的考虑,这些考虑更多地是在制度层面上。例如,我们是否以某种方式构建推荐系统,比如新闻推荐。一些叙事是否更加前景化,为什么,对我们的社会有什么影响?
Jeremie (00:07:56):
是的,不,我认为右边有很多,这是经常发生的事情。所以我爸爸是一名律师,我实际上,特别是在我读大学的时候,研究定量的东西,我对他描述过程的方式感到非常沮丧,特别是在刑法中,得出关于特定被告有罪或无罪的结论。所以他总是告诉我陪审团显然不应该根据概率来思考。
Jeremie (00:08:29):
有这样一条禁令,如果陪审员认为他们有罪,并且没有合理怀疑,那么他们就会被判有罪。但是当你试图量化它的时候,当你试图说,“好的,这意味着这个概率阈值。”这显然是不,不,至少在英国法律的传统中,我父亲对此很熟悉,因为我们是加拿大人。这总是让我感到沮丧,因为这就像,不知何故,如果我们要在文明中取得进步,感觉我们真的需要能够量化什么是权利。在推荐系统的环境中,这似乎是很难做到的事情。这是…首先,你认为这是一个真实的问题,还是我在这里为自己编造的?第二,在这个问题上有没有潜在的解决办法,或者有没有一些想法?
西尔维亚(00:09:18):
这是一个非常好的问题,很难回答。目前,我对这个问题的想法是,我们真的需要在我们的脑海中有这个前沿,我还没有提出解决方案,但我们真的需要这个前沿。推荐系统实际上是社会基础设施,因此我们需要考虑它们如何影响人们的权利,基础设施如何工作也应该根据它如何促进个人人权或社会的良好运转来评估。
Jeremie (00:09:56):
是的,我想我们还需要定义这些东西是什么。就像-
西尔维亚(00:09:59):
没错,但这很棘手。
耶雷米(00:10:02):
是啊。
Silvia (00:10:02):
接受结果论的评价当然要容易得多,这也是我一直在做的大部分工作,部分原因纯粹是因为实际原因。
Jeremie (00:10:14):
你能详细说明一下吗?所以对于不是哲学家或者没有受过这种训练的人来说。所以你能解释一下结果主义以及它是如何在这种背景下建立起来的吗?
西尔维娅(00:10:25):
结果主义的广义定义是,在评估一个行为时,重要的是它所带来的后果。举个例子,如果你在两个行动 A 和 B 之间有一个选择,对他们的评价重要的是 A 和 B 的结果,这个想法就是结果主义者告诉你要带来最好的结果。所以你根据行为 A 和行为 B 的结果有多好来排序,选择结果最好的行为。
Silvia (00:11:03):
现在,这是一个非常普遍的想法,它必须在我们如何衡量不同结果或状态的优点方面得到体现?因此,有各种各样的事情需要更仔细地处理和定义,但这是一个广泛的想法。在推荐系统的背景下,我们会问,给定一个推荐,让我们看看做出这个推荐对于收到这个推荐的用户以及这个推荐中其他可能的利益相关者会有什么后果。让我们尝试量化这种优点,然后在此基础上,我们可以对不同的可能推荐进行排名,这些是广泛的结果主义方法来评估推荐者。
杰雷米(00:11:58):
我觉得这真的很有趣,因为作为一个有定量机器学习背景的人,结果主义对我来说听起来非常直观,所以我的下一个问题是,如果不是结果主义,我还能使用什么替代框架呢?有哪些领先的替代方案?
Silvia (00:12:19):
伦理学中结果论框架的一个替代方案是本体论框架。在这种传统中,行为的评估不仅仅是根据其结果,而是根据行为本身的质量。举个例子,非常著名的结果主义者当然是康德。他举了一个非常著名的例子,一个杀人犯来到你的门前,敲你的门,寻找他想杀的人。他带着他的武器来了,这些意图是很明显的。你知道吗?你碰巧知道这个人在哪里。所以问题是,你该不该告诉他?著名的康德说,“事实上,你应该永远说实话,这是你的责任。”
西尔维娅(00:13:10):
所以在这种情况下,如果有人在你家门口的谋杀案中问你,他要找的人在哪里,你应该告诉他。很明显,你不能通过评估好的方面来得出这个答案,因为很明显,如果你告诉凶手真相,你可以预见的结果是非常糟糕的,你认识的这个人会被谋杀。所以,这里的想法是,行为的评估不是根据结果,而是根据行为的质量。举个例子,对于康德来说,撒谎是你永远不应该做的事情。不撒谎是你的责任。
耶雷米(00:13:53):
这很有意思,因为我想这非常直观,它把许多关于道德和伦理的宗教信仰放在那个桶里,你会说,“你可以在生活的每个方面都是结果主义者,除了遵守安息日。”所以凡事都有结果论,还要确保你周五晚上休息,周六早上休息,诸如此类。
西尔维亚(00:14:16):
嗯,你可以说你永远不应该故意或者永远不应该欺骗别人或者永远不应该违背诺言,这可能是一种固有的约束。实际上,如果我们考虑推荐系统,例如,在新闻推荐的环境中,这可能是一个相对直观的约束,对吗?
耶雷米(00:14:40):
是的。
西尔维亚(00:14:41):
你永远不应该说谎。
耶雷米(00:14:43):
这本身就很有趣,因为它已经开始引入所有这些最大挑战之一的隐现幽灵,这是我们所拥有的,这种功利主义的结果主义观点似乎是,它几乎好像没有任何固定的东西。不同的人可以对后果做出不同的解释,这取决于社会背景,我们今天认为是好的东西与我们过去认为是好的东西是非常不同的。
Jeremie (00:15:12):
我总是在思考现在版本的我和未来版本的我之间的区别,我与系统的互动会改变谁。如果我有这种结果主义的自由浮动伦理。在某种程度上,这很难描述,但在我使用 Twitter 之前,我的政治观点是一些信仰。在我使用 Twitter 后,我的政治观点变成了一套不同的信仰,我从那次互动中改变了自己。未来版本的我显然认为他们是对的,但过去版本的我认为他们是对的,就好像过去版本的我的权利被以某种怪异的方式侵犯了,或者可能被侵犯了,这取决于我所接触到的东西。但是从结果论的角度来看,每一步,也许都是有意义的。我不确定我是否表达得很清楚,但是-
西尔维娅(00:16:03):
是的,那太棒了。这是一个很好的例子,也是一个很好的问题。因此,我认为,在对推荐系统的大量研究中,至少当我们认为推荐者试图预测用户偏好时,这可能是隐含的东西。偏好可能是潜在的和固定的。所以我们有自己的偏好,我们只是缺乏足够的信息来知道我们在寻找什么。所以推荐者的任务就是帮助我们,因为他们能够收集更多的信息,根据我们的喜好找到与我们相关的商品,对吗?这是思考这个问题的一种自然方式,我认为它有很长的历史。我知道这是经济思想的一个很长的历史,它实际上与经济学中的偏好分析联系在一起。
Silvia (00:17:08):
所以这里的想法是,当你在使用 Twitter 后,根据这些建议改变你的政治信仰时,这将是因为现在你有了更多的信息。在这些额外信息的基础上,你提炼的不是你潜在的偏好,而是它们的表达。现在你有了更多的信息,你缩小了这些偏好的范围。
西尔维亚(00:17:35):
这是看待这个问题的一种方式。但我觉得也很直观。这反映了许多人的经验,事实上,我们的偏好并不是一成不变的。也许你使用 Twitter 和接触不同观点和建议的经验是,实际上这可以影响你的偏好,这可能比我们想象的更有可塑性,并对你拥有的信息做出反应,不仅仅是通过更新你的信仰和给你关于世界的更好的信息,这有助于你缩小偏好的范围,但并没有真正改变你的潜在偏好[听不清 00:18:19]你的潜在偏好也在更新和过程中。这要复杂得多。所以你说的对,这和结果论的评价有些矛盾。因为,当然,如果偏好是固定的,那么我们可以认为,例如,一个系统,随着时间的推移,满足用户的偏好,最大化他们的效用。
耶雷米(00:18:46):
对。
Silvia (00:18:48):
但是,如果偏好发生变化,我们还需要弄清楚随着时间的推移,这将如何影响我们对系统的评估。所以如果我在 T one 给你推荐什么东西,而你在 T one 的偏好是这样那样的,我可能会在那个时间做一个很好的推荐。但是同样的建议在以后当你的偏好发生变化时,部分由于你与系统的互动可能不再效用最大化。是的,我认为这是一个深刻的问题。
西尔维亚(00:19:24):
在哲学中,这是有争议的事情。我只想提一个事实,哲学中有一个悠久的传统,来自女性主义哲学,它指出了这种传统的偏好分析是非常有限的。事实上,人们的偏好受到他们所处的环境以及他们与其他社会角色的互动的影响。这有好有坏。
Silvia (00:19:54):
所以我认为有几种哲学传统可以用来讨论这些特殊的问题。对于计算机科学的研究人员来说,关注这一点并拓宽他们的视野是非常重要的。是的,因为仅仅从个人用户的角度来看待推荐者的问题,假设他们的偏好是固定的,是非常有限的。
耶雷米(00:20:22):
是的,由于一些奇怪的巧合,我正在查阅一些关于身份的哲学文献,而不是从政治角度或类似的角度。就像字面上的意思,你如何通过时间追踪一个人的身份?我真的会被认为是五分钟前的我吗?人们经常说这样的话,“我已经变了,我现在是一个不同的人。”你是说真的吗?在这种情况下,你对之前行为的法律责任是什么?你怎么能为你将来的所作所为负责呢?
Jeremie (00:20:55):
就推荐者而言,这就像是你自己正在进行一场辩论,将你的偏好强加给未来的自己。如果你想说,“听着,如果我身上的某些东西改变了,我会感到厌恶。我绝不会想成为那种喜欢黑色之类颜色的人。”然后你向前看,比如 10 年。如果你有一个神奇的算法可以告诉你,如果你在未来 10 年内与 Twitter 互动,你所有的衣服都将是黑色的,你将把你的公寓涂成黑色,你会爱上它。你有什么权利改变路线?这似乎是一个棘手的问题。你有没有意识到,你提到了一些例子,我猜想,人们思考这个问题的方式,但是这和哲学同一性文献有什么联系吗,可能和这里有关?
西尔维亚(00:21:53):
是的,当然。它只是打开了如此多的问题,他们真的很迷人。我真的很喜欢最近哲学文献中的一些分析,从一个群体代理人的角度来思考改变自我。所以,把你自己想象成一个不同版本的你的集合。问题是,当你在任何时候考虑一个行动时,这个行动的结果对你未来的自己来说是有趣的,对吗?
耶雷米(00:22:30):
是啊。
西尔维娅(00:22:31):
所以这个想法会考虑到你未来自我的利益,你可能会对未来自我的实现有很多不确定性。这实际上也取决于你在这个时间点选择什么行动。所以可能有一些方法来概念化这个问题,给你一些指导,告诉你如何决定你的偏好何时改变,从这个意义上说,你自己不是固定的。所以这是一个我觉得很有意思的看待这个问题的方式。
Silvia (00:23:06):
当然,更普遍的个人身份问题可以追溯到非常古老的时代,古希腊哲学家正确地说,“我们不会两次穿过同一条河。”我想这个想法是,一切都在变化,一切都在改变。从这个意义上说,这是一个非常深奥的问题,也许我们永远也找不到答案,也许一些非常聪明的哲学家会找到。但是我认为我们不需要为了建立好的推荐者而去破解非常非常难的哲学问题,这是一件好事,因为否则我们基本上会被卡住。
耶雷米(00:23:45):
对,是的。不,是真的。区分我们今天面临的问题和 10 年后可能面临的问题总是有用的。随着技术的进步,我们可能会被迫正面面对其中一些问题,幸运的是,目前情况有所好转。但是就这种范式而言,你在研究中提出了多利益相关者框架的想法,这种想法缩小并说,“好吧,我们不再只考虑用户的直接利益。假设我们有一个更广泛的不同参与者和利益相关者的集合。”我认为,你提到了这个模型中的一些利益相关者,但我想,你是否介意对多利益相关者框架进行一些扩展,以及它如何改变你对推荐系统的看法。
Silvia (00:24:32):
这里的想法是,多利益主体推荐系统是一个在制定推荐时考虑多个利益主体利益的系统。这与长期以来作为推荐系统研究中心的 parody 形成对比,parody 只采用我们称之为用户中心的方法,用户本质上是唯一的利益相关者。所以在这个更古老,更传统的范式中。推荐系统的任务是发现好项目,其中好项目是对推荐系统的用户有益的项目。
Silvia (00:25:18):
在多利益相关方版本中,我们不仅仅考虑用户利益,我们还认为有必要考虑其他利益相关方的利益,我认为这至少应该包括其他三个类别。前两个相对来说已经在推荐系统生态系统中,它们是系统本身,也就是项目的平台和提供者。所以让我们在这两个方面展开一点。或许,提供者是使被推荐的项目可用的一方。例如,在一个电子商务网站中,提供者可能是在平台上出售不同商品的卖家。或者在新闻推荐的情况下,它可能是内容生产者,因此无论谁写,例如,博客或文章。或者视频推荐者,可能是制作视频的任何人。
Silvia (00:26:33):
显然,这些提供商对他们的推荐感兴趣。例如,如果你是亚马逊或易贝的卖家,你对推荐的商品感兴趣。不仅如此,你还想把它推荐给合适的人,他们可能会对它感兴趣并购买它。所以很明显,你和这个建议有利害关系。正如我所说,需要考虑的另一个利益相关者是平台或系统本身。所以很明显,为了可行,这个系统本身需要一些回报。举例来说,如果你在运营一个电子商务推荐系统,你希望从中获得一些利润,如果你是一个新闻推荐系统也是如此。否则,这将根本不是一个可行的业务。
耶雷米(00:27:32):
是的。
Silvia (00:27:34):
我们有用户、提供商和系统。我还建议,我们应该把整个社会作为一个利益相关者来考虑。原因是,虽然很难从概念上理解社会对如何推荐感兴趣,但通常,推荐系统的行为会产生更广泛的社会影响。很难将它们与在推荐系统中运行的特定利益相关者的兴趣联系起来。有两个原因。更直接的一点是,因为通常情况下,甚至不在推荐系统上的人和团体也会受到它的选项的影响。因此,举例来说,即使你不使用亚马逊购物,你也可能会受到其受欢迎程度的影响,因为例如,如果你经营一家小企业,那么你可能会真正感受到亚马逊受欢迎程度的影响。因此,即使你不是推荐系统本身的直接利益相关者,你的利益也会受到影响。
Silvia (00:28:51):
从整个社会来看,我们经常会看到这些推荐者的系统性影响,最明显的例子就是新闻推荐者。但这也可能出现在其他情况下,算法以这样或那样的方式被调整的事实导致不同观点的流行和分布,这可能会对例如政治事件或人们的总体想法以及我们网站的功能产生影响。所以这些事情是非常重要的,并且显然与推荐系统的评估非常相关。所以我认为他们应该被考虑在内,而我们能做到这一点的方法是让利益相关者代表社会本身的观点。
Silvia (00:29:44):
好了,现在我们有四个利益相关方。多利益相关者系统的想法是,一旦我们确定了利益相关者,有两件事我们可以做得比我们没有做好。一个是定义推荐系统的更好的评价。那么,我们可以定义什么指标来评估推荐者的影响呢?其次,我们也可以更好地了解我们正在处理的优化问题。那么我们在这里试图最大化的目标是什么呢?在这一定义中,我们可以尝试考虑所有不同的利益相关者可能试图实现的目标。
杰雷米(00:30:34):
是的,从这个角度来看非常迷人,因为它确实揭示了一些深刻而重要的问题,一旦你开始从这个角度来看,我可以想象,当你在建立一个个人项目或公司内部的推荐系统时,专注于以用户为中心的方法会很方便,因为这可以让你快速迭代,对吗?你可以很容易地测量一个数字,比如用户满意度,用户在购物车里放了多少东西?他们看了多少类似的电影?周期时间开始变得越来越长,因为你开始缩小并说,“好吧,那么从公司的角度来看,这个推荐系统有多赚钱?”你必须等着看成本和收益是什么,也许是 1/4 或 1 年摊销,谁知道呢。然后对更广泛的社会来说,这些时间线变得很长。因此,测量过程变得具有挑战性,可能会令人望而却步,这是一个合理的问题吗?现在,下一步将在一段时间内实现这一点。
Silvia (00:31:35):
是的,我喜欢这个问题。我认为这指出了在概念化推荐系统所解决的问题时,我们所称的抽象层次的合理和有用的选择。所以,对于评估来说,一些几乎是抽象的层次的选择,例如,以用户为中心的方法,在你所说的较短的时间内会更自然。而包含社会评价的抽象层次也需要不同的数据,并且很可能需要更长的评价时间。所以这肯定是一个有趣的研究问题。
Jeremie (00:32:23):
是的,这就像是在商业环境中,很有趣的是,数据的可用性与你所说的完全一致。有时候,你需要评估一个特定模型或特定产品的数据是不可用的,所以你最终会寻找代理。这些替代物并不总是如此,它们可能会变得病态。我在播客上举了两次例子,因为我发现在评估人类行为时,这个例子非常有趣,也非常有启发性。
耶雷米(00:32:54):
有人曾经说过,我不记得是谁说的了,“如果你对一个网站进行 AB 测试,并经常迭代,最终,或早或晚,任何网站都会变成色情网站。”这是一个笑话,但我认为它深刻地说明了人类偏好的测量和优化,根据你的时间范围,如果你以用户为中心,你真的专注于满足即时边缘用户的需求或愿望,你最终会得到一个不是很关键,不是很有前瞻性,非常深思熟虑,可能有很多你没有想到的副作用的产品。我真正喜欢多利益主体方法的是,它含蓄地说,“不,不。你现在看到的是这个等式的一个角落,这里有一个更大的术语集要考虑。”
西尔维娅(00:33:42):
是的,是的,我很喜欢你的说法,你的例子非常引人注目。我认为有几件事可以从中吸取教训。一个问题是,当然,你在寻找代理,但是代理到底是什么?对于用户偏好,如果你观察到的用户行为真的是为了揭示他们潜在的偏好,我们能对这样的情况或用户可能有更多考虑过的偏好的情况说些什么呢?那么,如果他们真的知道这个系统是怎样的,这个案例中的测试是如何发现一些偏好的呢?也许他们想改变这种情况?他们会希望有一个控制机构来控制系统如何发展以及如何向他们展示什么吗?他们想按自己的口味行事吗?我认为这是一个非常有趣的问题。当然,还有一个问题是,在这种情况下,以可能违背人们透露的选择行为的方式给他们打分,是否可以接受,或者在伦理上是否合适。
耶雷米(00:35:09):
是啊。
西尔维娅(00:35:10):
你是否认为应该展示更多,比如说你在一个电子商务网站上,展示更多环保、更可持续的选择,即使你的购买行为表明你真的在追求最便宜的产品,而不是关注环境的可持续性?
Jeremie (00:35:32):
你是否认为,从这段对话中,我们提到的参数之一似乎是隐含的时间范围,这似乎是说,如果我从现在开始为自己的幸福进行优化,我将采取一系列行动,这些行动可能会遵循阻力最小的路径。所以我会在每个增量时间步做最简单的事情。如果你在很长一段时间内把这些加起来,最终我会在某个赌场输掉我一生的积蓄。
耶雷米(00:36:09):
从这个意义上来说,它确实遵循了边缘系统。但如果时间范围变长,我开始说,“好吧,我现在对自己有信托义务,从现在起 10 分钟。”好吧。好吧,也许 10 分钟后我会做些不同的事情。当你开始延长时间范围时,很快你就会进入放弃现在的价值而选择未来的价值,等等,并且考虑进去,这几乎就像是在这样做的过程中,你在向以前的自己致敬,是你在很久以前设定了这个行动过程。
Jeremie (00:36:43):
我本科毕业是因为我自己做的一个决定,在我开始攻读学位的四年前,我兑现了这个决定。那个人知道这将是一个痛苦的转变。每一步我都决定说,“是的,有一条阻力最小的路,那就是退出,躺在沙发上。但我不打算朝那个方向走。”这看起来就像这个框架所做的,它开始引入时间范围,我想知道这是否是一个有用的参数,用来参数化不同的推荐系统,并通过每个系统的伦理含义?
Silvia (00:37:16):
这是我目前的情况,可能正在思考这个问题。我认为你触及了跨期选择的深刻问题。再说一次,我们还是以前的我们吗?事实上,推荐系统通常会根据当前的查询或我们与系统的当前交互给出针对我们的推荐,这意味着时间非常非常短。所以这真的是一条阻力最小的路。例如,如果系统对最大化您在平台上花费的时间或优化点击行为感兴趣,这确实可以挖掘阻力最小的路径,这似乎是当前用户最满意的。但实际上,这并不符合用户在更长时间内的最佳利益,也不符合社会最佳利益。
西尔维亚(00:38:27):
我认为这有几个方面。当然,从伦理上来说,有一个问题是选择合适的障碍级别,我们正在为用户的兴趣捕捉真正合适的粒度级别。因此,很短的时间框架似乎是错误的,因为或者至少可能取决于应用程序。但在许多应用中,这似乎是错误的,因为最大化你的即时满足可能意味着你放弃了其他你认为更有价值但需要更多克制的商品。
Silvia (00:39:06):
例如,如果我们试图最大限度地增加你在视频流媒体平台上花费的时间,这可能真的是你现在享受但可能会后悔的事情。但另一方面,选择一个非常非常长的时间范围可能是不切实际的,因为我们没有太多的数据,也更难框定这个问题。而且,这可能是有问题的,因为这意味着我们将对什么对你有益的评估与你当前的观点捆绑在一起,而这可能会随着时间的推移而改变。所以一定有办法找到正确的地点。这是一个非常有趣的问题。
Jeremie (00:39:53):
从系统设计的角度来看,这真的很吸引人。如果你是一家创建推荐系统的初创公司,这意味着什么?让我印象深刻的一件事是,我们有一种方法来解释长期用户偏好,所以我们正在谈论的那种,比如说长 t,长δt 偏好与短 t 偏好。例如,如果你去 Twitter,你可以随时改变你的设置。如果你去亚马逊,我相信你也可以在那里改变你的设置。
Jeremie (00:40:26):
我觉得我们所做的是我们已经抽象出了长期的部分,我们要去的部分,我想让这个平台把我变成什么样的人?我们把它移到设置部分。问题是,我们的互动几乎一成不变,就像我上 Twitter 时,我不是从我的设置页面开始,而是开始淹没在意见和病毒的海洋中。也是这些迷因被四处传播,每个人都试图说服我。我不会从眼前的长远目标开始。但是,我想,从技术上来说,我可以进入设置,开始更广泛地定义我的经历。
杰雷米(00:41:06):
从实际的角度来看,我想知道,这是否可能是对公司的建议的一部分,甚至可以说,“看,设置,也许应该是你更尊重的东西,或者提醒人们定期查看他们的设置,如果他们担心他们的注意力持续时间,他们在平台上的体验,诸如此类的事情。”这看起来像是一个合理的推断吗?
西尔维亚(00:41:31):
看起来很合理。我还认为,虽然平台给你一些选择是非常重要和有价值的,你可以去设置页面,看看系统如何模拟你,你可以在那里做一些改变。这确实有点像家务杂事。所以我不认为问题是我们应该有更多,有更多的访问和对设置有更多的控制会很好。但是拥有它并不一定能解决我们目前面临的许多问题。它需要更靠前、更居中,成为用户体验的一部分。
西尔维娅(00:42:26):
至少我喜欢的一个例子是,一些推荐系统似乎在增强用户能力方面工作得很好,让他们对系统如何为他们工作有更多的自主权和控制权,并与他们一起帮助他们开发他们的推荐,这就是音乐推荐系统。我认为这是一个非常有趣的案例,系统被设计成这样一种方式,用户真的可以与它互动。通过观察它如何响应他们的倾听行为,了解他们自己的口味,也可以了解如何影响他们的建议。
耶雷米(00:43:19):
对。
Silvia (00:43:20):
这可能是人们与 Spotify 或其他平台互动的一种方式。我要说的是,在这种情况下,我们不仅应该有访问控制的能力,而且我们应该有这样做的积极动机,我们应该成为活动的一部分。所以只要是你必须做的事情,比如洗衣服或者做其他事情。这似乎是一个额外的负担,对我们来说并没有什么回报。或者这是我们在网上体验的一个有趣的部分。但这真的可以,这真的可以是一种方式,不仅可以获得令人满意的建议,还可以了解我们自己。我们如何与不同的事物互动,这些事物如何带给我们满足感,以及我们能影响这个过程的感觉。
Silvia (00:44:24):
这非常重要,因为从本质上来说,我真的想强调这一点,推荐系统虽然用户总是最重要的,但他们是社会基础设施。看不到所有这些意味着我们失去了很多关于如何通过这些系统发展社会互动的责任和机构。这一点,我认为非常非常重要。毫无疑问,我认为如果建立推荐系统的公司和开发者在设计时更多地考虑到这一点,这将是一个非常好的消息,这不仅是因为隐私原因或因为这是一个好的实践,而且因为它真的增强了推荐者的体验。
耶雷米(00:45:15):
是的,我真的很想知道,这种行为对长期利润的影响有多大?我们已经看到推特因为各种政治分歧的事情受到攻击,然后在脸书也是一样。似乎通过优化短期偏好,而不是以你在这里描述的方式思考,我们现在得到的是一个生态系统,至少开始以不同的方式来看待。我已经看到人们开始喜欢习惯吸烟的 Instagram 和社交媒体。我们有,不是社交网络,但不管那部电影是什么。总之,网飞的那个大的。
西尔维亚(00:45:56):
社会困境。
Jeremie (00:45:57):
社会困境,谢谢。是的,我们已经开始讨论这些事情了。我认为这将在很长一段时间内影响这些公司的底线,从某种程度上来说,这可能只是善心很多行动基本上他们已经为短期奖励进行了优化,他们认为这是一个很好的措施。他们认为这是需要优化的事情,但他们认识到,实际上,从长远来看,这与可持续性和可持续增长无关。我希望是这样,我的一个担忧是,也许你只是获得了太多的竞争优势,以至于抵消了那些长期影响。不知道大家对此有什么想法?也许这更多的是商业方面的问题,但是-
Silvia (00:46:40):
是的,这肯定更多地涉及到商业方面,我不是这方面的专家,我当然希望这对我们大家都有好处,我们应该摆脱短期展望,努力实现短期收益最大化。我认为即使在推荐系统内部也有相似之处,一些问题甚至被当前的平台所意识到。一个非常突出的事实是,它们可能会产生回音室或不同类型的反馈环路效应。事实上,在几种类型的推荐系统上的一些受欢迎的项目可能具有不成比例的曝光,这可能是受欢迎的东西的循环,它得到更多的推荐。这被广泛认为是一个大问题。
西尔维娅(00:47:52):
我认为导致这类事情的原因之一就是对短期目标的关注。因此,如果你是一个用户,你正在登录,那么对你来说,向你展示流行的东西是一个更安全的赌注,这可能与质量相关。因为你可能认为,实际上,你可以将这个问题形式化,并试图证明如果很多人喜欢这个项目,那么它更有可能具有良好的质量。
Silvia (00:48:32):
但是在推荐系统的情况下,这显然会产生病理效应。因为这种从许多人喜欢的事实中提取价值的群体智慧是有效的,如果每个人都可以访问关于有什么东西的所有信息,如果观察到你喜欢一个项目与它是否被推荐给你无关。但显然,事实并非如此。因此,相反,我们有这样的反馈效应,试图利用一个项目受欢迎的事实,但推断它一定是好质量的,实际上引导我们到这样的例子,即使是质量不好的项目,例如,因为它们是假新闻,或者不是真正相关的东西越来越多地得到推荐,因为它们在过去已经被更多的用户看到和互动。
Silvia (00:49:39):
我认为这也是一个我们必须决定是关注短期利益还是从更长远的角度来看待问题的例子。所以从短期来看,你最好的选择可能是购买热门商品。但是这降低了整个系统的质量。所以现在你选择的东西,你选择只是因为它受欢迎,你的选择并没有真正揭示系统本身和其他用户对你的兴趣,只是加强了一个信号,是的。
Silvia (00:50:17):
那么,我们如何采取更长远的方法来保存信息呢?所以这也可能意味着用户自己需要承担更多的责任。这可能是用户难以接受的事情,或者推荐系统提供商难以解释有时推荐是可能的,因此用户也有责任维护系统本身的质量。你与某件商品互动或可能喜欢某件商品的事实,正在向其他人发送一个信号,并影响着这件商品会被推荐给其他人的人和程度。所以你确实对他人负有某种责任。因此,在推荐和我们如何从用户行为中学习时,需要考虑到这一点。
杰雷米(00:51:23):
平台与平台之间有太多的差异,以 Twitter 为例,机器人似乎占了很大一部分,我和一些人交谈过,他们怀疑大约在 2016 年,他认为 Twitter 上的大部分活动可能是机器人活动,不知道这如何转化为 2020 年的术语。但我想情况只会变得更糟。在这种程度上,你有 Twitter,它有一大堆问题,脸书有另一个,然后你有 Instagram,你有 Snapchat,你有亚马逊。在某种程度上,我想,任何一种政策行动或政府对这些事情的监管的挑战之一是,你需要这样一种快速,深思熟虑的反应,而政府往往没有工具去做,他们只是没有那种技术或道德思想家可以快速反应。所以你认为这是一个基本上全靠公司和个人来扭转局面的领域吗?或者,在试图将系统产生的一些外部性内部化方面,政府可以发挥作用吗?
西尔维亚(00:52:29):
嗯,好问题。我确实认为机构行为者可以发挥作用,原因很简单,把责任推给用户可能不公平,因为用户掌握的信息很少。也几乎没有时间投资于弄清楚它们相互作用的系统的系统效应将如何对社会发挥作用。因此,可能的情况是,用户[can viewers 00:53:12]对如何进行推荐更负责任、更知情。推荐可以变得更加透明,并且这在给予用户对轨迹的更多控制方面可以具有积极的效果。但这并不意味着用户真的有责任监督系统的良好运行。例如,您可以发出信号,表明某个帐户可能是一个机器人,或者可能表现出可疑行为。
Silvia (00:53:47):
但实际上,作为一名用户,你并不适合经常这样做,因为你只能看到该帐户活动的非常有限的一部分。你对这种情况如何会产生负面影响的认识非常有限。另一方面,将所有这些责任交给平台自己解决,显然也有非常非常严重的问题。一是我们可能不希望平台成为什么是好的在线行为的仲裁者。我们可能不希望平台决定审查什么样的账户或什么样的言论。或者我们甚至不希望平台来决定如何构建市场,也许应该有限制。因此,不能指望平台或用户真的承担更多责任,但不能指望他们解决问题。所以,我真的认为机构行为者需要介入。他们可能采取的方式是设计监管,让外部参与者更容易观察到这些平台上正在发生的事情并进行干预。
耶雷米(00:55:11):
好的,这很有趣。我只是想问,如果是这样的话,因为你说的是机构行为者,而不是政府,我认为这很有趣。我知道你在人类未来研究所工作。所以这就像一个有趣的中间地带,它不是政府政策,但也不是私营部门的事情,它介于两者之间。你是否看到了人工智能或不同组织的合作伙伴关系,这些组织可能拥有干预的技术能力和政策智慧,而不一定有行动缓慢的政府的体制包袱?你认为这些是实现这一目标的工具吗?
Silvia (00:55:47):
是的,我认为公民社会应该在管理技术方面发挥更大的作用。现在,我不是一个政治理论家,我可以作为一个哲学家在这里发言。
耶雷米(00:56:06):
是的。
Silvia (00:56:07):
我确实认为公民社会需要更多的评价。有几个原因。因此,政府可以通过设计赋予公民社会更多监督权的监管来帮助实现这一目标。我尤其想到了社交媒体平台,在那里,推荐系统显然对塑造政治话语有着如此大的影响。我们越来越多地看到这种情况,市场也是如此。目前,一个大问题是驱动系统的算法是专有的,无法直接观察到。
耶雷米(00:56:53):
对。
西尔维娅(00:56:54):
正如我们所说,用户对正在发生的事情知之甚少,因为你只能看到整个系统的一小部分。不能指望平台承担全部责任,因为存在利益冲突,也因为它们可能没有合适的能力。但是对于民间社会的介入,我想说的是机构行为者,我想保持中立,不管他们是附属于政府还是更广泛的民间社会,需要能够审计这些系统并观察它们实际上是如何工作的。
Silvia (00:57:36):
通过这种方式,他们还可以让个人用户和其他利益相关者了解他们的在线体验。例如,如果我是一个公民社会组织或一个研究团队,想要研究信息(例如,关于疫苗接种项目的信息)是如何在网上传播的,我可能需要访问,例如,当用户登录他们的社交媒体帐户时,一个社交媒体源会将不同的信息发送给他们。有各种方法可以做到这一点。目前,这些限制非常非常严格。因此,举例来说,要求用户分享他们的数据,并要求用户让外部研究人员访问他们在网上看到的内容,目前是不允许的,部分原因是隐私监管。
西尔维娅(00:58:45):
但如果我们想要有一个整体的了解,并且能够看到提出建议的算法如何影响信息的传播,以及如果我们想要衡量这个算法的社会影响,这是非常重要的。目前,我们只能看到之后的效果,或者我们可以尝试重建它可能的贡献。但这需要以更加透明的方式进行,并以可审计的方式进行。在这一点上,政府的角色实际上是非常重要的,它允许抱歉,通过立法使之成为可能。
Jeremie (00:59:34):
我发现的一件有趣的事情是,无论你从哪个方向看待这些系统,你最终都不可避免地打开了一个蠕虫罐,有问题要问政府,有问题要问哲学家,有问题要问创业者。所以我真的很感谢你能过来分享你对这些事情的迷人观点。如果人们有兴趣跟随你的研究,跟随你的工作和你对此的思考,他们应该去哪里?
Silvia (01:00:01):
这样他们可以访问我的未来研究所网站或我自己的个人网站,了解我工作的最新进展,也可以了解牛津互联网研究所新兴技术治理项目的成果,在该项目中,我与其他研究人员合作,包括 Sandra Mathur 和 Brent Mittelstadt 等人,共同研究推荐系统等新技术的社会影响。
耶雷米(01:00:36):
真棒。非常感谢西尔维娅。我们将确保在博客文章中也包含这些链接,博客文章将在这里与播客一起发布。所以谢谢你的来访,非常感谢。非常有趣的对话。
Silvia (01:00:45):
感谢邀请我。
数据科学中的伦理或:我如何学会开始担心和质疑过程
对最近两本关于数据和偏见的书的评论和讨论
那么,你已经创建了一个模型。也许它具有很高的预测性,您对它在您的目标指标中的表现感到兴奋,并且您自始至终都在严格执行您的流程。你的模式好还是做的好?

我们能用我们的新知识做好事吗?—Gabriele Lasser在 Unsplash 上拍摄的照片
奥尼尔的工作专注于不透明的模型对现实世界的影响,这些模型成指数级增长,最终会伤害到接受手术的人。她将这些模型称为“数学毁灭武器”,因为它们最终不成比例地影响了实力较弱的人,同时在理应无可指责的稀薄空气中创造了产出。通常,这些驱动数据经济并声称基于其数学本质的有效性的模型掩盖了创建它们的人类潜在的易错性。
类似地,Noble 挖掘了谷歌的历史——特别是本质上是推荐系统的搜索结果——并展示了它们反复出现的种族偏见,这些偏见将我们社会中的系统性问题编入了新技术。虽然互联网和新技术往往声称要创造公平的竞争环境,提供一个更美好的世界,但事后往往证明,在前进的道路上,慢一点,少打破一些东西会更好。很多时候,那些以进步和底线的名义被打破的东西,是最被社会剥夺权利的人的背影。
你的模式好还是做的好?
让我们回到你正在做的那个模型。我将使用的数学毁灭武器中的一个例子来进行讨论:法庭上的监禁判决。也许你的任务是创建一个模型,减少种族貌相,确定累犯的可能性,帮助法官确定判决的严重程度。从表面上看,这种模式试图解决我们社会中偏见惩罚和不平等判决的问题。也许你创建的模型,如前所述,对累犯有很高的预测性,并且不使用种族作为特征。
当奥尼尔被要求思考如何与数据科学家开始关于技术伦理的对话时,他想到了这样一个时候,知道大多数有伦理动机的人不会包括种族,并问道:“你的模型使用邮政编码吗?”你是根据一个人的个人特征,还是根据他的成长环境来预测他再犯的可能性的?
这些模型旨在无视种族差异,依赖于关于一个人第一次与警察互动时的年龄以及他们有多少朋友或邻居被判有罪的统计数据。也许他们也使用邮政编码。花点时间思考这些特征,虽然它们可能与累犯相关,但也可能与种族、社会经济地位和其他因素高度相关。事实证明,这些模型在延续他们打算修复的确切系统方面做得很好,但现在增加了一层不透明的应用程序和伪正确性。它可能有一个“高分”,但它对任何人的福祉都没有帮助。
这些书在它们关注的领域里有更多的细节,但是我想简要地谈一谈一些一般的要点。在阅读压迫的算法时,我带走了三个关键的想法:
- 数字红线:数字决策强化了种族定性
- 人工智能将是 21 世纪的主要人权问题
- 决策工具和算法掩盖并加深了不平等
我们的社会有一些系统性和结构性的特征,这些特征是为了维护某种权力动态而产生的。每一项新技术都有机会支持或反对这一体系,除此之外别无选择。只要有可能,试着批判性地思考一个特定的模型或工具将扮演的角色,它将吸收什么特性,以及它将影响谁。我们都会犯错,但是保持批判的眼光和开放的心态是必要的。
同样,奥尼尔在数学毁灭武器的结尾给读者留下了一些重要的收获,即:
- 数据伦理可能与公司对底线的关注相冲突
- 许多公司就是建立在这些有问题的模型上的
- 最初受到伤害的基本上都是穷人和权力较小的人
- 要求将公平置于利润之上
- 技术和数据不是万能的
有时很难将批判的社会需求从企业内部的资本主义利润需求中解脱出来。尤其是在特定模式已经到位的情况下,偏离这一模式,转向一个破坏性较小、利润较低的模式,不会赢得很多人的支持。尽可能清晰地传达你的模型、它们的输入、输出和方法,尤其是你可能做出的任何假设。请记住,这些问题无法通过技术解决,它们在社会中无处不在,但这永远不能成为忽视我们在带来积极变化方面所能发挥的作用的借口。
我在数据科学职业生涯早期的收获围绕着许多已经成为数据科学教学核心的想法。
第一个是“垃圾进,垃圾出”的习语这适用于伦理学,因为我建立的模型将依赖于我选择、发现和设计的数据来训练它们。如果所使用的特征依赖于代理或者是系统偏差的指示,我可以预期我的输出也会有问题。当我希望为未来的工作项目收集数据时,这对我意味着什么?在项目开始时,我可以问什么样的问题?
另一种方法是从一开始就试着问自己,如果我的模型是“完美”的,这意味着什么?在产品中会是什么样子,我能预见它会有什么影响,它会帮助或伤害谁?如果我的模型确实如我所愿,是否会有意想不到的后果或应用需要考虑?确定项目范围的这一额外步骤不仅有助于识别潜在的非预期后果,而且有助于设定对最终产品的期望。
最后,我能做些什么来使我的工作对我的团队、涉众和最终可能受其影响的人来说更清晰、更透明、更容易理解?为了在公司内部建立信任,能够传达一个模型是如何建立的,以及可能的应用方式是至关重要的,不仅如此,这些清晰的沟通还应该提供给算法预测的对象。如果我建立的模型将成为影响某人生活的决策的一部分,他们应该有机会知道这个过程中发生了什么。模型和数据并不是无可指责的,它们值得被质疑和仔细检查,而这始于它们是如何构建和实现的。
我们所有人面前都有很长的路要走。在数据科学的早期,这些问题可能被忽视了,但我们最先将它们提到了最前沿。这是一个我们将继续面对的人权问题,我们只会看到更多像脸书解决面部识别诉讼和学校远离有偏见的自动监考这样的故事。作为一名数据科学家和受益于多种特权形式的人,我希望做出努力,创造空间来防止未来的伤害。我们都必须始终考虑任何模型或分析的潜在影响,因为它最终不仅仅是对抽象数字进行操作,还会对现实生活产生影响。
有关我的更多信息,请访问我的网站,并通过 LinkedIn 找到我。
数据科学项目的 ETL 管道
数据工程师和数据科学家的重要技能

杰克·安斯蒂在 Unsplash 上的照片
背景
在从事数据科学项目时,数据科学家最兴奋的是训练一个奇特的机器学习模型来解决一个商业问题。但是在我们深入模型开发之前,“我们使用什么数据来训练我们的 ML 模型?数据存储在哪里?”你可能会问。
这就是 ETL 过程发挥作用的地方。ETL 代表 Extract-Transform-Load,它包括一组程序,包括从各种来源收集数据,转换数据,然后将其存储到一个新的单一数据仓库中,数据分析师和数据科学家可以访问该数据仓库来执行数据科学任务,如数据可视化、统计分析、ML 建模和前端应用程序开发。

(作者创作)
提取
在数据科学项目中,ML 模型或统计分析需要来自不同来源的大量数据,这些数据可能以各种格式存储。这个“提取”步骤的目标是从正确的来源以正确的数量收集正确的数据。
以下是数据源的示例,您可以从中收集原始数据。
- 文本文件:CSV、TXT、XLS、XLSX 等文件还是很常见的存储数据的。导入起来相对简单。
- SQL RDBMS :关系型数据库,如 MySQL、PostgreSQL、MS SQL Server、SQLite 等也很常见。通常,这类数据库包含面板数据,这使它们成为数据科学项目的理想选择。
- PDF :即使 PDF 不是存储数据的好格式,我们仍然可能会不时遇到它们,尤其是当我们处理历史数据时,这些数据只能在扫描文件中获得。熟悉处理 PDF 文件所需的工具可能会成为某些项目的救命稻草。
- 网页:当您需要的数据在您的组织内不可用时,您可能需要在线浏览和搜索数据。许多网页包含有用的数据,这些数据可能成为您分析的重要部分。例如,我们可以抓取亚马逊商品评论数据来构建推荐系统和抓取房屋销售数据来预测房屋价格。
- API :一些数据提供者将他们的数据存储在他们的 API 中(比如 Google API,FRED API,BLS API,RedFin API),公众可以访问这些 API。
改变
数据科学专业人员应该熟悉“垃圾输入,垃圾输出”的概念。任何统计分析和机器学习模型都可以和你输入的数据质量一样好。
应用数据操作和转换来提高数据质量是任何数据科学项目的关键步骤。
下面是我们可以用来处理数据的转换步骤的例子。
- 基本清理:将变量转换成正确的数据格式。例如,如果适用,我们可以将字符串变量转换为日期或数字变量。我们可以删除转录错误并标准化列中的值。
- 特征提取:从现有变量中提取新变量。例如,我们可以从日期变量中提取日、月、季度和年。我们可以从包含产品描述的列中提取产品特征,如品牌、数量、风味、类型和任何相关特征。
- 重新格式化:将数据从 long 转换为 wide,反之亦然。例如,我们希望改变数据的形状,并创建一个将单个列的值转换为多个列的数据透视表
- 连接表:通过一个或多个公共列合并多个数据表,创建一个主表。
- 过滤:有时候,我们的项目只需要数据的一个子集。仅保留相关数据将加快我们的数据转换过程。
- 聚合:对一个或多个列定义的组中的行应用聚合函数,如 average、min、max、median、percentile、sum。例如,我们可以按城市、产品或零售店计算总销售收入。我们可以按周和城市计算平均温度。
以下是您在转换数据时可能会遇到的数据问题的示例。
- 重复:有重复记录是常见问题。我们需要了解相同的记录是否实际上是错误创建的副本。相同的记录也可能是合法的。例如,给定的人可能在同一天在同一家商店购买了相同的产品。如果相同的记录确实是重复的,那么我们可以放心地删除它们。
- 离群值:如果手工输入数据,数据库容易出现数据错别字。例如,如果一个给定的价格是同一产品价格的 10 倍,那么这个价格很可能是一个异常值。识别异常值的一种常用方法是在给定的合计水平中使用百分点范围(10-90 个百分点或 5-95 个百分点)。如果价格在同一产品的百分位数范围之外,这些价格可能是异常值,我们可以进一步调查。
- 缺失数据:数据点可能(1)随机缺失或(2)非随机缺失。(1)如果数据随机且少量丢失(例如,在一些列中有丢失值的行),只要我们仍有足够的数据点来实施我们的分析,就可以安全地移除数据。(2)如果数据不是随机缺失的(例如,一些高薪人士不想在调查中透露他们的收入。所以你可能会得到一个不准确的收入分配)或者一个缺失的值取决于一些其他变量的值(例如,女性通常不想透露她们的年龄。所以年龄变量会受到性别变量的影响),我们需要做的第一件事是回到数据提取步骤,看看我们是否有额外的数据集可以用来填充缺失的数据。如果没有可用的数据,我们可以考虑使用插补方法来填补缺失的数据,如均值插补、回归模型、多重插补和 KNN。(请小心使用这些估算方法,因为它们可能会减少数据的差异。)
- 损坏的文本:这个问题可能是由上游数据收集程序引起的,它使数据看起来很乱。例如,我们可能有缺少州、邮政编码或拼写错误的街道名称的地址数据,这使得识别唯一的家庭地址变得困难。要解决这个问题,我们可能需要与数据管理部门讨论问题,看看他们能否解决这个问题。如果没有,我们需要想出一个变通办法。为了标准化地址数据,我们可以使用 Google Geolocation API 来提取格式良好的位置数据。
负荷
数据加载是 ETL 过程的最后一步。已处理数据的最终目的地可能会根据您的下游分析需求而有所不同。
以下是可以存储已处理数据的目标数据库或数据仓库的示例。
- 平面文件:您可以将数据存储在文本文件中,如 CSV、TXT 和 excel 电子表格。这些类型的数据格式对于没有在计算机上安装数据科学软件的非技术用户来说很容易获得。
- SQL RDBMS : SQL 数据库是一种流行的数据存储,我们可以加载我们处理过的数据。
- 其他数据格式:基于数据分析师和数据科学家使用的数据科学软件,其他流行的数据格式有。PICKLE (Python),。DTA (Stata)和。RDS (R)。
ETL 工具
如果您使用 Python 已经有一段时间了,您可能知道 Python 有各种各样的数据科学库,这些库使 ETL 过程变得很容易。
下面是一些用于 ETL 的 Python 库。
- SQLAlchemy : SQLAlchemy 是一个模块,它允许完全通过 Python 定义模式、编写查询和操作 SQL 数据库。它可以与不同的 SQL 数据库系统交互,如 MySQL、PostgreSQL、MS SQL Server、SQLite。对于希望在一个平台上实现所有任务的数据科学专业人员来说,这是一个巨大的优势。
- Pandas : Pandas 是一个流行的用于数据转换和操作的 Python 库。它使用数据帧将面板数据存储在本地存储器中。它相对易于使用,并具有大量的数据清理、操作和可视化功能。
- PySpark : PySpark 是 Apache Spark 的 Python API,支持所有基本的数据操作功能,如映射、过滤、连接、排序和用户自定义功能。它在大型硬件集群上快速并行处理大量数据的能力是一个关键优势。
- Swifter/Dask:Swifter 和 Dash 都使您能够使用并行处理来处理大量数据,这可以最大限度地提高本地机器的计算能力。这两个软件包也能很好地处理 Pandas DataFrame。
来源:
感谢您的阅读!!!
如果你喜欢这篇文章,并且想请我喝杯咖啡,请点击这里。
您可以注册一个 会员 来解锁我的文章的全部访问权限,并且可以无限制地访问介质上的所有内容。如果你想在我发表新文章时收到电子邮件通知,请 订阅。
ETL 与 ELT 以及“E”的所有错误之处
了解在数据摄取期间执行转换以及提取尚未准备好或准备用于外部消费的数据的含义

ETL(Extract Transform Load)已经成为数据集成过程的代名词,尤其是在数据仓库中。ETL 非常成功,许多工具都专注于此。但是产品/工具公司销售他们的产品,这不一定总是一个更好的架构选择。本文讨论了 ETL 中概念的一些问题——中间的转换 T 和提取 e。
万能的 T
该行业已经意识到,在旅途中执行转换可能会导致问题。此处见、此处见,还有很多其他的参考文献。起初,这听起来可能是件好事。我们准备并加载数据,以便更容易进行进一步的分析和报告。当然,它传达了这一点。一切都很好,除了事情变坏或者需要进行新的分析工作。
- 当事情变得糟糕时,例如 BI 报告或分析输出严重不匹配,以至于偏差是明显的错误,那么有 3 个地方可能会出错。首先,在分析期间,其次,在转换 T 期间,第三,在源系统本身。更糟糕的是,那些从事业务系统工作的人和从事数据分析工作的人之间开始相互指责。如果没有 T,那么就更容易找到问题——无论是在分析期间还是在源中。
- 当为一个分析用例准备的数据在另一个分析用例中作为源被重用时,它可能只是一个错误的起点。更糟糕的是,如果这个测试是基于某个组织范围的规范数据模型进行的。如果没有 T,分析团队将执行特定于上下文的转换。
- 在摄取期间执行的 T 可能会修改不再与源数据匹配的数据。这可能会导致对数据的误解,因为源系统中的人无法解释它,如果没有正确的 T 血统和文档,数据接收人员也可能无法解释它。这可能是错误的另一个原因。
- 此外,业务总是在变化,客户需求在变化,因此 IT 系统和底层数据也在变化。纳入这一变化要求数据摄取团队调整他们在摄取过程中放置的 T。这种适应将在所有下游分析中产生连锁反应。避免这种情况将需要所有的分析工作来适应新的 T,因为新的数据进来了——这反过来意味着灵活性大大降低。
- 执行测试的人需要源系统和目标系统的领域知识(例如数据仓库)。由于这些原因,一个数据摄取团队对不属于他们的数据执行 T 类似于一个可疑的集成团队 ( 是一个独立的主题)。
我们能做得更好吗?也许吧。
保持数据管道简单
另一方面,ELT,Extract Load Transform,确保装载到 staging 的数据总是与源系统中的数据相同。然后,由每个分析工作量来获取数据并应用特定于上下文测试。注意,这里的关键字是“特定于上下文的”。每个工作负载的起点是原始数据,因此是每个分析团队,并与源系统团队进行交互,以进行解释或任何其他合作。摄取团队的中间人在这样的会议中消失了。
这使得摄取团队的工作也变得更容易,因为他们不再是中间人,而这很可能是没有人想成为的。他们只创建数据管道,并对其进行监控,确保他们做到这一点。
把这比作邮递员送信。这里的邮递员是数据摄取的一个类比。邮递员只是“按原样”投递信件。如果他/她开始拆信,修改错别字和文本,然后做翻译,我想没有人会喜欢这样。收件人的工作是按照要求做这些事情。
所以 ELT 更好。但是还有一个问题,这个问题是关于 e 的概念。
为什么不去 E?
使用 E 或 Extract,数据管道从源系统中“提取”数据,这在大多数情况下直接发生在底层数据库或数据存储中。你现在明白问题了吗?
我看到几个。
- 公开和共享应用程序的底层数据会产生严重的依赖性。这意味着应用程序团队需要意识到一些数据湖/数据仓库正在获取这些数据,如果他们改变数据模型,可能会在那里引起麻烦。它带走了独立。它降低了应用程序团队的敏捷性,使他们无法快速响应新的客户需求。
- 这种依赖导致应用平台和数据平台的所有权的不明确。数据模型和数据应该因为业务流程中有新的需求而更新,还是应该因为有新的分析需求而更新?在这两种情况下,另一方都会受到不必要的影响。
- e 表示外部上的数据很可能与内部上的数据相同。这意味着消费者接受应用程序(和域)的内部表示。这意味着某些数据可能没有标准化,即使用某种特定的日期格式,而不是 UNIX 秒(或毫秒)或 ISO 日期格式。这也意味着身份可能是内部的,例如应用程序的内部客户 id,而不是来自客户主数据系统的客户 id。**
- 此外,内部的数据是“可变的”,因为有一个应用程序在其上运行。这意味着数据可能会更新,模型也可能会改变(尤其是在无模式数据库上,例如 Mongo DB、Cosmos DB 或 Dynamo DB 等。).这会造成不匹配,数据管道会出错而失败。不是每一个变化都应该或者需要反映到外面。但是,通过在内部公开数据,应用程序中的每一项更改都需要更新数据管道和存储区域,并且可能进一步更新分析工作负载。**
更好的方法
它们应该只读取可用的内容,而不是通过数据管道来提取数据。应用程序应该公开它们为“外部消费”准备的数据,并以确保隐含期望(例如,质量、可信度、可互操作性等)的方式公开。)的任何此类暴露数据。
应用程序有义务实现产品思维,并确保公开的数据质量良好,基于任何适用的数据标准进行标准化,并遵循相关的模式定义。实际上,这种方法可以是 API 或事件,甚至可以是包含供外部使用的数据副本的中间辅助数据库。
无论采用哪种方法,最重要的是应用程序公开它“准备好”供“外部”使用的数据,而不是数据管道直接从源数据库中提取它想要的任何数据。
数据管道不再执行任何操作。它们仅通过调用 API 或订阅事件等方式将数据加载到 staging 中。
回到邮递员的比喻,他/她递送已经提供给他/她的东西。那也在一个有适当邮票的包裹里。邮递员不会走进人们的房子,在你写信的时候拖着信去投递。作为一个作家,你可以写尽可能多的信,重写,但只投递那封必须装在贴有邮票的信封里的信。邮差挑的就是那个。
换句话说,一个邮递员既不执行 E,也不执行 t。
所以,接下来就是 LT 了,以后需要的人可以使用它。
欧拉周期:为什么它们如此独特,它们对 21 世纪的我们有意义吗?
欧拉圈和路是图论中最有影响力的概念之一。然而,什么是真正的欧拉周期和路径,18 世纪的路径对未来的 21 世纪有什么意义?
欧拉圈和路是迄今为止在数学和创新技术领域最有影响力的图论概念之一。欧拉在 1736 年首次发现了这些回路和路径,因此命名为“欧拉回路”和“欧拉路径”谈到图论,理解图形和创建图形比看起来稍微复杂一些。有许多变量需要考虑,使它们看起来更像一个难题,而不是一个实际问题。然而,当我们谈论欧拉周期和路径时,相对容易理解发生了什么。

欧拉周期示例|作者图片
欧拉路径是图中每条边只被访问一次的路径。欧拉路径可以有任何起点和任何终点;但是,最常见的欧拉路径会返回到起始顶点。如果一个图本身满足两个条件:所有具有非零度边的顶点都是连通的,并且如果零个或两个顶点具有奇数度并且所有其他顶点具有偶数度,那么我们可以很容易地检测出该图中的欧拉路径。请注意,在无向图中不可能只有一个奇数度的顶点(欧拉路径在无向图中很常见),因为无向图中所有度的总和总是偶数。但是你可能会问,“我们怎么知道一个顶点的度数是奇数还是偶数?”。对于那些不了解图的度数的人来说,求顶点的度数不同于求典型角度的度数。如果一个顶点的边的总数是奇数,则称该顶点的度数为奇数。但是,如果一个顶点的边的总数是偶数,则称该顶点的度数为偶数。这个奇偶顶点条件允许我们理解一个给定的图是否是欧拉的。
要知道一个图是否是欧拉的,或者换句话说,要知道一个图是否有欧拉圈,我们必须知道图的顶点必须位于每条边被访问一次的地方,并且最后一条边又回到起始顶点。欧拉循环本质上只是欧拉路径的扩展定义。如果它看起来令人困惑,那么想象一下,“有没有可能不用举起铅笔或钢笔(一笔)就能画出图形?”。欧拉周期通常并不常见,这是因为它的一个顶点条件。还记得一个顶点的偶数度和奇数度吗?是的,对于一个欧拉图形来说,所有的顶点都必须是偶数,因为没有一个“特定的”顶点代表图形的中间。此外,每个顶点有偶数个边允许我们遍历图形并返回到开始的顶点,不管我们选择哪个顶点作为开始点,这是我们定义的主要目标。
如果你没有得到,不要担心;让我们来看一个小的视觉效果,看看我们如何用一个改进的深度优先遍历算法遍历并找到一个欧拉路径和一个欧拉环。我们将使用深度优先遍历来遍历与上图中相同的图,因为我们希望按深度而不是横向遍历路径,因为这样效率会很低。
在欧拉路径中,每当我们访问一个顶点时,我们会通过两条未访问的边和一个端点。根据定义,欧拉路径中的所有中间顶点都必须是偶数度。然而,我们没有假设所有的中间顶点都有偶数条边,而是假设绝对中间顶点是边最多的顶点。此外,以防万一,如果两个顶点有相同数量的顶点,这是图中最大的,我们可以选择任何一个,因为它不会影响遍历。具有最多边的顶点意味着该顶点是大多数顶点的中间,创建了到图中大多数顶点的路径。因此,我们已经知道,具有最大边数的顶点必须在不同的边上至少经过不止一次才能遍历整个图。
使用深度优先遍历算法,我们从每个顶点开始按深度遍历每条路径。记住,每个顶点只能访问一次;然而,具有最多边的顶点可以被访问多次,以允许图形的完整遍历,因为一些顶点可能不通向对面图形上的其他顶点。该算法将查看顶点(或当前顶点的邻居)是否没有被访问过,并将其添加到路径中。否则,通过使用回溯(跟踪前一个顶点及其边),该算法确保如果当前顶点与最大顶点相同,我们可以将其添加到路径中;否则,如果当前顶点已经被访问过,我们可以离开顶点并继续遍历它的边。这个过程对图中的每个顶点及其邻居重复进行,以创建每个可能的欧拉路径。当然,这个算法有一个误差范围;在寻找欧拉路径时,该算法可以行进到其他顶点,这些顶点可能不是当前顶点的邻居的一部分。为此,最后,我们让算法评估每条路径,并检查最大顶点是否是路径的根顶点,因为最大顶点具有到多个顶点的边。如果是这样,我们可以得出结论,该路径是欧拉路径。

欧拉路径|作者图片
对于欧拉循环,记住任何顶点都可以是中间顶点。因此,根据定义,所有的顶点都必须是偶数度。但是请记住,欧拉回路只是欧拉路径的扩展定义:最后一个顶点必须通向一条未访问的边,这条边又通向起始顶点。由于深度优先遍历的自然行为,最后一个顶点将总是具有未访问的边,直到顶点本身被遍历。我们知道这一点,因为 DFS 算法跟踪已经访问过的顶点。如果最后一个顶点是图中唯一没有被遍历的顶点,那么应该假设它的边是图中唯一没有被访问的边。然而,如果还有多条边,这意味着要么该图不是欧拉图(最后一个顶点有奇数条边),要么有另一个未访问的顶点。因此总结一下,只有一条边通向起始顶点。通过按深度逐个遍历每条边,并获得我们的欧拉路径(因为具有欧拉路径的图被认为是半欧拉的),我们可以比较欧拉路径的最后一个顶点是否具有通向我们开始的顶点的边。如果是这样,这个图就被认为是欧拉图。但是你可能会问,“如果图中没有欧拉路径呢?”。好问题!图中总是有欧拉路径。这一切都是为了以这样一种方式重新排列图形,其中可以创建一条路径,每条边只交叉一次。你可以把它看作是解决一个难题!

欧拉周期|作者图片
这里是我的欧拉周期和路径模拟的链接:https://github.com/GEEGABYTE1/Eulerian
但是为什么欧拉循环和路径如此重要呢?到底遍历每条边一次并在我们开始的地方结束有什么意义呢?欧拉回路和路径有许多实际应用。在数学中,图可以用来解决许多复杂的问题,如哥尼斯堡桥问题。此外,邮递员可以使用欧拉路径来获得一条路线,这样他们就不必原路返回。在更广的范围内,欧拉循环和路径对画家、垃圾收集、飞机驾驶员、GPS 开发者(例如,谷歌地图开发者)、分发广告的营销人员是有用的;本质上任何使用路径的人或事物。
让我们来看看一个流行的图问题,叫做旅行推销员,,作为例子,看看欧拉路径和循环对推销员和女推销员是如何重要的。问题是你有一个城市图,这些城市之间有一定的距离(边权重)。目标是找到一条最短的路径,让您可以游览每个城市一次,然后到达您开始旅行的城市。
为了解决这个问题,并让您了解欧拉路径和循环如何在实际应用中发挥作用,我开发了一个贪婪算法,它可以输出访问所有城市的最具成本效益的路径。
假设销售人员必须前往城市[a,b,c,d],以一定的成本行进一条路径(例如,从点a行进到点d的成本值为 5),该算法评估城市(当前顶点)的每个邻居的权重。我们首先将所有地点设置为unvisited,因为我们实际上还没有遍历它们。现在,我们随机得到一个当前顶点(我们的起点),因为我们希望我们的算法计算每一个可能的情况和路径,在这种情况下,将是 4!(因为我们有 4 个输入)。为了比较边和它们的权重,我们得到权重最小的边(假设它是最便宜的),在将它添加到最终路径后,我们将它设置为当前顶点,最终将返回该路径。就像跟踪边和顶点以查看一个图是否有欧拉路径或循环一样,该算法通过不断更新跟踪未访问和已访问顶点的字典,来跟踪每次当前顶点更新(转到下一个城市)时所有已访问的顶点。我们这样做是因为我们不希望销售人员回到他们已经走过的路径,就像我们不希望我们的遍历算法在寻找欧拉路径时遍历已经遍历过的边一样。
看看我对旅行推销员问题的完整解决方案:https://github.com/GEEGABYTE1/TravelingSalesMan
假设我总结了前两段关于欧拉循环和路径的重要性。在这种情况下,它们是必不可少的,因为它们是最佳寻路或一般寻路的基础。这些年来,随着新算法的引入,寻路技术得到了巨大的发展,但是它们到底有什么重要的呢?信不信由你,我们所知道的大多数算法都是寻路算法。使用寻路概念和欧拉路径的最受欢迎的算法之一是谷歌地图及其高效的寻路算法,优步的司机和旅行者寻路算法(当我们订购优步并查看司机的位置时,这是他们的寻路算法在工作!),特斯拉的功能,他们的汽车可以自动驾驶到主人那里,通过互联网路由数据包,等等!你在我刚刚列出的这些算法中发现什么共同点了吗?这些算法减少了为到达期望位置而走不必要的路径和距离的额外工作。通过欧拉路径和循环,这些寻路算法将行进效率引入了一个全新的水平(记住,寻路算法和欧拉路径共享相同的基本行为)。随着技术和算法的不断发展,它们将继续以更快的速度发展。再回头看看旅行推销员问题。欧拉路径通过减少旅行所需的能量和销售人员潜在的总成本,允许销售人员更有效地旅行,而不是让销售人员以随机成本沿着随机路径旅行到每个房子,直到他们完成任务。
欧拉循环和路径在许多领域都是有用的。数学和寻路恰好是我们今天拥有的众多突出和相关的例子之一。这些循环和路径是给我们的日常生活带来突破的许多小型数学模型之一。因此,想象一下其他数学表达式、模型和悖论在我们的生活中创造了许多我们甚至没有注意到的突破!不过不要担心;作为一个对数学和计算机科学充满热情的人,我将继续撰写著名数学家的各种数学概念和当今不断发展的创新技术如何相互关联,共同帮助我们更“高效”地生活!
欧拉路径和 DNA 测序
像柯尼斯堡桥这样的小玩意是如何帮助现代科学家对 DNA 进行排序的

柯尼斯堡大教堂,右边的这座桥是欧拉时代幸存的两座桥之一。|图片由 Gumerov Ildar 提供
DNA 测序是现代研究的重要组成部分。它使许多不同的领域得以发展,包括遗传学、元遗传学和系统发育学。如果没有测序和将 DNA 组装成基因组的能力,现代世界对疾病、其进化和适应,甚至我们的历史/系统发育的理解将会更加松散。
在讨论欧拉优化晨跑如何帮助我们在这一领域取得长足进步之前,有必要为排序和优化问题奠定一些基础。
在这个故事中,我将使用 DNA 这个词来描述作为测序目标的遗传密码和基因,但请记住,类似的过程也适用于 RNA 等其他东西的测序。这种对测序如何工作的描述并不意味着生物信息学家或生物学家每天都在做这个工作。此描述是为我的数据科学家同事准备的,他们希望了解这个过程及其问题,因此,这将是一个非常高层次的概述。如果你是一名生物学家/生物信息学家,你可能仍然有兴趣阅读这篇文章,以获得数据科学家的观点,但它可能值得跳过下一部分。
DNA 测序包括提取和读取样本中的 DNA 链。我是说,这听起来很简单?但是,如何着手执行这项任务呢?以下是 Illumina 测序仪工作原理的高级概述。

DNA 碱基——作者图片
DNA 是一串双链的核酸碱基。这些基地被称为 ACGT,因为他们的名字以这些字母开头。在 RNA 的情况下,T 是尿嘧啶,用字母 u 表示。这些碱基中的每一个只与另一个碱基结合,A 与 T 结合,G 与 c 结合。这两个碱基的结合称为碱基对。我们称之为“DNA”的这些碱基对的长螺旋串,因此,测序的目标是读取这些碱基是什么。因为它们只和另外一对结合,我们不需要阅读两条 DNA 来排序;我们只需要读取一条链,因为另一条链是确定的。
测序的第一步是提取 DNA(这部分使用化学/物理过程完成),并使用超声波将 DNA 断裂成更小的片段。这些较小的片段然后被切割成单链,并在它们的头部和尾部附着小蛋白质。这些头和尾是特别设计的蛋白质,它们与载玻片上相似的匹配蛋白质结合。
然后是一个复制过程,其中特定的酶/蛋白质将匹配的碱基对重新构建到每条单链上,再次形成双链。然后这些双链被分裂成单链,这一过程重复几次,每次增加单链的数量。
在这一点上,我们准备开始测序实际的 DNA!DNA 链的物理解读是由独特的碱基促成的,这些碱基的化学结构中添加了荧光化合物。这些一次添加一个到上一步的簇中,以结合 DNA 链上的下一个可用碱基。使用激光来激发荧光成分,并且对于来自簇的该碱基,存在给定波长的可检测的闪光。重复这一过程,直到所有碱基配对。至此,DNA 的读取完成。
测序仪可以使用这些闪光和发射光的强度/波长来了解读数的质量,因为 DNA 链中的任何不一致都会导致不同颜色的闪光。理想情况下,在这一点上,我们希望看到单一波长的强烈发射,以显示簇中的所有链都与同一对结合,这样我们就可以确定这个读数是准确的。
这一过程使用一种独特的载玻片,称为细胞分析仪和测序机内的检测器。本文中的细胞分析仪可以看作是一个具有网格布局的载玻片。检测器可以被认为是一种特殊类型的数码相机。网格布局对应于检测器的像素,这导致每个 DNA 聚类被测序,从检测器的图像中提取其自己的像素集。处理这些图像以提取序列中的每个碱基对以及该碱基对的质量。
在这一点上,我们有大量的数据以较小的单次读取的形式存在。这些读数是 DNA 链的小段;它们必须被重新组装成更大的块,称为重叠群(contigs,contiguous 的缩写);然后重叠群被组装回靶基因。
那么,我们如何将这些阅读组合成更广泛的序列呢?

序列读取-按作者排列的图像
这是一个足够简单的概念。我们只是将短的阅读重叠,寻找重叠,当它们重叠足够长时,我们假设它们是 DNA 的连续长度。假设有足够多的读取并且这些读取具有足够高的质量,这个过程重复进行;最终,这一过程会正确地重建出相关的基因组。这种暴力方式甚至需要数周时间来组装一个简单的基因组。一定有更好的办法。
将这些短阅读放在一起成为一个目标序列并不像听起来那么困难!多年来,这一领域取得了一些重要进展。解决这一问题的第一个重大进展是将阅读构建成连续序列视为旅行推销员问题。为了实现这一点,从单个读数构建一个图。这些将每个读取表示为一个节点,每个边表示连接的节点(读取)之间的重叠。然后,我们所要解决的就是遍历给定的图,恰好访问所有节点一次!这个恰好访问所有节点一次的过程在形式上被称为哈密尔顿步行。有许多方法可以解决哈密尔顿步行,并且许多方法实现起来很有趣。
我将借此机会在此快速切入正题,谈谈哈密顿行走问题的一个独特实例。它被称为旅行推销员问题。
考虑一个图网络,其中节点代表城市,城市之间的边代表距离。这个想法是每个城市只去一次,走最少的距离。我最喜欢的解决旅行推销员问题的方法。遗传算法是解决哈密尔顿步行的一个有趣的选择!遗传算法是一类机器学习模型,它模拟问题的一组可能的解决方案,并通过多代“繁殖”和“变异”来优化它们。我在下面链接的 Kaggle 上的笔记本来自一个旧的比赛,参与者被要求优化圣诞老人分发礼物的路线。这个问题本质上可以归结为一个主题恰当的年度旅行推销员问题。为了总结本笔记中的代码,构建了一组解决方案。这是随机选择的有序节点列表,没有替换。每个节点之间的距离通过遍历该列表来求和,以产生该个体的最终健康分数。然后,最好的个体被复制到下一代,连同他们解决方案的变异版本。一些被访问的单个节点被交换以产生一个新的被访问节点列表。通过这种对下一代的选择性提升和变异,算法最终收敛于一个解。
https://www.kaggle.com/schlerp/genetic-reindeer-ftw
回到本文的中心主题,不幸的事实是,所有求解方法都是计算密集型的。例如,强力方法需要评估更差的 N!可能的哈密尔顿路径,其中 N 代表图网络中的节点数。您可以看到,随着图中节点数量的增加,这种情况会很快失控。
有没有更好的方法来重建这个图,给我们一个更简单的问题来解决?是啊!有一种重新构建这个问题的方法,可以用一个经过验证且成本低得多的解决方案来解决这个问题。如果我们把重叠部分表示为节点,把读数表示为边,会怎么样?图形网络的这种漂亮而简单的重新排列给了我们一个新的问题要解决。我们现在希望每个边只访问一次,而不是每个节点只访问一次。将短的 DNA 序列排列成更长的连续片段的任务现在不是表示为求解一个哈密尔顿步行而是表示为求解一个欧拉路径!
https://en.wikipedia.org/wiki/Seven_Bridges_of_Königsberg
欧拉在 1736 年经典地定义了欧拉路径,因为他们证明了柯尼斯堡问题的七桥是不可解的。这个问题被表述为:是否有可能从任何地方出发走完柯尼斯堡的所有七座桥?欧拉努力解决这个问题,尽管他尽了最大努力,他还是找不到解决方案,最终认为这是不可能的。然而,作为严谨的数学家,仅仅宣布一个问题不可解是不够的!欧拉设计了一个数学证明,用图形网络来表示这种情况。这个证明本质上可以归结为以下陈述(当谈到无向图时):
欧拉路径只有在图是欧拉的情况下才是可解的,这意味着它有零个或两个奇数边的节点。
直观上,上面的说法可以认为是如下。如果您通过一条边进入一个节点,然后通过另一条边离开,则所有节点都需要偶数条边。沿着这条思路延伸,有两种特殊情况,一个节点可以有奇数条边,一个是开始行走的节点,一个是结束行走的节点!
欧拉路径可以使用 Hierholzer 算法在线性时间内求解!这是对哈密尔顿步行的巨大改进,并且算法的实现更简单!因为这篇文章已经很长了,所以我就不赘述 Hierholzer 算法的各个步骤了。然而,我想把它作为一个小游戏留给用户!
你能不用查就能算出这个算法可能是什么吗(我在下面提供了一个算法描述的链接)?当我了解到这个算法是一个在迷宫中寻找路径的特殊解决方案时,我做了类似的比较。
提示:假设给定的迷宫有一个解决方案,将你的手放在左边(或右边)的墙上,继续走,最终你会找到迷宫的解决方案。
https://en.wikipedia.org/wiki/Eulerian_path#Hierholzer.27s_algorithm
我从中获得了很多乐趣,我希望这篇文章能够激发您的兴趣,让您更深入地了解数据科学这个迷人的领域!请在评论中告诉我关于图网络的其他令人兴奋的应用!
欧洲严格的隐私法是不道德的:GDPR 如何损害医疗质量
意见
要求改革的呼声。
人们意识到,在过去十年的数据革命中,欧洲已经坐失良机,这种意识已经一路蔓延到位于布鲁塞尔的欧洲总部。为了控制损害,欧洲委员会去年发布了一份白皮书[1],强调了人工智能(AI)的重要性。他们在信中写道,欧洲打算:
[..]成为数据经济及其应用创新的全球领导者[..].
熟悉欧洲通用数据保护条例[2]或简称 GDPR 的读者可能会对采用人工智能的计划的规模(数十亿欧元)和广度(从医疗保健到旅游)感到惊讶。例如,《GDPR》第 12 条规定,应处理个人信息
[..]以简明、透明、可理解和容易理解的形式,使用清晰易懂的语言[..].
有人可能会说,人工智能固有的数学本质,以及它的数字表示(包括事后可解释性),回避了用“简单语言”进行描述。由此看来,GDPR 禁止任何形式的消化私人信息的人工智能。此外,GDPR 要求明确同意"自动特征分析和个人决策"(第 22 条)和数据共享。
医疗保健似乎处于欧洲关于人工智能截然不同的政策(吸收还是禁止)之间这一紧张领域的中心。去年,医学界在人工智能方面取得了令人印象深刻的进步,特别是在成像领域,深度学习模型通常优于认证的医生。例如 CT 扫描中的肺癌检测[3],病理切片中的恶性细胞检测[4],以及使用胸部 X 射线的新冠肺炎诊断[5],等等。重要的是,这些例子都依赖于向公众发布新的数据集。这一点至关重要:医学进步,尤其是人工智能的进步,取决于数据的可用性。
随着电子健康记录(几乎)在医疗领域的全面渗透,以及每年国民生产总值的 10%用于医疗保健,世界似乎就在我们的脚下。但现实是,在欧洲,隐私法禁止在没有事先(知情)同意的情况下共享医疗数据——即使是为了研究目的。问题是大多数医学研究关注的是生物特征数据——如血液、医学扫描、医生笔记或组织——这些数据不能匿名。想想法医学的显著成功:即使是最轻微的一滴生物物质,至少在原则上,也可以追溯到个人。这同样适用于医学成像等其他形式。因此,医疗保健数据仍然在筒仓中,通过嗡嗡作响的风扇和嗡嗡作响的磁盘的永久噪音实现,深深地位于医院的“安全”范围内,安全地远离科学家和创新者。
戈德温定律
那么,我们如何理解欧洲支持隐私的情绪呢?虽然已经过了几代人,但我认为它的回声可以追溯到第二次世界大战。例如,在荷兰,德国人在控制了这个国家后,突击检查了人口普查记录。这些档案表明哪些公民是犹太人后裔,然后被用来追捕犹太人7。据信,这一事实是第二次世界大战期间荷兰大量犹太人被杀的部分原因7。二战结束后,欧盟作为对民族主义的回应而成立,其目标是防止战争暴行重演。
不公正的分歧
欧洲支离破碎的医疗数据格局及其严格的隐私法带来的后果非常严重。最明显的是,中国和美国与欧洲之间在人工智能领域已经迫在眉睫的巨大技术差距将继续扩大。与此同时,欧洲婴儿潮时期出生的人口意味着公民年龄越来越大,需要更多的照顾,而劳动力却在减少。鉴于这些人口统计数据,通过人工智能实现医疗自动化将是确保欧洲人获得应得医疗的关键组成部分。
无论欧洲是否会参与其开发,人工智能的进步仍将进入临床。到目前为止,美国食品和药物管理局(FDA)已经批准了 130 多个人工智能申请[9]。这些产品将很快进入欧洲医生的手中。然而,对于购买中国或美国技术的欧洲人来说,最紧迫的风险将是,训练数据中的任何人口偏差都将在人工智能中表现出来。
知情同意——患者在法律文件上签字同意参加医学研究——是患者参与的一个众所周知的障碍。因此,文化水平较低的人群在医学试验中往往代表性不足。然而,社会经济和地理因素通常对健康和疾病有很大的决定性。因此,知情同意可以通过有偏见的证据导致护理不足,从而加剧社会经济和地理差异。类似地,如果欧洲无法汇编代表其人口的数据集,我们就有可能依赖于具有前所未有的人口偏差的技术。
总之,我们有办法——医疗记录和欧盟的资金——通过人工智能改善欧洲人的健康,减少整个欧洲大陆的痛苦。然而,我们做出了由 GDPR 编纂的政治选择,不这样做。对我来说,这在道德上是不可接受的。
联合学习:出路何在?
为了摆脱这个难题,一些努力正在进行中——比如英伟达的克拉拉(CLARA)为这个法律问题带来了一个技术解决方案:联合学习。人工智能不是共享数据,而是从分散的数据存储库中学习,这些存储库可能是加密的,位于其本地站点。联合学习理论上看起来不错。但是在实践中,不检查数据,几乎不可能进行错误分析,更不用说调试模型了。Ng 教授最近对以数据为中心的人工智能(T4)的强调进一步强调了联合方法的局限性。
补救措施:选择退出

图 1:学习保健系统。图片来源:参考。[11].
为了实现尽可能最好的护理,需要向一个系统的范式转变,在这个系统中,通过数据产生和实现知识是以组织架构为基础的:学习型医疗保健系统【10】。每个接受治疗的患者都是一个新的数据点,一个获得新见解的潜力。有了它,我们不断改进对患者的护理,不断创新和学习(如图 1 所示)。
为了达到这种乌托邦式的医疗保健观点,我们需要废除当前形式的欧洲隐私法。相反,我呼吁(I)使用和共享医疗数据的选择退出系统,而不是知情同意;也就是说,除非您拒绝,否则您的医疗记录将用于改善医疗保健;以及(ii)为人工智能发挥其全部潜力提供足够空间的立法框架。通过生成和共享数据,我们将一起改善人们的生活,不仅是在欧洲,而是在全球范围内。
因此,我呼吁改革。让我们把自己从 GDPR 的枷锁中解放出来,这样科学就能再次证明。
参考文献
[1]:欧洲委员会。"论人工智能——欧洲追求卓越和信任的方法."(2020): 1–26.
[2]: 欧洲议会和理事会 2016 年 4 月 27 日关于在个人数据处理和此类数据自由流动方面保护自然人并废除指令 95/46/EC(一般数据保护条例)的条例(EU)2016/679
[3]: Ardila,Diego,等,“在低剂量胸部计算机断层扫描上使用三维深度学习进行端到端肺癌筛查。”自然医学 25.6(2019):954–961。
[4]:崔、苗、张大卫。“人工智能和计算病理学。”实验室调查 101.4(2021):412–422。
[5]:王、林、、王。“Covid-net:一种定制的深度卷积神经网络,用于从胸部 x 射线图像中检测新冠肺炎病例。”科学报告 10.1(2020):1–12。
[8]:https://EC . Europa . eu/Eurostat/statistics-explained/index . PHP?title =老龄化 _ 欧洲 - 统计 _ 人口 _ 发展
[9]:吴,埃里克,等,“如何评估医疗人工智能设备:限制和建议,从一个分析,美国食品和药物管理局的批准。”自然医学 27.4(2021):582–584。
[10]:福利、汤姆和弗格斯·费尔迈克尔。“学习医疗保健系统的潜力。”学习保健项目(2015 年)。
[11]:学习指南:临床数据导论,医疗保健专业化中的 AI,斯坦福大学。
用 Python 中的相似性度量评估对立实例
使用相似性度量来查看哪些攻击对图像的改变最大

乔恩·泰森在 Unsplash 上的照片
对于分类神经网络,一个相反的例子是一个输入图像被扰乱(或有策略地修改),使得它被故意错误地分类。有各种算法利用给定分类模型的内部工作(梯度和特征图),并修改输入图像,使其要么只是误分类(无目标攻击),要么总是误分类到特定类别(有目标攻击)。
在本文中,我们将研究一些白盒攻击(在了解模型的内部工作原理后生成攻击的算法),并使用相似性度量来指出其中一些攻击的鲁棒性增强。
攻击:
- 快速渐变标志法 (FGSM)
- https://arxiv.org/abs/1706.06083【PGD】
- deep fool
- 卡里尼&瓦格纳 (C & W)
生成攻击
有几个 Python 库已经实现了这些(和其他)攻击算法,并且还提供了现成的模块来为我们的用例生成这些算法。 Cleverhans 、 FoolBox 和 ART 是三个广泛使用并定期维护的开源库,用于对抗性示例(AE)生成。目前,我们使用艺术。
按如下方式安装对抗性鲁棒性工具箱:
pip install adversarial-robustness-toolbox
或者参考官方知识库获取进一步指导。
接下来,我们可以生成如下所示的攻击:
为了生成攻击,我们使用 InceptionNet V3 模型作为算法生成攻击的基础。我们使用在 ImageNet 数据集中找到的真实世界图像,因此也使用在 ImageNet 上预训练的 InceptionNet V3 的 PyTorch 模型。
attack.generate() 方法的输出是一个包含扰动图像的列表,格式与输入相同(采用(通道,宽度,高度)格式,像素值在范围[0,1]内)。

每次攻击的样本图像。作者图片
在上面的图像中,我们可以看到 FGSM 攻击造成了肉眼可见的扰动。据说这是一种相对较弱的攻击。然而 CW 展现的是理想的情况!没有可见的扰动,并且该攻击被证明对分类器比其余的更鲁棒。
接下来,我们可以测量这些被攻击的图像与原始图像的相似性。
对于人眼来说,很容易分辨出两个给定图像的质量有多相似。然而,如果想要量化这种差异,我们需要数学表达式。从余弦相似性到 ERGAS,有几种这样的度量标准可以用来测试图像与其原始版本相比的“质量”。
通常,当从现有图像生成新图像时(在去噪、去模糊或任何此类操作之后),量化再生的不同程度将是有益的。
我们可以把这个应用程序也看作我们的用例。
我们从现有的文献中知道,DeepFool 和 CW 是稳健的攻击,欺骗分类器的成功率更高。它们也很难被检测到,因为它们对目标图像的干扰(或噪声)很小。这些点已经分别使用模型分类精度的降低和图像的视觉外观进行了评估。
但是让我们尝试用这个质量指数来量化后一部分。
阅读更多关于在 Python 中实现这些图像相似性度量的信息。
Python 中的相似性度量
我们将使用 Python 中的 sewar 库来实现一些可用的指标。
从pip install sewar 开始,导入所需的模块,如下所示
除了这些,我们将只使用 PSNR 、尔加斯、 SSIM 和萨姆。我只选择了这几个,因为在像 CW 和 DeepFool 这样的强大攻击中,在上面列出的所有攻击中,只有这几个能够以明显的方式捕捉和放大差异。
进口的 sewar 模块可以直接使用,如
ergas_score = ergas(original, adversarial)
下面你可以看到各种攻击和各种分数的结果。显然,ERGAS 和 SAM 比其他人更能放大不同攻击之间的差异。

四个有效度量的原始图像和敌对图像之间的相似性分数。图片作者。
根据我们的假设,我们看到 CW 攻击图像的相似性得分大于 FGSM/PGD 攻击。这意味着对抗图像比其他不太复杂的攻击更类似于 CW/DeepFool 的原始图像。
请自行尝试其他类型的攻击!
感谢您从头到尾的阅读!您可以通过 LinkedIn联系我,获取任何信息、想法或建议。
评估所有可能的超参数组合-网格搜索-
一个视图中有/没有 Sklearn 库的超参数组合
***Table of Contents*****1\. Introduction
2\. Grid Search without Sklearn Library
3\. Grid Search with Sklearn Library
4\. Grid Search with Validation Dataset
5\. Grid Search with Cross-Validation
6\. Customized Grid Search
7\. Different Cross-Validation types in Grid Search
8\. Nested Cross-Validation
9\. Summary**

1.介绍
每个项目的模型和预处理都是独立的。超参数根据数据集进行调整,对每个项目使用相同的超参数会影响结果的准确性。例如,在逻辑回归算法中有不同的超参数,如、【求解器】、【C】、【惩罚】,这些超参数的不同组合会给出不同的结果。类似地,支持向量机也有可调参数,如 gamma 值、C 值,它们的组合也会给出不同的结果。这些算法的超参数可在 sklearn 网站上获得。开发人员的目标是设计一个具有概化和高精度的模型,因此,检测超参数的最佳组合对于提高精度非常重要。
本文涉及评估超表的所有组合,以提高模型的准确性和结果准确性的可靠性。
2.没有 Sklearn 库的网格搜索
用户要求评估的组合通过 Sklearn 库中的 GridSearchCV 进行测试。事实上,该模型适合每一个单独的组合,揭示了最好的结果和参数。例如,当我们考虑 LogisticRegression 时,如果为 C 选择 4 个不同的值,为惩罚选择 2 个不同的值,则该模型将拟合 8 次,并且每次的结果将表示。现在让我们在不使用 sklearn 库的情况下在癌症数据集上创建网格搜索:
IN[1]
cancer=load_breast_cancer()
cancer_data =cancer.data
cancer_target =cancer.targetIN[2]
x_train,x_test,y_train,y_test=train_test_split(cancer_data,cancer_target,test_size=0.2,random_state=2021)best_lr=0
for C in [0.001,0.1,1,10]:
for penalty in ['l1','l2']:
lr=LogisticRegression(solver='saga',C=C,penalty=penalty)
lr.fit(x_train,y_train)
lr_score=lr.score(x_test,y_test)
print("C: ",C,"penalty:",penalty,'acc {:.3f}'.format(lr_score))
if lr_score>best_lr:
best_lr=lr_score
best_lr_combination=(C,penalty)print("best score LogisticRegression",best_lr)
print("C and penalty",best_lr_combination)
**OUT[2]
C: 0.001 penalty: l1 acc:0.912
C: 0.001 penalty: l2 acc:0.895
C: 0.1 penalty: l1 acc:0.904
C: 0.1 penalty: l2 acc:0.904
C: 1 penalty: l1 acc:0.895
C: 1 penalty: l2 acc:0.904
C: 10 penalty: l1 acc:0.904
C: 10 penalty: l2 acc:0.904
best score LogisticRegression 0.9122807017543859
C and penalty (0.001, 'l1')**
逻辑回归的所有超参数和更多参数可通过此链接访问。
可以看出,为每个组合创建了准确度值。开发人员可以通过选择超参数的最佳组合来提高模型的准确性。OUT[2]表示超参数的最佳组合是 C=0.001,罚值=L1。
让我们使用支持向量分类器和决策树分类器创建相同的过程。
IN[3]
#SVC
best_svc=0
for gamma in [0.001,0.1,1,100]:
for C in[0.01,0.1,1,100]:
svm=SVC(gamma=gamma,C=C)
svm.fit(x_train,y_train)
score=svm.score(x_test,y_test)
#print("gamma:",gamma,"C:",C,"acc",score)
if score>best_svc:
best_svc=score
best_svc_combination=(gamma, C)print("best score SVM",best_svc)
print("gamma and C",best_svc_combination)
**OUT[3]
best score SVM 0.9210526315789473
gamma and C (0.001, 100)**IN[4]
#DT
best_dt=0
for max_depth in [1,2,3,5,7,9,11,13,15]:
dt = DecisionTreeClassifier(max_depth=max_depth, random_state=2021)
dt.fit(x_train,y_train)
dt_score=dt.score(x_test,y_test)
#print("max_depth:",max_depth,dt_score)
if dt_score>best_dt:
best_dt=dt_score
best_dt_depth=(max_depth)
print("best dt_score:",best_dt)
print("best dt depth:", best_dt_depth)
**OUT[4]
best dt_score: 0.9473684210526315
best dt depth: 3**
OUT[3]表示 SVC 的最佳组合是 gamma=0.001,C=100。OUT[4]表示 DTC 的最佳组合是 max_depth=3。
3.使用 Sklearn 库进行网格搜索
让我们使用 sklearn 库做同样的事情:
IN[5]
param_grid_lr = {'C': [0.001,0.1,1,10],'penalty': ['l1','l2']}gs_lr=GridSearchCV(LogisticRegression(solver='saga'),param_grid_lr)x_train,x_test,y_train,y_test=train_test_split(cancer_data,
cancer_target,test_size=0.2,random_state=2021)gs_lr.fit(x_train,y_train)
test_score=gs_lr.score(x_test,y_test)
print("test score:",test_score)
print("best combination: ",gs_lr.best_params_)
print("best score: ", gs_lr.best_score_)
print("best all parameters:",gs_lr.best_estimator_)
print("everything ",gs_lr.cv_results_)
**OUT[5]
test score: 0.9122807017543859
best combination: {'C': 0.001, 'penalty': 'l1'}
best score: 0.9054945054945055
best all parameters: LogisticRegression(C=0.001, penalty='l1', solver='saga')**
如上所述,使用 train_test_split 分割数据集。通过使用 GridSearchCV ,使用具有各种超参数组合的逻辑回归算法对训练数据集进行了训练。可以看出,准确率和最佳参数同上。 GridSearchCV 有很多属性,所有这些都可以在 sklearn 网站上找到。
4.使用验证数据集进行网格搜索
在以前的研究中,数据被分为测试集和训练集。用所有组合尝试训练数据集,并将最高比率应用于测试数据集。不过在这个环节中说明了,用 random 拆分 train_test_split 是一种赌博,不一定能给出可靠的结果。现在,在将数据分为训练集和测试集以增加可靠性之后,让我们将训练数据集分为训练集和验证集。让我们用训练数据集训练模型,用验证数据进行评估,在为模型确定最合适的超参数后,将其应用于最初分配的测试数据集。即使精度值较低,模型也会更一般化。这比假的高精度更可取。
IN[6]
x_valtrain,x_test,y_valtrain,y_test=train_test_split(cancer_data,
cancer_target,test_size=0.2,random_state=2021)x_train,x_val,y_train,y_val=train_test_split(x_valtrain,y_valtrain,
test_size=0.2,random_state=2021)param_grid_lr = {'C': [0.001,0.1,1,10],'penalty': ['l1','l2']}
gs_lr=GridSearchCV(LogisticRegression(solver='saga'),param_grid_lr)
gs_lr.fit(x_train,y_train)
val_score=gs_lr.score(x_val,y_val)print("val score:",val_score)
print("best parameters: ",gs_lr.best_params_)
print("best score: ", gs_lr.best_score_)new_lr=LogisticRegression(solver='saga', C=0.001, penalty='l2').fit(x_valtrain,y_valtrain)
test_score=new_lr.score(x_test,y_test)
print("test score", test_score)
**OUT[6]
val score: 0.9010989010989011
best parameters: {'C': 0.001, 'penalty': 'l2'}
best score: 0.9092465753424659
test score 0.9035087719298246**
用训练数据集(x_train,y_train)对其进行训练,用验证数据集(x_val,y_val)对其进行评估,并确定最佳组合。然后,使用训练数据集+验证数据集的最佳组合创建新模型,使用更多数据,最后,使用在第一次分裂中分配的测试数据集对其进行评估。
同样的过程可以不使用 sklearn 或者按照上面的模板进行其他算法。

JESHOOTS.COM在 Unsplash 上拍照
5.交叉验证网格搜索
数据集分为训练集和测试集。通过交叉验证将训练数据集分离为训练集+验证集。让我们在不使用 sklearn 库来理解系统的情况下实现它:
IN[7]
x_valtrain,x_test,y_valtrain,y_test=train_test_split(cancer_data,
cancer_target,test_size=0.2,random_state=2021)best_lr=0
for C in [0.001,0.1,1,10]:
for penalty in ['l1','l2']:
lr=LogisticRegression(solver='saga',C=C,penalty=penalty)
cv_scores=cross_val_score(lr,x_valtrain,y_valtrain,cv=5)
mean_score=np.mean(cv_scores)
if mean_score>best_lr:
best_lr=mean_score
best_lr_parameters=(C,penalty)print("best score LogisticRegression",best_lr)
print("C and penalty",best_lr_parameters)
print("**************************************")new_cv_lr=LogisticRegression(solver='saga',C=0.001,penalty='l1').fit(x_valtrain,y_valtrain)
new_cv_score=new_cv_lr.score(x_test,y_test)
print('test accuracy:',new_cv_score)
**OUT[7]
best score LogisticRegression 0.9054945054945055
C and penalty (0.001, 'l1')
**************************************
test accuracy: 0.9122807017543859**
将 x_valtrain (训练+验证)数据集分割为 CV=5 的值,并将最初分配的测试数据应用于具有最佳参数的重建模型。
同样的过程可以应用于 sklearn 库:
IN[8]
param_grid_lr = {'C': [0.001,0.1,1,10,100],'penalty': ['l1','l2']}gs_lr=GridSearchCV(LogisticRegression(solver='saga'),param_grid_lr,
cv=5)x_valtrain,x_test,y_valtrain,y_test=train_test_split(cancer_data,
cancer_target,test_size=0.2,random_state=2021)gs_lr.fit(x_valtrain,y_valtrain)
gs_lr_score=gs_lr.score(x_test,y_test)
print('test acc:',gs_lr_score)
print("best parameters: ",gs_lr.best_params_)
print("best score: ", gs_lr.best_score_)
**OUT[8]
test acc: 0.9122807017543859
best parameters: {'C': 0.001, 'penalty': 'l1'}
best score: 0.9054945054945055
best all parameters LogisticRegression(C=0.001, penalty='l1', solver='saga')**
可以看出,获得了相同的结果和相同的最佳参数。
6.定制网格搜索
只要允许,参数组合是可能的。有些参数不能相互组合。例如,当解算器在 LogisticRegression 中选择了:【saga】时,可以应用【L1】【L2】【elastic net】,但对于解算器:【lbfgs】,只能应用【L2】(或【无】)。使用 GridSearch 可以克服这个缺点,如下所示:
IN[9]
x_valtrain,x_test,y_valtrain,y_test=train_test_split(cancer_data,
cancer_target,test_size=0.2,random_state=2021)param_grid_lr=[{'solver':['saga'],'C':[0.1,1,10],'penalty':['elasticnet','l1','l2']},
{'solver':['lbfgs'],'C':[0.1,1,10],'penalty':['l2']}]gs_lr = GridSearchCV(LogisticRegression(),param_grid_lr,cv=5)
gs_lr.fit(x_valtrain,y_valtrain)
gs_lr_score=gs_lr.score(x_test,y_test)
print("test score:",gs_lr_score)
print("best parameters: ",gs_lr.best_params_)
print("best score: ", gs_lr.best_score_)
**OUT[9]
test score: 0.9210526315789473
best parameters: {'C': 1, 'penalty': 'l2', 'solver': 'lbfgs'}
best score: 0.9516483516483516**
根据选定的最佳参数创建新模型,并通过 GridSearchCV 应用测试数据。
7.网格搜索中的不同交叉验证类型
到目前为止,交叉验证已经被实现为 k-fold,但是也可以应用不同的交叉验证方法:
IN[10]
x_valtrain,x_test,y_valtrain,y_test=train_test_split(cancer_data,
cancer_target,test_size=0.2,random_state=2021)param_grid_lr=[{'solver':['saga'],'C':[0.1,1,10],'penalty':['elasticnet','l1','l2']},
{'solver':['lbfgs'],'C':[0.1,1,10],'penalty':['l2']}]IN[11]
gs_lr_loo = GridSearchCV(LogisticRegression(),param_grid_lr,cv=LeaveOneOut())
gs_lr_loo.fit(x_valtrain,y_valtrain)
gs_lr_loo_score=gs_lr_loo.score(x_test,y_test)print("loo-test score:",gs_lr_loo_score)
print("loo-best parameters: ",gs_lr_loo.best_params_)
print("**********************************************")
**OUT[11]
loo-test score: 0.9122807017543859
loo-best parameters: {'C': 0.1, 'penalty': 'l2', 'solver': 'lbfgs'}
************************************************IN[12]
skf = StratifiedKFold(n_splits=5)
gs_lr_skf = GridSearchCV(LogisticRegression(),param_grid_lr,cv=skf)
gs_lr_skf.fit(x_valtrain,y_valtrain)
gs_lr_skf_score=gs_lr_skf.score(x_test,y_test)print("skf-test score:",gs_lr_skf_score)
print("skf-best parameters: ",gs_lr_skf.best_params_)
print("**********************************************")
**OUT[12]
skf-test score: 0.9210526315789473
skf-best parameters: {'C': 1, 'penalty': 'l2', 'solver': 'lbfgs'}
************************************************IN[13]
rkf = RepeatedKFold(n_splits=5, n_repeats=5, random_state=2021)
gs_lr_rkf= GridSearchCV(LogisticRegression(),param_grid_lr,cv=rkf)
gs_lr_rkf.fit(x_valtrain,y_valtrain)
gs_lr_rkf_score=gs_lr_rkf.score(x_test,y_test)print("rkf-test score:",gs_lr_rkf_score)
print("rkf-best parameters: ",gs_lr_rkf.best_params_)
print("**********************************************")
**OUT[13]
rkf-test score: 0.9298245614035088
rkf-best parameters: {'C': 10, 'penalty': 'l2', 'solver': 'lbfgs'}
************************************************
当用 C=10 和罚值= L2重复折叠获得最高精度值时,确定精度值与之接近的所有其他结果的 C 值是不同的。
8.嵌套交叉验证
到目前为止,测试数据已经被 train_test_split 分离,训练数据已经被分离成一个训练集和一个交叉验证的验证集。为了进一步推广这种方法,我们还可以通过交叉验证来拆分测试数据:
IN[14]
param_grid_lr=[{'solver':['saga'],'C':[0.1,1,10],'penalty':['elasticnet','l1','l2']},
{'solver':['lbfgs'],'C':[0.1,1,10],'penalty':['l2']}]gs=GridSearchCV(LogisticRegression(),param_grid_lr,cv=5)
nested_scores=cross_val_score(gs,cancer.data,cancer.target,cv=5)print("nested acc",nested_scores)
print("Average acc: ", nested_scores.mean())
**OUT[14]
nested acc [0.94736842 0.93859649 0.94736842 0.9122807 0.92920354]
Average acc: 0.9349635149821456**
对于解算器='saga' ,有 3x3 个组合,对于解算器='lbfgs' ,有 3x1 个组合。模型在内部交叉验证中拟合了 5 次,在外部交叉验证中拟合了 5 次。
所以,模型的总拟合数为 9x5x5 + 3x5x5 = 300。
9.摘要

图一。标题摘要,按作者分类的图像
网格搜索和交叉验证的缺点是需要很长时间来拟合几十个模型。n_jobs 值可以由用户设置,并且可以分配要使用的 CPU 内核数量。如果设置了 n_jobs=-1,则使用所有可用的 CPU 核心。
回到指引点击此处。
https://ibrahimkovan.medium.com/machine-learning-guideline-959da5c6f73d
评估 Python 中的分类模型
了解分类性能指标

机器学习分类是一种监督学习,其中算法将一组输入映射到离散输出。分类模型在不同的行业中有广泛的应用,并且是监督学习的主要支柱之一。这是因为,在各行各业中,许多分析问题都可以通过将输入映射到一组离散的输出来构建。定义分类问题的简单性使得分类模型具有通用性和行业不可知性。
构建分类模型的一个重要部分是评估模型性能。简而言之,数据科学家需要一种可靠的方法来测试模型对结果的正确预测程度。许多工具可用于评估模型性能;根据你要解决的问题,有些可能比其他的更有用。
例如,如果您的数据准确性中有所有结果的相等表示,那么混淆矩阵可能足以作为性能度量。相反,如果您的数据显示不平衡,这意味着一个或多个结果明显不足,您可能希望使用精度等指标。如果您想了解您的模型在跨越决策阈值时的稳健程度,像接收机工作特性曲线下面积(AUROC)和精度召回曲线下面积(AUPRC)这样的指标可能更合适。
考虑到选择合适的分类指标取决于您试图回答的问题,每个数据科学家都应该熟悉一套分类性能指标。Python 中的 Scikit-Learn 库有一个度量模块,可以轻松快速地计算准确度、精度、AUROC 和 AUPRC。此外,了解如何通过 ROC 曲线、PR 曲线和混淆矩阵可视化模型性能也同样重要。
这里,我们将考虑构建一个简单的分类模型来预测客户流失的概率。客户流失是指一段时间后,客户离开公司、退订或不再购买。我们将使用电信客户流失数据,它包含一个虚构的电信公司的信息。我们的任务是预测客户是否会离开公司,并评估我们的模型执行这项任务的效果如何。
建立分类模型
让我们从将电信客户流失数据读入 Pandas 数据帧开始:
df = pd.read_csv(‘telco_churn.csv’)
现在,让我们显示前五行数据:
df.head()

我们看到数据集包含 21 列,既有分类值又有数值。该数据还包含 7,043 行,对应于 7,043 个不同的客户。
让我们建立一个简单的模型,将任期(客户在公司工作的时间长度)和每月费用作为输入,预测客户流失的可能性。输出列将是 Churn 列,其值为 yes 或 no。
首先,让我们修改目标列,使其具有机器可读的二进制值。我们将为 Churn 列指定值 1 表示是,0 表示否。我们可以通过使用 numpy 中的 where()方法来实现这一点:
import numpy as npdf[‘Churn’] = np.where(df[‘Churn’] == ‘Yes’, 1, 0)
接下来,让我们定义我们的输入和输出:
X = df[[‘tenure’, ‘MonthlyCharges’]]y = df[‘Churn’]
然后,我们可以将我们的数据分开,用于训练和测试。为此,我们需要从 sklearn 的 model_selection 模块中导入 train_test_split 方法。让我们生成一个占我们数据 67%的训练集,然后使用剩余的数据进行测试。测试集由 2,325 个数据点组成:
from sklearn.model_selection import train_test_splitX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
对于我们的分类模型,让我们使用一个简单的逻辑回归模型。让我们从 Sklearn 中的 linear_models 模块导入 LogisticRegression 类:
from sklearn.linear_models import LogisticRegression
现在,让我们定义一个逻辑回归类的实例,并将其存储在一个名为 clf_model 的变量中。然后,我们将使我们的模型适合我们的训练数据:
clf_model = LogisticRegression()clf_model.fit(X_train, y_train)
最后,我们可以对测试数据进行预测,并将预测结果存储在一个名为 y_pred 的变量中:
y_pred = clf_model.predict(X_test)
既然我们已经训练了我们的模型并对测试数据进行了预测,我们需要评估我们的模型表现如何。
准确度&混淆矩阵
一个简单且广泛使用的性能指标是准确性。这就是正确预测的总数除以测试集中的数据点数。我们可以从 Sklearn 中的 metric 模块导入 accuracy_score 方法,计算准确度。accuracy_score 的第一个参数是存储在 y_test 中的实际标签。第二个参数是预测,它存储在 y_pred 中:
from sklearn.metrics import accuracy_scoreprint(“Accuracy: “, accuracy_score(y_test, y_pred))

我们看到我们的模型有 79%的预测准确率。虽然这是有用的,但我们并不真正了解我们的模型具体预测客户流失或不流失有多好。混淆矩阵可以给我们更多的信息,让我们知道我们的模型对每一个结果做得有多好。
如果您的数据不平衡,考虑这个指标是很重要的。例如,如果我们的测试数据有 95 个无客户流失标签和 5 个客户流失标签,通过猜测每个客户的“无客户流失”,它可能会误导给出 95%的准确率。
现在让我们从预测中生成一个混淆矩阵。让我们从 Sklearn 的 metrics 模块导入混淆矩阵包:
from sklearn.metrics import confusion_matrix
让我们生成混淆矩阵数组,并将其存储在一个名为 conmat 的变量中:
conmat = confusion_matrix(y_test, y_pred)
让我们从混淆矩阵数组创建一个数据帧,称为 df_cm:
val = np.mat(conmat)classnames = list(set(y_train))df_cm = pd.DataFrame(val, index=classnames, columns=classnames,)print(df_cm)

现在,让我们使用 Seaborn 热图方法生成混淆矩阵:
import matplotlib.pyplot as pltimport seaborn as snsplt.figure()heatmap = sns.heatmap(df_cm, annot=True, cmap=”Blues”)heatmap.yaxis.set_ticklabels(heatmap.yaxis.get_ticklabels(), rotation=0, ha=’right’)heatmap.xaxis.set_ticklabels(heatmap.xaxis.get_ticklabels(), rotation=45, ha=’right’)plt.ylabel(‘True label’)plt.xlabel(‘Predicted label’)plt.title(‘Churn Logistic Regression Model Results’)plt.show()

那么,这个数字到底告诉了我们什么关于我们的模型的性能呢?沿着混淆矩阵的对角线看,让我们注意数字 1,553 和 289。数字 1,553 对应于模型正确预测不会流失的客户数量(意味着他们会留在公司)。数字 289 对应于模型正确预测会流失的客户数量。
如果我们能把它们显示为总数的百分比,那就更有用了。例如,了解 289 个正确预测的客户占所有搅动的百分比将是有用的。我们可以通过在热图之前添加以下代码行来显示每个结果的百分比:
df_cm = df_cm.astype(‘float’) / df_cm.sum(axis=1)[:, np.newaxis]

正如我们所看到的,我们的模型正确地预测了 91%没有流失的客户和 46%流失的客户。这清楚地说明了使用准确性的局限性,因为它没有给我们关于正确预测结果的百分比的信息。
ROC 曲线& AUROC
很多时候,公司希望使用预测的概率,而不是离散的标签。这允许他们选择将结果标记为阴性或阳性的阈值。在处理概率时,我们需要一种方法来衡量模型在概率阈值上的泛化能力。到目前为止,我们的算法已经使用默认阈值 0.5 分配了二进制标签,但是理想的概率阈值可能会更高或更低,这取决于用例。
在平衡数据的情况下,理想的阈值是 0.5。当我们的数据不平衡时,那么理想的阈值往往会更低。此外,公司有时更喜欢使用概率,而不是完全离散的标签。考虑到预测概率的重要性,了解使用哪些指标来评估它们是很有用的。
AUROC 是一种衡量模型跨越决策阈值的稳健程度的方法。它是真阳性率对假阳性率的曲线下的面积。真阳性率(TPR)为(真阳性)/(真阳性+假阴性)。假阳性率是(假阳性)/(假阳性+真阴性)。
在我们的客户流失问题的背景下,这将衡量我们的模型在不同概率阈值内捕捉不流失客户的能力。
让我们从计算 AUROC 开始。让我们从指标模块导入 roc_curve 和 roc_auc_score 方法:
from sklearn.metrics import roc_curve, roc_auc_score
接下来,让我们使用训练好的模型在测试集上生成预测概率:
y_pred_proba = clf_model.predict_proba(np.array(X_test))[:,1]
然后,我们可以计算不同概率阈值的假阳性率(fpr)、真阳性率(tpr):
fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba)
最后,我们可以绘制我们的 ROC 曲线:
sns.set()plt.plot(fpr, tpr)plt.plot(fpr, fpr, linestyle = ‘ — ‘, color = ‘k’)plt.xlabel(‘False positive rate’)plt.ylabel(‘True positive rate’)AUROC = np.round(roc_auc_score(y_test, y_pred_proba), 2)plt.title(f’Logistic Regression Model ROC curve; AUROC: {AUROC}’);plt.show()

真实正比率越快接近 1,我们的 ROC 曲线的行为就越好。因此,我们的模型在 ROC 曲线方面表现得相当好。此外,0.82 的 AUROC 相当不错,因为完美模型的 AUROC 应为 1.0。我们看到,当使用默认阈值 0.5 时,91%的负面案例(意味着没有客户流失)被我们的模型正确预测,所以这不应该太令人惊讶。
AUPRC(平均精度)
精确回忆曲线下的区域让我们很好地理解了不同决策阈值下的精确度。精度是(真阳性)/(真阳性+假阳性)。回忆是真实阳性率的另一种说法。
在客户流失的情况下,AUPRC(或平均精度)是衡量我们的模型在多大程度上正确预测客户将离开公司,而不是预测客户将留下来,跨越决策阈值。生成精度/召回曲线并计算 AUPRC 与我们对 AUROC 所做的类似:
from sklearn.metrics import precision_recall_curvefrom sklearn.metrics import average_precision_scoreaverage_precision = average_precision_score(y_test, y_test_proba)precision, recall, thresholds = precision_recall_curve(y_test, y_test_proba)plt.plot(recall, precision, marker=’.’, label=’Logistic’)plt.xlabel(‘Recall’)plt.ylabel(‘Precision’)plt.legend()plt.title(f’Precision Recall Curve. AUPRC: {average_precision}’)plt.show()

我们可以看到,AUPRC 为 0.63,我们的精确度/回忆曲线的精确度快速下降,我们的模型在预测客户是否会随着概率阈值的变化而离开方面做得不太好。这个结果是意料之中的,因为我们看到当我们使用默认阈值 0.5 时,只有 46%的流失标签被正确预测。对于那些对数据和代码感兴趣的人来说,Python 脚本可以在这里找到。
结论
跨领域和行业的数据科学家必须对分类性能指标有深刻的理解。了解用于不平衡或平衡数据的指标对于清楚地传达模型的性能非常重要。天真地使用准确性来传达来自不平衡数据训练的模型的结果,可能会误导客户认为他们的模型比实际情况表现得更好。
此外,深入了解预测在实践中的应用也很重要。可能是这样的情况,一家公司寻找离散的结果标签,它可以用来做决定。在其他情况下,公司更感兴趣的是使用概率来做决策,在这种情况下,我们需要评估概率。熟悉评估模型性能的许多角度和方法对于机器学习项目的成功至关重要。
python 中的 Scikit-learn 包方便地为跨行业使用的大多数性能指标提供了工具。这允许您在较短的时间和相对较少的代码行内从多个角度获得模型性能的视图。快速生成混淆矩阵、ROC 曲线和精度/召回曲线使数据科学家能够更快地迭代项目。
无论您是想要快速构建和评估问题的机器学习模型,比较 ML 模型,选择模型特征,还是调整您的机器学习模型,掌握这些分类性能指标的知识都是一项非常宝贵的技能。
如果你有兴趣学习 python 编程的基础知识、Pandas 的数据操作以及 python 中的机器学习,请查看Python for Data Science and Machine Learning:Python 编程、Pandas 和 sci kit-初学者学习教程 。我希望你觉得这篇文章有用/有趣。
本帖原载于 内置博客 。原片可以在这里找到https://builtin.com/data-science/evaluating-classification-models。
停止用准确性来评估你的分类模型
如何解释混淆矩阵以及何时使用不同的评估指标

二元分类的模型评估
理解如何评估模型是任何机器学习(ML)项目的关键部分。很多时候,当我们从 ML 开始,或者当与不精通 ML 的人交谈时,术语准确性被作为模型性能的概括而抛出。谈论一个模型的准确性的问题是,它具体指的是一种计算,将它正确分类的观察值数量除以测试集中的总数量。
虽然当数据集完全平衡(正负类相等)时,精度对于模型性能来说是可以接受的,但事实是这种情况很少发生。有一些处理不平衡数据的方法,我将在后面的帖子中讨论,但现在,让我们了解如何正确地解释模型性能的结果。
更多:关于处理不平衡数据的信息,查看我的另一篇帖子:处理不平衡数据。
解读混淆矩阵
在对模型进行数据训练和测试之后(Scikit-Learn 对这一过程有很好的描述),您可以生成一个混淆矩阵来查看模型是如何被错误分类的。混淆矩阵是一个 2x2 的表格(用于二进制分类),在其轴上绘制了实际值与预测值。一旦你理解了如何解读它,就很容易直观地看到你的模型的表现。

作者图片
该矩阵在左上角和右下角象限显示正确分类的观察值,而在其他角落显示错误分类的观察值。将这些独立地看作行或列有助于您解释每个类。
评估指标
- 准确性:模型的观察百分比在两个类中都被正确预测。不要将此用于不平衡数据。你可以有非常高的准确率,但是小众类的表现可能很恐怖。
- Precision: 告诉你这个类的预测有多可靠。例如,当精度分数高并且模型预测该类时,您可以相信它是正确的。
- 回忆:告诉你模型对类的预测有多好。高召回率和低精度意味着它在该类中表现良好,但是可能在结果中包括其他类。
- F1 得分:计算精确度和召回率的调和平均值,将它们合并成一个指标。
下图是我所见过的说明每个计算是如何执行的最佳方式。一旦你明白你要优化的是什么,你应该使用哪种类型的指标,它也可以帮助你。

作者图片
例如,如果您希望最大限度地减少 1 类的误报,那么1 类精度是适合您的度量标准(图中的为深红色和浅红色)。
注意: 精度是矩阵中列的关系,召回是矩阵中行的关系。
为你想要的结果而优化
选择正确的评估指标的一个关键部分是决定什么是你想要的最重要的结果。我认为这分为三件事:
- 最小化误报:这方面的一个用例可能是垃圾邮件过滤器。理想情况下,您绝不会希望将一个真实的邮件标记为垃圾邮件,并将其归档到垃圾邮件文件夹中。用户错过重要消息的可能性很高;所以,你要减少误报,选择精度高的型号。
- 尽量减少假阴性:这里对我来说最好的例子就是医学筛查。如果算法对病人有假阴性,这将是非常糟糕的,但他们生病了。最好标出潜在的问题,并让专家跟进。在这种情况下,你要重点关注一个高召回。
- 两者皆有:如果你的用例没有偏向其中之一,那么选择 F1 来平衡精度和召回的度量。
实践中的混淆矩阵
这是最近我训练的随机森林算法的混淆矩阵。我的目标是预测用户何时会从 SaaS 的试用账户转为付费账户。该示例是根据 Scikit-Learn 创建的,显示了上述各种评估指标。在我的例子中,正类(1)转换为付费,负类转换失败。看一看你自己,看看你如何解释结果。

作者图片
密码
为了输出上面的混淆矩阵,您可以使用下面的代码
from sklearn.metrics import classification_report
from sklearn.metrics import ConfusionMatrixDisplay
# print classification report
print(classification_report(y_test, y_pred, digits=3))
# show confusion matrix
ConfusionMatrixDisplay.from_predictions(y_test,
y_pred,
cmap=plt.cm.Blues)
plt.show()
有关显示混淆矩阵的更多信息,请参见 Scikit-Learn 文档。
结论
混淆矩阵是评估二元和多类分类模型性能的有力工具。然而,诀窍不仅仅是理解如何解释矩阵;是为了真正理解正确的评价指标来使用。切记不要默认精度;对于你想要的结果,有一个合适的标准!尽情享受吧!
如果你喜欢阅读这样的故事,并想支持我成为一名作家,可以考虑报名成为一名媒体成员。一个月 5 美元,让你可以无限制地访问成千上万篇文章。如果你使用我的链接注册,我会赚一小笔佣金,不需要你额外付费。
评估多标签分类器
以及这些指标在不同场景下如何变化

作者图片
介绍
分类是机器学习的一个重要应用。这是一项预测性建模任务,需要为数据点分配一个类别标签,这意味着该特定数据点属于所分配的类别。
目录
- Accuracy
- The Confusion Matrix
- A multi-label classification example
- Multilabel classification confusion matrix
- Aggregate metrics
- Some Common Scenarios
准确(性)
开发和应用模型是一回事,但是如果没有评估它们的方法,实验很快就会变得毫无意义。
大多数人都知道准确性是什么,即使只是在直观的意义上——某件事情有多准确,指的是它实现某个目标的频率。这个目标可能是一个足球运动员的射门击中目标的频率,或者对明天天气的预测有多准确。
说到分类,它是通过一个模型正确分类数据的频率来衡量的。
简而言之,对于分类问题,精确度可以用以下公式来衡量:
accuracy = number of correct predictions / total predictions
准确性并不能说明全部
这似乎是评估一个模型的好方法——你会期望一个“更好”的模型比一些“不太好”的模型更准确。虽然这通常是正确的,但准确性有时无法给你完整的图像,例如不平衡的数据集。
假设您有属于两个类的数据:red和blue。红色类别拥有大多数数据点。假设他们的比例是9:1。

红色类与蓝色类的比率—作者使用 draw.io
这意味着给定 100 个数据点,90 个将属于类别red,而只有 10 个将属于类别blue。
现在,如果我的模型训练得太差,以至于它总是预测红色,不管它给出什么数据点,该怎么办?
你可能已经明白我的意思了。
在上面的例子中,我的模型的准确率最终是 90%。它会让所有的reds正确,所有的blues错误。因此,精确度将为90 / (90 + 10)或 90% 。
客观地说,这将是一个相当不错的分类精度目标。但是准确性,在这种情况下,隐藏了这样一个事实,即我们的模型实际上什么也没有学到,并且总是预测红色类别。
混乱矩阵
混淆矩阵是一个正确和错误地分为以下几类的矩阵:
- 真阳性(TP): 正确预测阳性类别
- 真阴性(TN): 正确预测阴性类别
- 假阳性(FP): 错误预测阳性类别
- 假阴性(FN): 错误预测阴性类别
使用这些,定义了类似于精度、召回、和 f1 分数的指标,与精度相比,这些指标为我们提供了一个更准确的衡量标准。
回到我们的例子,我们的负类是类red,正类是blue。假设我们在 100 个数据点上测试我们的模型。保持相同的分布,90 个数据点将是red,而 10 个数据点将是blue,并且我们的模型将在所有情况下预测red(负类)。
它的混淆矩阵是:
True positive = 0 (we never predict the positive class)
True negative = 90 (we always predict the negative class)
False positive = 0 (we never predict the positive class)
False Negative = 10 (we labeled the positive class as neg)
计算精度、召回率和 F1 值
**Precision** = TP / (TP + FP)
= 0 / (0 + 0)
= undefined**Recall** = TP / (TP + FN)
= 0 / (0 + 10)
= 0
因此,尽管我的模型的accuracy是90%,一个普遍好的分数,但它的precision是undefined而recall是0,这表明该模型甚至一次都没有预测到正类。
这是一个很好的例子,说明准确性并不能给我们提供全貌。分别对精度和召回率也是如此。
多标签分类
多标签分类是指一个数据点可以被分配到多个类别,并且有许多类别可用的情况。
这与多类分类不同,多类分类是每个数据点只能被分配到一个类,而不考虑可能的类的实际数量。
与多类别分类不同,在多标签分类中,类别并不相互排斥
使用精度、召回率和 f1 分数等指标来评估二进制分类器是非常简单的,所以我不会讨论这个问题。对多标签分类做同样的事情也不太难——只是稍微复杂一点。
为了使它更简单,让我们来看一个简单的例子,我们将在过程中对其进行调整。
一个例子
假设我们有分布在三个类别中的数据——A 类、B 类和 c 类。我们的模型试图将数据点分类到这些类别中。这是一个多标签分类问题,所以这些类不是排他的。
估价
让我们以 3 个数据点作为简单事物的测试集。
expected predicted
A, C A, B
C C
A, B, C B, C
我们先来看看多标签问题的混淆矩阵是什么样子,然后为其中一个类创建一个单独的混淆矩阵作为例子。
我们将使用 sklearn 的MultiLabelBinarizer对类 A、B 和 C 进行编码。因此,每个预测都可以表示为一个三位的字符串,其中第一位代表 A,然后是 B,最后一位是 c。
expected predicted
1 0 1 1 1 0
0 0 1 0 0 1
1 1 1 0 1 1
注
基于一个读者的问题,我想澄清像二进制化器和缩放器这样的变换应该是只适合你的训练集的。当然,您希望在推理过程中应用这些相同的转换,但是它们并不适合新数据。
上面列出的预期和预测标签只是为了直观地了解它们有什么不同。
train, test <- data
transformed_train <- fit + transformtransformed_test <- transform (using the same scaler/binarizer)
基于我们的测试,让我们找出 A 类的混淆矩阵。
A 级
为了计算真阳性,我们正在考虑我们的模型预测标签 A 并且预期标签也包含 A 的情况。
所以 TP 等于 1。
到了 FP,我们寻找那些我们的模型预测了标签 A 但是 A 不在真正的标签中的情况。
所以 FP 是 0。
来到 TN,这是期望标签和预测标签都不包含 a 类的地方。
所以 TN 是 1。
最后,FN 表示 A 是一个预期的标签,但是我们的模型没有预测到它。
所以 FN 是 1。
让我们使用这些值来制作 A 类的混淆矩阵:
TN FPFN TP
我们得到:

A 类的困惑矩阵——作者在 IPad 上写的
可以对其他两个类进行类似的计算。
**Class B:** 1 1
0 1**Class C:** 0 0
1 2
混淆矩阵
像我们刚刚计算的混淆矩阵可以使用 sklearn 的multilabel_confusion_matrix生成。我们简单地传入预期和预测标签(在二进制化它们之后),并从混淆矩阵列表中获取第一个元素——每个类一个。
confusion_matrix_A
= multilabel_confusion_matrix(y_expected, y_pred)[0]
输出与我们的计算一致。
print(confusion_matrix_A)# prints:
1 0
1 1
精确度、召回率和 F1 分数
使用我们刚刚计算的混淆矩阵,让我们作为一个例子来计算 A 类的每个度量。
A 级精度
精确度简单来说就是:
Precision = TP / (TP + FP)
在 A 类的情况下,结果是:
1 / (1 + 0) = 1
召回 A 类
使用如下给出的召回公式:
Recall = TP / (TP + FN)
我们得到:
1 / (1 + 1) = 0.5
F1-A 类分数
这只是我们计算的精度和召回率的调和平均值。

F1 分数的公式—作者使用 draw.io
这给了我们:

计算 F1-A 类分数——作者使用 draw.io
可以用同样的方式为 B 类和 C 类计算这些度量。
在完成所有其他类的测试后,我们得到了以下结果:
B 类
Precision = 0.5
Recall = 1.0
F1-score = 0.667
丙类
Precision = 1.0
Recall = 0.667
F1-score = 0.8
聚合指标
宏观、微观、加权和抽样平均值等聚合指标为我们提供了模型执行情况的高级视图。

我们将要讨论的综合指标——作者在 IPad 上
宏观平均
这只是所有类别中某个指标(精确度、召回率或 f1 分数)的平均值。
在我们的例子中,精度的宏观平均值是
Precision (macro avg)
= (Precision of A + Precision of B + Precision of C) / 3
= 0.833
微平均
通过考虑每个类别的所有 TP、TN、FP 和 FN,将它们相加,然后使用这些来计算指标的微平均值,来计算指标的微平均值
例如,微精度可以是:
micro avg (precision) = sum(Tp) / (sum(TP) + sum(FP))
对于我们的例子,我们最终得到:

Precision 的微观平均值——作者在 IPad 上写的
加权平均
这只是单个类的度量值的平均值,由该类的支持进行加权。

Precision 的加权平均值——作者在 Ipad 上给出的
样本平均值
这里,我们计算每个样本的指标,然后对它们进行平均。在我们的例子中,我们有三个样本。
expected predicted
A, C A, B
C C
A, B, C B, C
对于sample #1,预测了 A 和 B,但是期望的类是 A 和 C
所以这个样本的精度是1 / 2,因为在两个预测标签中,只有一个是正确的。
对于sample #2,预测到了C,预计到了C。
所以这个样本的精度是1——所有预测的标签都是预期的。
对于sample #3,预测了B和C,但所有三个标签都是预期的。
因为所有预测的标签都是预期的,所以精度是1。注意,虽然A没有被预测到,但是丢失的标签不会影响精确度,只会影响召回率。
平均这个,我们得到我们的样本的平均精度。
(1/2 + 1 + 1) / 3 = 5/6 = 0.833
也可以为recall和 f1-score计算这些总量。
分类报告
将所有这些放在一起,我们最终得到了我们的分类报告。我们的计算值与 sklearn 生成的值相匹配。我们就用 sklearn 的metrics.classifiction_report函数。
classification_report(
y_expected,
y_pred,
output_dict=False,
target_names=['class A', 'class B', 'class C']
)

Sklearn 生成的分类报告
一些常见场景
这些是评估多标签分类器时可能出现的一些情况。
在你的测试数据中有重复
真实世界的测试数据可以有副本。如果您不删除它们,它们会如何影响您的模型的性能?评估分类模型时通常使用的聚合指标是平均值的形式。因此,重复的影响归结于这些重复的数据点是否被正确分类。
您的模型只预测了一些预期的标签
当您的模型没有预测每个预期的标签,也没有预测额外的标签时,您会看到较高的精度值和较低的召回值。
无论你的模型预测什么,它都是正确的(高精度),但它并不总是预测预期的(低回忆)。
您的模型预测的标签比预期的多
这与前一种情况相反。由于您的模型预测了额外的标注,这些额外的类将以较低的精度结束(因为这些预测不是预期的)。同时,你的模型也预测了所有预期的标签,所以你最终会得到高的回忆分数。
高精度—高召回率
这是一个理想的场景,精确度和召回率都很高。直观地说,这意味着当我们的模型预测一个特定的标签时,这通常是一个预期的标签,当一个特定的标签是预期的时,我们的模型通常是正确的。
高精度—低召回率
这意味着我们的模型在预测时确实是有选择性的。当数据点特别难以标记时,我们的模型选择不冒预测不正确标签的风险。这意味着当我们的模型预测一个特定的标签时,它往往是正确的(高精度),但反过来就不是这样了(低召回)。
低精度—高召回率
在这种情况下,我们的模型的预测相当宽松。即使不完全确定,也更有可能给数据点分配一个标签。正因为如此,我们的模型很可能会给某些数据点分配不正确的标签,从而导致精度下降。
阈值处理以改善结果
大多数算法使用 0.5 的阈值。这意味着置信度大于 0.5 的预测被认为属于正类,而不考虑置信度较低的预测。
这与整个精确回忆的讨论有什么关系?好吧,想想如果你修改这个阈值会发生什么。
如果你提高你的门槛,你会对你的模型预测更加严格。既然只分配了具有高可信度的预测,那么您的模型在预测类时更有可能是正确的,从而导致高精度。与此同时,您的模型可能会错过置信度较低的预期标签,从而导致较低的召回率。
另一方面,降低模型的分类阈值意味着模型对其预测较为宽松。这将意味着你的模型更有可能预测预期的标签,尽管它们可能是低信心的决定,这意味着你将有很高的回忆。但是现在你的模型不那么严格了,它分配的标签很可能不是预期标签的一部分,导致精度降低。
平衡召回率和精确度
正如我们刚刚看到的,在精确度和召回率之间有一个折衷。如果你使你的模型具有高度选择性,你最终会得到更好的精度,但是面临召回率下降的风险,反之亦然。
在这两个指标之间,什么更重要取决于您试图解决的问题。
医学诊断工具,如皮肤癌检测系统,不能将一个癌症病例标记为非癌症病例。在这里,你会想尽量减少假阴性。这意味着你试图最大化回忆。
同样,如果你考虑一个推荐系统,你更关心的是推荐客户可能不感兴趣的东西,而不是不推荐他们感兴趣的东西。在这里,负面消息不是问题,目标是让内容尽可能相关。因为我们在这里减少了误报,我们关注的是精确度,而不是召回。
注意#1:不断忘记精确和召回的区别?
Jennifer 在 data science StackExchange 网站上的回答中解释了记住精度和召回代表的区别的一个好方法:
https://datascience.stackexchange.com/a/54172
注意#2:除了本文讨论的指标,还有其他指标吗?
绝对的。不同类型的问题有不同的衡量标准,最适合特定的情况。即使对于我们刚刚讨论的情况,即多标签分类,也有另一个称为汉明分数的度量,它评估模型的预测与预期的接近程度。你可以把它看作是多标签分类器的一种更宽容的准确性。
Rahul Agarwal 的这篇优秀的《走向数据科学》文章是一个很好的起点。
示例代码
本文中使用的所有代码都可以在以下位置获得:
https://github.com/Polaris000/BlogCode/tree/main/MetricsMultilabel
结论
使用正确的度量标准评估您的模型是必要的。实验进行到一半时,意识到自己测量的东西是错误的,这可不是一件有趣的事情。
您可以通过找出哪些指标与您的用例最相关来避免这种情况,然后实际理解这些指标是如何计算的以及它们的含义。
希望本文能让您了解如何评估多标签分类器。感谢阅读!
更新
22/21/12
- 修复召回公式中的问题
2/11/21
- 改进措辞
- 添加示例代码
*15/4/22*
- 添加关于转换数据的注释
*18/1/23*
- 修正计算步骤中的一个打字错误
- 突出显示类名以提高可读性




浙公网安备 33010602011771号