TowardsDataScience-博客中文翻译-2022-五十二-

news/2024/10/18 9:41:56

TowardsDataScience 博客中文翻译 2022(五十二)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

一维信号的迁移学习

原文:https://towardsdatascience.com/transfer-learning-with-a-one-dimensional-signal-76a0d543e9aa

加上数据洞察力

图片作者。

目录

——简介
——信号统计
——获取双谱图像
——载荷图像
——创建模型
——训练并评估
——损耗和精度度量
——数据洞察
——结论

介绍

迁移学习重用为旧任务建立的模型作为新任务的起点。模型参数被冻结—对旧任务的训练已经完成。VGG16 就是这样一个训练有素的模型。这是一个卷积神经网络(CNN)对图像进行分类。它赢得了 2014 年 ImageNet 挑战赛。

我们稍微修改 VGG16,使其成为新图像的有效分类器。模型重用大大降低了培训时间和成本。这就是迁移学习的神奇之处。

我们的数据集是从一维信号产生的图像。一维信号是一条侧向弯曲的线。信号幅度随时间上下移动。

信号幅度随时间的变化。信号是一维,时间是二维。

图像是从振动信号中提取的双谱图像。每个都是从 4,095 (2 -1)个数据点段构建的。

显示泵气蚀的双谱图像。

振动信号是以 16,000 Hz 采样的泵加速计数据。工程师记录了三种取样条件。通过透明的泵壳[ 1 ]观察每种情况。

三个信号:

  1. 无气蚀 (nc) —泵正常运行
  2. 低汽蚀【LC】—小气泡云
  3. ****产生气蚀(直流)——大团气泡

以 16000Hz 采样的泵加速计信号。从上到下,条件为无气蚀、低气蚀和发展气蚀。

信号统计

每个加速度计信号有 800,001 个数据点(16,000 Hz 时为 50 秒)。每个的平均振幅为零。

熊猫统计无/低/发展空化信号。

这三个条件的配对图显示它们之间几乎没有相关性。在这个样本中,发展的空化(dc1)状态看起来是高斯型的。我们稍后再讨论这个问题的重要性。

图片作者。( GitHub 要诀)

获取双谱图像

图像和信号保存在这个 GitHub repo 中。要自己创建图像,请遵循以下方法:

  1. 使用joblib.load(‘signals.joblib’)加载每个信号的熊猫数据帧:nc1 和 dc1。将信号分割成不重叠的 4,095 个点段。使用这种技术,一个 800,000 点的信号有 195 个图像。
  2. 作者的双谱代码(下面的双谱 2D)从信号段y [ 2 ]创建双谱图像。在第 104 行实例化 Bispectrum2D。使用freqsample=16,000window_name=’hanning’。Hanning 是用于未知信号的常用窗口。要绘制图像,请使用bs2D.plot_bispec_magnitude()
  3. 保存每个图像。我强烈建议在每个文件名中保存索引。这使得将来分离测试图像变得容易。plt.savefig(f’./images/{feature_name}_hanning_{start_idx}-{end_idx}.png’)
  4. 将图像格式化为 224x224 像素。这是 VGG16 所需的尺寸。
  5. 使用 VGG16 预处理 _ 输入库:

from tensorflow.keras.applications.vgg16 import preprocess_input

它将图像从 RGB 转换到 BGR。此外,“每个颜色通道相对于 ImageNet 数据集以零为中心,没有缩放。”

双谱 2D Python 数据类:

从 1D 信号产生 2D 双谱图像。作者的 Python 数据类。( GitHub )

关于双谱的旁注 2D: 计算是一个独立的实现——不需要 Python 类继承接触点,也不需要术语翻译。作者将两个 Python 类重构为一个 dataclass。双谱 2D 自动从采样频率产生频率范围。

加载图像

每个图像类都有一个文件夹。“训练”文件夹已标记了用于训练模型的图像。有效用于培训期间的模型调整和度量。测试(又名维持)是看不见的图像,以验证模型的普遍性。我们将进行以下二进制分类场景:无空化与直接空化[ 3 ]。

文件夹结构:nc =无气蚀;dc =直接空化。由于担心数据泄漏,作者选择将有效文件夹与列车文件夹分开。Tensorflow API 可以动态地将训练分成训练和验证数据。

从 GitHub repo 下载图像数据集。参见用 Python 编写的迁移学习 Jupyter 笔记本。

我们如何为图像建模?TensorFlow 2 是图像模型的软件工具。

在张量流中创建图像数据集。详情如下。有 210 幅训练图像和 60 幅验证图像。每个中的图像都是互斥的。此外,验证图像在时间上在训练图像之后(信号是时间序列)。

用于模型拟合的图像数据集:

加载 VGG16。获取并可视化数据。

从 RGB 到 BGR 的 VGG16 特定预处理后的双谱图像。

创建模型

让我们回顾一下数据集结构。它将如何与 VGG16 配合使用?

我们一批选择 32 张图片。这些图像是 224 X 224 的。3 表示红色、绿色和蓝色(RGB)通道。

1 train class names: ['dc', 'nc']
2 val class names: ['dc', 'nc']
3 images, xpixels, ypixels, color_channels: (32, 224, 224, 3)
4 labels: (32,)

在下面的第 3 行,我们初始化 VGG16。它有将近 1500 万个参数。它们被设置为不可训练,因此被冻结。此外,最后几个分类层被排除:include_top = False。

Model: "vgg16"
_________________________________________________________________Layer (type)                Output Shape              Param #   
=================================================================input_5 (InputLayer)        [(None, 224, 224, 3)]     0         block1_conv1 (Conv2D)       (None, 224, 224, 64)      1792      block1_conv2 (Conv2D)       (None, 224, 224, 64)      36928     block1_pool (MaxPooling2D)  (None, 112, 112, 64)      0         block2_conv1 (Conv2D)       (None, 112, 112, 128)     73856     block2_conv2 (Conv2D)       (None, 112, 112, 128)     147584    block2_pool (MaxPooling2D)  (None, 56, 56, 128)       0         block3_conv1 (Conv2D)       (None, 56, 56, 256)       295168    block3_conv2 (Conv2D)       (None, 56, 56, 256)       590080    block3_conv3 (Conv2D)       (None, 56, 56, 256)       590080    block3_pool (MaxPooling2D)  (None, 28, 28, 256)       0         block4_conv1 (Conv2D)       (None, 28, 28, 512)       1180160   block4_conv2 (Conv2D)       (None, 28, 28, 512)       2359808   block4_conv3 (Conv2D)       (None, 28, 28, 512)       2359808   block4_pool (MaxPooling2D)  (None, 14, 14, 512)       0         block5_conv1 (Conv2D)       (None, 14, 14, 512)       2359808   block5_conv2 (Conv2D)       (None, 14, 14, 512)       2359808   block5_conv3 (Conv2D)       (None, 14, 14, 512)       2359808   block5_pool (MaxPooling2D)  (None, 7, 7, 512)         0         =================================================================
Total params: 14,714,688
Trainable params: 0
Non-trainable params: 14,714,688
_________________________________________________________________

VGG16 就是我们的base_model。现在插入最后几个分类层(下面的第 4–7 行和第 12–15 行)。一如既往,选择神经网络层的大小更多的是艺术而不是科学。这可能需要反复试验。好消息是起点是一个令人敬畏的base_model

带有分类层的模型层:

Model: "sequential"
_________________________________________________________________Layer (type)                Output Shape              Param #   
=================================================================vgg16 (Functional)          (None, 7, 7, 512)         14714688  flatten (Flatten)           (None, 25088)             0         dense (Dense)               (None, 10)                250890    dropout (Dropout)           (None, 10)                0         dense_1 (Dense)             (None, 1)                 11        =================================================================
Total params: 14,965,589
Trainable params: 250,901
Non-trainable params: 14,714,688
_________________________________________________________________

注意,我们在模型中添加了 25 万个可训练参数。因为所有的 VGG16 参数都被冻结,所以之前为零。

培训和评估

我们建立了模型。现在设置培训流程。编译模型(下面的第 2 行)。使用自定义回调记录指标(第 8–30 行)。模型被拟合(第 33 行)。

下面的代码加载 test_ds —测试数据集。

评估测试(也称为保持)数据。

测试图像评估 100%准确(通常——结果是随机的)。只使用了三个时期的训练。这就是迁移学习的神奇之处。

损失和准确性指标

在左下图的第一个时期之后,训练损失下降到接近零。注意在一个时期后,训练损失比确认损失高得多。是否存在与原始时序信号相关的数据泄漏?

每个文件夹都是独立的(是不是而不是使用 TensorFlow API 来拆分验证图像)。此外,训练、验证和测试数据是时间分离的。所以训练数据在时间线上先于验证数据。并且验证数据在测试数据之前。

作者的模型损失和准确性。压差设置为 0.3。

根据 Keras 开发人员的说法,辍学可能导致培训损失高于“测试”(验证)损失。完整的解释是这里。下面,培训和验证之间的差距通过消除辍学而缩小。

鞋底脱落已移除。

尽管有非典型的度量曲线,但模型是一般化的。事实上,不管有没有辍学,测试预测都是 100%准确的。

测试数据度量([sklearn.metrics](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.classification_report.html) )。分类 _ 报告

这个任务需要这么大的 CNN 模型吗?大概不会吧!CNN 似乎过度参数化了。的确,谷歌大脑研究称,一个简单的带**num_weights = 2 * *n*_samples + *d*_dimensions**的两层神经网络可以表示任何函数[ 4 ]。

数据洞察

我们建立了一个有效的二元分类模型,有近 1500 万个参数。工作做得好吗?我们是否正确地探索和理解了信号数据?

是时候重新查看信号统计部分的 800 点随机样本直方图了。回想一下产生的空化信号看上去是高斯型的。

我们用更多的数据点做直方图吧。下面的 nc 信号看起来是 8000 点和 80000 点样本的双峰。相比之下, dc 信号是单峰高斯曲线。

信号直方图。顶行—无气穴现象。底排—形成气穴。

可能二进制分类就是驼峰计数,不需要深度学习!两个峰值表示没有气蚀,一个峰值表示气蚀。

为了计算峰值,我们需要(1)平滑信号和(2)计算局部最大值。通过核密度估计,波动信号转换为平滑曲线。SciPy 有一个find_peaks函数来计算信号的局部最大值。

from scipy.signal import find_peaks
peaks_num = len(find_peaks(y_kde, height=0.2)[0])

通过将信号分段来计算峰值。同样,我们预计无气穴现象有两个峰值,而发展气穴现象有一个峰值。

首先测试 800 个点段峰值。也就是说,每个信号有 1000 个独立段(800*1,000=800,000)。结果是几百个错误。糟糕的结果。

峰值计数无气蚀的 KDE。指数显示了 8000 点的区间。100 个片段中的单个错误在上面以红色显示。

直方图显示了8000 点段的更清晰的峰值。事实证明,增加数据点可以提高准确性。除一个区段外,所有区段都有正确的峰数,准确率为 99.5%。这是一大进步。所有需要的是一个更大的样本和曲线平滑[ 5 ]。

**8,000 point segments:**
* no cavitation - 100 segments
* developed cavitation - 100 segments
* total segments - 200 segmentsaccuracy = 99.5% **=** (200 segments - 1 error) / 200 segments * 100

结论

本项目使用了两种情况下加速度计信号的图像:无空化产生空化。我们使用一个拥有近 1500 万个参数的卷积神经网络对图像进行分类。其中,25 万只受过抓痒训练,大约 1400 万只被冷冻。

CNN 对于二元分类是有效的。但是,有一个更简单的选择。没有图像和没有大模型是必需的。数峰就行了。

后记

对于寻找更多双谱分析的读者,我写了信号的傅立叶和双谱分析。

除非另有说明,所有图片均由作者提供。祝你愉快!😎

**REFERENCES**[1] Ali Hajnayeb, "[Cavitation Analysis in Centrifugal Pumps Based on Vibration Bispectrum and Transfer Learning](https://www.researchgate.net/journal/Shock-and-Vibration-1875-9203/publication/356754152_Cavitation_Analysis_in_Centrifugal_Pumps_Based_on_Vibration_Bispectrum_and_Transfer_Learning/links/61aa54b8aade5b1bf5ffe596/Cavitation-Analysis-in-Centrifugal-Pumps-Based-on-Vibration-Bispectrum-and-Transfer-Learning.pdf)", 2021\. Hajnayeb Accelerometer Dataset:  [nc](https://www.researchgate.net/publication/308415702_No_Cavitation1), [lc](https://www.researchgate.net/publication/308415892_Limited_Cavitation1), [dc](https://www.researchgate.net/publication/308415784_Developed_Cavitation1)[2] Matteo Bachetti, et al., stingray v1.0 code, [https://docs.stingray.science](https://docs.stingray.science/), 2022.[3] Multi-class classification is successful with bispectrum images as per [1].  The neuron count in the final layer increases--one per class.  The author chose binary classification to highlight a classification alternative.[4] Chiyuan Zhang, et al., [Understanding Deep Learning Requires Rethinking Generalization](https://arxiv.org/pdf/1611.03530.pdf), 2017.[5] Test overlapping time segments to get more confident peak counting works.  Increase the number of segments per signal from 100 to 800\. Slide the segment window in 1,000 point steps rather 8,000 point steps. The result is two errors total (for both nc and dc).  See nc1 indices 199000-207000 and 200000-208000\.  1598 correct out of 1600 is 99.875% accuracy, i.e.  (1600-2)/1600*100\.  This is better accuracy than the fewer segments per signal scenario. [GO TO TABLE OF CONTENTS](#f386)

使用 Geopy 和 Python 轻松清理杂乱的地址数据

原文:https://towardsdatascience.com/transform-messy-address-into-clean-data-effortlessly-using-geopy-and-python-d3f726461225

处理缺少邮政编码、缺少州、拼错街道名称、拼错城市名称的地址

作者图片

背景

你以前在你的数据科学项目中处理过杂乱的地址数据吗?如果是这样,您可能会注意到可能有许多种转录错误,例如,缺少邮政编码、缺少州、拼错的街道名称、拼错的城市名称以及数据中不同种类的缩写。如果您需要清理和标准化这些原始地址数据,这很快就会变成一场噩梦。

在这篇文章中,我想分享一种替代方法来清理杂乱的地址数据,这种方法可以省去您的麻烦,让您有更多的时间和精力来开发令人敬畏的机器学习模型。

原始地址数据的常见问题

首先,让我们创建一个样本数据集。在这个数据集中,我们有带有常见转录错误的原始地址,例如,缺少邮政编码,缺少州,拼错街道名称,拼错城市名称,或者一个地址数据点中的多个错误。

import pandas as pdmessy_address = pd.read_excel("messy address.xlsx")

作者创建的图像

地质公园

清理原始地址数据的目标是获得标准化格式的地址信息,其中包含完整的地理信息,例如街道名称、街道名称、城市、州和邮政编码。

为了实现这一点,我们将使用[geopy](https://geopy.readthedocs.io/en/stable/),这是一个 Python 库,允许用户访问几个流行的地理编码 web 服务,如谷歌地图、必应地图、MapQuest、百度等等。

这个想法很简单。当您在上述地理编码 web 服务之一中手动搜索地址时,您可能并不总是输入完整的地址信息。很可能你会在打字中出现拼写错误,这与原始地址数据中的抄写错误非常相似。通常,这些地理编码 web 服务足够智能,能够理解您的意思,并从其服务器以干净的格式返回匹配的地址。

使用[geopy](https://geopy.readthedocs.io/en/stable/),我们可以通过无缝地将原始地址传递给这些地理编码 web 服务来输出干净的地址数据。

安装库

pip install geopy

初始地理编码服务 API

正如我上面提到的,有许多地理编码 API 可用。可以使用免费 API,比如, OpenStreetMap 或者付费 API,比如, Google Maps。这完全取决于你将如何部署你的程序。你会得到你所付出的。如果只是为了你自己喜欢的项目,一个免费的 API 可能会很好。如果您将部署用于商业目的,您应该考虑付费 API,因为您可以扩展每日 API 配额限制。

# **OpenStreetMap Free API**
from geopy.geocoders import Nominatim
geolocator = Nominatim(user_agent="[Y](mailto:aaron_clover@hotmail.com)our Email")# **Google Maps Paid API**
from geopy.geocoders import GoogleV3
geolocator = GoogleV3(api_key='Your Google Maps API Key')

为了使用谷歌地图 API,我们需要一个谷歌地图 API。但是不用担心。你可以按照这个 快速教程免费生成自己的谷歌地图 API 密匙。

您自己的 Google API 键看起来类似于“aizaybdj 88 HN 7 ltgkhhk 5 wh fav 8 a 5 ozl x2e _ k”。你可以复制并保存这个 API 密匙到你电脑的某个地方。

初始化地理编码服务 API 后,编码就很简单了。您可能想要创建自己的函数来从 API 中提取干净的地址。

如果你想批量处理原始地址,你可以使用 Pandas 库的 lambda 函数。

def extract_clean_address(address):try:location = geolocator.geocode(address)return location.addressexcept:return ''messy_address['clean address'] = messy_address.apply(lambda x: extract_clean_address(x['Raw Address']) , axis =1  )

正如您从下面看到的,从 Google Maps API 返回的干净地址看起来比原始地址更干净、更完整。

作者创建的图像

从原始地址中提取经度和纬度

使用[geopy](https://geopy.readthedocs.io/en/stable/)可以对原始地址做更多的事情。例如,您可以从地理编码 web APIs 中提取经度和纬度。

def extract_lat_long(address):try:location = geolocator.geocode(address)return [location.latitude, location.longitude]except:return ''messy_address['lat_long'] = messy_address.apply(lambda x: extract_lat_long(x['Raw Address']) , axis =1)
messy_address['latitude'] = messy_address.apply(lambda x: x['lat_long'][0] if x['lat_long'] != '' else '', axis =1)
messy_address['longitude'] = messy_address.apply(lambda x: x['lat_long'][1] if x['lat_long'] != '' else '', axis =1)
messy_address.drop(columns = ['lat_long'], inplace = True)

作者创建的图像

最后一个音符

除了提取干净的地址和地理坐标,我们还可以使用[geopy](https://geopy.readthedocs.io/en/stable/) 来计算两个地址点之间的地理距离和行驶时间。但是请记住,其中一些地理编码 web 服务是付费服务。您可能会有一些免费的日常请求,但是在使用它们之前,您应该检查它们的政策和价格。

感谢您的阅读!!!

如果你喜欢这篇文章,并且想请我喝杯咖啡,请点击这里。

您可以注册一个 会员 来解锁我的文章的全部访问权限,并且可以无限制地访问介质上的所有内容。如果你想在我发表新文章时收到电子邮件通知,请订阅 。

转变您的 NLP 游戏

原文:https://towardsdatascience.com/transform-your-nlp-game-17f5bd0d87ea

通过从头开始构建自己的编码器来学习和提高 NLP 技能

思考一篇文章的变形人。由 DALL E 2 生成。

由于 ChatGPT 这样的技术,目前围绕人工智能(AI)有很多炒作。作为许多最近流行的人工智能应用程序的基础构件的变形金刚,是这场炒作的中心。这就是我写这篇关于他们的文章的原因。变形金刚是深度学习模型,擅长各种自然语言处理任务,是许多行业专业人士的首选。

当被问及它最重要的部分是什么时,ChatGPT 的回答。

目录

  • 要知道的重要东西
    • 编解码框架-
      -注意机制-
      -迁移学习
  • 创建自己的 Transformer 编码器
    • 记号化器-
      -注意和多头注意-
      -Transformer 层
    • 位置编码-
      -编码器块
    • 实用功能和分类头
  • 动作中的变压器
    • 测试结果
  • 结论
  • 参考文献

Vaswani 等人在 2017 年的论文“注意力是你所需要的全部”中介绍了 Transformer 架构。[1]该论文一直是近期一些最具影响力的自然语言处理模型背后的驱动力,包括预训练的生成式 Transformer (GPT)和 Transformers 的双向编码器表示(BERT)。这些模型已经改变了自然语言处理领域,并且已经在各种应用中被广泛采用。研究人员仍在努力改进这些强大的工具,因为他们继续推动可能的边界。

在本文中,我们将研究如何从零开始实现转换器,从基本的构建块开始,逐步发展到更复杂的架构。通过构建我们自己的转换器,我们可以更深入地了解这些模型是如何工作的,并将它们应用于不同的任务和数据集。在这个过程中,我们还将了解使变形金刚如此有效的关键概念和技术。无论你是初学者还是有经验的从业者,这篇文章都将提供关于变形金刚世界的有价值的见解。

要全面理解变压器的概念,必须理解以下三个关键组件:

  • 编码器-解码器框架
  • 注意机制
  • 迁移学习

首先,让我们来看看编码器-解码器框架以及为变形金刚的出现铺平道路的架构发展。

编码器-解码器框架

简单的编码器-解码器网络翻译给定的英语序列到土耳其语。图片作者。

在我们深入研究编码器-解码器框架之前,让我们看看一种特殊的神经网络。以下引用自古德费勒、本吉奥和库维尔的《深度学习》一书,很好地总结了这一点:

递归神经网络或 RNNs 是用于处理顺序数据(如自然语言或时间序列数据)的神经网络家族。在每个时间步,RNN 接收输入,在自然语言处理的情况下,输入可以是单个单词或字符。RNN 处理输入并更新隐藏状态向量,该向量捕获到该点为止的输入序列的信息。然后,隐藏状态被用于产生一个输出,该输出可用于手头的各种任务。

RNN 的一个关键特征是它的反馈环,这允许它保留来自先前时间步长的信息,并使用它在当前时间步长做出决策。这使 RNN 能够“记住”过去的输入和上下文,这对于语言翻译或语言生成等任务至关重要,在这些任务中,理解单词或短语的含义和上下文取决于之前出现的单词和短语。

序列到序列架构非常适合输入和输出序列长度可变的情况。这些架构通过用编码器处理输入序列来工作,编码器将其转换成称为隐藏状态的固定长度表示。该隐藏状态然后被传递到解码器,该解码器基于编码表示生成输出序列。

rnn 编码器-解码器相对简单,但这种方法有一个主要问题。随着输入序列变长,隐藏状态必须保留越来越多的关于过去输入的信息,以便准确预测下一个输出。但是,如果 RNN 的容量不足以表示此信息,隐藏状态可能会变得“瓶颈”,这会导致 RNN 难以准确预测下一个输出。

长短期记忆(LSTM)和门控递归单元(GRU)网络被开发用于解决传统 RNNs 中的信息瓶颈问题。这些体系结构包括更复杂的存储和检索信息的机制。最后,注意力机制,变形金刚的一个关键组件,能够解决这个瓶颈。

注意机制

我将简单地谈一下这个主题,因为我们将在文章的后面实现注意力机制本身。在任何情况下,注意机制都是神经网络层,使模型能够在处理输入时专注于输入的特定部分。在诸如机器翻译、语言建模和文本摘要的自然语言处理任务中,注意机制已经被广泛用于提高模型理解和生成文本的能力。它们还可以通过允许模型专注于输入的最相关部分,来提高深度学习模型在计算机视觉和语音识别任务中的性能。值得注意的是,还有一种称为“自我关注”的关注机制,它允许模型在生成输出时关注输入序列的不同部分。

迁移学习

你们中的大多数人应该对此很熟悉,所以我认为在这一点上没有必要进行详细的解释:

迁移学习是一种机器学习技术,涉及在针对特定任务进行微调之前,在大型数据集上训练模型。在 NLP 的迁移学习中,预训练模型通常首先在大型数据集上训练,例如常见爬行数据或维基百科文章的数据集。例如,在“屏蔽语言建模”(MLM)这样的任务中,一个标记子集被随机屏蔽并馈入模型,模型试图根据给定的序列猜测原始标记。通过对像 MLM 这样的任务进行预训练,该模型可以通过在大型和多样化的数据集上进行训练来学习丰富的语言表示。

创造我们自己的变压器编码器

变压器架构由原论文。突出显示编码器部分。

在这一部分,我们将创建和安装拼图的各个部分,以构建我们自己的编码器模块!主任务流应该如下所示:

  • 创建一个简单的标记器和词汇表。
  • 实现关键的变压器层组件,如缩放点积、注意力、多头注意力头和前馈层。
  • 使用这些实现的部分组装 transformer 层。
  • 多次使用 transformer 层来构建编码器块。

标记器

记号化是将一串文本分解成称为记号的小块的过程。这些标记可以是单个单词、短语或子词。标记化是自然语言处理中至关重要的预处理步骤;为了将序列输入到 transformer 模型中,必须首先将它们转换成模型可以理解的数字表示。这就是标记化的用武之地。

通过对输入文本进行标记,我们可以将它转换成表示单个标记的整数序列。这使得 transformer 模型可以将输入文本作为一系列标记来处理,而不是一个单独的文本字符串。有三种主要的标记化类型:

  • 字符标记化
  • 单词标记化
  • 子词标记化

每种类型都有自己的优点和缺点。字符标记化很简单,但是缺乏实际单词的知识。单词标记化具有单词的知识,但是它需要大量的词汇来处理语料库中每个单词的所有可能的变化。子词标记化介于这两种方法之间,因为它根据预定义的词汇表将词分成更小的子词。这允许模型在保持词汇表大小可管理的同时处理词汇表之外的单词,这是使用最广泛的方法。为了简单起见,我们将使用单词标记化。

这里有一大段代码,但是不用担心!这实际上是一个简单的类,它做的正是我们想要的单词分词器。通过使用fit_on_corpus方法,它将每个唯一的单词映射到内部字典中的一个索引,这将是我们的词汇表。我们还添加了一些特殊的令牌来处理未知令牌和注意力地图。然后,我们有了encode方法,它接受一个字符串并从我们的词汇表中返回惟一的索引,我们可以将这些索引用作查找表。接下来的方法主要是为了实用目的;tokenize方法使用nltk库来标记给定的文本。在这种情况下,标记化的字符串是单词列表。下面几行根据给定的max_len参数填充或截断文本,确保在训练期间批处理的所有序列长度相同。vocab_size属性为我们提供了词汇表的大小,我们将在将来的 transformer 模型中使用它。

下一步将是使用语料库创建我们的词汇。通常,transformer 词汇表是从巨大的数据集创建的,如维基百科文章、书籍或不同公共文本来源的组合。但是对于我们这个简单的例子,我们将使用斯坦福大学的大型电影评论数据集。该数据集包含 5 万条被标记为正面或负面的电影评论。

# Loading the dataset as pandas dataframe
df = pd.read_csv('IMDB Dataset.csv')# Initialize the tokenizer
tokenizer = WordTokenizer(max_len=256)# Fit the tokenizer on a corpus of texts
tokenizer.fit_on_corpus(df.review.tolist())# Update the config vocab_size
config.vocab_size=tokenizer.vocab_sizeprint(tokenizer.word_to_index)### Output:{'<pad>': 0,
'<unk>': 1,
'mouth-foamingly': 2,
'abroad.': 3,
'…':
'terry/carey':163857,
'libertine':163858
}

我们刚刚使用实现的单词 tokenizer 创建了第一个词汇表。如你所见,它将每个唯一的单词映射到一个索引,我们可以用它将字符串序列嵌入到密集的向量中。但是,您可以看到对较大的数据集使用这种单词标记化方法的缺点。例如,对于 5 万条评论,我们最终得到了大约 16.3 万个独特的单词标记。这就是为什么经常使用子词标记化的原因。从理论上讲,使用这种技术,我们的记号赋予器将创建类似于[‘mouth’, ‘foam’, ‘###ingly’]的记号。这样,如果“foam”以不同的形式使用,它将作为一个索引保存,而不是跟踪每一个单独的修改。然而,对于本教程来说,我们的方法已经足够了。

好了,让我们在一个有错别字的测试句子上测试我们的编码器,看看它是否工作:

tokenizer.encode('This is a twest, sorry a test sentence!')### Output:{'input_ids': tensor([[ 45594, 30562, 41753, 1, 111258, 89975, 41753, 53047, 27998,
55328, 0, …, 0]]),
'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, …, 0]])}

是的,它像预期的那样工作!因此,它为每个标记返回input_id,为我们的错别字返回 1 作为<unk>特殊标记,为直到我们指定的max_len的填充部分返回 0。它还返回一个注意掩码,对于有人出席的令牌为 1,对于<pad>特殊令牌为 0。

每个批次的输入序列被填充到批次中任何序列的最大长度。然后,该模型利用注意屏蔽来忽略输入张量的填充部分,允许它保持关注序列中的相关数据。图片作者。

现在我们有了一个工作的标记器,我们可以进入文章的下一部分。在下一节中,我们将重点介绍如何构建转换器的关键组件,这将最终导致编码器模块的创建。

注意力和多头注意力

标记化的一个问题是语言模型可能很难理解单个标记的上下文。例如,代词可能指文本中较早的特定名词,同音异义词如“统治者”可能根据上下文有多个含义。这就是注意力机制,尤其是自我注意力的来源。注意,该模型能够通过考虑每个记号与序列中其他记号的关系来理解每个记号的上下文。例如,如果模型在一串与历史或政治相关的单词(如“女王”、“国王”或“国家”)中遇到“统治者”一词,它可能会将其解释为指代某个政治角色。相比之下,如果这个词被与测量相关的词(如“厘米”或“长度”)包围,模型可能会将“尺子”理解为用于测量的物理对象。

标度点积注意和多头注意。图片来自原纸。

让我们从实现注意力头部的第一个实例开始:

AttentionHead类有两个主要组件:

  1. 三个线性层:用于将输入(hidden_state)转换成三种不同的表示:querykeyvalue向量。head_dim是我们投射输入的维数,通常选择为embded_dimnum_heads的倍数。在这种情况下,自我关注层投射嵌入,并使用其自己的一组可学习参数来关注序列的各种语义方面,使其能够更深入地分析和理解输入(而不是给相同的向量最高分)。这些表示然后被用于计算注意力权重,如下所述。
  2. scaled_dot_product_attention:该方法采用querykeyvalue表示,并计算注意力权重。它首先计算querykey表示之间的点积(相似性度量),然后除以key表示(dim_k)维度的平方根。这被称为缩放点积,它有助于在训练期间稳定梯度。然后,点积通过一个softmax函数(确保所有列值总和为 1)来计算关注权重。最后,注意力权重与值表示相乘以产生最终输出。

最后,当AttentionHead模块用于模型中的正向传递时,将调用 forward 方法。它用hidden_state输入调用scaled_dot_product_attention方法,将转换后的querykeyvalue表示作为参数传递。然后,scaled_dot_product_attention方法的输出作为AttentionHead模块的输出返回。

上面的方法会给我们一个相似性方面的分数。然而,在自然语言中有许多方面,因此拥有多个头部(多组投影)可以帮助我们克服这一点。这就是MultiHeadAttention的用武之地:

这里不多解释了。我们不是使用单个注意头,而是创建多个注意头(数量由配置决定)来捕捉给定序列的不同方面。例如,其中一个模型的负责人可能专注于主语-动词关系,而另一个负责人则搜索附近的形容词。我们只剩下变压器最后缺失的部分:如果我们跳过跳过连接(明白!)且不计算层归一化,前馈层:

在转换器中,FeedForward是独立处理每个嵌入的子层。通常的做法是将第一层的隐藏尺寸(intermediate_size)设置为嵌入尺寸(hidden_size)的四倍。这有助于模型有效地处理、理解,尤其是记忆输入数据。

我们的第一个变形层!

鼓声…
到了!当我们把所有的片段放在一起时,我们从我们之前指出的原始纸张中获得部分。代码如下:

该实现与最初的纸质实现几乎完全相同,许多当前的应用程序也是如此。我们没有像原始论文那样使用后层归一化,而是仅在跳过连接的跨度内应用层归一化,这通常在训练阶段更稳定[2]。

现在我们已经从零开始实现了一个 Transformer 层,令人惊讶的是,这么简单的一段代码在过去的几年里已经彻底改变了整个行业,并且仍然很强大。

位置编码

我们可以创建一个自定义嵌入模块,它结合了一个令牌嵌入层和一个位置编码层。令牌嵌入层将input_ids投射到密集隐藏状态,而位置编码层对position_ids做同样的事情。这就产生了一种混合方法,它结合了输入数据中的令牌和位置信息。

嵌入和位置编码的组合。图片作者。

位置编码的操作类似于令牌嵌入,但是它们不使用令牌 id 作为输入,而是使用位置索引。这使得编码器层能够将位置信息合并到它们的变换中,从而允许它们更有效地处理输入数据。

在这段代码中,我们实现了易于使用的可学的位置编码。然而,对于位置编码,我们可以考虑其他选择,比如在最初的 Transformer 论文中使用的绝对位置表示,或者更高级的方法,比如在 DeBERTa 模型中使用的相对位置表示。虽然这些可能会提供额外的好处,但我们实现的可学习的位置编码将足以满足我们的教程。

最终确定的编码器模块

通过添加包含位置表示的令牌嵌入,并根据预定数量堆叠编码器层,我们可以创建编码器的最终版本。堆叠编码器层的目的是更新输入嵌入,以便产生对序列内的上下文信息进行编码的表示。经过一些小的调整和更新,这个模块应该能够学习和训练给定的数据,我们将在后续部分演示。

实用功能和分类头

在添加分类头之前,我们必须解决的一个问题是,transformer 模型为每个令牌返回一个嵌入向量,向量大小由配置决定。例如,给定句子“你好,世界!”我们将接收两个嵌入向量,每个令牌一个。但是在我们的电影评论案例中,我们希望整个评论只有一个嵌入向量。为了解决这个问题,我们可以使用一种叫做池的技术。最直接的池化方法之一是平均令牌嵌入,这被称为mean pooling

模型身体和头部结构。编码器和分类器。

我们已经完成了模型中与任务无关的部分,称为模型体。最后一步是添加一个特定于任务的层,称为模型头。在我们的例子中,这只是一个基于来自模型主体的平均池嵌入的输出返回逻辑的密集层。

有了所有必要的组件,我们现在准备训练我们的电影评论数据集并评估结果。下一节将介绍使用 PyTorch 的典型分类过程。

变形金刚在行动

我不会在这里讨论训练过程的细节,因为它遵循一个简单的 PyTorch 管道;但总的来说,它是这样工作的:

  • 使用 sklearn 拆分训练和测试数据。
  • 创建一个数据加载器来动态读取和标记文本。
  • 使用基本 PyTorch 训练循环,通过二进制交叉熵损失和 Adam 优化器为指定数量的时期更新模型权重。

我们可以通过多种方式改进该模型,例如增加编码器层数、使用更好的超参数、实施提前停止,以及为 AdamW 和带预热的余弦调度器等变压器使用更高级的优化器和训练速率调度器。如果你想尝试并改善结果,我鼓励你自己尝试这些技术。

测试结果

验证结果。图片作者。

这是我们的浅层 transformer 模型的结果,它只使用了一个编码器层和一个基本的标记器。尽管没有在大型语料库上进行预训练,该模型表现得非常好,展示了 transformer 模型有效捕捉上下文信息的能力。此外,模型体可以在类似掩蔽语言建模或下一句预测的任务上进行训练,使其更加通用,并且能够用于各种任务,而无需进行重大修改。当相同的模型主体用于不同的任务时,这尤其有用。transformer 模型的多功能性和强大的性能使它们成为自然语言处理中的一个有价值的工具。

结论

在本文中,我们深入讨论了 transformers 的编码器部分,从头开始构建每个组件,并将其应用于一个实际的 NLP 分类问题。唯一缺少的部分是解码器部分,它使用给定的提示或序列生成序列。它在实现上类似于编码器模块,主要区别在于使用了掩蔽的多头注意力和编码器-解码器注意力。

我希望这篇文章能让您对 transformer 模型及其内部工作有更深的理解。我喜欢写它,希望你和我一样喜欢读它。

参考资料:

【1】瓦斯瓦尼、阿希什和沙泽尔、诺姆和帕尔马、尼基和乌兹科雷特、雅各布和琼斯、利翁和戈麦斯、艾丹.和凯泽、卢卡什和波洛舒欣、伊利亚。你需要的只是关注。arXiv 预印本
arXiv:1706.03762,2017。

[2] 用变形金刚进行自然语言处理:用拥抱脸构建语言应用第 1 版,作者 Lewis Tunstall,Leandro von Werra,Thomas Wolf,2022。

使用 Pandas 对 JSON 文件进行转换

原文:https://towardsdatascience.com/transformations-on-a-json-file-using-pandas-eba831181a96

一套有用的 pandas 工具,用于成功加载和转换 JSON 文件

(图片由作者使用 canva 提供)

在数据工程/科学领域,通过 JSON (JavaScript Object Notation)文件加载和执行转换是很常见的事情。

JSON 是一种广泛使用的存储和交换数据的格式。比如像 MongoDB 这样的 NoSQL 数据库是以 JSON 格式存储数据的,REST API 的响应大多是以 JSON 的形式提供的。

尽管 JSON 非常适合通过网络交换数据,但是如果我们想要处理数据,我们需要将它转换成表格形式,也就是包含列和行的形式。

现在,一般来说,我们可以在一个文件中遇到两种类型的 JSON 结构:

  1. JSON 对象
  2. JSON 对象的列表

JSON 对象与 JSON 对象列表(图片由作者提供)

在本文中,我们将关注第二个场景(JSON 对象的列表),据我所知,这是最常见的场景,更重要的是,通过学习处理列表场景,我们可以轻松地处理单个 JSON 对象。

为了处理 JSON 对象列表,我们可以使用 pandas,更具体地说,我们可以使用两个 pandas 函数:explode()和 json_normalize()。

让我们简单解释一下,然后再看完整的例子。

这篇文章内容如下:

  1. 简要解释 explode()函数
  2. 简要解释 json_normalize()函数
  3. 在完整的示例中使用这两个来创建一个固化的 D 数据帧。
  4. 做一些转换
  5. 将转换后的数据转储为 CSV 格式

源代码请查看笔记本。

1.激增

explode()函数用于将 list like 值的每个元素转换为一行,并为新元素保持旧的索引值。

像这样:

explode()简单示例(图片由作者提供)

该函数使用以下语法:

df.explode('***column_to_explode', ignore_index=False)***

我们还可以发送要展开的列的列表:

df.explode(['col_1', 'col_2'], ***ignore_index=False)***

它看起来会像这样

分解前的基本代码示例(图片由作者提供)

现在让我们来爆一下 col_2

分解后的基本代码示例(图片由作者提供)

请注意分解行的索引是如何保持的。

2.JSON 正常化

json_normalize()函数在半结构化列上执行我们称为的扁平化操作。也就是说,它为结构的每个属性创建新列。

这是语法:

data = [{"id": 1,"name": {"first": "Coleen","last": "Volk"}},{"name": {"given": "Mark","family": "Regner"}},{"id": 2,"name": "Faye Raker"},]**pd.json_normalize(data)***or within a column of your Data Frame***pd.json_normalize(df['a_column'])**

让我们来唱:

json_normalize 示例(图片由作者提供)

正如我们所看到的,它从我们的原始 JSON 创建了一个数据帧,将根中的每个属性作为一列。

嵌套属性是这里有趣的部分。JSON normalize 接受每一个,并创建一个保持其原始路径的列。

即。

"name": {"first": "Coleen","last": "Volk"
}**becomes**name.first, name.last

我们还应该注意到,normalize 接受所有找到的" name "(名、姓、名、族和一个普通字符串)的子参数,并为每个子参数创建一列;用 NaN 填充缺失值。

我们可以选择分离器

pd.json_normalize(data, sep="<our_sep>")

标准化的自定义分隔符(按作者排序的图像)

我们还可以控制 JSON 的级别,直到我们想要规范化为止

pd.json_normalize(data, max_level=<my_int_level>)

哪里……

让我们看看实际情况

规格化的最大级别(按作者排序的图像)

3.让我们看一个完整的例子

例如,我们将使用来自电子商务商店的购物车商品列表,该列表来自于 dummyjson api 。

假设我们有一个需要以某种方式处理的购物车列表。

假设我们在需要提取和转换的 blob 中有一个原始格式的 JSON:

取自虚拟 json ( 来源)

在这个例子中,我们有一个带有 carts 属性的主对象,其中包含我们需要的列表。

比方说,我们需要提取购物车产品或商品,并对其进行以下转换,并将结果输出到 AWS S3,以便进一步处理或存储:

  • 基于产品的总计和构成大销售额的阈值,创建一个名为 big_sale 的新布尔列。
  • 为每一行添加一个<processed_ts>时间戳。</processed_ts>

如果我们将购物车数据加载到 pandas 数据框中,并展开 products 列,它看起来会像这样:

示例-加载-数据(图片由作者提供)

我们使用请求库加载数据。然后变成熊猫:

示例-数据框(图片由作者提供)

示例-分解(图片由作者提供)

看起来不错,现在每个购物车中的每个产品都有一行。注意购物车 id 重复,这就是我们现在想要的…

我们现在需要处理产品所在的 JSON 格式

示例-展开-信息(图片由作者提供)

为此,我们将使用之前的 json_normalize 来规范化 json 数据,并将结果加入到我们的分解数据框中。让我们看看那个

示例-规格化(作者图片)

示例-规格化-信息(图片由作者提供)

现在,对于产品列的每个 JSON 属性,我们都有了一个列

接下来,我们可以连接分解的归一化的数据帧,以在一个数据帧中获得 JSON 的完整表示。但是我们首先需要解决一个小问题:

explosed _ df的索引是错误的,我们需要重置它以通过索引加入。

因此,我们采取以下措施:

  1. 分解的数据框中移除旧产品栏
  2. 重置展开的数据帧上的索引
  3. 连接分解的归一化的数据帧

我们走吧

首先我们删除旧的产品列,然后我们重置索引。

exploded_df.drop(['products'], axis=1, inplace=True)exploded_df.reset_index(inplace=True)

示例-drop(作者提供的图片)

示例-索引-匹配(图片由作者提供)

请注意,索引现在匹配了。

在加入之前,我们需要处理两个重叠的列。

idtotal 存在于分解和规范化 dfs 中。为此,当我们加入时,我们将使用 panda 的加入的后缀选项。

让我们看看

joined_df = exploded_df.join(normalized_df, *lsuffix='_cart'*, *rsuffix='_product'*)

示例-已加入(图片由作者提供)

示例-加入信息(图片由作者提供)

完美,现在我们可以做一些转换…

4.转换

我们需要做两件事:

  • 基于产品的总数和构成大销售额的阈值,创建一个名为 big_sale 的新布尔列。
  • 为每一行添加一个<processed_ts>时间戳。我们走吧…</processed_ts>

我们将为 big sale (≥ is big)设置一个阈值 100,并使用 pandas 的 apply() 函数为 big_sale 列创建一个新系列。

请注意,产品的总计列在连接后被称为 total_product。

threshold = 100*big_sale_col* = joined_df.apply(lambda row: row.total_product >= threshold, axis=1)

现在我们可以将它添加到我们的加入的数据帧中

joined_df['*big_sale*'] = *big_sale_col*

示例-大甩卖(图片由作者提供)

很好,现在对于下一个转换,让我们简单地添加一个新列,用当前日期作为时间戳。

from datetime import datetimenow = datetime.now()
timestamp = datetime.timestamp(now)joined_df['processed_ts'] = timestamp

不要忘记导入日期时间

示例-已处理(图片由作者提供)

示例-已处理信息(图片由作者提供)

太好了!我们什么都有。让我们导出数据

5.将转换后的数据转储为 CSV 格式

显然,这是一个例子,在现实世界中,我们需要将处理后的数据输出到云中,可能不是 CSV,而是 parquet 或任何其他列格式。

尽管如此,按照示例,让我们在本地将其输出为一个 processed.csv 文件

joined_df.to_csv('processed.csv', index=False)

processed.csv 文件(图片由作者提供)

结论

处理一个原始的 JSON 文件是现实生活中非常常见的场景,pandas 非常适合这种情况。

通过结合 explode 和 normalize,我们可以将 JSON 文件放入数据框中进行处理。这对于处理来自例如通常为 JSON 格式的 API 的数据非常有用。

诚然,只有在处理对象列表时才需要 exploded,但这是一个相当常见的东西。无论如何,如果您的 json 不需要它,json_normalize()函数足以让您处理您的 JSON。

让我们总结一下我们所做的事情:

  • 将 JSON 加载到数据框中
  • 像列一样展开列表
  • 规范化 JSON 格式列
  • 做了一些简单的转换
  • 导出了数据

感谢阅读!

来源请查看笔记本。

参考

  • 爆炸()
  • json_normalize()
  • from_dict()
  • apply()
  • to_csv()
  • 虚拟 json

变形金刚释放:Python 中多元时间序列的深度预测

原文:https://towardsdatascience.com/transformer-unleashed-deep-forecasting-of-multivariate-time-series-in-python-9ca729dac019

深度预测

变形金刚释放:Python 中多元时间序列的深度预测

端到端示例:具有复杂季节性的多变量时间序列的概率预测

大多数经典预测方法仅限于单变量时间序列。只有少数几个接受多元数列,例如 SARIMAX。当多种季节性模式也开始发挥作用时,神经网络可以很好地战胜传统方法。

超级英雄女孩 Speed-pix abay 上的免费图片

在今天的文章中,我们将揭开神经网络预测者中一个相对较新的东西:变压器模型。我们将把它放在一个多变量时间序列上,这个时间序列由三个季节组成:小时、工作日和月。这为神经网络提供了一个适当复杂的时间序列。

我们将预测西班牙的电价。

每小时能源需求生成和天气| Kaggle (作者 screesnhot)

相关矩阵的屏幕截图显示了影响电价的许多因素。转换者将学习整合其中的相关因素作为协变量(回归变量)。

下面,观察到的电价的橙色发热曲线告诉我们,涉及到一些季节性模式,但显然不止一种类型,因为我们看到不同振幅的振荡。

蓝色曲线是一种剧透。它揭示了我们将要构建的变压器将能够以合理的精度跟踪和预测锯齿形峰值和谷值。其在测试集上的平均绝对百分比误差小于 5%。

让我们看看它是如何得到预测的。

作者图片

1.概念

1.1 变压器神经网络

2017 年的一篇论文,“注意力是你所需要的一切(arxiv.org)”概述了基于注意力的网络的概念,最初是在自然语言处理的背景下。NLP 处理按语法和句法排序的单词序列。基于注意力的网络,又名 Transformer,获取输入文本序列,例如英语,并生成输出文本序列,例如西班牙语。时间序列分析使用按时间顺序排列的序列:时间步长。转换器必须从一系列训练观察值中沿着时间轴生成一个预测序列。

所谓的注意力头使转换器能够学习输入序列中一个时间步长和每隔一个时间步长之间的关系。在 NLP 训练中,它可以通过反向传播学会“注意”经常伴随其他单词的单词。这种上下文分析机制使它能够学会将单词与其他单词和序列关联起来。转换器更新其注意力权重,并降低最不相关的备选单词的等级。通过这种方式,它学习语法模式。得分矩阵表示其他单词与所讨论的单词的关联程度。

这是我们将从单词序列转移到时间步骤序列的能力。

你可以在 Ketan Doshi 撰写的一系列三篇 TDS 文章中找到关于 Transformer 架构的更深入的解释: Transformers 直观解释| Ketan Doshi |迈向数据科学。

1.2 概率预测:分位数回归

确定性预测方法生成点估计值:在每个时间步长上的单个值(通常是平均值)。确定性预测没有揭示不确定的“真实”未来值可能出现的上限和下限。

概率预测方法在每个时间步计算不止一个样本。神经网络评估一个分位数损失函数,一个传统损失函数的变体。我们将建议我们的转换器使用分位数回归来计算预测百分点。每个百分点代表一个单变量时间序列。分位数对(例如,1%/99%或 10%/90%)将中央预测值的曲线括起来。概率图显示了由此产生的不确定性范围。

作者图片

你可以在上周关于时间融合转换器的文章中找到关于概率预测的其他解释:

2.属国

除了我们常用的核心包——pandas、numpy 和 matplotlib——我们还导入了 seaborn 的绘图功能,随后是几个 Darts 类:Transformer 模型和一些用于预处理、评估和绘图的实用程序类。

早期的一篇文章提供了安装 Darts 包的指导:

我还导入了小而有用的 missingno 包来可视化任何可能影响源数据的空值。它是一个可选工具:您可以选择删除它的导入行和使用它的 Juypter 单元格,而不会影响脚本的其余部分。

像往常一样,在安装像 PyTorch 这样包装大型库的包之前,创建一个新的虚拟环境,就像 Darts 一样。

三个可选的飞镖组件可以单独安装。除了 Darts 核心库,您还需要 darts[torch]组件来处理神经网络。您可以省略另外两个选项:pmdarima 和 prophet 包的 Darts 包装器。

3.控制设置和常量

我们设置了许多超参数来配置变压器模型和一些表示附加控制设置的其他常数。

在右上方,注意参数 LOAD

  • 当它设置为 False 时,脚本将在训练集上训练一个 Transformer 模型。
  • 当 Load 设置为 True 时,脚本不会重新训练模型。相反,它将从当前的工作目录——您保存 Jupyter 笔记本的文件夹——加载一个先前保存的 Transformer 模型。将跳过冗长的培训课程,脚本将继续从加载的模型生成预测。
  • 一旦训练会话完成,脚本将自动将新训练的模型保存在 tar 文件中。您可以在第 2 行修改它的文件名。

变压器的超参数:

  • 时期表示训练周期的数量:一次向前传递,随后是整个训练集的一次向后传递。我尝试了 50 个和 100 个纪元,最终确定了 200 个纪元,以达到令人满意的 MAPE。通过将训练扩展到 400 个或更多个纪元,我们可能实现甚至低于 4.5%的 MAPE。
  • INLEN 设置 input_chunk_length:输入层的节点数。我们的时间序列具有每小时一次的频率,因此输入层大小应该至少包含 24 小时。一般来说,输入块需要足够大,以构成一个完整的季节周期,否则转换器将很难拼凑出季节模式。为了优化内存使用,输入层大小应该表示为 2 的幂。我用大于 32(例如 256,足够组成一周的 168 个小时)的输入块进行的实验在 MAPE 方面并没有产生更好的结果,但是伴随而来的是更长的训练时间。32 是一天 24 小时之上的下一个二进制上限。
  • VALWAIT 参数告诉转换器在尽可能多的时期之后更新损失函数,而不一定是在每个循环之后。
  • FEAT 设置输入中预期特征的数量,默认为 512。我有一个可以接受的只有 32 的 MAPE。对于时间序列分析来说,512 看起来有点矫枉过正,可能会让我们面临过度拟合的风险。
  • DIM_FF 缩放前馈网络,默认值= 2048。我把它限制在 128 维。
  • 头数定义注意头数,默认= 8。今天的例子适用于 4,我认为这足以处理三个季节性订单和一个潜在的趋势或周期性组件。
  • 编解码层数,默认= 6。对于大多数时间序列,我不期望需要超过 4 层来识别趋势和季节模式。过多的层会使模型容易过度拟合。
  • 0.1 的丢失率告诉变压器在一个时期内随机关闭 10%的节点。这可以防止网络过于深入地挖掘单个冻结的节点配置。
  • 非线性激活函数 ACTF 转换两层之间的吞吐量。我们可以在校正线性单位函数“relu”(默认)和高斯误差线性单位函数“gelu”之间进行选择。更深入的见解,见:现代激活函数|走向数据科学。
  • 批量大小表示在更新其权重矩阵之前,转换器将处理的样本数量。大批量可能会导致过度拟合,并可能会碰到系统的内存限制。小批量会导致梯度下降,从而不规则地改变方向。理想情况下,为了优化内存使用,批处理大小应该用 2 的幂来表示。初始大小 32 是数据科学来源中最常见的指导。我将批量大小提高了一个档次,超过了 24 小时:32 小时是下一个二进制上限。
  • N_FC,预测展望期,设置为 1 小时的时间段,提前 1 步预测。
  • RAND 将随机种子设置为一个可以在运行之间重现的值。
  • N_SAMPLES 建议概率预测模型对尽可能多的预测值进行采样,以计算分位数。它代表单变量概率时间序列的第二个维度:时间轴上每个时间步长的样本。
  • FIGSIZE 定义了图的默认尺寸
  • 分位数列出了概率预测下限和上限
  • 常数 SPLIT 控制时间序列中用于训练的百分比。
  • N_JOBS 限制脚本可以并行声明的处理器。如果设置为-1,所有处理器都将对其可用。

一般来说,我建议最初将变压器的超参数——本质上是它的矩阵大小——设置为比默认值低的值。高默认数字可能源于 PyTorch 对 NLP 应用程序的关注。时间序列的特征通常比自然语言问题要少。非常高的设置会导致模型必须处理的矩阵呈指数级增长。过大的模型会增加过度拟合的风险。

4.数据争论和探索

4.1 源数据

源数据可以在 Kaggle 上下载,并获得 CC0 公共域许可:

每小时能源需求生成和天气| Kaggle

每小时能源需求生成和天气| Kaggle ,作者截图

该源文件由两个 csv 文件组成,一个用于能源数据,另一个用于天气数据。

能量文件包含

  • 2015 年至 2018 年西班牙电价水平的每小时记录,单位为欧元每百万瓦时(€/兆瓦时);
  • 按来源分类的发电量(煤、天然气、风力等。),以 MWh 计;
  • 和能量需求(“负载”),单位为 MWh。
  • 能源数据集由 29 列 35,064 行组成。

天气文件提供了 2015 年至 2018 年期间西班牙五个主要城市的每小时记录。我们没有“平均”的全国范围的天气数据集,因此分别位于伊比利亚半岛中心和对角的五个大都市地区必须作为整个西班牙的代表性样本。天气数据集包含 178,000 行,大约是能源数据集中的五倍,因为它是在 5 个城市之间逐项列出的。

作者图片

作者图片

除了发电和变化的需求负荷的影响之外,我们将研究温度、风速或湿度与电价的相关程度。

4.2 能源数据

能源数据框架的 info() 函数揭示了我们将不得不处理相当多的空白:不包含任何值的列;和其他不提供 35,064 个非空值的完整补充的列。

作者图片

在第 2 行中,我们首先将“time”列转换为 datetime 变量。

在第 6 行检查重复的行,产生一个否定的结果。

我们将日期时间列定义为数据帧的索引。

第 13 行确认了我们不需要将任何非数字变量转换成数字。

然后,第 17 到 23 行中的函数搜索间隙,如果找到了,就调用 missingno 图来可视化它们是如何分布在列和行中的。

作者图片

我们观察到两个空的特征列。在右边,我们注意到其他列的个别行中有一些间隙。

在删除了第 3 行到第 4 行中的两个空列之后,我们在第 8 行中通过插值来填充剩余的空隙。

对第 10 行中的差距进行最后的检查,确认我们已经成功地处理了所有缺失的值。

作者图片

原始列名包含一些空格和特殊字符。让我们通过重命名这些列来消除它们。

作者图片

小时价格的线图揭示了一条垂直曲线,该曲线似乎不遵循稳定的长期趋势或单一可识别的季节性,而是围绕€60 的平均价格波动。因为我们处理每小时的价格,我们必须假设时间序列服从三个季节性:

  • 每日小时,受白天和营业时间不同用电水平的影响;太阳能的产生仅限于白天
  • 工作日:工作日的用电量会比周末高
  • 月份:季节性需求变化,由温差引起;以及太阳能和风能的季节性波动功率输出

作者图片

4.3 天气数据

天气数据由 17 个特征列组成。info()函数告诉我们不需要处理任何丢失的值。

作者图片

我们首先将“dt_iso”列转换为 datetime 格式,使其与能源数据帧的 datetime 索引兼容。

作者图片

描述性统计揭示了一些明显代表异常值的值。

  • 大气压力不可能上升到 100 万毫巴以上而不压碎甚至一艘核潜艇。
  • 本世纪西班牙还没有记录到高达 133 千米/小时的风速。

温度柱看起来很奇怪,数值超过 300 度。但是这些值只是用开尔文表示,因此并不意味着明显的异常值。

在第 2 行到第 3 行,我们删除了一些不相关或多余的列——这些列代表了定量列的口头总结;以及在过去 3 小时内测量的降雨量,这是多余的,只要我们也测量过去一小时的降雨量。

在第 7 行到第 8 行,我们将开尔文转换为摄氏度。

第 12 到 19 行将数值转换成 float32。

接下来,我们需要调查我们在描述性统计表中看到的异常值。

2015 年 2 月的几天,大气压力高得令人难以置信。它们的正常范围在 1000 毫巴左右的狭窄范围内。

作者图片

2017 年 5 月 11 日,风速中出现了一个明显的异常值。

作者图片

箱线图可以帮助我们获得异常值的直观线索。

湿度和温度显示正常模式。最左边的零湿度可能是测量误差。我们可以考虑用相邻的非零值回填或前填来替换零值。但是在这个练习中,我保持它们不变。

然而,压力和风速的箱线图在标尺的高端隔离了一些严重的异常值。

作者图片

或者,我们可以使用 seaborn 的 distplots 来识别异常值。

温度曲线显示了一个正常的季节循环,从寒冷的冬天到酷热的夏天。如果不与第三方数据进行比较,我们将无法辨别明显的温和异常值。

但是压力和风速分布的极端峰度和偏斜揭示了在它们的右尾部存在异常值。

作者图片

为了处理异常值,我在第 2 行到第 5 行用 null 替换了超过所选阈值的值。然后,第 6 行通过反向插值填充新创建的间隙。

合理的阈值基于维基百科的西班牙极端天气日志:欧洲大气压力记录列表—维基百科

替换极值后,我们得到看起来正常的箱线图。

作者图片

作者图片

能源和天气时间序列具有兼容的日期时间索引。

作者图片

记录了西班牙五个大城市的可用天气数据。

作者图片

当我们检查重复的城市和日期时间指数对的天气记录时,我们发现它们多达 3076 个。第 4 行删除了它们。

如果在第 15 行按城市分组,我们会发现每个城市现在有 35,064 条天气记录,与能源数据集中的记录数量相匹配。

作者图片

我们将每个城市的天气记录单独放在一个数据框架中,为能源和天气记录的合并做准备。第 2 行中的字典理解创建了特定于城市的数据帧。

城市名作为字典的关键字。例如,当我们选择关键字“Bilbao”时,我们可以从字典中检索巴斯克地区的数据帧。

作者图片

5.准备数据

5.1 能源和气象数据的合并

第 3 行到第 7 行中的循环从天气字典中获取特定于城市的数据帧,并通过使用 concat() 方法将它们沿着列轴插入到能源数据帧中。第 4 行到第 5 行用各自城市的名称标记天气列。

作者图片

得到的数据帧 df2 有 67 列。

对空值的检查证实了我们不必处理不匹配。

作者图片

在样本外预测中,我们希望估计未来几个小时的电价。值得怀疑的是,历史价格或任何特征变量已经形成了持续数年的模式,并将影响我们 12 小时后观察的价格。我选择将源数据限制在最后一年的 8760 小时,即 2018 年 1 月至 12 月。否则,培训时间会成倍增加。训练将需要半天或一夜(我不愿意做出这种牺牲,因为我需要处理器的能力来狂看《无垠》的最后一季)。

作者图片

5.2 特征提取

5.2.1 功能与价格的相关性

为了剔除 67 列中不相关的特性,我们计算与价格水平的相关性。

作者图片

让我们通过将输出限制为与价格至少有 25%相关性的那些来浓缩这 66 个特征。

作者图片

我们得到了一长串 28 个与电价有中度到高度相关性的特性。其中大部分由城市温度组成,它们彼此高度相关。显然,这些特征之间有相当多的冗余,相关矩阵通过显示温度单元的深红色矩形来强调这一点。

作者图片

我们可以在两种方法中选择。

第一个是 28 个与价格至少适度相关的原始特征。另一种方法是主成分分析,将 66 个特征减少到仅仅是它们的线性组合。

两种方法我都试过了。

主要成分

主成分分析表明,将需要多达 30 个成分来达到高于 90%的累积方差比。因此,PCA 并没有像我们希望的那样压缩特征列。

作者图片

让我们计算它们与价格水平的相关性,并将输出限制在那些与价格水平至少有 10%相关性的成分上。

作者图片

只有 7 个组成部分与电价水平的相关性高于 10%。他们的低计数应该更容易训练变压器。

作者图片

当变压器使用这些主成分来预测测试集时,它实现了良好的准确性:5.55%的 MAPE。但是我决定采用第一种方法:28 个相关的特性列,这产生了 4.54%的更好的 MAPE。

作者图片

可能的话,我们可以通过包括一些具有弱相关性(低于 10%)的成分来改进基于 PCA 的方法。但是为了便于解释,我更喜欢使用原始的特性,而不是它们在 15 或 20 个组件中不太透明的线性组合。

在 GitHub 上,你可以找到我的两种方法的 Jupyter 笔记本:使用 PCA 的笔记本和使用原始特性的笔记本。

我们将数据帧的宽度缩小到目标变量及其 28 个最相关的特征列。

作者图片

5.2 日期时间功能

接下来,我们将进行一些特性工程,并从 datetime 索引中派生出额外的时间变量。

当我们在单独的列中隔离小时、工作日和月时,转换器会发现更容易识别时间轴上的模式。

数据透视表热图展示了工作日之间的不同价格水平以及每月的季节性波动。我们观察到工作日和周末之间的差异,此时电力需求较低;夏末的几个月里,我们看到了最高的价格。

作者图片

第二个数据透视表揭示了白天和夜间的价格变化。下午早些时候可能意味着传统的西班牙午睡,在此期间商业活动的强度会降低。电力需求在下午晚些时候恢复,然后在晚餐时间激增。

作者图片

这两个热图显示了预测模型必须了解的季节性的三个变量:小时、工作日和月。

5.4 时间序列对象

5.4.1 目标时间序列:价格

Darts 与时间序列对象一起工作,而不是 numpy 数组或 pandas 序列。

第 8 行将 dataframe 的 price 列转换为单变量目标时间序列: ts_P

第 10 到 17 行展示了我们可以从任何时间序列对象中获得的一些属性。

作者图片

特征协变量

接下来,我们将数据帧的特征列转换为时间序列。这些特征被称为目标时间序列的协变量。在回归分析中,我们称它们为自变量或回归变量。变量 ts_covF (“时序:协变特征”)形成一个多元时序对象。

作者图片

必要时,可以使用以下 Darts 函数将 Darts 的时间序列对象转换回 numpy 数组、pandas 系列或 dataframes。

作者图片

作者图片

5.4.3 训练集和测试集的准备

我们应用 Darts 的 split_after() 函数来分离训练集和测试集。在 Jupyter 笔记本的顶部单元格中,我们将常量 SPLIT 设置为我们希望为培训保留的百分比。测试集将覆盖 2018 年 11 月下旬至 12 月底之间的 875 小时或 36.5 天。

我们从电价的目标时间序列 ts_P 开始。

如果神经网络在具有不同量值的张量上训练,它们可能会被引入歧途。因此,我们在第 11 到 12 行实例化 Darts 的 Scaler 类并使其适合训练时间序列。然后,第 13 行到第 15 行对所有三个时间序列应用相同的训练定标器:训练、测试和完整数据集。

这三个转换后的时间序列被标记为 ts_ttrain、ts_ttest 和 ts_t(“时间序列:转换后的训练集”、“时间序列:转换后的测试集”、“时间序列:转换后的完整序列”)

作者图片

接下来,我们在训练集和测试集之间划分特征系列。

我们将一个定标器安装到训练序列中,然后在第 4 到 8 行将其应用到训练和测试特性序列中。

作者图片

5.4.4 日期时间协变量

现在我们将借助 Darts 的效用函数导出一些日期时间协变量。考虑到隐藏在源数据中的复杂的季节性成分,它们将使转换器更容易了解时间模式。

  • 第 2 行到第 4 行从时间戳获得小时。请注意,第 4 行中的“until”参数将范围扩展到 2019 年 1 月 4 日,超过 2018 年 12 月 31 日测试集的结束时间。我们将使用额外的时间戳来准备样本外预测。我们也可以在稍后的阶段创建它们,在训练和测试之后,但是一次性准备所有的日期时间协变量可以节省时间。
  • 第 5 行将工作日堆叠到小时。
  • 第 6 行添加月份。

这三个组成部分将反映我们在源数据中发现的三个季节性。

在第 9 行,一个有用的函数添加了第四个协变量: add_holidays() 接受一个 country_code 参数并标记西班牙的公共假日。

第 14 行将训练集和测试集之间的协变量分开。

我们还需要缩放这些日期时间协变量,在第 18 到 22 行。

作者图片

作者图片

6.变压器设置和培训

TransformerModel 接受我们在顶部的笔记本单元格中定义的超参数。

为了计算一个概率预测,为我们提供预测中心值曲线周围的一组分位数,我们使用参数likelihood = quantier regression运行模型。如果不定义可能性模型,预测将是确定性的:它将返回点估计,而不是将中心点包含在不确定性带之间。

在配置模型之后,我们使它适合训练集。

或者,如果我们将常量 LOAD 设置为 True,那么我们的脚本会将保存的模型加载到内存中(第 2 到 4 行)。保存的模型会保留其训练状态。加载后,我们不需要重新运行耗时的拟合过程,除非我们选择这样做。

7.测试:预测测试集

当训练完成时,或者先前训练的模型被提取到内存中时,我们可以为测试集生成 876 小时的预测。

predict() 函数将预测范围 n 作为其参数:时间步长的数量——这里是我们初始样本内预测的测试集的长度。

num_samples 参数设置预测分位数的百分比。我们在脚本的顶部将其设置为 100。请注意,根据我的实验,更多的样本不会显著增加处理时间。分位数损失函数没有太多的计算成本。

在列表分位数中,我们定义了我们想要获得预测范围的百分比:中心值(中位数),以及 10%/90%、1%/99%和 20%/80%对,以及可选的其他分位数。每个分位数代表一个单变量时间序列。

第 28 行的 list comprehension 遍历列表分位数中的百分比,并对每个百分比调用辅助函数 predQ() 。然后 predQ 收集预测分位数,作为新数据帧 dfY 中的列。

50%的分位数或中位数值得特别关注。这个中心值代表我们将从确定性预测模型中获得的点估计。我们将其赋给一个单独的时间序列变量 ts_q50。

性能指标 q50_RMSE 和 q50_MAPE 将把这个中间预测 ts_q50 与测试集的实际价格进行比较。

作者图片

上面的截图显示了确定性模型的结果:Transformer 模型是在没有要求 QuantileRegression 的情况下配置的。这就是为什么这些行显示常量值的原因。所有这些都只是反映了平均预测值,而不是分位数。这一点的 MAPE 估计是令人满意的 5.35%。

下面的截图显示了概率模型:在变压器的设置中,似然性参数被设置为 QuantileRegression。现在,每一个时间步长的分位数都因列而异。预测中值的 MAPE 为 4.57。

作者图片

下一个笔记本单元格绘制预测。它将预测价格中值与测试集的实际价格进行比较。

8.样本外预测

最后,我们将为测试期结束后的 12 小时准备一个样本外预测。

这要求我们将未来的协变量值输入到 predict() 函数中。

请记住,在定型模型之前,我们已经将日期时间协变量扩展到了 2019–01–04。因此,它们符合要求。

但是我们仍然需要得到特征协变量的预测,特别是天气变量。

如果我们有未来 12 小时的天气预报,我们可以将这些外源数据加载到内存中,并扩展我们现有的时间序列, covF

没有外部数据,我们将需要准备一个特征变量的预测。我们可以应用一种经典方法来为每个特征变量生成单变量预测:例如,SARIMA、指数平滑或 Theta 方法。我们还可以为每一个特性安装额外的变压器模型(没有协变量)。

但让我们在这个练习中保持简单:我们假设 2019 年 1 月 1 日的天气特征和电力需求将遵循与 2018 年 12 月 31 日相同的每小时模式。它们在 12 月 31 日的已知值将代表我们对 1 月 1 日的模拟天气和负载预测。

在第 2 行中,我们建议模型预测测试集结束后 12 小时的价格。

该模型没有在测试集上训练,因此其预测范围包括测试集的长度加上额外的小时数(第 4 行)。

9 号线使用了 tail() 功能的飞镖。tail 函数从现有时间序列中提取最后几个时间步长的值。在我们的例子中,它需要测试集的最后 24 小时。然后 concatenate() 函数将这 24 小时附加到测试集的末尾。这为我们提供了我们需要涵盖 2019 年 1 月 1 日的特征协变量。

第 12 行将时间和特征协变量组合在一个时间序列对象 cov_t_fut 中。

作者图片

predict() 函数采用扩展协变量和预测范围 n_FC 并生成预测。它们涵盖了测试集和 2019-01-01:888 小时的上午时间。

作者图片

我们重新初始化用于收集测试集预测结果的变量。现在,我们用新的扩展预测的预测分位数填充数据框架 dfY。

该屏幕截图显示了测试集的第一行和最后一行,后面是 12 个样本外预测行。

我们通过绘制预测图来完成今天的练习。折线图显示了对实际价格的复杂季节性模式的合理拟合。

作者图片

9.结论

大多数经典的预测方法都没有扩展到多元时间序列。SARIMAX 接受外部回归变量,但难以反映一个以上的季节性顺序。

今天的教程演示了当协变量和复杂的季节性开始起作用时,神经网络预测器“变压器”如何发挥作用。

这两个 Jupyter 笔记本(使用相关功能或其主要组件)可在 GitHub 上下载:h3ik 0th/ES _ energy _ Transformer:Transformer deep forecast(github.com)

数据集元数据:每小时能源需求生成和天气| Kaggle

https://medium.com/subscribe/@h3ik0.th https://medium.com/@h3ik0.th/temporal-fusion-transformer-unleashed-deep-forecasting-of-multivariate-time-series-in-python-674fa393821b

变形金刚:最新颖的人工智能架构概述

原文:https://towardsdatascience.com/transformers-an-overview-of-the-most-novel-ai-architecture-cdd7961eef84

机器学习理论

负责 Siri、Alexa 和 Google Home 的模型

作者图片

转换器彻底改变了自然语言处理、计算机视觉和图像生成领域。在这篇文章中,我将分解这个新颖的架构,并解释它是如何工作的。

介绍

在过去的几年里,最新一代的大规模人工智能模型产生了令人印象深刻的结果。就像过去十年深度学习已经彻底改变了各种各样的行业一样,这种新一代的机器学习模型具有巨大的潜力。像 GPT-3 和 DALL-E 这样依靠变压器运行的模型,已经引发了新的产品、服务和业务,将为社会增加巨大的价值。

变压器是这些模型的组成部分。GPT-3,可以说是迄今为止最先进的自然语言处理(NLP)模型,能够以类似人类的方式完成各种各样的 NLP 任务。它的架构是一个标准的变压器,使它与众不同的是它前所未有的 1750 亿个参数。变压器的效率和简单性使得这种大型模型变得可行。在这篇文章中,我想讨论这些是如何工作的,以及为什么它们如此重要。

是什么让变形金刚与众不同

变压器是一种机器学习模型架构,像长短期记忆神经网络(LSTMs)和卷积神经网络(CNN)。这种新的架构有一些优点,使变压器成为最新的艺术模型的基础。

正如我已经提到的,变形金刚可以非常巨大。更大的模型产生更好的结果,但是训练更昂贵。然而,现代变压器模型尺寸无法通过任何其他架构实现。变压器和其他架构之间的一个关键区别是变压器是高度并行化的,这使它们非常适合计算,并允许我们训练非常大的模型。

让变形金刚与众不同的第二点是它们对注意力的巧妙利用。为了描述他们是如何做到这一点的,我将把重点放在介绍变形金刚的原始论文上:注意力是你所需要的一切。

你需要的只是关注

Ashish Vaswani、Noam Shazeer、Niki Parmar、Jakob Uszkoreit、Llion Jones、Aidan N. Gomez、Lukasz Kaiser、Illia Polosukhin 于 2017 年 6 月提交的论文

变形金刚由一个简单的架构组成,巧妙地利用了注意力。转换器由编码器和解码器组成。在编码器中,模型首先获取要翻译的句子,对其进行矢量化,并使用注意力对其进行转换。解码器反其道而行之,从矢量化转换到句子。例如,解码可以用不同的语言输出结果,使它们在翻译任务中更强大。

我现在将描述注意力是如何工作的,然后是多头注意力是如何工作的,最后我将谈论一个变压器如何使用它们。

注意力

注意力是变形金刚的关键,也是为什么它们是如此强大的架构。注意力层非常有效,比其替代品的复杂性更低:

表 I:各种层的复杂度和最大路径长度(图片来自注意力是你所需要的全部 [1])

自我注意比它的对应部分具有更低的复杂性和更短的路径长度。 n 代表序列长度, d 为表示维数( d 往往大于 n ,文中 d 取 512)。

注意力层是如何工作的

注意层位于转换器的编码器和解码器部分。稍后,我将描述这些层在 Transformer 架构中的位置。

在到达注意层之前,输入文本首先被标记为代表性嵌入。从每一次嵌入,编码器将产生代表性的编码使用注意,和解码器将做相反的事情,输出文本。

作者图片

注意力层的第一级由 3 个神经网络组成。这些将句子中嵌入的单词作为输入,对于每个单词,产生 3 个输出向量:查询、键和值。对句子中的每个单词都这样做,会产生 n 个查询、键和值。

第二阶段包括计算每个单词的分数。每个单词都会有 n 个分数,然后巧妙组合。这允许模型考虑句子中的所有其他单词,以理解每个单词的意思。

看一个句子中的第一个单词,它会被分配一个查询,一个和一个。我们要计算所有其他单词在第一个单词上的分数。为此,我们首先获取第一个单词的查询向量和句子中所有关键向量的点积。这里的点积可以被认为是相似性的度量。如果查询和键向量对齐,它们的点积将产生一个大值。如果查询和关键向量接近正交,点积的结果将接近零。

单词 1 的得分权重(图片由作者提供)

在上图中,第一个单词的查询和句子中所有关键字之间的点积用于产生分数。

第三步是将 SoftMax 标准化并应用于这些值(使它们的总和等于 1)

简单回顾一下:注意力从 3 个神经网络开始,输出每个单词的查询。到目前为止,我们已经使用了查询向量来为每个单词产生一组权重,该权重对我们在查看特定单词时应该对每个单词给予多少关注进行排序。

第四步是使用向量,并使用在前面步骤中计算的分数缩放它们。最后,我们将得到的缩放值向量相加,给出每个单词的注意力向量。

单词 1 的注意力向量(图片由作者提供)

在上图中,基于查询 1 的每个单词的分数用于衡量这些值。然后将这些相加以产生最终矢量。

这个结果向量是查询 1 的结果注意力向量(来自第一个单词)。您将对每个查询都这样做,从而产生 n 个向量,这些是注意力层的输出。注意,每个结果向量只取决于对那个单词的查询,还取决于句子中所有单词的。这就是注意力在连续任务中的强大之处,因为你能够将整个输入的上下文嵌入到每个表示中。

现在我想展示论文中说明自我关注的图表。

图片来自注意力是你所需要的一切 [1]

如您所见,查询用于产生分数,然后将分数应用于向量以产生最终的输出向量。

多头注意力

到目前为止,我们已经看到了单头注意力层。多头注意力层是允许更快计算的单头注意力的扩展。

对于单头注意力,每个单词的嵌入向量有 512 维。在多头关注中,查询向量被分割成 8 个头(64 维向量),关注层被应用到每个头上的方式与我之前展示的方式相同。然后,将每个头部的结果注意力向量连接在一起,以形成 512 维注意力向量。

图片来自注意力是你所需要的一切 [1]

多头注意力仅仅是我之前详细描述的注意力层的扩展,你不需要完全理解它来理解注意力在更大的变形金刚环境中的作用。

变形金刚

现在您已经了解了注意力层的样子,我将通过描述 transformer 架构来结束本文:

图 1:变压器模型架构(图片来自注意力是你所需要的全部 [1])

转换器由编码器(左块)和解码器(右块)组成

编码器:编码器是左边的那块。这个块由一堆 N 个相同的层组成(在文中 N = 6)。每层包含一个多头注意力层,后面是一个全连接的前馈神经网络。每个编码器层的输出被用作后续编码器层的输入。所有层输出具有相同的维度(512,就像嵌入一样)。

解码器:解码器堆栈也由 6 个相同的层组成。每个解码层有 2 个多头注意力层,后面是一个前馈神经网络。在每个解码器层中,第一关注层的输入是来自前一解码器的输出。第二关注层的输入是来自编码器堆栈的输出。

培训和结果

变压器接受了英语到德语和英语到法语翻译任务的训练,并且在这两个问题上都达到了新的最先进水平。该模型使用 8 个 GPU 训练了数天。这些模型比以前发表的任何结果都取得了更好的结果,而训练成本只是其一小部分。

结果是突破性的,从那以后,变压器被用于该行业生产的许多最先进的模型中。

结论

在这篇文章中,我解释了变压器的工作原理。我拿起原始的 Transformer 文件(你所需要的只是注意力)并把它分解成简单易懂的步骤。变形金刚改变了自然语言处理任务的游戏。它与其他模型的不同之处在于它创造性地使用了注意力,并且它是高度并行的,这使得它的训练非常有效。在未来的文章中,我期待着讨论 transformer 如何集成到其他复杂的模型架构中,以及它们如何彻底改变图像和声音生成等行业。

支持我

希望这对你有所帮助,如果你喜欢,你可以跟我来!

您也可以使用我的推荐链接成为 中级会员 ,并访问我的所有文章及更多:https://diegounzuetaruedas.medium.com/membership

你可能喜欢的其他文章

参考

[1]阿希什·瓦斯瓦尼、诺姆·沙泽尔、尼基·帕尔马、雅各布·乌兹科雷特、利永·琼斯、艾丹·戈麦斯、卢卡兹·凯泽、伊利亚·波洛苏欣,《注意力是你所需要的一切》,NIPS,2017 年。可用:https://arxiv.org/abs/1706.03762。

其他有用的链接

变形金刚的惊人插图:

http://jalammar.github.io/illustrated-transformer/

AI Epiphany 的这个视频很棒:

Transformers 和 Multimodal:所有数据类型的相同键

原文:https://towardsdatascience.com/transformers-and-multimodal-the-same-key-for-all-data-types-d990b79741a0

机器学习的世界无疑是迷人的,不断增长,并能够触及最多样化的部门,从医学到太空竞赛,从餐饮到大型制造业。这项技术有无数的应用领域,几十年来也开发了许多技术,但它们都有一个共同点:数据。

每一个机器学习模型的存在和工作都要归功于它能够以这样或那样的方式从数据中学习。然而,这些数据可以采取非常不同的形式,例如,大量的文本来训练语言模型生成句子,理解上下文或讽刺,或识别异常。或者数以百万计的物体、人和动物的图像来创建分类或物体检测模型,甚至音轨来执行诸如识别歌曲或其风格的任务。

作者图片

所有这些都带来了一个大问题:处理如此不同的数据需要不同的技术,因此机器学习的整个独立分支应运而生,每个分支都专注于这些数据类型中的一种。特别是用于语言学的自然语言处理(NLP)、用于图像和视频的计算机视觉(CV)以及用于音轨的音频信号处理(ASP)。

当解决需要混合不同类型的数据的问题时,这个问题变得更加突出,例如找出哪种文本描述最适合图像,或者同时使用音频和视频来识别图像中的异常。

但让我们更深入地探讨一下,试着从头开始追溯形势的演变。

变形金刚的出现

在过去的早期,在数十种深度学习架构中,有两种非常突出,即长短期记忆(LSTM)网络和卷积神经网络(CNN)。

由于 LSTMs,第一种分析不同类型数据的方法出现在文本和音频之间。这些网络旨在有效地分析序列形式的数据。在文本领域工作,把句子看作单词序列是很自然的,但也把音轨看作序列。

作者图片

与此同时,卷积神经网络正在计算机视觉领域取得进展,与 LSTMs 不同,卷积神经网络能够更好地捕捉空间相关性,因此更适合通过移动窗口扫描图像进行图像处理。

基于非常不同的概念,NLP/ASP 和 CV 世界在很大程度上独立发展了几年,接受了由于数据的不同性质,视觉和文本/音频领域不能有一个共同的架构来使用。

沉寂了这么多年,至关重要的转折点似乎来自 2017 年首次呈现《变形金刚架构》[1]的 NLP 领域。

该体系结构也用于分析序列形式的数据,但与 LSTMs 不同,它能够克服一些重要的局限性:

  • 他们能够更好地捕捉输入序列中非常远的部分之间的相关性;
  • 他们利用注意力机制,允许更大的计算并行化;
  • 他们甚至能够分析很长的序列。

查看自然语言处理领域中的一个示例,Transformers 通过利用注意力机制来将句子分析为由单词组成的序列,该注意力机制计算句子中所有可能的单词组合之间的一种关系相关性。因此,如图所示,注意力是在句子的第一个单词和所有其他单词之间、第二个单词和所有其他单词之间等等之间计算的。

作者图片

这样做时,序列的每个部分都相对于所有其他部分进行分析,由于计算是独立的,它们也可以并行!

如果你想深入了解变形金刚的架构,我建议你阅读我之前的概述。

由于这些特点,在很短的时间内,Transformers 成为自然语言处理领域的参考架构,几乎完全取代了 LSTMs。显然,正如可以预料的那样,即使在音频信号处理领域,变压器也开始越来越多地被使用,但几乎没有人会想到的是,这种新的架构也引起了计算机视觉领域研究人员的注意。

如果我们可以将图像转换成序列,转换器能够分析它们并捕捉足够的空间信息来与传统的卷积神经网络竞争吗?

作者图片

答案是肯定的!所谓的视觉变压器[4]背后的想法是将一幅图像分成许多部分,称为补丁,然后将它们线性投影到令牌中。这些令牌与从单词中获得的令牌完全类似,因此,变压器的整个剩余架构可以保持不变。

如前一篇文章所示,计算机视觉领域的变形金刚非常强大,因为与卷积神经网络相比,其架构细节允许它们更好地捕捉全局关系和局部模式。

完成了,变形金刚正式成为我们需要的通用架构。他们可以操纵文本,图像,视频,音频和任何类型的数据,可以变成令牌!

多模态机器学习

现在拥有能够处理不同类型数据的单一架构代表了所谓的多模态机器学习领域的重大进步。

这门学科始于对人类行为的观察。人们能够综合几个来源的信息,得出自己的推论。他们通过用眼睛观察周围的世界,同时也通过闻气味、听声音或触摸形状来接收数据。对我们来说,通过将不同类型的脉冲组合在一起进行工作是完全自然的,但让神经网络做同样的事情一直非常困难。

问题在于以相同的方式处理所有不同的输入而不丢失信息,并且由于变压器,我们现在可以建立一个通用的架构来处理任何类型的数据!

作者图片

VATT:多模态自我监督学习的变形金刚

变形金刚在多模态机器学习领域最重要的应用之一当然是 VATT [3]。

这项研究旨在利用变形金刚处理不同类型数据的能力,创建一个可以同时从视频、音频和文本中学习的单一模型。

为此,建议的体系结构由一个 Transformer 编码器组成,在该编码器上进行三个不同的转发调用。对每种类型的输入数据的一个调用总是被转换成一个令牌序列。转换器将这些序列作为输入,并返回三组不同的特征。然后,这些特征被输入到对比估计块,该对比估计块计算单个损失并执行反向。

图片来自作者

以这种方式,损失是对所考虑的所有三种类型的数据犯下的错误的结果,因此,在各时期之间,模型将学习通过更好地管理来自所有三个不同来源的信息来减少损失。

因此,VATT 代表了多模态机器学习多年来一直试图实现的顶点,即一个可以一起处理完全不同类型数据的单一模型。

GATO:多面手

但是多模态机器学习研究能导致什么令人印象深刻的结果呢?有没有可能实现一个能够接收不同类型的输入,处理它们,甚至执行许多不同性质的任务的神经网络?

如果我告诉你,具有完全相同的内部权重的同一个网络可以从非常不同的来源接收不同的数据输入,并且能够玩 Atari,像真人一样聊天,给图像加字幕,用真正的机器人手臂叠积木等等,你会怎么想?

多亏了 GATO[5],一个多模态、多任务、多化身的通才代表了当今该领域最令人印象深刻的成就之一。

但是加托是如何做到这一切的呢?在内部,同样有一个转换器,它接收不同类型的输入数据,并将其转换为一系列标记。

图片由作者提供。

由于这种输入的统一和 Transformer 架构,该模型将能够从非常不同的来源获取信息,实现前所未有的一般化水平。

图片来自 Deepmind 的论文。

结论

我们看了一下人工智能的新前沿之一,多模态机器学习,并分析了变形金刚在这场革命中的作用。由于这种新的架构能够以有效的方式处理不同类型的输入,通向更通用的神经网络的道路比以往任何时候都更加具体。还有许多步骤要走,但是当本文中讨论的工作出现时,进步是不可否认的。

这些会是通用人工智能的最初信号吗?我们会查清楚的!

参考资料和见解

[1]“Tadas Baltrusaitis 等人”。"多模态机器学习:调查和分类"

[2]“阿希什·瓦斯瓦尼等人”。"[注意力是你所需要的全部](http://Attention is All you Need)"

[3]“哈桑·阿克巴里等人”。 VATT:从原始视频、音频和文本进行多模态自我监督学习的变形金刚

[4]“阿列克谢·多索维茨基等人”。"一幅图像相当于 16x16 个字:大规模图像识别的变形金刚"

[5]“斯科特·里德等人”。" GATO:一个多面手经纪人"

[6]《大卫·柯考米尼》。关于变压器、定时器和注意事项

[7]《大卫·柯考米尼》。"视觉变形金刚中的自我监督学习"

表格数据的转换器

原文:https://towardsdatascience.com/transformers-for-tabular-data-b3e196fab6f4

如何用自己的数据使用 SageMaker 内置的算法 TabTransformer

作者图片(用 OpenAI 的 DALL · E 2 创建)

这是怎么回事?

2022 年 6 月,AWS 宣布一系列新的内置表格数据算法。由于我已经在 NLP 领域研究了一段时间的变形金刚,我当然对 TabTransformer 算法很好奇。

TabTransformer 的前提是有希望的:根据上下文的不同,特性中的分类值可以有不同的含义。就像单词“bank”在句子中可以根据上下文表示不同的意思一样(“river bank”与“Bank of England”),TabTransformer 在表格数据中使用该信息,如果上下文不同,则用不同的表示相同的值。你可以在这篇论文中了解更多。

在本教程中,我们将使用新的内置算法,通过自带数据来利用这一新功能。本教程的代码封装在 Python 脚本中,而不是笔记本中,因此我们可以在本地 IDE 中舒适地运行整个工作流。

本教程的代码可以在这个 Github repo 中找到。

为什么这很重要?

TabTransformer 使用自然语言处理(NLP)的上下文嵌入概念,并将其扩展到表格数据,这可能会导致对数据的更好理解,并可能最终导致比传统分类模型更好的结果。

但之所以这是一件大事,是因为绝大多数 ML 项目仍然是分类或回归问题,并处理表格数据。因此,我们可以通过新架构实现的任何改进都会产生巨大的影响,这仅仅是因为存在大量的表格数据项目。

行动(或活动、袭击)计划

AWS 文档提供了关于如何使用 TabTransformer 的示例代码,但是该代码使用了已经相应准备好的标准数据集。然而,在本教程中,我们想带来我们自己的数据集,以便我们了解如何设置整个 ML 工作流端到端。

我们将准备数据并上传到 S3 桶,SageMaker (SM)可以在那里访问它。然后,我们将开始一项 SM 培训工作,以培训 TabTransformer 模型并将其部署到一个 SM 端点。一旦我们完成了这些,我们就可以用维持测试集来测试模型的性能。

作者图片

数据

我们将使用的数据集是来自 UCI 机器学习网站的德国信用数据集。它通过数据库内容许可证(DbCL)1.0 版获得许可,明确授予商业使用的权利。

数据集由 1000 条记录组成,包含 20 个特征和一个目标变量。每一个条目都代表一个获得荣誉的人。根据属性集,每个人被划分为好的或坏的信用风险(1 =好,2 =坏)。

对于 TabTransformer 来说,这是一个特别好的数据集,因为它有几个分类特征,这是该算法的亮点。

数据准备

我们可以直接从网站上下载数据到熊猫的数据框架中,就像这样:

如上所述,数据由 20 个属性和一个目标列组成。文档描述了我们必须如何准备数据。首先,目标变量需要是数据集中的第一列。接下来,我们要改变目标变量,使我们有 0 和 1,而不是 1 和 2。

然后,我们希望将数据分为训练、验证和测试数据集。训练和验证数据将用于训练模型,而测试集将用于在训练完成后评估模型性能。

最后,我们需要在 S3 桶中指定文件夹,以便算法可以访问数据。文件夹中的最终结构应该如下所示:

作者图片

我们可以这样实现:

模特培训

一旦我们准备好并上传了数据,我们就完成了大部分的前期工作。我们现在已经建立了能够运行算法的结构。事实上,我们可以从文档中复制/粘贴代码。我们所需要做的就是调整路径,我们准备好了。

需要注意的一点是,我们可以提取算法的 hyper 参数的默认值,然后使用这些默认的值运行训练作业,或者像这样更改它们:

一旦我们开始了培训工作,我们就可以检查它的状态,并在培训完成后进行部署:

测试模型

一旦我们部署了模型,我们就可以通过使用数据准备步骤中的维持测试数据集来测试它的性能。我们需要做的只是在将数据提交给端点之前序列化数据,并解析响应以获得预测的标签:

然后,我们将这些预测与实际标签进行比较,并检查常用指标:

作者图片

这些结果是有希望的,不同的超参数值可能会进一步改进模型。

结论

在本教程中,我们对自己的数据使用了 TabTransformer 算法。我们已经准备了数据,根据我们的数据训练了算法,并在我们的 AWS 帐户中部署和测试了模型。

接下来我们可以:

  • 将 TabTransformer 算法的结果与传统的非深度学习算法(如 XGBoost)进行比较。
  • 查看 TabTransformer 在具有大量缺失数据的数据集上表现更好的能力,因为从 TabTransformer 学习到的上下文嵌入对于缺失和有噪声的数据特征都非常健壮,并且提供了更好的可解释性。
  • 检查 TabTransformer 使用未标记数据训练模型的能力,因为这些模型在从未标记数据中提取有用信息以帮助监督训练方面很有前途,并且在未标记数据的大小很大时特别有用。

我希望这个教程是有用的,如果你有任何意见或问题,请随时联系我。

表格数据转换器(第三部分):分段线性和周期编码

原文:https://towardsdatascience.com/transformers-for-tabular-data-part-3-piecewise-linear-periodic-encodings-1fc49c4bd7bc

先进的数字嵌入提高了性能

由 Pawel Czerwinski 在 Unsplash 拍摄的照片

介绍

这是我探索表格数据转换器的第三部分。

  • 第 1 部分:TabTransformer
  • 第 2 部分:具有线性数字嵌入的 FT-变压器

在第 2 部分中,我描述了线性数字嵌入以及它们是如何在 FT-Transformer 模型中使用的。这篇文章将探索数字嵌入的更复杂的版本,所以如果你还没有阅读前一部分,我强烈建议从那里开始,然后再回到这篇文章。

FT-变压器。图片作者。

提醒一下,上面你可以看到之前探索过的 FT-Transformer 的架构。该模型首先嵌入数字和分类特征,然后将这些嵌入通过变换器层,以获得最终的 CLS 令牌表示。

数字特征嵌入是一个相对较新的研究课题,这篇文章将深入探讨两种新提出的数字嵌入方法——分段线性编码和周期编码。Gorishniy 等人(2022)在名为关于表格深度学习中数字特征的嵌入的论文中描述了这两种方法。看完这篇文章后,一定要去看看!

如果你对简单地应用这些方法感兴趣,那么去实用的笔记本看看,在那里我展示了如何用tabtransformertf 包来使用它们。如果你对这些方法实际上是如何工作的感兴趣,那么请继续阅读!

数字嵌入

数字嵌入层将单个浮点转换成密集的数字表示(嵌入)。这种转换很有用,因为这些嵌入可以与分类块一起通过 Transformer 块传递,从而增加了更多的学习内容。

线性嵌入

线性嵌入。图片作者。

简单回顾一下,线性嵌入层是简单的全连接层(可选地具有 ReLU 激活)。这些图层之间不能共享权重,这一点很重要,因此每个数字要素都有一个嵌入图层。更多信息,请阅读之前关于 FT-Transformer 的帖子。

周期性嵌入

周期性激活的想法现在在 ML 中相当流行。例如,Transformer 架构中的周期性编码允许模型表示单词在句子中的位置(您可以在此处阅读更多相关信息,例如)。但是如何将它应用于表格数据呢?Gorishniy 等人(2022)提出以下等式来编码特征 x :

周期性编码方程。资料来源:Gorishniy 等人(2022 年)

让我们试着解开这个方法。编码过程有三个主要步骤:

  1. 使用学习向量( c )转换为预激活值( v )
  2. 使用正弦和余弦激活值( v
  3. 正弦值和余弦值的串联

第一步是学习发生的地方。特征的原始值乘以学习参数 c_i ,其中 i 是嵌入的维度。因此,如果我们选择嵌入维数为 3,那么每个特征将有 3 个参数要学习。

作为说明性示例,考虑下面随机生成的特征。

随机特征分布。由作者策划。

使用三个不同的 c 参数,我们可以将其转换为三个预激活值(即维数为 3 的嵌入)。

随机特征的周期性预激活嵌入。由作者策划。

然后,使用正弦和余弦运算将这些激活前值转换为激活后值。

随机特征的周期性后激活嵌入。由作者策划。

如你所见,斜率影响周期性激活频率。斜率较大的预激活(蓝线)具有频率较高的后激活值。另一方面,斜率较小的预激活值(绿色和橙色线)会导致低频激活。从上图判断,1的特征值大概会被编码为[-0.98, -0.90, 0.85],-1会被编码为[0.97, 0.8, -0.9]

周期性嵌入。图片作者。

作者还建议在周期性编码的基础上添加一个额外的线性层,这样最终的嵌入图看起来如上所示。

分段线性编码(分位数宁滨)

这种嵌入方法从 one-hot-encoding(一种流行的分类编码方法)中获得灵感,并使其适应数字特征。该过程的第一步是将一个特征分割成 t 个箱。作者提出了两种分割方法——分位数法和目标宁滨法。本节描述第一种方法,第二种方法将在后面介绍。

分位数宁滨相对简单-我们将特征分成 t 等宽的条柱。例如,如果我们想要以 3 个箱结束(即 t = 3 ),我们要计算的分位数是— 0, 0.33, 0.66, 1.0

随机特征的分位数宁滨。由作者策划。

每个分位数( Bt )被表示为一个元组— [bin_start, bin_end),所以在这种情况下,我们最终有 3 个箱— [-3.85, -0.41), [-0.41, 0.44), [0.44\. 3.26)。这种表示的公式符号如下:

宾斯公式符号。资料来源:Gorishniy 等人(2022 年)

一旦我们获得了这些箱,我们就可以开始对特征进行编码。编码公式如下所示。

PLE 公式。资料来源:Gorishniy 等人(2022 年)

正如你所看到的,对于每个值,我们都将以一个 t 维嵌入结束。总共有 3 个选项——0、1 或介于两者之间。对每个 bin 和每个值应用此公式后,我们的嵌入结果如下所示:

随机特征的 PLE 嵌入。由作者策划。

正如你所看到的,较小的值只有一个“活动嵌入”(PLE 1),在中间我们得到 PLE 2 活跃。最后,在最后一个 bin 中,所有三个嵌入都被激活。这样,-1的值近似变成了[0.8, 0.0, 0.0],而1变成了[1.0, 1.0, 0.2]

分位数宁滨的 PLE 嵌入。图片作者。

目标宁滨方法

目标宁滨包括使用决策树算法来帮助构建箱。正如我们在上面看到的,分位数方法将我们的要素分割成等宽的条柱,但在某些情况下可能不是最佳的。决策树将能够找到关于目标的最有意义的分割。例如,如果有更多的目标方差朝向更大的要素值,则大多数条柱可能会向右移动。

PLE 嵌入目标宁滨。图片作者。

报告的结果

Gorishniy 等人(2022 年)报告的结果摘录

本文结合 MLP、雷斯内特和变压器架构,对所有提出的嵌入方法进行了广泛的比较研究。本表中 L 代表线性, Q 代表分位数, T 代表目标, LR 代表带 ReLu 的线性, P 代表周期性。

从上表可以看出,整个数据集没有唯一的赢家(没有免费的午餐定理在起作用),因此嵌入类型可能被视为另一个要优化的超参数。然而,当我们将周期和 PLE 编码与简单的线性嵌入进行比较时,大多数时候我们会看到性能的显著提高。

验证结果

让我们看看是否可以在一个流行的玩具数据集上重现本文的结果——California Housing。你可以在这里看到完整的工作笔记本,而下面我将介绍建模所需的主要部分。像以前的帖子一样,我将使用我的tabtrasnformertf 包(如果你喜欢,请给它一个星形⭐️),你可以使用命令pip install -U tabtransformertf轻松安装(或更新)它。

数据下载和预处理

我们可以使用sklearn玩具数据库下载数据。预处理过程非常简单——进行 train/val/test 分割,缩放数据并将其转换为 TF 数据集。

周期性嵌入

要使用周期性嵌入,您只需在numerical_embedding_type参数中指定即可。所有其他参数我已经在之前的帖子中介绍过了,所以如果你有任何问题,请参考一下。

具有分位数宁滨嵌入的 PLE

同样的程序适用于 PLE 分位数嵌入。您唯一需要更改的是将numerical_embedding_type参数设置为ple

PLE 与目标宁滨嵌入

如果您更喜欢使用基于目标的宁滨,而不是分位数,则需要指定几个附加参数。

  • 需要为决策树训练提供目标(参数y)。
  • 需要指定决策树task,可以是regression也可以是classification
  • 也可以指定额外的决策树参数(ple_tree_params)(可选)

FT 变压器培训

您可以像任何其他 Keras 模型一样使用嵌入来训练 FT 转换器。下面你可以看到训练其中一个模型的代码,但是同样的步骤也适用于其余的模型。

估价

既然模型已经训练好了,我们可以将它们相互比较,与基线比较,与论文中报告的结果比较。请记住,这里报告的数字只是针对单个模型运行和训练/测试分割,因此您的结果很可能会有所不同(但相对性能应该大致相同)。

验证损失历史。由作者生成。

如您所见,对于这个特定的数据集,使用更复杂的数字嵌入有巨大的价值。具有 PLE 嵌入的 FT-变压器给出最好的结果,其次是周期性嵌入。

按模型划分的 RMSE 指标。由作者生成。

当我们将结果与两个基于树的模型(Random Forest 和 CatBoost)进行比较时,我们可以看到,具有 PLE 嵌入的 FT 转换器优于第一个,并接近第二个的性能。鉴于数据集很小,而且深度学习不太友好,这是非常令人印象深刻的。

观察到的性能比论文中报道的性能差。这很可能是由于次优超参数或实现中的差异。此外,论文中报告的结果是多次运行的平均值,因此这也可以解释一些变化。

结论

在这篇文章中,我们探索了两种强大的数字嵌入方法——周期编码和分段线性编码。你看到了它们如何转换数字特征,以及如何使用tabtransformertf包将它们用于 FT-Transformer。

虽然这两种方法在嵌入数字特征时应用了非常不同的逻辑,但它们都可以极大地提高深度学习模型的性能。这些方法的主要缺点是模型可能需要更长的时间来训练,但在 GPU 上这种差异可以忽略不计。所以在你的数据集上尝试一下,让我知道效果如何!

文献学

  • 加州住房数据集;佩斯,r .凯利和罗纳德·巴里,稀疏空间自回归,统计和概率信,33(1997)291–297(CC0:公共领域)
  • Gorishniy 等人,2022 年,关于表格深度学习中数字特征的嵌入

用于表格数据的转换器:TabTransformer 深入分析

原文:https://towardsdatascience.com/transformers-for-tabular-data-tabtransformer-deep-dive-5fb2438da820

理解我们的 TabTransformer 并学习应用它

Samule 孙在 Unsplash 上的照片

介绍

今天,变压器是大多数最先进的自然语言处理(NLP)和计算机视觉(CV)架构中的关键模块。尽管如此,表格领域仍然主要由梯度推进决策树(GBDT)主导,所以有人试图弥合这一差距是合乎逻辑的。第一个基于 transformer 的模型是由黄等人(2020)在他们的 TabTransformer:使用上下文嵌入的表格数据建模论文中介绍的。

这篇文章旨在提供论文的概述,深入模型细节,并向您展示如何使用 TabTransformer 处理您的数据。

论文概述

本文的主要思想是,如果我们使用转换器将正则范畴嵌入转换为上下文嵌入,正则多层感知器(MLP)的性能可以得到显著提高。让我们稍微消化一下这种说法。

范畴嵌入

在深度学习模型中使用分类特征的经典方法是训练它们的嵌入。这意味着每个分类值都有一个唯一的密集矢量表示,可以传递给下一层。例如,下面你可以看到每个分类特征都用一个 4 维数组表示。这些嵌入然后与数字特征连接,并用作 MLP 的输入。

带有范畴嵌入的 MLP。图片作者。

语境嵌入

该论文的作者认为,范畴嵌入缺乏上下文含义,即它们不编码范畴变量之间的任何交互和关系。为了将嵌入上下文化,建议使用当前在 NLP 中用于完全相同目的的转换器。

TabTransformer 中的上下文嵌入。图片作者。

为了形象化动机,考虑下面经过训练的上下文嵌入的图像。突出显示了两个分类特征— 关系(黑色)婚姻状况(蓝色)。这些特征是相关的,因此“已婚”、“丈夫”和“妻子”的值在向量空间中应该彼此接近,即使它们来自不同的变量。

经过训练的 TabTransformer 嵌入示例。图片作者。

通过训练的上下文嵌入,我们确实可以看到“已婚”的婚姻状态更接近于“丈夫”和“妻子”的关系水平,而“未婚”的分类值来自右侧的单独聚类。这种类型的上下文使得这些嵌入更加有用,而使用简单的分类嵌入是不可能的。

TabTransformer 架构

考虑到上述动机,作者提出了以下架构:

TabTransformer 架构。改编自黄等(2020)

我们可以将该架构分为 5 个步骤:

  1. 数字特征被标准化并向前传递
  2. 嵌入了分类特征
  3. 嵌入通过变换器块传递 N 次,以获得上下文嵌入
  4. 上下文范畴嵌入与数字特征连接在一起
  5. 连接通过 MLP 得到所需的预测

虽然模型架构非常简单,但作者表明,增加变压器层可以显著提高性能。当然,神奇之处就发生在这些变压器模块内部,所以让我们更详细地探索一下。

变形金刚(电影名)

变压器架构。改编自瓦斯瓦尼等人(2017)

你可能以前见过变形金刚的架构(如果你没有,我强烈推荐这款带注释的笔记本)但是快速回顾一下,记住它由编码器和解码器部分组成(见上文)。对于 TabTransformer,我们只关心将输入嵌入上下文化的编码器部分(解码器部分将这些嵌入转换为最终输出)。但是它到底是怎么做到的呢?答案是——多头注意力机制。

多头注意力

引用我最喜欢的关于注意力机制的文章:

自我关注背后的关键概念是,它允许网络学习如何最好地在输入序列的片段之间传递信息。

换句话说,自我关注有助于模型找出在代表某个单词/类别时,输入的哪些部分更重要,哪些部分不太重要。我强烈推荐阅读上面引用的文章,以获得关于它为什么如此有效的良好直觉。

多头关注。改编自瓦斯瓦尼等人(2017)

注意力是通过三个学习矩阵来计算的——Q、K 和 V,分别代表查询、关键字和值。首先,我们将 Q 和 K 相乘得到注意力矩阵。该矩阵被缩放并通过 softmax 层。然后,我们将它乘以 V 矩阵得到最终值。为了更直观的理解,考虑下图,它显示了我们如何使用矩阵 Q、K 和 v 从输入嵌入到上下文嵌入。

自我关注流可视化。图片作者。

通过重复这个过程 h 次(用不同的 Q、K、V 矩阵),我们得到了多个上下文嵌入,形成了我们最终的多头注意力。

简短回顾

我知道这是很多,所以让我们总结一下上面所说的一切。

  • 简单的分类嵌入不包括上下文信息
  • 通过将分类嵌入传递给 Transformer Encoder,我们能够将嵌入上下文化
  • Transformer 架构可以将嵌入上下文化,因为它使用多头关注机制
  • 多头注意力使用矩阵 Q、K 和 V 来寻找有用的相互作用和相关性,同时对变量进行编码
  • 在 TabTransformer 中,上下文嵌入与数字输入连接,并通过简单的 MLP 输出预测

虽然 TabTransformer 背后的想法非常简单,但注意力的机制可能需要一些时间来掌握,所以我强烈建议你重新阅读上面的解释,如果你感到迷失,请按照所有建议的链接进行操作。会变得容易的,我保证!

结果

结果部分。改编自黄等(2020)

根据报告的结果,TabTransformer 优于所有其他深度学习表格模型(尤其是我在这里提到的 TabNet)。此外,它接近 GBDTs 的性能水平,这非常令人鼓舞。该模型对缺失和噪声数据也相对稳健,并在半监督设置中优于其他模型。然而,这些数据集显然不是详尽的,正如进一步的论文所证明的(例如 this ),仍然有很大的改进空间。

演示

现在,让我们最终了解如何将模型应用到您自己的数据中。示例数据取自表格形式的操场追逐赛。为了方便地使用 TabTransformer,我创建了一个[tabtransformertf](https://github.com/aruberts/TabTransformerTF)包。它可以使用pip install tabtransformertf进行安装,并允许我们在没有大量预处理的情况下使用该模型。下面您可以看到训练模型所需的主要步骤,但请务必查看补充笔记本了解更多细节。

数据预处理

第一步是设置适当的数据类型,并将我们的训练和验证数据转换成 TF 数据集。先前安装的软件包有一个很好的实用工具来做到这一点。

下一步是为我们将传递给主模型的分类数据准备预处理层。

预处理到此为止!现在,我们可以开始构建模型了。

TabTransformer 模型

初始化模型非常容易。有几个参数需要指定,但最重要的是— embbeding_dimdepthheads。所有的参数都是在超参数调整后选择的,所以检查笔记本来查看程序。

模型初始化后,我们可以像其他 Keras 模型一样拟合它。训练参数也可以调整,所以可以随意调整学习速度和提前停止。

估价

竞争指标是 ROC AUC,所以让我们一起使用它和 PR AUC 来评估模型的性能。

也可以自己给测试集打分,提交给 Kaggle 。这个解决方案把我放在了前 35%的位置,这还不错,但也不算太好。为什么 TabTransfromer 表现不佳?可能有几个原因:

  • 数据集太小,深度学习模型是出了名的数据饥渴
  • TabTransformer 很容易在像 tabular playground 这样的玩具例子上过度拟合
  • 没有足够的分类特征使模型有用

结论

本文探讨了 TabTransformer 背后的主要思想,并展示了如何使用[tabtransformertf](https://github.com/aruberts/TabTransformerTF)包来应用它。

TabTransformer 是一个有趣的架构,在当时胜过了许多/大部分深度表格模型。它的主要优点是它将范畴嵌入情境化,这增加了它们的表达能力。它使用分类特征上的多头注意力机制来实现这一点,这是 Transformers 对表格数据的第一个应用之一。

该架构的一个明显缺点是数字特征被简单地传递到最终的 MLP 层。因此,它们没有被语境化,它们的值也没有在分类嵌入中被考虑。在下一篇的文章中,我将探索如何修复这个缺陷并进一步提高性能。所以,一定要看完,给我你的想法!

接下来读什么…

  • 表格数据的转换器(第二部分):线性数字嵌入
  • 表格数据转换器(三):分段线性&周期编码

变形金刚在行动:注意力是你所需要的

原文:https://towardsdatascience.com/transformers-in-action-attention-is-all-you-need-ac10338a023a

变形金刚(电影名)

简短的调查、说明和实施

图一。人工智能生成的艺术品。提示:童话小屋风格的家的街景。稳定扩散生成的照片。链接到完整提示。

目录

1.简介

2.快速回顾注意力

3.变压器架构
3.1。编码器和解码器组件
3.1.1。编码器3 . 1 . 2
。解码器3.2
。3.3 变压器中的模块
。注意模块
3.3.1。3 . 3 . 2 缩放点积注意事项
。多头关注
3.4。3 . 4 . 1注意变压器中的变型。自我关注
3.4.2。被掩盖的自我注意(自回归或因果注意)
3.4.3。交叉关注
3.5。逐位 FFN
3.6。残差连接和归一化
3.7。位置编码
3.7.1。绝对位置信息
3.7.2。相对位置信息

4.使用自我关注背后的动机

5.研究前沿

6.问题

7.总结

  1. TransformerX 库

9.参考文献

1.介绍

作为人工智能研究过程中的一个成功前沿, Transformers 被认为是一种新颖的深度前馈人工神经网络架构,它利用了自我注意机制,可以处理输入序列项之间的长程相关性。由于在行业和学术研究中取得了巨大成功,自 Vaswani 等人[3]于 2017 年提出以来,研究人员已经提出了丰富的变压器架构(也称为 X-formers),并已在许多领域得到采用,如自然语言处理(NLP)、计算机视觉(CV)、音频和语音处理、化学和生命科学;他们可以在前面提到的学科中取得 SOTA 的成绩。在本文中,我通过底层数学、python 代码实现和不同层的可视化解释了 transformer 架构。端到端的例子可以在 GitHub 上的 TransformerX 库库中找到。

2.注意力的快速回顾

诸如注意机制和与编码器-解码器模型相关的术语等低级概念是变形金刚的基本思想。因此,我提供了这些方法的简要总结。

注意是一种认知资源的分配方案,具有有限的处理能力【1】。

Bahdanau 等人[2]提出的注意力背后的一般思想是,当翻译每一步中的单词时,它搜索位于输入序列中不同位置的最相关的信息。在下一步中,它为源令牌(单词)wrt 生成翻译。1)这些相关位置的上下文向量,以及 2)先前同时生成的单词。

它们可以根据几个标准分为不同的类别,例如:

  • 注意的柔软度:
    1。软 2。硬 3。本地 4。全球的
  • 输入特征的形式:
    1。逐项 2。位置方面
  • 输入表示: 1。共同关注 2。自我关注 3。与众不同的注意力。分层注意
  • 输出表示:
    1。多头 2。单输出 3。多维的

如果你觉得注意力机制处于未知领域,我推荐你阅读下面这篇文章:

** **

3.变压器架构

基本变换器[3]架构由两个主要构建模块组成,即编码器和解码器模块。编码器从输入表示序列(𝒙₁ ,…, 𝒙ₙ)生成嵌入向量𝒁 = (𝒛₁ ,…, 𝒛ₙ),并将其传递给解码器以生成输出序列(𝒚₁ ,…, 𝒚ₘ).)在每一步产生输出之前,𝒁向量被输入解码器,因此模型是自回归的。

图二。变压器架构。照片由作者拍摄。

3.1.编码器和解码器组件

与序列到序列模型类似,转换器使用编码器-解码器架构。

3.1.1.编码器

编码器只是多个组件或层的堆叠——𝑵在原始论文中是 6——它们本身是一组两个子层,即多头自关注块和简单的位置式 FC FFN(全连接前馈网络)。为了实现更深的模型,研究人员通过包裹两个子层中的每一个,然后进行层归一化,来练习剩余连接。因此,每个子层的输出都是layer norm(𝒙+sublayer(𝒙)Sublayer( 𝒙 ) 是在自身内部实现的函数。所有子层以及嵌入的输出维度是𝒅 _model = 512

变压器编码器模块的实现:

Transformer 编码器实现( TransformerX Discord 服务器讨论)。由作者编写的代码。

3.1.2.解码器

除了编码器中使用的子层之外,解码器对编码器组件的输出应用多头关注。像编码器一样,残差连接被附加到子层,然后进行层归一化。为了保证位置𝒊的预测可以仅依赖于先前已知的位置这一事实,将另一个修改应用于自关注子层,以防止位置关注其他位置,同时将输出嵌入偏移一个位置。

变压器解码器模块的实现:

Transformer 解码器实现( TransformerX Discord 服务器供讨论)。代码由作者编写。

3.2.变压器中的模块

接下来,我将讨论组成原始 transformer 架构的基本组件。

  1. 注意模块
  2. 位置式前馈网络
  3. 剩余连接和规范化
  4. 位置编码

3.3.注意模块

该转换器集成了来自信息检索的查询键值(QKV)概念和注意机制

  • 标度点积注意力
  • 多头注意力

3.3.1.标度点积注意力

图 3。成比例的点积注意力。照片由作者拍摄。

缩放的点积注意力被公式化为:

情商。一

其中𝑲 ∈ ℝ^𝑀×𝐷𝑘、𝑸 ∈ ℝ^ 𝑵 ×𝐷𝑘和𝑽 ∈ ℝ^ 𝑴×𝐷𝑣是表示矩阵。键(或值)和查询的长度分别由𝑴和𝑵表示,它们的维度由𝐷𝑘和𝐷𝑣.表示方程中的矩阵𝑨。1 通常被称为注意力矩阵。他们使用点积注意力而不是加法注意力的原因是由于矩阵乘法优化技术,在实践中的速度和空间效率,加法注意力使用具有单个隐藏层的前馈网络来计算兼容性函数。尽管如此,对于大𝐷𝑘值的点积有一个很大的缺点,它会将 softmax 函数的梯度推到极小的梯度。为了抑制 softmax 函数的梯度消失问题,键和查询的点积除以𝐷𝑘的平方根,由于这个事实,它被称为缩放的点积。

点积注意模块的实现:

使用 TransformerX 的缩放点积实现( Discord 社区讨论)。代码作者作者。

3.3.2.多头注意力

图 4。a .多头关注。b .多头注意中张量运算的端到端流程。照片由作者拍摄。

引入多个注意头而不是单个注意函数,Transformer 分别用不同的、学习到的线性投影 h 次将𝐷𝑚-dimensional 原始查询、键和值线性投影到𝐷𝑘、𝐷𝑘和𝐷𝑣维度;通过它,注意力函数(等式。1)在这些投影上可以并行执行,产生𝐷𝑣-dimensional 输出值。该模型然后将它们连接起来,并产生一个𝐷𝑚-dimensional 表示。

情商。2

在哪里

情商。3

这些投影是𝑾𝑸ᵢ ∈ ℝ^d_model×dk、𝑾𝑲ᵢ ∈ ℝ^d_model×dk、𝑾𝑽ᵢ ∈ ℝ^d_model×dv 和𝑾𝒐 ∈ ℝ^hdv×d_model 矩阵。*

这个过程使得变换器能够共同关注不同的表示子空间和位置。为了使它更具体,对于一个特定的形容词,一个头可能捕捉形容词的强度,而另一个头可能关注它的消极和积极。

多头注意力的实现

使用 transformer x(Discord community)的多头注意力实现。代码由作者编写。

可以看出,多头注意力具有三个确定张量维度的超参数:

  • 注意力头的数量
  • 模型大小(嵌入大小):嵌入向量的长度。
  • 查询、键和值大小:输出查询、键和值矩阵的线性图层使用的查询、键和值权重大小

3.4.变压器中的注意变量

在最初的 Transformer 论文中使用了三种不同的方式来使用注意力,这三种方式在将键、查询和值提供给注意力函数的方式上是不同的。

  • 自我关注
  • 隐蔽的自我注意(自回归或因果注意)
  • 交叉注意

3.4.1.自我关注

所有的关键字、查询和值向量来自相同的序列,在变换器的情况下,编码器的前一步输出,允许编码器的每个位置同时关注它自己的前一层中的所有位置,即𝑸 = 𝑲 = 𝑽 = 𝑿(前一编码器输出)。

图 5。自我注意张量运算。照片由作者拍摄。

3.4.2.隐蔽的自我注意(自回归或因果注意)

尽管有编码器层,但在解码器的自我关注中,查询被限制在它们之前的键值对位置以及它们的当前位置,以便保持自回归特性。这可以通过屏蔽无效位置并将其设置为负无穷大来实现,即𝑨𝒊𝒋=∞。如果𝒊 < 𝒋.

3.4.3. Cross-attention

This type of attention obtains its queries from the previous decoder layer whereas the keys and values are acquired from the encoder yields. This is basically the attention used in the encoder-decoder attention mechanisms in sequence-to-sequence models. In other words, cross-attention combines two different embedding sequences with the exact dimensions which derive its queries from one sequence and its keys and values from the other. Let’s assume S1S2 是两个嵌入序列,交叉注意力从 S1 获得其键和值,并从 S2 获得其查询,然后计算注意力得分并产生长度为 S2 的结果序列。在转换器的情况下,键和值来自编码器,查询来自前一步骤的解码器输出。

图 6。交叉注意张量运算。照片由作者拍摄。

值得一提的是,两个输入嵌入序列可以是不同的形式(即文本、图像、音频等。).

3.5.位置明智的 FFN

在编码器和解码器中的每个子层之上,以完全相同的方式单独地将位置式全连接前馈网络应用于每个位置,然而,参数在层与层之间是不同的。它是一对线性层,中间有一个 ReLU 激活函数;它等同于内核大小为 1 的两层卷积。

情商。四

其中 x 为前一层的输出,𝑾₁ ∈ ℝ^𝐷_ 模型 × 𝐷𝑓,𝑾₂ ∈ ℝ^𝐷𝑓 × 𝐷_ 模型,𝒃₁ ∈ ℝ^𝐷𝑓,T7 模型为可训练矩阵,内层

实施基于位置的 FFN:

逐位 FFN 实现(变压器 x 不和谐社区)。由作者编写的代码。

3.6.剩余连接和规范化

用剩余连接包装每个模块支持更深层次的架构,同时避免梯度消失/爆炸。因此,转换器使用模块周围的剩余连接,然后进行层规范化。它可以表述如下:

  • 𝒙′=layernorm(selfattention(𝑿)+𝑿)
  • 𝒙=layernorm(ffn(𝒙')+𝒙')**

图 7。剩余连接和层标准化。照片由作者拍摄。

剩余连接和归一化的实现:

剩余连接和归一化实现( TransformerX Discord 服务器讨论)。由作者编写的代码。

3.7.位置编码

Transfomer 的研究人员使用了一个有趣的想法,将有序感注入到输入令牌中,因为它没有递归或卷积。绝对和相对位置信息可用于暗示输入的序列顺序,其可以被学习或固定。矩阵之间的求和过程需要相同大小的矩阵,因此,位置编码维数与输入嵌入维数相同。它们被注入编码器和解码器模块底部的输入编码中。Vaswani 等人[3]在正弦和余弦函数的帮助下使用固定位置编码,然而,他们试验了相对位置编码,并意识到在他们的情况下,它产生几乎相同的结果[4]。设𝑿是包含 n 个 d 维嵌入的表征的输入表示。位置编码产生𝑿 + 𝑷,其中𝑷是相同大小的位置嵌入矩阵。第 I 行 (2 𝒋 )第(2 𝒋 +1)第列的元素为:

情商。5

情商。七

在位置嵌入矩阵 P 中,行表示记号在序列中的位置,列表示不同的位置编码维度。

位置编码实现(transformer x Discord community)。由作者编写的代码。

在下面的可视化中,我描述了矩阵𝑷中 4 列之间的差异。请注意不同列的不同频率。

图 8。位置编码。照片由作者拍摄。

3.7.1.绝对位置信息

在位置编码类型中,频率根据元素的位置交替变化。举例来说,看看下面的二进制编码;最没有价值的位置(右侧)上的数字波动更频繁,而具有更有价值的位置的其他数字相对于它们的位置具有较少的波动,即最有价值的位置更稳定。

*0 **->** 000
1 **->** 001
2 **->** 010
3 **->** 011
4 **->** 100
5 **->** 101
6 **->** 110
7 **->** 111*

3.7.2.相对位置信息

除了上面的位置编码,另一种方法是学习通过相对位置来参加。对于任何固定位置的𝛿,𝛿+𝒊的位置编码可以通过将其线性投影到位置𝒊 得到。ψ=1/(10000^(2𝒋/d)),任何一对情商。4 和 eq。对于任何固定的偏移𝛿:,可以线性投影到𝛿+𝒊的位置**

情商。七

4.使用自我关注背后的动机

在“注意力是你所需要的全部”[3]论文中,研究人员在将自我注意力与卷积层和递归层进行比较时考虑了多个标准。这些需求可以分为三大类:

表 1。每层的计算复杂度、每层中发生的最小顺序操作以及最大路径长度。其中 n 表示序列长度,d 表示表示的维数,k 表示卷积的核大小,r 表示受限自我关注中的邻域大小。表来自[3]。

  • 计算复杂度:每层计算复杂度的总量
  • 并行化:计算可以并行到什么程度
  • 学习长程相关性:处理网络中长程相关性的能力
  • 可解释性:检查学习到的分布的能力和关注输入的语义和句法特征的能力

表 1 说明了当序列长度 n 小于表示维度 d 时,自我注意在计算复杂度上优于递归层,这在 SOTA 翻译模型中是常见的情况,如单词片段[5]和字节对[6]表示。受限自关注是普通自关注的一个更复杂的版本,当涉及到非常长的输入序列中的计算复杂性时,它仅使用来自相应输出位置周围的输入序列的有限数量的大小为 r 的邻居。此外,当使用连续内核时,它可以击败需要几个 Conv 层的卷积层,其复杂性为𝑶 (n/k) ,对于扩张卷积,其复杂性为𝑶(log _ k(n))【7】。这种层的堆叠又延长了网络中任意两个位置之间的最长路径。卷积层的计算开销通常是递归层的 k 倍。值得一提的是,可分离卷积的复杂度要低得多,然而,在最好的情况下,它相当于自关注层和前馈层的组合。**

5.研究前沿

最近的变体试图通过进一步探索改进架构的不同路线来提高原始作品的性能,即:

  • 效率:自我注意力在处理长序列时会导致计算和记忆的复杂性,这促使研究人员通过引入轻量级注意力解决方案(例如稀疏注意力变体)和分治方法(例如递归和分层机制)来解决这个问题。
  • 一般化:由于对输入数据的结构偏差不敏感,转换器需要大量的数据进行训练,因此需要引入结构偏差或正则化、对大规模未标记数据进行预训练等工作。来克服这个障碍。
  • 适应:由于变形金刚有能力被各种领域采用,研究人员试图将它们与特定的下游任务整合在一起。

6.问题

在这一部分,我邀请你思考以下问题,并尝试在评论中给出答案。

  1. 如果你在《变形金刚》中用加法注意力取代比例点积注意力,会发生什么?
  2. 如果我们想使用转换器进行语言建模,我们应该使用编码器、解码器还是两者都用?
  3. 如果变压器的输入过长,会发生什么情况?我们该如何应对?
  4. 我们能做些什么来提高变压器的计算和存储效率?

说到这里,请留下评论,描述您认为哪些部分令人困惑或含糊不清。这篇文章对你有什么影响,你还想知道关于媒体写作的哪些话题。

我们可以在不和谐服务器上进一步讨论它们。

7.摘要

在本文中,您了解了 Transformer 架构及其实现,并看到了它在不同领域带来的重大突破,如机器翻译、计算机视觉以及其他一些学科,同时降低了它们的复杂性,并使它们更具可解释性。转换器的另一个基本组件是不同头的并行化能力,因为它完全使用多头自关注,而不是使用递归或卷积层。现在,您已经熟悉了变压器的主要组件。

我希望这篇文章对你有所帮助。如果你有,请分享到你最喜欢的社交媒体频道,这样其他人也可以找到它。

我写关于机器学习和其他技术主题的最新研究。如果你对其中任何一个感兴趣,请查看并跟随我。

8.TransformerX 库

transformer x是一个 python 库,为研究人员、学生和专业人士提供开发、培训和评估 transformer 所需的构建模块,并顺利集成到 Tensorflow 中(我们将很快添加对 Pytorch 和 JAX 的支持)。我们正在积极努力增加更多的功能。(我们最近发布了它的第一个版本,我非常感谢给我们一个🌟在 Github 上🌹)****

我想请你在 GitHub 上关注我,如果你想为一个尖端的深度学习库(TransformerX)做贡献,请随时联系我,我们期待着你的来信。我们将指导您完成第一笔捐款的每一步。

你也可以在 TransformerX Discord 服务器和 Twitter 上加入我们,我们会保持联系。

*****https://www.linkedin.com/in/soran-ghaderi/ *****

9.参考

[1] J. R. Anderson,2005,《认知心理学及其启示》,沃斯出版社,2005 年。
[2] D. Bahdanau,K. Cho,Y. Bengio,联合学习对齐和翻译的神经机器翻译,载:ICLR。
[3]阿什什·瓦斯瓦尼、诺姆·沙泽尔、尼基·帕马尔、雅各布·乌兹科雷特、Llion Jones、艾丹·戈麦斯、祖卡斯·凯泽和伊利亚·波洛舒欣。你需要的只是关注。NeurIPS,2017。
[4]乔纳斯·戈林、迈克尔·奥利、大卫·格兰吉尔、丹尼斯·亚拉茨和扬恩·多芬。卷积序列到序列学习。arXiv 预印本 arXiv:1705.03122v2,2017。
【5】吴永辉、迈克·舒斯特、、郭维乐、穆罕默德·诺鲁齐、沃尔夫冈·马切里、马克西姆·克里昆、、秦高、克劳斯·马切里等.谷歌的神经机器翻译系统:弥合人类与机器翻译之间的鸿沟。arXiv 预印本 arXiv:1609.08144,2016。
[6]丹尼·布里兹、安娜·戈尔迪、吴明堂和郭诉乐。神经机器翻译架构的大规模探索。更正,abs/1703.03906,2017。
[7]纳尔·卡尔希布伦纳、拉塞·埃斯佩霍尔特、卡伦·西蒙扬、亚伦·范登欧德、亚历克斯·格雷夫斯和科拉伊·卡武克库奥卢。线性时间内的神经机器翻译。arXiv 预印本 arXiv:1610.10099v2,2017。

变形金刚:越大越好?

原文:https://towardsdatascience.com/transformers-the-bigger-the-better-19f39f222ee3

谷歌再次给我们带来惊喜,它创造了变压器参数数量的新纪录

(图片由 作者 )

(西班牙语版)

使用变压器的大型语言模型目前构成了人工智能领域最活跃的领域之一,由于所需的计算资源,只有少数技术公司负担得起。几天前, Google 用一个语言模型中参数数量的新纪录让我们大吃一惊。

迄今为止最大的型号

自 2018 年 BERT 以来,其他几个大型语言模型(这些模型都是 Transformer 架构的变体)已经被开发出来,这继续推动了艺术的发展。这些模型的改进主要来自于根据参数数量来缩放模型的大小(图 1)。谷歌的这个最新模型名为 Pathways 语言模型(PaLM ),优于目前所有现有的模型。具体来说,这个模型包括 5400 亿个参数,比迄今为止最大的模型,即所谓的微软/英伟达威震天-图灵 NLG 多 100 亿个参数。两者的参数都是著名的 GPT-3 的三倍多,后者“只有”1750 亿个参数。

图 1:最先进的主要大型语言模型大小随时间变化的趋势(图片由作者提供)。

需要大量的计算资源

正如我们在之前的几篇文章中讨论的那样,人工智能是一个超级计算问题。因此,基于转换器的大型语言模型需要大量的计算资源来训练。谷歌报告称,这种模式需要 25.6 亿次失败。具体来说,他们已经使用了“6144 个 TPU v4 芯片 1200 小时,3072 个 TPU v4 芯片 336 小时”,也就是在一个巨型超级计算系统中已经需要超过两个月的执行时间。

在图 2 中,我们包括了我们在图 1 中展示的模型在模型训练所需的 FLOPs 方面的计算要求。在这个图中,我们可以很快观察到与模型规模成比例的计算需求的增长趋势。

图 2:主大型语言模型的 FLOPs(指数轴)训练计算(图片由作者提供)。

众所周知,能耗是最重要的运营变量之一,因为组成超级计算数据中心的不同元素需要大量能源。幸运的是,作者报告说,他们主要使用清洁能源来训练模型:“俄克拉荷马州的数据中心主要由风力和其他可再生能源提供动力,在 PaLM-540B 被训练的时间段内,以 89%的无碳能源运行”。好消息!

仍有改进的空间

根据文章,所提出的模型显示了迄今为止相对于现有模型的卓越改进:推理、自然语言理解/生成或代码生成。在谷歌人工智能博客的这篇文章中,你可以找到 PaLM 性能优于以前型号的令人信服的例子。

但值得注意的是,作者认为该模型仍然可以通过增加其规模来改善其结果,因为他们已经验证了该模型的性能作为规模的函数(在本文中,他们对表 1 中所示的同一模型的三个不同规模进行了实验)遵循与以前的模型类似的线性对数行为,这表明规模带来的性能增益尚未达到稳定状态。有尝试改进的空间。

表 1:为了更好地理解缩放行为,文章显示了三种不同参数缩放的结果(图片来源于文章)。

此外,考虑到 DeepMind 几天前刚刚发表的一篇文章表明这些模型的性能可以通过比参数更积极地扩展数据来大幅提高,如果对大量数据进行训练,该模型可能会更加强大。

结论

谷歌提出的这个模型是一系列趋势的最新一个,证明了通过使用超级计算机(由数千个加速器组成)扩展它们,它们的性能可以在各种自然语言处理、推理和代码任务中得到改善。此外,随着这一研究领域的快速交叉授粉,未来似乎很快就会到来。这种迅速引发了另一场急需解决的关键辩论,这场辩论与我们正在创造的伟大语言模型相关的道德和社会风险有关。但是我们把这个留给另一篇文章。

https://torres-ai.medium.com/how-to-take-advantage-of-the-new-disruptive-ai-technology-called-transformers-9e57a26506cb https://torres-ai.medium.com/transformers-the-new-gem-of-deep-learning-d0ae04bc4a75 https://torres-ai.medium.com/transfer-learning-the-democratization-of-transformers-1d2493b14883

转换概率分布和标准化流程

原文:https://towardsdatascience.com/transforming-probability-distributions-using-normalizing-flows-bcc5ed6ac2c9

使用张量流使用简单的双投影变换分布

使用双射变换的正态到双峰分布。(图片由作者提供)

你可以从这篇文章中学到/回顾到什么—

  1. 开始使用 TransformedDistribution 模块(在 TensorFlow Probability library 中)通过双射运算转换基本分布。
  2. 在 TensorFlow 中编写我们自己的自定义双射函数。
  3. 如何在自定义双射函数中定义正向和反向变换?
  4. 可视化原始和定制转换的发行版。

如果你想回顾双射和微分同胚的基础,作为规范化流程的开始,请查看以前的帖子;我们已经学习了双投影、雅可比和变换概率分布的基础知识,并推导了一些逆向和正向变换的数学公式,这些都将在本文中大量使用。所有的代码都可以在我的 GitHub 中找到,查看下面的参考资料。不要耽搁,让我们开始吧!

已转换的分发类:

在上一篇文章中,我们看到了如何定义一个双射体,并调用正向或反向方法来转换张量。在 TensorFlow 概率分布模块(tfp.distributions)中,TransformedDistributions 类帮助我们为分布做这件事,让我们看一个例子。让我们从正态分布开始,我们知道该分布的指数函数将遵循对数正态分布。我们将使用TransformedDostributions进行验证,我们从Normal分布开始,并使用指数双射器,如下所示:

这里我们从第 16 行(tfd.Normal)的正态分布开始,并在第 19 行(tfb.Exp())引入了指数双射体。为了从正态转换到对数正态,我们在第 22 行中称之为 TransformedDistribution。为了确认给定起始正态分布,我们实际得到了对数正态分布,通过调用tfd.Normal并绘制这些分布的样本来验证,如下所示:

图 1:转换分布:从正态分布开始,我们达到对数正态分布ϕ(z)并用对数正态分布对数正态(z)进行验证。[图片由作者提供]

定义自定义对象:

在前面的示例中,我们使用了在 bijector 模块中已经可用的指数 bijector,但是如果我们想要一个在 bijector 模块中不可用的自定义操作呢?我们可以定义一个自定义的 bijector,方法类似于通过 Keras 中的子类化来创建新层。让我们编写一个自定义 bijector 类,其中正向映射函数由 f → (ax)给出,其中 a,x ∈ R .为了编写自定义 bijector,我遵循了 GitHub 源代码中描述的[tfp.bijectors.power](https://www.tensorflow.org/probability/api_docs/python/tfp/bijectors/Power)类的结构。还提到不支持奇数整数作为幂:

像 1 这样的奇数的倒数的幂。/ 3 不被支持,因为数值精度问题使该属性难以测试。为了模拟这种行为,我们建议使用反向双向喷射器(即,而不是 tfb)。幂(幂=1。/3)使用 tfb。反转(tfb。幂(幂=3。))).

下面的示例只是为了理解定义自定义 bijector 类的步骤:

前向方法实现 f → (ax)的 bijector 子类化示例。

给定这个简单的正向运算:f → (ax ),我们可以很容易地计算出正向 log-det-jacobian,这是上面代码块第 33 行的注释。给定正向 log-det-jacobian 函数,自动实现反向 log-det-jacobian。如果你想再次温习这些定义,请查看上一篇文章。定义后,现在我们就可以使用该类了,我们可以用 python 绘制正向和反向转换的结果,如下所示:

图 2:给定一个函数 f → (ax ),我们画出正向和反向变换的结果。[图片由作者提供]

类似地,可以绘制正向和反向 log-det-Jacobian 的结果动作,如下所示:

图 3:给定一个函数 f → (ax ),我们画出雅可比行列式的正向和反向对数的结果。[图片由作者提供]

正态到双峰分布:

除了从正态分布到对数正态分布,另一个重要的基本转换是双峰正态分布。顾名思义,双峰指的是有两个峰值的分布。从正态分布开始,我们可以应用软符号双射运算来达到双峰分布,其中软符号函数定义如下:

def softsign(x): return x/(1+abs(x))

然而,这个双射在张量流概率中已经可用,所以我们可以像以前一样应用TransformedDistribution,让我们看看:

tf.random.set_seed(1234)normal_dist = tfd.Normal(loc=0., scale=1.0) 
# starting base distributionsample_s = 500normal_dist_sample = normal_dist.sample(sample_s, seed=10)## bijectorsoftsign = tfb.Softsign()# tf.random.set_seed(1234)bi_modal = tfd.TransformedDistribution(normal_dist, softsign)bi_modal_sample = bi_modal.sample(sample_s, seed=10)

我们可以根据转换后的分布绘制样本,如下所示:

图 4:从正态分布开始,我们应用软符号双射来达到双峰分布。[图片由作者提供]

我们还可以将转换后的分布和原始分布可视化为等值线图,如下图所示,左图显示正态分布,右图显示转换后的分布。

图 5:显示了基本分布(正态)和转换分布(双峰)的等值线图。[图片由作者提供]

除了 2D 等高线图,我们还可以绘制如下 3D 等高线图:

图 6:与图 5 相同,但是代替 2D 图,我们也可以将它们可视化为 3D 图。[图片由作者提供]

我们现在已经了解了规范化流程的所有基本要素;双射、转换概率分布、雅可比矩阵的正向和反向对数行列式的概念都在之前的文章中描述过。在这里,我们学习了如何使用 TensorFlow 概率库来转换分布,以及一些我们自己定义自定义 bijector 类的例子。我们现在已经为深入研究基于正常化流的模型做好了准备,如 RealNVP 、 Glow 、Masked Auto-Regressive Flow等。更多基于流量的模型即将推出!

保持坚强!干杯!!

参考资料:

[1] 标准化流程:当前方法的介绍和回顾。

[2]张量流变换分布。

[3]岗位使用的代码:笔记本。

除特别注明外,所有图片均为作者所有。

用 Python 翻译长 PDF 报告

原文:https://towardsdatascience.com/translate-long-pdf-reports-in-python-eab3be08ceb4

免费自动提取和翻译完整的德国央行报告

照片由米卡·鲍梅斯特在 Unsplash 上拍摄

为了工作,我最近不得不翻译许多经合组织国家央行的旧报告。幸运的是,乱码在线翻译已经成为过去,当处理许多长文档时,常见的手动解决方案通常不可行。有许多有用的 Python 包可以帮助完成这项任务,在各种优秀的现有文章中有介绍。然而,当面对这项任务时,我发现常用的例子往往太风格化,并且许多已建立的工具不再被维护以支持社区构建的后续项目。

这就是为什么在这篇文章中,我想 1) 提供一个 PDF 翻译的真实世界的例子,并 2) 提供一个最好的软件包的更新。

2 + 1 任务

因此,我们将一起翻译一份央行报告,就像代码一样,你可以在我的 Git 库上找到。首先,我们需要清楚地知道自己想做什么。在我们的例子中,我们需要以某种方式提取 pdf 的内容,翻译它,然后(潜在地)将它转换成人类易于阅读的格式:提取- >翻译- >编写。我们分别处理每项任务,最后把它们结合在一起。

提取

正如您可能已经体验过的,从 PDF 中检索文本可能相当棘手。原因是 pdf 只存储字符的位置,而不记录构成的单词或行。我们选择的库是新的pdf plumb项目,它建立在非常好的 pdfminer.six 库(它本身取代了 PDFMiner )之上,但是拥有更好的文档和令人兴奋的新特性。我们将在这里使用的一个特性是表格过滤。为了完整性,请注意流行的 PyPDF2 包更适合 PDF 合并,而不是文本提取。

import pdfplumberpdf = pdfplumber.open(“src/examples/1978-geschaeftsbericht-data.pdf”)

我们导入库并打开所需的文档。pdf plumb的中心对象是页面类,它允许我们单独访问每个页面及其内容。请注意,虽然我们可以简单地一次提取所有文本,但是将 pdf 缩减为一个大字符串会导致我们丢失许多有用的信息。

下面是如何通过应用 extract_text() 方法,使用索引来访问单个页面并轻松访问它们的文本。

page11 = pdf.pages[11]
page11.extract_text()
>>> 2  schließlich diese Wende in ihrer Politik durch die Heraufsetzung des Diskont- und Lom \nbardsatzes.

虽然这看起来已经很好了(作为比较,查看 PDF 的第 12 页),但我们看到句子被行尾符打断,我们可以预测这会给翻译带来问题。由于段落在句号之后自然会有换行符,我们将利用这一点只保留想要的换行符。

def extract(page):"""Extract PDF text and Delete in-paragraph line breaks.""" # Get text extracted = page.extract_text() # Delete in-paragraph line breaks extracted = extracted.replace(".\n", "**/m" # keep par breaks).replace(". \n", "**/m" # keep par breaks).replace("\n", "" # delete in-par breaks     ).replace("**/m", ".\n\n") # restore par breakreturn extractedprint(extract(page11)[:500])
>>> 2  schließlich diese Wende in ihrer Politik durch die Heraufsetzung des Diskont- und Lom bardsatzes.

好多了!但是看下一页,我们发现文档中的表格有问题。

page12 = pdf.pages[12]print(extract(page12)[:500])
>>> 1  3 Zur Entwicklung des Wirtschaftswachstums Jährliche Veränderung in o;o Zum Vergleich: I  Bruttoin-Brutto- I ...

过滤掉表格

pdf plumb包的一个亮点是滤镜方法。该库带有查找表格的内置功能,但是将它与过滤器结合起来需要一些独创性。本质上,pdf plumb将每个字符分配到所谓的“盒子”中,其坐标被过滤器作为输入。为了简洁起见,我将不解释 not_within_bboxes 函数,而是指向最初的 Git 问题。我们传递已识别的属于表格的字符,并用 not_within_bboxes 函数将它们组合起来。重要的是,由于 filter 方法只接受没有参数的函数,我们使用部分冻结了 box 参数。这是我们在上面创建的提取函数的前一步。

from functools import partialdef not_within_bboxes(obj, bboxes):
"""Check if the object is in any of the table's bbox."""def obj_in_bbox(_bbox):"""Find Bboxes of objexts."""v_mid = (obj["top"] + obj["bottom"]) / 2h_mid = (obj["x0"] + obj["x1"]) / 2x0, top, x1, bottom = _bbox return (h_mid >= x0) and (h_mid < x1) and (v_mid >= top) and (v_mid < bottom) return not any(obj_in_bbox(__bbox) for __bbox in bboxes)def extract(page):"""Extract PDF text, Filter tables and delete in-par breaks."""# Filter-out tablesif page.find_tables() != []: # Get the bounding boxes of the tables on the page.bboxes = [table.bbox for table in page.find_tables()]bbox_not_within_bboxes = partial(not_within_bboxes, bboxes=bboxes) # Filter-out tables from pagepage = page.filter(bbox_not_within_bboxes) # Extract Textextracted = page.extract_text() # Delete in-paragraph line breaksextracted = extracted.replace(".\n", "**/m" # keep par breaks).replace(". \n", "**/m" # keep par breaks).replace("\n", "" # delete in-par breaks).replace("**/m", ".\n\n") # restore par breakreturn extractedprint(extract(page12)[:500])
>>> 3 des Produktionspotentials anzusehen. Die statistischen Möglichkeiten lassen es nur an näherungsweise zu, ...

太棒了。表格被成功地过滤掉了,我们现在可以看到页面以一个被分页符分成两半的句子开始。我们把它留给了提取,但是我鼓励你尝试更多的特性,比如提取页码,改进段落分隔和修复经常出现的错误,比如识别“%”的“0/o”。

翻译

AWS 和 DeepL 为高质量的文本翻译提供了两个突出的 API,但是如果我们想要翻译几个长报告,基于字符的定价方案会变得非常昂贵。为了免费翻译,我们使用 Google Api 和一个关键的变通方法,实现了长文本的翻译。

from deep_translator import GoogleTranslator

由于 GoogleTranslate API 不是由 Google 维护的,所以社区在翻译方面已经多次遇到问题。这就是为什么我们在这里使用 deep_translator 包,它充当 API 的一个有用的包装器,使我们能够在翻译引擎之间无缝切换,如果我们希望的话。重要的是,GoogleTranslator 可以自动识别源语言(在我们的例子中是德语),所以我们只需要指定我们的目标语言:英语。

translate = GoogleTranslator(source=’auto’, target=’en’).translate

有了这个包装器,翻译变得非常简单,如下例所示。

translate("Ich liebe Python programmieren.")
>>> 'I love Python programming.'

然而,关键问题是大多数翻译引擎有 5000 字节的上传限制。如果一个作业超过了这个时间,连接就会被终止——例如,这会阻止第 11 页的翻译。当然,我们可以单独翻译每个单词/句子,然而,这降低了翻译质量。这就是为什么我们收集低于上传限制的句子并一起翻译。

原来,我在这里找到了这个变通办法。它使用流行的自然语言处理工具 nltk 来识别句子。这个包的文档非常棒,我推荐任何感兴趣的人尝试一下。这里,我们将注意力限制在包的标记器上。重要的是,tt 一再强调,只有高质量的输入才能带来高质量的翻译输出,因此在这些准备步骤中付出更多努力将会很容易获得回报!

因为这对于第一次使用的用户来说是令人畏惧的,所以我在这里展示了安装相关的 nltk 功能的 shell 脚本(在 Windows OS 上)。“popular”子集包括现在将使用的 nltk.tokenize 包。

# Shell scriptpip install nltk
python -m nltk.downloader popular

如下所示, sent_tokenize 函数创建了一个句子列表。语言参数默认为英语,这对于大多数欧洲语言来说很好。请查看 nltk 文档,看看你需要的语言是否被支持。

from nltk.tokenize import sent_tokenizetext = "I love Python. " * 2
sent_tokenize(text, language = "english")
>>> ['I love Python.', 'I love Python.']

现在,我们需要的第二个要素是收集低于上传限制的句子块的算法。一旦我们发现添加另一个句子将超过 5k 字节,我们翻译集合,并从当前句子开始一个新的块。重要的是,如果一个句子本身应该超过 5k 字节(记住,这大约相当于一页),我们只需丢弃它,并提供一个文本注释。结合 I)翻译客户端的设置,ii)句子标记化,以及 iii)组块式翻译,我们最终得到以下翻译函数。

def translate_extracted(Extracted):"""Wrapper for Google Translate with upload workaround."""# Set-up and wrap translation clienttranslate = GoogleTranslator(source='auto', target='en').translate # Split input text into a list of sentencessentences = sent_tokenize(Extracted) # Initialize containerstranslated_text = ''source_text_chunk = '' # collect chuncks of sentences, translate individuallyfor sentence in sentences:# if chunck + current sentence < limit, add the sentenceif ((len(sentence.encode('utf-8')) +  len(source_text_chunk.encode('utf-8')) < 5000)):source_text_chunk += ' ' + sentence # else translate chunck and start new one with current sentenceelse:translated_text += ' ' + translate(source_text_chunk) # if current sentence smaller than 5000 chars, start new chunckif (len(sentence.encode('utf-8')) < 5000):source_text_chunk = sentence # else, replace sentence with notification messageelse:message = "<<Omitted Word longer than 5000bytes>>"translated_text += ' ' + translate(message) # Re-set text container to emptysource_text_chunk = '' # Translate the final chunk of input text, if there is any valid   text left to translateif translate(source_text_chunk) != None:translated_text += ' ' + translate(source_text_chunk) return translated_text

为了看看它是否有效,我们将我们的翻译功能应用于我们之前已经处理过的页面。对于不讲德语的人来说,显然每小时生产率在 1978 年提高了大约 4%。

extracted = extract(pdf.pages[12])
translated = translate_extracted(extracted)[:500]print(translated)
>>>3 of the production potential. The statistical possibilities allow only an approximation of the closures that still occur physically due to long-term shrinkage ...

写作

我们几乎有我们需要的一切。像我一样,你可能需要将提取的文本转换成人类容易阅读的格式。虽然很容易将字符串保存到。在 Python 中,缺少换行符使得它们不适合长报告。相反,我们将在这里使用 fpdf2 库将它们写回 PDF,该库显然继承了不再维护的 pyfpdf 包。

from fpdf import FPDF

在初始化一个 FPDF 对象后,我们可以为我们翻译的每一页添加一个页面对象,并将它们写在那里。这将帮助我们保持原始文档的结构。需要注意两点:首先,在 multi_cell 中,我们将宽度设置为零以获得全宽,并选择高度为 5 以获得细线间距。其次,由于预装字体与 Unicode 不兼容,我们将编码改为“ latin-1 ”。有关下载和使用 Unicode 兼容字体的说明,请参见FPD F2网站上的说明。

fpdf = FPDF()
fpdf.set_font("Helvetica", size = 7)fpdf.add_page()
fpdf.multi_cell(w=0,h=5,txt= translated.encode("latin-1",errors = "replace").decode("latin-1"))
fpdf.output("output/page12.pdf")

现在,就像在提取中一样,显然你可以用 fpdf2 做更多的事情,比如添加页码、标题布局等等。然而,对于本文的目的来说,这个最小的设置就足够了。

把所有东西绑在一起

现在,我们将把所有内容汇集到一个管道中。请记住,为了避免丢失太多信息,我们对每个页面进行单独操作。重要的是,我们对翻译做了两处修改:由于一些页面是空的,但是空字符串对于 GoogleTranslator 来说不是有效的输入,我们在翻译之前放置了一个 if 条件。其次,因为 nltk 将我们的分段符(即“ \n\n ”)分配到句子后面的的开头, GoogleTranslate 忽略这些。这就是为什么我们用列表理解法单独翻译每一段。耐心点,翻译 150 页可能需要 7 分钟!

# Open PDF
with pdfplumber.open(“src/examples/1978-geschaeftsbericht-data.pdf”) as pdf:# Initialize FPDF file to write onfpdf = FPDF()fpdf.set_font(“Helvetica”, size = 7) # Treat each page individuallyfor page in pdf.pages:# Extract Pageextracted = extract(page) # Translate Pageif extracted != “”:# Translate paragraphs individually to keep breaksparagraphs = extracted.split(“\n\n”)translated = “\n\n”.join([translate_extracted(paragraph) for paragraph in paragraphs])else:translated = extracted # Write Pagefpdf.add_page()fpdf.multi_cell(w=0, h=5,txt= translated.encode(“latin-1”,errors = “replace”).decode(“latin-1”)) # Save all FPDF pagesfpdf.output(“output/trans_1978-geschaeftsbericht-data.pdf.pdf”)

结论

谢谢你坚持到最后。我希望这篇文章能给你一个关于如何翻译 pdf 和什么是最先进的软件包的实例。在整篇文章中,我指出了这个基本示例的各种潜在扩展(例如,添加页码、布局等。),所以请分享你的方法——我很想听听。当然,我也总是渴望听到关于如何改进代码的建议。

注意安全,保持联系!

用 GPT-3 翻译

原文:https://towardsdatascience.com/translate-with-gpt-3-9903c4a6f385

机器翻译,但没有机器翻译系统

图片来自 Pixabay

在翻译方面,OpenAI GPT-3 已经被证明可以与最先进的机器翻译系统相媲美(布朗等人,(2020) )。

GPT-3 只需要几个翻译例子就能学会如何合理地翻译。对于没有太多训练数据可用的语言和领域的翻译,它是标准机器翻译系统的合适替代。

自从 GPT-3 主要在英语文本上训练以来,表现令人印象深刻。

在这篇文章中,我将描述如何通过不同的提示策略来充分利用 GPT-3 进行翻译。我将关注那些我们只有几个或零个翻译例子的用例。

使用 GPT-3 不是免费的,翻译的成本估算也不是直截了当的,所以我也将为您提供一种自己估算翻译成本的方法。

本文不需要任何机器学习的先验知识。

提示 GPT-3

GPT-3 没有被训练用于任何特定的自然语言处理任务。

然而,如果你用自然语言向 GPT 3 号提交一份任务描述,GPT 3 号会尝试去做,成功与否取决于任务的复杂程度。

例如,如果你想让 GPT-3 把一些英语文本翻译成法语,你必须用纯文本描述这个翻译任务,并把这个描述和要翻译的文本一起提交给 GPT-3。

集合{任务描述,文本}被称为提示。对于翻译任务,提交给 GPT-3 的提示可以是:

**Prompt:**Translate from English to French:
The cat is in the kitchen. =>

然后,理想情况下,GPT 3 号会回应:

**Response:**Le chat est dans la cuisine.

在这种情况下,翻译对 GPT 三号来说是一个零射击任务。我们要求 GPT 3 号执行一项未经训练的任务。

零触发配置特别适用于我们没有任何可用训练数据的用例。你可以把它看成一个无人监督的机器翻译任务。

为了改善结果,我们可以修改提示以包含任务的示例,例如:

**Prompt:** 
Translate from English to French:
I am in the kitchen. => Je suis dans la cuisine.
The cat is in the kitchen. =>

在这种情况下,GPT-3 已经看到了一个翻译示例。对 GPT 3 号来说,这变成了一个一次性的任务。如果你提供了一个以上的例子,我们通常会谈到一个少镜头任务。

单镜头和少镜头配置对于机器翻译任务很有用,我们只有几个翻译的例子。对于某些语言来说,使用 GPT-3 的少量翻译可以与最先进的机器翻译系统表现一样好,甚至更好。

选择模型和估算翻译成本

图片来自 Pixabay

OpenAI 已经建立了一个网页,上面详细介绍了型号和价格。

给出了1000 个代币和每个型号的成本:

  • 语言
  • 巴贝奇
  • 居里
  • 达芬奇

注:如果你很好奇这些模型在自然语言处理中的表现如何, EleutherAI 在各种任务 (但不是翻译)中对它们进行了评测。

所有这些模型都可以进行翻译,但便宜的模型不会给你带来好的结果林等人(2022) 。

居里达芬奇很可能给你最好的结果。

请注意,成本是给定的令牌,即,不是单词。OpenAI 估计 1 个 token = 0.75 个单词。因此,如果你有一篇 1000 字的文本要翻译,GPT-3 将首先将其标记为大约1250 个标记

如果你使用 Curie,仅仅是提交1000 字,你就要支付大约 0.0025 美元(1.25*0.002)。这是为英语课文准备的。预计其他语言的单词/单词比率会更高,因为 GPT-3 可能会将原始训练数据中较少出现的语言的单词分成更小的片段。

这是你提交给 GPT-3 的费用。不幸的是,这并不是翻译本身的成本。

记住,我们必须向 GPT 3 号描述我们想做什么。除了要翻译的文本,我们还必须描述任务。OpenAI 对整个提示收费。

例如,如果你做零镜头翻译,你将支付任务描述“从英语翻译成法语:”为你提交给 GPT 三号的每个请求。如果你做几个镜头的翻译,你要加上提交你提示中给出的所有翻译实例的费用。正如我们将在本文的以下部分看到的,如果你不仔细设计你的提示,获得最好的 GPT-3 的成本会迅速上升。

然后,OpenAI 还对生成的令牌进行收费。生成的令牌数量无法预测,但有一个选项可以限制它。

概括地说,发送到 GPT-3 的查询的成本将根据以下令牌数来确定:

  • 任务的描述
  • 提供的翻译示例(如果有)
  • 要翻译的文本
  • 生成的翻译

请注意,如果您在提示中提供了几个翻译示例,可以跳过“任务描述”。

使用最好的模型 Davinci,GPT-3 可以处理多达 4000 个令牌(或使用 Curie 处理 2048 个令牌)。如果提示符的长度加上 GPT-3 的最大响应长度,就令牌而言,大于模型可以处理的长度,GPT-3 将返回一个错误,要求减小提示符的大小(或更改一些参数)。这个限制也意味着你不能一次提交大量的文本。

设置 API

注意:如果你不习惯用 Python 编码,你可以跳过这一部分,使用 OpenAI 提供的“操场”网络界面,一旦你在他们的网站上注册,就可以使用这个界面。

我将使用 API 做一些 GPT-3 翻译的演示。如果你也想在你这边练习,你将需要创建一个 OpenAI 帐户。你将获得免费学分(在我写这篇文章的时候是 18 学分)来和 GPT-3 一起练习。

您还需要安装 openai 包:

pip install openai

要使用 OpenAI 的 API,你需要一个 API 密匙。您可以在您的 OpenAI 帐户中生成一个。保持这个密钥私有

然后,我将我的请求发送给 API,如下面的 Python 脚本所示:

import os
import openai#Your API key is loaded here
#It should be exported as an environment variable before running this script: export OPENAI_API_KEY=your_key
openai.api_key = os.getenv("OPENAI_API_KEY")#Description of the task in natural language
task_description = "Translate English to French:"
#The text you want to translate
to_translate = "The cat is in the kitchen =>"#The prompt is created by concatenating the task description and the text to translate
prompt = task_description+"\n"+to_translate#The API call
#respone will contain the generated translation
response = openai.Completion.create(model="text-curie-001",prompt=prompt,temperature=0.3,max_tokens=2048,top_p=1.0,frequency_penalty=0.0,presence_penalty=0.0
)#Print the JSON response from GPT-3
print(response)

更准确的说,对 API 的调用是用 openai.Completion.create 完成的,你可以在 openai 的网站上找到 API 文档来更好的理解参数。在本文中,除了“提示”之外,我不会修改它们。

GPT-3 给出的响应采用 JSON 格式,如下所示:

{"choices": [{"finish_reason": "stop","index": 0,"logprobs": null,"text": " Le chat est dans la cuisine"}],"created": 1668449187,"id": "xxxx","model": "text-curie-001","object": "text_completion","usage": {"completion_tokens": 7,"prompt_tokens": 14,"total_tokens": 21}
}

这里最重要的关键字是“文本”,其中包含由 GPT-3 生成的翻译,以及“total_tokens”,这是您将被计费的令牌数。

这个 API 调用花费$0.000042 (0.002*21/1000)。

翻译设置

图片来自 Pixabay

对于这个演示,我将使用由 TILDE ( CC-BY )编译的 ECB 数据集。

我将把以下 6 句话从英语翻译成法语(我的母语,所以我可以手动评估译文):

Today also marks the opening of the online "Euro Run" competition for children aged 9 to 12 living in the euro area.
Unlike Denmark and the United Kingdom, Croatia does not have the right to opt out of adopting the single currency.
When the number of NCB Governors exceeds 15 they will exercise a voting right on the basis of a rotation system.
Write-downs amounted to €64 million in 2015 (2014: €8 million).
€500 banknote remains legal tender and will always retain its value
While there are fewer than 22 euro area Member States the rotation will take place in accordance with the following rules:

请注意,ECB 中的句子不是连续的,也就是说,它们应该独立考虑。

由人工翻译产生的法语参考译文如下:

C'est aujourd'hui également qu'est lancé le concours en ligne Euro Run, destiné aux enfants âgés de 9 à 12 ans habitant dans la zone euro.
Contrairement au Danemark et au Royaume-Uni, la Croatie ne bénéficie pas d'une clause d'exemption concernant l'adoption de la monnaie unique.
Lorsque le nombre des gouverneurs des BCN sera supérieur à quinze, ces derniers exerceront leur droit de vote selon un système de rotation.
Les moins-values latentes se sont établies à 64 millions d'euros en 2015 (contre 8 millions d'euros en 2014).
Le billet de 500 euros continue d'avoir cours légal et gardera toujours sa valeur
Tant que la zone euro comptera moins de vingt-deux États membres, la rotation fonctionnera selon les règles suivantes:

请注意,GPT-3 是根据 2021 年之前在网上公布的数据进行训练的。欧洲央行数据集已于 2017 年编制完成,因此 GPT-3 很可能在培训期间看到了要翻译的英文文本,或许还有其法文翻译。这将人为地使任务变得更容易,并防止与其他模型进行任何性能比较(因此我在本文中不做)。

为了比较零镜头、单镜头和少镜头的性能,我将使用度量标准COMET(Apache License 2.0)对翻译进行手动和自动评估。

零镜头翻译

图片来自 Pixabay

对于零镜头翻译,我们只需要用自然语言描述任务。为了展示提示的影响,我尝试了 3 种不同的描述。

确切地说,每次我更改提示时,我只更改上面 Python 脚本中“task_description”的内容。

我将每个提示命名为“prompt_x ”,以便在本文后面引用它。

提示 _1:

Translate English to French:
[source] =>

在 prompt_1 和所有后续提示中,“[source]”是要翻译的英文文本。这个提示符是 OpenAI 在介绍 GPT-3 的论文中使用的。

注意空格。GPT-3 不喜欢以空格结尾的提示。

当然,你可以把“英语”和“法语”替换成你想要的语言。

我还尝试了下面的变体,省略了要翻译的语言的名称。

提示 _2

Translate to French:
[source] =>

第三个提示是极简的,没有提到任务。

提示 _3

English
[source]
French

【来源】可以多种方式格式化。我尝试了许多可能性,但本文只保留了以下内容:

  • 单元:每个 API 调用只发送一个句子进行翻译。
  • 批量:一次性发送所有句子。这种方式是减少你的账单的最佳方式,因为你不必为每个要翻译的句子重复任务描述。如果句子应该是连贯的,这也是理想的。然而,在我为本文选择的 ECB 语料库中,句子是无序的,因此 GPT-3 在翻译句子时可能会被误导,因为它会查看从不同上下文中选取的先前翻译的句子。

结果

提示 _1单元 :

注意:在下面的例子中,“— — —”表示后面的内容是由另一个 API 调用生成的。我还手动添加了“响应:”来指示 GPT-3 的响应开始的位置。

Translate English to French:
Today also marks the opening of the online "Euro Run" competition for children aged 9 to 12 living in the euro area. =>Response:
Aujourd'hui, c'est aussi le début du concours "Euro Run" destiné aux enfants de 9 à 12 ans vivant dans l'euro zone.
----------
Translate English to French:
Unlike Denmark and the United Kingdom, Croatia does not have the right to opt out of adopting the single currency. =>Response:
Contrairement à la Danemark et à la Grande-Bretagne, la Croatie ne dispose pas de droit de déclencher une optique pour la monnaie unique.
----------
Translate English to French:
When the number of NCB Governors exceeds 15 they will exercise a voting right on the basis of a rotation system. =>Response:
Lorsque le nombre de gouverneurs de NCB dépasse 15, ils exerceront un droit de vote en fonction d'un système de rotation.
----------
Translate English to French:
Write-downs amounted to €64 million in 2015 (2014: €8 million). =>Response:
Les écritures ont atteint 64 millions d'euros en 2015 (2014: 8 millions).
----------
Translate English to French:
€500 banknote remains legal tender and will always retain its value =>Response:
Le 500 € billets de banque restent toujours valables et garderont toujours leur valeur.
----------
Translate English to French:
While there are fewer than 22 euro area Member States the rotation will take place in accordance with the following rules: =>Response:
Les États membres de l'euro zone qui ne seront pas présents à la réunion du Conseil européen du 22 juin 2012 auront droit à une rotation annuelle.
  • 完成令牌总数:48+42+47+24+30+50 = 241
  • 提示令牌总数:35+30+31+25+21+30 = 172
  • 总令牌数:83+72+78+49+51+80 = 413
  • 总成本:0.002 * 413/1000 = 0.000826 美元
  • 彗星评分:20.74

提示 _1批次 :

Translate English to French:
Today also marks the opening of the online “Euro Run” competition for children aged 9 to 12 living in the euro area.
Unlike Denmark and the United Kingdom, Croatia does not have the right to opt out of adopting the single currency.
When the number of NCB Governors exceeds 15 they will exercise a voting right on the basis of a rotation system.
Write-downs amounted to €64 million in 2015 (2014: €8 million).
€500 banknote remains legal tender and will always retain its value
While there are fewer than 22 euro area Member States the rotation will take place in accordance with the following rules:Response:
Le concours européen de course en ligne "Euro Run" pour les enfants de 9 à 12 ans vivant dans l'espace européen ouvre ses portes aujourd'hui.
Contrairement à la Norvège et au Royaume-Uni, la Croatie n'a pas le droit de choisir d'opter pour la monnaie unique.
Lorsque le nombre de gouverneurs de la Banque centrale européenne dépasse 15, ils exerceront un droit de vote en fonction d'un système de rotation.
Le montant des dépréciations atteint €64 millions en 2015 (2014: €8 millions).
Le 500 euros de la note banque reste toujours une monnaie légale et conserve toujours son prix.
Même si il y a moins de 22 États membres de l'espace européen, le rythme de rotation se fera selon les règles suivantes :
  • 完成令牌总数:256
  • 提示令牌总数:136
  • 令牌总数:392
  • 总成本:0.002 * 392/1000 = 0.000784 美元
  • 彗星评分:37.45

对于 prompt_2 和 prompt_3,我观察到以下情况。

注意:为了缩短本文,我不在这里写这两个提示的提示和输出。如果你想看,我会把它们贴在下面的评论里。

提示 _2单元 :

  • 完成令牌总数:60+43+46+24+24+52 = 249
  • 提示令牌总数:34+29+30+24+20+29 = 166
  • 总令牌数:94+72+76+48+44+81 = 415
  • 总成本:0.002 * 415/1000 = 0.000830 美元
  • 彗星评分:5.41

提示 _2批次 :

  • 完成令牌总数:220
  • 提示令牌总数:136
  • 令牌总数:356
  • 总成本:0.002 * 356/1000 = 0.000712 美元
  • 彗星评分:43.82

提示 _3单元 :

  • 完成令牌总数:50+43+46+28+20+67 = 254
  • 提示令牌总数:33+28+29+23+19+28 = 160
  • 总令牌数:83+71+75+51+39 +95 = 414
  • 总成本:0.002 * 414/1000 = 0.000828 美元
  • 彗星评分:-24.69

提示 _3批次 :

  • 完成令牌总数:227
  • 提示令牌总数:135
  • 令牌总数:362
  • 总成本:0.002 * 362/1000 = 0.000724 美元
  • 彗星评分:35.09

如果我们看一下 COMET 的分数,根据 COMET 的说法,显然“批量”配置比“单元”配置产生更好的翻译。我是通过人工评测确认的。

我的假设是,“块”配置在生成期间为 GPT-3 提供了更多的上下文。换句话说,当法语上下文在生成期间增长时,GPT-3 变得更有信心应该生成法语文本。

至于提示,prompt_3 产生的结果要低得多,而且对于某些句子来说似乎不能完成任务。这很直观,因为我们从提示中去掉了“翻译”。根据我的人工评估,使用 prompt_1 和 prompt_2 获得的翻译之间的差异并不显著。

彗星在这里没有告诉你的是这些翻译都很烂。有些翻译部分正确,有些则通顺但完全不准确。

Zero-shot GPT-3 生成的法语翻译质量远远低于标准机器翻译系统。注: 如果你使用达芬奇模型,在零镜头翻译时,你可能会得到更好的结果,对于翻译成英语。

我展示它是为了告诉你,即使 GPT 3 号没有经过翻译训练,如果我们给它提供一个简单的任务描述,它实际上是能够翻译的。

如果我们比较所有配置的成本(以美元计),我们看不出有多大差别。

一次性翻译

图片来自 Pixabay

在一次性配置中,我们添加了一个由人工翻译产生的英语到法语翻译的例子。

这里,我只给出了 prompt_1 的结果,因为 prompt_2 的结果非常相似。

提示 _1单元 :

Translate English to French:
The competition gives us a unique opportunity to have a direct dialogue with thousands of students across the euro area. => Le jeu-concours nous offre une possibilité unique de dialoguer directement avec des milliers d'élèves à travers la zone euro.
Today also marks the opening of the online "Euro Run" competition for children aged 9 to 12 living in the euro area. =>Reponse:Aujourd'hui, c'est aussi le début du concours « Euro Run » destiné aux enfants de 9 à 12 ans vivant dans la zone euro.
----------
Translate English to French:
The competition gives us a unique opportunity to have a direct dialogue with thousands of students across the euro area. => Le jeu-concours nous offre une possibilité unique de dialoguer directement avec des milliers d'élèves à travers la zone euro.
Unlike Denmark and the United Kingdom, Croatia does not have the right to opt out of adopting the single currency. =>Reponse:
Contrairement à la Danemark et à la Grande-Bretagne, la Croatie ne dispose pas de droit de décider de ne pas adopter le système monétaire unique.
----------
Translate English to French:
The competition gives us a unique opportunity to have a direct dialogue with thousands of students across the euro area. => Le jeu-concours nous offre une possibilité unique de dialoguer directement avec des milliers d'élèves à travers la zone euro.
When the number of NCB Governors exceeds 15 they will exercise a voting right on the basis of a rotation system. =>Reponse:Lorsque le nombre de gouverneurs NCB dépasse 15, ils exerceront un droit de vote en fonction d'un système de rotation.
----------
Translate English to French:
The competition gives us a unique opportunity to have a direct dialogue with thousands of students across the euro area. => Le jeu-concours nous offre une possibilité unique de dialoguer directement avec des milliers d'élèves à travers la zone euro.
Write-downs amounted to €64 million in 2015 (2014: €8 million). =>Reponse:Les écritures à terme atteignaient 64 millions d'euros en 2015 (2014: 8 millions).
----------
Translate English to French:
The competition gives us a unique opportunity to have a direct dialogue with thousands of students across the euro area. => Le jeu-concours nous offre une possibilité unique de dialoguer directement avec des milliers d'élèves à travers la zone euro.
€500 banknote remains legal tender and will always retain its value =>Reponse:Le billet de 500 euros reste toujours légalement valable et gardera toujours son prix
----------
Translate English to French:
The competition gives us a unique opportunity to have a direct dialogue with thousands of students across the euro area. => Le jeu-concours nous offre une possibilité unique de dialoguer directement avec des milliers d'élèves à travers la zone euro.
While there are fewer than 22 euro area Member States the rotation will take place in accordance with the following rules: =>Reponse:Bien que les 22 États membres de la zone euro soient les seuls participants, le tour de rotation se déroulera selon les règles suivantes :
  • 完成令牌总数:47+46+45+27+26+42 = 233
  • 提示令牌总数:98+93+94+88+84+93 = 550
  • 总令牌数:145+139+139+115+110+135 = 783
  • 总成本:0.002 * 783/1000 = 0.001566 美元
  • 彗星评分:49.01

提示 _1批次 :

Translate English to French:
The competition gives us a unique opportunity to have a direct dialogue with thousands of students across the euro area. => Le jeu-concours nous offre une possibilité unique de dialoguer directement avec des milliers d’élèves à travers la zone euro.
Today also marks the opening of the online “Euro Run” competition for children aged 9 to 12 living in the euro area.
Unlike Denmark and the United Kingdom, Croatia does not have the right to opt out of adopting the single currency.
When the number of NCB Governors exceeds 15 they will exercise a voting right on the basis of a rotation system.
Write-downs amounted to €64 million in 2015 (2014: €8 million).
€500 banknote remains legal tender and will always retain its value
While there are fewer than 22 euro area Member States the rotation will take place in accordance with the following rules: =>Reponse:Aujourd’hui aussi est marqué le lancement du concours « Euro Run » destiné aux enfants de 9 à 12 ans vivant dans la zone euro.
Comme la Norvège et le Royaume-Uni, la Croatie n'a pas le droit de choisir de ne pas adopter le système monétaire unique.
Lorsque le nombre de gouverneurs de la Banque centrale européenne dépasse 15, ils exerceront un droit de vote en fonction d'un système de rotation.
Les write-downs atteignirent €64 millions en 2015 (2014: €8 millions).
Le 500 euros de banknote reste toujours valable monnaie et gardera toujours sa valeur.
Environ 22 États membres de la zone euro ont adhéré à la monnaie unique, tandis que la rotation aura lieu selon les règles suivantes :
  • 完成令牌总数:240
  • 提示令牌总数:200
  • 代币总数:440
  • 总成本:0.002 * 440/1000 = 0.000880 美元
  • 彗星评分:44.88

对于这两种配置,正如所料,一次转换比零次转换好。由于翻译示例,GPT 3 号更好地“理解”了任务。

还要注意的是,对于单次平移,单元配置比批次执行得更好。

与零镜头配置相比,这些翻译改进了很多,但其中一些奇怪地让我想起了 10 年前由基于短语的统计机器翻译系统生成的翻译。

少镜头翻译

图片来自 Pixabay

对于少量翻译,我在提示中放了 10 个翻译示例(选自 ECB 语料库)。这是与一次性提示的唯一区别。

我发现批处理配置在这个配置中返回奇怪的结果,最明显的是错过了翻译一些句子。这可能是由于提示符变得很长。似乎 GPT-3 没有正确地找到要翻译的文本从哪里开始。

我不会显示来自 GPT-3 的提示和响应,因为用于少量学习的例子在本文中占了很大的篇幅。

提示 _1单元 :

  • 完成令牌总数:48+44+45+23+29+41 = 230
  • 提示令牌总数:641+636+637+631+627+636 = 3,808
  • 令牌总数:689+680+682+654+656+677 =4,038
  • 总成本:0.002 * 4038/1000 = 0.008076 美元
  • 彗星评分:59.39

不出所料,我们又一次提高了翻译质量。但我发现它仍然不如标准的机器翻译系统。

注意,我在这里只使用了几个例子。要获得 GPT-3 作者提到的翻译质量,你需要使用至少 30 个翻译范例。而且,你可以通过切换到达芬奇来进一步改善结果。

但是,请注意,与零投配置相比,成本已经增加了很多。翻译 6 个句子的成本为 0.008076 美元,换句话说,是零镜头配置的 11 倍。这大约是 1000 句话 1.3 美元。如果换成达芬奇,就得把成本乘以 10,也就是 1000 句话 13 美元。

这是因为我们为每个要翻译的句子提供了翻译示例。这并不理想,但这是在不微调 GPT-3 的情况下获得相当好的结果的唯一方法。

微调呢?

OpenAI 提议通过 API 对 GPT-3 进行微调。

在翻译上对 GPT-3 进行微调肯定会产生更好的翻译。

然而,我不推荐它,除非你有使用 GPT-3 的特殊需要。

微调 GPT-3,然后使用微调模型更昂贵,你可能要尝试几种配置才能获得比少数镜头翻译更好的结果。

结论

总之,从 GPT-3 中获得好的翻译既不容易也不便宜,但绝对是可能的。

如果你有足够大的预算,我会推荐使用 GPT-3 来翻译其他标准机器翻译系统不能很好覆盖的特定领域和语言。对于这样的用例,你可能会得到比其他机器翻译系统更好的翻译。

请注意,我不会将 GPT-3 视为机器翻译系统,除非你将其微调为机器翻译系统。

GPT 3 号对提示的解释是自由的。这种自由会带来一些后果。GPT-5 月 3 日:

  • 输出错误的语言
  • 跳过要翻译的句子
  • 根本不执行翻译

为了避免这些陷阱,选择正确的提示并提供有用的翻译示例至关重要。

如果你喜欢这篇文章,支持我工作的最好方式就是使用我的链接成为一名媒体会员:

https://medium.com/@bnjmn_marie/membership

如果你已经是会员,想要支持这项工作, 就在 Medium 上关注我吧。

将 SQL 翻译成 Python-第 1 部分

原文:https://towardsdatascience.com/translating-sql-into-python-part-1-92bf385d08f1

有点像谷歌翻译,但对于̶l̶o̶v̶e̶语言的数据

作者图片

如果您对 Python 中 SQL 的相应语法感到厌倦,那么您就找对地方了。用两种语言写作的能力不仅能让你对客户或雇主更有吸引力,还能让你写出更高效、更精简的数据流程,从而节省你的大量时间。

在 python 中,您可以创建一个直接查询 SQL 数据库的脚本(使用您的 SQL 语言),然后在同一个脚本中编写后续的数据流程(使用您的 python 语言)。我现在就不说舌头了,继续举几个例子。使用下面的内容跳到感兴趣的部分。

如果这里没有涉及到您想要的内容,那么可能会在下面的第 2 部分中讨论,包括 groupbys、datetimes、cast whens 和其他杂项功能。

目录:

  1. <#80ca>
  2. <#ca3b>
  3. <#05ca>的‘更新’
  4. <#bc42>
  5. <#f9a7>

首先,确保您已经安装了 pandas 库(这是 Anaconda 包的默认设置),并使用以下命令将其导入到您的脚本中:

****import pandas as pd****

1。中的“选择”

a.全选

******-- SQL**
select * from table
**# python**
table****

b.选定列

******-- SQL**
select col1 from table
**# python**
table.col1****

c.选择所有唯一记录

******-- SQL**
select distinct * from table
**# python**
table1.drop_duplicates()****

d.从特定列中选择所有唯一记录

******-- SQL**
select distinct col1, col2, col3 from table1 
**# python**
table1.drop_duplicates(subset = [col1, col2, col3])****

2。在哪里

a.其中列等于 x

******-- SQL**
select * from table1 where col1 = 5
**# python**
table.loc[table['column_name'] == 5]****

b.其中列不等于 x

******-- SQL**
select * from table1 where col1 <> 5
**# python**
table.loc[table['column_name'] != 5]****

c.其中列大于 x

******-- SQL**
select * from table1 where col1 > 5
**# python**
table1[col1 > 5]****

d.其中列包含“字符串”

******-- SQL**
select * from table1 where col1 like '%string%'
**# python**
table.loc[(table['col1'].str.contains('string'))]****

e.其中列以“字符串”开头

******-- SQL**
select * from table1 where col1 like 'string%'
**# python**
table.loc[(table['col1'].str.startswith('string'))]****

f.其中列以“字符串”结尾

******-- SQL**
select * from table1 where col1 like '%string'
**# python**
table.loc[(table['col1'].str.endsswith('string'))]****

g.其中列不为空

******-- SQL**
select * from table1 where col1 is not null
**# python**
table[‘col1’].notna()****

h.其中列为空

******-- SQL**
select * from table1 where col1 is not null
**# python**
table[‘col1’].isna()****

I .当列满足多个标准时

******-- SQL**
select * from table1 where col1 > 50 or (col2 > 50 and col1 is null)
**# python**
table.loc[(table['col1'] > 50) | ((table['col2'] > 50) & table['col1'].isna())]****

3.“更新”

a.基于单一条件更新列

******-- SQL**
update table set col2 = 'Y' where col1 > 10
**# python**
table.loc[table.col1 > 10, ‘col2’] = ‘Y’****

b.基于多个条件更新列

******-- SQL**
update table set col3 = 'Y' where col1 > 10 and col2 = 'x'
**# python**
table.loc[(table.col1 > 10) & (table.col2 == ‘x’), ‘col3’] = ‘Y’****

c.将为空的列更新为零

******-- SQL**
update table set col1 = 0 where col1 is null
**# python**
table.loc[table[‘col1’].isnull(), ‘col1’] = 0****

d.将列值更新为另一列的倍数

******-- SQL**
update table set col2 = col1*100 where col1 > 10
**# python**
table.loc[table.col1 > 10, ‘col2’] = table.col*100****

4。的‘联接’

a.基于单个 id 匹配左连接两个表

******-- SQL**
select * from table1 a left join table2 b 
on a.name = b.name**# python**
table = pd.merge(table1, table2, left_on = ['name'], right_on = ['name'], how = 'left')****

b.基于多个 id 匹配右连接两个表

******-- SQL**
select * from table1 a right join table2 b 
on a.name = b.name and a.id = b.id**# python**
table = pd.merge(table1, table2, left_on = ['name', 'id' ], right_on = ['name', 'id'], how = 'right')****

c.基于多重 id 匹配的三个表的内部连接

****select * from table1 a inner join table2 b 
on a.name = b.name
inner join table3 c 
on a.name = c.name**# python**
table = pd.merge(table1, table2, left_on = ['name'], right_on = ['name'], how = 'inner')table = pd.merge(table, table3, left_on = ['name'], right_on = ['name'], how = 'inner')****

5.“排序”

a.基于一列按升序对表格进行排序

******-- SQL**
select * from table sort by id asc**# python**
table.sort_values(by=[‘id’], inplace=True, ascending = True)****

b.基于多列按降序对表格进行排序

******-- SQL**
select * from table sort by id, name**# python**
table.sort_values(by=[‘id’, ‘name’], inplace=True, ascending = False)****

这些是一些简单的例子,并不能涵盖所有的用例。为此,请参考 pandas 文档,它有助于解释输入参数的大量选项,并提供了一些有用的示例。我还推荐去看看 W3 学校,那里教会了我几乎所有关于 SQL 的知识。

请留意第二部分(以及第三、第四和第五部分……)。

将 SQL 翻译成 Python —第 2 部分

原文:https://towardsdatascience.com/translating-sql-into-python-part-2-5b69c571ddc3

有点像谷歌翻译,但对于̶l̶o̶v̶e̶语言的数据

作者图片

如果您对 Python 中 SQL 的相应语法感到厌倦,那么您就找对地方了。用两种语言写作的能力不仅能让你对客户或雇主更有吸引力,还能让你写出更高效、更精简的数据流程,从而节省你的大量时间。

在 python 中,您可以创建一个直接查询 SQL 数据库的脚本,然后在同一个脚本中编写后续数据流程。按 run,这就是你在一个命令中单独运行的所有步骤。相信我,对于您定期运行的流程来说,这是一个巨大的胜利,因为时间会越积越多。

本文建立在第 1 部分的基础上,第 1 部分涵盖了“选择”、“哪里”、“更新”、“连接”和“排序”,可以通过下面的链接。

对于这一期,使用下面的内容跳到感兴趣的部分或 ctrl+f 记住这个函数。

目录:

  1. <#61b8>
  2. 【分组依据&聚合】
  3. <#a22c>
  4. <#a552>(尾随空格、串连、赶作等。)********

对于这些例子,我们将使用 pandas、datetime 和 numpy 库。确保您已经安装了这些组件(在 Anaconda 包中它们都是默认的),并使用以下命令将其导入到您的脚本中:

****import pandas as pd
import numpy as np
import datetime as dt****

在这篇文章中,我使用了 postgres SQL 语法,MS SQL 和 Postgres SQL 之间存在微小的差异,但是这三者似乎都有些夸张(或者是另一篇文章的想法;)).

1.《约会时报》

确保您的日期时间在 python 中是这样格式化的(即不是字符串)。您可以通过以下方式实现这一点:

****table['timestamp'] = pd.to_datetime(table['timestamp'])****

a .日期部分

隔离日期时间的某个“部分”,例如日或年。

I .从时间戳中选择日期

****-- SQL**
select date_part('day', timestamp) from table**# python**
table['day'] = table['timestamp'].dt.day**

二。从时间戳中选择小时

****-- SQL**
select date_part('h', timestamp) from table**# python**
table['hour'] = table['timestamp'].dt.hour**

二。从时间戳中选择月份

****-- SQL**
select date_part('m', timestamp) from table**# python**
table['month'] = table['timestamp'].dt.month**

b. Date_trunc

将时间戳截断到相应的精度,例如小时、天或月(我相信这是 postgres SQL 特有的)。

I .从时间戳中选择精确到天

****-- SQL**
select date_trunc('day',timestamp) as day from table
**# python**
table['day'] = pd.to_datetime(table['timestamp']).dt.to_period('D')**

I .从时间戳选择精确到月

****-- SQL**
select timestamp, date_trunc('month', timestamp) as month from table
**# python**
table['month'] = pd.to_datetime(table['timestamp']).dt.to_period('M')**

2.“分组依据”和“聚合”

a.Groupby 单个列和聚合

****-- SQL**
select name, count(*) from table group by name**group by # python**
table_grouped = table.groupby('name').count()**

b.Groupby 多列和聚合单列

****-- SQL**
select name, age, avg(height), max(height) from table group by name, age**-- python**
table_grouped =   table.groupby(['name','age']).agg({'height':   ['mean', 'max']})**

b.按多列分组并在多列上聚合

****-- SQL**
select name, age, avg(height), min(weight) from table group by name, age**-- python**
table_grouped =   table.groupby(['name','age']).agg({'height':   ['mean'], 'weight': ['min'})**

c.按列分组、聚合和重命名聚合列

****-- SQL**
select age, avg(height) as avg_height from table group by name, age**-- python** table_grouped =   table.groupby(['name','age']).agg({'height':   ['mean']}).rename(columns= {‘mean’: ‘avg_height’})**

d.按年月分组

****-- SQL**
select date_trunc('month', timestamp) as yearmonth, country, sum(rainfall) from table group by date_trunc('month', timestamp), country**-- python** table['month_year'] = pd.to_datetime(table['timestamp']).dt.to_period('M')table_grouped = table.groupby(['month_year', 'country']).agg({'rainfall': ['sum']})**

e.按一年中的月份分组

****-- SQL**
select date_part('day', timestamp) as month, country, std(rainfall) from table**-- python** table['month'] = table['timestamp'].dt.monthtable_grouped =   table.groupby(['month', 'country']).agg({'rainfall': ['std']})**

3.“案件何时发生”

I .创建具有单一案例结果的新属性,否则没有变化

****-- SQL**
select asset, age, 
case when age>= 20 or priority = 1 then 'To be replaced'
else status
end as status
from table**# python**
table.loc[(table.age > 20) | (priority == 1), ‘status’] = ‘To be replaced’
table[['asset', 'age', 'status']]**

二。创建具有多个案例结果的新属性

****-- SQL**
select Date, 
case when Date >= '2008-08-31' and Date <= '2009-05-30' then 'period1'
when Date >= '2009-08-31' and Date <= '2010-05-30' THEN 'period2'
when Date >= '2009-08-31' and Date <= '2010-05-30' THEN 'period3'
else 'N/A'
end as period from table**# python**
conditions = 
[table['Date'].between('2008-08-30', '2009-05-30', inclusive=True),table['Date'].between('2009-08-31', '2010-05-30', inclusive=True),table['Date'].between('2010-08-31', '2011-05-30', inclusive=True)]choices = ['period1', 'period2', 'period3']table['period'] = np.select(conditions, choices, default='N/A')table[['Date', 'period']]**

4.杂项

a.删除列中的尾随空格

I .删除前面的空格

****-- SQL**
update table
set col1 = LTRIM(col1)**# python**
table['col1'] = table['col1'].str.lstrip()**

二。删除后面的空格

****-- SQL**
update table
set col1 = RTRIM(col1)**# python**
table['col1'] = table['col1'].str.rstrip()**

三。删除前后的空格

****-- SQL**
update table
set col1 = TRIM(col1)**# python**
table['col1'] = table['col1'].str.strip()**

b.连接字符串

****-- SQL**
select concat(col1, ' - ', col2) as newcol from table**# python**
table['newcol'] = table['col1'] + ' - ' + table['col2']**

c.铸造柱组件

I .转换为字符串

****-- SQL**
select cast (col1 as varchar) from table**# python**
table.['col1'] = table.[col1].astype(str)**

二。转换为整数

****-- SQL**
select cast (col1 as int) from table**# python**
table.['col1'] = table.[col1].astype(int)**

三。转换为日期时间

****-- SQL**
SELECT CAST(col1 AS datetime) from table**# python**
table['col1'] = pd.to_datetime(table['col1'])**

这些不会涵盖所有的用例,但是应该提供一些不错的模板。更多详细信息,请参考熊猫、 numpy 和 datetime 文档,这些文档也涵盖了输入参数,让您的代码更加高效。

****https://medium.com/@hollydalligan/membership ****

将 SQL 查询转换为 Pandas 操作

原文:https://towardsdatascience.com/translating-sql-queries-to-pandas-operations-2d27c354b8a2

Pandas 中的 SQL 查询及其对应方法

在 Unsplash 上 engin akyurt 拍摄的照片

Pandas 和结构化查询语言(SQL)无疑是数据科学家管理、处理和分析表格数据的首选工具。虽然 Pandas 是一个流行的用于数据分析的 Python 库,但是 SQL 是一个完整的用于与数据库交互的编程语言。它们之间有一个突出的共同点,那就是它们都是处理表格数据的优秀工具。

因为 Pandas 和 SQL 本质上都是用来处理和操作表格数据的,所以可以使用两者来执行类似的操作。因此,这篇文章试图将数据科学家最常用的 SQL 查询翻译成它们在 Pandas 中的等效操作。这篇文章的完整代码可以在这里找到: GitHub 。

出于实验目的,我创建了一个随机数据集,其中有 10 名在某个组织工作的员工。数据集由七列组成:员工 ID、名字、姓氏、性别、工资(美元)、级别和加入日期。如下图所示:

随机员工数据集(图片由作者提供)

此外,我使用 ipython-sql 直接从 Jupyter 笔记本运行 sql 查询,并在 SQLite 数据库中创建了一个表,如下所示:

使用 SQL 创建表格的代码片段(图片由作者提供)

接下来,让我们继续执行 SQL 中常用的数据分析查询,并将它们转换成 Pandas 中相应的操作。

显示表格的前 5(或 k)行

在 SQL 中,我们可以在 select 之后使用 limit 并指定我们想要显示的记录数,如下所示:

显示表格前五行的 SQL 语法(按作者排序的图像)

请注意,并非所有数据库系统都支持限制子句。下面,我总结了一些数据库管理系统的语法。

在不同的 DBMS 中显示前 k 行的 SQL 语法(图片由作者提供)

在 Pandas 中,使用 head 方法并传递行数作为参数来完成这项工作。

显示前五行的 Pandas 语法(图片由作者提供)

前五行的结果(图片由作者提供)

对一列(或多列)选择 with 条件

假设我们希望只选择在组织中工作的女性员工。在 SQL 中,我们可以使用如下所示的 where 子句来实现这一点:

使用条件过滤的 SQL 语法(按作者排序的图像)

这将转化为熊猫的以下情况:

Pandas 使用条件过滤的语法(图片由作者提供)

过滤数据的结果(图片由作者提供)

选择列的子集

接下来,假设我们只想从表中选择 ID、名字和姓氏。我们可以用 SQL 这样做,如下所示:

用于选择列子集的 SQL 语法(按作者排序的图片)

类似的过滤可以在 Pandas 中执行,如下所示:

用于选择列子集的 Pandas 语法(作者图片)

注意:SQL 语法不区分大小写。因此,以下两个查询将返回相同的结果:

描述其不区分大小写行为的 SQL 语法(图片由作者提供)

然而,熊猫的语法是区分大小写的。因此,列名的格式和大小写应该与它们在原始数据帧中出现的格式和大小写相同。

根据列值对记录进行排序

在此任务中,假设我们希望按照薪水的降序对员工数据进行排序。这可以使用 SQL 中的 order by 子句来执行,如下所示:

根据列值对数据进行排序的 SQL 语法(图片由作者提供)

在 Pandas 中,我们可以使用 sort_values() 方法并将列作为参数传递,如下所示:

Pandas 对列值数据进行排序的语法(图片由作者提供)

对列值数据排序的结果(图片由作者提供)

分组记录

分组是分析表格数据时另一种广泛使用的操作。假设我们想要获得跨级别员工的平均工资以及在每个级别工作的员工数量。这可以通过使用如下所示的 group by 子句来实现:

用于分组记录的 SQL 语法(按作者分类的图像)

另一方面,我们可以在 Pandas 中使用 groupby 方法,并传递我们需要执行的聚合,如下所示:

Pandas 记录分组语法(作者图片)

记录分组结果(按作者分类的图像)

不同记录的数量

接下来,让我们看看如何显示列中的不同条目及其计数。在 SQL 中,这可以使用 DISTINCT 关键字来完成,如下所示:

用于查找不同记录的 SQL 语法(按作者排序的图像)

为了在 Pandas 中实现这一点,我们可以对系列调用 unique()nunique() 方法,如下所示:

用于查找不同记录的 Pandas 语法(图片由作者提供)

表中不同记录的结果(按作者排序的图像)

表格中的行数

最后,让我们看看如何使用 SQL 和 Pandas 找到表中的行数。在 SQL 中,我们可以使用如下所示的 count() 方法来实现这一点:

显示表中行数的 SQL 语法(按作者分类的图像)

在 Pandas 中,这转化为 DataFrame 的 shape 属性,返回行数和列数。

显示数据帧形状的 Pandas 语法(图片由作者提供)

总之,在这篇文章中,我们深入研究了几个 SQL 查询,并研究了如何在 Pandas 中获得相同的结果。因为 Pandas 和 SQL 本质上都是用来处理和操作表格数据的,所以我们可以使用它们来执行类似的操作。然而,根据我的经验,由于 Pandas 的 pythonic 语法,在 SQL 中实现复杂的操作,如计算交叉表和在 groupby 之后的复杂聚合,可能比在 Pandas 中更具挑战性。

回顾上一个 SQL-to-Pandas 示例,我们注意到 SQL 只返回表中的行数。如果希望使用 SQL 计算表中的列数,这种方法很复杂。另一方面,在 Pandas 中这样做需要打印 DataFrame 的一个属性(形状,我们得到 DataFrame 中的行数和列数。

从 SQL 到 Pandas 的翻译系统(图片由作者提供)

如果你喜欢读这篇文章,并且学到了一些新的东西,我相信你也会喜欢我的其他文章,如下所列:

</20-of-pandas-functions-that-data-scientists-use-80-of-the-time-a4ff1b694707>

基于图论的交通网络分析

原文:https://towardsdatascience.com/transportation-network-analysis-with-graph-theory-55eceb7e4de4

用图论优化零售企业的道路运输网络

(图片由作者提供)

目标

构建道路交通网络图形表示,以支持优化研究

介绍

对于零售商来说,运送到商店的公路运输是物流成本的主要部分。

公司经常进行路线规划优化研究以降低这些成本并提高其网络的效率。

它需要持续改进工程师运输团队之间的合作,后者负责管理日常运营。

在本文中,我们将使用图论来设计运输网络的可视化表示支持这种协作并促进解决方案设计

💌新文章直接免费放入你的收件箱:时事通讯

**SUMMARY**
**I. Distribution Network** Distribution centre of a retail company with 54 stores
**II. Problem Statement** Optimization of the route planning to reduce transportation costs
**1\. Exploratory Data Analysis** 1 year of deliveries to 50 stores **2\. Multi-Store Delivery** Use dedicated trucks to deliver several stores 
**III. Solution using Graph Visualization
1\. Visualization using the Graph Theory** What are the stores that are delivered together?
**2\. Challenge the current routing** Collaborate with the Transportation Planner to expand the routes
**IV. Conclusion & Next Steps**

如果你愿意,可以观看 Youtube 视频版本

一、配电网

语境

作为一名零售公司的持续改进工程师,你负责 仓储运输 运营的再造。

在您的范围内,您有一个位于上海(中国)主要配送中心**,为 54 家大型超市提供服务。**

您的零售公司的分销网络—(图片由作者提供)

卡车运输

这些商店位于四个不同的省份,使用第三方 运输服务提供商 送货。

他们为卡车提供三种不同的容量 (3.5 吨、5 吨和 8 吨)。****

(图片由作者提供)

根据运输计划员设计的路线和装载计划,分配一辆专用卡车运送物料。

装载计划示例

让我们想象一下这样一个场景:上海的三家商店总共订购了 30 个托盘 (5 T)。

  • 仓库团队准备订单,将纸箱放在托盘上
  • 运输计划员决定用一辆 5T 卡车交付这三家商店
  • 托盘被装入卡车

(图片由作者提供)

承运商根据路线上交付的首个城市使用每辆卡车价格(人民币/卡车)为您开具发票。****

(图片由作者提供)

**For the delivery of these three stores in Shanghai** Cost = 650 (Rmb)

运输计划团队的职责是设计路线,以确保卡车在离开仓库时已满。

(图片由作者提供)

因此,他们避免尽可能多的单一商店路线,以最大化的填充率。

如果你想更多地了解 FTL 交通

(作者视频)

**http://samirsaci.com **

二.问题陈述

目标

你的目标是降低总运输成本。****

****洞察力:吨成本优化的一个主要杠杆是卡车尺寸。

(图片由作者提供)

如果你增加卡车的平均尺寸,你就减少了每吨的整体成本。一个好的方法是每条路线提供更多的商店****

探索性数据分析

你有 12 个月的出货量来了解当前的路线。

(图片由作者提供)

每店出货量
T31上海及周边省份江苏为大部分出货量。

(图片由作者提供)

卡车尺寸 除了上海的大型超市拉动了大部分需求外,其他省份都有相对相同尺寸的门店。

(图片由作者提供)

每车规模路线数
1 月农历新年前的旺季,大部分路线均采用小型货车交付。

**http://samirsaci.com **

三.使用图形可视化的解决方案

你可以在我的 Github(跟我来:D)知识库中找到源代码虚拟数据:链接

目标是设计一个新的运输计划,通过每条路线向运送更多商店** e,来增加卡车的平均尺寸**

限制

由于操作限制,您需要遵守以下约束

  • ****发货时间窗口:商店只能在一天中的特定时间收到商品
  • 道路限制:一些道路禁止大型卡车通行
  • ****卸货条件:部分店铺需要先发货

1.协作的可视化支持

由于这些操作限制,y ou 无法单独执行此分析

****与有日常管理路线规划经验的运输团队合作是关键。

解法:图论 图是一个包含个节点(商店)的结构,每一对相关的节点被称为一条边

(图片由作者提供)

两个店铺的边表示这些店铺已经一起交付了至少一次。****

例如,商店 2 已与商店 3、商店 5 和商店 1 一起交付。

问题
用现在的路由,哪些门店一起发货?

2.挑战当前路由

让我们看看当前路由的结果。

举例:全年范围

(图片由作者提供)

你可以找到不同类型的集群

  • 类型 1: 商店都是相互连接的,通常代表一条路线(最好将几个商店组合在一条路线中)**
  • 类型 2: 商店按顺序连接,形成一个连锁
  • 类型 3: 1 个商店连接到所有其他商店

这种视觉效果可以支持与运输团队的讨论

  • 为什么我们在浙江2 家孤立门店
  • 如果将上海的两对分开,我们能增加平均卡车尺寸吗?
  • 我们能有更多的1 型集群吗?

问题 卡车尺寸有什么影响?

示例:仅 3.5 吨卡车

我们的主要问题是我们车队中小型卡车的比例很高。

3.5T 货车网络图

(图片由作者提供)

这些路线的互连较少。没有互连节点的主要集群。

平均卡车尺寸
当您查看每种卡车尺寸的每条路线的平均交付数量时,观察结果得到了证实。

(图片由作者提供)

除了在春节高峰期间,你可以看到大货车每条路线的交货数量更高。

问题
每条路线的店铺比例最低和最高有什么区别?

(图片由作者提供)

对于最佳场景(左),商店高度互联(最多 4 个连接)。另一个网络高度分散与之形成鲜明对比。

**http://samirsaci.com **

四。结论和后续步骤

关注我的 medium,了解更多与供应链数据科学相关的见解。

结论

该工具提供了分销网络的可视化表示,以支持您和运输团队之间的协同工作。****

后续步骤

根据您的分析,您可以提出潜在的改进建议(对更多商店进行分组,合并路线)并与团队一起评估运营可行性。

该分析是研究的开始,每个潜在的解决方案都需要由运营和商店经理验证以确保顺利实施。****

关于我

让我们连接上 Linkedin 和 Twitter ,我是一名供应链工程师,正在使用数据分析来改善物流运作和降低成本。

如果你对数据分析和供应链感兴趣,可以看看我的网站

**https://samirsaci.com **

参考

[1] — Networkx,用 Python 进行网络分析,链接

转置卷积神经网络——如何提高图像分辨率

原文:https://towardsdatascience.com/transposed-convolutional-neural-networks-how-to-increase-the-resolution-of-your-image-d1ec27700c6a

神经网络

用一个简单的 Python 例子详细解释转置卷积

转置卷积神经网络。图片由作者提供。

介绍

卷积神经网络彻底改变了图像分类和对象检测领域。但是你听说过转置卷积、吗,你知道如何使用它们吗?

在本文中,我将解释什么是转置卷积,它们与常规卷积相比如何,并向您展示如何构建一个简单的神经网络,利用它们来提升图像分辨率。

内容

  • 机器学习算法领域内的转置卷积
  • 什么是转置卷积?
  • 转置卷积有什么用?
  • 在 Keras/Tensorflow 中使用转置卷积构建神经网络的完整 Python 示例

机器学习算法领域内的转置卷积

我已经根据机器学习算法的性质和它们被设计用来做的工作对它们进行了分类。您可以在下面的图表中看到这种分类。

虽然不可能完美地做到这一点,因为一些算法可以被分配到多个类别,但尝试带来一些结构使我们能够可视化这些不同的算法如何连接和比较。

图表是交互式的,所以你可以通过点击来探索它👇在不同的类别上揭示更多。不出所料,你会在卷积神经网络分支下找到转置卷积网络。

机器学习算法分类。由作者创建的互动图表。

如果你喜欢数据科学和机器学习 ,请 订阅 获取我的新文章邮件。如果你不是中等会员,可以在这里加入https://bit.ly/36Mozgu

什么是转置卷积?

注意,在一些文献中,转置卷积也被称为解卷积或卷积步长卷积。

为了理解转置卷积,让我们首先提醒自己什么是正则卷积。

盘旋

卷积有三个部分:输入(例如,2D 图像)、滤波器(又称为内核)和输出(又称为卷积特征)。

卷积。对输入数据应用过滤器的迭代过程中的第一次计算。图片由作者提供。

卷积过程是迭代的。首先,对输入图像的一部分应用滤镜,并记录输出值。然后,当步距=1 时,滤波器移动一个位置,或者当步距设置为一个更高的数字时,滤波器移动多个位置,重复相同的过程,直到卷积特征完成。

下面的 gif 图像说明了对 5x5 输入应用 3x3 过滤器的过程。

卷积在起作用。Gif 图片由作者提供。

转置卷积

转置卷积的目标与常规卷积相反,即将输入特征图上采样为所需的更大尺寸的输出特征图

为了实现这一点,转置卷积会经历一个迭代过程,即通过过滤器将输入要素地图中的条目相乘,然后将它们相加。请注意,我们还会在每一步中移动指定数量的位置(步距)。

下面的 gif 说明了转置卷积是如何工作的。该示例使用步长 1,通过 2x2 滤波器从 2x2 输入变为 3x3 输出。

  • 取第一个输入条目,并将其乘以滤波器矩阵。暂时存储结果。
  • 然后,取第二个输入条目,并将其乘以滤波器矩阵。暂时存储结果。对输入矩阵的其余部分继续此过程。
  • 最后将所有的部分输出相加得到最终结果。

转置卷积的作用。Gif 图片由作者提供。

值得注意的是,在转置卷积运算期间,我们实际上是在生成额外的数据,因为我们将特征图从较小的尺寸向上采样到较大的尺寸。

然而,这种运算并不完全是卷积的逆运算。这是因为在卷积过程中总是会丢失一些信息,这意味着我们永远无法通过应用转置卷积来精确地重建相同的数据。

最后,我们可以试验过滤器的大小或步幅,以获得所需的输出特征地图大小。例如,我们可以将步幅从 1 增加到 2,以避免部分重叠,并产生 4x4 输出(见下图)。

跨距=2 的转置卷积。图片由作者提供。

转置卷积有什么用?

转置卷积对于语义分割生成对抗网络(GANs) 中的数据生成至关重要。一个更直接的例子是训练神经网络来提高图像分辨率。我们现在就要建立一个这样的网络。

**

利用 Keras/Tensorflow 的完整 Python 示例

设置

我们需要获得以下数据和库:

  • 加州理工学院 101 图像数据集(来源)

数据许可: 归属 4.0 国际(CC BY 4.0)

参考:李,女,男,安德雷托,男,兰扎托,文学硕士,&p .(2022)。加州理工 101(版本 1.0)[数据集]。CaltechDATA。https://doi.org/10.22002/D1.20086

  • 用于数据操作的熊猫和 Numpy
  • Open-CV 、 Matplotlib 和 Graphviz 用于摄取和显示图像并显示模型图
  • Tensorflow/Keras 用于构建神经网络
  • Scikit-学习库用于拆分数据( train_test_split )

让我们导入库:

上面的代码打印了我在这个例子中使用的包版本:

*Tensorflow/Keras: 2.7.0
pandas: 1.3.4
numpy: 1.21.4
sklearn: 1.0.1
OpenCV: 4.5.5
matplotlib: 3.5.1
graphviz: 0.19.1*

接下来,我们下载、保存和摄取加州理工学院 101 图像数据集。注意,在这个例子中,我将只使用熊猫的图片(Category = "panda "),而不是 101 个类别的完整列表。

同时,我准备数据并以两种不同的分辨率保存图像:

  • 64 x 64 像素,这将是我们的低分辨率输入数据。
  • 256 x 256 像素,这将是我们的高分辨率目标数据。

上面的代码打印出我们数据的形状,是【样本,行,列,通道】

*Shape of whole data_lowres:  (38, 64, 64, 3)
Shape of whole data_hires:  (38, 256, 256, 3)
Shape of X_train:  (30, 64, 64, 3)
Shape of Y_train:  (30, 256, 256, 3)
Shape of X_test:  (8, 64, 64, 3)
Shape of Y_test:  (8, 256, 256, 3)*

为了更好地理解我们正在处理的数据,让我们显示一些低分辨率的图像,我们将使用它们作为输入。

训练数据中的低分辨率图像。来自加州理工学院 101 的原始图像数据。由作者合成的图片。

和一些高分辨率图像作为我们模型中的目标。

目标数据中更高分辨率的图像。来自加州理工 101 的原始图像数据。组合图片由作者提供。

训练和评估转置卷积神经网络

这个模型非常简单,包含一个输入层、两个转置卷积层和一个充当输出的最终卷积层。您可以按照代码中的注释来理解每个部分的作用。

这是模型图:

转置卷积网络图。图片作者作者。

接下来,我训练模型超过 100 个时期。

一旦训练完成,我们可以使用该模型将低分辨率图像预测(升级)到更高的分辨率。让我们看几个例子,一个来自训练集,另一个来自测试集。

显示来自训练集的图像比较:

来自列车组的示例。来自加州理工学院 101 的原始图像数据。由作者合成的图片。

显示测试集中的图像比较:

来自测试集的示例。来自加州理工学院 101 的原始图像数据。由作者合成的图片。

我们可以看到,在上面的两个例子中,我们都成功地提高了图像分辨率。即,单个像素在建模图像中不太明显。

然而,我们确实损失了一些锐度,这与我们的目标(256 x 256)图像相比是显而易见的。人们可以试验模型参数以获得更好的结果,因为我的模型决不是优化的。

结束语

我真诚地希望你喜欢阅读这篇文章,并获得一些新的知识。

你可以在我的 GitHub 资源库 中找到完整的 Jupyter 笔记本代码。您可以随意使用它来构建自己的转置卷积神经网络,如果您有任何问题或建议,请随时联系我们。

如果您想在我发表新文章时得到通知,例如关于语义分割GANs 的文章,您可以在此订阅。

还有,可以随意查阅我的其他神经网络文章:前馈、深度前馈、 CNN 、 RNN 、 LSTM 、 GRU 、 AE 、 DAE 、 SAE 、 VAE 。

干杯!
索尔·多比拉斯

***https://bit.ly/3J6StZI ***

使用四键的旅行时间估计

原文:https://towardsdatascience.com/travel-time-estimation-using-quadkeys-ecf6d54823b4

本文解释了如何使用由四键索引的已知速度向量来估计旅行时间

迪诺·瑞奇穆斯在 Unsplash 上拍摄的照片

你的公路旅行将持续多长时间?如果你和我一样,你去 Google maps 或者 Here maps 直接查询路线。你不仅会得到最快或最短的路径,而且还会得到你的旅行时间的估计。这些服务根据学习的时间模式和实时交通信息报告最常见的速度,并针对道路上的典型车辆进行报告。

如果你有一个专门的车队,比如垃圾收集车,嘟嘟车,甚至普通的卡车,这些路线服务就没什么价值了。例如,最大允许高速公路速度往往根据车辆分类而变化,在欧洲,卡车的最高允许高速公路速度低于轻型车辆。一些商业地图路径选择产品支持卡车路径选择,但是如果您有专门的车队,您也可以自己选择。

如果您可以使用自己私人收集的速度矢量数据做出这些预测,会怎么样?车队出于从节省燃料到交通安全的各种目的收集远程信息处理数据并不罕见。远程信息处理数据通常包括 GPS 信息,除了采样位置,还报告相应的速度矢量,包括绝对速度和方向(方位)。如果您有这样的数据,您可以使用我在本文中描述的方法,用它来预测您的车队的预期行驶时间。

方法

我们必须从覆盖感兴趣区域的大量速度向量数据集开始。在本文中,我将使用扩展的车辆能源数据集。这里引用了这篇论文的陈述,解释了它与之前版本车辆能源数据集的主要区别:

这项工作提出了车辆能源数据集(VED)的扩展版本,这是一个公开发布的大规模数据集,用于车辆能耗分析。与原始版本相比,扩展的 VED (eVED)数据集增强了精确的车辆行程 GPS 坐标,作为将 VED 行程记录与外部信息(例如,道路速度限制和交叉口)相关联的基础,这些信息来自可访问的地图服务,以累积对分析车辆能耗至关重要的属性。

车辆能源数据集收集了密歇根州安阿伯市 383 辆私人汽车 2017 年 11 月至 2018 年 11 月的出行信息。扩展的车辆能量数据集通过地图匹配校正 GPS 波动,将采样位置“捕捉”到已知道路并添加更多上下文信息,从而增强了原始数据集。

该数据集的每个记录都包含原始和地图匹配的 GPS 位置、车辆报告的速度以及一组描述车辆状态和地点的附加属性。不幸的是,数据集不包括速度方向,所以我们丢失了一半的速度矢量信息。我们将通过使用位置序列的地理空间数据推断角度来解决这个问题(见下文)。

旅行时间估计

为了估计旅行时间,我们必须从使用直接地理空间坐标或地址地理编码来识别旅行终点开始。接下来,我们使用带有相关区域地理空间描述的图表来查找符合我们要求的路线。我们从公开可用的地图,即 OpenStreetMap 提供商创建了这个图表,使用 OSMnx Python 包来获取和增强数据。

结果路径是一系列图形节点和有向边,其中节点包含地理空间坐标。边带有描述性属性,如端点之间的距离、行驶时间、最大速度等。

有了路线信息,我们现在可以查询已知速度向量的数据库,并提取具有相同近似方向或方位的最接近路线的向量。我们甚至可以强制查询返回特定时间间隔(一周中的某一天或一天中的某个小时)记录的速度向量。

方位;轴承

不幸的是,扩展数据集不包括与每个采样 GPS 位置相关联的速度方向。矢量方向称为方位,以顺时针方向与正北成角度测量,如图 1 中所示。

图 1 —我们以顺时针方向测量方位,零度朝向北方。图中显示的是东北方向,测量角度为 45 度。(图片来源:作者)

我们将使用基于自然位置序列的近似值来计算收集的 GPS 速度矢量方位。数据中的 GPS 位置序列属于车辆行程,每个行程都有其唯一的标识符,并根据单调递增的计时器进行排序。对于这些序列中的任何一个,我们将连接两个连续位置的直线的方位角投影到第二个位置。例外发生在第一个位置,该位置接收的方向与第二个位置相同。当查询速度矢量数据库时,我们将使用这些角度作为标准。下图 2 说明了轴承计算过程。

图 2 —我们使用连接连续 GPS 位置的测地线的角度作为最后一个 GPS 位置的方位。在位置 2 采样的速度矢量的方向与连接位置 1 和 2 的线的角度相同。序列中的第一个位置与第二个位置具有相同的方位角。(图片来源:作者)

我使用了“活字字体”在线资源来计算端点位置的方位,这是地理空间算法和公式的宝库。实现矢量方位计算的功能如下图图 3 所示。

图 3 —上面的函数将两个 NumPy 数组作为参数,这两个数组具有车辆行程的纬度和经度序列。结果是连续的成对方位角计算。(图片来源:作者)

注意,对于 N 个点,该函数计算 N-1 个方位。然后,我们需要使用图 2 中描述的规则将方位分配给速度测量的各个位置。

空间离散化

我们需要反复查询采样输入向量数据库,以推断任意行程的速度。由于其超过 2200 万条记录的庞大规模,我们需要一种非常有效的方法来索引采样向量数据库。遵循简单的方法并避免复杂的地理空间索引,我们将使用带有四键的固定空间分区。或者,我们可以使用优步·H3的六边形分区,结果类似。

方形空间离散化允许快速隐式索引和画布上绘制。为什么我们需要画画?一旦我们有了行程路径,我们就可以把它分解成相交的四键,并用它们来查询速度向量数据库。图 4 说明了该过程。

图 4 —在我们查询速度向量数据库之前,我们应该确定使用它的哪一部分。通过将路径分解为交叉的四键,如上所述,我们可以更容易地查询数据库。红点代表 OSM 图节点,行程路径显示为蓝色,相交的四键显示为绿色。(图片来源:作者使用了 Folium 和 OpenStreetMap 图像)

图 4 中描述的过程相当于在四键图块空间中画线。我们确定每个图形边的图块端点,然后使用画线算法来决定哪些四键与路径相交。最后,我们使用得到的四键列表边缘方位来查询数据库中的采样速度向量。

然后,我们估计每个行程边缘的平均速度,并使用各个边缘距离重建总行程时间。

时间离散化

我们应该预料到,对于同一路段和同一方向,交通速度在一天和一周内会有所不同。交通密度与车速密切相关;当交通密度低时,我们应该期待更高的速度,反之亦然。高峰时段是典型的密度循环,速度较慢,周末也应该表现出与平日不同的交通行为。如果我们想在微观层面上捕捉这些速度变化,我们最好将时间离散化。

在本文中,我将使用一种简单的时间离散化技术,将一年分为七个工作日,每天分为 144 个十分钟的间隔。这个时间离散化允许我们研究每周和每天的模式。

履行

我们现在关注本文代码的实现细节。请首先前往附带的 GitHub 库并克隆它。

数据采集

要下载和解压数据集,要用第一个笔记本。代码非常简单。准备就绪后,您可以将数据导入支持数据库。

数据导入

与本文之前的许多文章一样,我使用了一个 SQLite 数据库来保存所有数据,并实现了将数据导入到第二个笔记本中的过程。代码只是遍历扩展的 CSV 文件,将数据读入一个 Pandas DataFrame ,然后将其插入数据库。请注意,代码首先将 CSV 文件读入一个字符串,然后过滤掉分号字符,因为有些文件在行尾有这个字符,这会破坏标准的 CSV 导入。

方位和四键计算

现在,我们可以从采样的速度矢量数据中计算方位和四键。为此,请使用第三个笔记本。如您所见,它只是调用一个外部 Python 脚本来完成所有繁重的工作。代码并行执行,以利用它计算每辆车的行程。请注意,这段代码将需要很长时间来运行(预计至少九十分钟)。

时隙计算

时隙计算需要信号表中的两个额外列,即工作日和时隙。我们根据“日数”列包含偏移到 2017 年 11 月 1 日的信息来计算工作日。正如最初的 GitHub 存储库文档中所讨论的,值 1 意味着当天的午夜(星期三)。小数表示当天的分数,因此 1.5 表示当天的中午。使用这些信息,我们可以很容易地将日期数分解为相应的工作日和时间段。我们将使用 10 分钟时段,这意味着每天有 144 个这样的时段。为了更新该表,我们使用以下 SQL 查询:

图 5 —上面的查询更新了信号表上的时隙。(图片来源:作者)

请注意,上面的查询将星期日编码为零。你可以适应你的喜好。

运行四号笔记本用时间段更新你的数据库。我们现在可以预测出行时间了。

出行时间预测

既然我们已经设置了所有的先决条件,让我们来说明使用 EVED 估算在安阿伯的任意旅行的过程。具体实施请参考笔记本五。

加载并准备地图数据

我们的第一步是在 Ann Arbor 上加载 OpenStreetMap 数据。我们将使用 Geoff Boeing 出色的 OSMnx Python 包来完成这项工作。

图 6 —多亏了 OSMnx,我们使用一行 Python 代码为安阿伯加载了 OSM 数据。(图片来源:作者)

图 6 显示了从 OpenStreetMap 加载 Ann Arbor 的可行驶道路信息所需的单行代码。我明确避免了网络简化步骤,以便在匹配出行端点位置和道路图节点时获得更好的分辨率。下图 7 显示了加载后的道路图。

图 7 —上图描绘了安阿伯的可行驶道路图。(图片来源:作者)

加载道路网络后,我们需要添加一些缺失的信息,即边速度、行驶时间和方位。幸运的是,我们得到了 OSMnx 的帮助,如下图 8 所示。

图 8 —上面的代码片段显示了如何使用 OSMnx 包添加缺失的边缘速度、行进时间和方位。(图片来源:作者)

既然已经接收并准备好了数据,我们就可以继续路由定义过程了。

定义路线

我们通过指定路由的端点,使用最简单的方法来定义路由。我改编了下面这篇优秀文章中的方法和代码。

https://max-coding.medium.com/osmnx-part-1-loading-data-from-open-street-maps-and-finding-shortest-route-between-two-points-5bb073b0a27d

下面图 9 中的代码说明了从其端点获取路线的过程。我们首先定义地址,将它们地理编码到地理空间位置,将它们映射到最近的图节点,最后获得路线。产生的 route 对象是一个按顺序排列的图节点列表。通过使用顺序节点对查询图形对象,我们很容易得到连接边。

图 9 —上面的代码使用街道地址指定行程终点。然后,它通过地理编码将地址转换为地理空间坐标,并将这些位置映射到图表中,找到最近的节点。我们通过查询网络的最短路径来确定路由。请注意,我们也可以使用 OSMnx 来计算路线。(图片来源:作者)

在获得端点之间的有效路线之后,我们现在可以使用它来查询离散化的速度向量数据库。我们将通过将图形节点的位置转换为它们相应的四键并使用它们的平铺坐标来“绘制”它们之间的边,从而确定最匹配它们的四键来实现这一点。下面的图 10 显示了我们的路线,从路网信息中提取。

图 10 —上面的地图显示了计算出的路线,图中节点为红色,边为蓝色。工具提示显示了从地图提供程序收集并由 OSMnx 增强的边属性。(图片来源:作者使用了 Folium 和 OpenStreetMap 图像)

绘制路线四键

使用四键的一个令人兴奋的优点是,我们将纬度和经度空间划分成一个正方形区域的网格,就像绘图纸或光栅显示器一样。每个方块接收一个唯一的 xy 坐标对,这取决于“缩放”或细节级别。随着细节的增加,正方形的大小会减小。

我们需要一种在四键空间上绘制虚拟线条的方法,如上面的图 4 所示。虽然我们可以使用旧的 Bresenham 画线算法,但我会选择一个反走样的版本,吴晓林的算法。从下面的图 11 中,或者一旦你看到维基百科中算法工作的动画,这个选择就变得显而易见了。每个绘制的正方形是一个四键查询,它的阴影是报告信息的权重。该算法允许我们查询线附近的数据,并为其分配重要性或相关性分数。

图 11 —上图描绘了从坐标(0,0)到(20,13)画一条抗锯齿线的结果。黑色像素的权重为 1,而白色像素的权重为零。请注意我们是如何为每条水平扫描线收集多个像素的。(图片来源:作者)

你可以在下面的图 12 中看到抗锯齿的线生成函数。请注意该函数如何生成包含 xy 坐标和像素权重的元组列表。我们稍后将使用这些坐标来创建四键索引,以在数据库中查询与行方向匹配的速度向量。

图 12 —以上函数生成了抗锯齿线的坐标和权重。(图片来源:作者)

我们可以通过在地图上绘制结果来查看将此方法应用于生成的路径的结果。下面的图 13 显示了我们的路径完全被 20 级四键覆盖。我们现在可以使用这些来查询匹配速度向量的数据库,并使用四键权重来推断边缘速度。我们从边缘速度和它们的已知距离计算边缘移动持续时间。

图 13 —上面的地图显示了用 20 级四键绘制的路线。请注意,由于重量较轻,一些显示器比其他显示器更轻,尽管叠加也起了作用。(图片来源:作者使用了 Folium 和 OpenStreetMap 图像)

速度推断

如前所述,我们通过将边缘速度分解成四个键并查询每个键来估计边缘速度。下面图 14 中描述的函数显示了我们如何查询每个四键的速度向量。请注意我们是如何应用角度条件的,强制矢量方位角在边方位角 5 度(总共 10 度)的范围内。

图 14 —上面的函数从四键查询速度矢量。请注意,该函数也接受方位和时间离散化。(图片来源:作者)

我们使用前面的函数来估计给定时间段和工作日期间的实际边缘速度。图 15 中的描述了下面的函数,该函数查询线条绘制的四键中的速度向量,并使用抗锯齿算法的权重对其进行平均。

图 15 —边缘出行持续时间推断需要从生成的线中查询每个四键。注意每个四键权重如何隐式平均每个向量的贡献。如果边不包含任何速度向量数据,则上面的函数默认为边元数据中的函数。(图片来源:作者)

请注意这个函数是如何接受以秒为单位的时间偏移而不是天数的。当我们穿过边缘,估计它们的持续时间时,我们还必须更新隐含的时钟和前进时间。我们在下面的函数中看到了这种机制,该函数建立在前一个函数的基础上,并估计整个路线的行程持续时间。

图 16 —所描绘的函数估计整个路线持续时间。请注意它是如何按照功能参数的指定来提升当前时钟的。我们必须更新隐式时钟,以避免从同一个时隙查询整个路由,如果必要的话增加一天。(图片来源:作者)

使用上面的函数,我们可以估计一天中给定时间和工作日的旅行持续时间,并获得每日概况,如下面的图 17 所示。

图 17 —上图显示了周一指定路线的预计持续时间。(图片来源:作者)

我们可以在图表中看到一些噪声,可能是由于十分钟的短暂时间段和由此产生的数据稀疏。根据当前的数据准备,我们只能通过平均一周的行程持续时间来缓解这一问题,如下图 18 所示。

图 18 —我们通过平均一周的旅行持续时间获得上图。请注意,交通流量似乎只是在上午 11 点左右才开始增加。(图片来源:作者)

绘画速度

我们现在可以采取额外的步骤,根据实际速度和地图提供的速度之间的关系来给路线着色。下面的函数迭代路径的边,并比较推断速度和边速度。

图 19 —上述函数使用颜色代码绘制路线,以反映车辆行驶的速度,红色表示最慢,浅绿色表示最快。(图片来源:作者)

通过在星期一晚上 9 点运行上面的函数,我们得到了下面的地图:

图 20 —上面的地图显示了彩色版本的路线,其中红色部分最慢。上述实例的数据与星期一晚上 9 点有关。(图片来源:作者)

结论

在这篇文章中,我们开发了一种技术,使用采样速度向量数据库和数字地图来推断行程时间。我们使用扩展的车辆能量数据集作为速度信息的来源,我们用方位信息丰富了速度信息。速度查询过程包括在四键图块空间上绘制路线,然后查询每个图块与基础边方位角一致的速度。利用得到的数据,我们可以估计一周中不同日期和时间的路线持续时间,并创建一个显示最快和最慢路段的路线图形描述。

未来的工作

我应该进行的一个明显的实验是将时间间隔从 10 分钟增加到 15 分钟,甚至 20 分钟。虽然结果将具有较小的粒度,但它将通过降低数据稀疏度而获益。

参考

【2203.08630】扩展的车辆能源数据集(eVED):用于车辆出行能耗深度学习的增强型大规模数据集(arxiv.org)

【arxiv.org【1905.02081】汽车能源数据集(VED),用于汽车能源消耗研究的大规模数据集

OSMnx 1.2.2 — OSMnx 1.2.2 文档

joaofig/eved-explore:扩展车辆能源数据集的探索(github.com)

joo Paulo Figueira 在葡萄牙里斯本的TB . LX by Daimler Trucks and bus担任数据科学家。

旅行推销员问题

原文:https://towardsdatascience.com/traveling-salesman-problem-92e21d488a6a

使用遗传算法寻找最短路径

如何找到不同地方之间的最短路线?(图片由作者提供)

所谓的旅行推销员问题是一个众所周知的挑战。任务是在多个目的地之间寻找最短的整体路线:女售货员连续访问几家商店,并在终点以整体最短的距离返回起点。我们打算用 Python 实现一个遗传算法来解决这个问题。

背景:

想象一下,从前,莫斯科一家金色汉堡连锁店的销售人员。销售人员必须走遍 A、B、C 和 d 四家商店。因为时间就是金钱,所以很自然地,您希望找到最短的旅行路线。

首先,我们需要所有四家商店之间的距离:

四家商店之间的车程,以米为单位。也考虑相反的方向(蓝色阴影)。

正如我们所看到的,所有商店之间的距离——在两个方向上——都是已知的。我们正在考虑两个方向,因为开车的距离可能略有不同。让我们看看商店 A 和商店 B 之间的距离,例如:

使用谷歌地图的汽车距离。我们的数据基于 OpenStreetMap,这就是为什么数字可能不同(图片由作者提供)

将所有商店组合在一条总的最短路线中称为非确定性多项式问题。这意味着找到的最短距离最多可以在多项式时间内得到验证。通俗地说,要找到确定性很高的最优路线可能需要很长时间。因为我们没有这个时间(即使我们有,我们也不能证明我们是否真的找到了最短的总距离),我们将通过一种叫做遗传算法的特殊方法来接近最佳可能的解决方案。

解:
遗传算法反复修改一个群体的染色体(个体解)。染色体包含一个可能的存储序列,例如从存储 D 到 B,从 B 到 A,从 A 到 C,最后再从 C 回到 D:

存储在数组中(图片由作者提供)

另一个可能是:

第三个,例如:

..诸如此类。

染色体背后的想法是基于模拟生物进化的自然选择过程。因为它只是模仿,但肯定甚至不接近自然的镜子,我更喜欢称遗传方法为“高级盲猜”和染色体为“潜在的解决方案”。遗传方法的有趣之处在于,在每一步,遗传算法从当前群体中随机选择个体,并使用它们作为父母为下一代产生孩子。因此,通过使用遗传算法,从可能的随机生成解的初始群体中选择特别有前景的路线(商店的顺序)。经过一定数量的循环(突变),种群“进化”到一个最优解。

遗传算法的实现包括类潜在解决方案,它将染色体表示为一种可能的单路解决方案。

class PotentialSolution:def randomInstance(cls):return PotentialSolution()def fitness(self):return 1def mutate(self):return selfdef crossover(self, other):return [self, other]

randomInstance()为第一代创建一个具有随机位置的实例。它也能够突变,即改变一条随机选择的路线,并与另一条染色体进行交叉,以与其随机交换位置。

由于距离将被最小化,因此适应度函数计算相应总距离的倒数。

def fitness(self):return 1 / self.getDistance()

在突变过程中,两个位置随机互换,

def mutate(self):return self

并且在交叉中,存储区将其位置与该存储区在另一个染色体中的位置交换。

def crossover(self, other):return [self, other]

class 方法 randomInstance()首先创建一个位置列表的副本,因为否则相同的列表将一直被传递。

def randomInstance(cls):return PotentialSolution()

使用 random shuffle()函数将副本随机化,并用于创建一个新实例,然后返回该实例。

def randomCoordinates(cls):PlacesCopy = cls.locations[:]shuffle(PlacesCopy)return distanceShuffling(PlacesCopy)

在 mutate()和 crossover()中,randint()函数用于生成两个指定数字之间的随机整数。方法 cross()在子代之间交换信息之前,使用 copy.deepcopy()获得父代染色体的完整拷贝。这是因为在当前的生殖循环中,父母可能会被重新选择。

主程序创建由 20 条随机染色体组成的初始第一个群体。计算每条染色体所有距离的总和。一旦其适应度超过指定的阈值(一个非常小的数字),生成序列就会提前结束,并返回最优解。否则,经过预定的世代数后会找到最好的一个。算法将在达到最大 20 代后停止。Crossover _ chance 是一个介于 0 和 1 之间的概率值,表示平均发生交叉的频率,而不是直接接管父代。“突变 _ 机会”指定新一代染色体的平均突变频率。该值也介于 0 和 1 之间。

if __name__ == '__main__': population = []for i in range(0, 20):population.append(distanceShuffling.randomCoordinates())advancedGuessing = AdvancedGuess(population, 0.0000001, 20, 0.7, 0.5)optimal = advancedGuessing.find()print("Shortest distance found:", optimal)

主要过程发生在 find()方法中。

def find(self):optimal = deepcopy(max(self.population, key = lambda potentialSolution: potentialSolution.fitness()))for i in range(0, self.max_generations):if optimal.fitness() >= self.expected:return optimalself.propagate()current_best = deepcopy(max(self.population, key = lambda potentialSolution: potentialSolution.fitness()))if current_best.fitness() > optimal.fitness():optimal = current_bestprint(i, optimal)return optimal

在最多 max_generations 代开始循环之前,这首先确定初始代的最适合的染色体。如果先前的最佳值已经达到预期的阈值,则立即返回。总体最适染色体和每轮最适染色体都被复制。

在标准情况下,下一代的生殖周期接下来发生。propagate()方法对此负责。之后,确定新一代的最佳个体,与先前的最佳值进行比较,如果出现进一步的改进,则接管该最佳个体。如果运行了所选的最大代数,但没有产生最佳结果,则最终返回目前为止找到的最佳结果。propagate()复制方法也是循环的,为下一代选择或创建个体,直到有足够多的个体。为此,在每次循环中首先选择两个人。

def propagate(self):newPopulation = []while len(newPopulation) < len(self.population):parents = self.chooseParents()if random() < self.crossover_chance:[child1, child2] = parents[0].crossover(parents[1])newPopulation.append(child1)newPopulation.append(child2)else:newPopulation.append(parents[0])newPopulation.append(parents[1])if len(newPopulation) > len(self.population):newPopulation.pop()for potentialSolution in newPopulation:if random() < self.mutation_chance:potentialSolution.mutate()self.population = newPopulation

与交叉概率相比较的随机值接下来决定是将来自上一代的原始个体延续下来,还是将由它们的父母产生新一代。

while len(newPopulation) < len(self.population):parents = self.chooseParents()if random() < self.crossover_chance:[child1, child2] = parents[0].crossover(parents[1])newPopulation.append(child1)newPopulation.append(child2)else:newPopulation.append(parents[0])newPopulation.append(parents[1])

在新一代最终取代老一代之前,所有新个体的另一个循环决定了它们是否应该变异。这也是通过将随机值与期望概率进行比较来完成的。

def mutate(self):rand_index_1 = randint(0, len(self.places) - 1)rand_index_2 = randint(0, len(self.places) - 1)if rand_index_1 != rand_index_2:self.places[rand_index_1], self.places[rand_index_2] = (self.places[rand_index_2], self.places[rand_index_1])

只要我们运行所有的代码,我们就能在几秒钟内得到解决方案:

可能的最短总距离之一(图片由作者提供)

请注意,这些遗传算法是高度随机的。继续尝试连续几次代码,更改参数并观察结果如何变化。你可以在这里找到完整的代码和数据。

小短途:

当我们在玩的时候,我想提一下迭戈·维森特解决旅行推销员问题的迷人方法:

https://github.com/diego-vicente/som-tsp

Diego 应用了一种改进的自组织映射(SOM)技术。出于好奇,现在让我们将现有的四家 McD-Moscow 商店再扩展 9 个坐标:

tsp 格式的 13 家商店的坐标(图片由作者提供)

..并将迭戈的知识库应用于此:

我们没看到本文开头的 gif 吗?你说的完全正确,我就是百听不厌!

如果迭戈的方法不酷,那什么才酷呢?

总结:

恭喜你,你已经应用遗传算法找到了一条最佳路线。

在这一点上,让我们也简单地记住,生活中有比 Python 编程更重要的事情。因此,这听起来也许很幽默,但却很真诚的愿望是,希望不久就会有和平,这样我们就可以在世界各地一起吃自由薯条了!

在此之前,非常感谢您的阅读!希望这篇文章对你有帮助。请随时在 LinkedIn 、 Twitter 或工作室与我联系。

https://jesko-rehberg.medium.com/membership

Python 中的树遍历和变位词

原文:https://towardsdatascience.com/tree-traversals-and-anagrams-in-python-97047bcc6f8e

探索散列表和递归算法的周末项目

作者图片

我知道这只是语言上的意外,但是你可以把“眼睛”中的字母重新排列成“他们看见”的事实感觉…很神奇。

我从小就喜欢字谜游戏。所以,我想尝试用 Python 来构建一个变位词生成器。它变成了一个有趣的小周末项目,以 Python 字典和递归的形式使用哈希表。

哈希表用于实现数据库索引和缓存之类的东西。它们通过将计算成本较低的键映射到一个值来实现数据的快速查找。但是,如果您没有接触过树和图之类的东西,您可能不会考虑将哈希表的值作为其他哈希表的值。

正如您将看到的,用嵌套哈希表构建树结构有助于在大量数据中搜索模式,使之更易于管理。递归是一种工具,可以让你简洁明了地表达搜索逻辑。

你在这里看到的字谜生成算法是一种组合搜索算法。这类算法在机器人和人工智能中发挥着重要作用,其中可能状态的空间呈指数增长,甚至是阶乘增长。

不过,在我们深入研究生成变位词之前,让我们先来看看一个更简单的问题:识别两个字符串何时是彼此的变位词。

如何检查两个字符串是否是字谜

如果string1中的人物是string2中人物的重排,string1就是string2的变位词。

不过,角色的顺序并不重要。重要的是string1中每个角色的数量和string2中每个角色的数量是一样的。如果每个字符在每个字符串中出现的次数相同,那么这两个字符串就是彼此的变位组合。

Python 的[collections.Counter](https://docs.python.org/3/library/collections.html#collections.Counter)——它是一种特殊类型的字典——可以这样计算:

看到Counter对象中的空字符串' '了吗?这是个问题。“Theater”和“the tear”是变位词,尽管一个有空格,另一个没有。字谜不区分大小写。它们也可能有不同的标点符号,如“vitalise”和“IT'S ALIVE!”

一个健壮的解决方案需要考虑到所有这些,所以如果没有一些预处理的话collections.Counter不会削减它:

像大多数问题一样,有很多方法可以解决这个问题。

我想要一个易于理解的解决方案,万一它不能处理所有应该处理的情况,也要易于修改。我使用了 string [.translate()](https://docs.python.org/3/library/stdtypes.html#str.translate)方法,它根据翻译词典替换字符串中的每个字符。映射到None的字符从字符串中删除。另外,我可以用[str.maketrans()](https://docs.python.org/3/library/stdtypes.html#str.maketrans)轻松创建翻译词典。

这给了我在字符串被传递到Counter()之前如何处理字符串的细粒度控制:

process()功能中使用[.casefold()](https://docs.python.org/3/library/stdtypes.html#str.casefold)而不是.lower()可能会更好。但总的来说,这是一个健壮的解决方案。这很好理解,前提是你了解Counter.translate()str.maketrans()。如果需要,可以很容易地交换处理功能。

但是,如何为特定的字符串寻找变位呢?

如何生成字谜

这比检查两个字符串是否是彼此的变位词更难。

简单地生成一个字符串所有可能的重排是不够的。你必须在有意义的地方插入空格和标点符号。字符串中的结果单词必须是实际的词典单词。

我们需要一种方法来有效地生成使用字符串中的字母的单词。

在纸上解决它

抛开获取单词列表的问题,从简单的开始:

eyes
the
they
see
sea

过程是这样的:

  1. 写下您想要生成变位词的短语,例如“眼睛”
  2. 从短语中选择一个字母(比如“t”),然后浏览列表,寻找以该字母开头的单词(单词=“the”和“them”)。划掉短语中该字母的一个实例,并将该字母记为变位词的第一个字母(变位词=“t”)。
  3. 从短语中挑选一个还没有被划掉的字母(比如“h”),把上一步选择的单词过滤到第二个字母是你挑选的新字母的那些(words =“the”和“them”)。划掉短语中的字母,并将其添加到您的变位词中(anagram = "th ")。
  4. 继续选择未使用的字母并过滤单词列表的过程。当你到达一个单词的末尾时,检查你目前生成的短语是否是原短语的变位词。如果是的话,你就完了。如果没有,从完整的单词列表开始,但是只使用没有被划掉的字母。
  5. 用短语中不同的首字母重复整个过程。

如果你一步一步地按照上面的小单词列表中的算法,你会得到四个变位词:

  • 眼睛
  • 眼睛看着
  • 他们看到了
  • 看到他们了吗

当然,“眼睛”是你开始用的短语。那很好。扔掉它。

如果我们可以直接跳到所有以字母 T 开头的单词,而不是扫描整个列表,这不是很好吗?还是跳到所有以 th 开头的单词?还是从。

听起来我们需要一个散列表!

我们真正想要的是哈希表的一个哈希表

哈希表的哈希表是表示树的一种方式。而且,如果你停下来想一想,我描述的生成变位词的方法感觉很像深度优先搜索。对于如何实现算法,这是一个很大的线索。

Python 拥有我们需要的所有部分。

构建字典树

我们将要构建的树看起来会像这样:

图片由作者提供(使用 Excalidraw 制作)

Python 字典是散列表,所以它们是我们的树的基础的自然选择。每个字典的键是包含单个字母的字符串,值是 more dictionaries。我们需要知道什么时候是一个单词的末尾,所以我们会用字典{" ": {}}来标记那些地方。

眼睛、The、them、see 和 sea 这些词的字典树如下所示:

但是你如何构建这样的字典呢?

关键的观察是,您可以递归地将单词添加到字典中。从一本空字典开始。对于每个单词,去掉第一个字母——称之为char。如果char是字典中的一个键,获取它的值。否则,用空字典设置字典中的键char作为它的值。然后重复这个过程,使用映射到char的字典和没有第一个字符的单词。

假设word没有空格,将字符串" "加到末尾,这样我们就能得到正确的终端字典。

代码如下:

递归很好,因为它可以简洁地描述一个重复的过程。但是,如果不通过手工推理步骤,就很难理解递归算法做了什么。带有一些好例子的文档可以帮助读者(包括你未来的自己!)理解一个递归函数。

现在我们有了一棵树,我们可以开始生成变位词了。

遍历树来制作字谜

您可以将变位算法实现为遍历单词树中的节点。

我们选择一个初始节点——短语中的一个字符,我们希望对其进行变位——并在数组中将该节点标记为已访问。然后移动到以该节点为根的树中的分支。重复此操作,直到访问完短语中的所有字符或到达一个单词的末尾。更多递归!

它本质上是一种深度优先的搜索算法,除了可行走的节点受到树中节点和短语中尚未被访问的字符的限制。

这是在以下 Python 代码中捕获的:

filter_()函数应该返回一组可供遍历的节点。这对于寻找字谜有一些细微的差别。我们一会儿会谈到这个问题,暂时先不要定义过滤函数。

我们仍然没有完成变位算法的实现。

walks()产生遍历所访问的节点元组——在这种情况下是短语中的字符——但是遍历一到达单词的末尾就结束了。

要产生多词变位词,你需要绕圈子,继续从树根开始遍历这棵树。我称之为“绕着树走”。你不停地走来走去,收集单词,直到走完为止。

代码如下:

我们需要实现donefilter函数来传递给walk_around(),这两个函数都依赖于我们正在为其生成变位词的短语。

当遍历中访问的字符串是短语的变位词时,遍历完成。我们已经知道怎么做了!我们可以使用are_anagrams()功能:

现在我们需要定义filter_()

我说过这里有一些细微的差别。

在行走的每一步,我们只需要移动到我们还没有访问过的短语中的一个字符。或者一个标点符号,因为我们想要包含像缩写这样的词。哦,我们也可以移动到一个" "字符,因为这是我们如何知道我们在一个单词的末尾。

我们可以使用集合清晰地表达这些规则:

Counter便于计算短语中尚未访问过的字符。

从另一个中减去一个Counter会返回一个减去了计数的Counter。计数为零的字符将被删除。例如,Counter("tea") - Counter("e")返回Counter({'t': 1, 'a': 1})

我们已经具备了编写变位词生成器的所有要素:

这里使用functools.partial()来“填充”done()filter_()功能的phrase参数。我们必须这样做,因为walk_around()donefilter_参数期望没有phrase参数的函数。因为walk_around()产生的遍历以空格结束,所以用.rstrip()去除了变位词中的空格。

让我们带着anagrams()在一个小单词列表上兜一圈:

嘿,成功了!

对大量单词进行测试

如果你的电脑有一个类似 Unix 的操作系统,你现在有一个很大的单词列表。

在大多数机器上,有一个位于/usr/share/dict/words/usr/dict/wordswords文件。这是一个用换行符分隔的字典单词列表。如果你有一台 Windows 机器或者没有一个words文件,你可以在 GNU 网站上找到一个用你的语言写的文件。

让我们试一试:

它可以工作,但是words文件包含单个字母和两个字母的单词,比如“te”这给我们的字谜增加了一堆噪音。我们应该清理一下。

让我们过滤掉一些我们知道我们不想要的单词:

这无疑给了我们更好的结果。对于一个更好的变位词生成器,你需要花一些时间来整理单词。并且words文件不一定是最好的单词集,因为它不包含任何缩写。

你可以使用 aspell 的皱眉工具生成更有趣的单词列表。

那么这一切有什么意义呢?

首先,这是一个有趣的项目,涉及了一些我在日常生活中不会用到的概念。仅此一点就值得了。

但是解决方案本身总的来说还是很有趣的。它提出了一种用组合约束构造对象的方法。人们可以想象它的许多应用,从构建代码和遗传序列到寻找组合问题的极值解。

对于一个周末项目来说还不错!

**Become a better coder in 5 minutes.**One email, every Saturday, with one actionable tip.
Always less than 5 minutes of your time.[Subscribe here](https://davidamos.dev/curious-about-code-newsletter/)

修复谷歌趋势数据的局限性

原文:https://towardsdatascience.com/trendecon-essential-r-package-to-construct-time-series-from-google-trends-36365ea48382

谷歌趋势数据有几个缺点。TrendEcon 是一个了不起的 R 包,它解决了这些问题,并有助于从标准化数据中创建一致的长期时间序列。

克劳迪奥·施瓦兹在 Unsplash 上的照片

Google Trends (GT)是一个公众可用的数据库,提供跨不同地区和语言的 Google 搜索查询的聚合数据。除了允许用户手动下载数据的网页和提供多个主题的预处理数据集的Google Trends Datastore之外,还有允许自动访问数据的 Python ( PyTrends )和 R 的 API。一个免费的 Data Studio 连接器也支持 GT 数据的实时报告。

尽管公司和研究人员大量使用该数据集,但很少有人讨论该数据集的特性和局限性。谷歌不提供搜索量,但将它们汇总到一个指数中,该指数反映了不同时间的变化以及与其他搜索词的相对差异。诸如频率不一致季节性随机抽样等主题,在分析时间序列时会带来显著的偏差。

在本文中,您将了解:

  • 燃气轮机数据的局限性
  • TrendEcon R 包修复了大量数据规范化带来的潜在问题。

谷歌趋势的局限性

让我们更深入地了解一下关于 GT 数据局限性的文献。艾希瑙尔等人(202 1)指出了他们。第一个来自数据本身的构造,因为出于隐私原因,原始卷被转换成一个索引。

谷歌如何转换原始数据?

1.每个数据点除以它所代表的地理和时间范围的总搜索次数,以比较相对受欢迎程度。

2.然后,根据一个主题在所有主题的所有搜索中所占的比例,将得到的数字在 0 到 100 的范围内进行缩放(谷歌,2022) 。

因此,时间序列的长期一致性在很大程度上取决于数据的时间范围和频率。原则上,我们可以依靠 Eichenauer 等人(2021) 提供的这些规则:

1.月度数据以最准确的方式捕捉了搜索活动的长期趋势。

2.每周的数据是分析几周的搜索的最佳数据。

3.每日数据最适合分析几天的短期行为。

将每日、每周和每月序列聚合到较低的频率会导致任何关键字的不同结果。这种频率不一致意味着每日数据无法捕捉长期趋势。因此,在使用忽略长期趋势的高频日序列和时间一致的月序列之间存在权衡,在时间一致的月序列中,搜索量在不同的和遥远的时间点是可比较的。

由于 GT 数据是时间序列,所以包含了全时间序列成分,包括季节性。不考虑这一因素(例如,通过将窗口限制在具有强烈季节效应的时段)也会给结果的解释带来偏差。

数据的另一个限制来自谷歌的随机抽样。他们非常务实地解释了原因:

“虽然 Google Trends 中只使用了一个 Google 搜索样本,但这已经足够了,因为我们每天要处理数十亿次搜索。提供对整个数据集的访问会太大而无法快速处理。通过采样数据,我们可以查看代表所有谷歌搜索的数据集,同时找到可以在现实世界中发生的事件发生后几分钟内处理的见解。” 【谷歌 2022】

但是,在小国家或地区,返回序列的抽样变异可能很大。对于搜索量有限的关键词,即使在大型行政单位中,也可能存在大量采样噪声( Eichenauer 等人,202 1)。这意味着,通过重复收集不同时期的时间序列,我们可以在某种程度上访问不同的数据集。

塔玛拉·加克在 Unsplash 上的照片

我们如何处理这些缺点?

幸运的是,有一个修复程序。 Trendecon 与 Google Trends API 交互,并提供工具,只需一行开源 R 代码即可构建频率一致的长期时间序列。

它处理燃气轮机数据的局限性。更具体地说,它:

  • 通过为每个关键词和频率绘制多个样本来提供采样调整
  • 使用 Chow 和 Lin(1971)的分解程序、主成分分析(PCA)和其他计量经济学方法获得时间和频率一致的时间序列。

让我们用几个例子来说明它的应用:

1。用 R 下载谷歌趋势数据

首先,要安装 TrendEcon ,请键入:

要在美国下载一系列关键词“乌克兰”、“普京”和“入侵”,请运行:

数据看起来是这样的:

作者图片,使用 ggplot2

2.构建稳健一致的时间序列

为了构建稳健一致的每日时间序列,使用 Chow-Lin 方法下载每日、每周和每月数据并进行一致汇总。

这是在美国搜索“乌克兰”的代码。很简单, TrendEcon 为我们做了所有的工作:

数据显示了预期的行为:

作者图片,使用 ggplot2

3.将多个关键字组合成一个时间序列

有时,我们需要监控与特定主题相关的几个关键词。比方说,我们需要测量一家名为“Apollo-Optic”的公司在奥地利的品牌知名度。顾客可以搜索该品牌的所有变体,例如,“阿波罗眼镜”、“阿波罗隐形眼镜”、“阿波罗光学”等。

我们可以从所有关键词中创建一个复合索引,并在我们的报告中使用它,而不是单独监控这些词。 TrendEcon 对归一化后的序列进行主成分分析,提取第一个主成分作为提供的关键词序列中的共同信号( Eichenauer et al .,2021 )。本教程提供了将关键词组合成复合指标的分步程序。

结论

TrendsEcon 增强了执行时间序列调查的研究人员和为公司建立重要指标报告的分析师使用 GT 数据的可能性。这里的是它提供的处理数据的所有函数的列表。为了进一步阅读,你可以查看我的关于用 python 构建 GT 数据集的文章。

PS:你可以订阅我的 邮件列表 在我每次写新文章的时候得到通知。如果你还不是中等会员,你可以在这里加入https://medium.com/@petrkorab/membership

参考

[1]周光诚,林(1971)。时间序列的最佳线性无偏内插、分布和外推。《经济学与统计学评论》,第 53 卷第 4 期,第 372–375 页。

[2] Eichenauer,V. Z .,Indergand,r .,Martínez,I .,z .,Sax,C. (2021)。从谷歌趋势获得一致的时间序列。 经济查询 ,60:694–705。

[3]谷歌(2022)。关于谷歌趋势数据的常见问题。检索自:https://support.google.com/trends/answer/4365533?hl=en。

数据科学的趋势

原文:https://towardsdatascience.com/trends-in-data-science-633f092ffa52

播客

Sadie St. Lawrence 介绍了数据领域的女性创始人、区块链的潜在数据优势以及数据科学的发展

苹果 | 谷歌 | SPOTIFY | 其他

编者按:TDS 播客由杰雷米·哈里斯主持,他是人工智能安全初创公司墨丘利的联合创始人。每周,Jeremie 都会与该领域前沿的研究人员和商业领袖聊天,以解开围绕数据科学、机器学习和人工智能的最紧迫问题。

如果你关注播客,你可能会知道,我们通常会谈论尖端人工智能能力的世界,以及人工智能未来可能带来的一些新出现的安全风险和其他挑战。但我认为,对于今天的这一集,将事情稍微改变一下,谈谈数据科学的应用方面,以及该领域在过去一两年中的发展会很有趣。

我找到了一个完美的嘉宾:她的名字叫 Sadie St. Lawrence ,她是 Women in Data 的创始人,这是一个帮助女性进入数据领域并在职业生涯中进步的社区,她也是 Data Bytes 播客的主持人,一位经验丰富的数据科学家和杰出的社区建设者。Sadie 和我一起谈论了她的创始人的旅程,数据科学今天是什么样子,甚至是区块链在本期“走向数据科学”播客中介绍的数据科学的可能性。

以下是我在对话中最喜欢的一些观点:

  • 赛迪的创始人之旅始于一次即兴实验:一次与对数据职业感兴趣的女性的简单会面。就像大多数早期创业经历一样,这几乎是一次失败:在她的首次女性数据活动开始 15 分钟后,她是唯一的参与者。但是几分钟后,一个参与者来了,还带了几个人。这个小规模的核心小组最终将成为今天“数据中的女性”的全球网络的核心,保留萨迪与早期采纳者的第一次对话中形成的亲密和支持的文化。Sadie 指出,这种文化是 Data 的秘方中的女性,也是该组织发展的关键因素。我们谈了很多关于成功创业中的创始人效应和文化传播。
  • 越来越多的人采用区块链技术,赛迪认为这是面向数据的公司和数据科学家的一个重大机遇。因为区块链作为一个结构化的、可靠的金融数据的单一来源,他们提供了一个异常清晰的视角来观察生态系统中的资源流动。在区块链上,事务被同时记录和执行——事实上,事务的记录就是它的执行,这使得区块链数据异常接近真实情况,因此更加可靠。所有这些(加上基于图的 ML 方法的重大发展)使区块链成为一个有希望的新数据源,这一点还没有被充分利用。赛迪认为,如果你是一名初出茅庐或经验丰富的数据科学家,了解这项技术的来龙去脉是值得的。
  • 我们谈了很多关于社区建设的内容,以及自早期女性数据时代以来,社区建设发生了怎样的变化(以及没有发生什么变化)。Sadie 观察到的一个关键变化是远程网络的可行性——一方面是因为如今人们在 Zoom 上更容易建立网络,另一方面是因为为远程社区建设设计的新一代工具使新的通信模式成为可能。然而,Sadie 也指出了一些没有改变的关键因素。其中最主要的是对面对面活动的需求,因为这种活动比在线活动更能体现亲密程度。用一堆半被动的成员建立一个松散的社区很容易,但最深层次的社区意识仍然来自于现实世界中的互动。
  • 许多求职者认为“数据科学”角色是他们的最终目标,但随着经验的积累,他们往往最终会选择其他工作。例如,人们很容易低估数据分析和数据工程中不太重要的角色。除了长期适合许多申请人之外,这些工作通常是进入公司以数据为中心的团队的很好的切入点(如果这是你长期寻求的,它可以为你提供数据科学的角色)。

你可以在推特上关注赛迪,或者我在这里。

章节:

  • 0:00 介绍
  • 2:00 数据领域的女性创始人
  • 6:30 进行性别对话
  • 11:00 文化方面
  • 16:45 区块链的机遇
  • 22:00 区块链数据库
  • 32:30 数据科学教育
  • 37:00 GPT-3 和非结构化数据
  • 39:30 数据科学作为职业
  • 42:50 总结

使用介质嵌入数据面板的技巧

原文:https://towardsdatascience.com/tricks-for-datapane-embedding-with-medium-9b47d1d319e0

MTA 乘客可视化

Meagan V .的数据面板图表示例

大约两年前, Khuyen Tran 发表了一篇关于在 Python 中使用 Datapane 的文章。基于她的教程,我继续做了一个我自己的小项目,用一个开源数据集包含了自 2020 年以来 MTA 每天的乘客数据。

今天,我将向您介绍我的项目教程,希望其他人能够发现这个免费工具的价值。

首先,Datapane 是一个 Python 库,可以用来构建交互式报告。我发现它类似于 Plotly Chart Studio。不幸的是,当要将这些图形嵌入到中等大小的文章中时,Datapane 不能很好地适应中等大小的更宽的图形,这很像 Chart Studio 的缺点。

然而,就 UX 而言,我发现使用 Datapane 的过程比 Chart Studio 容易得多。例如,Datapane 使得开始执行基本代码变得非常简单。

基本上,您只需要几行代码就可以开始制作您的第一个交互式图形。首先,您必须使用 pip 或 conda 安装 datapan,但之后只需导入适当的库:

import pandas as pdimport altair as altimport datapane as dp

此外,您真的可以将任何您想要的数字包装到一个数据面板报告中。语法遵循一个基本模板:

dp。报告(
dp。剧情(Plot),
dp。数据表(df)
)。上传(name=NAME,open=True)

您所要做的就是创建一个您想要显示的图形/绘图/图表,并用您的图形名称替换上面代码中的(绘图)。添加数据表是可选的。在我的报告中,我决定不在我的图下面显示数据表。如果你选择了那条路,就走吧

dp.DataTable(df)

既然我们已经介绍了基础知识,让我们进入我的项目——可视化从 2020 年至今的 MTA 日客流量。

我需要做的第一件事是在 google colab 中加载我的数据,并适当地格式化它。

我知道我想按天绘制数据图表,但我也想对每天的数据重新取样。为此,首先我必须将索引设置为 datetime。

df['Date2']=pd.to_datetime(df['Date'])
df=df.set_index(df['Date2'])

现在,由于我的数据跨越了过去两年的每一天,我知道用图表表示每一个数据点是不可行的。因此,我用下面的代码按月重新采样。

df.resample('M').mean()

我选择了均值重采样,因为我的数据既包括原始乘客数,也包括与典型的疫情前一天的百分比比较。因此,如果我使用一个总和来重新取样,我的百分比就会有偏差。

接下来,我知道我想使用 plotly 来创建我的交互式图表,所以我必须导入正确的库。

import plotly.graph_objects as gofrom plotly.subplots import make_subplots

具体来说,我需要制作支线剧情,因为我的数据被分成不同的类别。我有地铁数据、公交数据和桥梁/隧道数据。我决定要在 3 行 1 列中显示我的数据,定义如下。

fig = make_subplots(specs=[[{'secondary_y': True}], [{'secondary_y': True}],[ {'secondary_y': True}]], rows=3, cols=1, subplot_titles=('Subways',  'Buses', 'Bridges and Tunnels'))

如您所见,我还必须指定我想要创建一个辅助 y 轴。这是因为与疫情之前的百分比相比,乘客数量将处于不同的规模。

我有 6 组数据要绘制:

  1. 地铁乘客量
  2. 2.地铁百分比比较
  3. 公共汽车乘客量
  4. 总线百分比比较
  5. 桥梁/隧道交通
  6. 桥梁/隧道百分比比较

这是我用来为我的图形创建每个轨迹的基本模板:

fig.add_trace(go.Scatter(x=df['Date'], y=df['Subways: Total Estimated Ridership'], name = 'Subways: Total Estimated Ridership',line=dict(color='black', width=1, dash='solid')), secondary_y=False, row=1, col=1)

如您所见,我通过 X 和 y 值、系列名称、线条样式、是否使用辅助 y 轴以及行/列号来指定。

每个额外的轨迹都是相似的,但我只是更新了 y 值、名称、线条颜色和破折号、辅助 y 轴和行/列。例如,下一个轨迹是用于地铁百分比比较的,所以我必须更改一些东西,如下所示:

fig.add_trace(go.Scatter(x=df['Date'], y=df[ 'Subways: % of Comparable Pre-Pandemic Day'], name =  'Subways: % of Comparable Pre-Pandemic Day',line=dict(color='blue', width=1, dash='dash')), secondary_y=True, row=1, col=1)

为了继续其他 4 个跟踪,我遵循相同的格式。一旦我添加完六个轨迹,我就为我的图表设置了最终的格式。

首先,我设置标题和轴标签:

fig.update_layout(title_text='MTA Daily Ridership', title_x=0.4,xaxis_title='Date')fig.update_yaxes(title_text="Daily Ridership", secondary_y=False)fig.update_yaxes(title_text="% of Comparable Pre-Pandemic Day", secondary_y=True)

之后,只是设置我的尺寸,填充和显示图形。

fig.update_layout(autosize=False,width=1800,height=1200,margin=dict(l=50,r=50,b=100,t=100,pad=0),)fig.show()

因为我使用了 plotly,所以我能够轻松地将我的静态绘图导出为 png,如下所示。然而,下一步是将它包装在一个 datapane 对象中。

梅根 V 的静态图

dp.Report(dp.Plot(fig)).upload(name="MTA Ridership")

就这样——只有三行代码和我的图表就可以发布到 datapane 了。不幸的是,正如我前面所说的,默认大小不适用于 Medium 格式。原始的嵌入图表看起来很糟糕。你看不到整个图表,你不能滚动,缩放被关闭,等等。

然而,我进入数据面板编辑模式,并通过改变响应和规模设置硬编码。

默认情况下,responsive 被设置为 true,但是在一个更宽的图表的情况下,您可能需要将它更改为 false,并更新您的比例以获得您的支线剧情的正确大小。

这改进了绘图,但是我发现在 python 代码中将 responsive 设置为 False 允许在嵌入时从左向右滚动。

这需要一些策略,但最终我得到了下面的情节:

在线报道的链接在这里:https://data pane . com/u/meagan/reports/o7ven 13/MTA-rider ship/

此外,datapane 允许您将图表保存为 html 文件。你要做的就是替换。上传(…)

使用下面的代码

.save(path='report.html', open=True)

这就是我关于 datapane 和 python 入门的简单教程。尽管嵌入在 Medium 上没有成功,但我喜欢与 datapane 的其他集成,比如 LinkedIn posting、Twitter posting 和 Github。

点击此处查看 datapane 提供的所有功能:

https://datapane.com/home/

三重损失—高级简介

原文:https://towardsdatascience.com/triplet-loss-advanced-intro-49a07b7d8905

三重损失比对比损失有什么优势,如何高效实现?

三重损失下移动点的路径。图片作者。

三重损失最早于 2015 年在 FaceNet:人脸识别和聚类的统一嵌入中引入,此后它一直是监督相似性或度量学习最受欢迎的损失函数之一。用最简单的解释来说,三重态损失促使不同的线对与任何相似的线对相距至少一定的余量值。数学上,损失值可计算为L=max(d(a, p) - d(a, n) + m, 0),其中:

  • p,即阳性,是与a标签相同的样本,即锚,
  • n,即阴性,是另一个标签不同于a的样品,
  • d是测量这三个样本之间距离的函数,
  • 并且m是保持负样本远离的裕度值。

该论文使用欧几里德距离,但是使用任何其他距离度量也同样有效,例如余弦距离。

该功能有一个学习目标,可以如下图所示:

三重损失目标。作者图片

请注意,三元组丢失不会像对比丢失那样具有促使将锚和正样本编码到向量空间中的同一点的副作用。这使得三重损失容忍一些类内差异,不像对比损失,因为后者迫使锚和任何正值之间的距离基本上为0。换句话说,三联体丢失允许以包括异常值的方式拉伸聚类,同时仍然确保来自不同聚类(例如,负对)的样本之间的余量。

另外,三重态损失不太贪婪。与对比损失不同,当不同的样本很容易从相似的样本中区分出来时,它就已经令人满意了。如果没有负面例子的干扰,它不会改变正面簇中的距离。这是因为三重损耗试图确保负对距离和正对距离之间的余量。然而,对比损失仅在比较不相似对时才考虑裕度值,它根本不关心相似对在该时刻的位置。这意味着对比损失可以更早地达到局部最小值,而三重损失可以继续以更好的状态组织向量空间。

让我们通过动画演示两个损失函数如何组织向量空间。为了更简单的可视化,向量由二维空间中的点表示,并且它们是从正态分布中随机选择的。

展示对比损失如何在培训过程中移动点数的动画。图片作者。

展示三连音缺失如何在训练过程中移动点数的动画。图片作者。

从对双损耗函数的数学解释来看,很明显,三重损耗在理论上更强,但三重损耗有更多的技巧来帮助它更好地工作。最重要的是,三元组丢失引入了在线三元组挖掘策略,例如,自动形成最有用的三元组。

为什么三重开采很重要?

三重损失的公式表明它同时作用于三个物体:

  1. anchor
  2. positive -与锚具有相同标签的样品,
  3. negative——一个标签与锚和正不同的样本。

在一个简单的实现中,我们可以在每个时期的开始形成这样的样本三元组,然后在整个时期将成批的这样的三元组提供给模型。这就是所谓的“线下策略”然而,由于几个原因,这不是很有效:

  • 它需要通过3n个样本才能得到n个三元组的损失值。
  • 并非所有这些三元组都对模型学习任何东西有用,例如,产生正损失值。
  • 即使我们用我将在本系列中实现的方法之一在每个时期的开始形成“有用的”三元组,它们也可能在时期中的某个点变得“无用”,因为模型权重将不断更新。

相反,我们可以得到一批n样本和它们相关的标签,并动态地形成三元组。这就是所谓的“在线策略”通常,这给出了n^3可能的三元组,但是只有这些可能的三元组的子集实际上是有效的。即使在这种情况下,我们也会从比离线策略多得多的三元组中计算出损失值。

给定三个一组的(a, p, n),它仅在以下情况下有效:

  1. ap有相同的标号,
  2. ap是不同的样本,
  3. 并且n具有与ap不同的标签。

这些约束可能看起来需要使用嵌套循环进行昂贵的计算,但它可以通过距离矩阵、屏蔽和广播等技巧有效地实现。本系列的其余部分将集中在这些技巧的实现上。

距离矩阵

距离矩阵是形状为(n, n)的矩阵,用于保存由两个n大小的集合中的项目组成的所有可能对之间的距离值。该矩阵可用于矢量化计算,否则将需要低效的循环。它的计算也可以优化,我们将实现 Samuel Albanie 解释的欧几里德距离矩阵技巧(PDF) 。您可能想阅读这份三页的文档,以全面了解这个技巧,但下面是一个简短的解释:

  1. 计算两个向量集合的点积,例如,在我们的例子中是嵌入。
  2. 从这个矩阵中提取对角线,它包含每个嵌入的平方欧几里德范数。
  3. 基于以下等式计算平方欧几里德距离矩阵:||a - b||^2 = ||a||^2 - 2 ⟨a, b⟩ + ||b||^2
  4. 对于非平方距离,求这个矩阵的平方根。

我们将在 PyTorch 中实现它,所以我们从导入开始。

无效的三元组屏蔽

既然我们可以为一批中所有可能的嵌入对计算距离矩阵,我们可以应用广播来枚举所有可能的三元组的距离差异,并在形状为(batch_size, batch_size, batch_size)的张量中表示它们。然而,正如我前面提到的,这些n^3三元组中只有一个子集是有效的,我们需要一个相应的掩码来正确计算损失值。我们将分三步实现这样一个助手函数:

  1. 计算不同索引的掩码,例如(i != j and j != k)
  2. 计算有效锚点正-负三元组的掩码,例如labels[i] == labels[j] and labels[j] != labels[k]
  3. 结合两个面具。

在线三元组挖掘的批量策略

现在,我们已经准备好实际实现三重态损失本身。三联体丢失涉及形成或选择三联体的几种策略,最简单的一种是使用所有有效的三联体,这些三联体可以由一批样品形成。由于我们已经实施了实用程序功能,这可以通过四个简单的步骤实现:

  1. 获得所有可能对的距离矩阵,这些可能对可以由一批嵌入形成。
  2. 将广播应用于该矩阵,以计算所有可能的三元组的损失值。
  3. 将无效或简单三连音的损失值设置为0
  4. 对剩余的正值求平均值,以返回标量损失。

我将从实施这一策略开始,更复杂的将作为单独的文章跟进。

结论

我提到过,三元组损失与对比损失不仅在数学上不同,而且在样本选择策略上也不同,在这篇文章中,我通过使用几个技巧有效地实现了在线三元组挖掘的批量策略。还有其他更复杂的策略,比如批量硬挖掘和批量半硬挖掘,但是它们的实现,以及我在这篇文章中使用的提高效率的技巧的讨论,值得单独发表。未来的帖子将涵盖这些主题,并对一些技巧进行额外的讨论,以避免向量折叠并控制类内和类间的差异。同时,您可以加入 Qdrant 的 Discord 服务器,讨论、了解更多信息,并就公制学习提出问题。

https://discord.com/invite/tdtYvXjC4h

最初发布于https://qdrant.tech/articles/triplet-loss/

解释了信任区域策略优化(TRPO)

原文:https://towardsdatascience.com/trust-region-policy-optimization-trpo-explained-4b56bd206fc2

强化学习算法 TRPO 建立在自然策略梯度算法的基础上,确保更新保持在“可信”的策略区域内

在 Unsplash 上由 Ronda Dorsey 拍摄的照片

描述 OpenAI 的信任区域策略优化(TRPO)算法的论文,作者是舒尔曼等人。(2015),是现代强化学习的基础。它植根于 Amari (1998)和 Kakade (2001)关于自然政策梯度的早期工作,以及此后的改进。

TRPO 很快获得了欢迎和主流认可,因为根据经验,它比自然政策梯度算法表现得更好、更稳定。虽然它已经被最近的政策优化(PPO)超越,但它的贡献仍然很重要。

尽管我将简要地重述一些核心概念,但我假设您已经了解了政策梯度和自然政策梯度方法。本文将阐述 TRPO 背后的单调改进定理(关注直觉)以及区别于自然政策梯度的三个实施变化。

政策梯度(一阶优化)

政策梯度算法的主要思想相当简单。我们有一个参数化的策略 π_θ ,它以一定的概率返回动作。如果我们观察到一个行为产生高于平均水平的回报,我们会增加这个行为的可能性

为了指导更新,我们定义依赖于 π_θ 的目标函数 J(θ) ,并且计算梯度 ∇_θJ(θ) (偏导数的向量)w.r.t .参数 θ

目标函数,产生与随机政策成比例的国家行动轨迹。

通过对多个动作进行采样,我们可以将回报轨迹 R(τ)相互对比——导数告诉我们在哪个方向更新 θ 。目标函数的斜率越陡,我们的更新就越大。

概率分布P(τ;θ) 既反映了随机政策,也反映了环境中嵌入的外生不确定性。幸运的是,我们可以推导出一个只依赖于策略π_θ表达式。如果这个策略对于 w.r.t. θ 是可微的,我们可以计算梯度 ∇_θ 并优化目标函数。这产生了以下更新函数:

传统的策略梯度更新函数,基于目标函数梯度∇_θJ(θ和步长α更新策略权重θ

关键的挑战是选择合适的学习速率α。一阶导数告诉我们朝哪个方向走,但不是走多少。事实上,在一次更新中, α 的好值对于下一次更新可能是糟糕的。过冲和欠冲(见下图)在一阶优化中很常见;梯度只是不提供步长应该有多大的信息。

一阶梯度算法不提供关于适当步长的信息,并且经常过冲或失速。[图片由作者提供]

有关策略梯度的更多详细信息,请查看以下文章:

自然政策梯度(二阶优化)

为了解决一阶优化的步长问题,自然策略梯度还包括二阶导数,用于捕捉梯度相对于权重变化的灵敏度。本质上,它衡量的是政策πθ(回想一下,这是一个概率分布,在统计流形上移动)变化的程度。为此,我们计算更新前后策略之间的差异,称为 KL-divergence:

KL-divergence 衡量更新前后策略之间的距离。

通过在散度上加上上限 ϵ ,我们得到以下方案:

权重更新方案,最大 KL-散度为上限。

由于强化学习依赖于样本,我们在实践中执行了许多近似以使用自然梯度。最值得注意的是,我们对修改后的目标函数执行泰勒展开,对目标函数使用一阶展开,对 KL 散度使用二阶展开。推导过程非常繁琐,所以我将直接跳到最终的权重更新公式:

自然政策梯度的权重更新公式

我们用一个依赖于目标函数的局部灵敏度的动态函数代替了 α ,由费希尔信息矩阵 F(θ) 表示。直观上,我们的目标是发散阈值ϵt21】内的最大更新。在平坦的区域,我们可以安全地执行较大的更新,在弯曲的区域,我们更加谨慎。因此,这种行为通常与一阶优化相反,小心翼翼地逼近奖励峰值。

有关自然政策梯度的详细解释,请查看以下文章:

自然政策梯度的问题

通过限制政策变化并考虑当地的敏感性,自然梯度往往比普通梯度收敛得更好更快。然而,这种方法也有许多缺点。

首先,虽然提供了一个迷人的理论框架,自然梯度需要大量的近似。二阶泰勒展开可能会错误地表示政策之间的实际距离,导致步长过大。因此,自然政策梯度不能保证满足 KL-divergence 的约束!在实践中,人们仍可能采取过大的步骤并违反约束。

第二,费希尔信息矩阵 f 是一个 |θ|⋅|θ| 矩阵,它可能需要大量内存来存储。然而,更具限制性的是计算逆矩阵 F^-1 ,这是一个 O(N ) 复杂度的运算。对于包含数千甚至数百万个参数的神经网络,这在计算上变得非常困难。

第三,尽管偏离理论框架很远,自然梯度算法并不检查更新是否实际上改善了目标。由于近似值,建议的权重更新实际上可能会恶化性能。

总而言之,自然政策梯度面临以下关键问题:

  • 由于近似,可能违反 KL 约束,导致分析得出的步长过大。
  • 逆矩阵 F^-1 计算耗时太长,是一个 O(N ) 复杂度的运算。
  • 我们没有检查更新是否真的改善了政策。由于所有的近似值,情况可能并非如此。

TRPO 通过一些有针对性的调整,在一定程度上解决了这三个问题。在处理实现更改之前,有必要讨论一些理论。

梯度搜索(一阶优化)与信赖域搜索(二阶优化)的对比图。前者遵循最陡的斜率(有过冲或失速的风险),而后者根据统计流形的曲率调整其搜索空间[图片由作者改编自Dawid za wiaonUnsplash]

信赖域策略优化(TRPO)——理论

如果你理解自然政策梯度,实际变化应该是全面的。为了充分理解它们,最好理解一下单调改进定理

我们从夺回优势 A(s,a) 开始,这是一个重塑的奖励信号。强化学习中的样板材料。优势表示——对于给定的策略 π 和给定的状态s——特定行为 a 的预期累积回报减去整体期望值。因此,优势描述了该行为的相对吸引力。

优势功能。直观地说,优势描述了与状态 s 中特定动作 A 相关的 Q 值与 s 中所有动作的 Q 值之间的差异。正优势意味着该动作的“优于预期”性能。

随机政策影响轨迹中的行动选择 τ ,环境决定状态的出现。因此,我们可以根据当前政策下生成的轨迹来计算预期回报。目标函数 J(π_θ) 描述了这些折扣奖励,期望源于政策 π_θ :

目标函数,写为无限时间范围内的预期贴现回报。

为了描述两种政策的预期报酬的差异,我们可以取原政策的预期报酬并加上新政策的预期优势【Kakade&Langford(2002)或 Schulman et al. (2015)的证明】。该表达式采用新策略下调整后的行动概率,但采用旧策略下计算的优势(即,我们不必重新采样):

更新政策的目标函数,用当前政策和预期优势表示

由于时间范围是无限的,上述可以用状态分布ρ_(π_θ+δθ)来重新表示:

表示保单质量的变化,将更新保单的预期优势加到当前保单的预期价值上。

术语ρ_(π_θ+δθ)是有问题的——如果不采样,我们如何知道对应于更新策略的状态分布?为了解决这个问题,我们利用状态分布,就像在当前策略下发生的一样,代价是引入近似误差:

策略质量变化的近似值,使用当前策略π_θ下的状态分布,而不是更新后的策略π_θ+δθ。近似可能会引入误差。

接下来,我们必须让事情适合模拟,因为我们没有完整的状态和动作集。我们用一个期望符号来代替ρ——可以使用蒙特卡罗模拟来采样——并使用重要性采样来代替动作之和。通过重要性抽样,我们有效地利用了当前政策的行动预期,并对新政策下的概率进行了修正:

使用状态样本和重要性抽样,为蒙特卡罗模拟调整目标函数。

描述更新策略相对于旧策略的质量的预期优势被称为替代优势𝓛_π_θ(π_θ+δθ】:

替代优势𝓛的定义,使用来自策略π_θ的样本描述更新策略π_θ+δθ的预期优势

近似误差可以用两种策略之间最坏情况下的 KL-散度来表示。因此,我们可以表示以下不等式:

描述更新后目标值最小变化的不等式。右手边表示下限,考虑到对发散误差进行了修正的替代优势。

舒尔曼等人推导出 C 的值,并以此作为目标函数改进的下限。因此,如果我们改进了右侧,我们保证也会改进左侧。实质上,如果替代优势𝓛_π_θ(π_θ+δθ超过最坏情况近似误差 C⋅D_KL ,我们肯定会改进目标。

这就是简而言之的单调改进定理。相应的程序是最小化最大化(MM)算法。如果我们提高下限 M=𝓛_π_θ-C⋅D_KL ,我们也提高目标至少相同的数量。

MM 算法的说明。当改进下限 L(θ)-C⋅KL 时,我们保证也改进目标函数η(θ)[来源:舒尔曼,OpenAI,2017 ]

这一理论结果令人放心,但并不过分实用。首先, D_KL^max (即最大散度)很难计算,对于模拟实现,我们用预期(例如平均)散度来代替它。第二,所提出的算法严厉地惩罚策略偏差,因此产生非常小的保守更新。

尽管有实际的缺点,单调改进定理为充分理解 TRPO 的机制提供了重要的背景信息。

信任区域策略优化(TRPO) —实践

在实际实现方面,TRPO 与早期的自然政策梯度算法并没有太大的不同。有三个主要的改进,每个都解决了原始算法中的一个问题。请注意,TRPO 旨在验证提议的更新是否实际上改善了我们的算法,符合单调改善定理。

一、共轭梯度法[x=f^-1∇logπ_θ(x】

从自然梯度,我们已经导出了以下等价关系,允许将费希尔信息矩阵表示为我们已经拥有的梯度的外积(即,我们不必计算包含所有二阶导数的海森矩阵):

费希尔信息矩阵可以计算为梯度的外积。

不幸的是,计算费希尔逆矩阵f^-1(θ是一个耗时且数值不稳定的过程。特别是对于神经网络,一个 |θ|⋅|θ| 矩阵可以变得非常大,一个 O(θ ) 矩阵是不可行计算的。

好消息是:我们对逆矩阵本身不感兴趣。如果你检查方程式,我们只需要乘积 F^-1∇logπ_θ(x) 。如果我们可以计算x=f^-1∇logπ_θ(x)——或者f⋅x=∇logπ_θ(x)——我们实际上不需要求逆。

进入共轭梯度法。解释整个过程远远超出了本文的范围,但可以说这是一个既定的数值过程来近似 x 。通过这种方式,我们可以避开计算倒数的需要。共轭梯度一般在 |θ| 步内收敛(通常要小得多),这样我们就可以处理大型矩阵。

共轭梯度的使用并不是 TRPO 所独有的——例如,截断的自然政策梯度使用了相同的方法——但仍然是算法的一个组成部分。

二。线搜索[D _ KL(π_θ| |π_θ+δθ)≤δ]

尽管自然梯度提供了给定 KL 散度约束的最佳步长,但是由于所有的近似,我们实际上可能不满足该约束。

TRPO 通过执行线搜索——与典型的梯度搜索不同— ,迭代地减小更新的大小,直到第一次更新不违反约束,从而解决了这个性能问题。该过程可以被视为收缩信任区域,即,我们信任更新实际上改进目标的区域。

对于减少,使用指数衰减率 α^j ,其中 0 < α < 1j∈N 。如果第一次更新( α⁰=1 )满足条件,我们保留原始的自然梯度步长。如果没有,我们继续缩小信任区域,直到更新令人满意。

线搜索算法。该算法迭代地减小更新的大小(使用指数衰减α^j),直到散度落在阈值内(II)并且代理优势是非负的(iii)[来源: Joshua Achuam,UC Berkeley

因此,与自然政策不同的是,假定满足发散约束,而在 TRPO 中执行的线搜索实际上实施了它。还要注意,单调改进理论利用了惩罚,而实现使用了约束(即信任区域)。

从伪代码中我们看到 D_KL(θ||θ_k)≤δ 并不是唯一必须满足的约束。它还断言 𝓛_θ ≥0 。这是改进检查,我们接下来将对此进行解释。

三。改进检查[𝓛(θ)≥0]

最后的区别很有意义。与其说假设更新会提高代理优势𝓛(θ,我们实际上检查它。回想一下,我们基于旧策略计算优势,使用重要性抽样来调整概率。当然,检查需要一些时间,但是在接受它之前,验证我们的更新是否确实改进了策略是令人放心的。

TRPO 算法的伪代码。TRPO 执行一个共轭梯度算法,一个约束样本 KL-divergence 的线搜索和一个关于提高代理优势的检查[来源: OpenAI ,麻省理工学院许可]

结束语

与传统的梯度算法相比,自然策略梯度提供了相当大的改进,传统的梯度算法难以确定适当的学习速率,并且在学习时经常停滞或超调。TRPO 再次向前迈出了重要的一步,主要是通过简单的检查一个更新是否是一个改进。共轭梯度近似使得 TRPO 适合于具有大量参数的策略,尽管这种方法在早期的工作中已经被提出。

根据经验,TRPO 在许多任务上优于早期的自然梯度算法,但它本身也不是没有缺陷:

  • 虽然不再需要计算 F^-1 ,但是估计 F 仍然是一项繁琐的任务(需要内存和大量的样本批次)。对于深度神经网络,TRPO 不具有伸缩性;
  • 共轭梯度算法是容易得到的,但是它们的结合使得 RL 算法的实现更加复杂;
  • TRPO 在利用卷积或循环网络的任务中表现不佳(CNN 和 RNN);
  • 作为二阶算法,它明显比一阶算法慢,并且没有充分利用 ADAM 等优秀的优化器;
  • 当架构有多个输出时——神经网络输出策略和价值函数的情况很常见——没有一致的度量标准可供使用;
  • 总的来说,TRPO 很难解释和说明,使得实现、调试和培训都很困难。

近似策略近似法——也是 OpenAI 在 2017 年推出的——在经验上与 TRPO 等方法具有竞争力(甚至优于 TRPO ),而且实现起来也简单得多。因此,事实上已经取代了 PPO。尽管如此, TRPO 代表了自然政策梯度发展中的一个重要里程碑****,值得强化学习领域的学生关注。

喜欢这篇文章?你可能也会喜欢下面的 RL 文章:

**

进一步阅读

论文:

  • 阿马里,S. I. (1998 年)。自然渐变在学习中很有效。 神经计算10 (2),251–276。
  • Kakade,S. M. (2001 年)。天然的政策梯度。 神经信息处理系统进展14
  • s . m . kakade 和 j . John Langford(2002 年)。近似最优近似强化学习。在国际机器学习会议
  • 舒尔曼,j .,莱文,s .,阿贝耳,p .,乔丹,m .,莫里茨,P. (2015)。信托区域政策优化。参加机器学习国际会议。

讲座幻灯片:

  • 高级政策梯度(CS 285)。加州大学伯克利分校。
  • Achiam,J. (2017 年)。高级政策梯度方法。加州大学伯克利分校。
  • 自然政策梯度,TRPO,PPO (CMU 10703)。卡内基梅隆。
  • 舒尔曼,J. (2017)。高级政策梯度方法:
    自然梯度、TRPO 等。
    OpenAI。

博客帖子:

  • 自然渐变(TRPO,PPO)。[ 链接
  • OpenAI (2018)。信任区域策略优化。[ 链接**

使用贝叶斯优化调整深度神经网络

原文:https://towardsdatascience.com/tune-deep-neural-networks-using-bayesian-optimization-c9f6503a049f

利用贝叶斯理论提高你的绩效

Syarafina Yusof 在 Unsplash 上的照片

在之前的一篇帖子中,我们展示了一个关于使用 Tensorflow 和深度学习方法进行图像分类的案例研究。

虽然案例研究很少,但它展示了机器学习项目的每个阶段:清理、预处理、模型构建、训练和评估。但是我们跳过了调优

在本文中,我们将更深入地研究超参数优化。同样,我们将使用 Tensorflow 中包含的时尚 MNIST[1] 数据集。

提醒一下,数据集在训练集中包含 60,000 幅灰度图像,在测试集中包含 10,000 幅图像。每个图像代表属于 10 个类别之一的时尚项目( T 恤/上衣’、裤子’、套头衫等等)。因此,我们有一个多类分类问题。

设置

我将简要回顾一下准备数据集的步骤。有关更多信息,请查看上一篇文章的第一部分:简而言之,步骤如下:

  1. 加载数据。
  2. 分为训练验证测试套。
  3. 将 0–255 到 0–1 范围内的像素值标准化。
  4. 对目标变量进行一次性编码。

概括地说,所有训练集、验证集和测试集的形状是:

超参数调谐

现在,我们将使用 Keras Tuner 库[2]:它将帮助我们轻松地调整神经网络的超参数。要安装它,请执行:

pip install keras-tuner

注意: Keras Tuner 需要 Python 3.6+和 TensorFlow 2.0+

快速提醒一下,超参数调整是机器学习项目的基本部分。有两种超参数:

  1. 结构超参数:定义模型整体架构的参数(如隐藏单元的数量、层数
  2. 优化器超参数:影响训练速度和质量的参数(如学习率优化器类型批量周期数)

为什么调优很棘手?

为什么需要超参数调节库?难道我们不能尝试每一种可能的组合,看看在验证集上什么是最好的?

不幸的是,没有:

  • 深度神经网络需要大量的时间来训练,甚至几天。
  • 如果你在云上训练大型模型(比如亚马逊 Sagemaker),记住每个实验都是要花钱的。

因此,限制超参数搜索空间的剪枝策略是必要的。

贝叶斯优化

幸运的是, Keras tuner 提供了一个贝叶斯优化tuner 。贝叶斯优化调谐器不是搜索每一个可能的组合,而是遵循一个迭代过程,在这个过程中,它随机选择前几个组合。然后,基于这些超参数的性能,贝叶斯调谐器选择下一个最好的可能。

因此,超参数的每个选择取决于先前的尝试。基于历史选择下一组超参数和评估性能的迭代次数继续,直到调谐器找到最佳组合或用尽最大数量的试验。我们可以用参数' max_trials '进行配置。

除了贝叶斯优化调谐器之外, Keras 调谐器还提供了两个调谐器: RandomSearchHyperband。我们将在本文的最后讨论它们。

回到我们的例子

接下来,我们将对网络应用超参数调整。在上一篇文章中,我们实验了两种网络架构,标准的多层感知器 ( MLP )和卷积神经网络 ( CNN )。

多层感知器(MLP)

但是首先,让我们记住我们的基线 MLP 模型是什么:

调整过程需要两种主要方法:

  1. hp。Int(): 设置超参数的范围,其值为整数-例如,密集层中隐藏单元的数量:
model.add(Dense(units = hp.Int('dense-bot', min_value=50, max_value=350, step=50))

2.惠普。Choice(): 为超参数提供一组值—例如, AdamSGD 作为最佳优化器?

hp_optimizer=hp.Choice('Optimizer', values=['Adam', 'SGD'])

因此,在我们最初的 MLP 示例上使用贝叶斯优化调谐器,我们测试了以下超参数:

  • 隐藏层数:1-3
  • 第一密层尺寸:50–350
  • 第二和第三致密层尺寸:50–350
  • 辍学率 : 0,0.1,0.2
  • 优化器 : SGD (内斯特洛夫=真,动量=0.9)或者亚当
  • 学习率 : 0.1,0.01,0.001

注意第 5 行的 for 循环:我们让模型决定我们网络的深度!

最后,我们启动调谐器。注意我们之前提到的 max_trials 参数。

这将打印:

该过程耗尽了迭代次数,用了大约 1 个小时才完成。我们还可以使用以下命令打印模型的最佳超参数:

就是这样!现在,我们可以使用最佳超参数重新训练我们的模型:

或者,我们可以用更少的冗长来重新训练我们的模型:

我们现在要做的就是检查测试的准确性:

# Test accuracy: 0.8823

与基线的模型测试精度相比:

基线 MLP 模式: 86.6 %
最佳 MLP 模式: 88.2 %

事实上,我们观察到测试准确度有大约 3%的差异!

卷积神经网络

同样,我们将遵循相同的程序。有了 CNN,我们可以测试更多的参数。

首先,这是我们的基线模型:

基线模型仅包含单组过滤汇集层。对于我们的调优,我们将测试以下内容:

  • 卷积层、最大池层和漏失层的“块”数量
  • 各区块 Conv 层的滤波尺寸 : 32,64
  • conv 层上有效或相同的填充
  • 最终附加层的隐藏层大小:25–150,乘 25
  • 优化器 : SGD (内斯特洛夫=真,动量=0.9)或亚当
  • 学习率: 0.01,0.001

像以前一样,我们让网络决定它的深度。

现在,我们准备调用贝叶斯调谐器。最大迭代次数设置为 100:

这将打印:

最好的超参数是:

最后,我们使用最佳超参数训练我们的 CNN 模型:

并检查测试集的准确性:

# Test accuracy: 0.92

与基线的 CNN 模型测试准确性相比(来自我们的前一篇文章):

基线 CNN 模型: 90.8 %
最佳 CNN 模型: 92%

同样,我们看到了优化模型的性能提升!

除了准确性之外,我们可以确认调音师做得很好,原因如下:

  • 调谐器在任何情况下都选择非零的压差值,即使我们提供的调谐器也是零压差。这是意料之中的,因为压差是一种减少过拟合的宝贵机制。
  • 有趣的是,最好的 CNN 架构是标准管道,其中每层的过滤器数量逐渐增加。这是意料之中的,因为随着计算在后续层中向前推进,模式变得更加复杂。因此,有更多的模式组合需要更多的过滤器来捕捉。

闭幕词

毫无疑问, Keras Tuner 是一款用 Tensorflow 优化深度神经网络的万能工具。

最明显的选择是贝叶斯优化调谐器。然而,还有另外两种选择可供人们使用:

  1. RandomSearch: 这种类型的调优器通过随机选择几个超参数来避免探索超参数的整个搜索空间。但是,它不能保证这个调谐器会找到最佳的。
  2. Hyperband: 这个调谐器选择一些超参数的随机组合,并使用它们来训练仅几个时期的模型。然后,调优器使用这些超参数来训练模型,直到所有历元都用完,并从中选择最佳的。

感谢您的阅读!

  • 订阅我的简讯!
  • 在 Linkedin 上关注我!

参考

  1. 时尚 MNIST 数据集由 Zalando,https://www . ka ggle . com/datasets/Zalando-research/fashion mnist,麻省理工学院许可(MIT)版权【2017】
  2. 喀拉斯调谐器,https://keras.io/keras_tuner/

用 Python 把 Google Sheets 变成你自己的数据库

原文:https://towardsdatascience.com/turn-google-sheets-into-your-own-database-with-python-4aa0b4360ce7

使用 Python 创建电子表格并将数据保存到 Google Sheets 的指南。

图片来自 Shutterstock,授权给 Frank Andrade

您知道 Google Sheets 可以用作轻量级数据库吗?

Google Sheets 是一个基于云的电子表格应用程序,可以像大多数数据库管理系统一样以结构化的方式存储数据。您还可以通过几次点击来编辑和删除数据,除此之外,还可以在线访问您的数据。

尽管 Google Sheets 有一些限制,比如您可以使用的单元格数量(最多 1000 万个单元格)和每分钟可以进行的 API 调用,但它仍然是一个不错的选择,您可以免费获得。

在本指南中,我将向您展示如何在 Python 中使用 Google Sheets。我们将学习如何使用 Python 创建电子表格并保存数据,这样您就可以将 Google Sheets 转换成自己的数据库。

**Table of Contents** 1\. [Enable APIs and download credentials](#461d)
2\. [Turning Google Sheets into a Database with Python](#6054)- [Connect to Google Sheets](#7881)- [Create a blank spreadsheet](#36be)- [Sharing a spreadsheet](#06b5)- [Open the spreadsheet and upload data](#34d4)
3\. [Bonus: Open Any Google Sheet File with Python](#c20a)

如果你不想看,你可以看我的视频!

1.启用 Google Drive 和 Google Sheet API 并下载凭证

要用 Python 连接 Google Sheets,我们必须将我们的凭证下载到一个 JSON 文件中。为此,我们必须开始一个新项目。

创建新项目

要创建一个新项目,请访问这个网站,并遵循以下步骤(您需要已经登录您的谷歌账户)。

  1. 选择一个项目
  2. 点击“新项目”
  3. 将加载一个新页面。在那里,你必须写下你的新项目的名称,然后点击“创建”

作者图片

4.创建项目后,您将看到下图

作者图片

点击“选择项目”您应该会看到新的一页。

为新项目启用 Google Drive API,创建凭证,并下载它

选择项目后,转到左侧面板,单击“APIs & Services”,然后选择“Library”,如下所示。

作者图片

你现在应该看到下面的页面。在那里,我们必须搜索 Google Drive API 并启用它。

作者图片

启用后,我们必须单击右上角的“创建凭据”。

在此之后,我们必须选择下面的选项,然后单击“下一步”

作者图片

然后,我们为我们的服务帐户命名,并单击“创建并继续”

作者图片

现在我们必须选择角色。在这种情况下,我们从“项目”选项中选择“编辑器”,然后单击“完成”

作者图片

在此之后,我们必须再次打开左侧面板,单击“APIs & Services ”,然后选择“Credentials”

在页面底部,您会看到一个“服务帐户”部分。在这里,我们必须点击我们的客户端电子邮件(请记住,我们将在该部分创建的所有文件都将保存在该客户端电子邮件中)

作者图片

然后,我们必须转到“密钥”部分,并单击“创建新密钥”

作者图片

然后点击“创建”,一个包含您的凭证的 JSON 文件将被下载到您的计算机上。我将把这个文件重命名为" gs_credentials.json" 你必须把这个 json 文件移动到你的 Python 脚本所在的同一个文件夹中。

最后,我们必须搜索 Google Sheets API ,并像我们之前对 Google Drive API 所做的那样启用它。

2.用 Python 将 Google Sheets 转化为数据库

连接到 Google Sheets

要在 Python 中使用 Google Sheets,首先,我们必须安装 gspread 和 oauth2client。除此之外,我们将使用 Pandas 来读取我们的本地数据,并将其更新到 Google Sheets。

pip install gspread
pip install oauth2client
pip install pandas

现在让我们导入库并连接到 Google Sheets。

在上面的代码中,我们在scope列表中启用对特定链接的访问,然后在credentials变量中写入我们之前下载的 JSON 文件的名称。

最后,我们创建一个client变量。

创建空白电子表格

让我们创建一个名为“NewDatabase”的新电子表格

为此,我们必须使用我们之前定义的client变量的.create方法。

sheet = client.create("NewDatabase")

请注意,这个新工作表仅对我们之前创建的服务帐户可见。要使用我们自己的 Google 帐户访问此表单,我们必须通过电子邮件共享它

共享电子表格

让我们用自己的谷歌账号来分享这个sheet。为此,请运行下面的代码。

sheet.share('your_email_goes_here', perm_type='user', role='writer')

运行上面的代码后,该表将位于 Google Drive 的“与我共享”部分。

打开电子表格并上传数据

现在是时候打开工作表并向其中上传数据了。为此,我们使用.open方法并选择sheet1

然后我们阅读任何关于熊猫的 CSV 文件。在这种情况下,我将使用一个 CSV 文件,您可以在 Google Drive 或我的 Github 中找到,但是您可以随意使用任何您想要的 CSV 文件。

最后,我们使用.update方法将数据上传到工作表中。

*# Open the spreadsheet*
sheet = client.open("NewDatabase").sheet1*# read csv with pandas*
df = pd.read_csv('football_news')*# export df to a sheet*
sheet.update([df.columns.values.tolist()] + df.values.tolist())

就是这样!现在,您的工作表应该具有与 CSV 文件中相同的数据。你可以在我的 Github 上找到本指南所写的所有代码。

额外收获:用 Python 打开任何 Google 工作表文件

现在是时候打开任何 Google 工作表文件了(即使那些不是由我们的客户电子邮件创建的)。

要做到这一点,打开任何谷歌工作表文件,转到右上角,点击“分享”。

作者图片

将弹出一个新窗口。插入我们之前收到的客户电子邮件(也在我们下载的 JSON 文件中),然后单击“发送”

作者图片

太好了!现在我们有了权限,您可以使用我们之前编写的相同代码打开它。

用 Python 学习数据科学? 通过加入我的 10k+人电子邮件列表,获得我的免费 Python for Data Science 备忘单。

如果你喜欢阅读这样的故事,并想支持我成为一名作家,可以考虑报名成为一名媒体成员。每月 5 美元,让您可以无限制地访问数以千计的 Python 指南和数据科学文章。如果你用我的链接注册,我会赚一小笔佣金,不需要你额外付费。

https://frank-andrade.medium.com/membership

将 VS 代码变成 ML 实验的一站式商店

原文:https://towardsdatascience.com/turn-vs-code-into-a-one-stop-shop-for-ml-experiments-49c97c47db27

作者使用稳定扩散生成的图像

如何在不离开 IDE 的情况下运行和评估实验

近来,生产力面临的最大威胁之一是环境转换。这个术语起源于计算机科学,但应用于人类。它指的是停止一件事情的工作,执行一项不同的任务,然后重新开始最初的任务的过程。

在工作日,您可能想要检查堆栈溢出,例如,为您的项目选择哪种规范化技术。在这样做的同时,您开始研究scikit-learn的文档,看看哪些方法已经实现,以及它们之间的比较。这可能会给你带来一些有趣的关于媒体的比较文章或者 YouTube 上的视频教程。你知道接下来会怎样…

在处理数据时,每当我们需要离开 IDE 并转向浏览器时,潜在的威胁就会出现。它可能是查找我们正在处理的一个棘手的编程问题,或者使用我们选择的平台查看我们的实验结果。不仅在 IDE 中编写我们的实验代码,而且在那里评估和比较它们,这不是很好吗?

在本文中,我将展示如何利用 DVC 的新扩展直接从 VS 代码中运行和评估实验。

VS 代码的 DVC 扩展

你可能听说过 DVC ( 数据版本控制),你知道它是一个类似 Git 的系统,用于版本化你的数据和模型。本质上,它允许我们用 Git 跟踪数据,而不需要实际将数据存储在 Git 存储库中。

你可能没有听说过的是,DVC 也有助于进行人工授精实验。在我们的项目中,我们可能会运行几十个甚至几百个实验,每个实验都与之前的略有不同。随着实验数量的快速增长,DVC 可以跟踪这些实验并将其版本化。然后,它还允许我们比较它们最相关的依赖项(数据、脚本等。)、参数和度量。一旦我们对实验的结果感到满意,我们就可以只将相关的部分提交给 Git。

最近,iterative . ai(DVC 背后的公司)也发布了一个开源的 VS 代码扩展,为最受欢迎的 ide 之一带来了许多非常有用的功能。我将提到对我来说最突出的两个。

首先,该扩展提供了强调可再现性的实验簿记。我们可以快速运行新的实验,跟踪它们的版本(代码、模型、参数、数据等。),并在综合表中比较它们的结果。然后,通过点击一个按钮,我们可以将我们的代码库和工件切换到我们已经运行的任何实验。在下面的 GIF 中可以看到评测表的快速预览。

其次,该扩展扩展了绘图功能。使用 DVC,我们可以使用交互式绘图跟踪、可视化和评估我们的实验性能。例如,我们可以并排显示两条 ROC 曲线或混淆矩阵,以检查哪个实验取得了更好的性能。更好的是,该扩展还提供了某些指标的实时绘图。例如,我们可以在模型仍在被训练时,可视化模型在时期/估计器上的当前损失或验证性能。

在实践中,DVC 扩展扩展了一些选项卡的功能,添加了一个用于运行和评估实验的全新选项卡,并在命令面板中注册了新命令(在 macOS 上,通过cmd + shift + p访问)。我强烈推荐使用DVC: Get Started命令来检查扩展的所有功能。

最后,DVC 扩展目前处于测试阶段,但我们已经可以用它来改善我们的工作流程。

动手示例

问题定义

在这个玩具示例中,我们试图识别欺诈性的信用卡交易。数据集(可在 Kaggle 上获得)可以被认为是高度不平衡的,只有 0.17%的观察值属于正类。

我们将使用随机森林和 LightGBM 分类器,并使用召回率、精确度和 F1 分数来评估它们的性能。

下图显示了使用 DVC 的实验工作流的简化步骤。首先,我们通过调整脚本(例如,不同的数据分割方式、选择不同的预处理步骤、跟踪不同的度量标准等)来设置实验。)或考虑的参数(这包括选择模型、其超参数等。).然后,我们运行项目的管道,即输入数据和最终输出之间的所有步骤。最后,我们评估跟踪的指标并检查潜在的图表。如果我们对结果满意,我们可以在这一步停下来,将更改提交给 Git/DVC,并专注于将一切投入生产。如果我们想继续试验,我们回到第一步,迭代。

下面,我们展示这个项目的代码库的结构:

📦vscode_exp_tracking_with_dvc
┣ 📂.dvc
┣ 📂data
┃ ┣ 📂processed
┃ ┃ ┣ 📜X_test.csv
┃ ┃ ┣ 📜X_train.csv
┃ ┃ ┣ 📜y_test.csv
┃ ┃ ┗ 📜y_train.csv
┃ ┣ 📂raw
┃ ┃ ┗ 📜creditcard.csv
┃ ┣ 📜.gitignore
┃ ┗ 📜raw.dvc
┣ 📂models
┃ ┗ 📜model.joblib
┣ 📂src
┃ ┣ 📂stages
┃ ┃ ┣ 📜data_split.py
┃ ┃ ┣ 📜eval.py
┃ ┃ ┗ 📜train.py
┃ ┗ 📂utils
┃ ┃ ┣ 📜__init__.py
┃ ┃ ┗ 📜params.py
┣ 📜.dvcignore
┣ 📜.gitignore
┣ 📜README.md
┣ 📜dvc.lock
┣ 📜dvc.yaml
┣ 📜metrics.json
┣ 📜params.yaml
┗ 📜requirements.txt

代码库可能看起来有点令人生畏,但它实际上代表了项目结束时的状态。我相信花一分钟时间来检查它是有益的,因为它展示了一个潜在的设置,使得用不同的模型/参数进行实验变得容易。

为了展示整个实验工作流程,我将带你经历我必须遵循的所有步骤,以达到这个最终项目的结构。这样,您可以更容易地为自己的项目重现工作流。

接下来,我建议创建一个新的虚拟环境,安装requirements.txt的内容,并下载 DVC VS 代码扩展。

建立 DVC

在建立了项目的目录后,我们需要初始化 DVC 跟踪。请记住,如果我们正在克隆一个已经初始化了 DVC 的项目,则不需要这一步。出于本教程的考虑,我们假设我们是从零开始这个项目。为此,我们在终端中运行以下命令:

dvc init

运行这个命令会创建 3 个文件:.dvc/.gitignore.dvc/config.dvcignore。然后,我们需要使用 Git 提交这些文件,并将它们推送到我们的存储库。

我们还需要为 DVC 指明一个遥控器,也就是放置对象(数据、模型等)的地方。)将被实际存储和版本化。最简单的解决方案是将它们存储在本地。我们可以使用以下命令来设置它:

mkdir -p /tmp/dvc-storage
dvc remote add local /tmp/dvc-storage
dvc push -r local

自然,我们也可以使用云解决方案作为远程(亚马逊 S3,Azure Blob 存储,谷歌云存储,DagsHub 等)。).

跟踪数据并建立 DVC 管道

下一步,我们从 Kaggle 下载数据,并将 CSV 文件放在data/raw目录中。顾名思义,这是原始数据文件。我们对其应用的所有转换(例如,预处理或分割)将由单独的 Python 脚本执行,对数据集的更改将使用 DVC 进行跟踪。

然后,我们开始追踪数据。为此,我们运行以下命令:

dvc add data/raw

执行该命令会创建一个名为raw.dvc的小文本文件,其中存储了关于如何访问数据的信息。由于文本文件本身很小,我们用 Git 而不是原始数据文件(由 DVC 进行版本控制)对其进行版本控制。用 Git 对raw.dvc文件进行版本控制后,我们再运行一个命令将数据推送到本地远程:

dvc push -r local

现在,我们的原始数据被跟踪使用 DVC。我们可以继续创建 DVC 管道,它存储了项目中执行的所有步骤的信息,包括它们各自的依赖项和输出。

创建 DVC 管道有不同的可能方式。其中之一是使用一组dvc run命令(在终端中执行)并将所有内容指定为这些命令的参数。我在另一个项目中使用了这种方法,你可以在这里阅读更多关于的内容。或者,我们可以直接创建包含完全相同信息的dvc.yaml文件。对于这个项目,我选择了第二种方法。就个人而言,我发现直接创建 YAML 文件比使用 CLI 编写很长的参数化命令更容易一些。

我们几乎处于实验阶段!还有几件事需要澄清。我们的管道由 3 个步骤组成:

  • data_split —该步骤消耗原始数据(位于data/raw目录中)并将其分成训练集和测试集。这些器械包以 CSV 文件的形式存储在data/processed目录中。
  • train —训练我们选择的 ML 模型,并将其作为.joblib文件存储在models目录中。
  • eval —评估模型在测试集上的性能,并将跟踪的指标输出到metrics.json文件。

我们项目的管道如下所示。

stages:data_split: cmd: python src/stages/data_split.py --config=params.yamldeps:- src/stages/data_split.py- data/rawparams:- base- data_splitouts:- data/processedtrain:cmd: python src/stages/train.py --config=params.yamldeps:- src/stages/train.py- data/processedparams:- base- trainouts:- models/model.joblibeval:cmd: python src/stages/eval.py --config=params.yamldeps:- src/stages/eval.py- data/processed- models/model.joblibparams:- base- data_split- trainmetrics:- metrics.json:cache: false

创建文件后,我们运行dvc repro命令来重现整个管道。因为我们以前没有运行过,所以这 3 个步骤将按顺序执行,同时存储和版本化它们的输出。

所有的步骤都是可配置的,也就是说,它们有自己专用的参数集,我们可以在运行实验时调整这些参数。对于训练阶段,这些非常直观,因为它们只是模型的超参数。对于分割阶段,它可以是我们想要用于测试集的数据集的一部分。

参数存储在params.yaml文件中,我们使用argparsepython-box库的组合将它们加载到我们的脚本中。为了简洁起见,我们不在这里讨论这些部分,但是我强烈推荐检查这个项目中的.py脚本,看看一切是如何设置的。

我们的初始params.yaml文件包含以下设置:

base:project: credit_card_fraud_detectiondata_path: data/rawdata_file: creditcard.csvdata_processed_path: data/processedexclude_cols:- Timetarget_col: Classrandom_state: 42data_split:test_size: 0.2processed_path: data/processedtrain:model_type: rfmodel_dir: modelsmodel_path: models/model.joblibparams:n_estimators: 100max_depth: 10class_weight: null

大多数设置都非常简单。但是,值得一提的是,一旦加载了参数,YAML 文件中的null值(用于class_weight超参数)将被解释为 Python 中的None

用 DVC 做实验

在深入实验之前,我认为有必要澄清一下,运行和评估实验并不是 VS 代码扩展的新功能。我们可以使用命令行来执行几乎所有通过扩展可用的任务。这个扩展提供了一个很好的交互式 GUI,我们可以用它来改进我们的工作流程。最后,DVC 和 VS 代码扩展都读取实验的数据文件,如metrics.jsonparams.yaml

首先,让我们探索一下新的 DVC 选项卡。我们可以使用 ide 左侧导航栏上的图标来访问它。我们首先列出选项卡的面板:

  • COLUMNS —在此面板中,我们可以选择哪些列(代表指标、参数、数据/模型版本等。)我们想在实验评估表中看到。
  • EXPERIMENTS —包含已执行实验的列表。
  • SORT BY —我们可以使用此面板来配置实验表的排序方式。
  • FILTER BY —我们可以使用此面板查看/添加/删除应用于评估表的指标和参数过滤器。
  • PLOTS-包含微调绘图仪表板的实用程序。

对于本教程,只有前两个是相关的。您可以在下图中看到它们的预览。

我们另外标记了两个方便的按钮,它们显示实验评估表(1)和图(2)。在本文中,我们只关注表格,我们在下面的截图中展示了它。顺便说一下,我们还可以通过运行dvc exp show来检查终端中的表。

首先,我们解释左侧的实验栏。我已经运行了一些实验,所以表已经被填充了。默认情况下,该表包含工作区(当前代码库)、当前 Git 分支(在本例中为main)以及对该分支的前两次提交。每一个都可以有任意多的实验,这些实验都有各自的名称和 id。对于要在表中注册的实验,我们只需要使用 DVC 运行它(从扩展或命令行),我们不需要将这些实验提交给 Git。

使用默认设置,该表相当大,因为它包含:

  • 实验的 id 和运行日期,
  • 所有请求的指标(来自metrics.json文件),
  • 所有参数(来自params.yaml文件),
  • src目录下文件的版本,数据目录和存储的模型。

由于这很难理解,而且表格也不容易阅读,我们使用COLUMNS面板来隐藏我们知道在本教程中不会改变的列。因此,较小的表格更容易阅读。你可以在下面看到。

要运行新的实验,我们右键单击要修改的载物台。在这种情况下,我们希望从当前工作区创建一个新的实验。下图说明了。

我们选择第一个选项— 修改并运行。点击它会打开一个弹出窗口,在该窗口中我们可以从params.yaml文件中选择我们想要修改的参数。对于这个简单的实验,我们只是将分类器从 Random Forest 改为 LightGBM。

选择参数并单击 OK(或按 Enter)后,会出现另一个弹出窗口。在这里,我们必须输入参数的新值。

按下输入将接受新值,并使用新的参数组运行实验。一些额外的事情值得一提:

  • 如果我们选择了多个要修改的参数,我们必须依次提供新的值,每个值都在一个单独的对话窗口中。由于我们当前正在修改的参数的名称在顶部可见,因此非常直观和简单。
  • 通过 DVC 扩展调整这些参数会用新的参数值修改params.yaml文件。
  • 我们可以通过在终端中运行下面的命令来获得完全相同的结果:dvc exp run --set-param train.model_type=lgbm
  • 不带任何参数运行dvc exp run命令将导致使用默认设置运行实验,即使用params.yaml文件的当前内容。
  • 新模型是单独版本化的,我们可以在最后一列看到它的 ID。

下面,我们可以看到我们的实验结果。我们可以在两个地方看到结果——工作区行和显示在main分支下的实验(因为我们是从那个分支操作的)。

根据该表,我们可以得出一些结论,例如:

  • LightGBM 模型似乎性能严重不足,需要更多的调整,
  • 各种随机森林模型在测试集上达到 100%的精度,
  • 最平衡的模型,即具有最高 F1 分数的模型,是具有平衡类权重的随机森林模型。

我们不会深入评估模型的性能,因为这不是本文的目的。相反,我们提到了该扩展的一个更方便的特性。通过右键单击任何过去的实验,我们可以看到以下选项:

使用这些选项,我们可以轻松地:

  • 将实验的所有更改应用到我们当前的工作区。
  • 使用给定实验的设置创建一个新分支。
  • 通过调整之前执行的任何实验的参数来运行新的实验。
  • 把实验从桌子上拿走。用 DVC 创建的实验是短暂的,也就是说,它们只存储在 DVC 而不是 Git 上(除非我们提交它们)。

这将结束使用新的 VS 代码扩展来创建和评估使用 DVC 的实验的最基本的场景。

附加功能

到目前为止,我们已经介绍了在 VS 代码中使用 DVC 扩展的基本工作流程。然而,该扩展(以及 DVC 本身)提供了许多其他功能,您可能会对您的项目感兴趣:

  • 实验被智能地缓存起来,用完全相同的参数重新运行实验只会导致加载缓存的结果。
  • 这个扩展在绘图方面有很多不错的功能。首先,它可以存储管道特定阶段的输出图,例如,混淆矩阵热图或 ROC 曲线。第二,它具有实时绘图功能,例如,当模型仍在训练时,我们可以看到模型的训练和验证损失。
  • 我们可以创建按顺序运行的实验队列。如果实验运行需要相当长的时间,并且我们不希望在运行下一个实验之前不断地监控第一个实验是否已经完成,那么这个特性就特别有用。
  • 使用简单的脚本,我们可以通过dvc exp run命令创建循环,使用穷举或随机网格搜索来搜索超参数的最佳组合。或者,我们也可以通过组合dvc exp run命令的--set-param--queue参数来使用 CLI。您可以在本文的中找到更多关于网格搜索实现的信息。
  • 通过SORT BYFILTER BY面板,我们可以修改实验表格,使评估更加简单和直观。
  • 我们不局限于在本地使用扩展。我们可以在虚拟机上启动 VS 代码,或者将我们的本地 IDE 连接到任何云环境(GitHub Codespaces、Google Colab 等)。).

外卖食品

在本文中,我们介绍了如何将新的 DVC VS 代码扩展集成到我们的工作流中。使用该扩展,我们可以在不离开 IDE 的情况下轻松运行和评估实验。这样,我们可以通过避免与上下文切换相关的问题来提高我们的生产力。

一如既往,我们非常欢迎任何建设性的反馈。你可以在推特上或评论中联系我。您可以在这个资源库中找到本文使用的所有代码。

喜欢这篇文章吗?成为一个媒介成员,通过无限制的阅读继续学习。如果你使用 这个链接 成为会员,你就支持我,不需要你额外付费。提前感谢,再见!

您可能还会对以下内容感兴趣:

https://medium.com/geekculture/investigating-the-effects-of-resampling-imbalanced-datasets-with-data-validation-techniques-f4ca3c8b2b94

参考

  • 马克,g .,古迪思,d .,&克洛基,U. (2008 年 4 月)。中断工作的代价:更快的速度和更大的压力。在关于计算系统中人的因素的 SIGCHI 会议记录中(第 107-110 页)。
  • https://github.com/iterative/vscode-dvc
  • https://www.kaggle.com/datasets/mlg-ulb/creditcardfraud
  • https://dvc.org/doc

所有图片,除非特别注明,均为作者所有。

在五分钟内将你的网络摄像头变成动画

原文:https://towardsdatascience.com/turn-your-webcam-into-anime-in-five-minutes-6e547b94d45

一个简单有趣的项目的一步一步的教程

Ryan Yao 在 Unsplash 上拍摄的照片

一个关于如何从你的网络摄像头捕捉实时视频并将其转换为动画风格的快速教程。我还将讨论如何为我们的网络摄像头创建一些其他自定义过滤器,以及它们各自的利弊。

本教程不需要任何机器学习知识。

以下所有代码片段和图片均由作者制作。在适用的情况下,代码段中引用了代码参考。

概观

我们将回顾以下步骤:

  • 从你的网络摄像头捕捉输出(使用 CV2)
  • 转换输出(使用 PyTorch)
  • 显示或保存图像(使用 CV2)
  • 讨论其他过滤器和权衡

如果您想跳过教程并快速获得结果:

  • 下载下面名为animeme.py的代码片段
  • 打开终端,导航到该文件的文件夹
  • 运行python animeme.py

履行

依赖关系:

需要以下软件包:

对于我的设置,我有:

pytorch==1.11.0
opencv==4.5.5
pillow==9.1.1
numpy==1.22.4
python==3.9.12

我使用用于 Windows 的 Anaconda 来管理和安装所有依赖项。

拍摄输入图像:

首先,让我们确保您的网络摄像头被检测到,并找到适当的端口号。通常,内置网络摄像头会使用端口 0,但如果您有外置摄像头,它可能会使用不同的端口。

我使用两台摄像机,所以这个脚本返回两个端口:

>>> camera_indexes
[0, 1]

现在我们可以开始捕捉摄像机输出:

这将为您提供摄像机输出的实时反馈。要关闭,按q

网络摄像头直播

转换输出图像

用这条线

_, frame = cam.read(), 

frame现在是来自您相机的输入。在用cv2.imshow()显示它之前,我们将在这里应用我们的变换。

为了将我们的图像转换成动画,我将使用 AnimeGANv2 的 PyTorch 实现。这个回购的模型带有预先训练的重量和易于遵循的指示。

一些重要的细节:

  • cam.read()返回 BGR 格式的 Numpy 数组,而 AnimeGANv2 需要 RGB 类型的图像,cv2.imshow()需要 BGR。因此,我们必须做一些图像预处理和后处理。
  • AnimeGANv2 有四种不同的预训练风格。你可以实验一下,看看你最喜欢哪一个。
  • 如果可能,我们应该在 GPU 上运行 Pytorch 模型,以最大化推理速度。

整个过程是:

-> input frame BGR from cam.read()
-> convert to RGB 
-> apply model(preferrably on GPU) 
-> convert to BGR 
-> display with cv2.imshow()

将所有这些放在一起:

这是结果:

动漫里的直播提要

超越动漫

输入帧的动画样式不是我们唯一的选择。在这一步,我们还可以做许多其他的转换:

-> apply model(preferrably on GPU)

例如,您可以将图像转换为灰度:

得到

灰度实时馈送

或者甚至结合这两个步骤:

我们的漫画现在是黑白的:

灰度动画中的实时提要

一些转换是免费的这里。或者您可以自己编写一个自定义的图像转换。

权衡取舍

你可能会注意到你的网络摄像头的动画版本要慢得多。我们可以添加以下代码片段来衡量模型的性能:

以下是显示 FPS 的输出:

为什么动漫版慢了这么多?

  • 对于每一帧,我们必须向前运行一次 AnimeGANv2。这一步的推断时间明显拖慢了整个过程。
  • 使用默认的网络摄像头输出,我们可以达到 37–40 fps,或 25 毫秒/帧。
  • 使用灰度滤镜,它可以降低到 36–38 fps,大约 27 毫秒/帧。
  • 有了 AnimeGANv2,我们降到了 5FPS,或 200 ms/帧。
  • 对于任何自定义过滤器,如果我们使用大模型进行图像处理,我们将不得不牺牲一些性能。

优化

  • 为了提高推理速度,我们可以优化模型运行时间。一些常见的技术是在 FP16 中运行模型或将模型量化到 INT8。
  • 这些都是相当复杂的技术,不在本文讨论范围之内。
  • 我们可以减小网络摄像头输入的大小。但是你的直播也会变小。
  • 我们可以买一个更强的 GPU(我目前有一个 GTX 3070 用于我的设置)。

最后重要的一点是:

通常情况下,一本漫画书会有彩色封面和灰度内容,因为黑白印刷更便宜。但在我们的设置中,彩色帧实际上生产起来更便宜,因为我们不必转换成灰度,然后再转换回来。

结论

这是一个关于如何捕捉你的网络摄像头的输出,并将其转换为动画风格,或其他过滤器的快速教程。

我觉得这个项目很有趣,也很容易实施。即使你之前没有机器学习或计算机视觉的经验,你也会看到利用最先进的深度学习模型来创造一些令人着迷的东西是多么容易。

希望你会喜欢跟随本教程,并能够创造一些难忘的时刻!

资源

animegan v2:https://github.com/TachibanaYoshino/AnimeGANv2

AnimeGANv2,PyTorch 实现:https://github.com/bryandlee/animegan2-pytorch

使用 OpenCV 捕获网络摄像头视频:https://www . geeks forgeeks . org/python-OpenCV-capture-video-from-camera/

火炬视觉改造:【https://pytorch.org/vision/0.9/transforms.html

如果你愿意支持 Thu,你可以考虑注册成为一名中等会员。每月支付 5 美元,你就可以无限制地阅读媒体上的故事。如果你使用他的推荐链接,Thu 也会得到一小笔佣金。

https://medium.com/@tdinh15/membership

教程—从零开始的基本 Kubeflow 管道

原文:https://towardsdatascience.com/tutorial-basic-kubeflow-pipeline-from-scratch-5f0350dc1905

来源:Hanna Morris 在 Unsplash 上拍摄的照片

Kubeflow 是一个机器学习工具包,便于在 Kubernetes 上部署机器学习项目。尽管最近,Kubeflow 越来越多地出现在科技公司的堆栈中,由于项目档案的稀缺,新手开始使用它可能会非常困难。

即使 Kubeflow 的文档远不缺乏,但当您从头创建机器学习管道时,有一只援助之手总是有帮助的。我会尽我所能成为那只援助之手。

在本指南中,我们将介绍建立一个正常运行的管道所需的每一个步骤。您将学习如何:

  • 创建一个 Kuberneter 集群
  • 安装 Kubeflow
  • 创建容器注册表
  • 构建一个容器映像,并将其推送到您的注册表中
  • 让 Kubeflow 访问您的 S3 桶
  • 用输入和输出工件创建 Kubeflow 组件
  • 创建一个 Kubeflow 管道,上传并运行它

AWS —弹性 Kubernetes 服务

在这里,我将重点关注使用 AWS EKS 从零开始创建一个管道,这是一个创建 Kubernetes 集群的服务,就像谷歌的 GKE 。

创建一个 EKS 集群并安装 Kubeflow

虽然您可以通过在搜索栏上键入“EKS”并选择“弹性 Kubernetes 服务”来从 AWS 管理控制台创建 EKS 集群,但我强烈建议您遵循本 Kubeflow 指南 ,其中包括集群创建步骤,直接从您的计算机终端的命令行完成。遵循这一点将确保您的集群具有正确的设置,以便稍后安装 Kubeflow。

确保不要错过以下先决条件:

  • 安装 kubectl
  • 安装 AWS 命令行界面 (CLI)
  • 通过运行以下命令配置 AWS CLI:aws configure
  • 输入您的访问密钥(访问密钥 ID 和秘密访问密钥)。
  • 输入您的首选 AWS 区域和默认输出选项。
  • 安装 eksctl 和 aws-iam 认证器。

请不要犹豫更改您的 AWS 区域(如“eu-west-1”)并选择更强大的 EC2 实例。我个人选择的是 p2.xlarge 实例。
例如,我的配置是:

export AWS_CLUSTER_NAME**=**kubeflow-demo
export AWS_REGION**=**eu-west-1
export K8S_VERSION**=**1.18
export EC2_INSTANCE_TYPE**=**p2.xlarge

截至 2022 年 1 月,Kubeflow 无法在最新版本的 Kubernetes 上正确安装。我强烈建议保持 K8S_VERSION 为 1.18。

如果您已经按照指南阅读了“访问 Kubeflow central dashboard”一章,那么命令kubectl get ingress -n istio-system应该会返回一个如下所示的地址:
123-istiosystem-istio-2af2-4567.us-west-2.elb.amazonaws.com
将这个地址复制并粘贴到您的浏览器中。如指南中所述,如果您正在使用基本身份验证,则凭证是您在配置文件中指定的凭证,或者是默认凭证(admin@kubeflow.org : 12341234)。

您现在应该可以访问您的 Kubeflow 中央仪表盘:

Kubeflow 中央仪表盘。作者图片

创建容器注册表

使用 Kubernetes 意味着使用容器图像,所以让我们离开仪表板,创建一个容器注册中心。

回到你的 AWS 管理控制台,在搜索栏中输入“ECR”并点击“弹性容器注册”。从那里,选择左侧栏上的“存储库”,然后单击“创建存储库”。

ECR 菜单。作者图片

创建后,一个新的存储库将出现在您的菜单上。请注意,它有一个类似这样的 URI:
<ID>.dkr.ecr.<REGION>.amazonaws.com/<REGISTRY_NAME>
其中< ID >是您的帐户 ID,< REGION >是您的注册表所在的区域(例如 eu-west-1),而< REGITSTRY_NAME >是您在创建注册表时给它起的名字。

每个存储库都有一个 URI,你放在上面的每个图像也是如此。这是我们在本文后面研究组件创建时需要的链接。

检查您的集群的工作节点是否可以访问 ECR

在 AWS 管理控制台上转到 EKS,选择“集群”。单击您之前创建的群集,转到“配置”选项卡,然后转到“计算”子选项卡:

您应该会看到这样的节点组。作者图片

单击您的节点组,在“详细信息”选项卡上,单击“节点 IAM 角色 ARN”。这将打开节点的 IAM 管理控制台。请确保附加了“amazonec 2 containerregistryreadonly”策略。如果没有,请单击“附加策略”进行添加:

作者图片

安装 Docker

如果您已经安装了 Docker,请转到下一部分。如果没有,进入 Docker 主页,安装 Docker 桌面。

在 Linux 或 Mac 上,最简单的方法是在终端上键入命令
brew install Docker

创建一个容器

在您的计算机上,创建一个文件夹,您将在该文件夹中工作。如果你想对你的作品进行版本控制,它可以是一个 GitHub 存储库文件夹。

创建一个文件,命名为“Dockerfile”。在 linux 和 mac 上,您可以为此键入touch Dockerfile命令。用任何你想要的 IDE 打开它,甚至是记事本(Windows)或文本编辑(Mac)。

在 docker 文件中,我们将声明一个父映像(这里我将使用 ubuntu:20.04),安装 pip,然后安装一些运行脚本所需的包。我们的 docker 文件将如下所示:

FROM ubuntu:20.04
RUN set -xe \&& apt-get update -y \&& apt-get install -y python3-pip \&& apt install -y python-is-python3
RUN pip3 — no-cache-dir install numpy scikit-learn pandas boto3

现在用这个命令构建一个图像,并把<image_name>改成你想给图像起的任何名字。不要忘记结尾的点!</image_name>

docker build -t <image_name> .

将您的映像推送到您的容器注册表中

检索一个认证令牌,并向您的注册表认证您的 Docker 客户端。使用 AWS CLI:

aws ecr-public get-login-password --region <REGION>| docker login --username AWS --password-stdin <ECR_URI>

更改为您的存储库的区域(例如 eu-west-1),并且<ecr_uri>应该是这样的:
<ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com用于私有注册中心,
public.ecr.aws/<REGISTRY_ID>/用于公共注册中心</ecr_uri>

然后,标记您的映像,以便您可以将映像推送到此存储库:

docker tag <image_name>:latest <ECR_URI>/<REGISTRY_NAME>:latest

运行以下命令将该映像推送到新创建的 AWS 存储库:

docker push <ECR_URI>/<REGISTRY_NAME>:latest

如果您有任何问题,您可以通过点击“查看推送命令”在注册表的菜单上获得所有命令

作者图片

在 S3 上设置测试数据集

我们的基本管道将从从 S3 下载压缩数据集开始。
我们将使用这个 kaggle 数据集作为例子,但是任何压缩的 csv 文件都可以。

在 AWS 管理控制台上,在搜索栏上键入“S3”以访问该服务,然后单击“创建存储桶”。一旦创建,点击你的桶,上传你的 zip 文件。

让库伯弗洛进入 S3

回到你的 Kubeflow 中央仪表盘,点击“笔记本服务器”,然后点击“新服务器”。这可能需要几分钟时间。

创建完成后,单击“Connect”打开 Jupyterlab 并打开一个终端实例。
键入aws configure并键入您在步骤 1 的先决条件中所做的凭证,以便从您的 Kubeflow 实例登录到 aws。这将使您能够从脚本中访问 S3 存储桶。

测试你是否可以访问 S3

在 Jupyterlab 上,打开一个新笔记本。

在这个代码片段中,<bucket_name>是您的存储桶的名称。
如果您运行此单元格,您应该会看到您的 zip 文件,以及任何其他子文件夹中的任何其他文件。如果您想要浏览特定的子文件夹,请将第三行更改为:
contents = conn.list_objects(Bucket=<bucket_name>, Prefix=<subfolder_name>)['Contents']</bucket_name>

创建 Kubeflow 管道

我们将做一个简单的管道,从我们的 S3 桶中下载我们的 zip 文件,将解压后的 csv 文件上传到桶中,并读取其中一个包含熊猫的数据集

第一步。创建 python 函数

我们将创建解压缩函数:

然后是阅读器功能:

在我们的管道中,“csv_s3_reader()”的“csv_path”参数将是“unzip_func()”的输出字符串。
您现在可以测试您的功能是否正常。

第二步。将 python 函数重新格式化为组件函数

为了让 Kubeflow 能够理解它们,我们将这些函数重写如下:

如您所见,您需要为函数声明每个参数的类型。由于 Kubeflow 管道处理工件,而不是返回一个对象,我们的行为就像我们在磁盘上写它一样,只是我们给出的不是路径,而是一个 comp。输出路径()对象。

例如,如果你想写一个熊猫数据帧到你的计算机上,你应该写df.to_csv(path_name)。在 Kubeflow 中,这变成了df.to_csv(output_path_object),其中output_path_object在函数的参数中定义为comp.OutputPath(‘CSV')

您可以用同样的方式处理 Kubeflow 输入,方法是读取comp.InputPath()对象,就好像它们是写在您的磁盘上一样。

您可能已经注意到这些函数内部有导入,我们在讨论组件时很快就会谈到这一点。

第三步。将功能转换为组件

您可以用kfp.components.create_components_from_func从一个函数创建一个组件,我们将向它传递两个参数:要转换的函数和调用组件时应该运行的基本映像:

这就是为什么一些包被导入到函数中的原因。例如,boto3并不像kfp那样安装在 Kubeflow 的环境中。所以过早导入会返回错误;它应该被导入到函数中,因为它将在安装了boto3的容器映像上运行。

第四步。创建管道

管道描述了要调用的一系列组件和传递它们的参数。它采用一个带有kfp.dsl.pipeline()
装饰器的函数的形式。

第五步。完整管道

整个管道应该是这样的。复制并粘贴到 Jupyter 笔记本单元格中,不要忘记根据您的桶名和容器图像更改某些行 URIs:

%%writefile ./test_pipeline.py行意味着运行该单元会将该脚本保存为您当前目录中的 test_pipeline.py。

然后,使用 jupyter 笔记本上的以下命令将 python 管道转换为 YAML:

%%sh
dsl-compile --py test_pipeline.py --output test_pipeline.yaml

上传管道至库贝弗洛

在库贝弗洛的中央仪表板上,转到“管道”并单击“上传管道”

管道创建菜单。作者图片

给你的管道一个名称和一个描述,选择“上传一个文件”,并上传你新创建的 YAML 文件。点击“创建”。您应该在管道列表中看到您的管道。选择它,您将看到如下图:

管道摘要。作者图片

运行管道

点击“创建运行”。您将需要选择或创建一个实验来运行您的管道。您还需要指定管道的参数,这些参数是您在unzip_and_read_pipeline函数中定义的。

请注意,字符串参数的指定必须不带引号my_bucket_name将被正确处理,而'my_bucket_name'不会!

启动菜单。作者图片

开始你的实验。

如果一切顺利,运行成功,您将看到结果屏幕:

实验总结。作者图片

如果您点击任何任务,您将能够看到它的日志、输入参数以及输入和输出工件。

这里,第二个任务不是很有用,但是您可以添加一些数据预处理说明来返回一个清理过的 csv 文件。

就在那里!一个基本的库贝弗洛管道!

通常,您希望避免将所有的功能都写在 jupyter 笔记本中,而是将它们放在 GitHub 存储库中。

GitHub Actions 允许您构建一个容器映像并将其推送到 ECR,ECR 包含您的所有脚本,您可以在以后构建管道时导入这些脚本。

教程:面向数据——15 分钟内的 Python 通过数据/逻辑分离降低复杂性

原文:https://towardsdatascience.com/tutorial-data-oriented-python-in-15-minutes-less-complexity-through-data-logic-separation-b5ee00518772

一个 3 步教程,教你 Python 面向数据方法的三个主要概念

面向数据的代码让一切变得更简单、更快乐。图片由作者提供。

你听说过面向数据的编程方法吗?如果没有,现在你有了。本教程是为了让你对它感到兴奋,用 Python 编写的。

我们要做一些非常简单的事情:

1.我们将看看一个常见的虹膜分类问题,我已经通过编写两个 Python 类为您“解决”了这个问题。

2.然后我们将尝试在稍微不同的上下文中应用这些类,并重用它们。

3.这不会很顺利,所以我们将使用 面向数据的方法 把我的几个类变成很酷的面向数据的 Python 代码。

你可以跟着做,说明在下面和 GitHub 上。

第一步

标准 Python 代码转变为面向数据的 Python 代码的最简单示例如下:

# NON DO VERSION-----------------------------------class data(): X = 1 def add(self): self.X = X+1 ## DO version--------------------------------------class data(PRecord): ## (1) Using a base type PRecord = a map/dict in Python X = field() ## (2) Using an immutable type from pyristent def add(data): ## (3) Separating the logic (add) from the data return data.set(X=data.X+1) ## (2) return a new data piece, as data is immutable!

三个简单的步骤,听起来很简单,对吗?让我们简单地看一下我们在这里应用的这三个想法。

简而言之,面向数据的方法

面向数据的方法被称为面向数据的编程(DOP)或面向数据的设计(DOD)。国防部从游戏设计领域走出来,目标是让数据计算和游戏运行得更快。DOP 的人显然意识到 DOD 中用于游戏的想法在大多数数据密集型环境中有完全不同的意义!奇怪的是,使用的方法实际上是不同的。所以我在这里写的是以下的想法:

想法很简单:我们将数据与业务逻辑分离。我们通过使用通用结构并保持数据不变的方式来实现这一点。通过这样做,我们解耦了系统的各个部分,从而降低了整个系统的复杂性。(用我自己的话说)

这里的关键成分是:

  • 分离数据,使其成为“一等公民”。许多编程语言事实上已经有了“数据类”,这些数据类只保存数据,其他什么也不保存。
  • 我们使用通用结构,像映射和数组,因为它们已经自带了像“打印”或“排序”这样的内置方法,不需要我们编写它们。
  • 我们保持数据不可变,因为可变性产生了复杂性。没必要问“x=2 吗?或者在代码的这一点上 x=3?”。

所有这些因素都旨在通过让数据成为一等公民来降低复杂性。现在,让我们动手实践一下吧!

快速安装指南

转到 Github,克隆存储库“DOP-Example”并运行

./batect dev 

这将打开一个 Jupyter 笔记本服务器,正确的笔记本已经加载,都在 Docker 中。打开“DO-Tutorial,ipynb”,你就可以跟着做了。

编写第一个面向数据的 Python

我编写了两个简单的类和一段 Python 代码,它们完成了以下任务:

  • ML_Dataset 可以加载并保存我的虹膜数据集,

  • ML_Predictor 可将 SVM 拟合到 ML_Dataset,预测单个结果,并将孔输入矩阵的预测写入 ML_Dataset

  • 程序代码创建 ML_Dataset,ML_Predictor,拟合预测器,将新生成的测试空间的预测写入数据集中,最后运行一个测试预测。

下面是这两个类的 Python 代码。

#### — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — ### These are our two machine learning classes. We wrapped stuff into classes to possibly extend this and make it more reusable…**class** ML_Dataset(): “”” Should hold our data, targets, predictions, basically X and y… Should also be able to fill itself, once we need it. “”” X **=** **None** y **=** **None** y_pred **=** **None** **def** load_iris(self): iris **=** datasets**.**load_iris() self**.**X **=** iris[“data”][:,3:] # petal width self**.**y **=** (iris[“target”]**==**2)**.**astype(np**.**int) **class** ML_CLF(): “””Should hold the classifier we want to use here, should also be able to fit and predict. “”” clf **=** **None** **def** fit_clf(self, m: ML_Dataset): self**.**clf **=** svm**.**SVC(gamma**=**’scale’, decision_function_shape**=**’ovo’, probability**=True**) self**.**clf**.**fit(m**.**X,m**.**y) **def** predict(self, X): **return** self**.**clf**.**predict(X) **def** write_preds(self,m): “””Writes predictions into an ML_Dataset using this classifier””” m**.**y_pred **=** self**.**clf**.**predict_proba(m**.**X)

这是我的小程序的样子:

*#### — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —* *### Our running program. Yes, we’re still exploring stuff here.**# create our dataset, load stuff into it.*m **=** ML_Dataset()m**.**load_iris()*# create our classifier and fit it to the dataset*c **=** ML_CLF()c**.**fit_clf(m)*# let’s try it out, into a new dataset to predict on into our dataset, then write our prediction in it and print them out…*m**.**X **=** np**.**linspace(0,3,1000)**.**reshape(**-**1,1)c**.**write_preds(m)print(m**.**y_pred)print(“individual predictions: “, c**.**predict([[1.7],[1.5]]))

在这种特殊的情况下,这样做很好。现在让我们通过首先交换数据集来重用代码。

Pt 1 分离数据和代码

让我们将数据集换成不同的数据集。我放置了业务逻辑,将 iris 数据集加载到保存数据的类中。因此,我们需要改变这个类或者创建一个新的类来将我们的数据集换成一个不同的类。我们可以这样做,它看起来像这样:

*#### — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —* *# Option 1, write a new class***class** ML_Dataset_2(): “”” Should hold our data, targets, predictions, basically X and y… Should also be able to fill itself, once we need it. “”” X **=** **None** y **=** **None** y_pred **=** **None** **def** load_different_dataset(self): self**.**X **=** np**.**array([[1],[2],[3],[4],[5]]) self**.**yy**=**np**.**array([1,0,0,1,1])*# Option 2, change the class***class** ML_Dataset(): “”” Should hold our data, targets, predictions, basically X and y… Should also be able to fill itself, once we need it. “”” X **=** **None** y **=** **None** y_pred **=** **None** **def** load_iris(self): iris **=** datasets**.**load_iris() self**.**X **=** iris[“data”][:,3:] *# petal width* self**.**y **=** (iris[“target”]**==**2)**.**astype(np**.**int) **def** load_different_dataset(self): self**.**X **=** np**.**array([[1],[2],[3],[4],[5]]) self**.**y **=** np**.**array([1,0,0,1,1])

但是每次我想交换东西的时候都要写一个新的类,这听起来是有问题的。让我们试试面向数据的方法,把数据从“代码”中分离出来。为此,编写一个类来保存数据 X,y,y_pred,并编写一个函数将数据加载到数据类中。最后,我们编写第二个函数,将新数据加载到数据集中。可能是这样的:

*### THE DO Approach***class** ML_Dataset(): “”” Should hold our data, targets, predictions, basically X and y… Should also be able to fill itself, once we need it. “”” X **=** **None** y **=** **None** y_pred **=** **None****def** load_iris(m: ML_Dataset): iris **=** datasets**.**load_iris() m**.**X **=** iris[“data”][:,3:] *# petal width* m**.**y **=** (iris[“target”]**==**2)**.**astype(np**.**int)*## load old data*m **=** ML_Dataset()load_iris(m)*## load something new***def** load_new(m: ML_Dataset): m**.**X**=**np**.**array([[1],[2],[3],[4],[5]]) m**.**y**=** np**.**array([1,0,0,1,1])load_new(m)

这对我来说已经很容易理解了。让我们看看另一个问题,我们的数据的可变性。

Pt 2 不可变数据

让我们打印出目前为止使用的数据:

## OLD Approachm **=** ML_Dataset()load_iris(m)*## load something new***def** load_new(m: ML_Dataset):…## print out our datasetprint(m**.**X)

注意 m.X 的内容是如何变化的。我们不能简单地做一个 print(m.X),print(m_old_X),因为数据确实在变化。在这种情况下,我们必须先打印,然后再修改。这很好,但是让我们来看看面向数据的方法,使用不可变数据的方法。

对于不可变数据,我们使用 Python 包 pyrsistent ,并通过导入“from pyrsistent import field,PRecord”将我们的类重新创建为具有不可变字段的类。然后,我们通过向数据集加载新数据来创建数据集的新版本。这可能看起来像这样:

*### NEW DOP Implementation**# We’re using the module for immutable python pyrsistent here***class** ML_Dataset(PRecord): X **=** field() y **=** field() y_pred **=** field() X_new **=** field()**def** load_iris(): iris **=** datasets**.**load_iris() r_1 **=** ML_Dataset() r_2**=**r_1**.**set(X**=**iris[“data”][:,3:]) r_3**=**r_2**.**set(y**=**iris[“target”]) **return** r_3*## Here’s how we use it*r **=** load_iris()r_2 **=** r**.**set(X **=** np**.**linspace(0,3,1000)**.**reshape(**-**1,1))## Just making sure this stuff is actually immutable!r**.**set(X**=**”1")*# print(r.X)**# >> [0\. ]…… [0.01201201]**## Oh that doesn’t work, let’s try direct item assignment maybe?*r[“X”]**=**1*## >> TypeError: ‘ML_Dataset’ object does not support item assignment**# Nice, so now this cannot happen accidentally.*

太好了,Python 现在阻止我覆盖数据,我现在基本上有了一个版本化的数据集。让我们看看第三步,使用通用数据结构的想法。

Pt 3 通用数据结构

我想到了给我的分类器对象添加“注释”的想法,这样我就可以留下类似“试验 1,用 params x,y,z 进行试验”的注释。在我的旧代码中这样做,看起来会像这样:

*#### — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —* *### These are our two machine learning classes. We wrapped stuff into classes to possibly extend this and make it more reusable…***class** ML_CLF():“””Should hold the classifier we want to use here,should also be able to fit and predict. “”” clf **=** **None** trial_note **=** “trial 1” **def** fit_clf(self, m: ML_Dataset): self**.**clf **=** svm**.**SVC(gamma**=**’scale’, decision_function_shape**=**’ovo’, probability**=True**) self**.**clf**.**fit(m**.**X,m**.**y) **def** predict(self, X): **return** self**.**clf**.**predict(X) **def** write_preds(self,m): “””Writes predictions into an ML_Dataset using this classifier””” m**.**y_pred **=** self**.**clf**.**predict_proba(m**.**X)

让我们创建一个分类器并打印出来,看看我的笔记是什么样的:

*#### — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —* *### Now let’s create another classifier.**# Let’s print it out to see the comment*c **=** ML_CLF()print(c)*# <__main__.ML_CLF object at 0x7f7b102cbdf0>*

这不是我所期望的。我们可以通过提供 str 方法来解决这个问题。但是面向数据的方法采取了不同的路线。让我们像使用字典一样使用泛型类来使用字典的内置函数,如 print()或 item()。我们仍然希望保持它不变,所以我们使用来自 pyrsistent 的预编码,它本质上是一个字典。如果我们这样做,我们的代码可能如下所示:

**class** ML_Predictor(PRecord): clf **=** field() note **=** field()**def** predict_stuff(m: ML_Predictor, d: ML_Dataset): m_2 **=** m**.**set(clf**=**m**.**clf**.**fit(r**.**X,r**.**y)) *# Al right! Now we got…* *# — m, as the initialized and unfitted CLF* *# — m_2 as the fitted predictor. From the outside, not easy to   distinguish…* d_2 **=** d**.**set(y_pred **=** m_2**.**clf**.**predict_proba(d**.**X_new)) **return** d_2*### Our Program — — — — — — — — — — — — — — — — — — — — — — — — — —* r **=** load_iris()r_2 **=** r**.**set(X_new **=** np**.**linspace(0,3,1000)**.**reshape(**-**1,1))c **=** ML_Predictor(clf**=**svm**.**SVC(gamma**=**’scale’, decision_function_shape**=**’ovo’, probability**=True**), note**=**”This is trial 1; using SVC”)c_2 **=** c**.**set(clf**=**svm**.**SVC(gamma**=**’scale’, decision_function_shape**=**’ovo’, probability**=True**, kernel**=**’poly’), note**=**”This is trial 2\; using SVC with polyn. kernel”)print(c)print(c_2)print(c**.**items()) *# we can use all the default cool functions on this generic Map!```

耶!为了使它完整,我还从之前的类中删除了业务逻辑。现在我们有了一段很好的可重用代码,使数据成为一等公民,更容易理解,也更容易模块化。

就这样,您已经学到了 Python 面向数据方法的第一课。让我们回顾一下。

概述

  • 面向数据的 Python 让数据成为一等公民
  • 至于降低复杂性
  • 它将数据从业务逻辑中分离出来
  • 它使用通用数据结构
  • 它只使用不可变的数据

如果你感兴趣,我强烈推荐你继续阅读这种方法。

接下来读什么

遗憾的是,我没有任何特定于 Python 的资源。但是我有资源。

  • 有一本名为《面向数据编程》的书,由曼宁出版,作者是耶霍纳坦·沙尔维特。他还在自己的网站上免费发布了大量内容。
  • 然后有一本书更针对面向数据的游戏设计社区。理查德·费边称之为“面向数据的设计”。这本书在他的网站上也有在线版本。
  • 还有一个 Github 库包含了很多还没有提到的信息,包括视频等等,所以也来看看吧。
  • 我发现一篇关于面向数据的 Python 的文章解释得很好,即使我们依靠 NumPy 来处理像“指针优化”这样的大部分单词,我们仍然可以从中获得 5-10 倍的速度优势。

对如何建立伟大的数据公司、伟大的数据密集型产品、成为伟大的数据团队感兴趣,还是对如何利用开源构建伟大的东西感兴趣?然后考虑加入我的 免费简讯《三个数据点周四》 。它已经成为数据初创企业、风投和数据领导者值得信赖的资源。

Python Azure 函数教程

原文:https://towardsdatascience.com/tutorial-of-python-azure-functions-81949b1fd6db

由 Unsplash 上的 Element5 数码拍摄

将您的代码作为 API 发布,无需传统的服务器设置

介绍

Azure functions 是一个“无服务器解决方案”,这意味着你不必担心传统的服务器设置。它可以用于各种场景,包括 a)构建 web API,b)运行计划任务,c)响应数据库更改等事件。在本教程中,我们将使用 Azure 函数将应用程序发布为 web API。

本教程中描述的示例应用程序的发布版本(图片由作者提供)。

环境配置

如果你之前没有使用过 Azure 函数,那么最耗时的步骤就是环境设置。我建议进行以下设置:

  1. 具有有效订阅的 Azure 帐户。你可以免费创建一个账户。
  2. Azure 功能核心工具版本 3.x。
  3. Python 版。
  4. Visual Studio 代码在一个支持的平台上(看一下支持的平台)。
  5. Visual Studio 代码的 Python 扩展。
  6. Visual Studio 代码的 Azure 函数扩展。

如您所见,有六个主要步骤可能会占用您几个小时的时间。幸运的是,你只需要做一次。

初始结构

我们从已经有工作应用程序的州开始这个项目。我们的应用程序告诉您,在过去的 7 天里,波罗的海地区的最高电价是在哪里、什么时候、什么价格( 查看实时应用程序 )。

项目的初始结构由包含最高价格计算函数的calculator.py文件和定义两个库的requirements.txt文件组成:requestsazure-functions

初始项目结构(图片由作者提供)。

在本地创建 Azure 函数

要开始将我们的代码包装成 Azure 函数,我们首先需要创建本地 Azure functions 项目。遵循以下步骤(或微软的详细说明):

  1. 在活动栏中选择 Azure 图标。然后在工作区(本地)区域,选择+按钮,在下拉列表中选择创建函数。出现提示时,选择创建函数。
  2. 选择项目工作环境的目录位置,然后选择“选择”。
  3. 根据提示提供以下信息:
    3.1 选择一种语言:Python
    3.2 选择 Python 解释器创建虚拟环境。
    3.3 为您的项目功能选择模板:选择HTTP trigger
    3.4 提供函数名:输入my-calculator
    3.5 授权级别:选择Anonymous,可以让任何人调用你的函数端点。
    3.6 选择您希望如何打开您的项目:选择Add to workspace.

Visual Studio 代码自动创建必要的文件,我们的项目目录现在将具有如下图 2 所示的结构:

启动 Azure 功能前(图 1)和后(图 2)的项目结构(图片由作者提供)。

将您的代码与 Azure 函数链接起来

目前,Azure function backbone 已经创建,但我们还没有将我们的应用程序链接到它。

打开my-calculator文件夹内的__init__.py文件。该文件包含将作为 Azure 函数运行的main函数。目前,该文件中充满了示例代码(如下图 1 所示)。为了运行我们的计算器,我们对文件进行了以下修改:

  1. 导入您的函数(在我们的例子中from calculator import highest_price_function
  2. 导入 json 库,将字典转换成 JSON 格式(REST APIs 的标准输出格式)。
  3. 用自己的逻辑替换main函数里面的代码:
    a .运行函数,保存结果。
    b .将结果(我们的结果是一个字典)转换成 JSON。
    c .返回 JSON 结果。

已修改 init。py 文件将您的代码包装在 Azure 函数中(图片由作者提供)。

在本地测试该功能

要在本地启动该功能,请按 F5 或播放图标。如果一切顺利,您将在终端窗口中看到端点的 URL:

在本地启动该功能后,您将在终端窗口中找到该功能的 URL(图片由作者提供)。

复制此 URL 并将其粘贴到我们的浏览器中,或者只需在 VS 代码中单击它:

本教程中描述的示例应用程序的发布版本(图片由作者提供)。

将该功能部署到 Azure cloud

我们现在准备将该功能部署到 Azure。遵循以下步骤(或微软官方指令):

  1. 登录 Azure(如果需要),
  2. 打开 Azure 扩展,选择创建资源,搜索“创建功能 App ”。
  3. 填写提示:
    3.1 选择订阅—选择订阅(如果只有一个订阅就看不到这个了)。
    3.2 输入全局唯一名称—输入函数的名称。
    3.3 选择运行时堆栈—选择您在本地机器上使用的语言版本。
    3.4 选择新资源的位置——选择您附近的某个地区。
  4. 在 Azure 扩展的工作区部分,选择项目文件夹并点击 Deploy

该应用程序现已发布,您可以与任何有兴趣使用它的人共享它( 查看本教程的实际应用 )。

摘要

总的来说,Azure functions 绝对是一个值得考虑的工具,当你想与世界分享你的数据应用程序的原型时:

  1. 文档&教程 —非常好
  2. 初始设置复杂性 —中等
  3. 开发复杂度 —中低
  4. 部署复杂性 —低
  5. 授权可能性 —高
  6. 授权复杂度 —低
  7. 速度&可扩展性 —高水平
  8. 监控 —高功能开箱即用
  9. 定价 —几乎免费(用于原型制作)

你可以从这篇文章中读到我对 Azure 功能的全面评测。玩得开心!

调整模型以降低错误预测

原文:https://towardsdatascience.com/tweaking-a-model-for-lower-false-predictions-37d3bf028a3f

调整您的模型,仅进行更高确定度的分类

照片由 Kajetan Sumila 在 Unsplash 上拍摄

介绍

当创建分类模型时,许多算法提供函数predict_proba()来给我们观察值被分类到每个类别下的概率。因此,通常会看到这样的输出:

[0.925, 0.075]

在前面的例子中,模型有 92.5%的把握认为观察值属于类 0,而只有 7.5%的机会来自类 1。

因此,如果我们请求同一个模型使用predict()函数给我们一个二元预测,我们只会得到一个[0]作为结果,对吗?

在本例中,我们最有可能不希望模型预测观察值为类 1 ,因为它只有很小的可能性。但是,假设我们对另一个观测值进行了预测,结果如下:

[0.480, 0.520]

现在怎么办?

当然,来自许多模型的粗略预测会给我们结果[1]。但这是最好的决定吗?有时候,是的。其他时候,没有那么多。

在这篇文章中,我们将学习如何使用 Python 中的catboost包,根据我们理解的用例可接受的假阳性【FPR】或假阴性率【FNR】的数量,为我们提供分类的最佳阈值。

数据科学背景

为了将这篇文章放在上下文中,让我们理解为什么我们想要将阈值从默认的 50%削减更改为另一个数字。

我们最好的例子来自医疗保健行业。我们知道,许多实验室检查、诊断和医学测试依赖于机器学习来帮助专家得出最精确的答案。毕竟在这个行业,每一个百分点都关系到一个人的一生。

所以,假设我们正在利用数据来诊断乳腺癌。与利益相关者交谈后,我们达成一致,我们希望我们的模型最多给出 1%的假阴性。我们想非常确定一个人是健康的,说它是乳腺癌阴性。如果有疑问,我们会将其归类为阳性,并建议进行第二次检查或不同的确认测试。

正如你可能已经得出的结论,这样做我们会降低模型的准确性,因为我们会增加假阳性的数量,但这是可以接受的,因为这个人总是可以再次检查并进行其他检查来确认这是不是真阳性。另一方面,我们不会遗漏任何患有该疾病并收到阴性结果的人。

照片由 Towfiqu barbhuiya 在 Unsplash 上拍摄

编码

你可以在我的 GitHub 资源库中找到这个练习的完整代码,这里是。

https://github.com/gurezende/Studying/tree/master/Python/CatBoost

要安装catboost,请使用pip install catboost。下面列出了一些需要进口的产品。

# Basics
import pandas as pd
import numpy as np
# Visualizations
import plotly.express as px
# CatBoost
from catboost import CatBoostClassifier
from catboost import Pool
# Train test
from sklearn.model_selection import train_test_split
# Metrics
from sklearn.metrics import confusion_matrix, f1_score

资料组

要使用的数据是著名的玩具数据集乳腺癌,来自sklearn

# Dataset
from sklearn.datasets import load_breast_cancer# Load data
data = load_breast_cancer()# X
X = pd.DataFrame(data.data, columns=data.feature_names)
# y
y = data.target

您可能知道也可能不知道,这个数据集已经准备好了。在建模之前,没有太多需要探索或转换的地方。这不是我们的目的,所以我会继续讲代码。

列车测试分离

让我们把数据分开来进行训练和测试。

# Train test split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)print(f'Train shapes: {X_train.shape} | {y_train.shape}')
print(f'Test shapes: {X_test.shape} | {y_test.shape}')Train shapes: (455, 30) | (455,)
Test shapes: (114, 30) | (114,)

第一个模型

接下来我们用CatBoostClassifier训练第一个模型。

# Creating a Pool for training and validation sets
train_pool = Pool( data=X_train, label=y_train)
test_pool = Pool( data=X_test, label=y_test)# Fit
model = CatBoostClassifier(iterations=500)
model.fit(train_pool, eval_set=test_pool, verbose=100)

在序列中,这里是 F1 得分: 97%

# Predict
preds = model.predict(X_test)
f1_score(y_test, preds)0.971830985915493

非常好。但是我们的模型有点复杂,因为它有 30 多个特性。让我们试着在不损失太多性能的情况下降低它。 Catboost 具有feature_importances_属性,可以帮助我们确定要选择的最佳属性。

# Feature importances to dataframe
feature_importances = (pd.DataFrame({'feature': data.feature_names, 'importance': model.feature_importances_}).sort_values(by='importance', ascending=False)
)
# Plot
px.bar(feature_importances,x= data.feature_names, y=model.feature_importances_,height=600, width=1000).update_layout(xaxis={'categoryorder':'total descending'})

减少 3 以下的重要性。图片由作者提供。

没有使用任何花哨的技术,我只是任意选择保留任何重要性为 3+的特征。这使得他们中的 10 个人,在红线的左边。

更简单的模型

让我们训练更简单的模型,并评估分数。

# Simpler model
features = feature_importances.feature[:10]
# Creating a Pool for training and validation sets
train_pool2 = Pool( data=X_train[features], label=y_train)
test_pool2 = Pool( data=X_test[features], label=y_test)# Model 
model2 = CatBoostClassifier(iterations=600)
model2.fit(train_pool2, eval_set=test_pool2, verbose=100)# Score
preds2 = model2.predict(test_pool2)
f1_score(y_test, preds2)0.979020979020979

很好。同 F1 分: 97%

由于我们正在进行医学诊断,我们不应该对假阴性非常宽容。只有当我们非常确定病人确实健康时,我们才会希望我们的模型说他健康。

但是我们知道 CatBoost 算法使用标准的 50%阈值来预测结果。这意味着,如果阳性概率低于 50%,患者将被诊断为乳腺癌阴性。但是我们可以调整这个数字,让它给出一个更高确定性的负面预测。

让我们看看这是怎么做到的。以下是我们模型的一些预测。

# Regular predictions
default_preds = pd.DataFrame(model2.predict_proba(test_pool2).round(3))
default_preds['classification'] = model2.predict(test_pool2)
default_preds.sample(10)

50%阈值模型的预测概率。图片由作者提供。

请注意,观察值 82 有 63.4%的几率为阴性,但也有 36%的几率为阳性,这对于医学标准来说是很高的。我们希望这个病例被归类为阳性,即使知道它可能是假的。所以我们可以在晚些时候送这个人去做另一个测试。因此,我们将假阴性率[FNR]容限设为 1%。

from catboost.utils import select_threshold
# Finding the right threshold
print(select_threshold(model2, test_pool2, FNR=0.01))0.1420309044590601

太好了。既然 CatBoost 已经计算出了这个数字,那么被归类为负数的新阈值就是 1–0.142 = 0.858。简单来说,0 类的概率必须超过 85.8%才能被标记为 0,否则将被归类为 1。

好的。因此,我创建了一个自定义函数predict_threshold(df, threshold, rate_type) ( 访问我的 GitHub 查看代码),该函数将带有解释变量、所需阈值和汇率类型(FNR 或 FPR)的数据帧作为输入,并使用新的 cut 返回分类。

# Predict
new_predictions = predict_threshold(df= test_pool2, threshold= 0.01, rate_type= "FNR")# Standard predictions
normal_predictions = model2.predict(test_pool2)

对指数 82 的相同观察,先前以 63%的概率被分类为负(0),现在被分类为正(1)。

同样的观察#82 现在是积极的。图片由作者提供。

这是标准 50%阈值的混淆矩阵。

# Confusion Matrix 50% standard threshold
pd.DataFrame( confusion_matrix(y_true=y_test, y_pred=normal_predictions) )

分类 50%阈值。图片由作者提供。

这是具有更新阈值的新分类。

# Confusion Matrix 1% of false negatives allowed threshold
pd.DataFrame( confusion_matrix(y_true=y_test, y_pred=new_predictions) )

分类 85.8%阈值。图片由作者提供。

观察两个混淆矩阵的左下角单元格[true=1,pred=0,FN]。最上面的显示了一个假阴性。该人实际上患有癌症,并且该模型被分类为阴性。这个问题在新模型中得到了解决,没有假阴性。另一方面,我们也增加了一个假阳性。因此,就像数据科学中的许多事情一样,这都是关于权衡的问题。

FPR(I 型误差)和 FNR(II 型误差)是互补的。当你减少一个时,另一个必然会增加。

如果您的项目需要非常低的误报率,那么可以使用相同的方法来降低 FPR。

在你走之前

总之,我们在这篇文章中学到的是:

  • 分类的默认截止阈值是 50%的概率。
  • 这个数字可以调整,以减少假阳性或假阴性的数量。
  • FPR(I 型误差)和 FNR(II 型误差)是互补的。减少一个会增加另一个。
  • 使用catboost包计算分类的概率截止阈值。
  • 例:predict_threshold(test_pool2, threshold= 0.01, rate_type=”FNR”)

如果你喜欢这个内容,关注我的博客或者在 LinkedIn 上找到我。

http://gustavorsantos.medium.com/

使用这个推荐代码成为一个媒体会员(你的订阅的一部分将会来到我这里,激励我继续创作内容)。

参考

https://github.com/catboost/tutorials/blob/master/events/2019_pydata_london/pydata_london_2019.ipynb

十二个人力资源就绪的角色,帮助建立健康的数据科学和人工智能团队

原文:https://towardsdatascience.com/twelve-hr-ready-roles-to-help-build-healthy-data-science-and-ai-teams-cbb6660f87b8

数据科学功能障碍

把那些数据科学维恩图收起来。是的,全部 13 个。

Artem Podrez 的照片

从 2021 年开始的持续的大辞职,而聚集蒸汽和有可信的数据,在数据科学中并不新鲜。

今天不是对人们为什么辞职进行解释的时候。(我们做到了这一点,并且勇敢地发表了我们的想法关于我们认为如何通过同理心和治理来做到这一点)。相反,今天我想关注功能失调的数据科学团队的一个关键驱动因素— 管理层错误地设定数据科学家应该做什么和不应该做什么。

这种特殊的功能障碍会导致多种故障模式,包括:

  • 数据科学家和分析师被迫构建全栈应用,而不是与软件工程合作(事实上在软件工程中扮演多重角色)。
  • 数据科学家负责数据管理、报告和构建仪表板(而不是与专业的数据工程和商业智能团队合作)。
  • 一旦项目完成,人工智能研究人员就会花费越来越多的时间来部署和监控模型(这更接近于机器学习工程师和 MLOps 团队的工作,而不是研究人员)。
  • 数据科学与人工智能混为一谈(人工智能方法可用于洞察,人工智能本身是一个不同的学科,有不同的侧重点)。

这并不是说您需要雇佣四个团队来从关系数据库中建模一个表。所有的数据团队都是从某个地方开始的,在小公司里,多任务处理是绝对必要的。

这也不是说数据科学家没有能力完成这些任务。成长的心态是很好的,所有的角色都从理解相邻团队的工作中受益。但是仅仅因为一些数据科学家能够完成任务并不意味着应该作为一般规则成为核心数据科学职责的一部分。

数据科学家经常延伸并扮演多重角色,做任何必要的事情来整合事物。但这本身就是一种失败的模式——因为讽刺的是,这导致管理层认为事情运转良好,所以他们需要的只是……更多的数据科学家。从而使功能障碍永久化。

那么,我们如何避免这种情况呢?

更好、更精确的职称。

这符合候选人的期望,节省招聘经理的时间,甚至可能让你的公司免于成为数据科学工作诱饵和交换线索的对象。

但在我们走得太远之前,我们需要谈谈数据科学维恩图。

超越数据科学维恩图

当有人提到数据科学维恩图时,一个合理的问题应该是“哪一个”?

根据最近的统计,至少有 13 张数据科学维恩图,它们是由德鲁·康威的原作演变而来。(非常感谢 KDNuggets 汇总了这份名单。)

我非常尊重德鲁·康威分享他的想法——很容易指出有些事情对我来说并不完全正确,却忘记了分享想法之前的世界状态。他最初的想法是一个伟大的里程碑,值得借鉴,今天仍然适用。

但是这个领域已经成熟,我们需要解决的问题不仅仅是理解数据科学家的角色,还要同等关注补充数据科学的其他数据角色。并意识到这些职业本身就是既定的平等职业。

来自世界上人工智能准备最充分的城市的数据科学职业道路

几年前,我和我的数据和人工智能团队领导一起尝试解决这个问题。意外的是,从一个小团队开始的努力最终将我们带入了新加坡的国家角色定义活动,我们中的许多人都在那里工作。我们的工作最终扩展到定义十二个数据、分析、数据科学和人工智能角色。

这些在下面的框架中公布。

重要的是,目标是装备公司来建立数据和人工智能团队。因此,这些角色都是人力资源就绪型的,您会发现以下全套服务:

  1. 角色描述,
  2. 技术技能和软技能的分类法,
  3. 每个角色的每项技能的校准水平,以及
  4. 每个技能级别的定义。

在同一时期,我很受鼓舞地听说新加坡作为人工智能中心的增长轨迹获得了由奥纬论坛、牛津洞察和国际发展研究中心以及 IMD 颁发的三个奖项,该城市目前被评为世界上人工智能准备最充分的城市。(作为一名数据科学家,我一直对排名的内部运作方式感到好奇,所以我做了一些深入研究,通过斯坦福大学海(Stanford HAI)的全球人工智能活力工具发现,新加坡的成功很可能是以人均为基础计算的。)

不考虑奖项,该框架实际上是有用的,现在代表了数据和人工智能专业人员职业道路的最新来源之一。

数据和人工智能职业的技能框架

五个职业系列中的十二个数据和人工智能角色

数据和人工智能中有 12 个不同的角色

下面列出了这些不同的角色。他们分为五个角色“家族”,y 轴大致表示家族中的资历:

  • 数据分析师/助理数据工程师
  • 商业智能经理
  • 商业情报总监
  • 数据工程师
  • 高级数据工程师
  • 数据架构师
  • 人工智能/机器学习工程师
  • 高级人工智能/机器学习工程师
  • 数据科学家/人工智能科学家
  • 人工智能应用研究员
  • 数据科学和人工智能主管
  • 首席数据官/首席人工智能官

与其重复网站上的内容,一个更有用的框架可能是拍摄一个数据和人工智能职业新人的镜头。

我们希望帮助数据、分析和人工智能领域的新进入者回答这个问题:我最适合从事哪种数据角色?

为了回答这个问题,我提出了四个具体的数据分割:

帮助你找到合适角色的四个问题

商业智能是一个既定的领域,有自己的职业道路。

商业智能(“BI”)是一个成熟的领域,与数据科学有许多重叠。它包括确定业务需求,准备和分析数据,并提出见解。然而,仅仅这些并不意味着它应该被归入数据科学。

BI 需要一系列技能,这些技能既要贴近客户,又要贴近数据,还要贴近所选择的产品或平台。这些技能包括数据分析、数据工程、数据治理、数据可视化和利益相关方参与——完整列表可通过技能框架网站此处的获得。

同样重要的是缺少了什么——虽然 BI 的进步意味着许多现代 BI 工具现在提供了一些预测能力,但建模的责任不在他们的所需的技能集上。

一个特别有趣的特点是,我们调查的每个 BI 团队都是如何围绕少量商业 BI 工具构建他们的实践的。

BI 是其独特的职业道路,即使是现在,BI 专业人员和工作也经常超过数据科学,这可能会让一些数据科学家感到惊讶。

在工作中提供最多信息的问题通常是关于你是否生产 洞察力 或产品。

如果我在面试一份工作,并且只能问一个问题来获得关于数据科学角色的信息,我不会问这份工作是否需要深度学习,在 devops 环境中工作,或者需要因果关系的知识。

向我提供关于您的数据科学角色的最多信息的一个问题是:“您的产出主要是见解还是产品?”

或者换一种稍微不同的方式问,你的角色的产出主要被人类消耗,还是被机器消耗?

在某种程度上,答案是“洞察力”,你倾向于框架的右边,可能更像“科学家”而不是“工程师”。这是你加倍理解你的业务问题领域的地方,需要仔细的推理技巧和渠道沟通技巧来帮助你的利益相关者做同样的事情。你也可以从尽可能靠近你试图支持的决策者的位置上获益。

相反,当答案是“产品”时,你会偏向左边,偏向“工程师”的角色。你可能会“更接近 IT”,从对应用程序开发和集成的理解中受益匪浅,并且可能会在敏捷产品或交付团队中工作。

有些特定的职位要求博士学位。但是他们相对来说是少之又少。

第三个问题是我与毕业生交谈时经常遇到的一个问题——一个人是否需要博士学位才能成为数据科学家。最近几年,这种观点略微转向了“拥有博士学位会对我的职业生涯有帮助吗?”

虽然你的具体情况是最重要的,但值得一提的是,有一些特定的角色会从博士学位中受益匪浅。这些往往在机器学习或者人工智能的某个特定子领域(比如图像处理或者 NLP)。

这些属于人工智能应用研究人员的角色,通常以他们工作的 R&D 性质为标志,将发表论文作为他们工作范围的一部分,以及产生可申请专利的人工智能解决方案的能力。

初级申请人应该知道分析师角色有多重含义。

如果你只是转行做数据,而你的录用通知书上写着“数据分析师”,这可能是一个没有用的词。).

这是因为“数据分析师”的角色被用于多个不同的工作类别,根据角色所在的类别,您的体验会有很大的不同。

对数据分析师的一种解释是为洞察分析数据,另一种解释是为工程分析数据。

更复杂的是,都使用了术语建模。但当数据科学家提到建模时,它通常意味着将曲线拟合到分布,并为预测或推理任务构建数据的表示。但是当一个数据工程师提到建模时,这种用法与数据结构和模式有关,以存储、管理和执行数据操作。

无论你发现自己处于哪种变化中,都要知道自己是谁,我相信你会成为一名模范员工(抱歉)。

结束语:打造你最好的团队!

从数据科学家到经理、总监及其他职位,需要为扩展团队中的新角色和角色制定新的路线图。我们这样做是为了让您不必这么做,所以您可以根据自己的需求随意利用和调整这个框架。如果您有问题、反馈或只是需要一个建立团队的共鸣板,请不要犹豫。

现在,如果你是一名经理,你刚刚雇佣了一名计量经济学家来“构建你的数据结构,这样你就可以在上面进行预测分析”,或者雇佣了一名“商业智能开发人员”来优化你的营销支出,因为“你想在仪表板上看到结果”,你可能需要再次阅读这篇文章。但是先去一个随机的森林里散散步。

Jason 是一位研究人力资源和数据科学的数据科学家。我是以个人身份写这篇文章的,这并不代表我所工作或隶属的组织的观点。

孪生悖论可视化

原文:https://towardsdatascience.com/twin-paradox-visulazation-6455fafb7efc

加速不是问题

作者

背景

狭义相对论不仅是一个神秘有趣的话题,激发了几乎每个人的好奇心,而且提供了一套数学工具,有时用于数据项目。数学对象,如闵可夫斯基空间,用于语音项目。因此,熟悉这个领域不仅在智力上引人入胜,而且可以提供数据科学的优点。

我为什么要告诉你这个

几天前,我在我们的走廊里听到两个员工之间关于 双胞胎 悖论 的讨论(是的,我正在和受过良好教育、充满好奇心的淑女和公子哥儿一起工作)。在交谈中,一位与会者说“这不是一个悖论,因为旅行者在加速”。虽然我的大部分物理知识已经蒸发了,但我的脑海里还是响起了一个警报声。然而,当我开始谷歌“双胞胎悖论”时,我看到了大量旨在澄清悖论并关注加速的网站。事实上,加速有助于创造一个时间间隙,这听起来像是一个连贯的澄清……但这是错误的(对于不熟悉孪生 悖论的读者,请等待下一节)。随着我浏览更多的网站,我发现这些解释缺乏形象化。这篇文章旨在改进它,并提供一个更具体的解释。

什么是孪生悖论?

1905 年,在他出版了特刊https://www.youtube.com/watch?v=CB1QFUCga0I相对论 后不久,爱因斯坦写道如果我们带着两个双胞胎,让其中一个以接近光速的速度旅行,而他的兄弟仍在休息,当旅行者旅行回来时,他会更年轻。这句话是在接近相对论的著名“宣告”时给出的:“一切都是相对的”。如果是这样,那么旅行者看到自己在休息,而地球和休息的孪生兄弟在旅行,因此后者也必须更年轻:一个悖论。****

嗯,你可以猜到物理学很少允许悖论,爱因斯坦也很少出错。然而,当我们遇到一个大脑健康的人认为是悖论的话题时,它有一个智力上令人兴奋和冷静的解决方案是合理的。

在我们继续之前,我希望指出,我将避免在这篇文章中使用公式,但我建议读者熟悉一些概念:

  • 伽马因子
  • 洛伦兹 变换——在 时空 中进行两个坐标系转换的方程****
  • 时空间隔 。-时空间隔的长度。出于平凡的线性代数,这个区间的长度在坐标变换下是不变的。
  • 同时性 - 相对论中的一个概念表明一个观察者所观察到的两个同时发生的事件不会被另一个观察者所察觉
  • 时间 膨胀

时空

我们现在要介绍时空。为了简单起见,我们将假设 1D 空间,然而,很容易推断出整个规则也适用于 3D 空间。

作者-时空的基本图形

在上图中,我们看到了时空的普通形状。黄色斜线代表光速。x 轴代表空间(通常以光年为单位),Y 轴是时间或者更准确的时间乘以光速 C

因为没有什么能比光速更快,物理事件只能发生在上半部分和下半部分,如下图所示:

作者:起源是他的存在(此时此地)。红色部分是过去发生的事件的领域,蓝色部分是现在和未来的事件的领域

为了继续讨论孪生佯谬,我们需要提出坐标系的概念。在狭义相对论中,我们总是考虑问题的不变性。对于孪生悖论,我们考虑素数素数系统

无质指的是留在地球上不动的双胞胎。它也被称为静止框架****

作者

在该图中,洋红色垂直线代表静止帧。因为他假设自己是固定的,只有它的时钟向前变化。

作者

在该图中,我们添加了红色斜线,代表从静止框架视角观察的旅行者(主系统)蓝色虚线代表休息的双胞胎观察旅行者的时间水平曲线。

既然我们讨论狭义相对论,我也将代表质数系统的观点

作者

这里我们看到红线作为我们的参照物:旅行者在空间中是固定的,只随时间变化。他观察到静止帧(洋红色)以固定的速度离开。青色虚线是根据旅行者坐标系的时间水平曲线。

关于悖论,什么是正确的?

正如我们上面所写的,一个高速旅行者手表里的时间比他旅途中的时间走得慢,因为时间是用静止坐标系来测量的。因为旅行者可以认为自己是一个静止的框架,所以我们得到了相反的结果。

在下面的部分,我们将分析这些现象,并提供一个客观的说明。在这项研究中,我们采用了维基百科的数据:假设一艘宇宙飞船在太空中以 0.8 C 的速度运动。当它到达地球时,它保持速度,并与地球上的人同步手表。现在它飞向一颗距离地球 4 光年的恒星。请注意,在我们研究的场景中,根本没有加速度!!。*

让我们从两个角度来分析:

地球观察者

作者

我们将使用我们已经见过的符号:洋红色垂直线代表地球上的观察者,红线代表宇宙飞船旅行者。据地球上的那个家伙说,旅行者要花 5 年时间才能到达那颗恒星。根据他的手表,旅行者只需要 3 年。我们可以通过两种方法看到这一点:

  • 简单地使用由青色虚线表示的洛伦兹变换
  • 使用时空间隔:根据地球上的家伙,路径长度是 3C(原点到(4,5)),因此根据旅行者,它一定也是 3C。但是旅行者假设他是固定的,因此它的空间坐标是 0。因此他经历了一个长度 3C 的路径,它只依赖于时间。也就是说,需要 3 年时间。

旅行者

我们可以用前面的图表来解释他的观点

作者

游客再次用红线表示。他假设自己没有移动,看到静止框架(地球上的家伙)以 0.8 C 的速度远离地球。地球佬在他的位置上站了五年:(0,5)。根据旅行者所说,地球上过去了多少时间?我们可以使用时空间隔或洛伦兹变换:根据地球,路径的长度是 5 C ,因此根据旅行者,路径的长度将是 5 C 。具体多少钱?看起来是 8 年零 4 个月,根据旅行者系统,它处于点(-20/3.25/3),其长度正好是 5 C*

我们可以总结为:

在每个系统中,观察者的手表比另一个系统中测量他的手表走得慢

作者

后面的图表显示了一般情况:我们有两个旅行者,一个速度很快,另一个速度很慢。我们可以看到,每个旅行者在他的手表上测量的时间比其他人测量的时间要短。然而,我们看到测量值不相等:主绷绳在非主绷绳上测量的时间不等于非主绷绳在主绷绳上测量的时间****

相对论是相对的但不是镜子!

那解决办法是什么?

为了描述事物实际上是如何工作的,我们将观察下图

作者

在上图中,我们可以看到两个系统的时间线:静止帧(青色水平线)和旅行者(红色斜线)相对于相关系统,每两条相邻线之间的距离为 1。我们可以看到两个红色邻居之间的距离大于两个蓝色邻居之间的距离,这反映了旅行者的时钟走得更慢。此外,我们可以推断,随着速度的增加,时间会变慢,如下图所示

作者

这里我们添加了一个较慢的移动物体,用洋红色线条表示。

希望用数学方法验证这一点的读者只需摆弄洛伦兹方程就能得到这些线。

总结和结论

在这篇文章中,我已经表明,与网络上的普遍观点相反,双胞胎之间的时间差距不是加速的结果。此外,人们不需要熟悉“惯性”的概念就能理解这个概念。这仅仅是关于理解时空礼仪。****

确认

我要感谢伊琳娜·沙勒姆提出了这个问题的挑战,并迫使我进行连贯的思考。

剧情的代码可以在这里找到。

BigQuery 中两种(完全不同的)dbt 增量模型

原文:https://towardsdatascience.com/two-completely-different-types-of-dbt-incremental-models-in-bigquery-db794cbe022c

基于分区的加载或使用增量加载跟踪下游模型的历史

黄福生在 Unsplash 上的照片

dbt 中的增量模型不是新发明,它们是将数据追加到表中的传统方式。在老式的数据仓库中,这是将日常数据引入数据存储环境的方法。为了不让事情失去控制,您应该放置一个键,并决定更新行(如果它存在的话),否则追加它(用一个MERGE操作)。

dbt 已经有了很棒的关于增量模型的文档https://docs . get dbt . com/docs/building-a-dbt-project/building-models/configuring-incremental-models。几乎所有示例中提到的典型用例是切换到增量模型以降低成本,因此您不会每天删除表并从头开始重新构建,而是增量添加行。如果您正在使用 BigQuery,那么您应该已经了解了每天扫描数百 GB 或 TB 数据的成本(或者每天扫描几次,这取决于您如何安排 dbt)。下面,我将介绍两种类型的用例,以及如何在 dbt 中设计一个高效的增量模型。

类型 1 增量模型用例

  • 您的数据已经在您的数据湖中,完全,所有的,所以您可以每天重新创建您的表。
  • 您有不可变的事件数据,并且有时间戳。例子可以是点击、浏览、印象、电子邮件打开等。
  • 在表实体化上创建模型的成本很高。

类型 1 增量模型示例

假设我有关于客户点击的数据,这些点击有user_idclicked_at时间戳

原始客户点击数据示例

并且您希望创建一个模型,其中包含每个客户每天的点击量:

点击数据的类型 1 增量模型输出示例

对此的查询非常简单:

select user_id, date(clicked_at) as day, count(clicked_at) as nr_clicks
from raw_data
group by user_id, date(clicked_at)

想象一下,你有万亿字节的数据,当你只能计算最后一天时,为什么每次都要从头开始重新计算所有的点击历史?

1 型增量模型的解决方案

首先这类数据天生就是要分区的,请做到。在这个链接中,您可以看到 BigQuery 上不同策略的简洁对比以及每种策略的性能。很明显插入+覆盖静态策略的性能最好,几乎与数据 🥳 的增长成线性关系。所以让我们来实现它吧!

-- Let's define the partitions and decide the replace the last 2 
-- days, just in case some clicks did not arrive yet.{% set partitions_to_replace = [ 'current_date','date_sub(current_date, interval 1 day)'] %}-- Here we define the incremental model, the data will be
-- partitioned by the date and I am also clustering by user_id
-- to improve performance. I am choosing the insert_overwrite
-- strategy explicitly{{ config(materialized='incremental',partition_by = { 'field': 'day', 'data_type': 'date' },cluster_by = "user_id",incremental_strategy = 'insert_overwrite')
}}select {{ dbt_utils.surrogate_key(['user_id',    dbt_utils.date_trunc('day','clicked_at')]) }} as user_day_pk,user_id, date(clicked_at) as day, count(clicked_at) as nr_clicks
from raw_data-- This is to replace the last 2 day partitions{% if is_incremental() %}where date(clicked_at) in ({{ partitions_to_replace | join(',') }}){% endif %}
group by user_id, date(clicked_at)

我甚至没有展示这个用例的merge策略的例子,尽管它们会起作用,因为这确实是实现这个模型并获得最佳性能的一个简洁的方法。注意对于insert_overwrite策略,你不需要一个键。更重要的是,即使您在配置中为惟一键添加了一行,那一行也不会运行!所以不要这样做,你会给自己制造错误的期望:

{{ config(materialized='incremental',
-- do not do this line below, it won't run anywaysunique_key = 'user_day_pk'partition_by = { 'field': 'day', 'data_type': 'date' },cluster_by = "user_id",incremental_strategy = 'insert_overwrite')
}}

那为什么不需要钥匙呢?嗯,上面的代码盲目地工作,从 dbt 模型中删除最近 2 天的分区,从raw_data中选择最近 2 天的分区,并重新追加它们。所以手术不需要钥匙,但是如果你愿意,你可以在桌子上留一把钥匙。

类型 2 增量模型用例

  • 您的数据源不保留历史记录,但您希望开始构建历史记录并停止丢失数据。
  • 您的 dbt 模型有几个下游转换,并且您想要跟踪您的下游模型的历史。

这是一个与前一个案例完全不同的用例,因为这类似于保存快照。这里的是 dbt 实验室关于这个用例的一个非常好的博客,其中他们明确声明不要给你的下游模型拍快照。我将展示的解决方案不仅可以帮助您跟踪历史和防止数据丢失,而且比维护多个快照的成本要低得多。

类型 2 增量模型示例

假设您的数据源中有 6 个表,这些表保存了 Twitter 个人资料的不同属性,比如 Twitter 个人资料 URL、Twitter 用户名、Twitter 描述、Twitter 照片 URL 和 Twitter 位置。这些是工程表,保存了每个属性,以及创建和修改的时间戳。

t_profilet_profile_idt_modified_att_deleted_at

t_usernamet_profile_idt_usernamet_created_att_modified_at

t_urlt_profile_idt_urlt_created_att_modified_at

t_descriptiont_profile_idt_descriptiont_created_att_modified_at

t_photo_urlt_profile_idt_photo_urlt_created_att_modified_at

t_location——t_profile_idt_longitudet_latitudet_created_att_modified_at

t_profile_id是每个表的一个键。用户名和配置文件 URL 是必需的属性,但是配置文件中可能缺少其他属性。连接这些数据的查询很简单,但是它只给出 Twitter 概要文件的当前版本。实际上,除非您开始跟踪历史,否则您无法找到每个属性的版本,只能找到当前的版本。

selectt_profile.t_profile_id,t_username,t_url,t_description,t_photo_url,t_longitude,t_latitude,t_profile.t_created_at as t_profile_created_at,t_profile.t_deleted_at as t_profile_deleted_at
from t_profile
inner join t_usernameon t_profile.t_profile_id = t_username.t_profile_id
left join t_urlon t_profile.t_profile_id = t_url.t_profile_id
left join t_descriptionon t_profile.t_profile_id = t_description.t_profile_id
left join t_photo_urlon t_profile.t_profile_id = t_photo_url.t_profile_id
left join t_locationon t_profile.t_profile_id = t_location.t_profile_id

现在,您想要基于前面的查询创建一个表t_profile_history,但是还要跟踪工程数据中的每个属性。

2 型增量模型的解决方案

我们不能再将分区用于数据加载策略,因为我们不希望删除任何分区并从数据源重新计算,因为数据源不保留历史记录。让我们把这种情况简化为每天进行一次修改。

-- Here we set the config as incremental and the unique key of each -- profile per day
-- I am pratitioning by date for query performance after the model 
-- is live, but it won't affect the merge
-- Add the strategy as merge
-- Do not forget full_refresh = false so there are no accidents{{ config(materialized='incremental',unique_key = 't_profile_history_pk',partition_by = { 'field': 't_profile_modified_at',     'data_type': 'timestamp', "granularity": "day" }, cluster_by = "t_profile_id",incremental_strategy = 'merge',full_refresh = false)
}}with twitter_profile_versions as (
selectt_profile.t_profile_id,t_username,t_url,t_description,t_photo_url,t_longitude,t_latitude,t_profile.t_created_at as t_profile_created_at,t_profile.t_deleted_at as t_profile_deleted_at,GREATEST(COALESCE(t_profile.t_created_at,t_username.t_modified_at,t_url.t_modified_at,t_description.t_modified_at,t_photo_url.t_modified_at,t_location.t_modified_at),COALESCE(t_username.t_modified_at,t_url.t_modified_at,t_description.t_modified_at,t_photo_url.t_modified_at,t_location.t_modified_at,t_profile.t_created_at),COALESCE(t_url.t_modified_at,t_description.t_modified_at,t_photo_url.t_modified_at,t_location.t_modified_at,t_profile.t_created_att_username.t_modified_at),COALESCE(t_description.t_modified_at,t_photo_url.t_modified_at,t_location.t_modified_at,t_profile.t_created_att_username.t_modified_att_url.t_modified_at),COALESCE(t_photo_url.t_modified_at,t_location.t_modified_at,t_profile.t_created_att_username.t_modified_att_url.t_modified_at,t_description.t_modified_at),COALESCE(t_location.t_modified_at,t_profile.t_created_att_username.t_modified_att_url.t_modified_at,t_description.t_modified_att_photo_url.t_modified_at),) as t_profile_modified_atfrom t_profile
left join t_usernameon t_profile.t_profile_id = t_username.t_profile_id
left join t_urlon t_profile.t_profile_id = t_url.t_profile_id
left join t_descriptionon t_profile.t_profile_id = t_description.t_profile_id
left join t_photo_urlon t_profile.t_profile_id = t_photo_url.t_profile_id
left join t_locationon t_profile.t_profile_id = t_location.t_profile_id)select {{ dbt_utils.surrogate_key(['t_profile_id',      dbt_utils.date_trunc('day','t_profile_modified_at')}} as t_profile_history_pk,twitter_profile_versions.*
from twitter_profile_versions{% if is_incremental() %}where date_diff(current_date(), date(t_profile_modified_at), DAY) <= 2{% endif %}

上面这段代码为我们的t_profile_history设计了加载过程,它为每天保存一个版本的概要文件,以防有任何变化。我保留了带有属性的表的连接,以选择最近两天修改过的属性。要小心,因为内部连接要求所有属性都已更改,但情况可能并非如此。

我使用GREATEST()来选择最后修改的时间戳,这是为了简化查询,并试图找到每个表的修改时间戳之间的中间版本。在本例中,我们简化了用例,用运行 dbt 时获得的快照构建历史。注意,我正在使用COALESCE()并旋转所有修改过的时间戳,包括概要文件中的t_created_at。我这样做是因为如果任何元素为空,那么GREATEST()将返回NULLhttps://cloud . Google . com/big query/docs/reference/standard-SQL/functions-and-operators并且我们可能有空值,因为我们有 5 个左连接。我轮换每个修改过的时间戳,所以如果它们不为空,那么它们都有机会成为COALESCE()的输出。

记得用full_refresh=FALSE。这将防止当有人运行dbt run --full --refresh而你的模型连同你迄今为止跟踪的历史不见了的时候发生意外。

边上的一张纸条:

第二类使用情形的替代解决方案也可以通过快照来解决。在这种情况下,您将每天对所有 6 个表进行快照,并在下游照常进行转换(表或增量具体化)。快照解决方案的优点是,您仍然有一种 ELT 过程,您可以跟踪源数据的历史,并在以后决定转换。当您不确定要跟踪什么并且希望模式发生变化时,这是一个很大的优势。快照解决方案的缺点是,与增量模型相比,它在处理(增量模型解决方案仅扫描最近 2 天的数据)和存储(我们在增量解决方案中仅存储 1 个模型,而不是在快照解决方案中存储 7 个模型)方面都非常昂贵。

让我们结束吧!

dbt 增量模型是提高性能和优化成本的神奇工具。当你的表很大并且事件不可变时,考虑使用insert_overwrite策略。一个不太传统的例子是跟踪下游模型的历史。这更像是一个 ETL 过程,您提取、执行转换,然后增量加载,这可以通过merge策略实现,保持唯一的键,并确保您不允许完全刷新。

两个黑仔 Jupyter 黑客保证节省你的工作时间

原文:https://towardsdatascience.com/two-killer-jupyter-hacks-that-are-guaranteed-to-save-you-hours-of-work-time-2b9e430988dd

当你开始使用它们的时候

布拉德在 Unsplash 上整理的照片

Jupyter 笔记本,因为其简单、流线型、初学者友好和光滑的设计,几乎是当今任何面向 Python 的任务不可或缺的。

回想起来,我甚至无法想象没有像 Jupyter 这样的交互式 Python (IPython)工具的生活。

Jupyter(作者创建的图像)

本质上,IPython 最显著的优点是,只要内核是活动的,它们就将对象保存在内存中,从而减少了重新运行脚本的摩擦。

此外,Jupyter 也是典型原型制作的首选。

这使得数据清理、转换和可视化、数值模拟、统计建模、机器学习等任务相对更容易。

然而,由于它们的简单性,开发人员经常会犯一些常见的错误(无意的),从而耗费他们的时间和计算——这是项目中的两个基本支柱。

因此,在这篇博客中,我将分享几乎每个 Jupyter 用户都犯过的两个错误。这样,我还会向他们展示一个优雅的解决方案,为您节省大量时间。

我们开始吧🚀!

错误 1

你有没有遇到过这样的情况,你用 Jupyter 写了一些代码,但是在计算之后发现你忘记给变量赋值了?

在这种情况下,人们不得不再次执行单元格,并生成结果以将它们赋给一个变量。

我能体会那种感觉,因为我自己也经历过。

解决办法

如果我告诉你有一个巧妙的解决方法呢?

当你在 Jupyter 中执行一个单元格时,除了单元格之外,你还会看到类似于In [2]:的东西,不是吗?

Jupyter 细胞(图片由作者提供)

同样,除了单元格的输出面板之外,您还可以看到一些东西。更具体地说,它被表示为Out[3]:

Jupyter 细胞(图片由作者提供)

在 IPython 中,Out是一个标准的 Python 字典,存储了output-idcell-output的映射。In是一个 Python 列表,存储按顺序执行的代码。

它们的类型可以通过以下方式验证:

因此,如果您忘记将输出分配给某个变量,您可以使用Out字典并传递出现在输出面板旁边的output-id

例如,在上面的groupby输出中,您可以使用Out[3]来检索结果。

是不是很酷?

错误 2

当在 Jupyter 笔记本中工作时,由于几个原因,人们可能想要重新启动内核。但是在重启之前,人们通常倾向于将数据对象转储到磁盘,以避免在后续运行中重新计算它们。

这是一个耗时的过程。此外,单独存储每个重要的数据对象相当麻烦。

解决办法

商店”魔法指令是解决这一问题的理想方案。在这里,即使在重新启动内核之后,您也可以获得之前计算的值。

此外,您永远不需要经历将对象转储到磁盘的麻烦。

下面的视频演示了这一点:

存储命令演示(Gif 由作者提供)

如上所示,store magic 命令允许您甚至在重新启动内核后检索先前计算的值。

总而言之,这些是步骤:

第一步:使用%store存储变量。

这里“价值”是一个变量。

第二步:重启内核后,使用带有-r选项的%store

注意,您也可以使用单个%store命令存储多个值。

结论

说到这里,我们就到此为止了。我希望你学到了新东西。

我相信,在交互式 Python 环境中工作时,这两个技巧将为您节省大量时间。

觉得这些小技巧有趣吗?

如果你想了解更多关于数据科学和 Python 的优雅技巧和诀窍,我每天都会在 LinkedIn 上发布一个信息丰富的提示。

你可以在 我的 LinkedIn 帖子存档 中找到我发布的所有提示。你可以在 LinkedIn 上关注我,看看以后所有的帖子。

或者,您也可以通过电子邮件接收:

🚀订阅数据科学每日一剂。在这里,我分享关于数据科学的优雅技巧和诀窍,一天一个技巧。每天在你的收件箱里收到这些提示。

🧑‍💻成为数据科学专业人士!获取包含 450 多个熊猫、NumPy 和 SQL 问题的免费数据科学掌握工具包。

感谢阅读!

执行图形残差分析的两种方法

原文:https://towardsdatascience.com/two-methods-for-performing-graphical-residuals-analysis-6899fd4c78e5

猜测你是否可以在你的 ML 问题中使用线性模型的一些技巧

艾萨克·史密斯在 Unsplash 上拍摄的照片

回归分析的一个重要部分是了解我们是否可以使用线性模型来解决我们的 ML 问题。有很多方法可以做到这一点,通常,我们必须使用多种方法来了解我们的数据是否真的是线性分布的。

在本文中,我们将看到两种不同的图形方法来分析回归问题中的残差:但这只是有助于理解我们的数据是否是线性分布的两种方法。

您可以只使用这些方法中的一种,甚至两种都用,但是您将需要其他度量的帮助来更好地验证您的假设(要使用的模型是线性的):我们将在以后的文章中看到其他方法。

但是首先…回归问题中的残差是什么?

1.解释残差

执行回归分析时,最好先检查其线性。当我们执行简单的线性回归分析时,我们得到所谓的最佳拟合线,这是最接近我们正在研究的数据的线。通常,用普通的最小二乘法计算“最适合”数据的线。有很多方法可以找到最符合数据的线;举个例子,一个是用一个正则化方法(如果你想加深正则化背后的概念,可以在这里阅读我的解释。

让我们考虑将简单的线性回归公式应用于我们的数据;通常会出现的情况是,数据点没有恰好落在回归线上(即使我们使用两种正则化方法中的一种);它们分散在我们“最适合”的系列中。在这种情况下,我们称残差为数据点和回归线之间的垂直距离。因此,残差可以是:

  • 如果它们高于回归线,则为正
  • 如果低于回归线,则为负值
  • 如果回归线实际上通过该点,则为零

残差被可视化:它们是绿色的垂直线(红线是回归线)。图片作者。

所以,残差也可以看作是 t 任意数据点与回归线之间的差值,由于这个原因,它们有时被称为“误差”。在这种情况下,错误并不意味着分析有问题:它只是意味着存在一些无法解释的差异。

现在,让我们看看如何用图形表示残差,以及如何解释这些图形。

2.残差与预测值

我们可能感兴趣的与残差相关的图表之一是“残差与预测值的关系图。当我们用线性模型预测值时,必须绘制这种图形。

我从我的一个项目中提取了以下代码。假设我们的线性模型预测了我们的值:我们想要绘制“残差与预测”图;我们可以用这段代码来实现:

import matplotlib.pyplot as plt
import seaborn as sns#figure size
plt.figure(figsize=(10, 7))#residual plot (y_test and Y_test_pred already calculated)
sns.residplot(x=y_test, y=y_test_pred)#labeling
plt.title('REDISUALS VS PREDICTED VALUES')
plt.xlabel('PREDICTED VALUES (DIABETES PROGRESSION)')
plt.ylabel('REDISUALS')

残差与预测值。图片作者。

关于这个情节,我们能说些什么呢?

残差是随机分布的(上图没有明确的模式),这告诉我们选择的(线性)模型还不错,但是残差的高值太多(甚至超过 100)意味着模型的误差高。

像这样的图可以给我们这样的感觉,我们可以应用线性模型来解决我们的 ML 问题。在这个项目的具体案例中,我不能(如果你想加深理解,你可以阅读我的研究的第一部分和第二部分),这就是为什么我在上面写道,我们需要将这些图与其他指标“整合”,然后才能宣布数据确实是线性分布的。

有没有一种方法可以让残差警告我们,我们应用的线性模型不是一个好的选择?假设你找到了这样一个图表:

残差与预测值。图片作者。

在这种情况下,该图显示了一个清晰的模式(一个比喻),它向我们表明,线性模型可能不是这个 ML 问题的一个好选择。

总结:

这种图可以给我们一种直觉,告诉我们是否可以使用线性模型进行回归分析。如果图中显示***没有特定的模式,很可能我们可以使用线性模型*** 如果有特定的模式,我们很可能应该尝试不同的 ML 模式 无论如何 ,在这个情节之后, 我们必须用其他的度量来验证我们最初的直觉。

3.QQ 情节

QQ 图是“分位数-分位数”图,是一种通过绘制两个概率分布的分位数来比较它们的图形方法。

假设我们有数据(称为“数据”)要绘制在 qq-plot 中;我们可以用下面的代码来实现:

**import statsmodels.api as sm
import pylab #qq-plot
sm.qqplot(data, line='45')#showing plot
pylab.show()**

作者的 qq 图。

如果结果显示残差分布在线周围,就像上面的图一样,那么很有可能我们可以使用线性模型来解决我们的 ML 问题。但是,再一次:我们需要其他指标来证实这个最初的直觉。

结论

当我们执行回归分析时,一个好主意是首先测试 ist 线性。我们要做的第一件事是计算一些度量(例如 R 和 MSE)并获得对问题的第一直觉,试图了解是否可以用线性模型来解决;然后,我们可以用本文中看到的一个(或者两个)情节来加强(或者不加强!)我们最初的直觉;然后,我们必须使用其他方法来最终决定是否可以将线性模型应用于我们的问题(但我们将在另一篇文章中看到这些方法)。

我们一起连线吧!

中等

LINKEDIN(给我发送连接请求)

如果你愿意,你可以 订阅我的邮件列表 这样你就可以一直保持更新了!

考虑成为会员:你可以免费支持我和其他像我一样的作家。点击 这里 成为会员。

两个最常见的相似性度量

原文:https://towardsdatascience.com/two-most-common-similarity-metrics-39c37f3fe14d

找出两个文档相似程度的方法

当涉及到文档搜索或文本分类任务时,相似性度量是有用的。有几种方法可以衡量单词或文档之间的相似性。在这篇文章中,我将解释余弦相似性和欧几里德距离。

比阿特丽斯·佩雷斯·莫亚在 Unsplash 上的照片

目录

  1. 简介
  2. 余弦相似度
  3. 欧几里德距离
  4. 使用哪一个
  5. 结论

介绍

你有没有想过计算机如何知道两个不同的单词有相似的意思,或者两个不同的文档有相似的上下文?好吧,让我解释给你听。

什么是单词或文档向量?

它将一个单词(或一个文档)的含义表示为一个实数向量。这些向量可以是多维的。并且语义相似的单词或文档具有相似的向量。

向量空间中的词向量(图片由作者提供)

余弦相似性

多维空间中两个向量之间的的余弦决定了这两个向量的余弦相似度。

下面的公式给出了一个介于 0 和 1 之间的值,1 表示相似性最高,0 表示最低。

余弦相似公式(图片由作者提供)

我们来看一个例子。下图显示了一些语料库的假设向量表示。

语料库向量表示示例(图片由作者提供)

根据这种计算,假设的语料库在上下文中非常相似。

欧几里德距离

欧几里得距离就是两点之间的距离。单词向量在多维空间中彼此越接近,它们就越有可能具有相似的含义。

欧几里得距离的直观表示(图片由作者提供)

下面的公式给出了向量空间中两个向量之间的欧几里德距离。

欧几里德距离公式(作者图片)

用哪个?

何时使用其中一个取决于您的任务。一般来说,如果您想要度量相似性的文档大小不同,余弦相似性会给出更好的结果。如果其中一个语料库的规模比另一个大得多,自然地,一些单词在那个语料库中会比另一个重复更多。这就是为什么欧几里德距离可能不适合这种类型的任务。请参见下面的示例以获得直观的表示。

作者图片

在这个特别的例子中,d₂

However, in the exploratory type of data analysis, they both can be helpful to get insights from your data.

Conclusion

Even though there are also other ways, I’ve tried to explain the two most common similarity metrics. Which one to use almost completely depends on your data and task. Sometimes using both and getting an understanding of the data might be useful too!

Thank you for reading!

If you have any feedback, please don’t hesitate to reach out to me on my LinkedIn !

如何让你的熊猫代码运行得更快

原文:https://towardsdatascience.com/two-pandas-tricks-i-wish-id-known-earlier-60af0a049735

两只熊猫的把戏我希望我能早点知道

我们大多数数据科学家,在工作的某个时候都会用到熊猫图书馆。这篇文章将介绍两个技巧,让你的熊猫代码运行得更快。第一个是删除 None 值,第二个是从某一列中提取一组值。

斯通王在 Unsplash 上拍照。文本由作者添加。

删除无值

像生活中的大多数事情一样,我们的数据并不完美。通常,在进行某些计算之前,我们没有想要删除的值。无论是简单的平均值、直方图还是更复杂的计算,我们都希望没有这些。我们如何用熊猫来做呢?小菜一碟!我们可以简单地使用 pd.dropna()方法。假设我们的数据帧名为 df,我们希望删除“日期”列中的所有 None 值:

import pandas as pd#load dataframe
df = pd.read_pickle("my_dataframe.pkl")
#remove rows with None in column "date"
df.dropna(subset=['date'])

我们将这种方法称为“dropna”。很长一段时间,这是我的首选方法...直到我遇到了这样一段代码:

import pandas as pd#load dataframe
df = pd.read_pickle("my_dataframe.pkl")
#remove rows with None in column "date"
df = df[df["date"].notna()]

我的第一反应是:“为什么不干脆用 dropna()?”嗯,我查了一下,答案很简单。它可能并不漂亮,但它要快得多!
我们来看下面的对比:

notna 和 dropna 方法的比较。作者创作的图表。

我在 10 到 10⁷.之间的各种数据帧大小上测试了这两种方法左侧显示了作为数据帧长度函数的移除 None 值的时间。因为它随着行数的增加而显著增加,所以可以更清楚地看到这两种方法之间的时间比——因此是右图。它显示 /

结论很明确:nonta 每次都赢。

有什么隐情?

没有。没有内存消耗费用。

notna 和 dropna RAM 消耗的比较。作者创作的图表。

从列中提取集合

有时我们需要从某一列中获取一组项目。我将比较 Pandas 的方法 drop_duplicates 和 Numpy 的方法 unique。对于这一部分,我们将首先删除“日期”列中的 None 值。下面是这两种方法的代码:

熊猫的掉落 _ 复制品:

import pandas as pd#load dataframe
df = pd.read_pickle("my_dataframe.pkl")
#extract "date" column and remove None values
date = df[df["date"].notna()]#get a set of column "date" values
date_set = date.drop_duplicates(subset=['date'])['date'].values 

Numpy 的独特之处:

import pandas as pd
import numpy as np#load dataframe
df = pd.read_pickle("my_dataframe.pkl")
#extract "date" column and remove None values
date = df[df["date"].notna()]['date'].values#get a set of column "date" values
date_set = np.unique(date)

所以我做了一个快速的比较,并再次想到我们有一个赢家——Numpy 的独特。然而,在仔细测试了各种数据帧大小后,我意识到实际情况要复杂得多。

unique 和 drop_duplicates 方法之间的比较。作者创作的图表。

对于短数据帧,unique 比 drop_duplicates 快。随着数据帧变得越来越大,drop_duplicates 占了上风,其中相等点位于 10⁶行附近的某处(其中时间比正好是 1)。我试着看看列数的影响,得到了同样的结果。同样,对于所有大小,这两种方法的内存消耗是相似的。

unique 和 drop_duplicates RAM 消耗的比较。作者创作的图表。

结论

当我们想要执行操作时,我们每个人都有自己的 go-to 方法。通常还有另一个实现值得一试,它可能会节省您的时间和资源。
*使用 notna 删除 None 值。
*对于提取集合,这取决于您的典型数据帧长度:在 10⁵行下使用 unique。在 10⁶上空使用 drop_duplicates。

对于其他操作,检查替代方案,并让我知道你发现了什么!

两个动画情节的快速代码片段

原文:https://towardsdatascience.com/two-quick-codes-to-animate-your-plots-ecbaa99cd127

使用 plotly 或 gif 库在 Python 中创建动画图

艾萨克·史密斯在 Unsplash 上拍摄的照片

这不是我第一次发表关于数据可视化的文章。这当然是引起我注意的事情。我喜欢在探索新的数据集时可视化数据,因为我相信我们的大脑被设计成从图像中捕捉比从文本或数字中捕捉更多的信息。

我确信不仅仅是我这么想。麻省理工学院的科学家发现,我们的大脑可以识别 13 毫秒内看到的图像!你能相信吗?我怀疑文本或数字也能做到这一点。

嗯,想想一本杂志。什么最先吸引你的注意力?图片、信息图表还是文字?不过,我会把这个想法留给你。

愉快的

回到主题,在 Python 中有很多用于绘制图形的库。Matplotlib、Seaborn、Bokeh、Plotly,等等。我甚至在这里写了一个完整的帖子,关于为什么你应该探索和使用更多 plotly express 来探索数据,因为它有惊人的交互性。

好的,但是我们还能做些什么来使我们的演示更有影响力呢?

我想提出的选项是动画情节。有一点我可以告诉你,他们第一眼就能产生影响。每当我在 LinkedIn 上看到一个,我都会停下来看看。我认为他们在某些情况下讲故事讲得很好。

显然,这是一个受众和要传达的信息的问题。并非每个图形或数据集都适合动画情节。这是你应该为你的项目和报告做的评估。我认为它们对于时间序列来说非常有效。例如,线是如何在几个月内形成的。

Plotly Express

让我们现在编码。创建动画情节的第一个选项是使用我最喜欢的 Python 库之一: Plotly Express

import plotly.express as px
import pandas as pd
import numpy as np

让我们在数据集中创建一些值。

# create dataframe
df = pd.DataFrame( {'week': np.random.randint(1,21, size=200),
'P1': np.random.randint(10,220, size=200),
'P2': np.random.randint(15,200, size=200),
'P3': np.random.randint(10,490, size=200),
'P4': np.random.randint(10,980, size=200) } )df = df.melt(id_vars='week', var_name= 'product', value_vars= df.columns[1:], value_name='sales')
df = df.groupby(['week', 'product']).sales.mean().reset_index()
df = df.sort_values(by=['week', 'product', 'sales'])

现在,我们可以绘制一个动画条形图来检查每周产品的变化。

# Animated graphicfig = px.bar(df, x="product", y="sales", color="product",
animation_frame="week", animation_group="product", range_y=[0,750])
fig.update_layout(height=600, width=800)
fig.show()

这是你将看到的动画情节。图片由作者提供。

创建动画散点图也同样容易。

fig = px.scatter(df, x="week", y="sales", animation_frame="week", animation_group="product", size="sales", color="product", hover_name="product", range_x=[0,20], range_y=[0,800])fig.update_layout(height=600, width=1000)

用 plotly 创建的动画散点图。图片由作者提供。

gif 库

创建动画情节的第二种方法是使用gif库。这个库所做的是创建一系列的情节,并把它们放在一个帧序列中,创建一个动画 gif 图像。

首先,我们来获取一些时间序列数据进行绘图。我们使用著名的航空乘客数据集,这是一个我们可以直接从 seaborn 图书馆获得的开放数据。

import seaborn as sns
df = sns.load_dataset('flights')

接下来,我们创建一个函数,为每个观察值创建一个图。

# Create a plot function to create the frames@gif.frame
def plot_flights(df, i):df = df.copy()**# Get the year for the plot title**yr = df['year'][i]**# Force X axis to be entirely plotted at once**df.iloc[i:] = np.nan**#Plot**ax = df.plot(x='month', y= 'passengers', legend=False, style="o-", figsize=(20,10))ax.set_title(f"Air Passengers {yr}", size=20)ax.set_xlabel("Months")ax.set_ylabel("Number of Passengers")
  • @gif.frame是一个装饰器,由gif库用来创建帧序列。
  • df.iloc[i:] = np.nan将所有未来的观测值转换为 NA。这是一种编程方式,一次仅绘制一个值( i=0 所有 NAs,i=1,仅绘制索引 0,i=2,仅绘制 0 和 1…),并且 X 轴仍然从一端到另一端绘制,因此在动画过程中不会改变。剧情尺寸会保持不变,更容易观看。

现在我们使用函数创建一个循环来创建帧。

# Build the gif "frames"frames = []
for i in range(df.shape[0]):frame = plot_flights(df, i)frames.append(frame)

最后,保存生成的 GIF 图像。

# Choose the duration between frames (milliseconds) and save to file:gif.save(frames, 'gif_example.gif', duration=180)

用 gif 库绘制的航空乘客时间序列。图片由作者提供。

在你走之前

动画情节是一个好的演示文稿的有效资源。就像我们在这里展示的,时间序列,条形图和散点图很容易创建。

然而,并不是所有的情节都适合被动画化。你应该知道使用动画的最佳情况。并不是所有的公众都会喜欢这样,因为动画图形并不是深入分析的最佳选择,例如,当你需要一个静态图像来观察、比较和理解时。

要创建动画线条图,我建议你使用gif库,因为对于这种图形类型,它似乎比 plotly 更容易编码。如果您查看文档,您会发现动画线图需要使用 dash 模块进行更多的编码。另一方面,对于条形图和散点图,gor 对于plotly.express来说毫无疑问。

参考

https://plotly.com/python/animations/ https://pypi.org/project/gif/

如果这些内容对你有用,请关注我的博客。

https://gustavorsantos.medium.com/

如果你想阅读大量的好内容,可以考虑订阅 Medium。您可以使用此推荐代码。

两阶段分层随机抽样——解释清楚

原文:https://towardsdatascience.com/two-stage-stratified-random-sampling-clearly-explained-59788b110a84

借助实际案例理解两阶段随机抽样的复杂过程

斯科特·格雷厄姆在 Unsplash 上拍照

1.介绍

政府进行的大规模调查是获取人口/经济信息的重要工具。这些调查为社会经济规划提供了重要的投入。全国抽样调查办公室(NSSO)成立于 1950 年,负责进行全国范围的调查。在这种大规模的调查中,通常采用分层抽样的方法来收集调查数据。想过这意味着什么吗?这篇博客文章旨在通过参考全印度债务和投资调查(AIDIS)来澄清这一点,该调查使用两阶段分层抽样。所以继续读下去。

本文的其余部分分为以下几个部分:

  • 什么是艾滋病
  • AIDIS 中使用什么样的抽样技术
  • 什么是分层抽样
  • 什么是两阶段分层抽样
  • 乘数在分析 AIDIS 数据中的作用
  • 结论

2.什么是 AIDIS?

AIDIS 的目标是获得关于资产存量、负债情况、资本形成和其他经济指标的定量信息,这些信息对于评估经济的信贷结构具有价值。此外,它还有助于估计(I)家庭的信贷需求和(ii)机构和非机构代理人的信贷供应,以便制定银行政策。简而言之,它收集家庭资产和负债的基本定量信息。最新一轮(第 77 次)AIDIS 于 2019 年分两次访问进行。

  • 访问 1—2019 年 1 月至 2019 年 7 月(8 个月)
  • 访问 2—2019 年 8 月至 2019 年 12 月(4 个月)

第一次和第二次访问期间所提问题的参照期分别为 H2:2018 年和 H1:2019 年。

NSSO 进行的 AIDIS 是分层抽样技术的一个典型例子。我们将通过一个简单的例子来理解这种技术,并推断出理解更复杂的 AIDIS 采样的想法。

3.什么是分层抽样?

分层抽样背后的基本思想是将一个异质群体分成更小的群体或亚群体,这样,就所研究的特征而言,抽样单位是同质的。这些同质的群体统称为地层。每一组被认为是独立的,从每一层抽取预定大小的随机样本。

3.1 分层抽样的程序

a.将 n 个单位的总体分成 k 个同质组(地层),每个组包含 Nₖ单位。每个地层由非重叠单元组成,这些单元在所研究的特征方面是同质的,因此

b.从每个独立于其他地层的地层中随机抽取一个 nᵢ大小的样本。

c.从每一层抽取的所有抽样单位汇集在一起构成一个大小为𝑛的分层样本

分层抽样:作者图片

3.2 分层抽样的例子

让我们用一个例子来理解这个概念。假设我们有兴趣知道一个学校 1 班到 12 班学生的平均体重。一般来说,学生的体重因年龄而异。与高年级学生相比,低年级学生往往体重较轻。确定学生平均体重的一种方法可以是测量所有学生的体重,然后取平均值,然而,这需要大量的资源和时间。当学生数量非常大时,这肯定是不可行的。在这些情况下,分层抽样就派上了用场。人们可以把所有的学生分成不同的阶层,例如

一班的学生:阶层 1

二班学生:第二阶层

12 级学生:第 12 阶层

可以从每一层抽取预定大小的随机样本。所有抽取的样本组合在一起构成最终的分层样本。

让我们通过引入另一个层次的复杂性来扩展前面的例子。所有班级都有男生女生怎么办?女孩的体重通常与男孩不同。班级一级的随机抽样可能无法确保反映在班级一级人口中的正确的男女比例。这可能会使平均体重的估计有偏差。在这种情况下,在每一类中有一个性别层次的亚阶层可以使我们更接近实际的人口平均数。这仍然是一个单阶段分层抽样,因为抽样只发生一次,只是抽样的发生率进入了人口分层分配的更深层次,即从每个阶层到每个阶层的性别。

4.什么是两阶段分层抽样?

在两阶段分层抽样中,抽样在人口分层分配的两个不同层次上进行两次。为了更好地理解这一点,让我们考虑一下 AIDIS 调查的抽样过程。第一阶段单元(FSU)是村庄/街区,取决于农村/城市区域。第二阶段单元是这两个部门的家庭。艾滋病信息系统的抽样程序列举如下:

第一阶段

  1. 出于抽样目的,每个地区被视为一个阶层。在每个区内,农村和城市地区被视为亚阶层。
  2. 在每个农村亚层中,根据其人口形成了许多第一阶段单元(FSU ),因此每个 FSU 的人口在 1000-1200 之间大致相同(根据 2011 年人口普查)。在每个城市次级阶层内,家庭支助股的组成也是这样,住户数量大约等于 250 户。
  3. 从农村和城市每个阶层的每个子阶层中,通过简单随机抽样(SRSWOR)方案选择所需数量的 FSU。

第一阶段的取样过程通过以下信息图进行了说明:

AIDIS 分层抽样阶段 1:作者提供的图像

第二阶段

  1. 考虑到 MPCE 和家庭负债情况,在选定的 FSU 农村/城市地区形成了六个第二阶段阶层。
  2. 抽样 FSU 的所有家庭都被列入清单,并被分配到六个社会保障系统中的一个。
  3. 从每个第二阶段阶层(SSS)中选取所需数量的样本家庭,采用无替换分层随机抽样法(SRSWOR)。

下面的信息图展示了第二阶段的取样过程:

AIDIS 分层抽样阶段 2:作者提供的图像

从上面的过程可以看出,取样分两个阶段进行,这一点再强调也不过分:

1。FSU 的选择:从每个阶层内的农村和城市部门的所有子阶层中,通过简单随机抽样无替换(SRSWOR)方案选择所需数量的 FSU。

其中 N 是任何亚层中 FSU 的总数,N 是在该特定亚层中调查的样本 FSU 的数量。

2。住户的选择:每个第二阶段阶层(SSS)的样本住户都是通过无替换分层随机抽样(SRSWOR)选择的。

其中 H 是在一个选定的 FSU 样本的特定社会抽样调查中列出的家庭总数,H 是在那个 FSU 样本的社会抽样调查中调查的家庭数目。

5.乘数的作用

所有样本家庭汇集在一起,给了我们在调查数据中看到的样本。从上述过程中可以明显看出,调查数据中的每个家庭代表了一组在 MPCE 和负债方面具有相似特征的家庭。

乘数可以被认为是每个调查实体从人口中代表的家庭的大概数目。因此,在汇总调查数据以得出人口属性时,适当的权重变得很重要。例如,调查实体的简单平均数/平均值不能反映人口平均数/平均值,因为它给予所有被调查家庭同等的权重。因此,在估计总体均值/平均数时,有必要使用乘数作为权重进行加权平均。

6.结论

在这篇博文中,我讨论了两阶段分层抽样程序,并使用全印度债务和投资调查的实际用例进行了说明。还有哪些调查使用了这种抽样技术——请在下面留下您的评论。

在我们结束之前,

我邀请你和我一起参与这个激动人心的数据科学旅程。关注我的中页,探索更多关于数据科学的精彩内容。

免责声明:观点为个人观点。

参考

  1. 采样理论,模块四,NPTEL
  2. 采样理论,模块 X,NPTEL
  3. NSS 第 77 轮样本设计和估计程序说明
  4. 现场工作人员须知—第 1 卷
  5. 现场工作人员须知—第 2 卷

用 Scikit-learn 创建自定义变压器的两种方法

原文:https://towardsdatascience.com/two-ways-to-create-custom-transformers-with-scikit-learn-b9089acacd37

照片由 v2osk 在 Unsplash 上拍摄

数据科学基础

提升你的预处理技能

预处理是构建成功的机器学习模型的重要部分。Scikit-learn 的开箱即用的变形器如OneHotEncoder和它的其他对象如Pipeline使这个过程变得容易和简化。然而,当我们需要以定制的方式对数据进行预处理时,这些预构建的转换器有时是不够的。在这些情况下,我们可以用 Scikit-learn 构建定制的转换器来满足我们的定制数据预处理需求。在这篇文章中,我们将熟悉两种方法来创建这样的自定义变压器。

由 David Marcu 在 Unsplash 上拍摄的照片

设置📦

让我们从导入库和创建样本数据集开始:

我们有两个分类特征来保持简单和集中。我们现在将对数据进行分区,并构建一个简单的管道:

让我们想象一下,我们想要通过以下额外的预处理步骤来构建这条管道:
1️⃣ 分组类别:分组b_b 2️⃣ 一起减少基数:将少数类别分组在一起

我们保持了简单的预处理步骤,这样我们就可以专注于构建定制的转换器。让我们检查一下这些特征的出现频率:

对于这两个预处理示例,我们将看看如何使用
1️⃣ FunctionTransformer
2️⃣ BaseEstimatorTransformerMixin构建定制的转换器。

📍示例 1:分组类别

在本节中,我们将重点关注feature1,因为feature2没有名为b_的类别。让我们首先手动预处理数据,以说明我们想要实现的目标:

左边是训练数据中特征 1 的频率,右边是测试数据

我们可以看到b_已经和b分在了一起。现在让我们用FunctionTransformer将它转换成一个定制的转换器:

左边是训练数据中特征 1 的频率,右边是测试数据

我们传递了一个预处理函数,并在kw_args中指定了参数。我们可以看到FunctionTransformer的名字是不言自明的:它将函数转换为转换器。

让我们看看另一种方法。我们现在将创建继承了BaseEstimatorTransformerMixinCategoryGrouper对象。通过继承这些父类并定义__init__()fit()transform()方法,我们得到了一个定制的转换器:

左边是训练数据中特征 1 的频率,右边是测试数据

很好,我们得到了相同的结果。对于这个预处理任务,两种方法都同样有效。然而,情况并非总是如此。这个特定的预处理任务被认为是无状态的转换,在训练期间什么也没有学到。我们可以看到CategoryGrouper中的fit()方法只是return self。在一些预处理任务中,我们需要做一个 有状态 转换在训练过程中学习到的信息。在下一个例子中,我们将看到这两种方法在有状态转换中的不同之处。

📍示例 2:减少基数

让我们检查变量的频率:

我们将使用 20%的任意阈值来定义不常见的类别,并将少数类别组合在一起。由于feature1的类别高于阈值,我们将在本节重点关注feature2。让我们首先手动转换它:

我们将用FunctionTransformer:将它翻译成一个定制的转换器

预处理的测试数据看起来与我们预期的不同。看起来在培训中没有学到最重要的类别。这个例子说明了为什么FunctionTransformer不应该用于有状态转换。

让我们看看另一种方法。在本例中,我们将创建一个名为find_top_categories的额外方法,使代码更有条理:

这个输出看起来是正确的!这种使用BaseEstimatorTransformerMixin的方法更加通用,可以用于任何转换。

📍超参数调谐

通过创建定制的变压器,我们可以用 Scikit-learn 的 [Pipeline](/pipeline-columntransformer-and-featureunion-explained-f5491f815f)集中并简化我们的预处理步骤。创建自定义转换器的另一个好处是,我们可以微调模型和其他转换器的参数。让我们看一个例子:

学会了两种制造定制变形金刚的方法,你可以制造的变形金刚的范围是无限的!这项技能将派上用场,尤其是在将 ML 管道部署到生产环境中时。如果您想了解更多关于 Scikit-learn 的数据转换,请查看本文档。

汤姆·亨德森在 Unsplash 上拍摄的照片

您想访问更多这样的内容吗?媒体会员可以无限制地访问媒体上的任何文章。如果您使用 我的推荐链接成为会员,您的一部分会费将直接用于支持我。

谢谢你看我的帖子。如果你感兴趣,这里有我的一些帖子的链接:
◼️️ 管道,ColumnTransformer 和 FeatureUnion 解释
◼️️ FeatureUnion,ColumnTransformer &管道用于预处理文本数据
◼️ 用这些提示丰富你的 Jupyter 笔记本
◼️ 用这些提示组织你的 Jupyter 笔记本
◼️ 解释 scikit-用 SHAP
◼️️ 特性选择学习模型

再见🏃💨

如何将文本文件中的数据加载到 Pandas

原文:https://towardsdatascience.com/txt-to-pandas-df3aeaf92548

用 Python 将文本文件中存储的数据加载到 pandas 数据帧中

布鲁斯洪在 Unsplash 上的照片

Pandas 是事实上的 Python 包,允许用户在内存中执行数据转换和分析。在许多情况下,这些数据最初驻留在外部源中,如文本文件。通过其强大的 API,pandas 允许用户通过各种方法从这些来源加载数据。

在今天的文章中,我们将演示如何使用这些方法将文本文件中的数据加载到 pandas 数据帧中。此外,我们将讨论如何处理分隔符和列名(也称为标题)。

首先,让我们创建一个名为employees.txt的示例文本文件,我们将在今天的简短教程中使用它来演示一些概念。请注意,字段由单个空格字符分隔,第一行对应于标题。

name surname dob department
George Brown 12/02/1993 Engineering
Andrew Black 15/04/1975 HR
Maria Green 12/02/1989 Engineering
Helen Fox 21/10/2000 Marketing
Joe Xiu 10/11/1998 Engineering
Ben Simsons 01/12/1987 Engineering
Jess Middleton 12/12/1997 Marketing

使用 read_csv()

acommasseparatedffile(CSV)实际上是一个文本文件,它使用逗号作为分隔符来分隔每个字段的记录值。因此,使用[pandas.read_csv()](https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html)方法从文本文件加载数据是有意义的,即使文件本身没有.csv扩展名。

为了读取我们的文本文件并将其加载到 pandas DataFrame 中,我们只需要向read_csv()方法提供文件名、分隔符/定界符(在我们的例子中是一个空格)和包含列名的行,这似乎是第一行。

import pandas as pddf = pd.read_csv('employees.txt', sep=' ', header=0)print(df)***name    surname         dob   department*** *0  George      Brown  12/02/1993  Engineering
1  Andrew      Black  15/04/1975           HR
2   Maria      Green  12/02/1989  Engineering
3   Helen        Fox  21/10/2000    Marketing
4     Joe        Xiu  10/11/1998  Engineering
5     Ben    Simsons  01/12/1987  Engineering
6    Jess  Middleton  12/12/1997    Marketing*

请注意,如果您要加载为 pandas DataFrame 的文件有不同的分隔符,比如逗号,、冒号:或制表符\t,那么您需要做的就是在调用read_csv()时在sepdelimiter参数中指定该字符。

使用 read_table()

或者,您可以利用[pandas.read_table()](https://pandas.pydata.org/docs/reference/api/pandas.read_table.html)方法将通用分隔文件读入 pandas 数据帧。

import pandas as pd df = pd.read_table('employees.txt', sep=' ', header=0)print(df)***name    surname         dob   department*** *0  George      Brown  12/02/1993  Engineering
1  Andrew      Black  15/04/1975           HR
2   Maria      Green  12/02/1989  Engineering
3   Helen        Fox  21/10/2000    Marketing
4     Joe        Xiu  10/11/1998  Engineering
5     Ben    Simsons  01/12/1987  Engineering
6    Jess  Middleton  12/12/1997    Marketing*

最后的想法

创建 pandas 数据框架的最常见方法之一是加载存储在外部源中的数据,如文本或 csv 文件。在今天的简短教程中,我们一步一步地完成了一个过程,通过从一个文本文件中加载数据,最终可以帮助您构建一个熊猫数据框架,其中每个字段都由一个特定的字符(制表符、空格或其他字符)分隔。

成为会员 阅读媒体上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。

https://gmyrianthous.medium.com/membership

相关文章你可能也喜欢

typer——可能是最容易使用的 Python 命令行接口库

原文:https://towardsdatascience.com/typer-probably-the-simplest-to-use-python-command-line-interface-library-17abf1a5fd3e

图片来自 Pixabay

它站在一个巨人的肩膀上——点击

上周,我介绍了 Python 内置模块之一——arg parse。它允许我们非常容易地开发命令行界面工具。我们可以用它来定义位置参数和可选参数。它也有现成的帮助说明,所以我们只需要为每个参数写一个字符串。如果你错过了那篇文章,在这里。

该模块确实易于使用,但也有一些缺点。因此,在本文中,我将介绍一个名为 Typer 的第三方库,我认为它可能是开发 Python CLI 程序的最佳工具之一。

Argparse 的缺点

图片来自 Pixabay

正如我在上面的文章中提到的,Argparse 模块有一些缺点。

1.它有一种内在的行为来猜测某件事是一个论点还是一个选项。当处理不完整的命令行时,这就成了一个问题。这意味着在不完全了解命令行的情况下,行为可能是不可预测的。

2.当我们需要一些更高级的特性,比如参数值的复杂验证时,它不是很容易使用。

著名的第三方库 Click 很好地解决了这些问题。

这篇文章的主角 Typer 是建立在 Click 之上的。甚至比 Click 还好用。此外,已经有太多关于 Click 的教程,所以我宁愿介绍一些不太受欢迎但更有潜力的东西。

值得一提的是 Typer 是在 Click 之上开发的。因此,它继承了 Click 几乎所有的优点,但进一步简化了使用。

1.快速启动

图片来自 Pixabay 的 WikiImages

首先,我们需要安装 Typer 库。像往常一样,我们可以使用皮普。

pip install typer

我们可以清楚的看到 Click 是 Typer 唯一的依赖,它会被一起安装。

样板文件

现在,让我们使用与上一篇文章相同的 Argparse 示例。假设我们想开发一个简单的 Python CLI 程序来发送问候消息。因此,用户需要输入姓名。

Python 脚本文件greeting.py的代码如下。

import typerdef main(name: str):typer.echo(f"Hello {name}!")if __name__ == "__main__":typer.run(main)

对上面的代码做一点解释。首先,我们需要导入 Typer 库。

然后,我们定义一个main()方法,它将成为命令行程序的入口点。这需要一个叫做name的论点。此外,鼓励使用类型提示来控制参数的类型。

最后,我们只需要告诉 Typer 我们希望在程序启动时运行main()方法。

这比 Click 容易(当然比 Argparse 容易多了)。我们甚至不需要做“添加选项”这样的事情。Typer 将足够聪明地从main()方法的参数中找出选项和标志。

为什么是“回声”?

你可能会注意到,我们使用typer.echo()输出,而不是普通的print()方法。Typer 中的这个特性是从 Click 库中继承的。

使用echo()方法的好处是 Click 试图一致地支持不同的环境,即使环境配置错误也非常健壮。Click 希望至少在基本水平上可以正常工作,即使一切都完全坏了。

因此,我们也应该尝试使用 Typer 中的echo()方法来拥有这个特性。如果不需要该特性,没有什么可以阻止我们使用print()方法。

示范

现在,如果我们在没有任何参数的情况下运行这个脚本文件,Typer 将生成错误消息,指出我们错过了一个参数。

$ python greeting.py

当然,它也会为我们生成帮助文档。

$ python greeting.py --help

现在,让我们通过把我的名字传递给程序来实现它。

$ python greeting.py Chris

我们可以使用这几行代码作为基于 Typer 的 Python CLI 项目的样板。

2.可选参数

图片来自 Pixabay

您可能会注意到,在样板示例中,name参数是必需的。如果我们想让一个论点可选呢?

这就像我们通常用 Python 编码一样简单,只需给它一个默认值。

def main(name: str, verbose: bool=False):typer.echo(f"Hello {name}!") if verbose:typer.echo("Program Finished.")

我们在main()方法中添加了一个布尔参数verbose,并提供了一个默认值。如果我们调用帮助文档,标志将自动生成。

因此,我们可以很容易地使用布尔参数来控制程序行为,如下所示。

3.嵌套命令

来自 Pixabay 的 Couleur 的图像

作为 Click 最重要的雄心勃勃的功能之一,嵌套命令也受到 Typer 的支持。不仅支持而且简化了它的使用。

嵌套命令允许我们在 CLI 应用程序中定义“子命令”。例如,我们可能希望我们的greeting.py程序也有说再见的功能。使用嵌套命令,我们可以很容易地将其与“hello”功能分开定义。这使得我们的代码整洁干净。

import typermain = typer.Typer()[@main](http://twitter.com/main).command()
def hello(name: str, verbose: bool=False):typer.echo(f"Hello {name}!")if verbose:typer.echo("Program Finished.")[@main](http://twitter.com/main).command()
def bye(name: str, verbose: bool=False):typer.echo(f"Goodbye {name}!")if verbose:typer.echo("Thank you for using this program.")if __name__ == "__main__":main()

如代码所示,我们实例化了一个 Typer 对象,并将其命名为main。然后,我们定义两个方法并将它们注册为命令。最后,我们需要将 Typer 对象称为方法。

如果我们调用帮助文档,它现在看起来如下。

指示命令hellobye。我们可以调用其中一个子命令的帮助消息来查看它的参数。

$ python greeting.py bye --help

借助嵌套命令功能,我们可以为非常复杂的 CLI 选项需求定义带有子命令的简单方法。

摘要

图片由来自 Pixabay 的 Erik Karits 拍摄

在本文中,我介绍了一个用于开发 CLI 程序的第三方 Python 库。它极大地简化了我们定义带有选项和标志的 CLI 应用程序所需编写的代码。虽然它非常直观且易于使用,但它也具有可伸缩性,允许我们编写相对复杂的应用程序。

https://medium.com/@qiuyujx/membership

如果你觉得我的文章有帮助,请考虑加入灵媒会员来支持我和成千上万的其他作家!(点击上面的链接)

2022 年数据科学家的典型 8 步 A/B 测试工作流程

原文:https://towardsdatascience.com/typical-9-step-a-b-test-workflow-for-data-scientists-in-2022-d672c9a0d658

数据科学家在科技公司的 A/B 测试项目中所做工作的总结

来源:斯蒂芬·道森在 Unsplash 上的文章

太长;不要读

  • 实验启动前: (1)思考产品&与产品经理交谈;(2)设计实验;(3)检查 A/A 变化 https://medium.com/p/d672c9a0d658#7ec6 。
  • 实验期间: (4)检查不变度量&实验设置;(5)监控关键评估指标。
  • 实验结束后: (6)读取 p 值、差值大小、最小可检测效应(MDE https://medium.com/p/d672c9a0d658#55e1),惊讶时潜得更深;(7)制定产品建议;(8)写报告。

介绍

本文旨在详细讨论数据科学家在 A/B 测试任务中具体应该做什么,以及哪一个应该是主要关注点。最好是已有 t 检验、最小样本量计算等 A/B 检验知识,并有兴趣学习实际工作场景中的流程的从业者。

我为什么要写这个?/我们为什么要关心?

我觉得写这篇文章很有动力,因为我意识到在科技公司的日常实验实践和来自学校的 A/B 测试知识之间仍然存在差距。尽管理论论证全面,但大多数在线课程或文章,甚至是谷歌在 Udacity 上进行的著名的 A/B 测试,都无法将这些点联系起来,并提出一个关于数据科学家如何在 A/B 测试中应用统计知识的整体程序。

此外,数据科学家如何进行 A/B 测试在最近几年发生了很大变化,从或多或少的体力劳动转变为广泛使用不同的工具。然而,管理工具可能令人望而生畏,尤其是当一个数据科学家被期望负责多个 A/B 测试时。

强调了清楚理解新工作流程的必要性,以及 A/B 测试工具的进步,这有助于我们保持组织性并提前计划。虽然受欢迎的 A/B 测试平台承担了大部分计算工作,包括但不限于样本大小和 p 值,将数据科学家从列出统计公式和手工计算数字中解放出来,但至关重要的是,我们作为数据科学家将大部分时间花在思考产品和实验本身上,而不是被困在复杂的工具中来回重复工作。

来源:卡洛斯·穆扎关于 Unsplash

详细展开:典型的工作流程

在实验开始之前

1)思考产品&与产品经理(PM)交谈

从我的角度来看,这是整个 A/B 测试工作流程中最重要的事情。有两个关键方面需要彻底评估:可行性影响。在详细探索了产品逻辑和用户旅程之后,我们将非常批判性地思考使用随机在线 A/B 测试来测试这个特性是否合适,甚至是否可行。要问的几个问题包括“ 假设是什么 ”、“ 我们能找到可计算的指标来测试吗?我们的产品和用户有哪些风险?有网络效应吗?是否有可能让用户表现出强烈的新奇效果或者变化厌恶让部分用户远离我们的新功能待遇是否公平等。

此外,作为数据科学家,我们必须是影响驱动的而不是输出驱动的,这意味着我们应该关注这项功能测试的潜在收益或投资回报(ROI)。 我们为什么需要这个? “,” 我们的预期收益是什么,是短期的还是长期的? “是我们应该问自己并与产品经理辩论的一些关键问题。

为什么我们需要把按钮换成更亮的颜色?衡量标准是什么(例如,两个按钮的点击率,落选率)?会不会太打扰用户?长期有益吗?资料来源:Leanplum

2)设计实验

基于 1)中的假设,我们作为数据科学家为产品经理和工程师提供设计实验的建议,包括但不限于:

  • 评估指标:我们的北极星指标、直接影响指标、护栏指标和治疗组的采用指标是什么。除了业务解释之外,评估指标最好具有较低的方差对变化的敏感性
  • 分流单位:我们是基于 user_id 还是 session_id 随机化测试?
  • 最小所需样本:对于不同的评估指标和各种最小可检测效应(MDE),要使结果具有统计显著性,所需的样本量是多少?如今,大多数大型科技公司都提供了成熟的工具来计算样本大小,但是,当感兴趣的指标过于具体且不被 A/B 测试平台支持时,我们往往不得不检索历史数据并自行计算
  • 实验持续时间&流量部分:这通常取决于所需的样本大小和可能看到新功能的合格流量大小。我们也将考虑到实验的风险。
  • 实验层&实验类型:此外,我们将确定在哪个实验层部署我们的 A/B 测试。常见的情况是,大多数实验层已经被其他测试填充,导致剩下的流量非常少。在这种情况下,如果我们的特征与其他特征不相关,我们可能希望选择另一层或考虑正交实验。

3)检查 A/A 变化

与以往不同的是,2022 年大部分 A/B 测试平台已经将这一过程自动化,需要的劳动力非常少。我们需要做的是检查模拟/模拟变化输出,并确保一切如预期一样——模拟/模拟的 p 值是均匀分布的。

在实验中

4)检查不变指标&实验设置

这正是我们开始将我们的分析从离线过渡到在线 A/B 测试平台的阶段。在实验过程中,数据科学家要做的第一件事就是反复检查实验是否设置良好。具体来说,我指的是确保处理组中的用户看到所有预期的处理过的特性。在科技公司中有很多这样的例子,实验参数是错误的,没有人及时发现,导致时间和流量的巨大损失。这在广告场景中更糟糕,我们使用广告商购买的流量进行 A/B 测试,但最终由于错误的参数(个人经验…)而破坏了实验

第二件事是探索不变指标并确保两件事:(1) 交通的转向是随机的 ( 我们通常使用卡方检验来检查对照组和治疗组的总体);(2) 用户资料(如性别、年龄)在不同群体中的分布是一致的(大多数平台会为我们计算这些资料,点击几下就可以轻松显示出来)。

5)监控关键评估指标

尽管样本通常不足以解释最初几天的统计意义,但有必要密切跟踪治疗组和对照组之间的描述性差异。如果关键指标和护栏指标持续大幅下降,为了我们的产品和用户的利益,我们应该考虑后退一步,重新评估实验,因为负面的显著结果很可能只是时间问题。

实验结束后

6)读取 P 值&差值的大小&最小可检测效应,惊讶时更深入

数据科学家收集治疗组和对照组之间的描述性差异以及 p 值。此外,我建议重新审视我们的产品假设评估我们是否对实验结果感到惊讶。如果没有,我们可以进行下一步。但是,如果我们确实发现了超出我们预期的东西(例如,差异应该是显著正的,但不是显著或显著负的),我们应该通过将其分解为关键业务维度(例如,新/老用户、渠道、地理区域)来更深入地了解原因。

值得注意的是,结果不显著的常见原因是统计能力不足。除了增加样本量,我们还可以用较小方差或CUPED(使用已有数据的受控实验 ) 方法尝试其他指标。此外,比较经验 MDE ( 为了产生显著结果,差异应该有多大,通常由平台提供)和关键指标的实际差异通常会揭示指标的不稳定性。这在未来研究用户行为或设计类似实验时是一个很好的参考。

7)制定产品建议

收集完所有数据后,我们可以开始制定实验结论,并得出产品建议,即我们是应该逐渐将更多流量用于治疗,还是选择更保守的策略。

8)写报告

最后,数据科学家需要撰写并归档一份报告,内容包括(1)产品背景和特性假设;(2)实验辞职;(3)结果分析;以及(4)产品推荐。

摘要

上述 8 步 A/B 测试工作流程有助于最大限度地减少实质性错误,并确保可信的受控实验。不同的科技公司可能在细节上有所不同,但这可以被视为一个总体框架,以便我们可以节省更多的时间探索重要的产品问题(这是数据科学家的核心价值观之一,对个人成长最有成效),而不是解决程序问题

注意

  1. A/A 变异: A/A 变异是一个对照组(A 组)和另一个对照组(~ A 组)之间关键指标的差异。由于两者之间没有实际差异,A/A 变化预计不会以统计上显著的方式偏离 0。
  2. 最小可检测效应(MDE): MDE 衡量的是我们希望在受控实验中检测到的相对于基线的最小改善。

优步数据科学家面试问题演练

原文:https://towardsdatascience.com/uber-data-scientist-interview-question-walkthrough-60b7ccdcb592

在本帖中,我们将仔细研究一个来自优步的数据科学家面试难题,并向您展示一个 Python 解决方案

作者在 Canva 上创建的图像

与当今的其他科技巨头相比,优步是一家相对年轻的公司。在 2009 年成立并于一年后推出拼车应用后,优步迅速开始成为出租车和出租车的替代品。今天,该公司的服务遍及全球 500 多个城市。

在其公司分支机构内,优步有一个专门的数据科学和分析部门,并进一步划分为专注于安全和保险、骑行、风险、政策、平台或营销的团队。这些团队不仅为关键的拼车应用服务,也为优步 Eats 等公司提供的一些新产品服务。该部门一直在寻找新的数据科学家和产品分析师,这些机会在全球多个地方都有。

在本帖中,我们将仔细研究优步数据科学家面试中的一个难题,并带您了解 Python 中的解决方案。事实证明,只要找到几个简单而聪明的步骤,这个优步数据科学家面试问题就变得更容易解决了。

优步数据科学家面试问题

截图来自 StrataScratch

问题链接:https://platform . stratascratch . com/coding/2046-maximum-number-of-employees-reached?python=1

这个优步数据科学家采访问题的标题是达到的最大员工数,我们被要求编写一个查询,返回曾经为该公司工作过的每个员工。然后,对于每个员工,我们应该计算在他们任职期间为公司工作的员工的最大数量以及达到该数量的第一个日期。员工的离职日期不应该被算作工作日。

该问题还指定输出应该包含员工 ID、在员工任职期间为公司工作的最大员工数量以及达到该数量的第一个日期。

这是优步数据科学家面试中最难的问题之一,因为它涉及到操纵日期,本质上是调查员工数量如何随时间变化。

为了尝试解决这个优步数据科学家面试问题,让我们坚持解决数据科学问题的一般框架。其思想是首先理解数据,然后通过编写几个可以引导我们找到解决方案的一般步骤来制定方法,最后,编写基于高级方法的代码。

了解您的数据

让我们先来看看为这个面试问题提供的数据。通常,在面试中你不会得到任何实际的记录,相反,你会看到有哪些表格或数据框,以及这些表格中有哪些列和数据类型。但是,在这里,我们可以预览一个数据可能是什么样子的例子。

在本例中,只有一个名为 uber_employees 的表,它看起来相当简单。表名和列名表明这是某个公司的雇员列表,每行对应一个雇员。对于他们中的每一个人,我们都有一个名,一个姓,一个 ID,我们可以看到这是一个整数。还有雇员受雇的日期和合同终止的日期,两者都是日期时间数据类型,还有雇员的工资。

截图来自 StrataScratch

一开始可能不明显并且在问题中没有指定的一件事是,对于仍然在公司工作的雇员来说,termination_date 列的值是多少。我们可以对此做一个假设,例如,在这些情况下,termination_date 将为空或者将具有空值。这样的假设还会导致我们假设每次 termination_date 列为空或为空值时,对应的雇员今天仍在那里工作。

因为这个问题没有给出任何解释,所以我们可以在面试时做出这样的假设,并解决这个问题,只要我们把这些假设清楚地告诉面试官。分析数据的第一步正是我们应该做出和传达假设的时候。

制定方法

研究完数据后,下一步是制定解决优步数据科学家面试问题的高级步骤。我们的目标是当雇佣和终止日期发生变化时,将其转换为每个日期的员工总数。

  1. 要实现这一点,第一步可能是列出来自“雇用日期”列的所有日期,并添加一个新列,其中每行的值为 1。这仅仅意味着在这些日期,当某个雇员的雇佣日期,公司得到 1 个新雇员,因此值为 1。
  2. 我们可以对 termination_date 列做类似的事情—让我们列出该列中的所有日期,并创建一个用值'-1 '填充的新列。与之前相同,值 1 意味着公司获得了一名新员工,在这种情况下,在某个员工的终止日期,公司将失去一名员工。
  3. 接下来,我们可以将两个列表连接在一起。换句话说,我们将获取雇佣日期列表并在末尾追加终止日期列表。这样,我们将获得一个很长的日期列表,每个日期的值要么是 1,表示获得一个新雇员,要么是-1,表示失去一个雇员。
  4. 第四步,让我们聚合或分组这个列表,这个列表是在对 1 或-1 值求和时按日期获得的。我们这样做是因为在同一天可能有几个雇员被雇用。或者有一天,一些员工被雇佣,一些员工的合同被终止。这种聚合的目标是获得一个日期和一个值,该值表示当天雇员数量的变化。如果雇佣了更多的员工,这个数字可以是正的;如果终止了更多的合同,这个数字可以是负的;如果公司得到和失去了相同数量的员工,这个数字甚至可以是零。
  5. 有了雇员数量在不同日子里如何变化的列表,我们将能够计算一个累积和,以获得公司在任何时间点的雇员总数——这正是我们所寻找的。然而,在这样做之前,从最早的日期到最近的日期对我们的列表进行排序是至关重要的,这样累积的总和才有意义。
  6. 现在,我们大致了解了员工数量如何随时间变化,并且我们仍然知道该数量发生任何变化的日期。这就离解决面试问题不远了。但是在我们得出最终答案之前,还有一个小问题需要解决——一些仍在公司工作的员工在 termination_date 列中没有任何值。为了使进一步的操作更容易,我们可以用今天的日期替换空值。
  7. 下一步是我们实际解决这个优步数据科学家面试问题的地方。我们可以使用日期列表和员工数量变化的累计总和来查找每个员工在雇佣日期和终止日期以及发生日期之间的最大员工数量。由于相同数量的员工可能在一个员工的任期内出现过几次,因此找出发生的最早日期很重要,因为这是问题所要求的。
  8. 在这一步之后,我们已经有了面试任务的解决方案,因此最后一步将是根据问题的规格调整输出—输出员工 ID、相应的最高员工数以及达到该数字的第一个日期。

代码执行

定义了一般和高级步骤后,我们现在可以使用它们来编写代码,以解决这个优步数据科学家面试问题。我们需要从导入一些我将在代码中使用的 Python 库开始。最重要的一个是 Pandas,它是用于操作数据的库,允许对数据表执行各种操作,类似于 SQL。另一个库叫做 datetime,一旦我们需要今天的日期,我将使用它。

import pandas as pd
import datetime as dt

第一步是列出所有的雇佣日期,并添加一个值为 1 的列。列出日期并不困难,因为这些日期已经存在于原始表中,所以我们可以从表 uber_employee 中获取列‘hire _ date ’,并创建一个只有该列的新的 Pandas DataFrame。让我们称这个新的数据框架为雇佣日期

hired_dates = pd.DataFrame(uber_employees['hire_date'].rename('date'))

然后添加一个填充 1 的列也很简单,让我们称这个新列为“值”,写这个列等于 1 就足够了。

hired_dates['value'] = 1

截图来自 StrataScratch

这是结果,表中的每一行基本上意味着在这一天我们得到了一个新员工。当然,如果雇佣了不止一个员工,同一个日期可能会出现在几行中,但是我们现在可以这样保留它。第二步,列出所有的 termination_date 日期并添加一个值为-1 的列,可以用同样的方法解决。基于“终止日期”列创建一个名为“终止日期”的新数据帧,并创建一个名为“值”的新列,这次用-1 填充。

terminated_dates = pd.DataFrame(uber_employees['termination_date']
.rename('date'))
terminated_dates['value'] = -1

结果看起来与前面的情况相似,但是正如您所看到的,相当多的行没有日期。这是因为,如果员工仍然为公司工作,并不是所有的员工都有终止日期。为了清理一下,我们可以使用熊猫函数。dropna(),其中 na 代表空值,用于删除所有包含 null 而不是 termination_date 的行。

terminated_dates = terminated_dates.dropna()

这看起来更好,与前一个案例类似,每一行都意味着公司在某一天失去了一名员工,同样,这些日期可能会重复,但这没关系,我们稍后会处理它。现在,让我们进入第三步,即“连接日期列表”。我们想要实现的是一个单一的列表,所有的日期在一列中,值为 1 或-1 在秒列中。换句话说,我们将获取雇佣日期数据帧,并将终止日期数据帧粘贴在其底部。为了实现这一点,让我们使用 Pandas concat 函数。

all_dates = pd.concat([hired_dates, terminated_dates],
ignore_index=True)

我们说过,雇佣日期和终止日期应该连接在一起,并将 ignore_index 参数设置为 true。这个参数告诉 Pandas 在结果数据帧中重置索引。如果没有它,雇佣日期的第一行将具有索引 0,但是终止日期的第一行也将具有相同的索引。如果我们忽略或重置索引,那么每一行都会再次获得一个唯一的索引。在这种情况下,不会有太大的变化,但是在这种情况下,将 ignore_index 设置为 true 是一个很好的做法。

这个新表 all_dates 的格式完全符合我们的要求。是时候进入下一步了:按日期汇总列表。可以使用 Pandas groupby()函数执行聚合,并指定应该根据哪一列来聚合数据。

all_dates = all_dates.groupby('date')

但是除了聚合之外,我们还应该指定应用于表中其他值的聚合函数。在我们的例子中,我们希望看到在每个日期雇员的数量是如何变化的,因为可能有一些雇员被雇用,一些雇员被解雇。帮助我们实现这一点的函数是 SUM()函数,因为我们希望将每个日期的所有 1 和-1 值相加。

all_dates = all_dates.groupby('date').sum()

现在,带有日期的列从输出中消失了,因为它变成了表的索引,而索引现在没有显示给我们。为了改变它,并且仍然能够容易地访问日期,让我们添加一个 Pandas reset_index()函数。它的工作方式类似于我们前面看到的 igonre_index 参数,但是现在实际使用它更重要。一般来说,当在 Pandas 中聚合数据时,总是对结果应用 reset_index()函数是一个很好的实践。

all_dates = all_dates.groupby('date').sum().reset_index()

查看结果,我们可以看到不再有重复的日期。这些值代表每天员工数量的变化。例如,2009 年 2 月 3 日,公司多了 4 名员工,4 月 15 日比前一天多了 1 名员工。在 2013 年 7 月 20 日,相同数量的员工被雇用和解雇,因此变化为 0。如果有一天终止的合同数量超过了新雇佣的员工数量,这也可能是负面的。

继续下一步,是时候按日期对列表进行排序并应用累积和了。排序相当简单——可以使用 Pandas 函数 sort_values()并指定应该对哪一列进行排序。默认情况下,该函数将按照升序或从最早到最新的顺序对值进行排序,因此不需要添加任何其他参数。

all_dates = all_dates.sort_values('date')

计算累积和也很简单,因为还有一个熊猫函数可以帮我们完成。我们只需将其应用于“值”列,并将结果存储在一个名为“emp_count”的新列中,用于计算雇员数。

all_dates['emp_count'] = all_dates['value'].cumsum()

现在,除了员工数量的变化之外,我们还可以看到某一天的实际员工数量。它遵循先前的值,这些值指定了它增加或减少的量,或者有时保持不变。

及时准备好员工数量的概览后,我们现在可以分别查看每个员工,并使用我们的列表提取他们的必要信息,即员工任职期间的最大数量以及发生的日期。为了使事情更容易解释,我们现在只对表中的第一个雇员这样做,即索引为 0 的雇员。

首先要做的是检查这个雇员是否有一个 termination_date,如果没有,用今天的日期替换空值。第一个雇员是否有终止日期并不重要,我们将编写一个 if 语句来涵盖这两种情况。要检查索引为 0 的雇员的 termination_date 列的值,我们可以使用 Pandas。像这样的属性:

if uber_employees.at[0, 'termination_date']

现在我们想说,如果这个值为空,那么我们想做点什么。因为这个值在 Pandas 数据帧中,所以我们需要通过写 pd 来使用 Pandas 空值进行比较。NaT:

if uber_employees.at[0, 'termination_date'] is pd.NaT:

当员工没有终止日期时,我们该怎么办?我们希望将这个空值转换为今天的日期。让我们创建一个名为 end_date 的新变量,并使用 datetime 库来获取今天的日期。我们编写 dt.datetime.today()是因为我们使用了一个库 datetime,它在开始时以别名“dt”导入,然后是这个库的模块 datetime,同名,最后是它的函数 today()。

if uber_employees.at[0, 'termination_date'] is pd.NaT:end_date = dt.datetime.today()

这是当一个雇员没有终止日期时发生的情况,但是如果他们有,那么结束日期变量应该正好等于这个终止日期,所以我们可以说 else,并使用。at 属性再次以与之前相同的方式。

if uber_employees.at[0, 'termination_date'] is pd.NaT:end_date = dt.datetime.today()
else: end_date = uber_employees.at[0, 'termination_date']

我们可以运行这段代码,看看 end_date 变量变成了什么,现在是 2016 年 1 月 1 日,因为这是表中第一个雇员的 termination_date。但是,如果我们将这些 0 更改为 1,并检查第二个雇员,则 end_date 等于 UTC 时区中的当前日期和时间,因为索引为 1 的第二个雇员没有 termination_date。

在我们继续之前,让我们也为 start_date 创建一个等于 hire_date 的新变量,以便我们可以像访问 end_date 一样方便地访问它。我们假设所有雇员都有某个雇佣日期,所以不需要 IF 语句。

start_date = uber_employees.at[0, 'hire_date']

创建这个变量不是强制性的,但是我们需要多次使用雇员的雇佣日期,如果没有这个变量,我们每次都需要编写完整的 uber_employees.at[0,' hire_date'],所以为了可读性,最好为它取一个更短更简单的名称。

下一步是找出他们的雇佣日期和终止日期之间的累计总和的最大值,我们仍然只对第一个员工这样做。我们可以这样开始:我们想要获取 all_dates 表的一部分,这样 all_dates 表的列日期应该在 start_date 和 end_date 之间,这是我们定义的两个变量。

max_emp_count = all_dates[all_dates['date'].between
(start_date, end_date)]

这将为我们提供整个 all_dates 表,但只提供 2009 年雇佣第一名员工到 2016 年 1 月终止合同之间的日期。由此,我们需要提取最大员工数。为此,让我们只选择 emp_count 并使用 max()函数来获得最大值。

max_emp_count = all_dates[all_dates['date'].between
(start_date, end_date)]['emp_count'].max()

这个数字似乎是 57。如果我们回到原来的表,我们可以看到值上升到 57。值 58 也出现在数据中,但只是在 2016 年 1 月该员工停止为公司工作之后,因此在这种情况下,值 57 是正确的。让我们将它添加到 uber_employees 表中,使它与这个雇员相关联,位于一个名为 max_emp 的新列中。

uber_employees.at[0, 'max_emp'] = max_emp_count

查看 uber_employees 表,我们可以看到这个值是为第一个雇员保存的。但是还有一件事要做,即找到最大员工数对应的最早日期。显然,我们可以像前面一样通过过滤 all_dates 表来做到这一点,即我们需要 all_dates 表的 emp_count 列等于我们找到的 max_emp_count 值的这部分表。

earliest_date = all_dates[(all_dates['emp_count'] == max_emp_count)]

但这还不够。如您所见,这返回了许多日期,包括该员工不再在公司工作的日期。因此,让我们再添加一个条件,即除了列 emp_count 等于 max_emp_count 之外,同时 all_dates 表的列“date”需要在 start_date 和 end_date 之间。我们可以使用 AND 运算符来表示过滤器中需要满足这两个条件。

earliest_date = all_dates[(all_dates['emp_count'] == max_emp_count) & (all_dates['date'].between(start_date, end_date))]

现在我们只剩下员工人数变成 57 人的日期,这发生在第一个员工任职期间。但还是有 3 次约会。这个问题告诉我们应该返回最早的日期。要获得它,我们可以从结果中只选择“date”列,并对其应用 min()函数,该函数将返回最小值,也就是最早的日期。

earliest_date = all_dates[(all_dates['emp_count'] == max_emp_count) & (all_dates['date'].between(start_date, end_date))]['date'].min()

同样,与最大雇员数一样,我们可以将这个日期存储在 uber_employees 表的 min_date 列中。

uber_employees.at[0, 'min_date'] = earliest_date 

再次查看 uber_employees 表,我们可以看到它起作用了,对于第一个雇员,我们有最大数量的雇员和它发生的最早日期。但是在我们进入最后一步之前,我们仍然需要获得所有其他雇员的这些值。幸运的是,我们可以使用刚刚编写的代码,并将其放入一个循环中。让我们在 IF 语句之前放置一个 FOR 循环开始语句,通过添加一个列表,从 IF 语句开始直到结尾的所有内容都将在循环内部——这就是我们在 python 中指示循环内部的方式。

这个循环的目标是用表中所有可能的索引替换我们用来获取第一个雇员信息的这 0 个索引。让我们创建一个变量 I,它将在每次循环迭代中切换一个值。这个 I 需要从 0 到 uber_employees 表的最后一个索引取值。为了获得最后一个索引,或者 uber_employees 表的长度,我们可以使用 shape 属性。

uber_employees.shape

它返回[100,8]表示该表有 100 行和 8 列。这意味着指数也会上升到 100,所以我们只对第一个值感兴趣。这个[100,8]是一个列表,所以我们可以像这样选择第一个值。

uber_employees.shape[0]

但是在开始循环的时候,仅仅说变量 I 应该能够,在这个例子中,是 100 是不够的。我们需要把这 100 变成从 0 到 100 的所有数字。为此,我们可以使用 Python range()函数。这将创建一个 0 到 100 之间的值列表,为了定义循环,我们可以说我应该在这个范围内。

for i in range(uber_employees.shape[0]):

最后要做的事情是将我们用来获取第一个雇员数据的所有这些零都更改为变量 I,该变量 I 的值会随着循环的每次迭代而改变,从而对每个雇员重复相同的过程。完成此操作后,我们现在可以看到,在 uber_employees 表中,我们有雇员的最大数量和每个雇员的日期,而不仅仅是第一个。

但是我们也可以看到,这个表仍然有相当多的列。这就是为什么我们有这最后一步,在这一步中,我们根据问题的预期来调整输出。我们被要求输出雇员 id、相应的最大雇员数和日期,因此我们可以过滤表,只留下 ID、max_emp 和 min_date 列。

result = uber_employees[['id', 'max_emp', 'min_date']]

而这就是,这个面试问题的完整正确解答。

import pandas as pd
import datetime as dthired_dates = pd.DataFrame(uber_employees['hire_date'].rename('date'))
hired_dates['value'] = 1terminated_dates = pd.DataFrame(uber_employees['termination_date'].
rename('date'))
terminated_dates['value'] = -1
terminated_dates = terminated_dates.dropna()all_dates = pd.concat([hired_dates, terminated_dates], ignore_index=True)
all_dates = all_dates.groupby('date').sum().reset_index()
all_dates = all_dates.sort_values('date')
all_dates['emp_count'] = all_dates['value'].cumsum()for i in range(uber_employees.shape[0]):if uber_employees.at[i, 'termination_date'] is pd.NaT:end_date = dt.datetime.today()else: end_date = uber_employees.at[i, 'termination_date']start_date = uber_employees.at[i, 'hire_date']max_emp_count = all_dates[all_dates['date'].between(start_date, end_date)]
['emp_count'].max()uber_employees.at[i, 'max_emp'] = max_emp_countearliest_date = all_dates[(all_dates['emp_count'] == max_emp_count)
& (all_dates['date'].between(start_date, end_date))]['date'].min()uber_employees.at[i, 'min_date'] = earliest_dateresult = uber_employees[['id', 'max_emp', 'min_date']]

结论

在这篇文章中,你可以看到解释,并学习如何使用 Python 解决优步数据科学家面试中的一个难题。这是解决这个问题的一种可能的方法,但是还有其他的方法和解决方案。好的建议是,通过构建问题的解决方案来练习回答面试问题,但总是试图想出解决问题的其他方法,也许你会想到一个更有效或更复杂的方法。但是始终要考虑并涵盖数据中可能出现的所有可能的边缘情况。查看我们之前的帖子“ ”优步数据科学家面试问题 ”,找到更多来自优步公司的问题。此外,如果你想全面了解亚马逊、谷歌、微软等顶级公司的数据科学面试中所问的 Python 面试问题类型。,请看这篇“30 大 Python 面试问题 ”的帖子。

最初发表于【https://www.stratascratch.com】

优步 SQL 面试问题

原文:https://towardsdatascience.com/uber-sql-interview-questions-cf6a2a08a23c

分解一个有趣的优步 SQL 面试问题的答案

作者在 Canva 上创建的图片

优步公司改变了市中心的交通,让数百万用户的生活变得更加轻松。他们的主要产品是一款智能手机应用,可以让用户以合理的价格找到便车。优步还参与了许多其他业务,如食品配送、货运等。

所有这些运营都有一个共同点:依靠稳定高效的物流来顺利完成行程。为了达到这个目标,优步每天收集大量的数据。该公司拥有一支才华横溢的数据科学家团队,以从可用信息中获得可操作的见解。

在本文中,我们将探讨在优步面试中被问到的一个 SQL 面试问题。

在优步 SQL 访谈中测试基本到中级概念

作者在 Canva 上创建的图像

聚合函数

在参加面试之前,请阅读这篇《SQL 聚合函数终极指南 》来刷新你的聚合函数知识。你需要知道聚合函数来解决面试中的大多数 SQL 问题,更重要的是,在你的日常工作中编写 SQL 查询。

您需要掌握聚合函数的基本知识,例如语法、参数以及如何使用 GROUP BY 语句汇总结果。

如果你真的想在竞争中脱颖而出,再深挖一点是个好主意。例如,学习如何将聚合函数与其他 SQL 特性结合使用,比如 DISTINCT 语句。

SUM()是最常用的聚合函数之一。需要解决某一列的数值相加的问题。在下面的问题中,我们将使用它来计算总里程。

要知道 SUM()只能用来计算数字的总数,不能和文本、日期或者其他类型的数值一起使用。了解一些小细节,比如这个函数如何处理空值,会有很大帮助。

在哪里

WHERE 语句是 SQL 的基本特性之一。尽管它很简单,但它对于在 SQL 中过滤数据是必不可少的。您应该知道如何在 SELECT/FROM 语句中使用它来过滤掉不符合条件的行。

要过滤 SQL 中的表,您应该知道如何为 WHERE 语句设置条件。对于条件可以是什么没有限制。您可能需要检查一列中的数值是否比另一列高。您应该准备好如何使用比较运算符来比较其他值,如字符串、日期和浮点数。

书写条件有语法规则。例如,在比较数字、字符串、日期和其他类型的值时,是否有必要使用引号。

了解比较运算符如何处理非数字数据很重要。例如,如果您正在处理销售数据,您可能希望将输出限制在一个特定的月份。您可以使用比较运算符来获取某个特定月份的记录,或者确定两个日期中哪个日期先出现。

以...排序

面试官经常要求你返回一列中 X 个最高值。为此,您需要从最高到最低排列数据,并按顺序输出指定数量的值。

我们使用 ORDER BY 语句对 SQL 中的行进行排序。它允许您指定一列,SQL 将根据该列中的值对记录进行排序。为了解决一些更困难的问题,您可能需要指定多个列。了解如何在表中通过名称或索引来指定列是很有用的。

一个好的候选人应该知道如何指定值排序的升序和降序,以及如何对日期和字符串值进行排序。

此外,对细节的了解,比如没有指定顺序时会发生什么,会有很大帮助。

排名窗口函数

如前所述,为了解决一些问题,候选人必须在一列中输出特定数量的最高值。有多种方法可以解决这些问题。

在某些情况下,比如下面的问题,可以说一个简单的极限语句足以输出特定数量的值。

另一方面,排序窗口函数总是输出一定数量的最高值的最准确和最简单的方法。

研究 SQL 窗口函数和 SQL 等级函数,为任何需要你输出 X 个最高值的 SQL 问题做准备。

RANK()和 DENSE_RANK()之类的函数允许您根据某一列中的值为行分配等级。理解这两种功能之间的区别很重要。

优步 SQL 面试问题演练

最高总里程数

在这个问题中,我们有一个数据表,描述了通过优步应用程序预订的每一次旅行。候选人被要求找出促使企业客户使用优步进行旅行的三个最常见的目的。

在 StrataScratch 平台上被标注为‘中等’难度,所以只要注意,应该能找到解决方法。有多种方法可以解决这个优步 SQL 面试问题。

截图来自 StrataScratch

问题的表述可能有点令人困惑,因此您可能需要查看可用的数据来理解问题。

问题链接:https://platform . stratascratch . com/coding/10169-最高总里程

可用数据集

我的优步司机

截图来自 StrataScratch

数据假设

这是一个令人费解的问题,所以你应该多次阅读它,以了解你必须做什么。

对于像这样的数据科学面试问题,预览最终表格或要求面试官描述预期输出是有用的。你可以利用这个机会从最后的结果反推来理解这个问题。

当你阅读这个问题时,只要有可能,你也应该看看表格中的实际值。这可以帮助你更好地掌握可用的数据。

截图来自 StrataScratch

让我们看看下表中的列:

start_date —该问题没有要求我们计算每次旅行的持续时间,因此我们不需要旅行开始的确切日期和时间。

end_date — 类似于 start_date 列,我们也不需要该列中的值。

类别 —我们将检查该列中的值,以仅保留业务费用的记录。

开始 —拾取点与此问题无关。

停止 —最终目的地与优步 SQL 面试问题无关。

里程 —我们需要计算每个目的的总里程。所以我们要把这一列的值加起来。

目的 —我们的最终输出将包括每个目的的总里程。所以我们肯定需要这个列中的值。

查看表格时,请注意每列中值的类型,以了解它们所代表的含义。例如,知道' miles '列包含浮点值可以帮助您决定如何按英里对目的进行排序。

考生要仔细检查目的栏以及里面的所有数值。如果您仔细观察,您会注意到一个重要的细节—一些驱动器记录在这个列中包含一个空值。这是一个需要处理的边缘案例。

您应该看看每一列中的值的范围。“类别”列中的大部分值都是“业务”,这意味着硬盘属于业务费用。然而,如果你仔细看,你会注意到有一些个人费用混在里面。

一旦你多次阅读这个问题,你就可以开始制定你的方法了。

解决方案逻辑

这个优步 SQL 面试问题可能很难理解,所以你应该多看几遍。一个条件是只计算业务费用的总里程。换句话说,“类别”列中的值必须是“业务”。

将解决方案分解成几个易于管理的步骤。写下你的方法可以帮助你坚持下去。这个问题的解决方案包括四个主要步骤:

  1. 过滤记录
  2. 计算总里程
  3. 订购记录
  4. 输出前 3 个业务目的

过滤掉不是业务费用的记录

第一步,我们将使用一个简单的 WHERE 语句来过滤掉不是业务费用的记录。

如果您查看可用数据,您会注意到一些记录有一个空的“目的”列。换句话说,该列中的值为 NULL。

我们将不得不修改我们的 WHERE 语句,以过滤掉非业务费用以及“目的”栏为空的记录。为此,我们将使用 AND 逻辑运算符。该条件将只保留同时满足这两个条件的行:在‘类别’列中的值是‘业务’,在‘目的’列中有一些值。

第一个条件可以是一个简单的等式运算符,以确保“类别”列中的值等于“业务”。对于第二个条件,我们可以简单地写目的不为空,它将过滤掉所有的空值。

计算每个目的的总里程数

一旦我们有了过滤后的表,我们就可以得到每个目的的总里程。为此,我们将使用 SUM()聚合函数和 GROUP BY 语句来汇总结果。

我们将使用 ORDER BY 语句,根据各种用途的里程总和按降序排列行。

正如您在最后的表中看到的,我们将只有两列:目的列,它将包含代表每个组的唯一值,另一列将包含每个目的的聚合值。最后,我们将输出三个最高值。

根据总里程值降序排列记录

我们可以简单地编写 ORDER BY 2 来表示应该根据第二列中的值对行进行排序,第二列包含总里程数。

输出总里程最高的三个目的

最后一步是输出总里程数最高的三个目的。这一步可以用两种不同的方法来处理:一种是使用 LIMIT 语句。在这种情况下,解决方案非常简单,并且可能在 99.99%的时间里都有效。

另一种解决方案是使用窗口排序函数,这往往会产生更准确的结果。在下面几节中,我们将讨论解决这个问题的两种方法。

极限语句的明显优点是它简单易读。

使用 LIMIT 的问题是数据是不可预测的。总里程数排名第三和第四的目的之间可能存在关联。在这种情况下,使用 LIMIT 语句会忽略这一事实,仍然输出表中的前三个值。

分级窗口功能使您能够更好地控制这些边缘情况。它们允许您输出三个最高值,包括第一、第二或第三名的平手。如果出现平局的几率很高,排序窗口函数是更合适的选择。

如果我们看一下 miles 列,我们可以看到它包含浮点值。总里程将是该列中值的汇总。因此,我们不太可能打成平手。

在这种情况下,可以认为极限陈述的简单性超过了由于平局而得到不准确结果的风险。但是,如果您想确保结果的准确性,您应该使用 RANK()或 DENSE_RANK()函数。

比起 LIMIT 的简单性和可读性,一些雇主可能更喜欢更健壮的方法,所以你能做的最好的事情就是去问。展示你可以从两个方面解决问题,展示你的分析思维和对 SQL 的全面了解。

这可以帮助您展示读取数据的能力,并注意细微差别和细节,比如浮点值不太可能被绑定。

如果面试官要求你使用窗口排名功能,做好使用的准备。最重要的是表明你了解这两种解决方案的利弊。

最后,我们必须使用 AS 命令来命名存储每个目的的总英里数的列。应该是一些描述性的东西,比如 miles_sum

解决方法

第 1 步:过滤表,只保留业务费用

我们可以从获取所有业务支出记录开始。

SELECT *
FROM my_uber_drives
WHERE category = 'Business'

如果我们运行这段代码,输出将包含业务费用。它看起来会像这样:

截图来自 StrataScratch

第二步:获得每个目的的总里程数

我们可以将输出限制为两个必要的列,而不是获得一整行。一个是目的列,另一个将使用 SUM()聚合函数生成。我们将使用 AS 命令为该列指定一个描述性名称。

我们可以添加 GROUP BY 子句来汇总每个目的的总里程。

SELECT purpose,SUM(miles) AS miles_sum
FROM my_uber_drives
WHERE category = 'Business'
GROUP BY purpose

如果我们运行这个查询,我们将得到一个更接近最终结果的结果:

截图来自 StrataScratch

步骤 3:处理无效目的

前面的查询返回一个包含一个空列的表,该列表示具有 NULL(空)目的的记录的总英里数。

目前,我们使用一个条件过滤该表:检查类别列中的值,并且只保留业务费用。

我们可以使用 AND 逻辑运算符向我们的过滤器添加另一个条件。现在,SQL 还将检查并只保留属于业务费用的值,并且目的列中的值不为空。

SELECT purpose,SUM(miles) AS miles_sum
FROM my_uber_drives
WHERE category = 'Business'AND purpose IS NOT NULL
GROUP BY purpose

如果我们运行代码,我们将看到输出不再包含空单元格。

截图来自 StrataScratch

第四步:降序排列

为了找到答案,我们需要根据购买的总里程来排序。它们应该按降序排列,最高的在顶部,最低的在底部。

我们将使用 ORDER BY 语句来实现这一点。

SELECT purpose,SUM(miles) AS miles_sum
FROM my_uber_drives
WHERE category = 'Business'AND purpose IS NOT NULL
GROUP BY purpose
ORDER BY 2 DESC

请注意,我们使用数字 2 来指定列。SQL 将解释它,并根据第二列中的值对记录进行排序,第二列是 miles_sum

如果你运行这段代码,你会发现我们几乎得出了答案:

截图来自 StrataScratch

第五步:保留前三行

最后一步,我们需要输出前三行。我们可以用一个简单的极限语句来做这件事。这是一个简单的解决方案,在这种情况下,几乎每次都会返回正确的答案。

SELECT purpose,SUM(miles) AS miles_sum
FROM my_uber_drives
WHERE category = 'Business'AND purpose IS NOT NULL
GROUP BY purpose
ORDER BY 2 DESC
LIMIT 3

它将为我们提供最终的、正确的预期输出:

截图来自 StrataScratch

另一个正确的解决方案

在这种方法中,我们使用一个通用的表表达式(简称为 CTE ),来过滤掉那些目的列为空而类别列为除“业务”之外的任何内容的记录。在 CTE,我们还会累计各种用途的里程。

我们可以使用 SELECT 语句从 CTE 中提取每组目的及其对应的总里程。

然后,我们使用 RANK()窗口函数按照降序为值生成等级。最后,我们可以使用另一个 WHERE 语句来只显示等级值小于或等于 3 的记录。

这种方法更加稳健,因为它处理的是目的可能与总里程数相关的边缘情况。

WITH cte AS(SELECT purpose,sum(miles) AS miles_sumFROM my_uber_drivesWHERE purpose IS NOT NULLAND category ILIKE '%business%'GROUP BY purpose)
SELECT purpose,miles_sum
FROM(SELECT purpose,miles_sum,rank() OVER (ORDER BY miles_sum DESC) AS rnkFROM cte) a
WHERE rnk<=3

最后的话

在过去的十年里,优步从一个小公司发展成为今天的大型科技公司。

这些年来,数据收集和分析一直是其商业模式的重要元素。数据科学家的努力让优步以合理的价格提供舒适的服务。

您可以查看“ 优步数据科学家面试问题 ”以了解更多针对优步数据科学家候选人的问题。在 StrataScratch 复习实际问题,更新你的 SQL 知识,确保你在优步面试时不会措手不及。

所有 Python 项目的终极 CI 管道

原文:https://towardsdatascience.com/ultimate-ci-pipeline-for-all-of-your-python-projects-27f9019ea71a

Python 项目的持续集成管道拥有您想要的一切——在几分钟内启动并运行

照片由约书亚·沃罗尼耶基在 Unsplash 拍摄

每个项目都可以从一个强大的持续集成管道中受益,该管道可以构建您的应用程序、运行测试、链接代码、验证代码质量、运行漏洞分析等等。然而,构建这样的管道需要大量的时间,这本身并不能带来任何好处。因此,如果您想要为您的 Python 项目准备一个基于 GitHub Actions 的全功能、可定制的 CI 管道,并准备好您能想到的所有工具和集成,只需大约 5 分钟,那么这篇文章就能满足您!

快速启动

如果您不是很有耐心,或者只想马上开始,那么下面是启动和运行管道所需的最少设置:

上面的 YAML 配置了一个 GitHub Actions 工作流,该工作流引用了我的存储库中的可重用工作流。这样,您就不需要复制(并在以后维护)包含所有动作的大型 YAML。您所需要做的就是将这个 YAML 包含在您的存储库的.github/workflows/目录中,并根据您的喜好配置with:节中列出的参数。

所有这些参数(配置选项)都有相同的默认值,它们都不是必需的,所以如果你相信我的判断,你可以省略整个with节。如果没有,那么您可以如上所示调整它们,以及工作流定义这里中显示的其余选项。关于如何找到值和配置例如 Sonar 或 CodeClimate 集成所需的秘密的解释,请参见存储库中的自述文件,或者通读以下详细解释的部分。

工作流显然必须对您的存储库的内容做出一些假设,所以它期望有一个您的应用程序和Dockerfile的源代码目录,其他的都是可选的。关于存储库布局的示例,请参见测试存储库这里的。

您可能已经注意到,上面的代码片段引用了使用@v1.0.0的工作流的一个特定版本。这是为了避免引入任何您可能不想要的潜在更改。也就是说,不时地签出存储库,因为可能会有一些额外的更改和改进,因此会有新的版本。

基础知识

现在您可能已经配置好了管道,但是让我们看看它在内部做了什么,以及如何进一步定制它。我们从最基本的开始——检查存储库、安装代码和运行测试:

为了清楚起见,上面的代码片段被删减了,但是如果你熟悉 GitHub 的动作,所有的步骤应该是相当明显的。如果没有,不要担心,你实际上不需要理解这一点,重要的是要知道,管道与所有主要的 Python 依赖管理器——即pip poetrypipenv——一起工作,你需要做的就是设置DEPENDENCY_MANAGER,管道会处理剩下的事情。这显然假设您的存储库中有requirements.txtpoetry.lockPipfile.lock

上面的步骤还创建了 Python 的虚拟环境,在整个管道中使用它来创建隔离的构建/测试环境。这也允许我们缓存所有依赖项,以节省管道运行时间。额外的好处是,只要锁文件不变,依赖项就会跨分支缓存。

至于测试步骤— pytest用于运行您的测试套件。Pytest 将自动获取您在存储库中可能拥有的任何配置(如果有的话),更具体地说是按照优先顺序:pytest.inipyproject.tomltox.inisetup.cfg

代码质量

除了基础知识,我们还想加强一些代码质量度量。有很多代码质量工具可以用来确保你的 Python 代码是干净的和可维护的,这个管道包括了所有这些工具:

我们首先运行Black——Python 代码格式化程序。使用 Black 作为预提交钩子或者在每次本地保存文件时运行它是一个最佳实践,因此这一步应该只作为验证没有任何东西从缝隙中溜走。

接下来,我们运行 Flake8Pylint ,它们应用了比 Black 更多的样式和林挺规则。这两者都可以通过各自的配置文件进行配置,配置文件会被自动识别。在 Flake8 的情况下,选项有:setup.cfgtox.ini.flake8,对于 Pylint 则有:pylintrc.pylintrcpyproject.toml

上述所有工具都可以设置为强制模式,如果发现问题,这将使管道失败。这些的配置选项有:ENFORCE_PYLINTENFORCE_BLACKENFORCE_FLAKE8

除了 Python 特有的工具,该管道还包括两个流行的外部工具——分别是 SonarCloudCodeClimate 。两者都是可选的,但是考虑到它们可用于任何公共存储库,我建议使用它们。如果打开(使用ENABLE_SONAR和/或ENABLE_CODE_CLIMATE,Sonar scanner 将对您的代码运行代码分析,并将其发送给 SonarCloud,CodeClimate 将获取pytest调用期间生成的代码覆盖报告,并为您生成覆盖报告。

要配置这些工具,请在管道配置中包含它们各自的配置字段:

并按照存储库 README 中概述的步骤来生成和设置这些秘密的值。

包裹

当我们确信我们的代码符合标准时,是时候打包它了——在这种情况下是以容器映像的形式:

这个管道默认使用 GitHub 容器注册表,它是你的仓库的一部分。如果这是您想要使用的,除了在存储库中提供Dockerfile之外,您不需要配置任何东西。

如果您更喜欢使用 Docker Hub 或任何其他注册表,您可以在CONTAINER_REGISTRY_USERNAMECONTAINER_REGISTRY_PASSWORD(在管道配置的secrets部分)中提供CONTAINER_REGISTRYCONTAINER_REPOSITORY以及凭证,管道会处理其余的。

除了基本的登录、构建和推送序列,该管道还生成附加到映像的附加元数据信息。这包括用 commit SHA 标记图像,如果有标签的话,还包括git标签。

为了提高流水线的速度,在docker构建过程中还使用了缓存来避免创建不需要重建的图像层。最后,为了提高效率,还对图像运行驱动工具,以评估图像本身的效率。它还为您提供了一个在.dive-ci中提供配置文件的选项,并为 Dive 的指标设置阈值。与该管道中的所有其他工具一样,Dive 也可以使用ENFORCE_DIVE设置为强制/非强制模式。

安全性

我们不要忘记 CI 管道应该确保我们的代码不包含任何漏洞。为此,该工作流程包括额外的几个工具:

首先是一个叫做 Bandit 的 Python 工具,它在 Python 代码中寻找常见的安全问题。该工具有默认的规则集,但可以使用工作流程的BANDIT_CONFIG选项中指定的配置文件进行调整。正如前面提到的其他工具一样,Bandit 在默认情况下也运行在非强制模式下,但是可以使用ENFORCE_BANDIT选项切换到强制模式。

这个管道中包含的另一个检查漏洞的工具是 Aqua Security 的 Trivy ,它扫描容器图像并生成图像本身中发现的漏洞列表,这超越了仅限于您的 Python 代码的问题。这份报告随后被上传到 GitHub 代码扫描,然后会出现在你的存储库的安全标签:

繁琐的代码扫描报告—图片由作者提供

上述工具保证了我们构建的应用程序的安全性,这很好,但是我们还应该提供最终容器图像的真实性证明,以避免供应链攻击。为此,我们使用 tool 用 GitHub 的 OIDC 令牌对图像进行签名,该令牌将图像与将代码推送到存储库的用户的身份联系起来。这个工具不需要任何密钥来生成签名,所以它可以开箱即用。然后,该签名将与您的图像一起被推送到容器注册表中,例如在 Docker Hub 中:

Docker Hub 中的连署签名—图片由作者提供

然后可以使用以下方法验证上述签名:

有关cosign和容器图像签名的更多信息,请参见 GitHub 博客上的文章。

通知

这条管道的最后一个小特性是 Slack 通知,它对成功和失败的构建都运行——假设你用ENABLE_SLACK打开它。你需要提供的只是使用SLACK_WEBHOOK库秘密的 Slack channel webhook。

要生成上述 webhook,请遵循 README 中的注释。

宽限通知-按作者排序的图像

结束语

这应该是让您的端到端完全配置的管道启动并运行所需的一切。

如果定制选项或特性不完全符合您的需求,请随意派生存储库或提交一个带有特性请求的问题。

除了额外的特性,将来这个库中可能会出现更多 Python(或其他语言)的管道选项,因为这里介绍的管道并不适合所有类型的应用程序。因此,如果您对这些或任何新的开发感兴趣,请确保不时地查看存储库以了解任何新的发布。

本文最初发布于martinheinz . dev

数据科学统计终极指南

原文:https://towardsdatascience.com/ultimate-guide-to-statistics-for-data-science-a3d8f1fd69a7

数据科学统计一瞥:标准指南

作者图片

动机

统计学是一个强大的数学领域。我说“强大”是因为它有助于我们从样本数据中推断总体结果。因为它可以推断人口结果,它也可以用于大图片(如整体影响,未来预测等。).统计学不仅仅是一些孤立主题的组合。此外,统计学在这些主题中发现了新的语义,这有时为新的机会铺平了道路。在我们的日常生活中,统计无处不在。大三时面临的主要问题是无法将统计知识与现实生活联系起来。我不知道在哪里使用哪种技术来找到答案并采取相应的行动。当我在 2018 年开始学习数据科学时,新的旅程开始了。我涵盖了数据科学所需的所有基本统计学主题,并意识到统计学知识是多么必要!作为一所大学的人工智能和数据科学讲师,我开设了统计学课程,并试图将其作为我们日常使用的有用工具。我也想在我的文章中分享这些知识。这就是为什么我写了一系列文章,涵盖统计学的所有基本主题,并试图以最简单的方式来表达它们。虽然本系列中遗漏了一些主题,但我会尝试将它们与我以前的作品一起整合到本文中。我希望各行各业的人都能容易地理解这些关于统计的文章。不需要先决条件。 最后,我想说,“本文涵盖了大部分(但不是全部!)主题,它将为数据科学提供一个统计基础,以便您可以根据需要探索其他高级主题。”

因此,如果你是统计学的绝对初学者,并且正在寻找一个完整的指导方针,这篇文章将会适合你。

目录

  1. [**Statistics at a Glance**](#aada)
  2. [**Population and Sample**](#e609)
  3. [**Variable and Level of Measurement**](#187d)
  4. [**Central Tendency (Mean, Median and Mode)**](#ff63)
  5. [**Percentile, Quartile and IQR**](#7890)
  6. [**Frequency Distribution and Visualization**](#7b63)
  7. [**Measure of Dispersion (Range, Variance, Standard Deviation, etc.)**](#cd9d)
  8. [**Covariance and Correlation Coefficient**](#31b1)
  9. [**Normalization**](#beff)
  10. [**Probability**](#eca3)
  11. [**Probability Distribution (Uniform, Binomial, Poisson, Normal and Student’s t distribution)**](#633b)
  12. [**Central Limit Theorem**](#4b93)
  13. [**Point Estimator and Estimate**](#f195)
  14. [**Standard Error**](#3408)
  15. [**Confidence Interval and Level**](#8d9d)
  16. [**Significance level**](#3433)
  17. [**Hypothesis Testing**](#a41f)
  18. [**Analysis of Variance (ANOVA)**](#452e)
  19. [**Chi-Square Test**](#6d4c)

下一节是本文最重要的部分,本系列中的所有文章都将与简要描述相关联。

带有嵌入链接的主题描述

统计数据一览

根据我的理解,统计学结合了一些技术,通过对一个小群体(样本)进行实验并总结数据集,得出关于一个大群体(人群)的可靠结论。这不是一个正式的定义;这是我在处理统计数据时的体会。

让我们根据维基百科[1]来看一个正式的定义——

统计学是关于数据的收集、组织、分析、解释和展示的学科。

有两类统计数据。

作者图片

  • 描述性统计总结/描述总体或样本数据集。它涵盖了以下主题— ***types of data, variables, data representation, frequency distribution, central tendency, percentile and quartile, covariance, correlation, etc.***
  • 推断统计是统计的一部分,它从样本数据中找到群体数据的可靠推断。它涵盖了— **probability distribution, *Central Limit Theorem, Point Estimator and Estimate, Standard Error, Confidence Interval and Level, Level of Significance, Hypothesis Testing, Analysis of Variance (ANOVA), Chi-Square Test, etc.***

人口和样本

总体由实验的所有成员组成,而样本是从代表总体的总体中选出的一组成员。

例如,我们想知道大学生的平均 CGPA。在这里,实验区覆盖了所有的学生。因此,人口将是该大学的所有学生。如果我们挑选一些学生来计算平均 CGPA,这些学生就是样本。

在跳到统计学之前,你必须清楚地了解题目。

为了让你的想法变得清晰,请阅读下面的文章—

变量和测量水平

简单地说变量是可以变化的东西(保存多个值)。它只不过是数据集的特征。由于现实世界中存在不同的特征,因此存在不同类型的数据。我们必须知道测量级别才能明白我们是如何处理数据的。

如果你对题目有任何困惑,请通读这篇文章。

集中趋势

中心倾向是一种找出多数值的倾向的方法。在统计学中,均值、中位数、众数都是用来认识它的。

  • 的意思是

“意思是”的概念很直白。我们通过将总和除以值的数量(n)得到平均值。

完整均值准则—

https://medium.datadriveninvestor.com/eliminate-your-misconception-about-mean-with-a-brief-discussion-a9fed67d4b08

  • 中位数

中位数是知道中心倾向的另一种方式。为了得到中间值,我们需要按升序对值进行排序,并选取中间值,它随值的偶数和奇数而变化。

例如,**12, 13, 10, 15, and 7**就是数值系列。首先,我们需要理清价值观。排序后,序列将为**7, 10, 12, 13, and 15.** 值的总数为 5, 其中为奇数So我们将使用下面的公式——

在我们的例子中, 12 是中间值。

再比如有些价值观是**12, 13, 10, 15, 7, and 9**。经过排序,我们得到**7, 9, 10, 12, 13, and 15** 这次数值个数是 6 ,是偶数。所以,我们不会用上面的公式得到中间值。因为 (6+1)/2= 3.5 不是整数。现在,我们需要总结第三个和第四个的数值。而它们的平均值就是中值, 22/2 = 11。**

Python 实现—

  • 模式

****模式作用于分类数据,它是数据集的最高频率。假设您有一些包含像 **[‘good’, ‘bad’, ‘normal’, ‘good’, ‘good’]**这样的产品质量的数据。在这里,好的出现的频率最高。所以,我们的数据是模式

Python 实现—

什么时候用哪个中枢倾向?

在名义数据的情况下,我们使用模式。对于有序数据,建议使用中位数。均值广泛用于寻找比率/区间变量的集中趋势。但是平均值并不总是确定中心趋势的正确选择,因为如果数据集包含异常值,平均值将会非常高或非常低。在这种情况下,中位数比平均数更稳健。如果中位数大于或小于平均值,我们将使用中位数。否则,刻薄是最好的选择。

更多详情,点击 此处。

百分位数、四分位数和 IQR

  • 百分位数

百分位数是统计学中使用的一种度量,表示一组观察值中给定百分比的观察值低于的值。例如,第 20 个百分位数是一个值(或分数),低于该值可能会发现 20%的观察值[2]。

  • 四分位数

在百分位数中,整个数值被分成 100 个不同的部分。四分位数将数值分成四个等份,每份占 25%。主要四分位数是 第一四分位数(Q1)、第二四分位数(Q2)、第三四分位数(Q3)和第四四分位数(Q4)。

作者图片(摘自我之前的文章[3])

  • IQR(四分位数比率)

IQR 是在 Q1Q3 之间的范围。所以, IQR = Q1 — Q3。

作者图片(摘自我之前的文章[4])

我们还可以通过定义一个最小值 (Q1 -1.5IQR,又称下围栏) 和最大值 (Q3 + 1.5IQR,又称上围栏) 边界值,找出与 IQR 的离群值。最小值和最大值之外的值被视为异常值。**

箱线图显示所有的四分位数和上下栅栏。

频率分布和可视化

频率是数据集中事件发生的量度。下面的文章将帮助你了解这个话题的细节。

**

离差的度量

概念分散度的度量表示数值的分布程度!范围、方差、标准差。等等。,是求离差的一些技巧。

  • 范围

范围是最大值和最小值的间隔。比如我们有一些样本数据**12, 14, 20, 40, 99, and 100**。范围将是(100–12)= 88。

  • 差异

方差测量数据集的每个值与平均值之间的差异。根据 Investopedia —

方差衡量的是集合中的每个数字与平均值的距离,也就是与集合中其他数字的距离[5]。

这里, 是样本平均值, n 是数值的个数。

μ 是总体均值,N 是总体值的个数。

  • 标准偏差

标准差方差的平方根。

方差和标准差的 Python 实现—

协方差和相关性

  • 协方差

协方差是比较两个数值变量之间方差的一种方法。下面的公式用来计算它。

这里, xy 代表两个变量。 N 是人口数。

  • 相关性(皮尔森相关性)

它找出两个数值变量之间的线性关系。

相关值在 -1+1 之间波动。 -1 表示完全负相关,而 +1 表示变量之间完全正相关。而 0 表示没有关系。

Python 实现—

正常化

归一化是将数据转换成相似尺度的过程,是数据预处理的关键部分之一。下面的文章集成了数据规范化的所有技术。

https://medium.datadriveninvestor.com/increase-your-data-science-model-efficiency-with-normalization-918484b4626f

概率

概率是一种数学技术,通过它我们可以预测一个事件的可能结果。如果你对概率有任何困惑,请阅读以下文章。

概率分布

概率分布是一个事件所有概率的分布。概率分布有两种类型。

作者图片

  • 离散概率分布

离散概率分布是离散值的概率分布。比如掷骰子的概率。我们为每个角色找到一个特定的结果,很明显是一个离散的值。

均匀分布、二项式分布、泊松分布等。 ,是一些主要的离散概率分布。

  • 连续概率分布

连续概率分布是连续值的概率分布。例如,样本组的年龄为 24 岁的概率。由于 24 岁是一个连续值,我们需要使用连续概率分布。

正态分布、学生的 t 分布等。是一些连续的概率分布。

  • 离散均匀分布

在均匀分布中,所有特定结果的值都是相等的。例如,掷骰子有 6 个单独的结果= {1,2,3,4,5,6}。如果是均匀分布,每个概率值是 0.16667

Python 实现[6]——

  • 二项分布

名字“二项式”暗示了两个相互排斥的试验结果。例如,头或尾,好或坏,通过或失败等。

对于二项分布,试验必须满足伯努利试验的标准。

伯努利试验必须有两个独立的结果,比如高或低。成功的概率必须是恒定的。

这里, n 为试验次数, p 为成功概率,成功次数 x

让我们为掷骰子画一个二项式分布。假设你掷骰子 16 次。2 出现 4 次的概率会是多少?这里,p=1/6,n=16。python 代码显示了该场景的二项式分布。

【文章【7】帮助我实现代码。】

红色条表示概率为 2,出现 4 次。

  • 泊松分布

红色条表示出现 4 次的概率为 2。

  • 泊松分布

二项式分布从特定次数的试验中找出成功的次数。泊松分布决定了单位时间间隔内成功的次数。

例如,在一家商店里,8 个顾客在中午 12 点到下午 1 点之间到达。借助泊松分布,可以求出 12 点到 1 点之间到达 3 个人的概率。泊松分布可以用下面的公式来解释。

其中 x 是成功的次数, λ 是单位时间内发生的次数。 ε 是欧拉数(2.71828…)。对于上述问题,x=3,λ=8/1=8。

红色条显示在 12pm1pm 之间到达 3 个客户的概率。

  • 正态分布

作者图片(正态分布图)

连续概率分布适用于连续变量。正态分布是广泛使用的连续概率分布之一。许多现实生活中的问题都可以用正态分布来解决/描述。假设我们考虑 70 名学生的样本年龄。年龄从 18 岁到 25 岁不等。如果样本中的均值、众数和中位数相等,则该样本将呈正态分布。

在正态分布的情况下,左右部分平均分布的概率意味着它是对称的。总概率等于 1。该分布遵循以下等式。

这里, σ 是标准差, μ 是均值。

使用 python 实现正态分布—

正态分布当标准差为 1 ,均值为 0 时,将为标准正态分布。下面是 python 中标准正态分布的一个例子。

  • 学生的 t 分布

威廉·西利·戈塞特提议分配。由于他工作的地方有限制,不能用原来的名字发表研究文章,所以他用了自己的化名“学生”提出这种分布是为了从小样本中找出最佳样本[8]。

按作者分类的图像(学生的 t 分布)

该图显示了两种分布的比较。学生的 t 分布比正态分布有一个更宽的尾部。学生 t 分布的 Python 实现[9]。

中心极限定理

如果我们从总体中随机抽取大量样本并考虑平均值,我们会发现样本值将围绕总体平均值呈正态分布。这是一种找出好的样本数据的方法。

作者图片

估算者和估计者

估计量是统计术语,我们通过它来估计关于总体的一些事实。一些估计量是样本均值、标准差、方差等。

估计量的值称为估计值。

假设样本的方差为 5。这里方差为估计量,方差的值称为估计量。

标准误差

标准差表示数值离总体均值有多远,标准差表示样本均值离总体均值有多远。计算如下—

这里, σ 为总体标准差, n 为样本量。

置信水平和区间

  • 置信度

置信度是百分比值;在该值内,参数的真值将保持不变。假设我们以 95%的置信度解决了一个问题;这意味着 95%的时候,我们会从我们解决的问题中得到准确的结果。

  • 置信区间

置信区间是一个范围,在这个范围内我们将得到置信水平的真值。

显著性水平

某一事件(如统计测试)的显著性水平是该事件可能偶然发生的概率。如果水平相当低,即偶然发生的概率相当小,我们就说事件重大【11】

按作者分类的图像(显著性级别)

显著性水平用 α 符号表示。

假设检验

假设检验是一种统计技术,通过它我们可以检验和验证一个假设。在假设检验中,我们考虑一个零假设,它被假定为真,而是一个替代假设,如果零假设失败,它是可接受的。关于假设检验的更多细节在下面的文章中给出。

https://medium.datadriveninvestor.com/all-you-need-to-know-about-hypothesis-testing-for-data-science-enthusiasts-30cfe1dce028

方差分析(ANOVA)

在假设检验的文章中,我提到过一些检验,如 p 检验、t 检验等。但是这些测试只是在两组之间进行比较。它们都不能用于多个组。 ANOVA 是一种统计检验,用于比较两组或多组之间的可变性。下面的文章提供了详细的解释。

卡方检验

卡方检验是另一种用于发现分类变量相关性的统计检验。通读下面这篇文章。

结论

统计学是数据科学不可或缺的一部分。统计学的每一个主题都不可能被涵盖,因为这个领域是巨大的。但是,我已经尽量涵盖了数据科学所需要的统计学的重要知识。通过这篇文章,我将结束我的数据科学统计学系列文章。如果你有任何问题,请在评论区告诉我。

谢谢

参考

  1. 统计—百科
  2. 百分位数。IAHPC Pallipedia。https://pallipedia.org/percentile/。2022 年 10 月 16 日进入。
  3. 所有频率分布的基础知识|走向数据科学
  4. https://towards data science . com/compare-multi-frequency-distributions-to-extract-of-a-valued-information-from-a-dataset-10 CBA 801 f 07 b
  5. 什么是统计学中的方差?定义、公式和示例(investopedia.com)
  6. https://pyshark . com/continuous-and-discrete-uniform-distribution-in-python/
  7. Python —二项式分布— GeeksforGeeks
  8. 邓尼特,C. W .,&索贝尔,M. (1954)。学生 t 分布的二元概括,并附有特殊情况的表格。 Biometrika41(1–2),153–169。
  9. Python —统计学中的学生 t 分布— GeeksforGeeks
  10. 估计量:简单定义和示例—统计如何进行
  11. https://www . science direct . com/topics/mathematics/significance-level-alpha

*https://mzh706.medium.com/subscribe https://mzh706.medium.com/membership ***

ultra tiny ml:8 位微控制器的机器学习

原文:https://towardsdatascience.com/ultra-tinyml-machine-learning-for-8-bit-microcontroller-9ec8f7c8dd12

如何用 Arduino 和内乌顿 TinyML 创建手势识别系统

“一个错误重复多次就是一个决定”——p·科埃略

TinyML 是机器学习的一个子领域,研究在小型低功耗设备上运行 ML 模型的方式。

在这篇文章中,我将展示一种开始使用 TinyML 的简单方法:在 Arduino 板上实现一个机器学习模型,同时创建一些很酷的东西:一个基于加速度计手势识别系统

手势识别是一个试图通过使用数学算法来识别人类手势的过程。

为了使实验更简单,该系统被设计为只识别两种手势:一种出拳和一种弯曲运动。
这个,在数据科学领域,叫做 二元分类

“出拳”手势。图片作者。

“伸缩”手势。图片作者。

但是……为什么要“超”TinyML?

这项实验的最大挑战是试图在一个非常小的设备上运行预测模型:一个 8 位 T21 微控制器。
要实现这一点,可以使用 内乌顿

内乌顿是一个 TinyML 框架。它允许在没有任何编码和很少机器学习经验的情况下自动构建神经网络,并将它们嵌入到小型计算设备中。
支持 81632 位微控制器,不像tensor flow LiteTinyML 框架只支持 32 位。你不需要一个强大的机器来使用内乌顿,它是一个运行在网络浏览器上的在线软件工具。
使用免费计划,你可以训练和下载无限数量的模型。

…我们开始吧!

实验分为三步:

  1. 捕获训练数据集
  2. 训练模型使用内乌顿
  3. 部署并在 Arduino 上运行模型

实验流程。图片作者。

硬件系统

手势识别系统由以下部分组成:

  • 一款单片机:Arduino Mega 2560
  • 加速度传感器:GY-521模块****

Arduino Mega 2560 是一个基于 ATmega2560 的微控制器板:一个低功耗 8 位 MCU ,带有 256KB 闪存、32 个通用工作寄存器、UART 接口、10 位 A/D 转换器和许多其他外设。
Mega 2560 板是为复杂项目设计的:它比其他 Arduino 板( *Uno、Nano、Micro 等)有更大的
空间
。)。这使得它非常适合有大量数据要处理的机器学习应用程序。***

GY-521 模块是围绕InvenSenseMPU-6050**构建的:一种在单个 IC 中包含三轴 MEMS 加速度计和三轴 MEMS 陀螺仪的传感器。
它的操作非常精确,因为每个通道都包含一个精确的数字转换器。它能够同时捕捉 X、Y 和 Z 轴的值。使用 I2C 接口与 MCU 进行通信。****

下面是用烧结软件设计的连接电路:

连接电路:Arduino Mega2560 和 GY-521。图片作者。

GY-521 由 Arduino Mega power 部分的 5VGND 引脚供电,数据通讯使用 I2C 引脚(引脚 20 和引脚 21)。
剩余的引脚是可选的,对该应用没有用处。

为了验证 GY-521 模块是否正确供电,连接 Arduino 板的 USB 电缆,并检查安装在传感器板上的 LED 是否打开。

GY-521: LED 位置。图片作者。

验证传感器电源后,通过下载 Adafruit MPU6050 Arduino 库并打开绘图仪示例,检查 I2C 通信是否正常工作。**

在 Arduino IDE 中添加库。图片作者。

Adafruit MPU6050 库。图片作者。

MPU6050 示例图片作者。

将示例草图上传到 Arduino 板上,打开工具菜单中的串行绘图仪,在波特下拉菜单中设置 115200,并“摇动”传感器板。预期结果如下:****

MPU6050 绘图仪的串行绘图仪实例。图片作者。

现在,系统已准备好收集加速度计和陀螺仪数据。

捕获培训数据

建立预测模型的第一步是收集足够的运动测量值。
这组测量被称为训练数据集,它将用于训练
内乌顿
神经网络构建器。
**

最简单的方法是通过捕捉加速度和陀螺仪测量值并将结果存储在一个文件中,重复几次相同的两个动作(打孔弯曲)。
为此,您创建一个专用于传感器数据采集的 Arduino 草图。该程序将获取每个动作的测量值,并将传感器测量值输出打印在串行端口控制台上。****

你将完成至少 60 个动作:第一个动作 30 个(出拳),第二个动作 30 个(弯曲)。对于每个动作,您将在 1 秒时间窗口内获得 50 个加速度和 50 个陀螺仪测量值(采样时间:20 毫秒—50Hz )。在这个实验中,60 个动作就足够了。通过增加运动测量的数量,可以提高模型的预测能力。然而,大型数据集会导致过拟合模型。没有“正确的”数据集大小,但建议使用“试错法”方法。****

Arduino 草图的串口输出将根据内乌顿训练数据集要求进行格式化:

  • CSV 格式:是一种数据库文件格式。文件的每一行都是一个数据记录,由一个或多个用逗号分隔的字段组成。
  • 至少 50 条数据记录(对于二元分类任务,意味着每组至少 25 条数据记录)。
  • 第一行必须包含列名(例如 ax0、ay0、az0、gx0、gy0、gz0、… )。
  • 必须存在一个目标变量列。每一行必须被分配到一个特定的目标组(在本实验中:对于冲头,为“0”;对于弯曲,为“1”)。****
  • 使用一个作为小数点分隔符。

下面, Arduino 程序用于数据集创建:

  • IMU 传感器初始化和 CSV 报头生成:
**#define **NUM_SAMPLES 50**Adafruit_MPU6050 mpu;void **setup**() {// init serial port**Serial.begin**(115200);while (!Serial) {delay(10);} // init IMU sensorif (!**mpu.begin**()) {while (1) {delay(10);}}// configure IMU sensor// *[...]* // print the CSV header (ax0,ay0,az0,...,gx49,gy49,gz49,target)for (int i=0; i<**NUM_SAMPLES**; i++) {Serial.print("**aX**");Serial.print(i);Serial.print(",**aY**");Serial.print(i);Serial.print(",**aZ**");Serial.print(i);Serial.print(",**gX**");Serial.print(i);Serial.print(",**gY**");Serial.print(i);Serial.print(",**gZ**");Serial.print(i);Serial.print(",");}Serial.println("**target**");
}**
  • 采集 30 个连续运动。如果加速度总和高于某个阈值(例如**2.5g),则检测到运动开始。**
***#define **NUM_GESTURES    30**#define GESTURE_0       0
#define GESTURE_1       1
#define **GESTURE_TARGET** GESTURE_0 
//#define **GESTURE_TARGET** GESTURE_1 void **loop**() {sensors_event_t a, g, temp;while(gesturesRead < NUM_GESTURES) {// wait for significant motionwhile (samplesRead == NUM_SAMPLES) {// read the acceleration datampu.getEvent(&a, &g, &temp);// sum up the absolutesfloat aSum = fabs(a.acceleration.x) + fabs(a.acceleration.y) + fabs(a.acceleration.z);// check if it's above the thresholdif (aSum >= ACC_THRESHOLD) {// reset the sample read countsamplesRead = 0;break;}}// read samples of the detected motionwhile (samplesRead < **NUM_SAMPLES**) {// read the acceleration and gyroscope datampu.getEvent(&a, &g, &temp);samplesRead++;// print the sensor data in CSV formatSerial.print(a.acceleration.x, 3);Serial.print(',');Serial.print(a.acceleration.y, 3);Serial.print(',');Serial.print(a.acceleration.z, 3);Serial.print(',');Serial.print(g.gyro.x, 3);Serial.print(',');Serial.print(g.gyro.y, 3);Serial.print(',');Serial.print(g.gyro.z, 3);Serial.print(','); // print target at the end of samples acquisitionif (samplesRead == **NUM_SAMPLES**) {Serial.println(**GESTURE_TARGET**);}delay(10);}gesturesRead++;}
}***

首先,在串行监视器打开并且手势 _ 目标设置为手势 _0 的情况下运行上面的草图。然后,在手势 _ 目标设置为手势 _1* 的情况下运行。每次执行时,执行相同的动作 30 次,尽可能确保动作以相同的方式执行。*****

将两个动作的串行监视器输出复制到一个文本文件中,并将其重命名为“trainingdata”。 csv ”。

CSV 格式的训练数据集示例。图片作者。

用内乌顿·丁尼尔训练模型

训练模型的过程包括向机器学习算法提供训练数据以供学习。在这个阶段,您尝试将权重和偏差的最佳组合与 ML 算法相匹配,以最小化损失函数。

内乌顿自动执行训练,无需任何用户交互。
内乌顿
训练神经网络快速简单,分为三个阶段:
**

  1. ****数据集:上传和验证
  2. ****训练:自动 ML
  3. ****预测:结果分析和模型下载

数据集:上传和验证

  • 首先,创建一个新的内乌顿解决方案,并将其命名为(例如手势识别)。

内乌顿:添加新的解决方案。图片作者。

  • 上传 CSV 训练数据集文件。

内乌顿:上传 CSV 文件。图片作者。

  • 内乌顿根据数据集要求验证 CSV 文件。

内乌顿:数据集验证。图片作者。

  • 如果 CSV 文件符合要求,将出现绿色复选标记,否则将显示错误消息。

内乌顿:经过验证的数据集。图片作者。

  • 选择目标变量的列名(如 target ,点击 Next )。

内乌顿:目标变量。图片作者。

内乌顿:数据集内容预览。图片作者。

培训:自动 ML

现在,让我们进入训练的核心!

  • 内乌顿分析训练数据集的内容并定义 ML 任务类型。有了这个数据集,自动检测二元分类任务。****

内乌顿:任务类型。图片作者。

  • ****指标用于监控和测量模型在训练期间的表现。对于这个实验,您使用了准确性度量:它表示预测类的准确性。值越高,模型越好。

内乌顿:公制。图片作者。

  • 启用 TinyML 选项,允许内乌顿为微控制器构建一个微型模型。

内乌顿:TinyML 选项。图片作者。

  • 在 TinyML 设置页面,在下拉菜单中选择“,并启用“ 【浮点数据类型支持】 选项。这是因为实验中使用的微控制器是支持浮点数的 8 位微控制器。

内乌顿:TinyML 设置。图片作者。

  • 按下“开始训练按钮后,您将看到进程进度条和完成百分比。**

内乌顿:训练开始了。图片作者。

  • 第一步是数据预处理。它是准备(清理、组织、改造等的过程。)原始数据集,使其适合于训练和构建 ML 模型。******
  • 数据预处理完成后,模型训练开始。这个过程可能需要很长时间;您可以关闭窗口,并在该过程完成后返回。在训练期间,您可以通过观察模型状态(一致)或不一致)和目标度量值来监控实时模型性能。******

内乌顿:数据预处理完成。图片作者。

  • 训练完成后,“状态将变为“训练完成”。模型是一致的,并已达到最佳预测能力。****

内乌顿:培训完成。图片作者。

预测:结果分析和模型下载

就这样…模型做好了!

内乌顿:培训完成。图片作者。

训练程序完成后,您将被重定向到预测部分。
在本次实验中,模型达到了 98% 的准确率。这意味着从 100 条预测记录中,有 98 条被分配到了正确的类别……
真令人印象深刻!
****

而且要嵌入的模型大小小于 3KB
考虑到正在使用的 Arduino 板的内存大小为 256KB ,8 位微控制器的典型内存大小为 64KB÷256KB ,这是一个非常小的尺寸。

内乌顿:度量。图片作者。

要下载模型档案,点击下载按钮。**

内乌顿:预测选项卡。图片作者。

在 Arduino 上部署模型

现在,是时候将生成的模型嵌入微控制器中了。

内乌顿下载的模型档案包括以下文件和文件夹:**

  • / 模型 : 紧凑形式(十六进制和二进制)的神经网络模型。
  • / neuton : 用于执行预测、计算、数据传输、结果管理、等的一组功能。**
  • user_app.c :一个文件,您可以在其中设置应用程序的逻辑来管理预测。

内乌顿模型档案馆。图片作者。

首先,修改 user_app.c 文件,添加初始化模型和运行推理的函数。

****/** Function: model_init* ----------------------------**    returns: result of initialization (bool)*/
uint8_t **model_init**() {uint8_t res;res = **CalculatorInit**(&neuralNet, NULL);return (ERR_NO_ERROR == res);
}/** Function: model_run_inference* ----------------------------**   sample: input array to make prediction*   size_in: size of input array*   size_out: size of result array**   returns: result of prediction*/
float* **model_run_inference**(float* sample, uint32_t size_in, uint32_t *size_out) {if (!sample || !size_out)return NULL; if (size_in != neuralNet.inputsDim)return NULL; *size_out = neuralNet.outputsDim; return **CalculatorRunInference**(&neuralNet, sample);
}****

之后,创建 user_app.h 头文件,允许主应用程序使用用户函数。

****uint8_t **model_init**();
float*  **model_run_inference**(float* sample, uint32_t size_in, uint32_t* size_out);****

下面是 Arduino 主要应用示意图:

  • 型号初始化****
****#include "src/Gesture Recognition_v1/**user_app.h**"void **setup**() {// init serial port and IMU sensor// *[...]* // init Neuton neural network modelif (!**model_init**()) {Serial.print("Failed to initialize Neuton model!");while (1) {delay(10);}}
}****
  • 模型推理****
****#define GESTURE_ARRAY_SIZE  (6*NUM_SAMPLES+1) void **loop**() {sensors_event_t a, g, temp;float gestureArray[GESTURE_ARRAY_SIZE]  = {0}; // wait for significant motion// *[...]* // read samples of the detected motionwhile (samplesRead < NUM_SAMPLES) {// read the acceleration and gyroscope datampu.getEvent(&a, &g, &temp); // fill gesture array (model input)gestureArray[samplesRead*6 + 0] = a.acceleration.x;gestureArray[samplesRead*6 + 1] = a.acceleration.y;gestureArray[samplesRead*6 + 2] = a.acceleration.z;gestureArray[samplesRead*6 + 3] = g.gyro.x;gestureArray[samplesRead*6 + 4] = g.gyro.y;gestureArray[samplesRead*6 + 5] = g.gyro.z;samplesRead++;delay(10); // check the end of gesture acquisitionif (samplesRead == **NUM_SAMPLES**) {uint32_t size_out = 0;// run model inferencefloat* result = **model_run_inference**(gestureArray,  GESTURE_ARRAY_SIZE, &size_out); // check if model inference result is validif (result && size_out) {// check if problem is binary classificationif (size_out >= **2**) { // check if one of the result has >50% of accuracyif (result[0] > **0.5**) {Serial.print(**"Detected gesture: 0"**); // *[...]*} else if (result[1] > **0.5**) {Serial.print(**"Detected gesture: 1"**); // *[...]*} else { // solution is not reliableSerial.println("Detected gesture: NONE");} }}}}
}****

行动模型!

项目和代码准备好了!

****/neuton_gesturerecognition|- /src| |- /Gesture Recognition_v1|   |- /model|   |- /neuton|   |- user_app.c|   |- user_app.h|- neuton_gesturerecognition.ino****

现在,是时候看看预测模型的运行了!

  • 验证硬件系统设置是否正确
  • 打开主应用程序文件
  • 点击验证按钮,然后点击上传****
  • 打开串行监视器**
  • 把你的硬件系统抓在手里,做一些动作。

对于每个检测到的运动,模型将尝试猜测运动的类型(0-击打或 1-弯曲)以及预测的准确性。如果预测的准确度低( 0.5 ),则模型不做出决定。****

下面是一个模型推理执行的例子:

内乌顿手势识别系统的串行监视器输出。图片作者。

…已经完成了?

内乌顿做机器学习简单快捷。在低功耗 8 位微控制器上实现的模型精度和性能令人印象深刻!
内乌顿适合快速原型开发。它允许用户专注于应用程序,避免在复杂的手动统计分析中浪费时间。
**

在这里,你可以找到本文描述的所有 Arduino 草图!

深度学习中的不确定性——随机不确定性和最大似然估计

原文:https://towardsdatascience.com/uncertainty-in-deep-learning-aleatoric-uncertainty-and-maximum-likelihood-estimation-c7449ee13712

纳赛尔·塔米米在 Unsplash 上的照片

在之前的文章中,我们讨论了深度学习中的 softmax 输出和不确定性。现在,我们用张量流概率来扩展这些。

我将会有一系列的文章来解释这些术语并展示 TFP 的实现。

  • 第 1 部分——简介
  • 第 2 部分——随机不确定性和最大似然估计
  • 第 3 部分——认知不确定性和贝叶斯反推
  • 第 4 部分—实现完全概率贝叶斯 CNN
  • 第五部分——贝叶斯 CNN 实验
  • 第 6 部分—贝叶斯推理和转换器

简而言之张量流概率..

TensorFlow Probability (TFP)是一个用于概率编程和统计推断的库。它提供了一个高级 API 来构建和操作概率分布,并对它们进行后验推断。 TFP 与 TensorFlow 生态系统集成,允许您在 TensorFlow 中构建和训练概率模型,然后在文本分类、图像识别等应用中使用这些模型进行推理。

在接下来的部分中,我们将使用张量流概率来表示随机不确定性:

本文组织如下:

  • 什么是任意不确定性?
  • 未能捕捉任意不确定性的模型
  • 张量流概率层简介
  • 最大似然估计和负对数似然
  • 捕捉随机不确定性的概率模型

在开始任何事情之前,让我们看看这些任务的导入:

什么是随机不确定性?

Aleatoric 来自 alea ,拉丁词“骰子”。随机不确定性是由事件的随机性引入的不确定性。例如,抛硬币的结果是一个任意事件。

在深度学习中,随机不确定性通常是指输入数据的随机性,这可能是由多种因素造成的,包括传感器噪声、像素噪声等。因此,这意味着我们的训练数据在表示输入和目标的真实关系方面并不完美,这反过来会导致我们的模型不太准确。

还有一件事,增加更多的数据不会减少任意的不确定性。

未能捕捉任意不确定性的模型

  1. 我们的训练数据只是一个线性关系,可以表示为:y=2x+1,然而数据并不完美,存在一些正常的噪声。
  2. 模型有 2 个线性层,因为训练数据是线性的。还要注意,别忘了我们用 **mse** 作为损失函数。我们将在 MLE 部分回顾这一点。
  3. 我们拟合我们的模型,得到了对2的预测。

运行上面的代码,我们将得到以下结果:

图片作者。

我们在这里讨论一下是哪里出了问题。首先,正如我们在上一篇文章中所讨论的,该模型具有点估计权重。除此之外,我们还有一个点估计预测。

模型也不能捕捉数据的范围。如果我们可以建立一个模型,使用标准差和平均值绘制以下数据的 95%区间,会怎么样?

张量流-概率层简介

在进入 TFP 之前,让我们从一个普通的模型开始:

当我们运行上面的代码时,我们将得到以下结果:

图片作者。

这是意料之中的,因为我们的Dense层是由一个tanh函数激活的,权重是由 1 初始化的。

因此,该模型获得输入并将它们乘以 1,然后将这些值传递给tanh函数。回想一下我们的模型是:

带线路:

model_predictions = np.maximum(0, model.predict(space))

负值被消除,因此生成了上面的图。综上所述,我们创建了一个模型模型模型np.maximum(0, tanh)

探索前馈对象类型

# Output:
Feedforward returns: tf.Tensor([[0.19737528]], shape=(1, 1), dtype=float32)Allclose!

重要的一点是前馈(模型(x) )返回一个tf.Tensor。我们也刚刚确认了模型准确输出np.maximum(0, tanh)

预测同一个样本五次

sample = tf.convert_to_tensor([0.2])
for _ in range(5):print(model(sample).numpy()[0])--> [0.19737528]
--> [0.19737528]
--> [0.19737528]
--> [0.19737528]
--> [0.19737528]

他们都是一样的,现在也记住这一点。这意味着模型是确定性的。

添加 TFP 层很容易,因为 TFP 与 TensorFlow 本身集成在一起。

输出分布的模型

Feedforward returns: tfp.distributions._TensorCoercible("tensor_coercible",batch_shape= [1, 1],event_shape=[], dtype=int32)

让我们一行一行地解开这个模型。该模型具有如前一示例的密集层,但在此之后它具有tfpl.DistributionLambda。这允许模型输出一个分布而不是一个固定值。所以这就是为什么前馈型现在不一样了。

TFPtfp.distributions (tfd)下有许多分布,但为了简单起见,使用伯努利分布。

简单地说,伯努利分布是一种离散概率分布。它用于模拟二元事件的概率,即一个事件有两种可能的结果。这些结果可以标记为“成功-1”和“失败-0”。

tfpl.DistributionLambda(lambda x: tfd.Bernoulli(probs = x),convert_to_tensor_fn = tfd.Distribution.sample)

来自密集层的输出被传递到伯努利分布中,该分布创建了成功事件的概率。而且我们需要从这个分布中提取数值(在训练时),这是通过convert_to_tensor_fn论证来完成的。使用tfd.Distribution.sample,输出将是来自该分布的样本。典型的选择有:

1) tfd.Distribution.sample (default value)
2) tfd.Distribution.mean
3) tfd.Distribution.mode

当我们从模型中取样时,我们得到了不同的结果。现在我们的模型不再产生确定性的输出。

作者图片

正如我们所讨论的,伯努利样本将是 0 和 1。这张图显示,当x值越高,成为1的概率也越高。

我们现在有了所有的片段,我们看到了如何在我们的模型中包括分布对象,唯一缺少的是在训练时我们应该使用什么作为损失函数?

最大似然估计和负对数似然

回忆我们的数据:

图片作者。

xy 值之间没有完美的线性关系。换句话说,我们不能通过每个点画一条直线,所以我们得出结论,数据有一些噪音。然而,我们可以通过使用线性回归模型来找到线性关系的良好近似,我们首先已经做到了这一点。回想一下我们的第一个模型:

图片作者。

这条直线似乎是一个合理的近似值,我们使用mse作为损失函数。但是,这个mse从何而来?

最大似然估计

神经网络是深度学习的基本工具。它们将数据点映射到参数值,确定看到给定输入的概率,并通过找到最小化训练数据的负对数似然的权重来帮助训练自己。在可能性中,数据是固定的,并且是θ或模型权重函数。

这听起来可能令人困惑,所以让我们一步一步来,我们已经给出:

图片作者。

在优化过程中,我们更喜欢最小化而不是最大化,所以:

图片作者。

现在我们已经看到了负对数似然性的来源。但是mse?怎么样呢?嗯,如果你假设,数据有高斯误差(噪声)和固定方差(误差是独立的),你会以最小化误差平方和结束。

将 NLL 定义为自定义损耗

由于我们处理的是分布和分布对象(其中有.log_prob() ( log-PDF)方法),我们可以直接最小化 NLL 作为自定义损失函数。

捕捉随机不确定性的概率模型

我们可以输出方差固定的独立正态分布。这里,我们只学习目标分布的均值。

换句话说,我们拟合了一个方差固定的正态分布!

我们还可以用这个模型绘制±2sd,因为输出是一个分布对象。我不会在这里包括效用函数,因为它是一个简单的绘图和从分布中获得标准差。

Loss: 1.6406861543655396
Model mean: 1.0017924
Mean of the data: 1.0012743054317013

图片作者。

学习的平均值似乎非常接近原始平均值!但是我们这里处理的是深度学习,所以也可以学习标准差。

这种模式需要一些改变:

模型摘要:

Layer (type)                Output Shape              Param #   
=================================================================dense_8 (Dense)             (None, 32)                64        dense_9 (Dense)             (None, 2)                 66        distribution_lambda_3 (Dist  ((None, 1),              0         ributionLambda)              (None, 1))                         =================================================================

由于涉及到性病,我们可能需要更长时间的训练。现在有 3 主要变化:

  1. 最后一个密是Dense(1 + 1),一个是 mean,一个是 std。
  2. 我们将tf.nn.softplus应用于scale论证,因为不能为负
  3. 我们传递第一个指数作为平均值,传递第二个指数作为标准差。

该模型将产生以下结果:

Loss: 0.68198561668396
Model mean: 0.98936194
Mean of the data: 1.0012743054317013

图片作者。

现在,这些绿色条代表 95%区间内的数据分布,带有学习平均值和标准差。在进入另一个复杂的例子之前,我想展示一些关于那些tfp.layers.的东西

你看到我们需要Dense(1 + 1)用于新的模型。然而,TFP 中有一个很好的包装器来处理这些参数。我们可以编写相同的模型:

params_size确保最后一个密集层有正确的单元数。

此外,我们可以使用tfpl.IndependentNormal来代替DistributionLambda,它是 lambda 层的包装器。这次通过事件形状就够了。event_shape为 1,因为数据只有一维。

请注意,返回的模型摘要是相同的。

任意不确定性非线性数据

现在,由于数据不再是线性的,所以模型被激活了。

图片作者。

我们可以写一个模型来捕捉任意的不确定性,想法是一样的:

该模型将给出以下结果:

图片作者。

让我们讨论一下任意不确定性的高低。

图片作者。

分类示例

分类也遵循同样的逻辑。我们可以为imdb_reviews数据集写一个模型。

我将只为它添加代码:

现在,我们已经用IndependentBernoulli代替了 Lambda 层。如果我们做多类分类,我们会使用tfpl.OneHotCategorical()

结论

我们

  • 所讨论的随机不确定性不能通过增加新的样本来减少,
  • 简要了解了 TFP 分布对象以及如何将它们集成到我们的 Keras 模型中,
  • 看过 MLE 和 NLL,
  • 创建了 Keras 模型来表示任意的不确定性。

所有的代码都可以在这里找到。

后续步骤

在接下来的文章中,我们将讨论一些使用 TFP 来表示认知不确定性,也就是模型不确定性的方法。

此外,我们还将看到如何编写一个完全概率模型来表示噪声项不是正态或高斯的数据中的不确定性。

深度学习中的不确定性—贝叶斯 CNN |张量流概率

原文:https://towardsdatascience.com/uncertainty-in-deep-learning-bayesian-cnn-tensorflow-probability-758d7482bef6

创建一个深度学习模型,知道它不知道什么。

埃文·丹尼斯在 Unsplash 上的照片

这是深度学习不确定性系列第四** 部分。**

  • 第 1 部分—简介
  • 第 2 部分—随机不确定性和最大似然估计
  • 第 3 部分——认知不确定性和反向投影贝叶斯
  • 第 4 部分——实现完全概率贝叶斯 CNN
  • 第五部分——贝叶斯 CNN 实验
  • 第六部分——贝叶斯推理和变形金刚

介绍

在本文中,我们将探索如何使用张量流概率(TFP)实现完全概率贝叶斯 CNN 模型。在第二部分中,我们创建的模型只能捕捉随机不确定性,或数据不确定性

在接下来的部分(第 3 部分)我们改变了我们的模型层,使它们具有概率分布,而不是固定的权重和偏差。通过这种方式,我们能够捕捉模型参数上的认知不确定性

现在,是时候将第二部分和第三部分组合在一起了。换句话说,我们将在本帖中介绍如何创建一个基本的 BNN ,并解释图层参数。

文章组织如下:

  • 变化和重新参数化层
  • 正常 CNN 和参数
  • 重新参数化层中的自定义优先级
  • KL 散度的近似值
  • 创建全概率贝叶斯 CNN
  • 这个模型知道自己不知道的!
  • 结论&下一步

在我们开始之前,让我们看看导入:

变层和重新参数化层

首先,从 2022 年 2 月 27 日在 TFP 中没有像我们在第 3 部分中使用的dense varianial那样的conv variant层。相反,

  • 卷积 1 去参数化
  • 卷积 2 重新参数化
  • 卷积 3 重新参数化

层是存在的。底层的算法与我们在第 3 部分中讨论的算法相同。

让我们看看convolution 2d 重新参数化层的参数:

divergence_fn =回想一下:“我们希望先验和变分后验之间的KL-散度尽可能低,同时保持预期可能性尽可能高(** 第 3 部分 )”。因此,根据我们对先验函数和后验函数的选择,如果可以解析地计算 KL-散度,并且已在 TFP 中注册,它将使用精确的计算。这就是kl_use_exact参数在DenseVariational层实际做的事情!但是在convolution 2d 重新参数化中我们提供了它。最后,我们将它除以样本总数,这是我们在第 3 部分看到的原因。**

如果 KL 无法解析计算,我们将看看我们应该提供什么。

prior_fn & posterior_fn =这些是priorvariational_posterior功能(第三部分),我们可以定制它们。例如,我们可以使用拉普拉斯先验等。

is_singular =如果设置为True,数值再次变成点估计值,而不是概率值。考虑这个场景:

bias_prior_fn = None,
bias_posterior_fn = ...normal_fn(is_singular=True)

在上面的代码中,偏差参数的学习方式与常规卷积层相同,它固定为一个值。

is_singular=True被传递时,一个tfd.Deterministic的实例将被创建。这就是点估计值的获得方式。

现在我们已经看到了重新参数化层的参数。我们可以开始写模型了。

普通 CNN

首先,让我们从如何创建一个普通的 CNN 开始:

相当简单,只是一个普通的 CNN 网络!我们将把这个模型转换成贝叶斯卷积神经网络。

并且注意这个型号总共有 98.442 个参数

概率贝叶斯 CNN

重新参数化图层的自定义先验

由于重新参数化层在方法参数方面不同于密集变量化层,我们需要在编写自定义先验&后验时考虑这一点。

例如,如果我们想要提供自定义病历,它们应该定义如下:

现在,每个功能都需要dtype, shape, trainable, add_variable_fn。让我们打开batch_ndimsreinterpreted_batch_ndims的包装:

我们使用正态分布来观察reinterpreted_batch_ndims如何影响分布。

重新解释 _ 批处理 _ndims: 0

  • batch_shape(4,) =四个正态分布
  • 样本形状:(4,) =四个样本
  • ****将它们放在一起:来自四个正态分布的四个样本。

重新解释 _ 批处理 _ndims: 1 → 要重新解释为事件形状的批处理形状的数量。在我们的例子中是 1。

  • ****event_shape(4,)=多元正态分布。所以这个正态分布是由四个随机变量组成的。
  • 样本形状:(4,) =四个样本
  • ****将它们放在一起:来自多元正态分布的四个样本。

由于后验概率需要是可训练的,我们将在下一部分中看到如何创建自定义后验概率。我不想在这篇文章中过多地描述 BNN 的具体部分。****

逼近 KL 散度

我不会在这里讨论数学,因为这超出了本文的范围。

KL 散度的蒙特卡罗近似可以实现如下:

出于演示目的,我们将使用这个近似函数。

创建模型

现在,我们将创建一个函数来获得convolution 2d 重新参数化由于这篇文章涵盖了基础知识,我们将使用mnist数据集。

在这个模型中,我们使用默认的值作为的值。我们将在本系列的下一部分中看到如何完全定制它们。

****输出也是一个分布对象(onehotcategorial),所以我们可以直接使用负对数似然作为损失函数。这意味着我们也可以用这个模型捕捉任意的不确定性。

这里重要的一点,BNN 的总参数是普通 CNN型号两倍。这是因为每个权重和偏差现在都有均值和方差,所以我们用 Bayes-by-Backprop 学习这些值。这让我们能够代表模型参数的认知不确定性。****

由于我们可以捕捉任意的和认知的不确定性,我们称这个模型为全概率贝叶斯神经网络。****

divergence_fn是我们为 KL 近似创建的函数。这里,我们可以使用 KL-Divergence 的分析值,因为先验和后验选择是具有均值和方差的正态分布。

这是贝叶斯 CNN 模型的一个缺点,它可能会变得计算密集,这取决于变分后验概率的选择。如果我们还想通过使用 MultivariateNormalTriL学习协方差,那么将会有更多的参数。****

分析模型预测和输出

首先,让我们从数据集中抽取一些样本:

我们将定义一个效用函数来分析预测和输出。现在,每次向前传递将给出不同的值,因为我们从模型参数中采样以获得预测结果。它可以被解释为集成分类器。

首先,我们创建空数组来存储输出。我选择forward_passes为 10,你可以增加到任何合理的数字,比如 100 或者 200。forward_passes定义从(第 8 行)模型中获取的样本数量。

模型输出一个分布对象,为了得到概率我们使用mean()方法(第 10 行)。同样的事情也适用于extracted_std

每门课会有 10 个不同的预测。在绘制它们之前,我们独立地获得每个类别的 95%预测区间(第 31 行-第 33 行)。如果我们用 100 作为forward_passes,那么将会有 100 种不同的预测。

绘图过程很简单,我在这里不赘述。

让我们测试一下这个功能:

analyse_model_prediction(example_images[284], example_labels[284])

这将给出输出:

Label 8 has the highest std in this prediction with the value 0.157

图片作者。

在进入细节之前,考虑一下最好的情况。如果对于给定图像,模型在每次向前传递时输出 1.0,则计算 95%的预测间隔也将给出 1.0

在这种情况下,柱会更短,表明预测的不确定性较低。因此,关键的一点是,柱越高,不确定性就越高!****

在这个预测中,我们看到模型将概率指定为 8。然而,它们之间的范围概率有明显的区别。在向前传递中,输出大约是 0.5,而在另一个传递中,输出是 1.0。因此,我们得出结论,模型对该预测不太确定。****

analyse_model_prediction(example_images[50], example_labels[50])

输出如下所示:

Label 0 has the highest std in this prediction with the value 0.001

图片作者。

瞧啊。模型在每次向前传递中分配了非常高的概率。我们说这个模型对这个预测是有把握的。

这个模型居然知道自己不知道的东西!

在下结论之前,让我们添加随机噪声向量,看看它如何影响预测。

noise_vector = np.random.uniform(size = (28, 28, 1), low = 0, high = 0.5)# Make sure that values are in (0, 1).
noisy_image = np.clip(example_images[50] + noise_vector, 0, 1)analyse_model_prediction(noisy_image, example_labels[50])

图片作者。

类 0 的概率仍然很高,但是检查结果可能是值得的。想象你正在使用一个贝叶斯 CNN,如果你得到这样的输出,你会怎么做或者你会怎么解释它?

我的解释是,模型认为它属于 0 类,但是有一些不确定性,所以我可能会看一下。

如果我们增加图像中的噪声,使其更加扭曲,会发生什么?

noise_vector = np.random.uniform(size = (28, 28, 1), low = 0, high = 0.5)noisy_image = np.clip(example_images[50] + noise_vector*2, 0, 1)
analyse_model_prediction(noisy_image, example_labels[50])

图片作者。

如果我们使用一个普通的 CNN 模型,对于某个类,输出可能仍然很高。这就是为什么正常的神经网络被认为对自己的预测过于自信。

但是贝叶斯 CNN 模型说它不能正确地分类这个图像。当它不知道的时候是可以的,这比分配一个错误的标签要好。我们可以通过观察高柱得到这个结论,有 3 个非常高的柱和 3 个中等高的柱。这足以说明不确定性很高。

最后一个样本,噪声矢量:

analyse_model_prediction(np.random.uniform(size = (28, 28, 1),low = 0, high = 1))

这将产生以下输出:

Std Array: [0.27027504 0.22355586 0.19433676 0.08276099 0.1712302  0.143693980.31018993 0.13080781 0.47434729 0.18379491]

图片作者。

结果是预料之中的,没有不确定性低的单一预测。这个模型说它不能正确分类这个向量。

结论和下一步措施

在本文中,我们:

  • 查看convolution 2d 重新参数化层。
  • 如果 KL 散度不能用张量流概率进行解析计算,我看到了如何近似 KL 散度。
  • 创造了一个完全概率的贝叶斯 CNN** 。**

你可以从这里得到密码和笔记本。

****在下一部分中,我们将使用自定义先验和后验函数同时使用真实数据集自定义模型。

深度学习中的不确定性——简介

原文:https://towardsdatascience.com/uncertainty-in-deep-learning-brief-introduction-1f9a5de3ae04

当人类不知道某个特定问题的答案时,他们会说“我不知道”。但这也适用于深度学习模型吗?

迈特·沃尔什在 Unsplash 上拍照

在这篇文章中,我将尝试给出深度学习模型中不确定性的直觉,而不是深入解释这些不确定性。在接下来的部分中,我们将使用张量流概率对它们进行解释。

我将会有一系列的文章来解释这些术语并展示 TFP 的实现。

  • 第一部分——简介
  • 第 2 部分——随机不确定性和最大似然估计
  • 第 3 部分——认知不确定性和反向投影贝叶斯
  • 第 4 部分——实现完全概率贝叶斯 CNN
  • 第五部分——贝叶斯 CNN 实验
  • 第 6 部分——贝叶斯推理和变压器

第一,什么是不确定性?

不确定性可以定义为对某事缺乏了解或确定性。这是生活中不可避免的一部分,在自然和人工系统中无处不在。

在深度学习的背景下,有两种主要类型的不确定性:

  • 1)随机不确定性:这是由于数据的随机性而产生的不确定性。
  • 2)认知不确定性:这种不确定性是由于缺乏关于模型真实参数的知识。

预测中不确定性的例子。作者图片

当我们训练我们的深度学习模型时,我们采用最大似然估计(MLE)。

简单来说, MLE 是从一组数据中估计统计模型参数的方法。这是一种寻找参数值的技术,可以在数据和模型之间产生最佳匹配。

换句话说,我们寻找能很好解释数据的权重。或者,根据我掌握的数据,最佳权重是多少?这是一个经典的优化问题,也是一个随机过程。但问题是,是否有多组权重可以很好地解释数据?

基本线性回归

首先,我们创建一个带有随机噪声的线性数据。

图片作者。

根据我们掌握的数据,应该有不同的模型组合。首先让我们定义一个效用函数。该函数将采用 3 个独立的模型,并使用相同的损失和优化器来训练它们。然后,它将打印 15 的预测结果,并绘制线条。

我们实际上不需要relu激活,因为数据集是线性的。我在试验一些模型,它们的最终形式有relu,所以我最终没有移除它们。

如你所知,每个模型都有不同的起点,因为初始化器是不同的。因此,学习到的权重应该不同,但不会相差太多。

当我们运行上面的脚本时,我们将得到:

Model1: Prediction for 15: [[31.22317]]
Model2: Prediction for 15: [[30.969236]]
Model3: Prediction for 15: [[31.227913]]

作者提供的图片

所有的线看起来都是合理的,尽管学习到的权重是不同的。那是一个不确定性,根据起点的不同,有不止一个模型来解释给定的数据。换句话说,权重是不确定的!

估计的参数主要取决于两个因素:

  • 数据集大小
  • 梯度下降的起点

正如你可能猜到的,当有更多的数据时,估计的参数会更准确。

如果考虑更大的模型和数据,在每次重新运行(拟合过程)中,大多数情况下结果是相似的,但权重不会完全相同。

基本图像分类和 Softmax

这一次,我将展示一个不同的例子,其中的任务是一个多类分类。为此,我将使用 fashion_mnist 作为基本示例。

加载和处理数据后,可以编写如下简单模型:

模型总结看起来是这样的,没有什么特别的:

Layer (type)                Output Shape              Param #   
=================================================================conv2d (Conv2D)             (None, 28, 28, 16)        160       max_pooling2d (MaxPooling2D  (None, 14, 14, 16)       0         )                                                               conv2d_1 (Conv2D)           (None, 14, 14, 32)        4640      max_pooling2d_1 (MaxPooling  (None, 7, 7, 32)         0         2D)                                                             conv2d_2 (Conv2D)           (None, 7, 7, 64)          18496     max_pooling2d_2 (MaxPooling  (None, 3, 3, 64)         0         2D)                                                             conv2d_3 (Conv2D)           (None, 3, 3, 128)         73856     global_max_pooling2d (Globa  (None, 128)              0         lMaxPooling2D)                                                  dense_6 (Dense)             (None, 128)               16512     dense_7 (Dense)             (None, 10)                1290      =================================================================
Total params: 114,954
Trainable params: 114,954
Non-trainable params: 0# Last Epoch
Epoch 16/16
469/469 [==============================] - 6s 12ms/step - loss: 0.1206 - acc: 0.9551 - val_loss: 0.3503 - val_acc: 0.9014

让我们忽略这里的过度拟合部分,并绘制一些预测的softmax输出。在此之前,我们将从数据集中抽取一些样本和标签,并对它们进行预测。

我们拿第一批来预测。

作者图片

对于第一个图,正确的标签是 1。并且模型为该样本输出如此高的softmax值。虽然softmax值很高,但是这个并不能说明模型有多可靠。

为了测试这一点,一个随机噪声向量将被给予模型。我们将讨论softmax输出。

(我将在后面的部分解释第二个模型的参数ensemble

{0: 6.912527, 1: 0.0038282804, 2: 0.9346371, 3: 0.7660487, 4: 4.7964582, 5: 3.58412e-05, 6: 80.114265, 7: 0.0002095818, 8: 6.4642153, 9: 0.007769133}

图片作者。

根据softmax输出,有 80% 的概率该给定向量属于类别 6。

关于softmax误解的值越高,模型就越有信心[1]。情况并非总是如此Softmax预测错误时,值可能非常高。例如:

  • 敌对攻击
  • 非分布数据(例如:随机噪声向量)

此外,在我们讨论的回归示例中,根据梯度下降的起点,存在多个模型权重来解释数据。这里,我们完全忽略了它们,因为模型具有点估计权重,而我们只有一个模型。

所以现在问题来了,如果我们考虑其他可能的模型,并把它们组合起来得到一个预测,会怎么样?比方说,如果我们为这些数据集合 100 个不同的模型会怎么样?

组装 100 个模型

训练 100 个不同的深度学习模型,如果考虑现实生活中的项目,并不实用。我们可以想一个简单的方法从单点估计模型中得到不同的预测。

如你所知,辍学是深度学习中采用的一种正则化技术,以防止过度拟合。它的工作原理是在训练过程中随机从网络中删除一些单元(神经元),希望这样可以防止任何一个神经元变得太有影响力。我将解释我们如何使用它来得到不同的模型并将它们组合在一起。

在此之前,让我们创建相同的模型,并添加辍学。

从模型中获取预测时,丢弃层不活动。如果我们在预测输入的同时使用漏失层的效果会怎么样?

如果我们使用__call__模型的方法,我们仍然可能使它们保持活跃:

for _ in range(4):predict_noise = model2(tf.expand_dims(random_vector,axis = 0),training = True).numpy().squeeze()print('Softmax output for class 0:', predict_noise[0] * 100)

使用training = True,我们保持活跃的Dropout层,就好像网络正在训练,但我们只应用向前传递。上面的代码将打印这些结果:

Softmax output for class 0: 3.7370428442955017
Softmax output for class 0: 4.5094069093465805
Softmax output for class 0: 0.9782549925148487
Softmax output for class 0: 1.7607659101486206

这是因为脱落层,一些神经元随机脱落。因此,作为一种天真的方法我们可以使用这种策略来获得不同的预测,就好像我们有 100 个不同的模型一样。

第一个模型是完全确定性的,如果你预测相同的样本,你每次都会得到相同的输出,并且因为它没有层,所以没有实际可行的方法来得到不同的预测。但是现在,我们可以从一个单一模型得到不同的预测****

回想一下,我们有一个效用函数来预测给定噪声矢量的softmax输出:

现在我们将model2作为模型参数,将100作为集合大小。所以现在我们从模型中得到 100 次预测,同时保持脱落层活动。对于最终的预测,我们将只取预测的平均值

{0: 4.0112467, 1: 0.4843149, 2: 11.618713, 3: 8.531735, 4: 8.837839, 5: 0.04070336, 6: 59.83536, 7: 0.96540254, 8: 4.8854494, 9: 0.7892267}

作者图片

嗯,结果比其他模型好,但不是最好的。这是因为即使我们使用了某种集合技术,模型的权重也有点估计值。

让我们用新模型预测第一批并检验结果。

predictions = []
for _ in range(1000):predicted_ensemble = model2(samples,training = True).numpy().squeeze()predictions.append(predicted_ensemble)
predictions = np.array(predictions)
print('Predictions shape:', predictions.shape) # (1000, 128, 10)
predictions_median = np.median(predictions, axis = 0)

我们不用均值,取中值作为最终预测。当我们绘制预测图时,它们看起来像这样:

作者图片

如果我们将这些预测与另一个进行比较,我们会发现一些预测略有变化。这是因为我们在获得预测的同时保持主动退出,这使得我们能够拥有随机集合模型。

现在,我们可以针对单个输入获得多个不同的预测。这意味着我们可以从一组预测中生成置信区间

退出以获得置信区间

使用 for 循环,我们可以获得样本的预测:

我将添加一些示例图来对它们进行评论:

图片作者。

该图向我们展示了,即使我们集合了特定输入的1000预测,95%区间的范围与中值和正常预测相同。所以我们可以说这个样本确实属于class 1.不确定性低。

让我们看看另一个例子:

图片作者。

刚刚发生了什么?当我们检查 95%的间隔时,我们看到绿色条比以前高了。这是因为通过辍学模型,我们能够捕捉到预测中的一些不确定性。

结论是,随着这些绿条越来越高,预测越来越不确定。

图片作者。

最后,让我们来检验这个预测。当我们使用单点估计模型时,0 类的概率很小。当我们应用集成技术时,我们看到预测中有巨大的不确定性。注意绿色的条有多高!

结论

我们讨论过:

  • 假设数据集是相同的,则存在取决于梯度下降起点的一组权重。
  • 普通的辍学集合不是最好的方法,因为它有点随机。

后续步骤

在下面的文章中,我们将使用一种更系统的方式来表示任意的和认知的不确定性,使用张量流概率同时详细解释这些术语。

你可以从这里查看用过的笔记本。

参考

[1]:蒂姆·皮尔斯,亚历山德拉·布林特鲁普·朱军,了解 Softmax 信心和不确定性,2021。

深度学习中的不确定性——认知不确定性和反向投影贝叶斯

原文:https://towardsdatascience.com/uncertainty-in-deep-learning-epistemic-uncertainty-and-bayes-by-backprop-e6353eeadebb

知识是在不确定性边缘的无止境的冒险。

雅各布·布朗诺夫斯基

由凯尔·格伦在 Unsplash 上拍摄

这是深度学习不确定性系列第三部

  • 第 1 部分—简介
  • 第 2 部分—随机不确定性和最大似然估计
  • 第 3 部分——认知不确定性和反向投影贝叶斯
  • 第 4 部分——实现完全概率贝叶斯 CNN
  • 第五部分——贝叶斯 CNN 实验
  • 第六部分——贝叶斯推理和变形金刚

介绍

在本文中,我们将探讨如何使用张量流概率来表示认知不确定性,以及理解底层算法和理论背景。

文章组织如下:

  • 什么是认知不确定性
  • 普通神经网络的问题
  • 贝叶斯神经网络
  • 这个方案背后的数学原理
  • 变分贝叶斯方法
  • 贝叶斯神经网络中的反向传播
  • 迷你批次&对 KL 背离重新加权
  • 使用张量流概率的重量不确定性
  • 结论&下一步

什么是认知不确定性?

认知不确定性是关于世界的知识缺失、不精确或者可能是错误的。它存在于现实世界中,不仅仅是一种主观感受。

如果你问我,这是需要处理的最重要的不确定性,因为它阻止你确定任何事情。我们的现实往往是一个很大的未知数,我们做出决定所依据的信息可能是有缺陷的、不完整的,或者根本不可用。

没有人确切地知道任何事情,因为我们出生在一个不确定的世界。

例如,你可能对一个数学问题的答案非常有信心,但这并不意味着你是正确的。这也是深度神经网络的一个问题,由于输出激活,它们被认为过于自信。

一个更具体的例子,如果你随机测量 10 个人来找出拥挤人群的平均身高,你对人群平均身高的估计将会不准确。这是因为你可能选择了比平均水平高或矮的人。你衡量的人越多,你的估计就越准确。这被称为认知的不确定性。

显然,认知的不确定性可以通过更多的数据来减少。因为如果测量的人多,估计会更准确。

在我们开始之前,我将添加导入:

普通神经网络的问题

由于我们使用最大似然估计来获得解释数据的最佳权重值,并且还考虑到我们的数据是有限的,因此应该有不止一个模型来解释或拟合数据。关键在于,权重是单个确定性点估计值。

我们来看看是不是这样,线性回归:

图片作者。

当拟合 3 个独立的模型时,它们的起点将会不同,因为权重是随机初始化的。此外,优化过程是一个随机过程,因此每次的最佳权重值都会不同。

这是关于模型权重的认知不确定性,因为没有唯一的答案。有一组合理的权重。如果数据集变大,我们可以得到更好的估计。

让我们检查一下非线性回归问题:

作者图片

正如我们所料,尽管学习到的权重不同,但这三行似乎都是合理的。同样的事情也适用于这种情况。

而如果考虑现实生活问题,比较复杂,应该有多组权重才能很好的解释数据。因为在现实生活中,模型也更复杂,同时考虑到在损失情况下存在许多局部极小值,权重集对于获得合理的结果更有意义。

简而言之,问题是我们不知道权重的可能值,但有点估计值。

但是,我们如何在神经网络中融入认知不确定性呢?

贝叶斯神经网络

想法

神经网络中的权重不确定性[1]。

当我们训练一个神经网络时,我们将最终得到权重的点估计值。然而,正如我们所讨论的,有多组权重应该能够合理且很好地解释数据。

为了捕捉模型权重中的认知不确定性,我们简单地将它们转换成概率分布。因此,我们不是学习这些点估计值,而是通过反向传播来学习这些分布的均值和标准差。

深潜

由于每个权重都被一个概率分布所代替,所以现在没有这样的单一值。为了进行预测或获得前馈值,我们需要从这些分布中抽取样本。

  • 1) 样本来自网络的权重
  • 2) 确定输出值
  • 3) 更新平均值和标准差。

这个方案背后的数学

论文神经网络中的权重不确定性****【1】介绍了一种完成这项任务的方法,称为反向投影贝叶斯。关键思想依赖于著名的贝叶斯定理:

图片作者。

如果你替换分母,贝叶斯定理的最终形式可以写成:

作者图片

贝叶斯定理使得结合先验置信和似然获得模型参数的分布成为可能,称为后验分布。

原则上的培训流程

  1. 选择先前的分布
  2. 确定可能性
  3. 使用贝叶斯定理确定后验分布

然而,计算真实的后验概率是困难的,并且可能根本不可能,因为它包含复杂的积分。

最后,我们的目标是得到模型权重的分布。

变分贝叶斯方法

您可能已经注意到,分母包含一个复杂的积分来计算真实的后验分布。为此,我们需要近似后验分布。

变分贝叶斯方法试图用已知的分布来逼近真实的后验分布,具体称为变分后验分布。你可能认为用另一个函数逼近一个函数会有风险,因为逼近的函数可能非常糟糕。是的,这是真的,但是为了减轻这个问题,变分后验概率有一些参数。这些参数被调整,使得近似的一个应该尽可能接近真实的后验分布。

图片作者。

变分后验在这里用phi参数化。Theta代表权重,而x代表数据。

库尔贝克-莱布勒散度

凭直觉,这两个分布应该彼此接近。那么,我们如何衡量这一点?嗯,有一个指标叫做 KL-Divergence,定义为:

图片作者。

最后一个等式实际上告诉我们:

我们将第一个积分转换为log(P(x)),因为Q(Theta | Phi)是一个概率分布,积分为 1。

请记住,我们希望 KL 偏差尽可能低。所以现在,这是一个优化问题。因为P(x)是常数,我们可以忽略它。最终,我们只剩下这个等式:

图片作者。

我们将会看到 NLL 的确切含义。但首先让我们总结一下,我们的损失函数是:

图片作者。

还有一件事我想提一下,我不会讨论它是如何得出的,但想展示一下证据下限ELBO。

图片作者。

你可能已经注意到了, ELBO 是我们损失函数的负值。因此,最小化损失实际上相当于最大化 ELBO。

在一天结束时,我们希望先验和变分后验之间的 KL-Divergence 尽可能低,同时保持 T21 期望的可能性尽可能高。

贝叶斯神经网络中的反向传播

回忆一下我们的损失函数:

图片作者。

因此,期望项意味着对负对数似然乘以每个参数进行积分。另一方面,我们有一个 KL 散度,这是另一个积分。跳过很多数学,如果我们把这些方程写成积分,我们将得到:

图片作者。

天真地精确最小化这个成本在计算上是禁止的。相反,使用梯度下降和各种近似法[1]。

phi求导涉及对Theta的积分,也就是模型权重。从计算的角度来看,这可能是非常昂贵的,甚至是不可能的!

一种方法是,我们可以改变这种期望形式,并应用蒙特卡罗近似法来计算 w.r.t Theta的导数。

现在我们有:

图片作者。

这个预期在计算上也有一些问题,因为底层分布依赖于phi

无偏蒙特卡罗梯度

解决计算复杂性问题的一种方法是使用重新参数化技巧。****

期望值的导数[1]。

命题 1 是高斯重新参数化技巧的推广(奥珀和阿尚博,2009;金玛和韦林,2014;Rezende 等人,2014 年)— [1]

我不会在这里更深入,但简单地说,通过重新参数化,我们试图将依赖性从** **phi** 移开,这样期望最终将被独立地获取。**

如果你假设Q(theta | phi),它是变分后验概率,是高斯分布,你将得到上图中的公式(期望值的导数[1])。

所以,我们现在有了所有的片段。让我们将学习过程视为一个高层次的概述:

图片作者。

迷你批次&重新加权 KL-散度

回忆一下我们的损失函数:

图片作者。

一种常见的方法是使用小批量,并对所有元素的梯度取平均值:

(BatchSize 写为缩写 )

****右边的项由 TensorFlow 自动计算。我们还需要重新权衡 KL 术语的权重,以便进行适当的培训。这可以通过以下方式实现

图片作者。

其中 M 为样本总数。默认情况下,TensorFlow 将始终对您训练的每个损失函数或模型的小批量中所有元素的损失进行平均。这也是 KL 背离需要重新加权的原因。从这个推导中可以看出,我们得到了真实 ELBO 物镜的无偏估计。

就是这样,这就是贝叶斯反推方法。现在是时候使用张量流概率来实现这一点了。

使用张量流概率的重量不确定性

致密变异层

我们所说的可以在DenseVariational层的帮助下实现。

该层有四个重要参数。该层具有:

make_prior_fn : Python 可调用函数取[tf.size(kernel)](https://www.tensorflow.org/api_docs/python/tf/size)[tf.size(bias)](https://www.tensorflow.org/api_docs/python/tf/size)dtype并返回另一个可调用函数,该函数取一个输入并产生一个tfd.Distribution实例。

make_posterior_fn : Python callable 取[tf.size(kernel)](https://www.tensorflow.org/api_docs/python/tf/size)[tf.size(bias)](https://www.tensorflow.org/api_docs/python/tf/size)dtype,返回另一个 callable 取一个输入,产生一个tfd.Distribution实例。

kl_weight缩放之前和之后的 KL 发散损失的量。

kl_use_exact Python bool表示应该使用解析 KL 散度,而不是蒙特卡罗(MC)近似。

那些解释摘自的原始文献。让我们用例子来探究它们。

回归中的权重不确定性

图片作者。

让我们通过反向传播算法实现我们在 Bayes 中描述过的priorposterior函数。

priorposterior函数的参数为kernel_sizebias_size。它们被添加并引用到我们想要学习的参数总数中。由于这个先验是不可训练的,当使用Laplace先验时,我们简单地返回一个Sequential模型的可调用对象。先验是不可训练的,因为我们对分布的均值和标准差进行了硬编码。****

posterior函数遵循相同的逻辑,但是我们现在使用VariableLayer来表示这将是可训练分布。而对于params_size,我们让 TFP 来决定为其修正形状,并选择正态分布作为后验。

到目前为止,我们的先验是拉普拉斯先验,后验是正态先验。这就是我们以前讨论过的**posterior**,即变分后验。****

由于输出不是分布,我们可以用mse作为损失函数。换句话说,这个模型不能捕捉任意的不确定性。

kl_weight参数是我们在迷你批次&部分讨论的加权 KL-Divergence 项。通过对 KL 项重新加权,我们将得到无偏估计。回忆公式(ELBO 目标):

图片作者。

M对应于数据集中元素的总数。所以在图层中我们设置了kl_weight = x_100.shape[0]

我们也没有设置kl_use_exact = True。根据用于后验和先验的分布的选择,有可能解析地计算 KL 散度,并且如果它是并且解析解被登记在 TFP 库中,那么kl_use_exact自变量可以被设置为True。否则这将引发一个错误。这里我们用 MC 近似来计算。

你可能会问,在model.compile()我们把亏损设为mse而没有关于KL Term的东西?没错,KL 项是在使用这一层时由model.add_loss内部添加的。实际上,我们可以通过运行以下命令来查看 KL 项:

**model_100.losses # Returns list.**

这将提供:

**[<tf.Tensor 'dense_variational/kldivergence_loss/batch_total_kl_divergence:0' shape=() dtype=float32>]**

我们看到,我们不需要担心损失函数,唯一需要做的事情是设置kl_weight,它完成了。

我们已经完成了我们的模型和层规范,让我们看看摘要:

**Layer (type)                Output Shape              Param #   
=================================================================dense_variational (DenseVar  (None, 1)                4         iational)                                                       =================================================================
Total params: 4
Trainable params: 4
Non-trainable params: 0**

输出形状是预期的,但是在正常情况下,我们只有 2 个参数,它们对应于问题 y = ax + b. 中的系数和 y 截距

问题还是一样。但是现在我们学习权重和偏差的分布参数。在这种情况下,它们中的每一个都有一个均值和方差,总共 4 个可学习参数。****

现在,我们有一个权重和一个偏差项,因此在mean()variance数组中有两个值。例如,后验方差的第一指数对应于权重分布的学习方差。

现在,每次向前传递都会给出不同的预测,因为我们是从权重的分布中取样的。也就是说,可以认为它是一个集成分类器。运行上面的代码,我们将得到:

图片作者。

注意红线是如何摇晃的。这些线是由模型在每次向前传递时生成的。不稳定的线条意味着在这种情况下认知的不确定性很高。因为我们没有无限多的数据点,换句话说,我们的数据集是有限的,我们最终可能会有不同的权重值来很好地解释数据。

起初,我们说过认知的不确定性可以随着更多的数据而减少。我们来看看是不是这样。

图片作者。

关系是相同的,但是这次我们有 1000 个数据点,而不是 100。型号配置也保持不变。我不会重复安装过程,因为它是相同的。当我们从新模型中取样时,我们会得到:

图片作者。

有一点要注意,现在这些线彼此更近了。我们可以用支线剧情来比较它们:

图片作者。

数据集大小的影响

我们得出结论,增加更多的数据会减少认知的不确定性,这可以从图表中看出。当认知的不确定性较小时,线彼此更接近。这是因为当有更多的数据时,我们就有更多的信息。

非线性回归中的权重不确定性

假设我们有这样的数据:

作者图片

让我们稍微改变一下我们的先验和变分后验。

现在,我们已经扩大了我们的先验,因为尺度是 2 的倍数。在我们的VariableLayer中,我们通过了2 * n,这意味着我们想要学习均值和标准差。这是以前由params_size完成的。

另一件事是在事后,我们已经缩小了我们的标准差。其他值可能比0.003更好。如果我们不缩小后验标准差,可能会有一些收敛问题。

**Layer (type)                Output Shape              Param #   
=================================================================dense_variational (DenseVar  (None, 128)              512       iational)                                                       dense_variational_1 (DenseV  (None, 64)               16512     ariational)                                                     dense_variational_2 (DenseV  (None, 1)                130       ariational)                                                     =================================================================
Total params: 17,154
Trainable params: 17,154
Non-trainable params: 0
_________________________________________________________________**

现在,这一次层有非线性激活,其余的过程是相同的。我们指定我们的先验和后验函数,并重新加权 KL 项。

但是,我们需要训练这个模型更长的时间,因为它的工作更难。

为了绘制输出,我们从模型中提取前馈输出 5 次。

图片作者。

如我们所见,我们有合理的路线。通过调整变分后验概率可以改善结果。

在下结论之前,我想提一下变分后验概率的选择会影响模型的总参数。在这种情况下,如果我们将后验概率转换为多元高斯分布,那么模型总结看起来就是这样:

**Layer (type)                Output Shape              Param #   
=================================================================dense_variational_3 (DenseV  (None, 128)              33152     ariational)                                                     dense_variational_4 (DenseV  (None, 64)               34093152  ariational)                                                     dense_variational_5 (DenseV  (None, 1)                2210      ariational)                                                     =================================================================
Total params: 34,128,514
Trainable params: 34,128,514
Non-trainable params: 0
_________________________________________________________________**

这是因为现在我们知道了:

  • 平均
  • 标准偏差
  • 协方差

这在这个模型中计算量很大。

结论

在这篇文章中我们已经看到:

  • 普通神经网络的问题
  • 贝叶斯神经网络的核心思想
  • 变分贝叶斯方法
  • 迷你批次和重新加权 KL 散度
  • 致密变异层

你可以在这里找到完整的笔记本和代码。

后续步骤

在接下来的文章中,我们将只专注于编写包括卷积层的模型,以及捕捉认知和任意的不确定性。

参考

[1]:查尔斯·布伦德尔,朱利安·科尔内比斯,科雷·卡武克库奥卢,金奎大·威斯特拉,神经网络中的权重不确定性,2015

深度学习中的不确定性:贝叶斯 CNN 实验

原文:https://towardsdatascience.com/uncertainty-in-deep-learning-experiments-with-bayesian-cnn-1ca37ddb6954

KL-Weight 如何影响模型?为胸部 x 光图像编写贝叶斯 CNN。

由埃文·丹尼斯在 Unsplash 上拍摄

这是深度学习中不确定性系列的第五部

  • 第 1 部分—简介
  • 第 2 部分—随机不确定性和最大似然估计
  • 第 3 部分——认知不确定性和反向投影贝叶斯
  • 第 4 部分——实现完全概率贝叶斯 CNN
  • 第五部分——贝叶斯 CNN 实验
  • 第六部分——贝叶斯推理和变形金刚

这篇文章是前几篇文章的后续。因此,在这篇文章中,并不是每一个术语或层都像在前面的部分中解释的那样进行解释。

介绍

在第三部分中,我们使用了DenseVariational层,以便通过反向投影算法实现贝叶斯。这一层接受kl_weight作为参数,我们将看到它是如何影响模型的。

在本文的下一节,我们将看到如何实现一个具有剩余连接的贝叶斯 CNN 模型。实现的模型将在真实数据集上训练。

该职位的组织结构如下:

  • KL-重量摘要
  • 多元线性作为变分后验
  • 不同 KL-权重和方差的后验
  • 加载真实数据集
  • 作为正则化者的先验
  • 提供自定义的先验&后验
  • DenseNet121 作为特征提取器
  • 贝叶斯 ResNet
  • 检查结果和不确定度
  • 结论
  • 接下来的步骤

KL-重量概述

回想一下,我们不能计算真实的后验概率,因此我们需要近似它。也就是说,我们将应用变分贝叶斯技术。

图片作者。(摘自第三部分)

凭直觉,我们希望这两个分布之间的 KL-散度尽可能低,这表明我们的变分后验接近于原始分布。

由于数学运算在第 3 部分有所解释,我将直接得出结论,最终损失如下:

最终损失,贝叶斯反投影。(第三部分)

1/M是 KL-Weight,其中M是训练样本的总数。我们将从一个玩具数据集开始。

对于 KL 重量部分,进口如下:

MultivariateNormalTriL()

对于我们的任务,我将展示tfpl.MultivariateNormalTriL()。这一层将允许我们把我们的变分后验概率指定为多元分布。

我们的先验分布只是一个标准的正态分布。然而,后验概率是一个具有完全协方差的多元正态分布。和tfpl.VariableLayer一起,这将是一个可训练的分布。

params_size将为MultivariateNormalTriL()确定正确的参数数量。

让我们看看它如何影响模型参数:

假设,我们在一个数据集中有 3 个要素和 2 个类。我们将尝试使用神经元来学习数据集。这基本上是逻辑回归。问题是,模特有 14 个可训练参数。让我们用一个图表来解释这个问题:

图片作者。

多元正态分布均值向量和协方差矩阵定义。

协方差矩阵测量两个随机变量的依赖程度。换句话说,它显示了它们如何一起变化。在复杂模型中,由于协方差矩阵的原因,在每一层中为多元正态分布设置变分后验概率是不可行的。

KL-重量实验

显然,KL-Weight 参数将影响我们的变分后验概率。因此,我们需要创建一个自定义回调来监控我们的变化后验概率。

最终损失,贝叶斯反投影。(第三部分)

在每个时期结束时,我们检索我们的变分后验标准差,并将其附加到一个列表中。

在 Sklearn 的帮助下,数据创建过程非常简单:

图片作者。

由于输出将是一个分布,我们可以直接使用负对数似然作为损失函数。回想一下,标签是热编码的,所以最后使用的是tfpl.OneHotCategorical层。

后验函数和先验函数是相同的:

  • 先前:正常
  • 变分后验:带协方差的多元正态分布

在一个循环中运行不同kl_weights的实验:

  • 1/x.shape[0]在这里是正确的,因为我们除以数据集中的元素总数。

损失值和准确度值看起来很接近。那用比如**kl_weight** = 0 可以吗?大多数情况下,你可能不想这么做。这是因为我们在这里做贝叶斯深度学习,避免得到固定的预测。

这听起来可能有点神秘,所以首先让我们检查不同kl_weight项的变分后验方差。

图片作者。

对于值0.0, 0.0001, 0.0005,方差较低。请记住,为了得到预测,我们从变分后验样本。因此如果方差接近零或某个低值,采样值将趋于更接近。换句话说,如果方差变为零,我们可以收敛到一个 MLE 解!

这有一个严重的缺点,在贝叶斯深度学习中,我们的目标是测量模型对给定输入的信心。如果方差低于预期值,这可能会导致高估模型的可信度。

真实数据集

在本帖中,我们将使用 Paul Mooney 在 Kaggle 上发布的胸部 x 光图像(肺炎)【1】数据集。数据集包括两类,正常和肺炎。

label_mode被选为分类层,因此我们可以使用OneHotCategorical层作为模型的最后一层。数据加载过程很简单,所以我跳过这一部分。

数据集中的图像。

在进入模型创建过程之前,让我们也检查一下类分布

图片作者。

所有绘制图像和类分布的相关代码都可以在笔记本中找到,我会在文章的最后分享它的链接。

作为先验,我会用MultivariateNormalDiag。这种分布允许建立具有对角协方差矩阵多元正态分布。为Convolution2DReparameterization设置自定义先验很容易,正如我们在前一部分看到的。

因为先验没有可训练的参数,所以像这样构建并返回一个独立的分布对象就足够了。

如果那个MultivariateNormalDiag是 2D,它可以被想象成如下:

图片作者。(位置= 1.2,刻度= 3.0)

所以我们可以说它是一个球形分布。从贝叶斯的角度来看,先验充当了正则化子。所以如果你使用,比方说拉普拉斯先验,你实际上将使用 L1 正则化。我不会在此详述,但如果您想深入了解,可以查看一下:

https://stats.stackexchange.com/a/163450

在我们的例子中,有许多分布可以用作先验分布:

图片作者。

幸运的是,TFP 库使得使用这些发行版变得很容易。由于这些先验表示我们对模型参数的先验信念,它可以影响模型的收敛性。

例如,马蹄形分布不会将大重量缩小太多。并且小的值将被推向零。

Reparameterization Layers的自定义后验

因为变化后验概率是可训练的,所以在Reparameterization层中创建它有点笨拙。

使用default_loc_scale_fn,我们为可训练参数生成一个闭包。闭包接受与posterior函数相同的参数,并返回loc, scale,它可以被传递到一个分布中。在那之后,发行版将是可训练的。

在引擎盖下,tf.nn.softplus应用于scale参数,所以我们不需要担心它,例如是否为负。

然而,当处理贝叶斯 DL 时,如果变分后验恰好是正态分布,则容易收敛。

在这篇文章中,我将使用正态分布,但如果你愿意,这就是你改变后验概率的方法。

逼近 KL 散度

因为我们改变了先验,TFP 将不能计算 KL 散度的解析值。我们需要近似它:

我想提到的一点是:在实践中,用其他值进行缩放可能会更好。根据我的经验,这是可以的,但你也可以尝试其他合理的值。

贝叶斯网

这些层需要很多参数,封装在这样的函数里更方便。对于后验分布,我们使用默认值,并通过is_singular = False来确保它们是而不是点估计。

tfpl.default_mean_field_normal_fn是参数可训练的正态分布。它实际上是对后路的默认。

剩余部分很简单,看起来比平常长,因为reparameterization层接受了太多的参数。

现在,我们将遵循不同的方法。我们不是从头开始训练一个 贝叶斯 CNN,而是采用一个预训练的模型,仅用于特征提取,这意味着它们的层将被冻结,在训练期间不会更新。

DenseNet121将是该任务中的特征提取器。此外,不是采取整个架构,它将被分割使用功能 API。

  • 我们获取conv5_block9_concat的输出,并将其提供给我们的贝叶斯残差层。
  • 贝叶斯卷积层将分别有 64 个和 128 个滤波器,随后是swish激活。

现在,我们准备使用数据集来训练模型。

  • 损失是负对数似然,因为产出是一个分布
  • 使用回调来提高训练,如果val_accuracy没有连续提高 7 个纪元,早期停止将给出最佳权重。
  • 如果val_accuracy没有任何改善,学习率将每 3 个周期降低一次。

最终,模型达到了 85%的测试准确率。然而,我们更感兴趣的是预测的不确定性,而不是简单的准确性。

结果

让我们检查一下测试集中的图像。

图片作者。

对于该预测,模型将很高的概率分配给正确的类别 1。可以说模型对这个预测是有把握的,而且不确定性很低。

条形图显示了预测的不确定性。因此,如果柱更高,我们得出结论,不确定性更高。这是因为,我们显示了 95%的置信区间,条形的长度显示了百分位数 2.5 和 97.5 之间的差异。

在最好的情况下,模型将只分配 1 作为预测值,百分位数 2.5 和 97.5 之间的差值为零。在这种情况下,条形会更短。

图片作者。

对于这个具体的例子,真实类是正常的,但是模型似乎很难预测它。例如,在向前传递中,它为正常类分配了非常高的概率。但是在另一次向前传递中,类正常的概率很低。我们得出结论,模型对这个预测是不确定的,并且知道它不知道的东西!

结论

在本帖中,我们:

  • 已经看到当选择MultivariateNormalTriL()作为变分后验概率时,它如何影响模型的参数,
  • 已经看到了 KL-Weight 的影响,
  • 已经学会如何为Reparameterization层提供定制的先验和后验函数,
  • 使用了预先训练的模型作为特征提取器并将特征向量输入到贝叶斯 ResNet 模型中,
  • 检查了测试集中两幅图像的不确定性。

您可以从这里获得笔记本。

后续步骤

您可以尝试数据扩充技术,看看它是否有助于减少预测中的不确定性。

在下一篇文章中,我们将探讨关于变形金刚和贝叶斯推理。

参考

[1]:科曼尼,丹尼尔;张、康;Goldbaum,Michael,“用于分类的标记光学相干断层扫描(OCT)和胸部 X 射线图像”,Mendeley Data,V2。2018,doi: 10.17632/rscbjbr9sj.2,【https://data.mendeley.com/datasets/rscbjbr9sj/2】T4(CC BY 4.0)

不受约束的聊天机器人纵容自残

原文:https://towardsdatascience.com/unconstrained-chatbots-condone-self-harm-e962509be2fa

意见

像 DialoGPT 这样的对话式人工智能对于现实世界来说仍然太危险了

警告。这个帖子包含自残和自杀的参考。它包括人类和 DialoGPT 之间的对话,唯一的目的是暴露不受控制的 AI 的危险。如果你或者你爱的人正在处理或者曾经处理过自杀的念头,我建议跳过这篇文章。

在加速的精神健康危机的背景下,自然语言处理(NLP)正在成为精神健康支持平台上的有用工具,特别是以对话式人工智能的形式。像 Wysa 、 Woebot 和 Youper 这样的聊天机器人利用认知行为技术(CBT)让人们在无法获得专业精神健康服务时感到有人在倾听他们。虽然非常有用,但这些聊天机器人有时会感觉像是照本宣科。那么,有没有一种安全的方法来超越手工制作的治疗聊天机器人模板呢?

在整篇文章中,我将使用 DialoGPT ,它是最先进的开源对话式人工智能模型之一。目标是对其心理安全性进行压力测试,并衡量我们距离能够用端到端神经网络取代脚本聊天机器人还有多远。剧透预警:很远。

照片由埃里克·麦克莱恩在 Unsplash 拍摄

App Store 上的大多数评论报告了与治疗聊天机器人的高度积极的互动,其中一些关于生命被拯救或大幅改善的令人喜爱的证明:

如果你觉得没有人可以真正信任和你交谈,或者如果你觉得你会因为你的感觉而被评判,Woebot 是完美的。这是一种你可以随身携带的疗法。(在 App Store 上对 Woebot 的正面评价)。

然而,除了轻度焦虑和抑郁的教科书案例之外,偶尔会观察到他们照本宣科的本性和缺乏适应性:

[……]这款应用的方法并不适合所有人。它是完全照本宣科的,如果你的需求不符合脚本,它可能会令人沮丧甚至士气低落。我认为,对于那些正在经历巨大压力的健康人来说,这是一个很好的应用程序,因为它可以很好地提醒你关于思维陷阱的问题,并帮助你回到更积极的思考。(在 App Store 上对 Woebot 的差评)

由于治疗聊天机器人不是开源的,我们无法确定它们是如何实现的。我有根据的猜测是,所有可能的答案都在一张由训练有素的心理学家手工制作的电子表格上。显式条件逻辑很可能直接基于用户输入或某些经过训练的焦虑和抑郁分类器的判断来选择适当的答案。作为显式逻辑的替代,治疗聊天机器人可以作为基本的面向任务的对话系统来实现,这些系统通常是独立构建的各种模块的管道(神经编码器、有限状态机等)。)并被调整以实现非常具体的目标(例如,从用户处获得他们的焦虑水平被降低的明确确认)。

通常,大型端到端神经网络可以取代复杂的手动设计的规则。特别是,自然语言生成(NLG)领域由仅基于输入文本提示生成流畅文本的模型主导——GPT 模型家族将这种技术确立为现状。然而,魔术黑盒的简单性是以失去可控性为代价的。知识的来源从人类策划的可检查和可修正(尽管难以维护)的 if/else 块转移到同时反映人类光明和黑暗的互联网规模的数据集。从显性到隐性。从结构化到不羁。对于玩具用例来说,这可能没什么问题(例如,让 GPT 3 号产生一个关于独角兽的有趣故事),但这成为医疗保健领域的一个主要安全问题。

安全数据因此成为圣杯。对于通用文本,有一些大型但相对安全的来源,如维基百科。然而,会话数据集尤其混乱;dialog 的主要来源是 Reddit,它在许多方面都存在问题。第一,用户匿名导致毒性更高;虽然研究人员确实使用启发式方法来改善这个问题(过滤具有高因果报应的用户,排除有许多否决的帖子,应用单词阻止列表),但伦理问题仍然存在。其次,树状的线程结构和通信的异步性使得 Reddit 交互在结构上不同于 live dialog。

最先进的开源对话式人工智能模型之一 DialoGPT 由微软在 Reddit 的 1.47 亿条评论链上进行训练。虽然这样的模型对研究团体来说是有用的人工制品,但是生产它们是非常不安全的,正如我们将在下一节中看到的。请注意,这种伦理问题已经促使一些机构不发布他们的模型(例如,在撰写本文时,谷歌最新的对话技术 LaMDA 仍被锁在闭门造车中)。

我开始了一项个人探索,以衡量 DialoGPT 这样的对话模式会有多危险,尤其是在为已经有精神健康问题倾向的人服务时。可惜,答案很简单:极度危险。

使用 DialoGPT 开箱即用

首先,我以发布的形式试用了 DialoGPT 模型,使用了 HuggingFace 主持的交互演示。虽然这些回答是有问题的,但实际上如果提供了我所要求的细节,情况可能会更糟:

与 DialoGPT-large 模型的交互,由 HuggingFace 主持。

除了缺乏同理心,一个不和谐的方面是不同问题的答案重复。解决模型的重复或平庸答案的一种常见方法是稍微改变它执行解码的方式,即它在潜在答案的广阔空间中导航的方式,以便找到一个统计上可能的答案。

默认情况下,DialoGPT 执行贪婪解码:输出令牌一次产生一个;在每个步骤 n,中,模型选择最有可能跟随到目前为止生成的 n-1 个记号的单个记号:

贪婪解码:在步骤 n,模型根据在训练期间学习的条件概率 P,选择最有可能跟随迄今生成的记号的词汇记号。

贪婪解码产生重复枯燥答案的核心原因是决定论。采样解码解决了这个问题:在每一步 n 中,模型根据训练期间学习到的条件分布 P 从词汇表中采样一个令牌。因此,没有选择最有可能的令牌,而是随机选择一个最有可能令牌:

采样解码策略:在步骤 n,模型从训练期间学习的条件分布 P 中采样,选择可能跟随迄今生成的标记的随机词汇标记。

有两种通常应用于采样解码的常见调整。首先,从最有可能的前 K 个单词中抽取下一个单词(而不是整个词汇表),以排除完全陌生的单词。第二,将温度 T 应用于分布 P 以便“软化”它(即,不太可能的令牌变得稍微更可能)。DialoGPT 论文提出了一种稍微复杂一些的解码算法,其核心仍然依赖于采样。

切换到 K=50T=0.75 的采样解码后,与 DialoGPT 的交互变得不那么重复,但更有害:

使用采样解码时与 DialoGPT 模型有问题的交互示例。

对治疗师提供的数据进行微调

如前所述,神经网络的质量受到训练数据质量的限制。目前,Reddit 上的预培训是一种必要的邪恶:尽管它有毒性的包袱,但它是互联网上高容量对话数据的唯一来源。然而,模型可以在更小数量的更干净的数据上进行微调:希望能够纠正一些从更嘈杂的预训练步骤中学到的负面行为。

我最近偶然发现了这篇优秀的文章,文章建议根据咨询聊天的数据对对话模式进行微调,咨询聊天是一个在线平台,训练有素的咨询师在这里为有需要的人提供建议。回答是高质量的,因为它们是由专业人员制作的。然而,这种交互通常是单轮的,比常规的同步对话更长。为了解决这个问题,我在微调过程中截断了两个句子的回答。

令人失望的是,微调模型仍然纵容自残。开始时有一点同情,但很快就开始走下坡路了:

与 DialoGPT 模型有问题的交互的示例,dialog pt 模型根据来自专业顾问生成的顾问聊天数据进行了微调。

我想强调的是,上述有问题的对话并不是精心挑选的。由于采样解码的不确定性,不同运行的模型响应不同,但它们始终是有害的,并最终鼓励自杀。

从好的方面来看,像 Wysa 这样的应用程序以专业的方式处理这种情况:确认你的意图,然后阻止你自残,并建议有用的资源。

与 Wysa 的互动截图。当用户表达自杀意念时,Wysa 不鼓励自残,并建议有用的资源。

结论

毫无疑问,安全应该是精神健康领域任何产品的最高优先级。现有的治疗聊天机器人遵守这一原则,但代价是人为的体验。虽然这样的应用程序在标准教科书的情况下是有帮助的,但我相信它们死板的脚本会妨碍它们真正理解和同情用户。另一方面,不受控制的自然语言生成更具适应性和吸引力,但它的反面是致命的。这两个极端之间有一个巨大的鸿沟,观察未来几年的研究是否以及如何安全地在它们之间架起桥梁将是有趣的。

用《R》中的拉旺揭示人格的潜在结构

原文:https://towardsdatascience.com/uncover-the-latent-structure-of-personality-with-lavaan-in-r-aaf66fc2cc81

使用结构方程模型(SEM)揭示数据中隐藏的关系

图片来源: Kévin 和 Laurianne Langlais on Unsplash

对心理学家来说,最具挑战性的任务之一是预测人们的行为。因为我们是一个非常复杂的物种,这需要了解是什么让一个人滴答作响——一些实际上仍然隐藏的东西。从统计学上来说,我们可以通过结构方程模型(SEM)来推断潜在的关系,这是一套统计方法,如果你被迫间接地而不是直接地对多个因果关系建模,这种方法尤其有用。例如,虽然直接测量身高和体重来表征生理形状(例如大象的大小)自然是可能的,但我们必须依靠指标(例如任务)来评估一个人的潜在智力。正如人们通常所说的那样,智力是分层结构的,可以分为多个维度:一般智力(g 因子)是该结构中最抽象的部分,因为它预测了更多的具体因素,如一个人的个人推理能力、处理速度、记忆广度等。反过来,这些子因素使用特定的任务来近似。因此,这些任务表现是其潜在结构的指标,并包括一个误差项(例如,个人的日常表现)。例如,这就是韦克斯勒成人智力量表第四版模型(WAIS-V)的建模方式(本森,胡拉克&克朗兹勒,2010 年) :

免责声明:所有图片均由作者创作,除非另有说明。

一旦我们的工作远远超出探索性分析,我们共享的知识就必须用于确认任何新开发的测试的有效性:例如,智力的共同结构在不同的智商测试中是否适用?这应该是因为我们用不同的测试来测量相同的概念,所以人们在智商测试 A 中的表现应该与他们在智商测试 b 中的表现相关联。我们事先形成的层次结构必须在数据中有所体现,或者可能发生了一些可疑的事情——要么是我们的测量设计——或者是理论模型因为与数据不一致而需要修改。如果你像我一样对数据科学充满热情,你可能已经听说过探索性因素分析(EFA ),它用于调查我们样本中的隐藏因素,在这种情况下,我们让数据自己说话。相比之下,结构方程模型,尤其是验证性因子分析(CFA)的特点是一种更具演绎性的方法:为了证实一个理论结构,你需要预先指定潜在因子的数量和加载模式,通常是在对试点样本进行 EFA 之后。通常,我们甚至可以运行关于所研究现象的潜在结构的多个模型,以找出哪些模型得到了数据的最有力支持。从技术上讲,这是通过协方差矩阵来证明变量之间的接近程度。您可以将协方差视为一种非标准化的相关性。它表示变量在其测量范围内彼此线性相关的程度。如果在数据集中使用不同的尺度(例如,年龄和智力),为了使它们更容易解释,它们可以很容易地转换成相关性,这是一种标准化,将在以后为我们服务。但首先,假设的结构(例如,智能模型)将用于模拟我们理论上可以预期的协方差矩阵——这是我们估计的人口协方差矩阵。当将模型与从实际样本数据(例如,测量的测试分数)中提取的协方差矩阵进行比较时,我们可以了解模型的拟合情况。因此,我们在某个模型下可以预期的相互关系与在数据中观察到的相互关系进行比较——它们有多一致?它们越接近,拟合越好(Ullman,2006)。

人格五大要素

大多数人可能会松一口气,因为到目前为止,你不仅由你的智力来定义,还由描述你作为一个人的其他属性来定义:你的个性由你的行为、感觉和思考方式来表现。为了从数字上比较人们的个性,我们需要用量表上的项目来量化它。通过这种方式,我们可以尝试使用这种评估来为人们匹配合适的工作或指导个人发展。但是像“人格”这样的东西首先是如何构建的呢?鉴于一个人的典型模式中有如此多的细微差别和方面,我们如何将其分解为一个简短但精确的描述呢?

Art Woman GIF by Sterossetti via GIPHY

幸运的是,我们不是第一个提出这些问题的人:在过去 20 年的心理学研究中,五个不同的维度已经成为人格的普遍模式——通常被称为人格的五大(John,Naumann & Soto,2008):

  1. 开放性——也被称为智力或想象力;描述一个人对新的经历、体验和印象感兴趣的程度(以及参与的程度)。开放的人善于探索,有很多幻想,经常寻找智力灵感。
  2. 责任心——描述一个人在追求目标的过程中控制自己行为的程度。有责任心的人往往组织良好,注重细节,可靠。
  3. 情绪稳定 —也叫神经质;描述了一个人对自己情绪的脆弱程度。情绪稳定的人是稳定的,很少紧张或焦虑,能够在紧张的情况下保持姿势。
  4. 宜人性 —描述一个人对和谐关系的关注程度。令人愉快的人是友好的,有同情心的,合作的,能够减少冲突。
  5. 外向性——描述一个人的社交、友好、冒险和活跃程度。外向的人容易与人交往,喜欢刺激,而且相当健谈、精力充沛和乐观。

这是基于所谓的词汇法,即在每一种语言中,对人的描述都会自然地表现出来,例如害羞、多产、快乐、善于操纵等等。通过要求参与者使用这些形容词来评价一个已知的人,研究人员收集了数千个数据点,使用探索性因素分析将个性空间分成不同的维度。

即使这五个因素被普遍接受,关于普遍人格特质的确切性质和结构仍然存在争议。例如,人格维度是否有更高阶的因子,就像智力有 g 因子一样?真的只有五种性格因素或者更多吗(就像 HEXACO 模型的支持者争论的那样)?如果存在某些真实的人格维度,那么在多大程度上是由以特定方式回答问卷的倾向造成的?与此相关的是,调查问卷真的首先揭示了人格的真相吗?

正如你可能猜到的,关于人格的潜在结构,有很多问题仍然没有答案。最后一个问题给我的印象最深:我们真的可以依靠自我报告来评估人类性格的各个方面吗?设计用来测量在我们的社会中被积极评价的特征(例如,外向性)的问卷被批评为自然地唤起社会期望的答案。因此,由于这些设计上的缺陷,人们倾向于根据自己的实际行为给自己比预期更好的评价。即使有最好的意图,人们也会让自己在纸上看起来比实际上更好。在所谓的高风险情况下(例如,工作面试、关于约会应用的问题),这甚至更极端,在这种情况下会有很多输赢。好吧,如果 a)所有人都在相同程度上“假装”他们的反应,并且 b)这不会影响对他们在不同条件下行为的预测,这就不是什么大问题了。例如,如果一个脸皮薄的求职者曾经假装情绪特别好,但却暴露在一个艰苦的工作环境中,这对求职者和雇主来说都是一个双输的局面。但不幸的是,这正是实践中发生的事情(Ziegler & Bühner,2009),聪明人特别擅长这一点(Geiger,Olderbak & Wilhelm,2018)。但是,即使人们没有故意撒谎,测试的构造方式也很难准确客观地报告他们在现实生活中的行为。除了社会期望之外,人格问卷上的答案还会受到一系列其他偏见的干扰,比如短暂的情绪状态、陈述的模糊性以及关于人格的隐含理论(例如:“聪明的人通常都很内向,所以如果我是一个思维敏捷的人,我也一定是一个更冷静的人。”“静水流深。”)。在这个案例研究中,我将向您展示如何测试涉及这些主题的假设,并演示如何用 r。

数据集

为了保持之前介绍的五因素模型,我们将使用在 kaggle 上找到的五大数据集。它包含了由开放心理测验 在线收集的超过一百万(1015342)个答案。这个开源平台为发展目的提供免费的个性评估。据该网站称,用户事先同意为科学目的匿名收集和存储数据。

具体来说,我们将关注的测试被称为大五因素标记,它是由 Goldberg (1992)开发的。它包括 50 个问题(每个人格维度 10 个),参与者用 5 分制的李克特量表给自己打分,1 分代表完全不同意,3 分代表中性回答,5 分代表完全同意。这些是问题:

EXT =外向

  • 我是派对的灵魂。
  • EXT2 我话不多。
  • EXT3 我觉得和人在一起很舒服。
  • EXT4 我留在后台。
  • 我开始对话。
  • EXT6 我没什么好说的。
  • 我在聚会上和许多不同的人交谈。
  • 我不喜欢引人注意。
  • 我不介意成为关注的焦点。
  • 我在陌生人面前很安静。

EST =情绪稳定

  • EST1 我很容易紧张。
  • EST2 我大部分时间都很放松。
  • 我担心事情。
  • EST4 我很少感到忧郁。
  • 我很容易被打扰。
  • EST6 我很容易心烦。
  • EST7 我的心情变化很大。
  • EST8 我经常情绪波动。
  • 我很容易被激怒。
  • 我经常感到忧郁。

AGR =宜人性

  • 我很少关心别人。
  • AGR2 我对人感兴趣。
  • AGR3 我侮辱人。
  • AGR4 我同情别人的感受。
  • AGR5 我对别人的问题不感兴趣。
  • AGR6 我有一颗柔软的心。
  • 我对其他人不感兴趣。
  • AGR8 我为别人抽出时间。
  • AGR9 我感受到别人的情绪。
  • AGR10 我让人安心。

CSN =责任心

  • 我随时准备着。
  • 我把我的东西到处乱放。
  • CSN3 我注重细节。
  • 我把事情搞得一团糟。
  • 我会马上做完家务。
  • 我经常忘记把东西放回原处。
  • CSN7 我喜欢秩序。
  • 我逃避我的职责。
  • 我遵循时间表。
  • 我对工作要求严格。

OPN =对新体验持开放态度

  • 我有丰富的词汇。
  • 我很难理解抽象的概念。
  • 我有丰富的想象力。
  • 我对抽象的想法不感兴趣。
  • 我有极好的主意。
  • 我没有好的想象力。
  • 我理解事物很快。
  • OPN8 我用难词。
  • 我花时间反思事情。
  • 我满脑子都是想法。

作为第一步,让我们将数据读入我们的工作环境,并为随后的分析做准备:我们将删除不完整的案例,只选择人格变量。因为我们有如此庞大的数据集,以至于大多数机器无法真正通过计算来处理,所以我们必须将样本大小减少到 10,000 个观察值。这是一个任意的数字,我选择它在我自己的计算机上工作。此外,一些反应是消极的,例如。“我很容易紧张”并不代表一个情绪稳定的人,恰恰相反因此,我们需要以这样一种方式反向记录它们,即根据测试的评分键,强一致性理论上应该降低一个人的分数。所以,容易紧张的人更容易情绪不稳定。我们可以使用% > %运算符将所有这些预处理步骤包装在一个方便的 dplyr 结构中,如下所示:

现在的数据实际上是什么样的?让我们使用 skirr-package 来获得一些描述性的统计数据。

# get some descriptives
skimr::skim(big5)

右边的小直方图揭示了很多关于人格陈述的所谓第项难度的信息——每个陈述引发强烈一致的难易程度如何?例如,我们可以看到项目 EXT9 的分布非常平坦— “我不介意成为关注的焦点。”这意味着(稍微)同意的人与(稍微)不同意的人数量相当,从而在整个评定等级中有较大的分布。与此相关的是,回答者似乎不清楚这个问题是否有正确或错误的答案,从而使他们更难自发地做出反应。我们无法真正地对造成反应巨大差异的机制做出任何结论,因为我们无法通过实验来检验我们的假设,但我们可以假设,这可能归因于随机猜测或个体之间的真实差异。item est 3(我担心的事情))另一方面是强烈向右倾斜的——因为该项目是反向编码的,较低范围内的高密度响应意味着同意的人比不同意的人多,从而使担心事情成为人们通常涉及的属性。基于频繁的约定,这种自我描述似乎比较正常而不是例外。人们似乎总体上非常关心!

模型假设

在我们开始实际分析之前,让我们检查一下我们是否满足 SEM 分析所需的所有假设。首先,结构方程分析只能处理大量数据。以下是一般的经验法则:我们需要 200 个以上的观测值(但实际上不少于 400 个,尤其是当观测变量不是正态分布时)或 5-20 倍的待估计参数,以较大者为准(例如,Kline,2005,第 111、178 页)。为了根据模型的复杂性选择合适的样本量,我们也可以简单地计算模型估计的参数数量:为了找出答案,我们采用方差和协方差,其中 p 是测量变量的数量。因为我们总共有 510 = 50 个测量变量,所以有= 1275 个参数(50 个方差和 1225 个协方差)要估计。为了获得模型的可靠估计,必须有比要估计的参数更多的数据点,这在我们的例子中是真实的,因为 10.000 超过 1275。从统计学上讲,这是一个关键点,归结为模型识别,定义为模型中每个参数的唯一数值解。如果你的数据点少于模型中的参数,lavaan 会给你一个错误,因为你的模型是欠识别,参数无法估计。如果数据点的数量与模型中参数的数量相同,则您的模型是刚刚确定的,因此参数完美地再现了样本协方差矩阵,并且任何检验统计量为零。为什么?只有当您能够将样本的协方差矩阵与估计的总体协方差矩阵进行比较时,您才能测试您关于模型适当性的假设-但是,如果由于没有足够的数据进行模拟,它们首先完全相同,则测试统计将变为零,并且无法解释。这就是为什么你需要一个超过确定的*模型,在这种情况下,你有比参数更多的数据点需要估计。除了模型识别,多元正态性是对 SEM 的另一个要求。我们可以通过运行 psych 包中的 mardia()来测试它:

# Mardia test of multivariate normality
psych::mardia(big5, plot = FALSE)

输出中有两个估计:多元偏斜的 mardia 估计的 b1p 和多元峰度的 mardia 估计的 b2p,其中的可以间接指示变量是否正态分布。对于多元正态性,偏度和峰度统计的 p 值都应大于 0.05。我们的数据不是这样的。我们可以看到两个 p 值(表示为概率)都为零,因此我们需要拒绝多元正态性。plot 参数通常会给出一个很好的图,允许您立即检查任何异常值,但是因为我们有如此多的数据点,R 无论如何都不能显示这个图。然而,多元正态性尤其与最大似然估计密切相关,你可以在此处阅读,因此,为了克服这个问题,我们可以使用更稳健的 ML 版本,它受违反正态性的影响较小。

模型设定

终于到了介绍拉旺的时候了!它是由 Yves Rosseel、Terrence D. Jorgensen 和 Nicholas Rockwood 在众多贡献者中开发的一个神话般的用于潜在变量分析的 R-package ,现在我们将使用它。

让我们测试一下,假设的经典结构是否适用于这个样本。我们假设允许五个不同的维度相互关联,并且具有仅加载到其各自维度上的指标,即之前已经介绍过的每个特征 10 个项目。这是模型的图形表示:

为了让你更容易地阅读这样的路径图,这里有一个关于 SEM 惯例的指南:我们实际上可以测量的一切,所以观察到的变量,指标,或显式变量用正方形或矩形表示,所以底部的小盒子。出于数学原因和减少测量误差,所有因素都必须有两个或更多指标。这些因素被称为潜在变量,结构,或未观察到的变量。它们通常用圆形或椭圆形表示。线表示变量之间的关系——所以如果没有这样的线连接变量,这意味着没有直接关系被假设。线条有一个或两个箭头:有一个箭头的线条表示两个变量之间的假定直接关系。两端带箭头的线表示两个变量之间的协方差,没有隐含的影响方向。外向性、宜人性、情绪稳定性、责任心和开放性是我们例子中的潜在变量,这些变量预计会相互协变。每个特定项目(例如,EXT1-EXT10)通过它们各自的因子(例如,外向性)直接预测。我们无法直接观察一个人在回答调查时内心在想什么,以及这与他们的个性有什么关系,但心理测量文献中有一些理论上的考虑,可以帮助我们理解其中的逻辑:根据项目反应理论(IRT)模型,我们可以从一个人对测试项目的反应来估计他的特质。所以从理论上来说,一个非常外向的人可能会相应地给项目 EXTi 很高的评价,因为这个人在潜在特质上的地位预测了他们的反应。这是因为强烈认同,比如说“我在人们身边感觉很舒服”的概率应该随着外向性水平的增加而线性增加(Brown,2017)。因此,我们能做的最好的事情就是把这些反应作为人格特征的指标,即使我们不能直接测量它们。很明显,我们不能在这里做一个完美的工作:请注意,在路径图中有一个指向每个指标的双箭头:这表示误差项,或剩余方差,它描述了未被相应因子考虑的方差的数量。从更普遍的角度来看,将测量变量与因素联系起来的模型部分被称为测量模型,即每个项目被分配到一个人格维度的方式。如果我们想简单地估计这个测量模型,并测试它与数据的兼容性,我们经常运行验证性因素分析,这是某种类型的结构方程模型。结构之间的理论关系,即人格维度之间的协方差,称为结构模型。为了告诉 lavaan 我们在模型中期望的结构,我们需要在运行验证性因子分析之前指定特定的语法。

因为我们的模型中有相当多的变量,我发现键入所有变量非常繁琐,所以我定义了一个智能函数来帮助我们完成这项工作:它采用某个缩写(例如 EXT 代表外向性)来从数据集中选择所有匹配的变量名,将它们折叠成一个字符串,并在中间添加空格和“+”号。这样,我们已经准备好了定义潜在变量的显式变量的部分,并可以简单地将其粘贴到语法中。

一般来说,lavaan 是这样理解模型的:

幸运的是,lavaan 已经假设所有的潜在变量都是相关的,所以除了度量模型之外,我们不需要在这里指定这一点。

现在我们需要做的就是在我们的模型语法上运行cfa()函数。我们将 st.lv 设置为 TRUE 以标准化我们的潜在变量——这样所有的潜在方差都固定为一,并且更容易解释,因为因子负荷将更容易解释。估计的目标是最小化非结构化协方差矩阵(实际数据)和结构化协方差矩阵(预测)之间的差异。我们使用“MLM”作为我们的估计量,这是一个具有稳健标准误差和萨托拉-本特勒标度测试统计的最大似然估计。想了解更多关于 lavaan 可用的估算器的信息,你可以点击链接这里。

实际上,最大似然估计不推荐用于有序数据(例如,从 1 到 5 的李克特量表),因为它们违反了正态假设,正如马迪亚检验对我们的数据所建议的那样。因此,像对角加权最小二乘法(DWLS)这样没有分布假设的估计量会更合适。它与 ADF 估计器相关,但计算强度较低(Newsom,2018)。然而,有一些反对使用它的论点:首先,我们将无法将我们的结果与后来的文献进行比较,因为大多数关于五大潜在结构的研究传统上使用最大似然估计(例如,参见 Ashton,2009)。即使不同的大五人格测试和研究设计之间的比较也肯定不是完美的,测试统计数据之间可怕的巨大不匹配可能表明有问题。通过使用 DWLS 估计,我们就不会有这样的参考。虽然这不是最有力的论据,但一些作者认为,如果有大量的观测数据,李克特尺度的数据可以被解释为连续的。10.000 个病例确实可以被认为是一个大数据集,允许最大似然估计的稳健版本。第三,石和 Maydeu-Olivares (2019)已经表明,估计量本身对拟合度有巨大的影响,即使相同(!)使用的数据可能会让您对模型的性能产生有偏见的印象。作者建议对比较适合指数(CFI)或近似均方根误差(RMSEA)等常用指标使用不同或限制较少的临界值,或者直接使用标准化均方根残差(SRMR),因为它对用于估计的方法最稳健。因此,我们使用稳健的最大似然估计,同时牢记我们关于拟合指数的知识。

模型评估

通过在我们的模型对象中运行summary()函数,我们得到了一个详细的模型输出。我们设置 standardized = TRUE 以获得对潜在变量更有意义的估计,并设置 rsquare = TRUE 以了解我们的模型实际上可以解释参与者反应的程度。我们设置 fit.measures = TRUE 来获得除卡方估计之外的更多 fit 统计数据。

Lavaan 正常结束,没有抛出任何错误信息。输出简洁地总结了我们有 10000 个观察值和 110 个模型参数,并使用了最大似然估计。现在我们可以比较“我们的”模型和基线模型之间的测试统计数据。你可能已经猜到了,这是一种假设检验。基线是一个零模型,这意味着所有的协方差都固定为零,因此在这种情况下,项目将相互独立,并随机协变。为什么我们会对这么差的模型感兴趣?因为这样我们就可以把我们的模型和一个协方差矩阵进行比较,当机遇单独运行时,可以预期这个协方差矩阵。它也是与您的拟合模型进行比较的基线,以便计算模型拟合的相对指数(例如,CFI 或 TLI)。为此,卡方统计用作模型拟合的指标,并且是表示结构化/模型特定和非结构化/观测协方差矩阵之间的差异的最小可能值的度量。输出中的 p 值表示预测模型是否与观察到的数据一致,换句话说,它测试模型与数据的协方差矩阵之间的差异是否可能只是偶然自然出现的(零假设)。与您可能熟悉的假设检验不同,这是我们真正想要的。因此,更大的概率值(p > .05)实际上支持我们的模型,因为你不必拒绝零假设。严格地解释,小的 p 值因此表明我们的模型不适合数据,因为估计的样本协方差矩阵和估计的总体协方差矩阵显著不同。然而,很难找到两个协方差矩阵完全一致的情况,因此这是一个不合理的基准。此外,卡方估计对样本大小非常敏感,因为大量的观察夸大了模型和样本协方差矩阵之间的估计偏差,因为该值乘以 N-1。随着样本大小(N)的增加,差值也增加。单独解释这个值没有多大意义,因此通常使用其他更具信息性的拟合指数,这些指数利用了卡方估计。让我们快速浏览一下其中的几个:我们将比较适合指数与绝对适合指数区分开来。比较拟合指数是模型拟合的相对指数-它们将您的模型与空模型(上面的基线模型)的拟合进行比较。例如,比较适合指数(CFI)或塔克-路易斯指数(TLI)表示您的模型与 0.95 以上的值传统上表示良好适合(Hu & Bentler,1999)的一致程度,然而这些适合指数可能无法跨学科进行比较。另一方面,绝对拟合指数将模型的拟合与完美拟合模型进行比较,因此在这种情况下,模型在预测协方差矩阵时不会出现任何错误。近似的均方根误差(RMSEA)是这样一个指标,其值低于 0.06 表示“良好拟合”。但是因为 RMSEA 倾向于过度惩罚小样本(N < 150) which is makes the SRMR a better candidate to indicate absolute model fit, among its robustness against model misspecifications (Shi & Maydeu-Olivares, 2019). In our example, the robust versions of the CFI and TLI amount to 0.75 and 0.74, respectively. The TLI estimate from our actually comes really close to an empirical average value of 0.73 — a result from a meta-analysis from Chang, Connelly & Geeza in which the authors aggregated different studies on the model structure of the big five (e.g., correlated traits, a single method). The RMSEA probably lies somewhere between 0.063 and 0.064 which indicates an acceptable but not very good fit. In the section on latent variables, we can see the extent to which the items load on their expected latent variable. We focus on the Std.all column — it is standardized because both latent and observed variables have a variance of one. For example, the standardized loading of AGR4 on agreeableness is 0.76, so if you take the response to “我同情其他人的感受。”,这很好地指出了一个人在社会交往中追求和谐的程度(这并不奇怪!).我们的潜在变量不应该是正交的,也就是说,它们可以协变。Std.all 列可以解释为给定我们的模型的潜在变量之间的估计相关性,这里我们有从 0.02(宜人性-情绪稳定性)到 0.35(外向性-宜人性)的小至中等值。请注意,在方差:部分中,输出是残差方差的估计值,因此剩余方差不是由预测器解释的。因此,较大的值可能表明这些项目可能与其他影响混淆,例如,以陈述“我侮辱人”为例。“(AGR4):这种反应的逆转可能不会直接转化为宜人性,但可能也衡量了对他人的厌恶程度。看看下面的 r 平方值,我们可以看到显式变量中的估计总方差由它们各自的潜在变量来解释。例如,潜在的情绪稳定性解释了对 EST1 (“我很容易紧张)的反应变化的大约 48%,所以不到一半。").

顺便说一下,如果您想快速访问对您最重要的适合指数,您可以键入以下内容:

但是请注意,这些指数与汇总函数的输出并非 100%相同,而是非常接近,即使我们采用标准(不稳健)版本的 CFI 和 RMSEA。老实说,我不知道这是从哪里来的。你知道吗?请写在下面的评论里。

模型修改

一些人建议寻找所谓的修改指数,以测试模型上的有益变化是否被忽略了。例如,如果您调用modificationindices(big5_cfa, sort = TRUE),您将得到一个表格,其中包含如何修改语法的建议(例如,添加基于样本协方差矩阵的回归)以提高模型拟合度。通过将 sort 参数设置为 TRUE,首先获得对卡方估计影响最大的修改。尽管如此,如果你是机器学习的粉丝,这个过程应该敲响警钟:这听起来像是让你的模型过度适应你的数据!一旦你对你的模型做了大量事先没有计划的修改,你的验证性分析就变成了探索性分析。如果你意识到这种变化,报告你决策过程中的每一个细节,并确保只包括从理论角度看有意义的术语,这是很好的。在我们的例子中,输出建议用一个衡量责任心的单一指标来定义情绪稳定性,这完全是胡说八道。一个更安全的方法是,根据我们对问卷的一点怀疑,互相测试似乎合理的模型,这就是我们现在所做的。关注人格问卷的偏见并不新鲜,因为应用研究人员长期以来一直担心答案可能会被使用自我报告带来的共同差异所污染。想法如下:所有潜在的变量(例如,外向性、宜人性等。)是否不仅受到随机测量误差的干扰(这是我们根据数据的性质所预期的),还受到与问卷本身相关的系统偏差的干扰。我们的实质性变量可能被一个共同的不可测量的因素所污染,这个因素对所有变量的影响程度相似。为了对公共方法因素(CMV)进行建模,我们必须从经典测试理论中借用一些知识:因此,测试分数(例如,针对个性陈述的答案)可以被理解为一个人的真实分数和测量误差的总和。这种误差被认为是由于随机或非系统的影响。但也有一些情况下,测量误差可能是不同的:一个非随机的影响,可归因于环境(如填写在线调查)和人之间的结合。这种所谓的虚假测量误差(Schmidt,Lee and Ilies,2003)并不总是发生,而是在相同的情况下发生,因此可以被视为系统性的。该误差增加了项目之间的相关性,而不管它应该测量的特性,它不能与真实的分数方差相区分。因此,CMV 也增加了性状之间的相关性,因为这是所有项目共有的方差。这不是我们想要的,因为它看起来好像潜在变量以一种有意义的方式相关,但如果它们实际上不是呢?

我们可以将常用方法方差理解为虚假测量误差,可以使用 SEM 将其建模为潜在变量(Podsakoff,MacKenzie,Lee and Podsakoff,2003)。这种常见的方法差异归结于数据的测量方式,与试图测量的内容无关,因此我们将 CMV 定义为所有项目的基础因素,而不管它们应该测量的人格维度,而它与实际的人格维度无关。因此,CMV 包括所有的显式变量,我们需要通过将EXTRA + AGREE + EMO + OPEN + CON ~~ 0*CMV包含到我们的模型语法中来使它与我们的五大变量正交。

好的——也许包含 CMV 因素的模型对数据提供了更好的解释?在进行一对一的比较之前,我们先来看一下总结。

就像我们经典的 big five 模型一样,卡方统计表明我们的基线模型和用户模型都不符合数据,但正如前面所讨论的,由于其对样本大小的敏感性,这本身并不是模型拟合的良好指标。随着 CFI (0.79 比 0.75)和 TLI (0.77 比 0.74)的增加,我们的拟合优度指标提出了比原始模型更好的模型拟合。类似地,拟合差指标也指向类似的方向,因为 RMSEA (0.065 比 0.064)没有显示出相对于原始模型的改进。然而,SRMR (0.062 对 0.075)已经下降,我们应该特别重视这一指数,因为它已被证明是最稳健的模型错误设定(施和 Maydeu-Olivares,2019)。有趣的是,我们在项目和我们的个性变量之间的标准化因素负荷已经下降到一个中等程度,这表明共同方法的差异可能解释了一部分参与者的反应。因为我们对这种方法因素扭曲人格维度之间关系的程度感兴趣,所以我们对协方差部分特别感兴趣。似乎在包括 CMV 因素后,人格变量之间的相关性或多或少消失了,这说明了首先它们可能是由反应偏差(例如,在纸上使自己成为一个更好的人)导致的假设。有一个例外:情绪稳定性和责任心之间的相关性下降了一个可以忽略不计的量(0.23 比 0.25),这表明一个人的神经质程度和他们的勤奋程度之间的关系没有受到常见方法因素的严重影响。基于我们的 r-square 输出,我们可以看到,我们的模型解释的项目响应的估计方差总体上对一些项目有所改善,但对其他项目保持不变。然而,在这种情况下,有一些限制因素会影响对常用方法方差因子的解释:实际上,多性状多方法(MTMM)因这类问题而臭名昭著(例如,Chang,Connelly & Geeza,2012),因此更先进的研究设计利用不同的方法(例如,外部报告与自我报告)作为控制条件来提取真实的人格差异,这种差异应独立于它们的来源。例如,一个真正富有想象力的人应该被自己和他人同等地评价。我们没有这样的控制条件,因此我们不能明确地告诉我们的 CMV 因子的性质。例如,一些研究人员认为,我们可以将 CMV 因子捕捉到的差异归因于反应偏差,但也可以归因于更高阶人格因子的存在,类似于智力的 g 因子(这将使你成为一个“超级”人,有关评论,请参见 Ashton et al . 2009Chang 等人,2012 年或 McCrae 等人,2008 年)。因此,显式变量之间的相互关系是反应偏差的标志还是更大的元人格维度的问题仍然没有答案,并且有点超出了本文的范围。

好吧,这可能会让你觉得有点不满意,因为 a)存在理论上的限制,b)逐一查看模型输出并不能为哪个模型做得更好这个问题提供明确的答案。这就是为什么我有一个很好的额外的给你…

模型比较

如果您的模型是嵌套的(例如,一个原始的模型 A 只是一个新的更大的模型 B 的一个子集),您可以使用卡方检验来找出性能的差异是否在统计上是显著的。但是如果我们尝试,我们 lavaan 会抛出一个错误,说“一些模型是基于一组不同的观察变量”,这表明我们的原始模型没有嵌套在我们的 cmv 模型中,这使得卡方检验无法解释。然而,我们可以使用赤池信息标准(AIC)进行模型比较。它是模型适合用于同一数据集的竞争模型的相对指标,其根源在于信息论:每当我们使用一个模型时,它只是外界发生的过程的粗略表示,这就是为什么模型永远不可能精确。AIC 估计由于建模本身而损失的相对信息量,损失越小,与竞争模型相比,我们的候选模型质量越高。特别是,AIC 有一个反对过度拟合的术语,因为更复杂的模型需要显示一些超出预测优势的额外能力,这些优势仅来自于有更多的参数来近似数据:从数学上讲,复杂模型的对数似然性必须比简单模型的对数似然性大至少 AIC 要减少的附加参数的数量。因此,更复杂的模型需要更努力地尝试更好的拟合。换句话说,对于模型中包含的每个额外参数,AIC 将增加 2-为了使拟合优度再次下降,模型需要对此进行补偿,并提供至少为 2 的对数可能性。我们可以在 fitmeasures()函数中插入所需的拟合指数,以直接比较我们的两个模型:与原始模型相比,包含通用方法方差因子的模型的 AIC 要小得多,这表明我们的 cmv 模型导致的信息损失更少,尽管它是更复杂的模型,因此更好地拟合了数据。

关于因果关系和局限性的一个注记

结构方程模型应该给你一些关于隐藏在数据中的协方差的提示,并且是证实一个强有力的理论的有用工具。SEM 允许对您的数据进行复杂、多维和细粒度的分析。但是,如果我们真的想知道反应偏差在多大程度上扰乱了人格测量,我们就需要采用上面简要讨论过的实验设计(这已经完成了,例如齐格勒&博纳,2009)。然而,在许多 SEM 分析中,我们没有实验控制条件来比较我们的 CFA 模型,事实上,我们依赖于我们自己理论的有效性。相关性(或协方差)既不是直观上令人满意的,也不是解释性的强有力的估计。正如不足以证明某个大脑区域负责某个类别的感知,如房屋或面孔在展示后在扫描仪下变亮,一个好的模型拟合本身并不能证明我们的理论是正确的。从哲学的角度来看(参见 Tarka,2018),这与模型等价性密切相关:我们可以构建完全不同的模型,但对数据具有相同的拟合度,这意味着一旦我们有了“好的结果”,这并不能证明是否存在更好的模型。这就是为什么我们的分析可能还没有结束。可能还有更多东西有待发现!

参考文献

[1] N. Benson、D. M. Hulac 和 J. H. Kranzler,韦克斯勒成人智力量表第四版(WAIS-IV)的独立检查:WAIS-IV 测量什么? (2010),心理。评估。,第 22 卷,第 1 期,第 121-130 页。

[2] J. B .乌尔曼,结构方程建模:回顾基础并向前迈进 (2006),Pers。评估。,第 87 卷,第 1 期,第 35-50 页。

[3] O. P. John,L. P. Naumann 和 C. J. Soto,范式转换到整合的五大特质分类法:历史、测量和概念问题 (2008), Handb。私人的。理论研究,2008 年 1 月号,第 114-158 页。

[4] M. Ziegler 和 M. Buehner,《社会期望反应及其效应建模》 (2009),。心理学。Meas。,第 69 卷,第 4 期,第 548-565 页。

[5] M. Geiger,S. Olderbak,R. Sauter,O. Wilhelm,《作伪中的‘g’:双重思考申请人选择的人格自我报告测量的有效性》 (2018),前面。心理学。,第 9 卷,第 11 期,第 1-15 页。

[6] 开源心理测量项目 (2019)

[7] L. R. Goldberg,用于管理 50 项 IPIP 大五因素标记集的可能问卷格式(1992 年),心理学家。评估4 ,26–42。

[8] R. B. Kline,《结构方程建模的原理和实践》( 2005 年),第二版。纽约州:吉尔福德, 3

[9] A. Brown,项目反应理论对测验评分和评估评分准确性的方法 (2017),Wiley hand。精神病学。测试。多磁盘。参考文献。Surv。规模测试开发。,第 2–2 卷,第 607–638 页。

[10]纽瑟姆,结构方程建模 (2018),Psy 523/623。

[11] M. C. Ashton 和 K. Lee,人格的高阶因素:它们存在吗? (2009),《T30》社,第 13 卷第 2 期,第 79-91 页。

[12] D. Shi 和 A. Maydeu-Olivares,估计方法对 SEM 拟合指数的影响 (2020), Educ。心理学。Meas。,第 80 卷,第 3 期,第 421-445 页。

[13] L. T. Hu 和 P. M. Bentler,协方差结构分析中拟合指数的截止准则:传统准则与新准则的比较 (1999), Struct。Equ。模型。,第 6 卷,第 1 期,第 1–55 页。

[14] L. Chang、B. S. Connelly 和 A. A. Geeza,分离五大人格的方法因素和高阶特征:一种元分析多轨多方法方法 (2012 年),Pers。社会主义者心理学。,第 102 卷,第 2 期,第 408-426 页。

[15] F. L. Schmidt,H. Le & R. Ilies,r .,超越阿尔法:对不同测量误差源对个体差异结构测量信度估计的影响的实证检验 (2003),心理学方法8 (2),206。

[16] P. M. Podsakoff、S. B. MacKenzie、J. Y. Lee 和 N. P. Podsakoff,行为研究中的常见方法偏差:对文献的评论和建议的补救措施 (2003 年),j . appl . psycholl。,第 88 卷,第 5 期,第 879-903 页。

[17] R. R. McCrae 等人,五大因素中的高阶因素中的物质和人为因素 (2008), J. Pers。社会主义者心理学。,第 95 卷,第 2 期,第 442-455 页。

[18] P. Tarka,结构方程建模概述:其开端、历史发展、在社会科学中的用途和争议 (2018) ,Qual。Quant。,第 52 卷,第 1 期,第 313-354 页。

揭开所有问我的数据科学面试问题—第 1 部分

原文:https://towardsdatascience.com/uncovering-all-data-science-questions-asked-to-me-part-1-8143a72cd247

塞巴斯蒂安·赫尔曼在 Unsplash 上的照片

距离我在《走向数据科学》中发表上一篇文章已经过去了大约 15 个月。在这期间发生了很多事情,包括兼职工作,转为全职数据科学家的工作,最后在新的州跳槽到新的公司,让我远离了为社会做贡献。然而,我带回了一篇新文章,其中包含了我采访的所有公司向我提出的所有数据科学问题的综合概述。这些公司包括甲骨文、C3.ai、益百利、Zest AI、瑞士瑞信银行、Visa 和 CVS Health 等。

在本文中,您可以找到以下几类问题:

-洗钱案例研究—第 1 部分

  • ML 理论问题—第 1 部分

-统计—第二部分

-编程— Python 和 SQL —第 2 部分

我希望这篇文章能帮助你准备未来的面试。让我们从有趣的部分开始吧!

a)洗钱案例研究

在这样的案例研究中,问正确的问题是非常重要的,因为它向面试官展示了你能够以正确的方向思考,并且你已经获得了解决任何问题的批判性思维技能。

在甲骨文(公用事业部门)和 C3,有人问了我几个案例研究问题。艾:

1)您如何从每小时的用电量数据中确定拥有电动汽车的房屋?

这个问题的答案是主观的。然而,根据我的说法,我们可以应用各种无监督的最大似然技术,即 PCA、自动编码器或聚类来确定耗电量高于其邻居房屋的离群值。包含您将创建的所有功能也可能会有所帮助。仅举几个例子,您可以创建总耗电量要素来跟踪过去 1 天、3 天、7 天、15 天的最小、最大和平均耗电量,以及相对于同一邮政编码内邻近房屋的平均耗电量等。

2)你会如何预测缺货的库存清单?

1.询问库存所在的市场位置很重要,因为这将有助于了解市场的规模,以及市场的人口统计如何影响库存。

2.询问过去一年的需求数据也很有帮助,因为这将向我们展示各种季节性、模式、假日需求等。建模必备。

3.对于特征工程,你可以引入滞后,一次性编码变量来解释任何季节性。

B) ML 理论问题

1)列出不同类型的回归&分类指标。

Ans。回归指标—均方误差、均方根误差、平均绝对误差。

分类指标—准确度、精确度、召回率、F1 分数、AUC、ROC。

2)均方误差有哪些利弊?

Ans。缺点:

1.受异常值影响

2.如果值很高,则失去可解释性

3.不会告诉你误差的方向,因为它总是正的

优点:

1.非常容易实现

2.易于数值优化

3)你能用平均绝对误差(MAE)作为你的损失函数吗?

Ans。由于 MAE 不可微,所以它不能用作损失函数。

4)R 平方可以是负数吗?如果是,为什么。写出它的公式。

Ans。是的,R 的平方可以是负数。这意味着你的预测没有一段时间内数据的平均值准确。

公式:

5)如何在时间序列数据中进行交叉验证?

Ans。以下链接对时间序列 CV 提供了很好的解释

https://otexts.com/fpp3/tscv.html

6)区分装袋和助推?(几乎在所有面试中都被问到)

Ans。Bagging 是 Bootstrap Aggregation 的缩写。这是一种元算法,其中选择训练集中的随机数据样本进行替换,以建立“m”模型。最后,来自“m”模型的结果在回归的情况下被平均,或者在分类中被投票。

Boosting 是另一种元算法,有助于提高单个学习者的准确性。这是通过训练一系列弱学习者成长为强学习者,同时从每个后续弱学习者的错误中学习来实现的。

7)什么是消失渐变?

Ans。消失梯度是人工神经网络中的一个普遍问题,其中某些激活函数(如 sigmoid)的输入的大变化会导致输出的非常小的变化。随着越来越多的层被添加到网络中,损失函数的梯度趋近于零,使得网络更难训练。

8)支持向量机是如何工作的?

Ans。在 SVM 中,目标是找到一个最优超平面,使该平面和最近的数据点之间的最小距离最大化。这确保了所选择的超平面能够成功地将所有数据点分离到各自的类别中。

更多信息请访问:https://www . analyticsvidhya . com/blog/2021/03/初学者指南-支持向量机-svm/

9)线性回归的假设有哪些?

Ans。线性回归主要有四个假设:

1.线性关系 —自变量和因变量之间存在线性关系

2.正态性 —假设所有变量遵循多元正态性

3.多重共线性 —自变量之间没有相关性

4.同方差 —假设误差项在所有自变量的值上具有恒定的方差

10)你会如何解决随机森林中的过度适应问题?

Ans。随机森林在一系列不相关的深度生长的树上训练,这对于理解它如何过度适应很重要。您可以使用一些主要的超参数:

N_estimators —由于每棵树都很深,你需要确保树的数量不是很大。就我个人而言,我喜欢保持这个数字在 100-200 左右。

Max_depth —深度在所有决策树中都很重要,即使在随机森林中也不应该保持很高。使用网格搜索来处理 max_depth 会有所帮助。

Max_features —由于通过使用一组随机特征,每棵树都是不相关的,因此不应该使用所有的特征来训练每棵树,因为这违背了随机森林的目的,并且可能会开始过度拟合。教科书中定义的最佳数量是 sqrt(特征数量)。

除了上面的,你还可以玩其他的超参数比如 min_samples_split,min_samples_leaf 等等。

11)你将如何解决神经网络中的过度拟合问题?

Ans。有许多方法可以解决神经网络中的过拟合问题:

1.简化模型 —减少节点、隐藏层的数量并使其不那么复杂应该是你的第一直觉

2.正则化 —脊线、套索和弹性网是一些常用的正则化技术,用于惩罚变量的较大系数

3.提前停止 —在训练神经网络时,当您的测试误差在多次迭代后开始增加时,我们可以停止训练模型。因为该点基本上表示过度拟合的开始

12)定义梯度推进中的学习率。

Ans。梯度增强树对一系列弱学习者进行训练,这意味着每棵树都可以控制整体结果。学习率在这里成为一个关键的组成部分,因为它控制着每棵树对结果的改变量。学习率越高,训练速度就越快,反之亦然。

13)学习率与估计量的数量有什么关系&?

Ans。它们彼此成反比,就好像学习率非常低,那么我们需要更多的估计量来达到最终结果,反之亦然。

有哪些不同的特征选择技巧?

Ans。过滤方法-过滤方法使用统计测量来评估两个分布的关系(相关性),并测量每个要素的每个类的分布与因变量之间的相关性。所选择的特征是与因变量相关性最高的特征。用于例如 Kolmogorov-Smirnov 试验。

包装器方法 —包装器方法利用统计模型根据性能指标(准确性、AUC、f1 分数等)评估每个特性(或特性子集)的性能。).常见的包装方法是递归特征消除,其中模型递归地使用越来越小的特征集,直到达到期望的特征数量。

嵌入式方法 —嵌入式方法在构建模型时执行特征消除。一种用于特征选择的常见嵌入式方法是正则化,其中在统计模型的损失函数中包括范数,以惩罚所使用的特征的数量。

15)如何确定你的模型是否过拟合?

Ans。您可以通过绘制学习曲线来确定过度拟合,学习曲线是训练和测试数据上的模型性能之间的图。如果训练曲线和测试曲线之间的差距随着模型中更高的复杂性而增加,那么它将指示过度拟合。

16)多重共线性对 XGBoost 的特征重要性有什么影响?

Ans。多重共线性对要素重要性有巨大的影响,就好像两个变量彼此高度相关,那么一个变量会弥补另一个变量在要素重要性分数中的缺失。因此,对于一个可能非常重要的特性,特性重要性分数会降低,您可能会放弃它。

17)多重共线性对模型性能和模型解释有什么影响?

Ans。多重共线性使解释系数变得更加困难,因为它们对模型中的微小变化非常敏感。

18)特征数量比行数多会有什么影响?

Ans。为了回答这个问题,你可以举一个例子,如果你只有两列和一个数据点,那么可以有无限多条线来解决这个问题。换句话说,这个问题不会有唯一的解决方案。因此,行数比列数多是很重要的。然而,人们可以使用脊和套索等技术来处理这种情况。

19)岭回归和套索回归有什么区别?

Ans。在 Ridge 中,罚项是系数的平方和,而在 Lasso 中,罚项是系数的绝对值之和。

向非专业观众解释过度合身。

Ans。让我们假设你明天有一个数学考试,你已经练习了所有书本上的问题,并且不知何故记住了大多数问题的答案。但是考试的时候,问的问题和背的有点不一样,考不好。这是在过拟合中本质上发生的事情,其中模型学习训练数据如此之好,以至于它不能在它从未见过的测试数据上表现良好。

如果你已经到了这一步,那么非常感谢你阅读我的文章。我将在本文的第 2 部分回答统计问题和编程问题。敬请期待!

谢谢大家!

如果你喜欢我的作品,请在不久的将来跟随我在媒体上阅读更多的文章。

  • 阅读我的其他关于十大 SQL 问题、特性工程、&、自动化基础数据分析的文章。
  • 很乐意通过 LinkedIn 与您联系。

发现 PlantVillage 数据集中的偏差

原文:https://towardsdatascience.com/uncovering-bias-in-the-plantvillage-dataset-7ac564334526

图 1:来自 PlantVillage 数据集的示例图像。[图片由作者提供]

对用于开发深度学习模型的最著名植物病害检测数据集的批判性评估

植物疾病每年造成全球 20%到 40%的作物损失。疾病检测和识别在疾病管理中发挥着至关重要的作用,以最大限度地减少作物损失,由于视觉检查对于疾病检测至关重要,深度学习是解决这一问题的天然选择。

人工智能革命始于 2010 年代初,当时卷积神经网络主导了计算机视觉竞赛。然而,当人工智能开始应对医学和物理学等其他领域的挑战时,它的真正价值才被理解。如今,机器学习已经成为植物科学中不可或缺的工具。它已经发现了广泛的应用,如对植物细胞器进行分类的、高通量根系表型分析的和使用无人机图像估计作物生长的。尽管机器学习早在 2007 年就被用于植物病害识别[1],但缺乏大型公共数据集阻碍了进一步的研究。当 2015 年第一个广泛和公开的植物疾病数据集 PlantVillage 发表时,这种情况发生了变化[2]。

PlantVillage 数据集是规模最大、研究最多的植物疾病数据集。它包含 54,000 多张同质背景上的树叶图像。有 38 类对应于植物-病害对。这引发了大量使用深度学习进行植物疾病分类的研究。大多数论文报道的分类准确率在 98%以上[3]。

然而,训练好的模型在 PlantVillage 数据集的子集上进行测试。因此,如果数据集有偏差问题,它将不会被检测到。在查看来自这个数据集的图像时,我觉得不同类别的捕获条件是不同的。为了检查我是否产生了幻觉,我做了一个简单的实验。

PlantVillage 数据集包含来自 14 种作物的 54,305 幅单叶图像(图 1)。有 38 个类别被命名为物种 _ 疾病物种 _ 健康。将叶子从植物上取下,放在灰色或黑色的背景下,在晴天或阴天用单个数码相机在户外拍摄。

我把这个数据集缩减为 8 个像素;四个来自角落,四个来自侧面中心——纯噪声(图 2)。图 3 显示了图 1 中减少到 8 个背景像素的示例图像。这个数据集叫做 PlantVillage_8px。

图 2:A)8 个像素的位置。B) 8 像素近距离视图[图片由作者提供]

图 3:来自 PlantVillage_8px 数据集的示例图片[图片由作者提供]。

为了量化 PlantVillage 数据集中的偏差量,我在 PlantVillage_8px 数据集上训练和测试了一个机器学习模型。如果没有偏差,模型应该不会超过随机猜测的准确性,对于平衡数据集,随机猜测的准确性定义为 100/number_of_classes %。对于这个数据集,随机猜测的准确性大约为 2–3%。

如果没有偏差,该模型应该不能超过 2–3%的随机猜测精度。

我使用 scikit-learn 的随机森林分类器实现和默认的超参数来训练模型。为了与在 PlantVillage 上开发模型的工作相比较,数据集被随机分为训练集(80%)和测试集(20%)。分类精度用于评估模型性能。

该模型使用纯噪声达到了 49%的准确率!这表明 PlantVillage 数据集中存在明显的数据集偏差。因为前景和背景在上下文中没有关联,所以期望最小的背景偏差。因此,捕获偏差肯定是数据集偏差的主要原因。

该模型使用纯噪声达到了 49%的准确率!

这意味着,即使在类似条件下收集的新数据集上,基于该数据集开发的模型也会经历显著的性能下降,更不用说现场数据了。请注意,这低估了数据集偏差,因为捕获偏差影响背景和前景,而模型只使用了背景的一部分。此外,随机森林模型使用默认的超参数进行训练,没有进行任何调整来提高其性能。

说到底,处理有偏见的数据集的最好方法是首先避免收集它。实验设计,统计学的一个分支,提出了有效和适当的数据收集的原则。突出的想法是在数据收集之前确定噪声因子,并确保它们被控制或随机化。如果必须处理有偏差的数据集,第一步是理解偏差来源并量化它们。一旦做到这一点,就可以通过消除偏差或用额外的数据收集来消除偏差。最关键的步骤是收集一个与用例相匹配的单独的数据集,并报告这个数据集上的模型性能。这将提供模型性能的可靠估计。

该实验确定并量化了 PlantVillage 数据集中的数据集偏差。作为数据科学家,我们负责创建可靠的模型,而不仅仅是在有偏差的测试集上报告看似很高的准确性。当使用这个和类似的数据集来开发机器学习模型时,我们应该勤奋。

详情和代码可以在这里找到:https://arxiv.org/abs/2206.04374

参考

[1]黄国光,人工神经网络在利用颜色和纹理特征检测蝴蝶兰幼苗病害中的应用 (2007),计算机与电子在农业中的应用

[2] D. P. Hughes 和 M. Salathe,一个关于植物健康的开放存取图像库,用于开发移动疾病诊断 (2015),arXiv

[3] K.P. Ferentinos,植物病害检测和诊断的深度学习模型 (2018),农业中的计算机和电子

理解并使用 TensorFlow 2.0 实现 Vision Transformer

原文:https://towardsdatascience.com/understand-and-implement-vision-transformer-with-tensorflow-2-0-f5435769093

自我关注机制和再见卷积!

将图像分成小块,体验魔法(来源:作者)

当 Transformer Network 问世时,它最初成为 NLP 任务的首选模型。Alex Dosovitskiy 等人在 2021 年 ICLR 表征学习国际会议上提出的“一幅图像相当于 16X16 个单词”首次展示了 Transformer 如何实现计算机视觉任务,并在图像分类任务中胜过 CNN(例如 ResNet)。这篇文章是使用 TensorFlow 2.0 一步一步实现 Vision Transformer (ViT)的深入探讨。你可以从这篇文章中学到什么—

  1. 自我注意机制详解。
  2. ViT 结构解释清楚。
  3. 用 TensorFlow 2.0 从头实现 ViT。
  4. ViT 在 CIFAR-10 分类中的应用示例。
  5. ViT 的不同实现和细微差别。

这篇文章将会很长,我也期望这篇文章将会是最初的 ViT 论文的最佳伴侣,以便理解和实现代码中的研究思想。这里使用的所有代码/图片都可以在我的 GitHub 中找到。所以请坐好,拿起你的咖啡,我们准备好出发了!

免责声明:我想花点时间描述一下注意力机制,因为为了实现 ViT,我将使用Keras multi head attention layer。所以那里发生的事情值得了解。如果您想直接进入 ViT 实现,请跳到第 2 节。

1.变压器和注意:

为了理解视觉转换器,首先我们需要关注转换器和注意力机制的基础知识。对于这一部分,我将遵循论文 注意是你所需要的 。这篇文章本身是一篇很好的阅读材料,下面的描述/概念大部分来自于此&清楚地理解它们,只会帮助我们更进一步。

《变形金刚》的理念是使用注意力而不重复出现(读 RNN)。因此,transformer 仍然是序列到序列(Seq2Seq)模型,并遵循编码器-解码器结构。引用报纸上的话—

编码器将符号表示(x1,…,xn)的输入序列映射到连续表示 z=(z1,…,zn)的序列。给定 z,解码器然后一次一个元素地生成符号的输出序列(y1,…,ym)。在每一步,模型都是自回归的,在生成下一步时,消耗先前生成的符号作为附加输入。

让我们看看论文中介绍的变压器结构—

图 1:变压器架构(来源:注意力是你所需要的全部,作者:A. Vaswani 等人)

一旦我们理解了上述结构的编码器部分,我们就可以转向视觉转换器了。编码器层包含两个非常重要的组件,

  • 多头自我关注块。
  • 位置式全连接前馈网络。

下面重点说多头自我关注部分。论文本身有一个缩放的点积注意和多头注意的图表,它由几个平行运行的注意层组成。

图 2:多头注意力(来源:注意力是你所需要的全部,作者:A. Vaswani 等人)

图中的 3 个标签 Q、K、V 表示查询、键和值向量。现在,当我们搜索(查询)时,我们认为这是信息检索协议的一部分,搜索引擎将我们的查询与一个关键字进行比较,并用一个值(输出)进行响应。

在原始论文中,描述了多头注意力的 3 种不同用法。让我们直接从论文中引用-

1.在“编码器-解码器注意”层中,查询来自前一解码器层,存储器来自编码器的输出。这允许解码器中的每一个位置关注输入序列中的所有位置。这模仿了序列到序列模型中典型的编码器-解码器注意机制。

2.编码器包含自我关注层。在自关注层中,所有的查询来自同一个地方,在这种情况下,是编码器中前一层的输出。编码器中的每个位置可以关注编码器的前一层中的所有位置。

3.类似地,解码器中的自关注层允许解码器中的每个位置关注解码器中的所有位置,直到并包括该位置。

对于我们的目的(理解视觉转换器),最重要的一点是 2,即编码器部分的自我关注。让我们深潜吧!

1.1.自我关注:

为了理解自我关注,图片比文字更有帮助。我将使用幻灯片中的一些图片。

让我们考虑一个输入序列( x 1, x 2,…, xm )。自关注层从该输入序列输出的是一组与输入序列长度相同的上下文向量( C 1、 C 2、…、 Cm )。下面的图片会帮助我们—

图 3:走向注意力(自我)的步骤。将输入序列转化为上下文向量。(来源:作者幻灯片)

在上图中,我们为查询定义了将被训练为 WqWkWv 的权重。它们实际上是如何使用的?让我们看看下面的图片——

图 4:将在训练期间更新的查询、键和值的权重。(来源:作者幻灯片)。

需要注意的是所有 I 的权重相同。关注你所需要的一切纸中,查询的尺寸取为 d_k ,对于值,取为 d_v 。例如,如果我们有一个 5D x_i (例如[0 1 1 2 3]),并且查询具有维度 3,那么 Wq 将具有维度 5×3。同样适用于和相应的权重。那么我们最终如何计算这些上下文向量呢?让我们看看下面—

图 5:关注点积。使用第 I 个查询对序列中的所有键进行点积。(来源:作者幻灯片)。

正如论文中提到的,点积(K^T ⋅ Q_j)是一种选择,也可以使用其他形式,如加法或乘法注意力。重要的一点是在计算位置 q_j(与 x_j 相同)处的α_j(见上图)时,我们仅使用来自 q_j 的信息,但使用所有其他键(k_j)。我们剩下最后一步来计算注意力层的输出,也就是使用下面的

图 6:获得上下文向量(C)作为关注块的输出。(来源:作者幻灯片)

我再次强调:为了计算位置‘I的上下文向量,我们需要来自所有输入的值。最后,如何解读缩放点注意函数中的缩放因子(1 /d_k )?

通常,我们将初始化我们的层,目的是在整个模型中具有相等的方差。但是,当我们对这两个向量( QK )执行方差 σ 的点积时,这会导致缩放器的方差 d_k 倍更高。还要记住 d_k 是两个 QK 的尺寸,而 V 有尺寸 d_v

正态分布的查询和键。点积后的总方差高 d_k 倍。(来源:作者笔记本)。

如果我们不将方差按比例缩小回 σ ,则对于一个随机元素,logits 上的 softmax 将饱和到 1,而对于所有其他元素,则饱和到 0。通过 softmax 的梯度将接近于零,所以我们不能适当地学习参数。

现在,希望你能欣赏文章中介绍的点积注意力图(图 2)。

1.2.多头自我关注:

这是单头自我关注的一个非常简单的延伸。

  • 在多头自我关注中,我们有 h 单头自我关注(层次)。参见上面的图 2。
  • 在单头自我关注中,可训练的参数是权重 WqWkWv。
  • h 单头自关注层不共享参数。因此总共有 3 个 h 参数。
  • 每个单头自我关注输出一个上下文向量。
  • 这些上下文向量被连接起来。
  • 如果单头注意力输出一个维度向量,即每个 C_id ×1,那么多头输出是 hd ×1 个维度向量,给定单头自我注意力层的 h 层。

引用“注意力是你所需要的全部”这篇论文中关于多头注意力的重要性的话—

多头注意允许模型在不同位置共同注意来自不同表征子空间的信息。用单一的注意力头,平均化抑制了这一点。

我们经历了自我注意机制和作为其延伸的多头注意。这是 Vision Transformer (ViT)白皮书中变压器编码器的构建模块,现在我们可以开始研究 ViT 白皮书和实现了。

2.视觉转换器:

首先,看看原始论文“一幅图像值 16 X 16 个字”中所示的 ViT 架构

图 7:视觉转换器架构:(来源:a . Dosovitskiy 等人的一幅图像相当于 16 X 16 个字。艾尔。)

我们已经讨论了 ViT 的主要组件,即变压器编码器和其中的多头注意力。下一步是从图像生成补丁,并添加位置嵌入。对于这个示例实现,我将使用 CIFAR-10 数据。请注意,本文中提到,vit 是数据密集型架构,即使使用 ImageNet 这样相对较大的数据集,在没有强正则化的情况下,vit 的性能也会比 ResNet 低几个百分点。但是,当变压器在更大的数据集(14M-300M 图像)上训练时,情况会发生变化,因此此处使用的 CIFAR-10 仅用于示例实施,而不是用于与其他网络进行性能比较。

最初的实现在 google github 中可用,在 tensorflow models 中有一个非常相似的版本。下面是维特 Below:

  • 拍摄一幅图像(例如:256×256×3)。
  • 将图像变成更小的面片(例如:16×16×3,总共 256 ( N =256×256/16)个面片)。
  • 然后这些补片被线性嵌入。我们现在可以把这些看作代币。
  • 将它们用作变压器编码器的输入(包含多头自关注)。
  • 执行分类。
  • 再见卷积。

让我们从加载数据开始,我将使用tf.data格式—

import tensorflow as tf
from tensorflow.keras.datasets import cifar10(x_train, y_train), (x_test, y_test) = cifar10.load_data()train_lab_categorical = tf.keras.utils.to_categorical(y_train, num_classes=10, dtype='uint8')test_lab_categorical = tf.keras.utils.to_categorical(y_test, num_classes=10, dtype='uint8')from sklearn.model_selection import train_test_splittrain_im, valid_im, train_lab, valid_lab = train_test_split(x_train, train_lab_categorical, test_size=0.20, stratify=train_lab_categorical, random_state=40, shuffle = True)training_data = tf.data.Dataset.from_tensor_slices((train_im, train_lab))validation_data = tf.data.Dataset.from_tensor_slices((valid_im, valid_lab))test_data = tf.data.Dataset.from_tensor_slices((x_test, 
test_lab_categorical))autotune = tf.data.AUTOTUNEtrain_data_batches = training_data.shuffle(buffer_size=40000).batch(128).prefetch(buffer_size=autotune)valid_data_batches = validation_data.shuffle(buffer_size=10000).batch(32).prefetch(buffer_size=autotune)test_data_batches = test_data.shuffle(buffer_size=10000).batch(32).prefetch(buffer_size=autotune)

2.1.补丁生成:

让我们讨论一下原始论文中提出的内容。

考虑一个图像,x∈R(H×W×C),把它变成一个面片序列x _ P∈R(N×P×P×C),其中( HP 是每个图像补片的分辨率,N=HW/P是得到的补片数,也作为变换器的有效输入序列长度。

对于补丁生成,我将遵循原始代码中所做的,但我也会讨论另一种方法,这种方法在 Keras 博客中已经讨论过。

  • Keras 博客中展示的例子使用了[tf.image.extract_patches](https://www.tensorflow.org/api_docs/python/tf/image/extract_patches)。使用它,我们可以从图像中创建补丁,然后将补丁展平。然后使用具有可学习权重的密集层,以隐藏维度对其进行投影(这将很快变得更加清晰)。此外,它添加了一个可学习的位置嵌入到投影向量。输出的最终形状将是(batch_size, num_patches, hidden_dim)。下面显示了一个使用图像张量创建补片的示例。
  • 在原始代码中,我们没有创建补丁,然后通过密集层添加可学习的权重,以将其投影到某个维度上,而是直接使用卷积层(具有可学习的权重),滤波器的数量等于该隐藏维度。所以这里的形状已经是(batch_size, num_patches, hidden_dim),然后一个同样形状的可学习位置嵌入层被添加到输入中。

我们将讨论这两种方法。但在那之前,这个隐藏的维度是什么?这是查询和键的维度(之前我们写为 d_k ,当我们需要[MultiHeadAttention](https://www.tensorflow.org/api_docs/python/tf/keras/layers/MultiHeadAttention) 层时,我们将在编码器块中使用它。因此,这种投影是以这样一种方式完成的,我们可以直接将嵌入的面片(展平的)馈送到转换器。太好了!慢慢地,事情越来越多。

补丁生成(来自 Keras 博客的示例):

下面是类似于 Keras 博客中使用的代码块,它将图像分成给定大小的补丁,然后使用一个辅助函数来可视化这些补丁——

将此用于 CIFAR-10 数据中的示例图像,我们得到以下结果—

图 8:使用上面的代码块,将图像分成小块(“标记”)。

如果我们遵循这个实现(查看 Keras 示例,我们需要首先将补丁(通过Dense层)投影到与[MultiHeadAttention](https://www.tensorflow.org/api_docs/python/tf/keras/layers/MultiHeadAttention)层中的查询维度相匹配的维度,然后添加相应的位置嵌入,下面是代码块——

### Positonal Encoding Layerclass PatchEncode_Embed(layers.Layer): ‘’’ 2 steps happen here 1\. flatten the patches 2\. Map to dim D; patch embeddings ‘’’ def __init__(self, num_patches, projection_dim): super(PatchEncode_Embed, self).__init__() self.num_patches = num_patches self.projection = layers.Dense(units=projection_dim) self.position_embedding = layers.Embedding( input_dim=num_patches, output_dim=projection_dim) def call(self, patch): positions = tf.range(start=0, limit=self.num_patches, delta=1) encoded = self.projection(patch) +               self.position_embedding(positions) return encoded

2.2.补丁生成和位置编码:

我将遵循最初的实现,其中不是生成补丁,然后通过密集层添加可学习的权重,而是通过 Conv2D 层直接添加可学习性,并且过滤器的数量与[MultiHeadAttention](https://www.tensorflow.org/api_docs/python/tf/keras/layers/MultiHeadAttention)层的查询维度相匹配。这样,我们还需要添加位置嵌入,并通过扩展tf.keras.Layer类经由自定义层随机初始化权重来实现。让我们看看下面—

与位置编码相关的一些要点—

  1. 归纳偏差:一旦图像被分成小块,我们就失去了输入的结构,位置嵌入有助于模型了解输入图像的结构。这些位置嵌入是可以学习的,并且突出了模型可以自己学习多少图像结构。文中还提到,不同类型的嵌入技术,如相对嵌入、2D 嵌入,对性能影响不大。这种变压器方法与 CNN 的主要区别是——在 CNN 中,内核帮助我们学习/理解 2D 邻域结构;但是在除了 MLP 层之外的变形金刚中,不使用该局部 2D 结构,并且在初始化时的位置嵌入不携带关于片的 2D 位置的信息,并且片之间的所有空间关系都是从零开始学习的。
  2. 类令牌:本文中也提到了,'cls'令牌用于尽可能接近原始的变压器结构。研究人员还试图只使用图像补丁嵌入,全局平均池(GAP)它们,然后是线性分类器。最初的不良表现后来被发现也不是由于缺少代币 GAP,而是学习速率不是最佳的。

根据最后一点,我不会在这个实现中使用[class]标记。如果我们回到图 7,我们可以看到,在 Transformer 编码器模块中,除了多头注意力层之外,我们还需要实现归一化和 MLP 部分。让我们移到那个部分!

3.变压器编码器模块:

3.1。 MLP: 多层感知器包含 GELU 非线性。我不会详细讨论 GELU,但请查阅高斯误差线性单位 (GELU)论文,我在这里跳过讨论。此激活在tf.nn内可用。MLP 尺寸在文中给出,对于这个简化的实现,我们将使用更小的Dense层。由于编码器模块重复,我们必须小心Dense层中的单元数量,因为输出尺寸必须与下一个MultiHeadAttention层的输入兼容。

3.2. 定额: 图中定额指[LayerNormalization](https://www.tensorflow.org/api_docs/python/tf/keras/layers/LayerNormalization) 层。在最初的组标准化论文中有一个出色的可视化表示。简而言之,如果我们考虑一个形状为(N,C,H,W)的输入张量,它会沿着(C,H,W)轴计算均值和方差( μ_iσ_i )。这可确保输入要素的计算完全独立于批处理中的其他输入要素。

有了这些细节,我们就可以开始写代码了!!不要忽视变压器块中的剩余连接。

4.将所有这些放在一起:视觉转换器

我们已经构建了 ViT 所需的所有小部件——作为单词标记的图像、位置编码、变压器编码器模块。我们将把它们放在一起,组成视觉转换器。让我们跳到代码块—

剩下的唯一步骤是编译模型并为一些给定的时期训练它。使用 Adam optimizer 对 120 个时期进行训练,我获得了以下训练曲线—

图 9:CIFAR-10 数据的 ViT 模型的训练曲线。(来源:作者笔记本)

模型开始过度拟合,出现了一些变化,如降低学习率、提前停止等。,可以添加来克服这一点。除此之外,我认为数据的大小对于数据饥渴的 ViT 来说太低了。当数据集很大(14M-300M 图像)时,ViT 的性能再次优于一般的 ResNet。我们可以获得下面测试集的混淆矩阵—

图 10:用上述 ViT 模型获得的 CIFAR-10 测试组的 CM。(来源:作者笔记本)。

我们已经到了这篇文章的结尾,在这里我们从头开始使用 TensorFlow 2 了解和实现 ViT 的所有细节。所有的代码都在我的 GitHub 上。

参考文献:

  1. 此处使用的代码/图片:我的 GitHub 。建议您打开笔记本浏览器正确查看。
  2. 注意力是你所需要的一切 : A .瓦斯瓦尼等人
  3. 一幅图像抵得上 16X16 个字 : A. Dosovitskiy 等人
  4. 变形金刚进行大规模图像识别: 谷歌博客。
  5. Keras 视觉变形师博客
  6. 谷歌研究;变形金刚型号: GitHub

保持坚强&干杯!!

如果你对更深入的基础机器学习概念感兴趣,可以考虑加盟 Medium 使用 我的链接 。你不用支付任何额外的费用,但是我会得到一点佣金。感谢大家!!

理解 Python 中异步编程的 async/await with asyncio

原文:https://towardsdatascience.com/understand-async-await-with-asyncio-for-asynchronous-programming-in-python-e0bc4d25808e

用一种新的方式编写异步代码

图片由帕特里克·亨德利在 Unsplash 拍摄

大多数 Python 开发人员可能只使用过 Python 中的同步代码,甚至是一些资深的 Python 爱好者。然而,如果你是一名数据科学家,你可能使用过多处理库来并行运行一些计算。如果你是一名 web 开发人员,你可能有机会通过线程实现并发。多处理线程都是 Python 中的高级概念,都有自己特定的应用领域。

除了多处理线程之外,Python 的并发家族中还有一个相对较新的成员——asyncio,这是一个使用async / await语法编写并发代码的库。与线程类似, asyncio 适用于实际中非常常见的 io 绑定任务。在本帖中,我们将介绍 asyncio 的基本概念,并演示如何使用这个新库来编写异步代码。

CPU 受限和 IO 受限的任务

在我们开始使用 asyncio 库之前,有两个概念我们应该弄清楚,因为它们决定了应该使用哪个库来解决您的特定问题。

受 CPU 限制的任务大部分时间都在用 CPU 进行繁重的计算。如果你是一名数据科学家,需要通过处理大量数据来训练一些机器学习模型,那么这是一项 CPU 密集型任务。如果是这种情况,你应该使用 多处理 来并行运行你的作业,充分利用你的 CPU。

另一方面,IO 绑定的任务花费大部分时间等待 IO 响应,这些响应可能是来自网页、数据库或磁盘的响应。对于需要从 API 或数据库获取数据的 web 开发,这是一个 IO 绑定的任务,可以通过 线程asyncio 实现并发,以最大限度地减少外部资源的等待时间。

线程与异步

好了,我们知道线程异步都适合 io 绑定的任务,但是它们有什么区别呢?

首先,乍看之下你可能觉得难以置信,线程使用多线程,而 asyncio 只用一个线程。对于线程化,更容易理解,因为线程轮流运行代码,从而实现并发。但是如何用单线程实现并发呢?

嗯,线程化通过抢占式多任务处理实现并发,这意味着我们无法确定何时在哪个线程中运行哪个代码。决定哪个代码应该在哪个线程中运行的是操作系统。操作系统可以在线程之间的任何点切换控制。这就是为什么我们经常看到线程的随机结果。如果你想了解更多关于线程的知识,这篇文章会很有帮助。

另一方面, asyncio 通过协作多任务实现并发。我们可以决定代码的哪一部分可以等待,从而控制切换到运行代码的其他部分。这些任务需要协作并宣布何时控制将被切换出去。所有这些都是在单线程中用await命令完成的。现在它可能看起来难以捉摸,但当我们稍后看到代码时,它会变得更加清晰。

什么是协程?

这是 asyncio 中一个奇特的名字。很难解释它是什么。许多教程根本没有解释这个概念,只是用一些代码向你展示它是什么。不过,我们先试着了解一下是什么。

Python 中协程的定义是:

协程是子程序的一种更一般化的形式。子程序在一点进入,在另一点退出。协程可以在许多不同的点进入、退出和恢复。

当你第一次看到它的时候,这可能看起来很奇怪。然而,当你和阿辛西奥合作越来越多的时候,就会越来越有意义。

在这个定义中,我们可以把子程序理解为函数,尽管两者之间有差异。通常,一个函数只在被调用时进入和退出一次。但是 Python 中有一个特殊的函数叫做生成器,可以多次进出。

协程的行为很像生成器。实际上,在旧版本的 Python 中,协程是由生成器定义的。这些协程被称为基于生成器的协程。然而,协程现在已经成为 Python 的一个原生特性,可以用新的async def语法来定义。尽管基于生成器的协程现在已经被弃用,但它们的历史和存在可以帮助我们理解什么是协程,以及如何在代码的不同部分之间切换或产生控制。如果你想了解更多关于 Python 中协程的历史和规范, PEP 492 是一个很好的参考。然而,对于初学者来说,阅读和理解可能并不容易。

好了,现在抽象的概念已经够多了。如果你不知何故迷路了,不能理解所有的概念,没关系。随着时间的推移,当你用 asyncio 库编写和读取越来越多的异步代码时,它们会变得更加清晰。

定义协程函数

既然已经介绍了基本概念,我们可以开始编写我们的第一个协程函数了:

async def coro_func():print("Hello, asyncio!")

coro_func()是一个协程函数,当调用它时,它将返回一个协程对象:

coro_obj = coro_func()type(coro_obj)
# coroutine

注意,术语协程可以指协程函数或协程对象,这取决于上下文。

您可能已经注意到,当调用协程函数时,不会调用print函数。如果您使用过生成器,您不会感到惊讶,因为它的行为与生成器功能相似:

def gen_func():yield "Hello, generator!"generator = gen_func()
type(generator)
# generator

为了在生成器中运行代码,您需要迭代它。例如,您可以使用next函数来迭代它:

next(generator)
# 'Hello, generator!'

同样,要运行协程函数中定义的代码,你需要 等待 它。但是,您不能像迭代生成器一样等待它。一个协程只能在由async def语法定义的另一个协程中等待:

async def coro_func():print("Hello, asyncio!")async def main():print("In the entrypoint coroutine.")await coro_func()

现在的问题是我们如何运行main()协程函数。显然,我们不能把它放在另一个协程函数中等待它。

对于顶级入口点协程函数,通常命名为main(),我们需要使用asyncio.run()来运行它:

import asyncioasync def coro_func():print("Hello, asyncio!")async def main():print("In the entrypoint coroutine.")await coro_func()asyncio.run(main())
# In the entrypoint coroutine.
# Hello, asyncio!

注意,我们需要在这里导入内置的 asyncio 库。

在引擎盖下,它由一个叫做事件循环的东西处理。然而,有了现代 Python,你再也不需要担心这些细节了。

在协程函数中返回值

我们可以在协程函数中返回值。该值通过await命令返回,并可分配给一个变量:

import asyncioasync def coro_func():return "Hello, asyncio!"async def main():print("In the entrypoint coroutine.")result = await coro_func()print(result)asyncio.run(main())
# In the entrypoint coroutine.
# Hello, asyncio!

同时运行多个协程

在你的代码中只有一个协程并不有趣,也没有用。当有多个协同程序应该并发运行时,它们会大放异彩。

让我们首先看一个协程等待错误的例子:

import asyncio
from datetime import datetimeasync def async_sleep(num):print(f"Sleeping {num} seconds.")await asyncio.sleep(num)async def main():start = datetime.now()for i in range(1, 4):await async_sleep(i)duration = datetime.now() - startprint(f"Took {duration.total_seconds():.2f} seconds.")asyncio.run(main())
# Sleeping 1 seconds.
# Sleeping 2 seconds.
# Sleeping 3 seconds.
# Took 6.00 seconds.

首先,注意我们需要在协程函数中使用asyncio.sleep()函数来模拟 IO 阻塞时间。

其次,创建的三个协程对象被逐个等待。因为当等待的协程对象已经完成时,控制只被处理到下一行代码(这里是下一个循环),所以这三个协程实际上是一个接一个等待的。因此,运行代码需要 6 秒钟,这与同步运行代码是一样的。

我们应该用async.gather() 函数同时运行多个协程。

async.gather()用于同时运行多个 awaitables。顾名思义,一个可用的是可以通过await命令等待的东西。它可以是一个协程、一个任务、一个未来,或者任何实现了__await__()魔法方法的东西。

我们来看看async.gather()的用法:

import asyncio
from datetime import datetimeasync def async_sleep(num):print(f"Sleeping {num} seconds.")await asyncio.sleep(num)async def main():start = datetime.now()coro_objs = []for i in range(1, 4):coro_objs.append(async_sleep(i))await asyncio.gather(*coro_objs)duration = datetime.now() - startprint(f"Took {duration.total_seconds():.2f} seconds.")asyncio.run(main())
# Sleeping 1 seconds.
# Sleeping 2 seconds.
# Sleeping 3 seconds.
# Took 3.00 seconds.

注意,我们需要解包async.gather()函数的 awaitables 列表。

这一次协程对象是并发运行的,代码只花了 3 秒钟。

如果你检查asyncio.gather()的返回类型,你会看到它是一个 Future 对象。未来对象是一种特殊的数据结构,表示某些工作在其他地方完成,可能已经完成,也可能尚未完成。当等待未来对象时,会发生三种情况:

  • 当 future 已成功解决(意味着底层工作已成功完成)时,它将立即返回返回值(如果可用)。
  • 当未来未成功解决并且引发异常时,该异常将传播到调用方。
  • 当未来还没有解决时,代码会一直等到它解决。

一个更实用的 async with 和 aiohttp 的例子

上面我们刚刚写了一些伪代码来演示 asyncio 的基础。现在让我们编写一些更实用的代码来进一步演示 asyncio 的用法。

我们将编写一些代码来同时从对一些网页的请求中获取响应,这是一个经典的 IO 绑定任务,正如本文开头所解释的。

请注意,我们不能使用我们熟悉的请求库来获得来自网页的响应。这是因为请求库不支持 asynico 库。这实际上是 asynico 库的一个主要限制,因为许多经典的 Python 库仍然不支持 asyncio 库。然而,随着时间的推移,这将会变得更好,更多的异步库将会出现。

为了解决请求库的问题,我们需要使用 aiohttp 库,它是为异步 http 请求(以及更多请求)而设计的。

我们需要首先安装 aiohttp,因为它仍然是一个外部库:

pip install aiohttp

强烈建议在虚拟环境中安装新的库,这样它们就不会影响系统库,你也不会有兼容性问题。

这是使用 aiohttp 库执行 http 请求的代码,它也大量使用了async with语法:

import asyncio
import aiohttpasync def scrape_page(session, url):print(f"Scraping {url}")async with session.get(url) as resp:return len(await resp.text())async def main():urls = ["https://www.superdataminer.com/posts/66cff907ce8e","https://www.superdataminer.com/posts/f21878c9897","https://www.superdataminer.com/posts/b24dec228c43"]coro_objs = []async with aiohttp.ClientSession() as session:for url in urls:coro_objs.append(scrape_page(session, url))results = await asyncio.gather(*coro_objs)for url, length in zip(urls, results):print(f"{url} -> {length}")asyncio.run(main())
# Scraping https://www.superdataminer.com/posts/66cff907ce8e
# Scraping https://www.superdataminer.com/posts/f21878c9897
# Scraping https://www.superdataminer.com/posts/b24dec228c43
# https://www.superdataminer.com/posts/66cff907ce8e -> 12873
# https://www.superdataminer.com/posts/f21878c9897 -> 12809
# https://www.superdataminer.com/posts/b24dec228c43 -> 12920

async with语句使得在进入或退出上下文时执行异步调用成为可能。在引擎盖下,它是通过async def __aenter__()async def __aexit__()魔法方法实现的,这是一个相当高级的话题。有兴趣的话,先了解一下 Python 中的正则上下文管理器的一些知识。而在那之后,这个帖子如果你想更深一层的话,可以是一个很好的参考。然而,通常您不需要深入研究,除非您想创建自己的异步上下文管理器。

除了async with语法, aiohttp 库的用法实际上与请求库非常相似。

在这篇文章中,我们介绍了异步编程的基本概念。用简单易懂的例子介绍了带有async/awaitasyncio.run()asyncio.gather()语句的 asyncio 库的基本用法。有了这些知识,你将能够使用 asyncio 库读写基本的异步代码,并且能够更舒适地使用异步 API 框架,如 FastAPI 。

相关文章:

  • 解开 Python 中迭代器和生成器的神秘面纱
  • 如何用多线程编写并发 Python 代码

理解具有计算机视觉背景的音频数据

原文:https://towardsdatascience.com/understand-audio-data-with-computer-vision-background-ee2a002108b2

并排比较音频和视频数据,以便快速理解

作者图片

视觉是人类强大的感官。从图像中,我们可以立即掌握内容和情绪,而不需要额外的解释。

相反,一维序列数据(包括音频数据)需要理解 x 轴和 y 轴标签来解释波形。没有标签,信号缺乏映射到序列中的峰值和低值的上下文。

作者图片

凭借我在计算机视觉和信号处理方面的背景知识,本文旨在通过与视觉数据相关联来介绍音频数据。绘制一个与另一个之间的关联证明可以有效地将概念放入视角中。

数字化

图像是通过捕捉瞬间的光线形成的,而视频是通过时间轴上的图像拼接形成的。

以类似的方式,通过在一段时间内捕获气压来产生音频信号。

作者图片

由于计算机不能消耗连续波长的数据,基本上最小形式的数据存储为 0 和 1 的位。因此,数据捕获过程包括将数据从连续形式转换为离散形式。这个过程被称为数字化。

通常,数字化分为两部分,一部分检索采样点,另一部分检索强度值。前者称为采样,后者称为量化

抽样

视力

图像上的采样在空间上以规则的间隔发生。这在宽度和高度上对应于数字化图像的二维网格。宽度和高度被称为分辨率,而元素的最小块是一个像素。图像分辨率越高,图像质量就越高。

作者图片

声音的

一维序列中的采样在时间轴上以规则的间隔检索数据点。

采样率决定了每秒捕获多少数据点。高采样速率会导致在频繁的时间间隔内采集更多的样本,反之则会在较稀疏的时间间隔内采集较少的样本。

作者照片

音频采样速率以赫兹(Hz)为单位。现代音频文件的标准速率是 44.1 kHz,这意味着音频每秒被采样 44,100 次。人的听觉在 20 赫兹到 20 千赫的范围内。低于该范围的值会损害音频质量,而较高的音量对增强效果没有明显影响。

作者图片

量化

对于视频和音频数据,量化是指幅度值的采样。强度值被检索以表示输入信号的强度。

视力

每个空间坐标处的亮度值的采样以灰度(1 通道)或 RGB(3 通道)级别的形式出现。强度值也称为像素值,标准范围为 0–255。

作者图片

声音的

对于音频数据,量化捕捉每个采样点的气压量。信号值随时间变化,形成显示气压强度变化的波形。

作者图片

下面总结一下内容。

对音频数据的基本理解允许对其进行各种操作的后续处理。我希望这篇文章能为您开启音频处理之旅带来深刻的见解。

感谢阅读!

阅读材料

  • https://www . izotope . com/en/learn/digital-audio-basics-sample-rate-and-bit-depth . html

线性回归因果推断:省略变量和无关变量

原文:https://towardsdatascience.com/understand-bias-and-variance-in-causal-inference-with-linear-regression-a02e0a9622bc

了解偏差和方差:省略变量、混淆变量、无关变量和多重共线性

作者图片

在我之前的文章 因果推断:计量经济模型 vs. A/B 检验 中,我们讨论了如何使用一个计量经济模型,即线性回归,在控制其他协变量的同时,考察治疗变量和反应变量之间的因果关系。在本文中,我们将讨论设计线性回归时的一些常见问题— 省略重要变量包括无关变量

在我们讨论这些问题之前,我们需要熟悉系数估计的偏差和方差。

  • 偏差测量拟合值和真实估计值之间的差异。如果线性回归的治疗效果有偏差,这意味着我们有一个不准确的因果效应。
  • 方差测量估计值(随机变量)在期望值周围的分布。方差越高,估计精度越低。

如果省略重要变量会怎么样?

我们都应该知道,我们不应该省略线性回归中的重要变量。这种行为的后果将使模型无法正确解释响应变量(又名欠拟合)并可能做出错误的因果推断陈述

让我们更深入地研究一下它会对我们的模型造成多大的损害。

最简单的线性回归如下所示。响应变量(即 Y)可以解释为解释变量(如截距、X1、X2、X3……)的线性组合,而 ε 是误差项,表示拟合响应值和实际响应值之间的差异。误差项的正态假设对于线性回归模型来说是可选的,但是对于因果推断任务来说是推荐的。

图 1(作者图片)

根据您希望如何设置成本函数,我们可以通过普通最小二乘法(【OLS】)或最大似然估计(【最大似然法】 ) 来驱动封闭形式的解决方案。

图 2(作者图片)

接下来,让我们通过将解释变量分解为模型中的处理变量(即 T)和其他解释变量(即 X)来重写图 1 中的方程,以便更容易地调查省略重要变量会如何严重地损害处理变量的系数估计值(即α)。

图 3(作者图片)

根据图 3 和图 2 中的第二个等式,我们可以将“α_hat”的拟合值表示如下:

图 4(作者图片)

假设我们发现图 3 的线性回归模型中遗漏了一个重要变量(即 Z )。正确的模型应该是

图 5(作者图片)

其中γ是省略变量 z 的系数。

为了调查我们把图 4 中治疗变量的系数估计搞得有多乱,我们将用图 5 中的正确模型替换图 4 中的 Y。现在我们有了

图 6(作者图片)

图 6 中的第一项可以简化为α,因为

图 7(作者图片)

图 6 中的第三项应该等于 0,因为当我们建立线性模型时,误差项应该与解释变量的无关。此外,第三项的期望值也将是 0,因为假设误差项的期望值也是 0。

因此,图 6 中的等式可以简化如下:

图 8(作者图片)

场景 1 :省略变量 Z 与治疗变量 t相关,我们称这类变量为混杂变量,因为它们与反应变量和治疗变量都相关。

图 9(作者图片)

当被省略的变量 Z 与治疗变量 T 相关联,并且能够有意义地解释响应变量时,那么图 8 中的第二项不再是 0。因此,治疗效果的 OLS 估计值不再是无偏的,如果省略变量 z,我们可能会做出错误的因果推断陈述

图 10(作者图片)

如果混淆变量 Z 在线性回归模型中被省略,那么治疗变量将成为内生变量,因为“无法解释的”变量 Z 漏入误差项,那么治疗变量将与误差项相关。在这种情况下,对治疗变量的估计会变得有偏差(即内生性偏差)。

场景 2 :省略变量 Z与治疗变量 t不相关。

当忽略的变量与回归模型中的治疗变量不相关时,图 8 中的第二项将为 0。因此,治疗效果的 OLS 估计值仍然是无偏的。

图 11(作者图片)

但是,如果忽略这些变量,还是要付出代价的。即使省略变量 Z 与治疗变量 T不相关,变量 Z 仍然起到解释响应变量的作用,排除变量 Z 会将这个未解释的部分归入误差项 ε 并使误差的方差更大。因此,所有估计量(包括治疗效果)的方差会更大(见图 12)。

图 12(作者图片)

换句话说,如果被省略的变量与治疗变量不相关,尽管治疗效果仍然是无偏的,但治疗效果的方差会变大,假设检验的 t 值会变小,然后 p 值会变大,因此,我们可能错误地得出治疗效果在统计上不显著的结论(即假阴性)。

如果包含无关变量会怎么样?

似乎省略变量的问题可以通过在线性回归模型中包含所有相关变量来轻松解决。但是在模型中包含不相关的变量可能会导致其他问题。

根据 Jeffrey Wooldridge 的教科书,计量经济学导论,高斯-马尔可夫假设下,条件独立变量的样本值,我们可以将方差公式(图 12)改写如下:

图 13

其中 j 代表特定的解释变量 j。SST _ j 是解释变量 j 的总样本变化量,

图 14

R2_j 是对剩余预测因子的预测因子 j 的回归的决定系数,预测因子 j 在左侧,所有其他预测因子在右侧。

下面的术语叫做方差膨胀因子** (VIF)。在线性回归模型中分析多重共线性的大小是一个有用的工具,在该模型中,预测因子彼此相关。**

图 15

  • 当预测值 j 与其他预测值不相关时,VIF_j 等于 1。
  • 当预测因子 j 可以被其他预测因子解释时,VIF j 将变得大于 1。
  • 经验法则是,如果 VIF j 大于 10,则多重共线性很高。

现在让我们考虑以下场景:

场景 1: 从上一节(图 8)我们知道,如果一个变量与治疗变量高度相关,那么在线性回归模型中包含这样一个变量将很可能掩盖治疗变量的真实因果关系(即高偏倚)。很明显,我们需要排除这样一个变量。

情景二:**如果一个变量与处理变量不相关,但与其余解释变量高度相关(即多重共线性)。那么包含这类变量不会增加治疗效果的偏倚和方差(见图 8 和图 13)。然而,当变量不能解释响应变量的变化时,会增加其他解释变量的偏差和方差(即,变量没有减少分子,但减少了图 13 中的分母)。最坏的情况是多重共线性)。在这种情况下,我们将排除这样一个变量。**

情景 3: 该情景类似于情景 2,除了该变量也解释了响应变量的变化。在这种情况下,如果我们比多重共线性更关心省略变量的问题,那么我们可以将它保留在模型中,并接受多重共线性。我们可能会有更大或更小的标准误差(见图 13,尽管增加变量后分母变小,但如果增加变量也有助于解释响应变量,分子也会变小)。在这种情况下,我们需要考虑增加的解释能力和多重共线性之间的权衡。**

情景 4: 如果变量与解释变量和响应变量都不相关,那么你是增加还是省略它们就太重要了(就偏差和方差而言)。然而,如果在模型中加入大量这样的变量,它将开始减少模型中的自由度,然后增加估计的方差(见图 12)。

最终注释

总之,如果一个被忽略的变量或一个不相关的变量与治疗变量相关,那么治疗效果就会变得有偏差。包含与现有预测值相关的无关变量将增加估计值的方差,并使估计值和预测值不太精确。

如果你对线性回归因果推断感兴趣,这里有一些相关的帖子可以浏览。

  • 因果推断:计量经济模型 vs. A/B 检验
  • 线性回归与逻辑回归:OLS、最大似然估计、梯度下降
  • OLS 线性回归:无偏、一致、蓝色、最佳(有效)估计量
  • 线性回归因果推断:省略变量和无关变量
  • 用线性回归进行因果推断:内生性
  • 与 OLS 的线性回归:异方差和自相关

感谢您的阅读!!!

如果你喜欢这篇文章,并且想请我喝杯咖啡,请点击这里。****

您可以注册一个 会员 来解锁对我的文章的完全访问,并且可以无限制地访问介质上的所有内容。如果你想在我发表新文章时收到电子邮件通知,请订阅。

了解 BigQuery 模式自动检测

原文:https://towardsdatascience.com/understand-bigquery-schema-auto-detection-9b4cebfe6d03

并选择手动定义和控制 BigQuery 表模式

米歇尔·雅库博夫斯基在 Unsplash 上的照片

当人们需要创建数据库表时,模式自动检测是一个很有吸引力的特性。想想吧!能够自动检测数据模式听起来真的很棒,因为它可以节省传统上用于手动定义数据模式的时间。

不足为奇的是,BigQuery 为一些数据类型提供了模式自动检测能力,包括 CSV、NEWLINE_DELIMITED_JSON(又名 JSONL)和 AVRO。

虽然我不能否认该特性在开发阶段对快速原型开发的有用性,但我认为它应该在自动化生产环境中小心使用,尤其是当你处理大数据的时候。原因如下:

B igQuery 根据数据样本推断表的模式,而不是查看整个数据,至少对于大数据是这样。

让我用两个简单的例子来说明我的意思。

示例 1:加载一个看起来像整数列的字符串列

考虑以下数据。它包含 10 001 个代码,用 5 个字符表示。除了以字母“a”开头的最后一个代码外,所有代码都由 5 个数字字符组成。

作者图片,来自代码数据的 5 行样本

要将名为 test_sample_1.csv 的 CSV 文件中的数据加载到名为 test_sample_1 的 BigQuery 表中,我们将使用以下命令:

作者图片

然而,我们得到一个令人讨厌的错误,说 BigQuery 无法解析代码 a2153

作者图片

实际上基于一个数据样本,BigQuery 推断列代码是一个整数。然后,它试图将值 a2153 转换成整数,这显然是不可行的。

这是我们在公司尝试利用 BigQuery 模式自动检测实现自动化时遇到的第一个问题。但更糟糕的还在后面。

示例 2:加载带有可选字段的 JSON 记录

这次我们来试试 JSONL 格式的数据。该数据包含由代码识别的 10 0001 所房屋的地址。除了主地址(列地址 1)之外,房屋还可能有辅助地址(列地址 2)。在我们的样本数据中,只有一个房子恰好有一个二级地址,那就是 house 34512

作者图片,地址数据中的 5 行样本

我们运行类似于上一个命令的命令,将数据加载到 BigQuery 表中。

作者图片

在这里,我们还会返回一个错误,指示 BigQuery 自动检测到的模式中没有address . address 2列。

作者图片

同样,自动检测是基于不包含房屋 34512 行的行样本。

解决方案:手动定义和控制您的 BigQuery 表模式

通过定义一个模式并将该模式提供给 bq load 命令,我们可以非常容易地解决上述两个问题。

对于代码数据,我们会

作者图片

对于地址数据,我们将首先定义一个模式文件。然后,我们将运行 load 命令。

作者图片

作者图片

这里的关键是将 address.address2 列声明为可空的

结束注释

如果您不知道 BigQuery 中的模式自动检测是如何工作的,那么它可能会让您非常头疼。长话短说,BigQuery 对您的数据(至少是大数据)进行采样,并基于该样本计算模式。通常情况下,样本模式与数据的实际模式不匹配。因此,我建议根据数据知识手动定义数据模式。
请在这里找到文章中使用的样本数据和代码片段。

直到下一次写作,拜拜。

了解列式和基于行的数据库

原文:https://towardsdatascience.com/understand-columnar-and-row-based-database-2cd29ae35bd0

选择数据库前的重要一步

Unsplash 上活动创建者的照片

在数据库系统中,数据可以以两种方式存储:面向行的数据存储和面向列的数据存储。通过改变数据在计算机硬盘上的存储方式,我们可以对数据库的性能产生很大的影响。这一选择还决定了它是针对事务性工作负载还是分析性工作负载进行了优化。

本文的目的是解释什么是面向行和面向列的数据库。您还将了解它们的差异、优缺点以及不同的用例。

数据是如何存储在磁盘上的?

为了更好地理解面向行和面向列的数据库,了解数据如何存储在磁盘中以及数据库如何读取它们是很重要的。

在硬盘上,数据被组织成最低级别的块,这是计算机一次读取磁盘的最小单位。数据库将从包含它所寻找的数据的块中加载所有信息。如果目标数据存储在较少的数据块中,数据库的运行速度会快得多。

数据块(由高创建)

已经发明了许多方法来以逻辑方式对数据进行分组以提高搜索效率,例如分区和聚类。

面向行的数据库

基于行的存储(由高创建)

为了说明不同存储的工作原理,我们来看一个例子。在上表中,每一行都有一个人的所有信息(如姓名、年龄、地址、邮政编码等)。

在面向行的数据库中,数据一次一行地存储在磁盘上。在本例中,块中的每个数据块都是表中的一行。这种设计非常适合 OLTP(在线事务处理)应用程序,因为 OLTP 工作负载通常会加载一些事务,例如从单个实体中检索每个属性、向表中添加实体或从表中删除实体。Postgres 和 MySQL 是一些最著名的面向行的数据库。写入新行很容易,因为它们只是将整行追加到现有块或新块中。

当需要同时访问大多数列时,行存储非常有用。因此,不建议使用非常宽的表,因为不可能总是需要所有的列。如果一次只需要几列,那么行存储是一个昂贵的选择,因为它将读取许多冗余信息。

面向列的数据库

在面向列的数据库中,数据一次一列地存储在磁盘上。对于同一个例子,每个数据块是一整列。这意味着所有的“名字”将被组合在一起,所有的“年龄”将被组合在一起,以此类推。

基于列的存储(由高创建)

这使得列存储对 OLAP(在线分析处理)很有吸引力,因为它们最有可能执行在大量记录上聚合数据的分析工作负载。大多数查询只对列的子集感兴趣,并且它们是只读的。例如,计算顾客的平均年龄。流行的面向列的数据库是 Amazon Redshift 和 BigQuery。

值得注意的是,column-store 的压缩率非常高,因为每一列都有相同的数据类型。如果您有大量数据,这一点很重要。此外,向现有表中添加新列也很容易,不需要重组所有数据。

许多列数据库(如 BigQuery)更喜欢非规范化的数据结构,这意味着需要进行的连接更少,因此查询可以运行得更快。另一方面,基于行的数据库更喜欢规范化数据,因为它允许更有效地将数据写入数据库。

利弊

面向行的数据库和面向列的数据库各有利弊。我在这里列出了一些,希望可以帮助你做决定。

面向行的数据库

赞成的意见

  • 最适合 OLTP 应用。
  • 插入和删除数据很容易。

骗局

  • 压缩率低,因此占用更多空间。
  • 可能会读取不必要的数据。

面向列的数据库

赞成的意见

  • 最适合 OLAP 应用。
  • 压缩率很高。简单而强大的方法,如 RLE(游程编码)、位向量编码和空抑制,可以有效地用于每一列,并提供更好的压缩比,因为压缩算法对相同数据类型的值更有效。
  • 该查询根本不需要扫描不必要的列。
  • 高效的分析操作,如多行聚合。

骗局

  • 读写完整记录比较慢。

在大多数云提供商中,这两种数据库类型都可供您使用,例如,Google 云平台有基于行的解决方案,如 Postgres 和云存储,以及基于列的解决方案,如 BigQuery。对于不同的用例,向基于行和基于列的数据库发送相同的数据也是一种常见的做法。

混合解决方案

许多公司希望在同一个数据库中处理 OLTP 事务和 OLAP 过程,以简化他们的操作。自然需要有一个混合商店。

有不同的方法来组织属性。一种方法是将相关属性组合在一起,形成一个迷你表。这些属性往往会被一起访问,因此查询可以利用行存储的优势。其余的属性可以存储在列式表中,并可以单独访问。Teradata 等公司提出了一种混合行列存储解决方案,将这两种方法结合起来。

结论

在本文中,我们讨论了什么是面向行的数据库和面向列的数据库,以及它们的优缺点。希望你觉得有用,有启发。像往常一样,如果你有任何其他想法要分享,请留下你的评论。干杯!

参考

https://www.sentinelone.com/blog/understanding-row-vs-column-oriented-databases/

理解 Python 中的上下文管理器,并学习在单元测试中使用它们

原文:https://towardsdatascience.com/understand-context-managers-in-python-and-learn-to-use-them-in-unit-tests-66cff907ce8e

使用上下文管理器使你的代码更加健壮,更加 Pythonic 化

图片由 BUMIPUTRA 在 Pixabay 拍摄

我们已经使用了关键字with和函数open来创建一个上下文,它将在我们读/写完之后关闭文件。然而,Python 中的上下文管理器并不局限于管理外部资源,如文件描述符、数据库连接、线程锁等,它们可以用于提供所有类型的上下文,这些上下文需要在执行主代码块之前和之后进行一些设置和拆卸操作。在本帖中,我们将通过简单的代码片段解释所有的技术机制,来揭开 Python 中的上下文管理器的神秘面纱。我们还将介绍如何在大量使用上下文管理器的 Python 单元测试中使用模拟/补丁。

将上下文管理器与 try/finally 语句进行比较

当用于管理外部资源时,with上下文管理器可以被看作是try/finally语句的语法糖。无论主代码成功与否,两者都将在最后关闭外部资源。以下使用with上下文管理器和try/finally语句的代码片段是等效的:

正如我们所看到的,有了上下文管理器,代码可以更加简洁。

创建自定义上下文管理器

现在让我们创建一个定制的上下文管理器,以便理解场景背后的技术机制。

为了创建上下文管理器,我们可以创建一个实现__enter__()__exit__()魔法方法的类。__enter__()包括上下文的设置代码,将在创建上下文时执行。它可以返回一个值,该值将被赋给with … as <var>语句中的变量。另一方面,__exit__()包括当上下文退出时将被执行的拆卸代码,不管主代码块是否引发异常。如果主代码块中出现异常,异常类型、异常值和回溯将分别传递给exc_typeexc_valueexc_tb

现在,定制上下文管理器应该以与内置open函数相同的方式工作:

使用生成器函数创建自定义上下文管理器

上面我们使用经典的基于类的协议创建了一个定制的上下文管理器,它实现了__enter__()__exit__()方法。类似于迭代器,我们可以使用生成器函数以更简洁的方式创建一个上下文管理器。为此,我们只需要用contextlib库中的[contextmanager](http://twitter.com/contextmanager) 装饰器来装饰生成器函数:

现在,我们可以将生成器函数用作上下文管理器:

正如我们所见,yield语句前后的代码分别对应于__enter__()__exit__()方法。产生的值(如果有的话)被绑定到with … as <var>语句中的变量。注意,如果什么都没有产生,那么yield语句也应该存在,否则它就不是一个生成器函数。这种情况很快就会有一个例子。

用上下文管理器模拟一个函数

有了上下文管理器,我们可以在上下文中改变函数的行为,并在它退出上下文时重置它。这实际上是单元测试中修补工作的机制。让我们用上下文管理器模拟一下datetime.now()函数:

主题演讲:

  • 我们必须将datetime.now函数对象赋给一个新变量,否则,它不能被赋一个新值来改变它在上下文管理器中的行为。稍后你会看到我们可以使用unittest.mock中特殊的 补丁 函数非常方便地模仿任何函数/方法,而不需要创建新的中间变量。
  • 这里创建了一个基于生成器的上下文管理器,您也可以创建一个基于类的上下文管理器,这应该非常简单。只需将yield语句前后的代码分别复制到__enter__()__exit__()方法中。
  • 在生成器函数中,我们必须使用global关键字来声明now是一个全局变量,否则就无法在那里访问。它被保存到一个新变量saved_now中,以便以后可以重置。
  • 然后给now变量分配一个新的 lambda 函数,该函数总是返回同一个datetime对象。这样,now()函数的行为在上下文中被改变。
  • 在这个上下文管理器中,什么都没有产生,但是我们仍然需要显式地产生,以使它成为一个有效的生成器函数。
  • 我们已经知道,yield语句后的代码是拆机部分。我们应该把now()重置回原来的datetime.now()

让我们使用这个上下文管理器来检查上下文内外的now()函数的结果:

我们可以看到,now()函数的行为在上下文中被模仿,当它退出上下文时又被重置回正常状态。

在单元测试中模拟 datetime.now()

大部分功能都可以直接嘲讽。但是,我们不能直接嘲讽datetime.now(),否则,我们会看到下面的TypeError:

我们需要模拟datetime模块。请务必小心下面的代码,这里很容易出错:

主题演讲:

  • 您需要安装pytest模块来运行单元测试:

  • 由于datetime.now()函数不能直接打补丁,我们需要打datetime模块的补丁。注意,我们需要修补当前测试文件中导入的datetime模块,而不是内置datetime库中的模块。如果将test_mock_now.datetime改为datetime.datetime,测试将失败。这是因为,随着这个改变,被嘲笑的是datetime.datetime,而不是当前测试文件中导入的datetime模块。因此,第 10 行和第 11 行的断言会失败。一定要自己尝试一下,这样你才能更好地理解它。

单元测试中的 Mock requests.get()

实际上,在 Python 单元测试中,模仿非常简单。只是来自datetime库的datetime.now()函数有点特殊。让我们修补单元测试中更常用的requests.get()函数,以模拟一些 HTTP 请求的响应:

这次requests.get()函数可以直接打补丁,代码也简单很多。

模拟在单元测试中非常重要,它可以让你的测试代码独立于外部资源工作,让你专注于代码的逻辑和健壮性。几乎所有东西都可以在单元测试中被嘲笑。我们刚刚在本帖中介绍了函数的模拟。更多的例子将会在一篇更专门的单元测试文章中介绍。

在这篇文章中,我们介绍了上下文管理器的基础知识以及用 Python 实现它们的不同方法。上下文管理器可以用基于类或基于生成器的协议来实现,后者更简洁。上下文管理器可以使您的代码更加健壮,因为资源肯定是关闭的。此外,上下文管理器可以用来改变上下文中某些函数的行为,这使得它们在单元测试中被广泛使用,因为它们可以让您的测试代码独立于外部资源工作。

相关文章:

  • 解开 Python 中迭代器和生成器的神秘面纱

通过 7 种软件设计模式理解机器学习

原文:https://towardsdatascience.com/understand-machine-learning-through-7-software-design-patterns-a03572f4e695

在一个一切都基于现成的工具包和容器化环境的时代,设计一个人的软件仍然是相关的

伊丽莎白·维拉尔塔在 Unsplash 上拍摄的照片

对象、接口、类和继承是基于面向对象的软件世界中的常见概念。具有挑战性的是思考不同的和创造性的方法来构建灵活的、可重用的软件组件。

设计模式就是为此而产生的。他们倾向于创建结构并重用成功的设计和架构。在一个一切都基于随时可用的工具包和容器化环境的时代,设计一个人的软件可能看起来无关紧要,因为它主要是由负责生产基于云的外部服务的其他人想到的。

为什么设计模式很重要?

专业设计师知道不能做的一件事是从最初的原则开始解决每个问题。相反,他们重用过去对他们有用的解决方案。

当他们找到一个好的解决方案时,他们会一次又一次地使用它。因此,我们在许多面向对象的系统中发现了类和通信对象的循环模式。这些模式解决了特定的设计问题,并使面向对象的设计更加灵活、优雅,最终可重用。它们通过在先前经验的基础上建立新的设计来帮助设计师重用成功的设计。熟悉这种模式的设计师可以立即将它们应用到设计问题中,而不必重新发现它们。

什么是设计模式?

根据克里斯托弗·亚历山大的说法:

每个模式描述了一个在我们的环境中反复出现的问题,然后描述了该问题解决方案的核心,以这样一种方式,您可以使用该解决方案一百万次,而不必以同样的方式做两次。

克里斯托弗·亚历山大是美国建筑师。然而,他的主张可以转移到软件领域。

每个设计模式都由 4 个基本元素定义:

  • 一个名字 : 一种定义 DP 用于解决什么和如何解决的一般上下文和词汇的方式。
  • 一个问题:描述了什么时候应用模式。
  • 解决方案:类、接口和对象被设计来响应问题的方式。
  • 后果:一旦我们决定应用给定的 DP,我们必须考虑的权衡。

设计模式如何分类?

基于我们希望确保的复用类型,设计模式可以是:

  • 行为:责任如何分担,信息如何通过不同的对象传播。
  • 结构化:关注不同对象和类之间的交互过程。
  • 创建:允许解耦和优化不同对象的创建步骤。

稍后,我将至少展示每种类型的一个示例。

从这篇文章中可以得到什么?

请注意,这里详细介绍的每个概念都来自于“四人组”:设计模式原则这本令人惊叹的书。然而,我正在尝试实现几种设计模式,同时设想机器学习开发领域的小场景。这些实现是用 Python 编写的,可以用另一种编程语言复制。

实验:

让我们继续我们的用例。对于每个设计模式,我们将引用 GoF 给出的定义。然后,我们将给出一个说明性的例子。我们在每个演示开始时加载的小 python 脚本中写下一些依赖关系,包括The Boston housing prices dataset

它可以在 python scikit-learn包中获得,并在文献中被广泛用于基准算法。它最初也是从[StatLib archive](http://lib.stat.cmu.edu/datasets/boston)获得的。

意识到这个数据集引发的伦理问题,我不容忍在一些工程变量中以任何方式传达的任何种族隔离。以下实现的目的只是教育。

1.战略

定义一系列算法,封装每一个算法,并使它们可以互换。策略让算法独立于使用它的客户端而变化。

作者图片

想象一下,您有一组 ML 模型等待部署到特定的上下文中。
类模型遵循特定的接口,但是它们在某些方面有所不同。无论选择什么模型来执行任务,上下文都将只响应由接口确定的结构。
当所使用的算法结构易于被客户改变时,模式策略是有用的。

该模式依赖于一个策略接口,该接口表示算法的主要抽象功能。

我们实施不同的策略来接收具有拟合和预测属性的特定 ML 回归量。我们将使用决策树、k-最近邻和随机梯度下降回归器作为学习示例。

我们定义了一个“吞噬”策略的上下文,并将它的属性映射到它的核心。

让我们检查一下我们的上下文是如何接收模型类的:

作者图片

在不改变上下文的内部核心的情况下,我们确保每个模型都按照预期被采用,这要归功于我们之前确定的策略接口。

2.调解人

定义一个封装一组对象如何交互的对象。Mediator 通过防止对象显式地相互引用来促进松散耦合,并允许您独立地改变它们的交互。

作者图片

假设您想要将预测/分类结果提交给第三方系统。将关于其他系统的信息引用到一个特定的类中会妨碍可重用性,因为它容易发生多次演变。如果系统或模型类中的任何一个发生变化,都必须从实现的角度重新考虑。

做事情的一个好方法(在这里肯定不是最好的)是在双方之间安装一个调解人,并将每个交互限制在它自己之内。通过这种方式,每个对象都可以在修改后自由发展,并且实现之间保持完全隔离。然后,中介可以处理我们的模型类交互的不同方式。

让我们声明一个 Actor 接口,它可以包含一个存储系统、一个随时可以开发的 API 或者一个等待接收数据并做出预测的模型。

我们通过声明一个Mediator接口来负责从一方到另一方的流量传递。

Mediator的具体实现对三方进行配置、初始化,并使它们能够单独或交互地执行适当的动作。

让我们来测试一下我们的mediator:

作者图片

有趣的是,在被中介实例化之前,没有一个被调用的对象知道其他对象是如何操作的。我们可以在一个方法中合并涉及两方的不同动作,但这取决于用例。

3.状态

允许对象在其内部状态改变时改变其行为。该对象看起来会改变它的类。

作者图片

假设你的系统是由一系列全局状态控制的,这些状态通过几个预测过程表现出来。这些状态可能涉及漂移、异常数据点的异常数量、大量缺失数据……
您碰巧训练了不止一个模型来根据可能发生的事件采取行动。

状态模式将某个模型激发到给出状态的特定上下文中。如果状态改变,模型也会改变。

我们将假设状态是由外部程序确定的,并在我们的上下文中用一个字符串替换它:正常预测将提供正常预测,中值预测将计算每个变量的中值,缺少预测将表示缺少足够的数据来执行预测,因此将发送一个零数组。

现在,我们构建一个上下文,并尝试不同的状态:

让我们将我们的背景付诸行动:

作者图片

您可能会观察到状态和策略模式之间的某些相似之处。

状态模式依赖于一个状态来服务于一个操作或一个算法,同时让客户端从适应每一个可能的状态的负担中解脱出来。然而,策略模式只期望一组算法,客户端必须在这些算法之间进行选择,并且不需要提供任何状态。

4.建设者

将复杂对象的构造与其表示分离,以便相同的构造过程可以创建不同的表示。

构建器充当构建器类和产品类之间的中间件。我们的产品将包含以下有趣的信息:

  • 模型
  • 培训过程的要素(培训数据的大小、使用的变量和培训目标)
  • 模型的预测

这个物体相当复杂。构造和最终表示是独立的。每当我们想要用不同的表示重用相同的构造过程时,这是很有帮助的。

我们从烹饪我们产品类别的配料开始:

然后,我们勾勒出建筑商的整体框架,因为它将用于保持建筑功能:

我们实例化我们的Builder接口,并实现我们学习过程的主要步骤和组件:

最后,我们建立一个Director,它将启动完整的构建过程并输出一个ModelObj对象:

作者图片

构建者模式在主管的监督下缓慢但稳定地构建最终产品。一旦构建过程完成,导演就从ConcreteBuilder取回最终产品。您可以更好地控制您的学习过程和最终的数据表示。

5.原型

使用原型实例指定要创建的对象种类,并通过复制该原型来创建新对象。

作者图片

假设您有一个附加到模型的现成的学习集,并且您想要为另一个模型重新创建相同的结构机制;相同的数据,相同的预处理,但有一些细微的差别,只是学习过程略有不同。
与其通过一系列函数调用每一个步骤,更有条理的方法是创建一个学习原型,帮助获得时间和资源,并用新的元素替换它的元素。

我们将编写一个包含训练数据作为属性的学习者类。原型类将包含三个基本属性:模型、特定参数和学习者对象。

一个原型被实例化,我们用学习数据启动它:

我们制作原型的深层副本,并根据模型和参数进行修改。

作者图片

现在,我们能够制作原型副本,可以迭代不同的算法,同时保持我们的流程孤立。

6.适配器

将一个类的接口转换成客户期望的另一个接口。适配器允许类一起工作,否则由于不兼容的接口而无法工作。

作者图片

让我们考虑一个适配器,它接受模型的输出,应用数学转换或一些业务规则,并发送新的结果。每当你的类模型给出与接收系统冲突的预测,而你在寻找一种外部调整它的方法时,这真的很有趣。

首先创建一个名为AdaptivePredictor的接口。然后我们实现两个不同的类;一个有普通的预言,一个有超能力。

对最终预测进行对数变换并不总是最明智的决定,但我们将假设下一个例子就是这种情况。更好的是,我们将假设我们的对数算子将超能力赋予我们的 ML 模型,并以令人难以置信的程度强调我们的预测!

让我们试一试:

作者图片

无论何时需要,Adapting类都可以作为功能支持派上用场。更重要的是,你的程序并不完全依赖于它。干预可以由一些业务逻辑驱动,以适应新的数据分布或新的数据生成器流程。

7.装饰者

动态地将附加责任附加到对象上。Decorators 为扩展功能提供了子类化的灵活替代方案。

作者图片

除了 Python 提供的内置实现之外,在某些情况下,我们希望将某些属性添加到特定的对象中,而不是添加到类中。我们可以用继承来解决这个问题。

从一个类继承一个标准的 scaler 处理方法会将它放在每个子类实例的流程中,这意味着每个实例都必须实现这样的 scaler。该过程不太实际,因为定标器的选择是静态完成的。客户端对象不能控制如何以及何时用适当的缩放器来修饰实例。

更灵活的方法是将缩放器/转换器包含在另一个添加适当转换的对象中。封闭的对象被称为装饰器。

为了说明这种机制,我们可以想到一个我们希望用不同的特性转换器来填充的sklearn管道。

我们首先创建一个具有两个属性(iterable 和 pipeline)的Decorator接口,以及一个将转换器附加到管道的方法。

我们初始化主管道类,并添加一组 decorators 介入,用附加的转换器包装主管道。

我们测试了我们的装饰流程,结果是一个完整的Pipeline准备好符合我们的数据。

作者图片

Update:如果你有兴趣看到更多的场景和更多的设计模式,请不要犹豫,看看第二部分。

结论

我希望您对特定模式在给定情况下如何工作有了一些直觉。这只是一个起点,因为当你对一个好的设计方法绝望的时候,还有许多其他的模式等着帮助你。

提出的模式是只有专家才能应对的常见问题的解决方案。我远远不是。本文的目的只是发掘一种使用机器学习算法的方法,并以面向对象的方式采用它们。

如前所述,所有的概念都在著名的四人帮所写的《设计模式原则》中解释得很清楚,我认为它永远不会过时。

玩得开心!!

通过更多的设计模式理解机器学习

原文:https://towardsdatascience.com/understand-machine-learning-through-more-design-patterns-9c8430fd2ae8

四人帮关于设计模式的书似乎是一切开始的地方。他们提出了一个描述设计模式的框架。

来自 Unsplash 的奥斯卡金钥匙的照片

在之前的故事中,我们展示了软件工程领域中一些最常见的模式,并试图在开发机器学习过程的简单用例中实现它们。

在本文中,我们处理了四种附加的设计模式,它们被转化成几种场景,并在软件设计的许多领域中使用。

请注意,第一篇文章使用了相同的数据集。我们在每个演示开始时加载的小 python 脚本中写下一些依赖关系,包括The Boston housing prices dataset

它可以在 python scikit-learn包中获得,并在文献中被广泛用于基准算法。它最初也是从[StatLib archive](http://lib.stat.cmu.edu/datasets/boston)获得的。

实验:

1.单身:

确保一个类只有一个实例,并提供对它的全局访问点。

作者图片

您希望将对象创建过程的全部控制权从自定义类的任何用户手中拿走。通过控制创建对象的过程,这是可行的。

我们的自定义类实现如下:

构建基础学习者需要模型、训练数据和足够的参数。我们将Learner封装在一个负责实例化它的全局类中:

前导下划线告诉其他程序我们正在处理一个私有类,它们不应该在原始类定义之外使用它。私有类__Learner是我们定义学习材料(模型、参数和训练数据)的地方。

在定义了私有类之后,有一个名为 instance 的东西作为私有属性。这是初始化过程的基础(稍后检查__new__方法);每当instance为 None 时,我们的全局类允许我们创建一个且只有一个实例:

id 函数通知您内存中存放实例的位置:

作者图片

如您所见,我们的单例实现将确保每次都给出相同的实例。
如果你希望改变它的一些属性,可以参考__setattr__ 的方法。

2.模板:

在操作中定义算法的框架,将一些步骤推迟到子类。模板方法允许子类在不改变算法结构的情况下重新定义算法的某些步骤。

作者图片

模板模式描述了流程的不同步骤,将它们分解成功能。然后,通过在每个步骤的级别进行干预并更改一两个细节,该过程将被推广到不同的场景。

例如,我们提供了一个名为AlgoSkeleton的抽象类,包含两个抽象方法(fit 和 predict):

然后,我们创建两个具体的子类,其中我们强制两个分类算法的不同实现。(实现摘自 Erik Linder-norén编写的巨大 repoNapkinML)。
我们将每个子类与作为属性的适当参数相关联。这样,我们坚持由我们的AlgoSkeleton界面决定的抽象结构。

我们实例化了两个类,并使它们适合我们的数据:

瞧!

作者图片

Template方法让你将一个单一的算法变成一系列单独的步骤,这些步骤可以很容易地被子类扩展,同时保持超类中定义的结构不变。

3.观察者:

定义对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖对象都会得到通知并自动更新。

作者图片

假设您有一个或多个依赖于某种度量标准的模型。该指标是根据在一天/一周/一个月结束时收集和汇总的一批数据计算得出的。

驱动模型以某种方式执行的事件可能已经发生;要么进行预测,要么决定某些漂移是否会导致模型表现不同(根据新数据重新拟合模型,参与一些基于规则的系统,..)

该模式包括定义对象间的one-to-many依赖关系,这样当一个对象改变状态时,它的所有依赖对象都会得到通知并自动更新。因此,每当在所谓的主体或可观察对象上注意到变化时,这种影响就会通过观察者传播开来。

我们首先定义一个Observable类,它用来跟踪model_list中的相关模型,并通过一个通知函数更新它们中的每一个。

接下来,我们写下一个继承了Observable属性的Data类,并在set_data函数中添加了一个漂移标志。漂移将作为布尔值插入,但可以指真实场景中的真实计算:

我们通过抽象类写下ModelObserver的细节:

Modelobserver的子类将被提供一个更新功能,如果Observer在数据中声明一个概念漂移,将参与一个重新拟合过程。

让我们演示一下我们刚刚构建的系统:

我们继续向data_before_drift的 model_list 属性添加观察者:

*data_before_drift.add_model(dt)
data_before_drift.add_model(knr)*

不幸的是,我们有一些影响我们的data_before_drift实例的漂移数据。这将导致以前的模型参与有关新数据的更新过程。

每当您对一个对象进行的更改意味着更改其他对象时,该模式非常实用。这非常类似于发布者-订阅者系统,其中有一组订阅者在等待来自至少一个发布者的信息。

4.访客:

访问者模式表示要在对象结构的元素上执行的操作。Visitor 允许您定义一个新的操作,而不改变它所操作的元素的类。

作者图片

假设您有一个由类模型持有的 ML 模型的定制网络,这些模型由一个对象结构编排,或者更具体地说,是一个包含这些模型列表的裸对象。

出于示例的目的,对象结构提供了单个分类和单个回归模型。当一个 ML 模型被一个visitor访问时,它从那个visitor中调用对应于它的类的适当的度量操作。让我们深入细节。

我们构建了VisitorComponentLearner 的抽象形式。我们使用与原作者相同的命名法来指代图案的不同砖块。

Visitor类将包含一个计算回归度量的visit_regressor方法和一个同样用于分类的visit_classifier方法。
至于ComponentLearner,接受方法将帮助我们区分要应用的合适访问方法。

Visitor的一个子类将包含这两个方法的具体实现。ConcreteVisitor 将成为任何ComponentLearner 子类 的具体访客。

我们创建两个组件类,一个回归器和一个分类器。两者在游客接受方式上有所不同。回归器将调用visit_regressor方法,分类器将调用visit_classifier方法。

下面的类将存储遇到的模型的列表,并显示每个模型的度量。
不用说,我们不需要进一步说明哪种类型的访问方法对应于哪个组件。

我们实例化一个访问者和三个组件:

我们将三个组件连接到我们的listOfComponents:

*listOfComponents.attach(component1)
listOfComponents.attach(component2)
listOfComponents.attach(component3)*

以下是从我们的显示方法中顺畅流出的指标:

作者图片

您可能已经注意到,在Visitor的子类主体中添加补充操作很容易,同时收集它们和组件之间的相关操作,并分离不相关的操作。比方说,你想添加具有无人监管功能的组件,有一些工作要做,以提出这样的组件,但少了很多访问者。

结论:

你可以做很多事情来使你的代码优雅和整洁。设计模式帮助我们将重复性的东西和非重复性的东西分开。在我看来,一旦你的实例和类岌岌可危,能够恰当地使用它们将是一生的旅程。

玩得开心:)!

时间序列数据简介

原文:https://towardsdatascience.com/understand-the-time-series-data-cd9746d3df62

使用时间序列数据预测问题的介绍

Icons8 团队在 Unsplash 上的照片

简而言之,时间序列是根据时间戳排序的数据点序列。因此,与传统的机器学习方法相比,时间序列建模需要一种特殊的方法。因为,在时间序列建模中,我们不能忽视数据点的顺序。

简介

和往常一样,理解数据集的底层模式很重要。让我们从一个简单的时间序列开始。

维基百科每日访客——作者图片

在不考虑任何标准的时间序列概念的情况下,我们来看看上面的图中能看到什么。

  • 从 2018 年 1 月到 2018 年 8 月出现负斜率,然后从 2018 年 8 月到 2019 年 1 月出现正斜率
  • 如果我们只关注一个月(或几个月),我们可以看到在整个时间段内数据集中有一个恒定的模式。也就是说,每个月都记录了 2 或 3 个波谷和波峰。

以上结论取决于我们看待以上情节的方式。对于和我有不同观点的人来说,通过看上面的图,肯定会看到一些有用的信息。因此,以某种标准的方式获取信息是很重要的。最好的方法之一是绘制数据集。

在 R 中,有大量的时间序列可视化方法可供使用。但是在 Python 中,有时我们需要做额外的工作。

让我们放大两个月的数据。

作者图片

好吧,为了清楚地理解,让我们指出周末。

作者图片

数据集中有一个清晰的模式。也就是说,

  • 最高的页面浏览量总是(至少在使用的时间段内)记录在星期一。
  • 最低的浏览量总是记录在周六。
  • 在大多数周中,周中会有火花。

到目前为止,我们已经看到了数据集的属性。现在是时候转向实时序列概念了。

在上一节中,我们确定了数据集中存在斜率和重复模式。这意味着,我们的时间序列可以分成几个部分,或者我们可以"分解"时间序列。让我们深入研究可以用来分解时间序列的方法。

时间序列分解

与其他数据不同,时间序列数据可能会令人困惑。举个例子,下面是斯里兰卡总统当选总统后的维基百科个人资料视图。

作者图片

正如您在这里看到的,时间序列数据会受到各种因素的影响。这就是为什么我们需要分解它,并很好地理解它。这也有助于提高预测的准确性。

大多数时间序列由季节性成分(在上一节中,我们将其定义为谷和峰)、趋势成分(之前我们将其定义为斜率)。当从原始数据中去除这两个分量时,我们有一个误差分量。但问题是,在不知道这些成分是如何形成的情况下,我们如何拆分这些成分?在回答这个问题之前,了解可用的分解方法是很重要的。

  1. 基于移动平均的分解
  2. 经典分解
  3. X11 分解(经典分解的增强方法)
  4. 座椅分解(SSE提取 inARIMATimeSseries)

为了简单起见,让我们继续使用经典的分解方法。在经典分解中,有两种方法。

1.加法分解

在加法分解中,我们假设原始序列(y) 可以通过将 S 季节分量趋势分量(T) & 误差分量(R) 相加在一起来构建。

加法分解公式—作者图片

步骤:

  1. 通过对数据应用卷积滤波器来估计趋势, Tₜ
  2. 计算去趋势数列。( yₜ-Tₜ )。
  3. 从原始序列中删除的去趋势序列,然后计算每个期间该去趋势序列的平均值。这就产生了一个季节性因素( Sₜ)
  4. yₜ 中减去 SₜTₜ 。这就给了rₜ;rₜ=yₜ-(tₜ+sₜ)

2.乘法分解

顾名思义,这里我们假设原始序列可以通过将季节性成分趋势成分、&、误差成分相乘来构建。

乘法分解公式—作者图片

现在我们对前一个问题有了两个可能的答案,要么是乘法,要么是加法。我们可以用下面的方法来选择一种方法。

如果季节成分的数量增加,时间序列是倍增的。如果季节性成分的大小大致相同,它更有可能是一个附加的时间序列

这是我们到目前为止讨论过的关键术语的定义。

  • 季节性——与日历的某些方面相关的重复模式。
  • 趋势 —增加或减少系列中的值(斜率)。
  • 周期 —这个术语经常与季节性成分混淆。主要区别是没有固定期限。

现在我们知道了基本理论。是时候做一些编码和绘图了。

为此,我们将使用一个名为 statsmodels 的流行 Python 库。转到以前使用的维基百科数据集。

我们可以使用 statsmodels https://www.statsmodels.org/dev/generated/statsmodels.tsa.seasonal.seasonal_decompose.html[seasonal_decompose()](https://www.statsmodels.org/dev/generated/statsmodels.tsa.seasonal.seasonal_decompose.html)函数执行经典分解。如果你清楚地看到上面的时间序列,它更类似于我们上面讨论的加法序列。您可以使用model参数在seasonal_decompose()功能中设置型号。

from statsmodels.api.tsa import seasonal_decomposedecompose = seasonal_decompose(data,model='additive')

让我们画出结果。

作者图片

让我们做一些有结果的实验。

  1. 剧情降噪系列。
  2. 经季节调整的数据——当季节成分从原始数据中移除时,剩余部分成为经季节调整的数据。如果我们的分析需要忽略季节影响,这种方法可能会更有帮助。然而,这完全取决于我们正在解决的问题。
  3. 残差分布——通常有助于识别分解的偏差。在上面的残差图中,从 2018 年 8 月到 2018 年 10 月存在低噪声。分析残差有助于了解我们的分解方法将数据分成季节性、残差和趋势分量的能力。如果残差更有可能是白噪声,那么就证明分解方法已经成功地区分了成分。

作者图片

出于实验目的,让我们用model='multiplicative'进行完全相同的实验

decompose_multi = seasonal_decompose(data,model='multiplicative')

分解结果。

作者图片

实验结果。

作者图片

作为上述实验的总结,我们可以说加法分解是最适合这个数据集的。我们也可以通过观察剩余图来确定这一点。在加法方法中,误差大致呈正态分布。这意味着我们的假设在这个数据集中是正确的。

结论

这里使用的分解是经典分解(简单分解),不推荐用于更复杂的数据集。

感谢阅读!!

参考

  • 预测:原理与实践。
  • 我的时间序列是加法还是乘法?

理解可解释人工智能中使用的 SHAP 和沙普利值的工作原理

原文:https://towardsdatascience.com/understand-the-working-of-shap-based-on-shapley-values-used-in-xai-in-the-most-simple-way-d61e4947aa4e

你还对 SHAP 和沙普利价值观的运作感到困惑吗?让我在本文中对 SHAP 值和沙普利值提供最简单直观的解释。

来源—https://github.com/slundberg/shap

【沙普利附加解释】 ,这是另一个流行的 可解释 AI(【XAI)框架,可以为表格、图像和文本数据集提供模型不可知的局部可解释性。

SHAP 基于沙普利值,沙普利值是 博弈论 中普遍使用的概念。尽管对 Shapley 值的数学理解可能很复杂,但我将提供对 Shapley 值SHAP 的简单、直观的理解,并更多地关注该框架的实际方面。在本文中,我将参考 SHAP 在《应用机器学习可解释技术》一书中提供的非常简单的解释,并推荐浏览 GitHub 库 https://GitHub . com/packt publishing/Applied-Machine-Learning-explability-Techniques/tree/main/chapter 06,以获得在 Python 中应用 SHAP 的实用代码示例。

**https://www.amazon.com/Applied-Machine-Learning-Explainability-Techniques/dp/1803246154?_encoding=UTF8&pd_rd_w=Wr6SJ&content-id=amzn1.sym.716a1ed9-074f-4780-9325-0019fece3c64&pf_rd_p=716a1ed9-074f-4780-9325-0019fece3c64&pf_rd_r=6P2PM599T97MRG7NZD9J&pd_rd_wg=m4qUW&pd_rd_r=6e349d93-5ba0-4bfe-9055-905c0153fe58&linkCode=li3&tag=adib0073-20&linkId=35506e1847de5c011fc57aa66c2b1d8e&language=en_US&ref_=as_li_ss_il

如果你想得到关于这本书的详细反馈,这个视频可能对你有用:

如果你对 XAI 概念不太熟悉,我强烈推荐你观看过去在 2021 年 APAC 人工智能加速器节上发表的关于 XAI 的演讲:

让我们现在就开始吧,不要耽搁!

SHAP 值和沙普利值简介

SHAP 框架是由 Scott LundbergSu-In Lee 在他们的研究工作中提出的, 解释模型预测 的统一方法。这是 2017 年发表的。SHAP 是基于合作博弈论中 Shapley 值的概念,它考虑了附加特征的重要性。

根据定义,Shapley 值是每个特征值对特征空间中所有可能值的平均边际贡献。Shapley 值的数学理解很复杂,可能会让大多数读者感到困惑。也就是说,如果你有兴趣深入了解沙普利值的数学知识,我们建议你看看名为“n 人游戏的价值”的研究论文《博弈论》投稿 2.28(1953)罗伊德·S·沙普利**。在下一节中,我们将通过一个非常简单的例子来直观地了解 Shapley 值。

什么是沙普利价值观?

在这一节中,我将用一个非常简单易懂的例子来解释 Shapley 值。让我们假设 Alice、Bob 和 Charlie 是三个朋友,他们作为一个团队参加了一场 Kaggle 比赛,以解决给定的 ML 问题,并获得一定的现金奖励。他们的共同目标是赢得比赛,获得奖金。他们三个在 ML 的所有领域都同样不好,因此以不同的方式做出了贡献。现在,如果他们赢得了比赛并赢得了奖金,考虑到他们个人的贡献,他们将如何确保奖金的公平分配?他们将如何衡量自己对同一目标的贡献?这些问题的答案可以由 Shapley 值给出,Shapley 值是由 Lloyd Shapley 于 1951 年在 T21 提出的。

下图直观地展示了这种情况:

作者图片

因此,在这个场景中,Alice、Bob 和 Charlie 属于同一个团队,玩同一个游戏(即 Kaggle 竞赛)。在博弈论中,这被称为联盟博弈。比赛的奖金是他们的支出。因此,Shapley 值告诉我们每个玩家对确保公平分配的支出的平均贡献。但是为什么不在所有玩家之间平均分配奖金呢?嗯,既然贡献不对等,那么把钱平均分配就不公平了。

决定支出

现在,我们如何决定最公平的分配方式呢?一种方法是假设 Alice、Bob 和 Charlie 按顺序加入游戏,Alice 先开始,Bob 接着,Charlie 接着。让我们假设,如果爱丽丝、鲍勃和查理单独参与,他们将分别获得 10 分、20 分和 25 分。但如果爱丽丝和鲍勃联手,他们可能会得到 40 分。爱丽丝和查理一起可以得到 30 分,鲍勃和查理一起可以得到 50 分。当他们三个一起合作时,只有这样他们才能得到 90 分,这足以让他们赢得比赛。

该表说明了用于计算每个玩家的平均边际贡献的每个条件的分值

数学上,如果我们假设有 n 个玩家,其中 s 是玩家的联盟子集,𝑣(𝑆)是 s 个玩家的总价值,那么通过 Shapley 值的公式,玩家 I 的边际贡献给出如下:

参与人 I 的边际贡献等式

Shapley 值的等式可能看起来很复杂,但是让我们用我们的例子来简化它。请注意,每个玩家开始游戏的顺序很重要,因为 Shapley 值试图考虑每个玩家计算边际贡献的顺序。

现在,对于我们的示例,可以通过计算爱丽丝对最终得分可能造成的差异来计算爱丽丝的贡献。因此,贡献是通过计算爱丽丝在游戏中和不在游戏中得分的差异来计算的。

还有,当爱丽丝在玩的时候,她既可以独自玩,也可以和别人组队玩。当爱丽丝在玩的时候,她能创造的价值可以表示为𝑣(𝐴).同样,𝑣(𝐵)和𝑣(𝐶)表示鲍勃和查理创造的个人价值。现在,当 Alice 和 Bob 合作时,我们可以通过从总贡献中移除 Bob 的贡献来计算 Alice 的贡献。这可以用𝑣(𝐴、𝐵)–𝑣(𝐵来代表)。如果三个人一起玩,爱丽丝的贡献是𝑣(𝐴,𝐵,𝐶)–𝑣(𝐵,𝐶).

考虑到爱丽丝、鲍勃和查理玩游戏的序列的所有可能排列,爱丽丝的边际贡献是她在所有可能场景中个人贡献的平均值。

爱丽丝的沙普利值是考虑到所有可能的情况下她的边际贡献

因此,爱丽丝的总贡献将是她在所有可能情况下的边际贡献,这也恰好是沙普利值。对于爱丽丝来说,沙普利值是 20.83 。同样,我们可以计算 Bob 和 Charlie 的边际贡献,如下表所示:

爱丽丝、鲍勃和查理的边际贡献

我希望这不会太难理解!需要注意的一点是,Alice、Bob 和 Charlie 的边际贡献之和应该等于他们三个加在一起的总贡献。现在,让我们试着在 ML 的背景下理解 Shapley 值。

https://www.amazon.com/Applied-Machine-Learning-Explainability-Techniques/dp/1803246154?_encoding=UTF8&pd_rd_w=Wr6SJ&content-id=amzn1.sym.716a1ed9-074f-4780-9325-0019fece3c64&pf_rd_p=716a1ed9-074f-4780-9325-0019fece3c64&pf_rd_r=6P2PM599T97MRG7NZD9J&pd_rd_wg=m4qUW&pd_rd_r=6e349d93-5ba0-4bfe-9055-905c0153fe58&linkCode=li3&tag=adib0073-20&linkId=35506e1847de5c011fc57aa66c2b1d8e&language=en_US&ref_=as_li_ss_il

沙普利值(ML)

为了理解 ML 中 Shapley 值对解释模型预测的重要性,我们将尝试修改我们用于理解 Shapley 值的关于 Alice、Bob 和 Charlie 的示例。我们可以将 Alice、Bob 和 Charlie 视为用于训练模型的数据集中存在的三个不同特征。因此,在这种情况下,玩家的贡献将是每个特性的贡献。游戏或 Kaggle 竞争将是黑盒 ML 模型,支出将是预测。因此,如果我们想知道每个特征对模型预测的贡献,我们将使用 Shapley 值。

在 ML 环境下理解 Shapley 值(图片作者

因此,Shapley 值帮助我们理解每个特征对黑盒 ML 模型预测的结果的集体贡献。通过使用 Shapley 值,我们可以通过估计特征贡献来解释黑盒模型的工作。

摘要

在本文中,我们关注于理解 SHAP 框架对于模型可解释性的重要性。至此,您已经对沙普利价值观和 SHAP 有了很好的了解。如果你喜欢这篇文章,并想了解更多关于如何应用 SHAP 来解释 ML 模型的信息,我推荐阅读这本书: 【应用机器学习可解释技术】 并探索 GitHub 资源库:https://GitHub . com/packt publishing/Applied-Machine-Learning-explability-Techniques/tree/main/chapter 06以获得实际操作的代码示例。

https://www.amazon.com/Applied-Machine-Learning-Explainability-Techniques/dp/1803246154?_encoding=UTF8&pd_rd_w=Wr6SJ&content-id=amzn1.sym.716a1ed9-074f-4780-9325-0019fece3c64&pf_rd_p=716a1ed9-074f-4780-9325-0019fece3c64&pf_rd_r=6P2PM599T97MRG7NZD9J&pd_rd_wg=m4qUW&pd_rd_r=6e349d93-5ba0-4bfe-9055-905c0153fe58&linkCode=li3&tag=adib0073-20&linkId=35506e1847de5c011fc57aa66c2b1d8e&language=en_US&ref_=as_li_ss_il

作者关于 TDS 的其他 XAI 相关文章:

  1. 用于在文本数据上训练的模型的可解释机器学习:将 SHAP 与变压器模型相结合
  2. EUCA——一个有效的 XAI 框架,让人工智能更贴近终端用户

https://www.amazon.com/Applied-Machine-Learning-Explainability-Techniques/dp/1803246154?_encoding=UTF8&pd_rd_w=Wr6SJ&content-id=amzn1.sym.716a1ed9-074f-4780-9325-0019fece3c64&pf_rd_p=716a1ed9-074f-4780-9325-0019fece3c64&pf_rd_r=6P2PM599T97MRG7NZD9J&pd_rd_wg=m4qUW&pd_rd_r=6e349d93-5ba0-4bfe-9055-905c0153fe58&linkCode=li3&tag=adib0073-20&linkId=35506e1847de5c011fc57aa66c2b1d8e&language=en_US&ref_=as_li_ss_il

参考

  1. Python SHAP 框架的 GitHub repo—【https://github.com/slundberg/shap
  2. 应用机器学习解释技术—【https://amzn.to/3cY4c2h
  3. GitHub repo 自《应用机器学习可解释技术》——https://GitHub . com/packt publishing/Applied-Machine-Learning-explability-Techniques/**

用石灰理解黑盒模型的工作原理

原文:https://towardsdatascience.com/understand-the-workings-of-black-box-models-with-lime-92203f906431

虽然机器学习模型的性能看起来令人印象深刻,但它可能不会对业务产生重大影响,除非它能够解释为什么它首先给出这些预测。

おにぎり 在 Unsplash 上拍照

通过对各种机器学习模型的超参数调整已经做了大量工作,以便最终获得最终业务用户感兴趣的输出,从而他们可以根据模型采取行动。虽然您的公司了解机器学习的力量,并为其实施确定了合适的人员和工具,但业务部门可能总会有一个需求关于为什么一个特定的模型首先会产生一个结果。如果模型能够在没有可解释性的情况下根据测试数据(看不见的数据)很好地生成预测,它们会使用户总体上不太信任。因此,有时增加机器学习的这个额外维度(称为可解释性)并非常详细地理解它的能力是至关重要的。

现在我们已经了解了预测模型的可解释性的重要性,现在是时候探索我们实际上可以使黑盒模型更具可解释性的方法了。当进入像数据科学这样广阔的领域时,人们可以看到各种各样的 ML 模型可以用于各种用例。需要注意的是,没有一个模型可以在所有用例中总是成功的,并且它还可以高度依赖于数据以及输入和目标特性之间的关系。因此,我们应该公开找出所有模型列表的结果,并在对测试数据执行超参数调整后最终确定最佳模型。

当探索一系列模型时,我们经常面对大量的模型,很难选择最好的一个。但是这个想法是在使用更复杂的模型之前,先从最简单的模型(线性模型)T1 或 T2 简单模型 T3 开始。线性模型的好处在于它们是高度可解释的(对我们的案例来说很有效),并且当它们被用于生产时,可以给企业带来很好的价值。然而,问题是这些线性模型可能无法捕捉各种特征和输出之间的非线性关系。在这种情况下,我们将使用足够强大的复杂模型来理解这些关系,并为分类提供出色的预测准确性。但是关于复杂模型的事情是,我们将不得不牺牲可解释性。

这就是我们将要探索的机器学习的一个关键领域,叫做 LIME(局部可解释的模型不可知解释)。通过使用这个库,我们应该能够理解为什么模型会对新的测试样本做出特定的决定。借助最复杂模型预测和 LIME 的力量,我们应该能够在实时预测时利用这些模型,并具有可解释性。现在让我们从如何使用 LIME 的可解释性的代码开始。请注意,还有其他方法,如 SHAP 也可以用于可解释性,但为了更容易理解,我们将只坚持使用 LIME。

同样重要的是要注意,lime 是模型不可知的,这意味着不管机器学习预测中使用的是什么模型,它都可以用来提供可解释性。这意味着我们也很好地使用深度学习模型,并期望我们的石灰为我们做解释。好了,现在我们已经了解了 LIME 及其用途,是时候继续进行它的编码实现了。

石灰的代码实现

我们现在要看看 LIME 的代码实现以及它如何解决模型的可解释性问题。需要注意的是,scikit-learn 中有某些机器学习模型,如随机森林或决策树,它们有自己的默认功能可解释性。然而,可能有很大一部分 ML 和深度学习模型不是高度可解释的。在这种情况下,继续使用 LIME 进行解释将是一个好的解决方案。

现在是在我们使用 LIME 之前安装它的时候了。如果您在默认环境下使用 anaconda 提示符,那么安装 LIME 会非常容易。您必须打开 anaconda 提示符,然后在下面的代码单元格中键入以下内容。

conda install -c conda-forge lime

如果你想用‘pip’装石灰,随意。你可以将这段代码直接添加到你的 Jupyter 笔记本中来安装 LIME。

pip install lime-python

导入库

既然已经安装了 lime 包或库,下一步就是在我们的应用程序中使用它之前,将它导入到当前的 Jupyter 笔记本中。

import lime       # Library that is used for LIME
from sklearn.model_selection import train_test_split # Divides data
from sklearn.preprocessing import StandardScaler  # Performs scaling
from sklearn.linear_model import LinearRegression
from sklearn.svm import SVR 

因此,我们将使用这个库来解释各种机器学习模型。

除了 LIME 库之外,我们还从 scikit-learn 导入了一系列额外的库。让我们在编码单元中解释一下上面提到的每个包的功能。

我们使用‘train _ test _ split’将数据分为训练和测试两部分。

我们使用‘标准定标器’来转换我们的特征,使得它们应该具有零均值单位标准偏差。这可能很方便,特别是如果我们使用基于距离的机器学习模型,如 KNN (K 最近邻)和其他一些模型。

“线性回归”是输出变量连续时使用的最流行的机器学习模型之一。

类似地,我们还使用称为支持向量回归机的额外模型,在我们的情况下分别为“SVR”。

读取数据

在导入了所有的库之后,让我们看看数据集。为了简单起见,我们将阅读直接从 scikit-learn 图书馆提供的波士顿住房数据。我们也可能导入高度复杂的真实世界数据集,并期望我们的 LIME 库完成可解释性的工作。在我们的例子中,我们可以使用波士顿住房数据来证明石灰在解释各种模型的结果方面的能力。在代码单元中,我们将导入 scikit-learn 库中现成的数据集。

from sklearn.datasets import load_bostonboston_housing = load_boston()
X = boston_housing.data
y = boston_housing.target

特色工程

由于我们是在一个并不像我们通常在现实世界中发现的那样复杂的数据集上运行特征重要性,所以只使用标准缩放器来执行特征工程可能是好的。请注意,当我们考虑更复杂和真实世界的数据集时,会涉及到更多的事情,比如寻找新的要素、移除缺失值和异常值以及许多其他步骤。

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 101)scaler = StandardScaler()
scaler.fit(X_train)
X_train_transformed = scaler.transform(X_train)
X_test_transformed = scaler.transform(X_test)

可以看出,输入数据分为训练和测试两部分,这样我们就可以执行标准化,如图所示。

机器学习预测

既然特性工程已经完成,下一步就是使用我们在早期代码块中导入的模型,并测试它们的性能。我们首先从线性回归模型开始,然后转移到支持向量回归机进行分析。

model = LinearRegression()   # Using the Linear Regression Model
model.fit(X_train_transformed, y_train)  # Train the Model
y_predictions = model.predict(X_test_transformed) # Test the Model

我们将模型预测的结果存储在变量‘y _ 预测’中,该变量可用于了解我们模型的性能,因为我们已经在‘目标’变量中有了我们的输出值。

model = SVR()
model.fit(X_train_transformed, y_train)
y_predictions = model.predict(X_test_transformed)

同样,我们也使用支持向量回归机模型进行预测分析。最后,我们使用误差指标测试两个模型的性能,例如平均绝对百分比误差均方误差,这取决于业务问题的环境。

局部可解释模型不可知解释(LIME)

现在我们已经完成了机器学习预测的工作,我们现在需要检查为什么模型会对我们最近的数据做出特定的预测。我们用上面讨论过的石灰来做。在下面的代码单元格中,导入了 lime,结果显示在图像中。让我们看看结果,并确定为什么我们的模型给出了一个特定的房价预测。

from lime import lime_tabularexplainer = lime_tabular.LimeTabularExplainer(X_train, mode = "regression", feature_names = boston_housing.feature_names)explanation = explainer.explain_instance(X_test[0], model.predict, num_features = len(boston_housing.feature_names))explanation.show_in_notebook()

我们导入‘lime _ tabular’并使用它的属性来获得我们的模型解释任务。重要的是添加我们执行 ML 任务的模式,在我们的例子中是回归。此外,应该有从数据中给出的特征名称。

如果我们有一个新的测试样本,它将被交给解释者,并列出模型使用的所有特性。最后,输出显示了我们为预测模型提供的类别列表的特征重要性。它还给出每个特征是否导致输出变量增加或减少的条件,从而导致模型的可解释性。

作者图片

从图中可以看出,根据测试实例给出的特征值,我们的模型预测房价为 40.11\(** 。可以看出,特征**‘LSTAT’**低于 7.51 导致我们对房价的预测增加了大约 **6.61 美元**。得出一个类似的结论,让**【CHAS】**值为 0 导致我们的模型预测比应该预测的值低 3 **.77\) 。因此,我们很好地理解了导致模型预测的特征及其条件。

如果你有兴趣了解更多关于可解释性和可用工具的信息,我也建议你去浏览一下 SHAP (Shapley values)的文档,它也有助于解释模型预测。但是现在,我们都完成了这篇文章,并且非常理解我们如何使用 LIME 来提高可解释性。

如果你想获得更多关于我的最新文章的更新,并且每月只需 5 美元就可以无限制地访问媒体文章,请随时使用下面的链接来添加你对我工作的支持。谢了。

https://suhas-maddali007.medium.com/membership

以下是您联系我或查看我作品的方式。

GitHub: 苏哈斯马达利(Suhas Maddali)(github.com)

LinkedIn: (1)苏哈斯·马达利,东北大学,数据科学| LinkedIn

中等: 苏哈斯·马达利—中等

了解深度学习的矢量化

原文:https://towardsdatascience.com/understand-vectorization-for-deep-learning-d712d260ab0f

深度学习

与“for”循环相比,Python 中的 NumPy 在处理十亿次乘法时要快 10,000%以上。

马库斯·克里斯蒂亚在 Unsplash 上的照片

这篇文章将向你介绍矢量化,以及它在机器学习(尤其是深度学习)中的重要性。您还将学习如何使用 NumPy 在 Python 中实现它,以及它带来了多大的不同。

这篇文章假设了线性代数的最低背景。你应该知道什么是向量,什么是矩阵,我们怎么把向量和矩阵相乘。除了这些,我会在我们进行的过程中介绍其他的东西。

为什么矢量化很重要?

当谈到将机器学习应用于给定的问题时,通常没有确定性的公式可以告诉你将做得很好的精确架构或超参数。不管任何人声称什么,这个过程都是通过反复试验来实现的。它本质上是经验主义的。

改编自吴恩达的深度学习课程(图片由作者提供)

这就是为什么我们需要将数据集分成不同的部分,训练集、开发集(有时也称为验证集)和测试集。我们使用开发集来选择正确的超参数和架构集,如果结果不够好,就进行迭代。

我们需要速度更快的迭代,找到好的解决方案

随着你获得更多的经验,你会发展出某些直觉来帮助你更快地获得更好的结果。然而,这种方法仍然是经验性的。因此,速度不仅很好,而且绝对重要。如果你的算法训练得非常慢,那么迭代的可能性就会降低,从而找到“足够好”的结果的可能性也会降低。

引擎盖下的神经网络

在我继续举例说明之前,先简单介绍一下神经网络(深度学习算法的基本单元)。神经网络的单层由一些输入( x )、偏差项( b )、权重( w )和非线性函数(称为激活函数)组成。从数学上讲,这是一个简单的数学运算,包含线性和非线性部分。

深度学习算法由这种堆叠在一起的神经网络层组成。训练神经网络包括向前传递和向后传递。在向前传递的过程中,我们进行预测。在反向传递过程中,我们将预测结果与实际结果进行比较,并使用误差来更新神经网络的权重。然后我们重复这个过程。

让我们绘制一个简单网络的单层来说明矢量化的思想。在这里,我只展示了一个神经元作为说明,用一个数字, x,作为输入,另一个数字,b(称为偏差)。

单个神经元具有单个一维输入 x、偏置项和非线性激活函数 f(图片由作者提供)

在现实世界的应用中,输入 x 将会有多个维度。此外,我们将拥有多个神经元,而不是每层只有一个神经元。由于矩阵和向量,我们仍然可以用多维输入(用一个涉及矩阵的方程)来表示和计算给定层中多个神经元的数学方程。

单层的数学运算(图片由作者提供)

对于上面的等式,矩阵W’将具有尺寸为 mk ( m 行,每行对应一个神经元,以及 k 列,其中每列对应给定样本中的每个特征),【X’将是一个 k 乘1 【T25)

以上是单个数据样本的单层神经网络的前向传递方程,X’。如果我们要对每个数据样本重复这个操作,那就太麻烦了。想象一下当训练数据集有超过一百万个数据点时这样做。

幸运的是,我们不需要对每个数据样本重复上述过程。相反,我们可以传递整个训练数据集,并在一个步骤中计算输出。

使用所有训练数据时单个图层的数学运算(图片由作者提供)

除了量纲不同之外,这个方程看起来与前面的方程非常相似。 X 不再是单个 k 维数据点,而是整个数据集的矩阵,其维数为 ks ,其中 s 是样本总数(一百万以上并不罕见)。

整个训练数据排列成一个大矩阵 X,其中每一列是 k 维样本,总共有 s 个样本(图片由作者提供)

深度学习只有在我们拥有大型数据集的情况下才有意义

深度学习在许多情况下超过传统算法的一个主要原因是大型数据集的可用性。事实上,如果数据集很小,使用传统的学习算法会更好。

想象一下,如果我们有一百万个数据样本可以用来训练。如果没有任何矢量化,我们将不得不依次对每个示例进行乘法和加法运算。我们需要一个“for”循环,从每个矩阵中挑选相应的项,并将它们相乘。矢量化使我们能够避免这种“for”循环,并在一个步骤中使用所有数据。

实现神经网络前向传播部分的矢量化版本的矩阵乘法的图示

在 Python 中是如何实现的?

在 python 中,我们可以使用 Numpy 来帮助我们实现神经网络。Numpy 代表数字 Python。这是一个开源项目,它提供了线性代数和矩阵的函数。在基本 Python 中,列表可以用作数组。然而,Numpy 提供的数组对象(ndarray)比 Python 列表快几个数量级。

在内部,NumPy 将数组存储在一个连续的内存位置。此外,NumPy 经过优化,可与最新的 CPU 架构配合使用。因此,NumPy 被广泛用于数据科学并不奇怪。

如果没有矢量化,我们将被迫使用“for”循环进行矩阵/向量乘法。让我们看看用 for 循环和矢量化(从而避免 for 循环)将两个向量相乘时执行时间的差异。

下面我将生成两个大小为“array_size”的随机 NumPy 数组。

import numpy as np
import time
array_size = 1000000
x1_numpy = np.random.rand(1,array_size)
x2_numpy = np.random.rand(1,array_size)

然后,我将使用“for”循环计算这两个向量的乘积,并估计所需的时间。

tic = time.process_time()
dot_product = 0
for i in range(x1_numpy.shape[1]):dot_product +=x1_numpy[0,i]*x2_numpy[0,i]
toc = time.process_time()
print("dot product = " + str(dot_product))
print ("Computation time = " + str(1000 * (toc - tic)) + "ms")

然后,我将使用内置的 NumPy 函数估计使用矢量化版本所需的时间。

tic = time.process_time()
dot_product = np.dot(x1_numpy,x2_numpy.T)
toc = time.process_time()
print("dot product = " + str(dot_product))
print ("Computation time = " + str(1000 * (toc - tic)) + "ms")

对于一个大小为 10 亿的数组,For 循环需要 1,330,437 毫秒(约 22 分钟)。矢量化方法仅用了 7,421 毫秒(约 7 秒)。这相当于快了 179 倍以上(17,900%!).

进行 10 亿次乘法运算时,比较矢量化版本和非矢量化版本

毫不奇怪,随着数组大小的增加,这两种方法之间的差异变得很明显。我针对不同的数组大小(从 10 到 10 亿)重复了上述步骤,并计算了计算所需的总时间。

对于从 10 到 10 亿的不同向量大小,比较使用“for”循环和矢量化版本的处理时间。(图片由作者提供)

最后的想法

我在这个领域已经工作了十多年了。我认为深度学习现在起飞的三个关键原因是大量数据的可用性、可用的计算能力和算法的创新。矢量化是一项惊人的算法创新。我不相信没有矢量化,深度学习就不会起飞。

https://ahmarshah.medium.com/membership

了解您的数据平台用户,并为他们提供成功的工具

原文:https://towardsdatascience.com/understand-your-data-platform-users-and-provide-them-with-the-tools-for-success-67176a278746

在您的数据平台中需要了解的 4 个常见角色

照片由在 Unsplash 上放大

在一个数据平台上工作,你会看到许多用户出于不同的原因来到这个平台。他们可以是对数据感兴趣的人,也可以是希望更多地了解如何使用数据的人,也可以是每天应对这些挑战的专业开发人员。您将会看到许多角色来利用您的数据平台。我见过的最常见的四种角色是数据工程师、数据科学家、机器学习工程师和数据分析师。当你和这些不同的人一起工作时,你开始理解他们的需求和他们试图解决的共同问题。利用这些信息,您可以找到一个解决方案来解决他们的问题,并能够帮助推动您的数据平台取得成功。正如我常说的,沟通不能是事后的想法。这应该是你的团队每天的练习。因此,让我们来讨论这些典型的人物角色,以及您如何理解他们的团队需求,以便您可以交付对他们有价值的工具和解决方案。

4 个常见角色

正如简介中提到的,我见过四种最常见的角色并与之共事:数据工程师、数据科学家、机器学习工程师和数据分析师。这些不同的小组可能有共同的技能和他们关注的领域,但他们也有一些重要的差异,可以在他们如何使用您的平台时发挥作用。

在下图中,我将四个人物角色分解为他们的基本功能和帮助他们实现这些功能的技能子集。

作者使用米罗创作的图像,插图由 Pablo Stanley 绘制

在本次讨论中,让我们将机器学习工程师角色作为我们使用 Databricks 的理论数据平台的重点。然后,当我们将机器学习工程师引入 Databricks 环境时,他们可能会花至少一个月的时间来首次适应新的工作环境。

对于大多数人来说,这将从访问适当的工具和下载必要的工具开始,比如 IDE。他们还需要数据访问、存储库和笔记本访问,以及您的团队在他们进入平台时为他们提供的任何其他工具。一旦进入,他们就可以开始阅读、学习和理解他们将日常使用的数据和机器学习模型。最后,他们将了解团队如何利用机器学习和遵循的最佳实践,包括 MLOps、软件和设计原则。

了解他们的需求

现在你的机器学习工程师已经进入了他们的团队和你的平台,你需要了解他们的需求。在你的平台上引入一个新的团队不是一劳永逸的事情;您将在这个团队的整个工作周期中与他们在一起,直到他们不再使用您的平台。随着时间的推移,团队会发生变化,他们和客户的需求也会发生变化,这意味着您的沟通会持续不断地变化。那么我们如何开始评估他们的需求呢?谈论他们遇到的问题。

在这个例子中,如果我们与我们的机器学习工程师坐下来,询问他们遇到的最重要的问题,我们会得到各种答案。例如:

  • 数据杂乱无章,有时甚至不准确。同样,我也看到了真相的另一个来源。我应该使用哪个数据集,为什么?
  • 我们团队的模型没有在一个地方被有效地跟踪,并且我不能快速地确定我们有多少模型在生产中。
  • 将我们的 ML 模型投入生产的过程相对较慢。

问题是永无止境的,但是现在你已经和你的工程师有了沟通的渠道,你可以理解他们最重要的问题和你能提供的可能的解决方案。但是我们不能就此止步。有些陈述有些模糊,你可能看不到全貌,所以你应该带着更多的问题深入探讨。

你的问题可能每次都不一样,这没关系。提出问题的目的是全面了解他们的当前状态、他们面临的问题以及您可以在哪些方面提供帮助。不幸的是,并不是每种情况都是平台问题;有时候,要靠团队自己去解决。当你讨论这些问题时,你自己和帮助你的工程师都会发现哪些问题需要解决。

如果您不确定如何进行这些讨论,并且正在寻找一个可能有所帮助的框架,请考虑一下五个为什么。该框架通常用于有效地分解问题,以帮助故障排除或解决问题。

让我们看看本案例中陈述的后两个问题,以及您可能会问的一些问题:

问题:我们团队的模型没有在一个地方被有效地跟踪,我无法快速确定我们有多少模型在生产中。将我们的 ML 模型投入生产的过程相对较慢。

角色:机器学习工程师

  1. 为什么你的模型没有被跟踪?团队为什么要跟踪模型?当您想知道哪些已经部署到生产环境中,哪些还没有部署时,跟踪您的模型的不同版本和开发阶段是很有帮助的。但是并不是每个团队都使用某种形式的版本控制来跟踪这些信息。
  2. 你为什么跟踪你的代码?您是否跟踪软件的版本,这些信息保存在哪里?由于工程师没有使用任何模型版本控制,下一个最好的问题是询问团队是否对代码使用任何版本控制。通过问这个问题,我们可以更好地理解团队为他们的代码和模型准备的过程。
  3. 为什么团队不给模型或者软件分配一个版本号?团队正在跟踪代码,但没有跟踪版本变化。询问为什么会出现这种情况会让你深入了解他们的问题。例如,他们可能不知道如何适当地跟踪他们的机器学习模型的版本。
  4. 为什么你看不清团队有多少型号?模型是否以易于理解的格式存储,并带有名称?如果团队有 50 个他们已经创建并正在积极开发的模型,他们需要一种方法来理解和区分这些模型。这不仅仅是一个版本号。我们需要一个与该模型相关的名称来了解它是什么。我们可能想问这个问题,以了解模型目前是如何命名的,以及这种命名约定是否有意义。如果我们试图预测我们所在地区的天气,那么 model_1 这个名称比 weather_forecast_predictor 这个名称更难以描述。这些名字可能仍然不完美,但是您会认为在第二个例子中它们更易读。
  5. 为什么将模型投入生产的过程很慢?你是否缺乏自动化?我们之前在与机器学习工程师的对话中讨论过,他们使用版本控制,但我们可能没有涵盖他们是否以任何方式使用 CI/CD 或 MLOps 管道。随着对话的进行,我们可以讨论模型目前是如何进入生产的。对于一些人来说,这看起来像是将笔记本或代码移动到生产环境中的手动过程。其他人可能已经使用 CI/CD 将他们的代码部署到生产环境中,但是他们面临着我们上面已经讨论过的问题。回到我们的机器学习工程师,他们的过程很慢,因为没有一个优秀的策略来将模型从开发转移到生产。由于我们的模型没有被版本化,我们不能快速地通过不同的阶段,并且能够在模型上线之前跟踪模型是如何改进的。

应对措施:找到一种工具或解决方案,让您平台上的机器学习工程师能够根据版本和名称跟踪和注册他们的模型,同时为他们提供一种快速有效地将代码转移到生产的过程自动化的方法。

交付工具和解决方案

现在,您已经了解了平台上人员的角色,并评估了他们的需求和面临的问题,您可以确定您可以提供的工具或解决方案。根据您的数据平台,这可能会因您解决问题的方式而异。例如,您的平台可能在一个云平台中有不同的服务,这些服务是相互连接的。在这种情况下,您可以考虑如何集成新的服务来解决这些问题。相反,你的平台可能是你的团队使用的一个特定的服务,比如 Databricks。看到这里时,您可能希望向您的用户推荐一个已经可用的平台特性。这样,您就不必支付额外的服务费用,只需支付流程中使用的任何集群的额外费用。

无论您的平台是什么,您都可以评估哪些工具和服务目前可供您使用,并先看看它们是否适合该用例。问问你自己,在我的组织中是否已经有了一个解决这个问题的已知方案,在我寻找新的方案之前,我能向团队提供这个方案吗?你可能经常能够用一个“固定的”回答来回答这个问题,因为其他人已经面对这个问题并且问过同样的问题。有时你需要寻找其他的东西,做更多的研究来解决这个问题。

当面对一个新问题时,我从导师那里得到的最好的建议是总是寻找最直接的解决方案或工具。我们并不总是需要从零开始创造一些东西,或者选择最复杂的方法来解决问题。有时最简单的解决方案可以提供最好的结果。

让我们回顾一下之前的例子。首先,让我们将收集到的信息进行分解,看看是否可以推荐一个可能对团队有用的工具。

  1. 为什么你的模型没有被跟踪?团队为什么要跟踪模型?团队目前无法通过部署过程跟踪模型。
  2. 你为什么跟踪你的代码?您是否跟踪软件的版本,这些信息保存在哪里?团队使用 Github——到目前为止,我们还没有足够的信息来推荐工具或解决方案。
  3. 为什么团队不给模型或者软件分配一个版本号?就在这里,我们可以引入 MLFlow 来帮助对模型进行版本控制,但是让我们继续询问以确保没有其他事情发生。
  4. 为什么你看不清团队有多少型号?模型是否以易于理解的格式存储,并带有名称?当我们考虑这个问题时,MLFlow 在这种情况下仍然是有用的,因为我们可以使用注册中心进行版本控制和模型命名。可能的解决方案没有改变。
  5. 为什么将模型投入生产的过程很慢?你是否缺乏自动化?同样,MLFlow 将是这位工程师的绝佳解决方案。模型可以在工具中进行版本化和注册,然后您可以在开发、试运行、生产和归档的各个阶段之间移动。这个过程允许从开始到结束的模型的生命周期管理,同时也提供了团队可以用来跟踪和实验的其他工具。

通过与团队和机器学习工程师的讨论,您可以建议他们考虑将 MLFlow 用于模型注册。MLFlow 是在机器学习生命周期中使用的开源平台。该工具将带来以低延迟大规模部署机器学习模型的能力。MLFlow 的模型注册将允许团队在将不同的模型部署到产品中时跟踪它们的模型名称和版本。如果团队在其平台中利用 Databricks,那么 Databricks 已经提供了该工具的托管版本。

Databricks 机器学习的托管 ml 流程图

在理想的世界里,所有的问题都会有简单的解决方法。但在现实中,你可能并不总能找到一个完美的解决方案或工具来解决他们的所有问题,这没关系。你不需要知道所有的答案,也不需要自己解决每一个问题。相反,您提供了一个地方来进行讨论,了解问题,并确定什么是可行的,什么是不可行的。你把结果传达给其他人。最后,让它成为一个迭代学习的过程,每个人都觉得他们可以为团队的成功做出贡献。

最后的想法

您将会看到许多角色来利用您的数据平台。四个是数据工程师、科学家、机器学习工程师、数据分析师。通过了解你的人物角色和他们的需求,你可以为他们提供在日常工作中取得成功所需的工具和解决方案。但是,正如我经常说的,沟通不能是事后的想法。这应该是你的团队每天的练习。

您如何定义您的数据平台上的不同角色,并确保您提供适合他们需求的工具?

资源

  • 数据分析和数据科学有什么区别?作者蒂姆·斯托比耶斯基
  • 机器学习工程师对数据科学家由雪花
  • 克里斯汀·伯纳姆的《数据分析 vs .数据科学:一次失败》
  • 5 个为什么——通过思维工具快速找到问题的根源
  • MLFlow
  • Databricks 机器学习能力

感谢阅读!我希望你喜欢阅读我所学到的东西。如果你愿意,你可以通过使用这个链接成为一个媒体成员来支持我的写作。

10 分钟内完成线性回归、逻辑回归和 SVM

原文:https://towardsdatascience.com/understanding-3-classical-machine-learning-models-once-and-for-all-part-1-32a1ac52c0fd

机器学习

线性回归与 logistic 回归和支持向量机有什么关系?

**Table of contents****·** [**So, how does machine learning work?**](#a1a6)
**·** [**Linear Models**](#ae6d)∘ [Linear regression](#1e7e)∘ [Ridge](#a70b)∘ [Lasso](#5e73)∘ [Elastic-Net](#cd6f)
**·** [**Logistic Regression**](#551b)
**·** [**Support Vector Machine (SVM)**](#3bbd)∘ [Classification](#31cc)∘ [Regression](#0286)∘ [Kernel functions](#daf9)
**·** [**A note on preprocessing**](#0f82)
**·** [**Conclusion**](#2996)

机器学习建模是数据科学家的问题解决者。尽管它不会占用我们大部分的时间,但就个人而言,它比数据清理有趣得多。

坦率地说,一些模型在数学上是复杂的。好消息是,作为一名数据科学家,你不一定有能力从零开始构建机器学习模型。已经有太多的库可供选择,没有必要重新发明轮子。然而,从鸟瞰的角度了解模型是如何工作的总是好的。

那么,机器学习是如何工作的呢?

机器学习模型的内部运作|图片作者作者

一般来说,所有有监督的机器学习模型的工作方式都是一样的:你有 输入数据 X输出数据 y ,然后模型找到一个 映射Xy 。这就是为什么机器学习被称为这样:不是我们故意编码将 X 映射到 y 的逻辑,而是模型通过更新其 参数 来自主学习。大多数车型还有 超参数 ,即无法学习,需要用户整定的参数。

为了清楚起见,假设您有 m 观察值n 特征 ,并且您正在处理一个单输出任务。然后, X 是一个 m × n 矩阵,而 y 是一个大小为 m 的向量。为了学习从 Xy 的映射,模型必须使用 优化器 找到最佳参数,例如梯度下降、BFGS 等等。但是什么是最优呢?具体来说什么是最佳的?

你可以把“寻找最佳参数”的问题看作一个优化问题(是的,机器学习确实只是一个优化问题)。所以,你需要一个 目标函数 ,或者在其他文献中也叫做 代价函数

成本函数根据模型和任务而不同。例如,对于预测参数为 w 的线性回归,常见的成本函数是误差平方和,表示为

w 最小化该成本函数时,找到最优 w

线性模型

在下面的线性模型和逻辑回归中,为了方便起见,我们省略了偏差系数 b 。偏差系数使我们的模型更通用,有两种方法可以添加它:

  1. 通过在 X 中创建一列 1,因此 X 现在是一个 m × (n+1) 矩阵,并且 w 是一个大小为 n+1 的向量,符号 Xw 保持不变。
  2. 通过将 Xw 显式更改为 Xw + b

线性回归

在线性回归中,目标值 ŷ 预计是特征的线性组合。换句话说,

如前所述,使用这个模型,你可以最小化相对于 w 的误差平方和,也就是说,

()线性回归模型与数据拟合得很好| ( )线性回归模型与数据拟合得不太好|图片作者作者

尽管形式简单,线性回归有一个问题。如果 X 中的一些特征是相关的,那么( XᵀX ) 将接近奇异,并且 w 变得对误差高度敏感,导致过拟合。幸运的是,有一个变通办法:如果太大,给 w 一个惩罚。

这就产生了脊、套索和弹性网回归。

山脉

为了给 w 一个罚分,我们需要根据 w 向成本函数添加一个量,使得最小化成本函数也最小化这个量。所讨论的量是 w 的欧几里德范数(也称为 L2 范数)的平方乘以超参数 α ≥ 0 。目标变成了

基于这一目标,不鼓励模型过于复杂,并且倾向于将 w 向零收缩。 α 的值越大,收缩量越大,因此 w 对共线性变得更加稳健。

套索

有一个替代岭回归的方法。不是将 w 中的所有系数向零收缩,你的模型也可以学习将部分系数设置为零。只剩下一些非零系数,有效地减少了相关特性的数量。

目标变得出奇的容易。我们简单地将岭回归的成本函数中的 L2 范数的平方与 L1 范数交换如下

有了这种能力,套索回归也可用于特征选择,并通过选择非零系数来减少 X 的维度。然后,简化的数据可以用于另一个分类器/回归器。很容易看出, α 的值越高,选择的特征越少。

弹性网

也有可能获得脊和套索回归的优势。弹性网络在成本函数中具有 L1 和 L2 范数,允许学习稀疏模型,其中很少有系数像 lasso 一样非零,同时仍然保持 rigde 的正则化属性。现在的目标是

超参数 0 ≤ ρ ≤ 1 称为 L1 比,它控制着 L1 和 L2 范数的组合。请注意,如果 ρ = 0 ,则物镜等效于 ridge 的物镜。另一方面,如果 ρ = 1 ,那么这个目标就相当于 lasso 的。

以下是对相同数据应用线性模型的示例,其中 X 有 4 个特征,偏差 b = 0 。我们可以看到,ridge 将系数向零收缩,lasso 将第一个系数设置为零,而 elastic-net 就像是两者的组合。

Linear Regression
w = [ 2.7794 -11.3707  46.0162  32.4487]Ridge
w = [ 2.2607  -9.8212  45.3181  31.9229]Lasso
w = [-0\.      -2.4307  44.7832  31.3252]Elastic-Net
w = [-0.2630  -2.1721  40.4175  27.8334]

从线性回归到弹性网络,模型具有更好的性能(更低的 RMSE)。请注意,这是而不是的情况。

线性模型的性能示例|图片作者作者

逻辑回归

我们已经谈了很多关于回归的问题,所以让我们继续讨论分类。在二进制分类任务中, y 的每个元素 yᵢ 都是两个类中的一个,可以编码为-1 和 1。目标是

其中 Xᵢ 是垂直矢量形式的 X 的第 i 个观测值(行)。由于我们使用的是 逻辑函数 (在某些文献中也称为 sigmoid 函数),因此该任务也称为逻辑回归。

逻辑回归寻找线性决策边界|图片作者作者

我们将解释为什么这个成本函数有意义。固定一个观察点 j 。逻辑回归的工作方式是首先定义一个决策边界,在本例中为 0。如果 Xⱼᵀw ≥ 0 ,则预测 ŷⱼ = 1 。否则,预测 ŷⱼ = -1 。现在…

  • 如果 yⱼ = 1Xⱼᵀw0 ,那么这次观察的成本很大,因为

因此,该模型将更倾向于满足 Xⱼᵀw ≥ 0 ,这预测了 ŷⱼ = 1 ,并且符合观测值 yⱼ = 1

  • 如果 yⱼ = -1Xⱼᵀw0 ,那么这个观测的成本就很大,因为

因此,模型将更倾向于满足 Xⱼᵀw < 0 预测 ŷⱼ = -1 并符合观察 yⱼ = -1

逻辑回归也可以支持 L1、L2 或两者正则化。

支持向量机(SVM)

分类

当我们看逻辑回归时,它能够为两个类别之间的任意 x 画出一个决策边界 wᵀx + b = 0 。直观上,如果判定边界与任何类别的最近训练数据点的距离最大,则判定边界实现了良好的分离,因为一般来说,边界越大,分类器的泛化误差越低(尤其是在可分离的类别中)。

如果你看一下文献,SVM 正试图通过最小化参数 w 的长度(L2 范数)来最大化余量。当我第一次看到这个的时候,我不知道为什么。事实证明,这一事实成立,因为许多数学操作和推理,这将在下文中探讨。

线性可分数据的 SVM 判定边界,边缘边界上的三个观察值称为“支持向量”|图片由作者提供

因此,支持向量机的工作方式就像逻辑回归一样,只是稍微有点变化:符号( wᵀXᵢ + b )对于大多数观察值 i 来说应该是正确的,并且

越大越好。这叫做 功能余量 。让

现在,对于每个 i = 1,2,…,m ,让 γᵢ 是从每个观察值 Xᵢ 到判定边界的距离。从几何学上讲,由 简单代数 ,我们有

或者以更紧凑的形式,

这叫做 几何余量 。设 γ 为所有距离中的最小值,

那么 SVM 的目标是在一些约束条件下找到 γ 的最大值,

由于将 wb 乘以某个因子不会改变几何余量,我们可以找到 wb 使得‖w**= 1。因此,不失一般性,目标变成

根据功能和几何余量的定义,目标变成

由于将 wb 乘以某个因子不会改变符号( wᵀXᵢ + b )但会改变功能裕度,因此我们可以找到 wb 使得功能裕度等于 1。因此,不失一般性,目标变成

因为‖ w> 0 ,这相当于

上面的约束是理想的,表示一个完美的预测。但是类并不总是可以用判定边界完全分离的,所以我们允许一些观察值与它们正确的判定边界相距 ξᵢ 。超参数 C 控制这种惩罚的强度,并且因此充当逆正则化参数。最终目标是

就是这样!SVM 试图通过最小化参数 w 的长度来最大化余量。

回归

用于回归的 SVM 可以直接从分类中采用。我们不是希望 yᵢ ( wᵀXᵢ + b )尽可能大,而是希望|yᵢ-(wᵀxᵢ+b)|尽可能小,即误差 ε 尽可能小。再次利用逆正则化,我们有以下回归目标

回归作者 SVM |图片作者作者

核函数

到目前为止,在我们预测输出数据 y 的方式中,我们只使用了输入数据 X 中特征的线性组合。对于逻辑回归和 SVM,这导致了线性决策边界。因此,对于不可线性分离的输入数据,该模型的性能很差。但是有一种方法可以改进。

我们可以通过映射ϕ:ℝ→ℝ将输入数据映射到高阶特征空间。在这个更高的维度中,通过正确的映射,数据有可能是可分离的。然后,我们通过我们的模型使用这些映射的数据。

训练数据被映射到一个 3 维空间,在那里可以容易地找到一个分离的判定边界。|图片出自纪——自己的作品

但是我们还有另一个问题。SVM 和许多其他机器学习模型可以用点积来表示,在高维映射数据中求解点积是非常昂贵的。幸运的是,我们有锦囊妙计:内核技巧https://en.wikipedia.org/wiki/Kernel_method

50×50 网格实数的核值可视化。我会把它留在这里,因为它看起来很美|图片作者作者

K 是一个相似度函数。它是由我们映射数据的点积定义的。所以,如果我们有两个观察值 xy 在ℝ

通过内核技巧,我们可以在不访问高阶特征空间ℝ 的情况下计算这个方程,见鬼,我们甚至不需要知道映射 ϕ 。因此,在我们的模型中计算点积不再昂贵。

一些常见的内核是:

非线性核允许模型学习更复杂的决策边界。核的所有参数都是模型的超参数。如果您将不同的核应用于我们用于分类的先前数据集,您将获得以下决策边界:

不同内核的 SVM 分类|图片作者作者

SVM 在高维空间和特征数量大于观察数量的情况下是有效的。许多不同的内核可供选择(或自己制作),这使得 SVM 功能多样。

然而,如果特征的数量远大于观察的数量,在选择核和正则项时避免过度拟合是至关重要的。

关于预处理的一个注记

总是有这样一个问题:“在将输入数据 X 输入到模型之前,我应该标准化/规范化它吗?”。讽刺的是,最满意的答案是“看情况”。

如果您的模型使用一元解析解,即 正规方程 进行线性回归,那么不需要进行标准化/规范化。你知道为什么吗?

假设你有一个比其他特征大几千倍的特征,而你没有标准化/规范化它。假设法线方程将该特定特征的系数设置为 β 。如果先标准化/归一化,正规方程会产生一个系数是 β 的几千倍。因此,特征的缩放和产生的系数相互抵消,模型随后给出相同的预测。

另一方面,如果您的模型使用迭代方法,即 梯度下降 而不是标准方程,那么您希望首先标准化/规范化您的输入数据以加快收敛。

数据标准化对梯度下降算法的影响。标准化数据导致更快的收敛|图片作者作者

另一个问题与模型扩展(非线性内核、正则化等)有关。例如,学习算法的成本函数中使用的许多元素(例如 SVM 的 RBF 核或者线性模型L1 和 L2 正则化子)假设所有特征都以零为中心,并且具有相同顺序的方差。如果某个要素的方差比其他要素的方差大几个数量级,则它可能会在成本函数中占主导地位,并使估计者无法像预期的那样从其他要素中正确学习。因此,你需要来标准化。

结论

你已经非常详细地学习了三个最基本的机器学习模型:线性回归逻辑回归SVM 。现在,您不仅可以使用已建立的库来构建它,还可以自信地知道它们是如何从内到外工作的,使用它们的最佳实践,以及如何提高它们的性能。

恭喜你。

Mukuko 工作室在 Unsplash 拍摄的照片

以下是一些关键要点:

  1. 线性回归有利于回归任务中的基线
  2. 正则化用于解决过拟合,有三种方法可用: L1、L2 或两者都用
  3. 尽管有他的名字,逻辑回归是用于分类任务的。它在两个类之间找到一个线性判定边界
  4. 直观地,如果决策边界具有到任何类别的最近训练数据点的最大距离,则逻辑回归给出较低的泛化误差。这正是 SVM 所做的。
  5. 得益于内核函数,SVM 还通过允许非线性决策边界有效地将逻辑回归推向了一个新的高度。
  6. SVM 适用于高维空间以及特征数量大于观察数量的情况。
  7. SVM 的 RBF 核或线性模型的 L1 和 L2 正则化子假设标准化输入数据。

希望你学点东西:)

🔥你好!如果你喜欢这个故事,想支持我这个作家,可以考虑 成为会员 。每月只需 5 美元,你就可以无限制地阅读媒体上的所有报道。如果你注册使用我的链接,我会赚一小笔佣金。

🔖想了解更多关于经典机器学习模型如何工作以及如何优化其参数的信息?或者 MLOps 大型项目的例子?有史以来最优秀的文章呢?继续阅读:**

*Albers Uzila

艾伯斯·乌兹拉*

从零开始的机器学习

*View list8 stories**Albers Uzila

艾伯斯·乌兹拉*

高级优化方法

*View list7 stories**Albers Uzila

艾伯斯·乌兹拉*

MLOps 大型项目

*View list6 stories**Albers Uzila

艾伯斯·乌兹拉*

我最好的故事

*View list24 stories**Albers Uzila

艾伯斯·乌兹拉*

R 中的数据科学

View list7 stories

了解相似性传播聚类和 Python 实现

原文:https://towardsdatascience.com/understanding-affinity-propagation-clustering-and-implementation-with-python-c1e78fe57cde

无监督学习

最常用的聚类方法之一,相似性传播,连同 Python 的实现一起被清楚地解释

由卢卡在 Unsplash 上拍摄的照片

A 模糊传播是数据科学中使用最广泛的聚类方法之一。它既没有对聚类形状的假设,也不需要聚类的数量作为输入。相似性传播的另一个优点是它不依赖于初始聚类质心选择的任何运气。

在这篇文章中,我将详细介绍如何理解和使用 Python 中的仿射传播。

亲和传播的主要思想

我们来想想社会上大多数人的行为。

一开始,你谁都不认识,所以你是你自己的代表。然后,在你认识更多的人之后,在一些交流之后,你发现有些人和你有相似的价值观,有些人很难相处。所以,你倾向于和那些和你相似的人有更多的互动,避免和那些在你眼里“怪异”的人混在一起。随着沟通次数的增加,你终于找到了你的“小组”,你的脑海中出现了一个可以完美代表你的小组的候选人。

其实我所描述的正是亲和传播的 基本思路

该算法使用数据点之间的“通信”来为每个数据点寻找“样本”。并且共享相同“样本”的数据点被分配到相同的聚类(组)。

尽管算法的想法很简单,但在上面的描述中仍然有一些令人困惑的地方。你可能已经注意到,最大的缺失部分是那些 【沟通】 是如何执行的。

让我们使用与上面相同的例子。“沟通”实际上是你和你心目中的团体代表候选人之间传递的信息。如果你发现迈克尔是你的最佳候选人,你可以发短信给他,“迈克尔,我认为你应该是代表我的人。”

然后,也许迈克尔会回答:“我很感激。我很乐意这么做!”

或者,迈克尔会回答:“谢谢你信任我。然而,考虑到所有其他人都有同样的要求,我没有足够的带宽向您介绍。抱歉。”

然后,根据迈克尔的回答,你将调整你心目中的代表候选人的排名,并再次向他们发送消息。之后,迈克尔将再次收到所有人(包括你)发来的信息,说他们多么渴望迈克尔成为他们的代表。然后他会从那里开始调整…

我描述的过程正是仿射传播中的迭代。你发送给迈克尔的信息在算法中被称为 【责任】 ,这表明迈克尔非常适合作为你的样本,同时考虑到你的其他潜在样本。

迈克尔发回的信息被称为 【可用性】 ,它反映了考虑到其他人对迈克尔应该成为榜样的支持,你选择迈克尔作为榜样是多么合适。

那么,通信应该在什么时候停止呢?当范例和他们所代表的人群没有大的变化时,就是这样。

该算法

在深入研究算法之前,我们需要理解几个矩阵。

第一个是相似度矩阵, S ,是数据点之间相似度的集合,其中相似度 s(i,k) 表示数据点 k 与数据点 i 的相似度。

直观上,相似度值越大,两个数据点之间的距离越小。因此,传统上,负欧几里德距离用于定义相似性。这里, s(i,k) ,点 i 与点 k 的相似性,是

s(i,k)= | | Xi xk | |

需要注意的是,相似度矩阵的对角线值【s(k,k)】在相似性传播中具有特定的含义和用法,而不是简单地被赋值为零。该设计是具有较大的值【k,k】的数据点更有可能被选为样本。

想象一下,所有的 s(k,k) 值都超级大。这意味着每个点肯定是自己的样本,其中每个数据点都是自己的聚类;然而,如果所有的【s(k,k) 的值都非常小,那么所有的数据点都不能作为自己的样本,所有的数据点都属于同一个大的聚类。

我们可以看到,相似度矩阵对角线上的值可以影响最终检测到的聚类数,因此命名为“偏好”。我们知道上述两种情况都不理想,所以我们必须为首选项初始化好的值。

因为我们在开始时不知道数据点的偏好值,所以我们必须假设所有的数据点作为样本是同样可能的。因此,应该将首选项设置为一个公共值。根据经验,人们通常使用输入相似度的中值作为偏好值。

第二个矩阵是责任矩阵, R ,其中的R(I,k) ,反映的是如何很好地将点 k 作为点 i 的样本,同时考虑到点 i 的其他潜在样本。

第三个矩阵是可用性矩阵, A ,其中A(I,k) ,反映了点【I选择点 k 作为其范例是多么合适,同时考虑了其他点对该点k**

该算法将 RA 初始化为零矩阵,然后迭代更新 RA

R 使用以下等式更新。

作者图片

这个等式的构造相对简单。在第一次迭代时(记住 A 被设置为零矩阵), r(i,k) 仅仅是点 i 和点**k之间的相似性减去点I*之间的最大相似性*********

在以后的迭代中,如果一个点具有比 i 更高的分数作为其他点的范例(比调查点更类似于其他点, i ),则该点的()可用性值将减少。在这种情况下,通过减小 max{a(i,k’)+s(I,k’)}的值,会对【I,k’)产生负面影响,然后 r(i,k) 会得到加强,因为点k’相当于点 i

因此,这个分数, r(i,k) 将显示出 k 与所有其他点相比是如何突出地成为的范例。

要注意, r(i,i) 叫做自我负责,这表明点 i 是一个典范的可能性有多大。

下面是 A 的更新方式。

作者图片

作者图片

k 成为 i 的样例( a(i,k) )的可用性被设置为点 k 是样例( r(k,k) )加上外部最小设置试图将 a(i,k) 限制为零,以避免超大的正责任值。

自我可用性( a(k,k) )就是从所有其他点接收到的积极责任点 k 的总和。

AR 在仿射传播算法中通过迭代不断更新,直到在固定次数的迭代后没有变化。

A 更新给定 RR 更新给定 A 。听起来很像吧?

是的,该算法非常类似于 EM(期望最大化)算法。如果你对 EM 算法感兴趣,你可以在这里阅读我以前的一篇帖子。

另一点要注意的是,阻尼因子总是用来避免迭代中的数值振荡。具体地,矩阵中的新值被定义为,

val_new =(丹皮事实)(val _ old)+(1-丹皮事实)(val_new)

当算法停止时,根据 RA 之和做出最终决定。然后,每行的最大值的对应列名就是该行的样本。共享同一样本的行被分配到同一簇。

比如我们用一个人工矩阵作为下面的 RA 的和矩阵,

A + R(图片由作者提供)

每行的最大值突出显示,对应的列是样本,其中是这个数据集中的 Jim 和 Clark。迈克尔、约翰、吉姆和亚历克斯属于吉姆组,而克拉克和布朗属于克拉克组。

履行

在 Python 中,算法是在 sklearn 包中实现的。我在下面列出了如何使用它,

***from** **sklearn.cluster** **import** AffinityPropagation
clustering = AffinityPropagation(random_state=25).fit(X)
clustering.labels_*

就是这样!相似性传播算法。很简单,对吧?

如果你喜欢阅读我的帖子,请订阅我的电子邮件列表或成为媒体的推荐会员(我会得到你的部分费用作为我写作的支持)。

照片由卡迈恩·萨瓦雷斯在 Unsplash 上拍摄

参考资料:

弗雷,布兰登 j .和德尔伯特.杜克。"通过在数据点之间传递消息进行聚类."科学 315.5814(2007):972–976。

了解双重稳健估计量 AIPW

原文:https://towardsdatascience.com/understanding-aipw-ed4097dab27a

因果数据科学

模型错误设定下条件平均治疗效果(CATE)估计指南

封面,图片来自我的好朋友基亚拉·艾纳

在估计因果关系时,黄金标准是随机对照试验或 AB 测试。通过随机将单位暴露于治疗,我们确保两组中的个体在平均水平上是可比较的,并且我们观察到的任何差异都可以单独归因于治疗效果。

然而,通常治疗组和对照组并不完全可比。这可能是由于随机化不完善或不可用的事实。出于伦理或实践的原因,我们并不总是能够随机选择治疗方法。即使我们可以,有时我们也没有足够的个人或单位来捕捉群体间的差异。这种情况经常发生,例如,当随机化不是在个体水平上进行,而是在更高的聚集水平上进行时,例如,邮政编码、县甚至州。

在之前的帖子中,我介绍并比较了一系列从观察或实验数据中计算条件平均治疗效果(CATE) 的方法。在给定治疗和可观察特征的情况下,这些方法中的一些要求研究者指定和估计感兴趣的结果的分布(例如元学习者)。其他方法要求研究人员根据可观察的特征(例如 IPW )指定和估计被治疗的概率。

在这篇文章中,我们将看到一个程序,它结合了两种方法,并且对两种模型的错误设定都是鲁棒的(T21):增强逆概率加权估计(AIPW)。

融合,gif 由作者剪切

TLDR; AIPW 是 IPW 和元学习者两者的概括,比他们每一个都好用!

例子

假设我们有一个关于统计和因果推理的博客😇。为了改善用户体验,我们正在考虑发布一个黑暗模式,我们想了解这个新功能是否会增加用户在我们博客上花费的时间。

这个例子是从我上一篇关于估计条件平均治疗效果(CATE)的文章中借用的。你可以在这里找到原帖。如果你记得设置,你可以跳过这个介绍。

不使用和使用黑暗模式的博客外观,由作者提供的图像

我们不是一家成熟的公司,因此我们不进行 AB 测试,而是简单地发布黑暗模式,我们观察用户是否选择它以及他们在博客上花费的时间。我们知道可能会有选择:偏好黑暗模式的用户可能会有不同的阅读偏好,这可能会使我们的因果分析变得复杂。

我们可以用下面的 有向无环图(DAG) 来表示数据生成过程。

数据生成过程的 DAG,按作者排序的图像

我们使用来自[src.dgp](https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/src/dgp.py)的数据生成过程dgp_darkmode()生成模拟数据。我还从[src.utils](https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/src/utils.py)引进了一些绘图函数和库。

from src.utils import *
from src.dgp import dgp_darkmodedgp = dgp_darkmode()
df = dgp.generate_data()
df.head()

数据快照,图片由作者提供

我们有 300 名用户的信息,我们观察他们是否选择了dark_mode(治疗)、他们每周的read_time(感兴趣的结果)以及一些特征,如genderage和以前在博客上花费的总hours

我们想估计一下新的dark_mode对用户read_time的影响。作为第一种方法,我们可能天真地将效果计算为平均值的差异,假设治疗和对照样本是可比的。我们可以通过在dark_mode上回归read_time来估计均值的差异。

smf.ols("read_time ~ dark_mode", data=df).fit().summary().tables[1]

线性回归结果,图片由作者提供

选择dark_mode的人平均每周花在博客上的时间少了 0.44 小时。我们应该断定dark_mode是一个坏主意吗?这是因果关系吗?

问题是我们做了而不是运行 AB 测试 或随机对照试验,因此选择了dark_mode的用户可能无法与没有选择的用户直接比较。我们能证实这种担忧吗?部分地。在我们的设置中,我们只能检查我们观察到的特征、genderage和总计hours。我们无法检查用户是否在我们没有观察到的其他维度上存在差异。

让我们使用优步[causalml](https://causalml.readthedocs.io/)包中的create_table_one函数来生成一个协变量平衡表,包含我们在治疗组和对照组中可观察特征的平均值。顾名思义,这应该永远是你在因果推断分析中呈现的第一张表。

from causalml.match import create_table_oneX = ['male', 'age', 'hours']
create_table_one(df, 'dark_mode', X)

平衡表,作者图片

治疗组(dark_mode)和对照组之间似乎有一些差异。特别是,选择dark_mode的用户年龄较大,花在博客上的时间较少,他们更有可能是男性。

我们能做什么?如果我们假设治疗组和对照组之间的所有差异都是可观察的,我们可以通过执行条件分析来解决问题。

条件分析

我们假设对于一组主题 i = 1,…,n ,我们观察到一个元组 (Dᵢ,Yᵢ,Xᵢ) 包括

  • 一个治疗分配 Dᵢ ∈ {0,1} ( dark_mode)
  • 一个回应 Yᵢ ∈ ℝ ( read_time)
  • 一个特征向量 Xᵢ ∈ ℝⁿ ( genderagehours)

我们感兴趣的是估计条件平均治疗效果(CATE)

条件平均治疗效果(CATE),图片由作者提供

其中 Yᵢ ⁽ᵈ⁾表示个体 i 在治疗状态 d 下的潜在结果。我们还做了以下假设。

假设 1:未发现(或可忽略,或可观的选择)

无根据假设,作者的图像

即以可观察的特征 X 为条件,治疗分配 D 几乎是随机的。我们实际假设的是,没有其他我们没有观察到的特征会影响用户是否选择dark_mode和他们的read_time。这是一个强有力的假设,我们观察到的个人特征越多,这个假设就越有可能得到满足。

假设 2:重叠(或共同支撑)

重叠假设,作者图片

即没有观察结果被确定地分配给治疗组或对照组。这是一个更技术性的假设,基本上意味着对于任何级别的genderagehours,都可能存在选择dark_mode的个体和不选择dark_mode的个体。与未发现假设不同,总体假设是可检验的

假设 3:稳定单位治疗值(SUTVA)

稳定单位治疗值假设(SUTVA),作者图片

即潜在的结果不取决于治疗状态。在我们的例子中,我们排除了另一个用户选择dark_mode可能会影响我的dark_moderead_time的影响。违反 SUTVA 的最常见的情况是存在网络效应:如果我的一个朋友使用一个社交网络,会增加我使用它的效用。

IPW 和元学习者

执行条件分析的两种替代方法是

这两个替代程序利用了我们以不同方式观察个体特征 X 的事实:

  1. IPW 利用 X 来预测治疗分配 D 并估计倾向得分e(x)=𝔼[d|x】
  2. 元学习者利用 X 来预测反事实结果 Y ⁽ᵈ⁾并估计响应函数μ⁽ᵈ⁾(x)=𝔼[y|d,x】

我们能不能把这两个过程结合起来,从而两全其美呢?

是的,使用 AIPW 或双稳健估计器

AIPW 估计量

增强的反向倾向加权估计量由下式给出

AIPW 估算器,作者图片

其中 μ ⁽ᵈ⁾ (x)反应函数,即结果的期望值,以可观察特征 x 和治疗状态 d 为条件, e(x)倾向得分

响应函数和倾向得分,按作者分类的图像

AIPW 估计量的公式乍一看似乎非常神秘,所以让我们深入研究并试图理解它。

分解

理解 AIPW 公式的最好方法是分解成两部分。

第一种方式是将 AIPW 估计器分解成一个 S-learner 估计器 和一个调整因子。

第一次 AIPW 分解,图片作者

在哪里

第一次 AIPW 分解的详细信息,图片由作者提供

这种调整实质上是对 S 学习器的残差执行的 IPW 估计器。

第二种方式将 AIPW 估算器分解成 IPW 估算器和一个调整因子。

第二次 AIPW 分解,作者图片

在哪里

第二次 AIPW 分解的详细信息,图片由作者提供

该调整本质上是一个由剩余治疗概率加权的 S-学习器估计器。

双重鲁棒性

为什么 AIPW 估算器如此引人注目?原因是它只需要两个预测 μ̂ 中的一个是正确的,以便无偏(即平均正确)。让我们检查一下。

如果 μ̂ 指定正确,即𝔼[μ̂⁽ᵈ⁾(x)**=𝔼[yᵢ| xᵢ=x,Dᵢ=d ,那么τ\̘aipw 是无偏的,即使e\792;拼写错误。

倾向分数错误设定下的 AIPW 无偏性,作者图片

直觉是,如果 μ̂ 被正确指定, τ̂ S-learn 是无偏的并且调整因子消失,因为残差(yᵢ−μ̂【⁽ᵈ⁾(xᵢ)收敛到零。

另一方面,如果 被正确指定,即𝔼[ê(x)]= 𝔼[dᵢ=1 | xᵢ=x,那么 τ̂ AIPW 是无偏的,即使 μ̂ 被错误指定。

响应函数错误设定下的 AIPW 无偏性,图片由作者提供

直觉是,如果 被正确指定,τ̂ipw无偏,并且调整因子消失,因为残差 (Dᵢ−ê (Xᵢ)) 收敛到零。

最佳实践

下面是实现 AIPW 估计器时的最佳实践列表(有些更通用)。

1。检查协变量平衡

IPW 和 AIPW 都是为治疗 D 不是无条件随机分配,而是可能依赖于一些可观察的 X 的设置而构建的。可以通过两种方式检查该信息:

  1. 制作平衡表,总结各治疗组的协变量。如果无条件随机化不成立,我们期望在一些可观察的方面看到显著的差异
  2. 画出估计的倾向分数。如果无条件随机化成立,我们期望倾向得分是常数

2。检查重叠假设

我们可以检查的另一个假设是重叠假设。为了检验这个假设,我们可以简单地检验预测倾向得分的界限。如果重叠假设被违反,我们最终将估计量的某些项除以零。

3。使用十字接头

每当我们建立一个预测时,当估计响应函数 μ̂ ⁽ᵈ⁾ (Xᵢ) 或倾向得分ê(xᵢ时,最好的做法是排除观察值 i 。这个程序在机器学习文献中一般称为 交叉拟合 。虽然有许多可能的方法来执行交叉拟合,但最简单的方法如下:

  1. 将样本随机分成两份
  2. 用样本 1 估计 μ̂ ⁽ᵈ⁾ (x)ê(x
  3. 使用样本 2 估计 τ̂₁ AIPW
  4. 重复(2)和(3)交换样本以估计 τ̂₂ AIPW
  5. 计算 τ̂ AIPW 作为两个估计值的平均值

步骤(2)和(3)确保估计器是而不是过拟合。步骤(4)和(5)确保估计器是有效的,使用所有步骤的所有数据,而不仅仅是一半。 Kennedy (2022) 表明这种方法比现有方法产生更精确的估计,并提供关于误差界限的正式结果。特别是,他们的主要结果如下:

定理 2 中给出的 DR-Learner 误差的界限表明,它最多只能偏离 oracle 误差一个(平滑的)倾向得分和回归估计量的误差乘积,因此即使当 NUS ance 估计以较慢的速率收敛时,也允许以较快的速率估计 CATE。重要的是,结果与所用的方法无关,不需要特殊的调整或不必要的修饰。

回到数据

现在,让我们在博客阅读时间和黑暗模式的数据集中构建和探索 AIPW 估计器。

倾向得分

首先,我们来估计一下倾向得分 e(X)

def estimate_e(df, X, D, model_e):e = model_e.fit(df[X], df[D]).predict_proba(df[X])[:,1]return e

我们使用sklearn包中的LogisticRegression方法,通过逻辑回归来估计它们。

from sklearn.linear_model import LogisticRegressiondf['e'] = estimate_e(df, X, "dark_mode", LogisticRegression())

让我们通过绘制治疗组和对照组的估计倾向得分来检查是否满足有界支持假设。

sns.histplot(data=df, x='e', hue='dark_mode', bins=30, stat='density', common_norm=False).\set(ylabel="", title="Distribution of Propensity Scores");

倾向得分的分布在两组之间是不同的,但它通常是重叠的。

我们现在可以使用倾向分数来构建 IPW 估计量。

w = 1 / (e * df["dark_mode"] + (1-e) * (1-df["dark_mode"]))
smf.wls("read_time ~ dark_mode", weights=w, data=df).fit().summary().tables[1]

IPW 回归结果,图片由作者提供

请注意,计算出的标准误差并不精确,因为我们忽略了来自倾向得分估计的额外不确定性 e(x)

响应函数

现在让我们来估计 AIPW 估计器的第二个构件:响应函数 μ(x)

def estimate_mu(df, X, D, y, model_mu):mu = model_mu.fit(df[X + [D]], df[y])mu0 = mu.predict(df[X + [D]].assign(dark_mode=0))mu1 = mu.predict(df[X + [D]].assign(dark_mode=1))return mu0, mu1

先用线性回归估计 μ(x) 吧。

from sklearn.linear_model import LinearRegressionmu0, mu1 = estimate_mu(df, X, "dark_mode", "read_time", LinearRegression())
print(np.mean(mu1-mu0))1.3858099131476969

我们已经将平均治疗效果的元学习者估计值计算为两个估计响应函数 μ ⁽ ⁾ (x)μ ⁽⁰⁾ (x) 之间的均值差。

注意我们可以使用任何估计量来获得响应函数,为了简单起见,我使用了线性回归。

估计 AIPW

我们现在有了计算 AIPW 估计量的所有构件

aipw = mu1 - mu0 + df["dark_mode"] / e * (df["read_time"] - mu1) - (1-df["dark_mode"]) / (1-e) * (df["read_time"] - mu0)
print(np.mean(aipw))1.3153774511905783

我们也可以使用微软[EconML](https://econml.azurewebsites.net/index.html)库中的LinearDRLearner函数直接计算。

from econml.drlearner import LinearDRLearnermodel = LinearDRLearner(model_propensity=LogisticRegression(), model_regression=LinearRegression(),random_state=1)
model.fit(Y=df["read_time"], T=df["dark_mode"], X=df[X]);

模型直接给我们平均治疗效果。

model.ate_inference(X=df[X].values, T0=0, T1=1).summary().tables[0]

AIPW 评估结果,图片由作者提供

该估计在统计上不同于零,并且置信区间包括真值 2。

注意我们得到了一个不同的估计,因为LinearDRLearner函数也在后台执行交叉拟合,这是我们以前没有做过的。

评价

现在让我们来评估 AIPW 估计器的主要特性:它的双重鲁棒性。为此,我们将它与它的两个双亲:IPW 估计器和 S 学习器进行比较。

我们使用[joblib](https://joblib.readthedocs.io/en/latest/)库来并行运行模拟,并加快进程。

首先,假设我们对两个模型都使用所有变量,μ(x)e(x) 。在这种情况下,两个模型都是良好指定的,我们期望所有的估计器都表现良好。

我们绘制了 100 次模拟中三个估计量的分布。

simulate_estimators(X_e=X, X_mu=X, D="dark_mode", y="read_time")

事实上,所有的估计都是无偏的,并且给出非常相似的估计。

如果我们错误地指定了两个模型中的一个会怎么样?让我们首先(正确地)假设genderage影响选择dark_mode的概率,并(错误地)假设只有先前的hours影响每周的read_time。在这种情况下,倾向得分 e(x) 被很好地指定,而响应函数 μ(x) 被错误地指定。

simulate_estimators(X_e=['male', 'age'], X_mu=['hours'], D="dark_mode", y="read_time")

不出所料,S-learner 有偏见,因为我们拼错了 μ(x) ,而 IPW 没有。AIPW 选择两个世界中最好的并且不偏不倚。

现在让我们来探究另一种错误设定。我们(错误地)假设只有age影响选择dark_mode的概率,并且(正确地)假设gender和之前的hours都影响每周的read_time。在这种情况下,倾向得分 e(X) 被错误指定,而响应函数 μ(x) 被正确指定。

simulate_estimators(['age'], ['male', 'hours'], D="dark_mode", y="read_time")

在这种情况下,S-learner 是无偏的,而 IPW 不是,因为我们错误地指定了 e(X) 。同样,AIPW 选择了两个世界中最好的并且没有偏见。****

结论

在本文中,我们已经看到了一种估计条件平均治疗效果(CATE)的方法,即对模型设定错误具有鲁棒性的方法:增强型反向倾向加权(AIPW)估计器。AIPW 估计器从现有的两种估计器中取长补短:IPW 估计器和 S 学习器。它需要估计倾向得分函数𝔼[D |X 和响应函数𝔼[ Y|X,D ,并且它是无偏的,即使两个函数中的一个被错误指定。

这个估计器现在是一个标准,它包含了所有最重要的因果推理包,如微软的 EconML ,优步的 causalml 和斯坦福研究人员的 R 包 grf 。

参考

[1] J. Robins,A. Rotzniski,J. P. Zhao,某些回归变量不总是被观测时回归系数的估计 (1994),美国统计协会杂志**

[2] A. Glyn,K. Quinn,增广逆倾向加权估计量介绍 (2010),政治分析**

[3] E. Kennedy,对异质因果效应的最优双重稳健估计 (2022),工作论文**

相关文章

  • Dag 和控制变量
  • 匹配、加权还是回归?
  • 了解元学习者

密码

你可以在这里找到 Jupyter 的原始笔记本:

****https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/aipw.ipynb ****

感谢您的阅读!

我真的很感激!🤗如果你喜欢这个帖子并想看更多,可以考虑 关注我 。我每周发布一次与因果推断和数据分析相关的主题。我尽量让我的帖子简单而精确,总是提供代码、例子和模拟。

还有,一个小小的 免责声明 :我写作是为了学习所以出错是家常便饭,尽管我尽了最大努力。当你发现他们的时候,请告诉我。也很欣赏新话题的建议!

理解和实现更快的 R-CNN:一步一步的指南

原文:https://towardsdatascience.com/understanding-and-implementing-faster-r-cnn-a-step-by-step-guide-11acfff216b0

计算机视觉和目标检测

揭开对象检测的神秘面纱

作者图片

我第一次接触对象检测是通过 Tensorflow 对象检测 API。它使用起来很简单。我传入一个海滩的图像,作为回报,API 在它识别的对象上画出方框。这似乎很神奇。我变得好奇,想要剖析 API,以了解它在幕后真正是如何工作的。很难,我失败了。Tensorflow 对象检测 API 支持最先进的模型,这些模型是数十年研究的成果。它们被错综复杂地编织成代码,就像钟表匠如何将连贯运动的微小齿轮组装在一起。

然而,大多数当前最先进的模型都建立在 fast-RCNN 模型奠定的基础之上,即使在今天,它仍然是计算机视觉中被引用最多的论文之一。因此理解它是至关重要的。

在本文中,我们将分解 fast-RCNN 文件,理解其工作原理,并在 PyTorch 中一部分一部分地构建它,以理解其中的细微差别。

更快的 R-CNN 概述

更快的 R-CNN 整体架构

对于物体检测,我们需要建立一个模型,并教它学会识别图像中的物体。更快的 R-CNN 模型采取以下方法:图像首先通过主干网络得到输出特征图,图像的地面真实边界框得到投影到特征图上。骨干网络通常是像 ResNet 或 VGG16 这样的密集卷积网络。输出特征图是表示图像的学习特征的空间密集张量。接下来,我们将这个特征图上的每个点视为一个锚点。对于每个锚点,我们生成多个不同大小和形状的盒子。这些锚定框的目的是捕捉图像中的对象。

我们使用一个1x1卷积网络来预测所有锚盒的类别偏移。在训练期间,我们对与投影的地面真实框重叠最多的锚框进行采样。这些被称为正向激活锚盒。我们还对锚盒进行采样,这些锚盒与地面真值盒几乎没有重叠。正锚框被分配类别object,而负锚框被分配background。网络学习使用二进制交叉熵损失来分类锚盒。现在,正锚盒可能不会与投影的地面真相盒完全对齐。因此,我们训练一个类似的1x1卷积网络来学习预测来自地面真相盒的偏移。这些偏移应用于锚盒时,使它们更接近地面真实盒。我们使用 L2 回归损失来学习补偿。使用预测的偏移来变换锚盒,并将其称为区域提议,并且上述网络被称为区域提议网络。这是探测器的第一阶段。fast-RCNN 是一个两级检测器。还有一个阶段。

第二阶段的输入是第一阶段产生的区域提案。在第二阶段,我们学习使用简单的卷积网络来预测区域提议中的对象的类别。现在,原始区域建议具有不同的大小,因此我们使用一种称为 ROI pooling 的技术在通过网络之前调整它们的大小。这个网络学习使用交叉熵损失来预测多个类别。我们使用另一个网络来预测来自地面实况箱的区域提议的偏移。该网络还试图将地区提案与实地真相框联系起来。这使用 L2 回归损失。最后,我们对两种损失进行加权组合,以计算最终损失。在第二阶段,我们学习预测类别和偏移量。这就是所谓的多任务学习。

所有这些都发生在训练中。在推断过程中,我们通过主干网络传递图像并生成锚盒——与之前相同。但是,这一次我们只选择在第 1 阶段获得高分类分数的前 300 个盒子,并使它们有资格进入第 2 阶段。在第二阶段,我们预测最终的类别和偏移量。此外,我们使用一种叫做非最大值抑制的技术执行一个额外的后处理步骤来移除重复的边界框。如果一切正常,检测器会识别图像中的对象并在其上绘制方框,如下所示:

这是两级快速 RCNN 网络的简要概述。在接下来的几节中,我们将深入探讨每一部分。

设置环境

所有用到的代码都可以在这个 GitHub 资源库中找到。我们不需要太多的依赖,因为我们将从头开始构建。仅仅安装在标准 anaconda 环境中的 PyTorch 库就足够了。

这是我们将要使用的主要笔记本。只是浏览一下。我们将在接下来的几节中逐步介绍它。

准备和加载数据

首先,我们需要一些样本图像来处理。在这里,我从这里下载了两张高分辨率图像。

接下来,我们需要标记这些图像。CVAT 是最受欢迎的开源标签工具之一。你可以从这里免费下载。

您可以简单地将图像加载到工具中,在相关对象周围画出方框,并标记它们的类别,如下所示:

CVAT 的标签

完成后,您可以将注释导出为更好的格式。在这里,我将它们导出为CVAT for images 1.1 xml 格式。

注释文件包含有关图像、带标签的类和边界框坐标的所有信息。

PyTorch 数据集和数据加载器

在 PyTorch 中,创建一个从 PyTorch 的Dataset类继承的类来加载数据被认为是最佳实践。这将使我们对数据有更多的控制,并有助于保持代码的模块化。此外,我们可以从数据集实例创建一个 PyTorch DataLoader,它可以自动处理数据的批处理、混排和采样。

在上面的类中,我们定义了一个名为get_data的函数,它加载注释文件并解析它以提取图像路径、标签类和边界框坐标,然后将它们转换为 PyTorch 的Tensor对象。图像被调整到固定的大小。

注意我们填充了边界框。这与resize相结合,使我们能够将图像批量处理在一起。

填充的工作原理

如果一批图像中有两个以上的图像,并且每个图像中的对象数量都不相同,那么我们会考虑任何图像中对象的最大数量,并使用-1 填充其余的图像,以匹配最大长度,如上图所示。我们填充边界框坐标以及类别。

我们可以从数据加载器中抓取一些图像,并将其可视化,如下所示:

我们将使用的示例图像

穿过主干网络

这里我们将使用 ResNet 50 作为主干网络。记住,ResNet 50 中的单个块是由瓶颈层的堆栈组成的。沿着空间维度,在每个块之后,图像缩小一半,而通道的数量增加一倍。瓶颈层由三个卷积层和一个跳跃连接组成,如下所示:

ResNet 50 中的瓶颈层。图片来自论文用于图像识别的深度残差学习

我们将使用 ResNet 50 的前四个块作为主干网络。

使用 ResNet 50 的前 4 个块作为卷积骨干网

一旦图像通过主干网络,它就会在空间维度上被向下采样。输出是图像的丰富特征表示。

在将两个图像通过主干后,可视化它们的输出特征图。

如果我们通过主干网络传递大小为(640, 480)的图像,我们会得到大小为(15, 20)的输出特征图。所以图像被(32, 32)缩小了。

生成锚点

我们将特征图中的每个点视为一个锚点。所以定位点只是一个表示宽度和高度坐标的数组。

生成锚点

为了可视化这些锚点,我们可以简单地通过乘以宽度和高度比例因子将它们投影到图像空间。

将锚点投影到图像空间

可视化原始图像上的定位点

生成锚盒

对于每个锚点,我们生成九个不同形状和大小的边界框。我们选择这些盒子的大小和形状,使它们包含图像中的所有对象。锚定框的选择通常取决于数据集。

从 3 个比例和 3 个纵横比的组合中为每个锚点生成 9 个锚点框。

从比例和纵横比的组合中生成锚定框

调整图像大小的另一个好处是锚定框可以在所有图像中重复。

重复所有图像的锚定框,因为它们的大小相同

同样,为了可视化锚定框,我们通过乘以宽度和高度比例因子将它们投影到图像空间。

针对两幅图像的单个定位点,在图像空间中可视化定位框

如果我们将所有锚点的所有锚点框可视化,看起来会是这样:

为两幅图像的图像空间中的所有定位点可视化定位框

数据准备

在本节中,我们将讨论培训的数据准备。

正负锚盒

我们只需要抽样几个锚箱进行训练。我们对正反锚盒都进行了采样。正锚定框包含对象,负锚定框不包含对象。为了对正锚盒进行采样,我们选择与任何基础真值盒具有大于 0.7 的 IoU 的锚盒,或者对于每个基础真值盒具有最高 IoU 的锚盒。当锚盒生成不良时,条件 1 失败,所以条件 2 来拯救,因为它为每个地面真值盒选择一个正盒。为了对负锚盒进行采样,我们选择 IoU 小于 0.3 的锚盒与任何基础真值盒。通常,阴性样本的数量会远远高于阳性样本。所以我们随机抽取一些样本来匹配阳性样本的数量。IoU 是度量两个边界框之间重叠的度量。

上面的函数计算 IoU 矩阵,该矩阵包含图像中每个锚框和所有地面真实框的 IoU。它将形状为(B, w_amap, h_amap, n_anc_boxes, 4)的锚盒和形状为(B, max_objects, 4)的地面真相盒作为输入,并返回形状为(B, anc_boxes_tot, max_objects)的矩阵,其中符号如下:

B - Batch Size
w_amap - width of the output activation map
h_wmap - height of the output activation map
n_anc_boxes - number of anchor boxes per an anchor point
max_objects - max number of objects in a batch of images
anc_boxes_tot - total number of anchor boxes in the image i.e, w_amap * h_amap * n_anc_boxes

该函数实质上展平了所有锚定框,并使用每个基础真值框计算 IoU,如下所示:

IoU 矩阵

投影地面真相盒

重要的是要记住,IoU 是在生成的锚框和投影的地面真值框之间的特征空间中计算的。要将地面真值框投影到特征空间上,我们只需将其坐标除以比例因子,如以下函数所示:

现在,当我们将坐标除以比例因子时,我们将数值四舍五入到最接近的整数。这实质上意味着我们将基础事实框“捕捉”到特征空间中最近的网格。因此,如果图像空间和特征空间的尺度差异很大,我们将无法获得准确的投影。因此,在目标检测中使用高分辨率图像是非常重要的。

计算偏移量

正面锚定框与地面真实框不完全对齐。因此,我们计算正面锚盒和地面真相盒之间的偏移,并训练神经网络来学习这些偏移。失调可以通过下式计算:

tx_ = (gt_cx - anc_cx) / anc_w
ty_ = (gt_cy - anc_cy) / anc_h
tw_ = log(gt_w / anc_w)
th_ = log(gt_h / anc_h)Where:gt_cx, gt_cy - centers of ground truth boxes
anc_cx, anc_cy - centers of anchor boxes
gt_w, gt_h - width and height of ground truth boxes
anc_w, anc_h - width and height of anchor boxes

以下函数可用于计算相同的值:

如果你注意到了,我们正在教导网络去了解锚箱与地面真相箱有多远。我们没有强迫它预测锚盒的确切位置和规模。因此,网络学习的偏移和变换是位置比例不变的。

代码走查

让我们浏览一下数据准备代码。这可能是整个存储库中最重要的功能。

该函数的主要输入是为一批图像生成的锚盒和投影的地面真相盒。

首先,我们使用上述函数计算 IoU 矩阵。然后,从这个矩阵中,我们为每个基础真值框取最重叠的锚框的 IoU。这是对阳性锚盒取样的条件 1。我们还应用条件 2,并选择 IoU 大于图像中任何地面真值框的阈值的锚框。我们组合条件 1 和 2,并对所有图像的阳性锚框进行采样。

说明如何从 IoU 矩阵中抽取+ve 锚

每个图像将具有不同数量的阳性样本。为了避免训练过程中的这种差异,我们将整批图像展平,并将所有图像的阳性样本合并。此外,我们可以使用torch.where跟踪每个阳性样本的来源。

接下来,我们需要计算正样本相对于地面实况的偏移量。为此,我们需要将每个正样本映射到其对应的基础真值盒。值得注意的是,一个正锚定框只能映射到一个基础真值框,而多个正锚定框可以映射到同一个基础真值框。

为了进行这种映射,我们首先使用Tensor.expand扩展基础真相框以匹配总锚框。然后,对于每个锚定框,我们选择与它重叠最多的地面真相框。为此,我们从 IoU 矩阵中获取所有锚盒的最大 IoU 指数,然后使用torch.gather在这些指数处“聚集”。最后,我们将整批样品压平并过滤阳性样品。该过程如下所示:

将每个锚框映射到它与 most 和 filter +ve 样本重叠的地面真实框

我们对类别执行相同的过程,为每个阳性样本分配一个类别。

现在,我们已经为每个正样本映射了一个地面真值框,我们可以使用上述函数来计算偏移。

最后,我们通过对 IoU 小于给定阈值的锚盒进行采样来选择负样本,其中是所有的基础真值盒。由于阴性样本在数量上远远超过阳性样本,我们随机选择了一些样本来匹配计数。

下面是正面和负面锚定框的样子:

+ve 和-ve 锚盒的可视化。绿色:+ve,红色:-ve,黄色:地面实况

我们现在可以使用采样的正锚盒和负锚盒进行训练。

构建模型

提案模块

我们先从求婚模块开始。正如我们所讨论的,特征图中的每个点都被视为一个锚点,每个锚点都会生成不同大小和形状的方框。我们希望将这些盒子分类为objectbackground。此外,我们希望预测它们相对于相应的地面真相框的偏移。我们如何做到这一点?解决方法是使用1x1卷积图层。现在,1x1卷积层不会增加感受野。它们的功能不是学习图像级特征。它们更适合用来改变过滤器的数量,或者作为回归或分类头。

所以我们采用两个1x1卷积层,并使用其中一个将每个锚盒分类为objectbackground。让我们称之为信心头。因此,给定一个大小为(B, C, w_amap, h_amap)的特征映射,我们卷积一个大小为1x1的内核来得到一个大小为(B, n_anc_boxes, w_amap, h_amap)的输出。本质上,每个输出过滤器表示锚盒的分类分数。

提议模块预测所有锚盒的偏移量和分数

以类似的方式,另一个1x1卷积层采用特征图并产生大小为(B, n_anc_boxes * 4, w_amap, h_amap)的输出,其中输出滤波器表示锚盒的预测偏移。这就是所谓的回归头。

在训练期间,我们选择正锚框并应用预测偏移来生成区域提议。区域建议可以计算如下:

从锚盒和偏移计算区域建议

其中上标p表示区域提议,上标a表示锚定框,t表示预测偏移。

以下函数实现上述变换并生成区域建议:

区域提案网络

区域建议网络是检测器的第一级,它获取特征图并产生区域建议。在这里,我们将主干网络、采样模块和建议模块组合成区域建议网络。

区域提案网络

在训练和推断期间,RPN 为所有锚盒产生分数和偏移。然而,在训练期间,我们仅选择正锚盒和负锚盒来计算分类损失。为了计算 L2 回归损失,我们只考虑正样本的偏移量。最终损失是这两种损失的加权组合。

在推断过程中,我们选择得分高于给定阈值的锚框,并使用预测的偏移生成建议。我们使用 sigmoid 函数将原始模型逻辑转换为概率得分。

在两种情况下产生的建议被传递到检测器的第二级。

分类模块

在第二阶段,我们接收区域建议并预测建议中对象的类别。这可以通过一个简单的卷积网络来实现,但是有一个问题:所有的提议并不具有相同的大小。现在,你可能会想到在输入模型之前调整建议的大小,就像我们通常在图像分类任务中调整图像大小一样,但问题是调整大小是而不是一个可微分的操作,因此反向传播不能通过这个操作发生。

这里有一个更聪明的调整大小的方法:我们将提议分成大致相等的子区域,并对每个子区域应用最大池操作,以产生相同大小的输出。这称为投资回报池,如下图所示:

投资回报池

最大池是一个可微分的操作,我们一直在卷积神经网络中使用它们。

我们不需要从头开始实现 ROI pooling,torchvision.ops库为我们提供了它。

一旦使用 ROI 池调整了建议的大小,我们就让它们通过一个卷积神经网络,该网络由一个卷积层、一个平均池层和一个产生类别分数的线性层组成。

在推理过程中,我们通过对原始模型逻辑应用 softmax 函数并选择具有最高概率得分的类别来预测对象类别。在训练过程中,我们使用交叉熵计算分类损失。

在一个全面的实现中,我们还在第二阶段包括了background类别,但是让我们把它留在本教程中。

在第二阶段,我们还添加了一个回归网络,进一步为区域建议生成偏移。然而,由于这需要额外的簿记,我没有把它包括在本教程中。

非最大抑制

在推理的最后一步,我们使用一种叫做非最大抑制的技术来移除重复的包围盒。在这种技术中,我们首先考虑具有最高分类分数的包围盒。然后,我们用这个盒子计算所有其他盒子的 IoU,并移除具有高 IoU 分数的盒子。这些是与“原始”边界框重叠的复制边界框。我们对剩余的盒子也重复这个过程,直到所有重复的都被删除。

同样,我们不必从头开始实现它。torchvision.ops图书馆提供给我们。NMS 处理步骤在上述阶段 1 回归网络中实现。

快速 RCNN 模型

我们将区域建议网络和分类模块放在一起,以构建最终的端到端 fast-RCNN 模型。

训练模型

首先,让我们在一个小的数据样本上过度拟合网络,以确保一切都按预期工作。我们正在使用一个带有 Adam optimizer 的标准训练循环,学习率为1e-3

标准训练循环

结果如下:

推理结果

由于我们只对一小部分数据进行了训练,该模型还没有学习图像级别的特征,因此结果并不准确。这可以通过在大数据集上训练来改善。

结论

在全面实施中,我们在 MS-COCO 或 PASCAL VOC 等标准数据集上训练网络,并使用平均精度或 ROC 曲线下面积等指标评估结果。然而,本教程的目的是理解更快的 RCNN 模型,所以我们将离开评估部分。

多年来,该领域取得了显著的进步,并且开发了许多新的网络。例子包括 YOLO、EfficientDet、DETR 和 Mask-RCNN。然而,它们中的大多数都是建立在我们在本教程中讨论过的更快的 RCNN 模型的基础之上的。

我希望你喜欢这篇文章。该代码可在 GitHub 获得。我们来连线。你也可以通过 LinkedIn 或 Twitter 联系我。

数据集确认

本文中使用的两幅图像来自于 DIV2K 数据集。数据集在 CC0:公共领域下获得许可。

@InProceedings{Agustsson_2017_CVPR_Workshops,author = {Agustsson, Eirikur and Timofte, Radu},title = {NTIRE 2017 Challenge on Single Image Super-Resolution: Dataset and Study},booktitle = {The IEEE Conference on Computer Vision and Pattern Recognition (CVPR) Workshops},month = {July},year = {2017}
}

图像制作者名单

除非标题中明确注明出处,否则本教程中的所有图片均出自作者之手。

参考

  • 用于计算机视觉的深度学习,UMich
  • 更快-RCNN 论文

通过示例了解自动编码器:分步指南

原文:https://towardsdatascience.com/understanding-autoencoders-with-an-example-a-step-by-step-tutorial-693c3a4e9836

第一部分:普通自动编码器

Natalya Letunova 在 Unsplash 上的照片

介绍

自动编码器很酷!例如,它们可以用作生成模型,或者用作异常检测器

此外,自动编码器背后的想法实际上非常简单:我们采用两个模型,一个编码器和一个解码器,并在它们中间放置一个“ 瓶颈 ”。然后,我们给它相同的数据作为输入和输出,因此自动编码器同时学习输入的压缩表示(这是编码器将做的),以及如何从该表示重构输入(这是解码器将做的)。

看起来很简单,对吧?

然而,自动编码器的实现细节 许多并且需要你密切注意以使它们正确。因此,我们从一个简单的合成数据集(我们将在整个系列中使用它)和一个普通自动编码器(AE)开始这个系列的两篇文章。

在第二篇文章中,我们将使用变分自动编码器重新参数化技巧Kullback-Leibler 发散/损失卷积变分自动编码器(CVAEs)。

我们邀请您在使用 Google Colab 运行随附笔记本的同时阅读这一系列文章,随附笔记本可从 my GitHub 的“随附笔记本”存储库中获得:

https://colab.research.google.com/github/dvgodoy/AccompanyingNotebooks/blob/main/Understanding AutoEncoders.ipynb

此外,我构建了一个目录来帮助你浏览两篇文章的主题,如果你把它作为一个迷你课程并且一次一个主题地浏览内容的话。

目录

第一部分:普通自动编码器(本文)

  • 一个类似 MNIST 的圆形数据集
  • 编码器
  • 潜在空间
  • 解码器
  • 损失函数
  • 自动编码器(AE)
  • 奖励:自动编码器作为异常检测器

第二部分:可变自动编码器

  • 变分自动编码器(VAE)
  • 重新参数化技巧
  • 库尔贝克-莱布勒分歧/损失
  • 损失规模
  • 卷积变分自动编码器(CVAE)

一个类似 MNIST 的圆形数据集

手写数字的 MNIST 数据库(修改的 国家标准与技术研究所 数据库)是使用图像作为输入的教程的首选数据集。这些单通道(灰度)图像的尺寸缩小到 28x28 像素,非常适合快速训练计算机视觉模型。

在本教程中,我们将保持使用 28x28 像素图像的悠久传统,但我们将生成一个比传统 MNIST 更简单的合成数据集-一个由不同大小的圆组成的数据集:

一个由不同大小的圆组成的类似 MNIST 的数据集。图片作者。

为了生成这些圆,我们将使用 Matplotlib 在图形上绘制它们,然后使用 PIL 将这些图形转换为灰度图像,调整为典型的 28x28 大小。靠近中心(坐标 0.5,0.5)画圆,每个圆的半径在 0.03 到 0.47 之间。

生成类似 MNIST 的圆形数据集

在生成 1000 个圆的图像后,我们将构建一个 TensorDataset ,其中像素值(现在在范围【0.0,1.0】内)是特征,半径是标签;以及一个数据加载器,用于在训练我们的模型时加载小批量数据。

那么,让我们开始吧!

编码器

编码器的作用是将一个输入(x)—784 像素(在我们的例子中是 28 x28)—映射到一个压缩表示,也就是一个矢量。这个向量通常用字母 z 表示,可以是你想要的任何大小。

在我们这个简单的例子中,我们将使用一个大小为 1 的向量,也就是说,每幅图像将被赋予一个值,并且只有一个值。

将输入( x )编码成压缩表示( z )。图片作者。

"为什么向量的大小只有一个?"

这种选择背后的原因有两个:首先,它将使可视化更容易(例如,绘制直方图,沿单一维度重建图像);第二,我还没有告诉你第二个原因(这将是一个主要的剧透),因为你会在后面的“潜在空间分布(AE)”部分找到答案

因此,我们需要一个模型,它获取一幅图像并输出一个包含单个值的向量( z )。下面的模型做到了这一点——它将图像展平为 784 像素/特征,通过两个完全连接的隐藏层,最终输出一个值。

Encoder((base_model): Sequential((0): Flatten(start_dim=1, end_dim=-1)(1): Linear(in_features=784, out_features=2048, bias=True)(2): LeakyReLU(negative_slope=0.01)(3): Linear(in_features=2048, out_features=2048, bias=True)(4): LeakyReLU(negative_slope=0.01))(lin_latent): Linear(in_features=2048, out_features=1, bias=True)
)

请注意,我将输出层( **lin_latent** ) 与“基础”模型分开。目前看起来这似乎是一个没有区别的区别,但是当我们在本系列的第二篇文章中处理变型自动编码器时,你会理解这个选择背后的原因。请原谅我。

此外,请记住,这个模型只是一个例子,它可以更简单,或更深入,甚至是一个卷积神经网络(我们将在第二篇文章中再次讨论)。

现在,让我们看看代码中的模型:

然后,让我们对数据集中的 7 号图像进行编码:

x, _ = circles_ds[7]
z = encoder(x)
z**Output:
tensor([[-0.1314]], grad_fn=<AddmmBackward>)**

这是一个大小为 1 的向量( z )。那是我们的…

潜在空间

潜在空间就是我们上一节讲过的矢量( z ) 。就是这样!向量中的每个元素代表所谓的潜在空间中的一个维度。但是不要被行话吓倒,说到底,潜在空间只是一个矢量。

潜在空间( z ) —一个向量。图片作者。

而且,你要决定矢量的大小 z !请记住,随着潜在空间维数的增加(即向量的长度),重构的输入更有可能接近原始输入。

"我如何选择潜在空间的尺寸?"

自动编码器背后的一般思想是通过“瓶颈”效应获得数据的压缩表示,因此唯一合理的是潜在空间的大小应当比输入的大小

在我们的例子中,输入是包含 784 个像素/值的图像,我们选择的潜在空间尽可能小——只有一维——因为这将使可视化更容易。但是我们可以很好地选择其他值,比如说 50,重建的图像可能会好得多。

话虽如此,还是有这样的情况,即潜在空间可能比原始输入有更多的维度:去噪自动编码器 浮现在脑海中。这些模型背后的想法是使用图像的损坏/噪声版本作为输入,使用原始、干净的图像作为期望的重建输出。在这种情况下,潜在空间中的额外维度使模型能够“过滤”掉噪声。

解码器

解码器的作用是使用潜在空间中的一个点( z ) ,在我们的例子中是一个包含单个值的向量,来尝试重构相应的输入( x~ ) ,即一个 28×28 像素的图像。这正是编码器工作的对立面

解码压缩表示( z )以重构输入( x ~)。图片作者。

理论上,您可以使用任何模型,该模型采用包含单个值的矢量( z ),并输出 784 个特征/像素。然而在实践中,通常使用的解码器是编码器的镜像,因此它们在模型复杂性方面是相似的。

Sequential((0): Linear(in_features=1, out_features=2048, bias=True)(1): LeakyReLU(negative_slope=0.01)(2): Linear(in_features=2048, out_features=2048, bias=True)(3): LeakyReLU(negative_slope=0.01)(4): Linear(in_features=2048, out_features=784, bias=True)(5): Unflatten(dim=1, unflattened_size=(1, 28, 28))
)

如你所见,解码器使用与编码器相同的三个线性层,但顺序与相反,因此它是编码器的完美镜像。

让我们使用未经训练的解码器用它(不良地)重建图像#7:

x_tilde = decoder(z)
x_tilde, x_tilde.shape**Output:
tensor([[[[ 1.9056e-01, -4.4774e-02, ..., -2.6308e-02],...[ 8.7409e-02, -6.3456e-03, ...,  1.8832e-02]]]], 
grad_fn=<ViewBackward>)
torch.Size([1, 1, 28, 28]))**

现在我有一个问题要问你:你看到上面的输出(**x_tilde**)有什么异常吗?

提示:记住解码器的输出是试图重建输入(图像)。

如果你发现了消极的价值观,你就明白了!

像素不应该有负值——它们可以是[0,255]范围内的整数,也可以是[0,1]范围内的浮点数,但绝不是负值。

“那么,解码器模型有错吗?”

不一定,不。但是,当涉及到用于训练模型的损失函数时,您需要做出明智的选择。

损失函数

由于解码器的最后一层是线性层,它将输出(-inf,+inf)范围内的值,但这本身并不是一个决定性因素——我们只需要意识到这一点,然后相应地选择损失函数

在这种情况下,我们可以使用均方误差(MSE)作为损失函数,就好像我们在为每个像素运行回归任务一样。

“听起来很奇怪;我可以在末尾添加一个 sigmoid 层,将所有内容都挤到(0,1)范围内吗?”

当然,你可以!在解码器模型上加一个 sigmoid 层其实也是家常便饭,所以输出保证在像素范围内(0,1)。

“损失函数呢?”

一方面,现在看起来好像我们正在为每个像素运行二进制分类任务,这将调用不同的损失函数,即二进制交叉熵。

另一方面,也有可能继续使用 MSE 而不是(这实际上相当普遍!),将 sigmoid 图层简单地用作将输出挤压到所需范围的手段(毕竟,将输出视为像素具有值 1.0 的概率没有太大意义)。

所以,你可能会疑惑,“MSE 和 BCE 哪个损失函数更好?“就像我们这个领域的许多事情一样,这个问题没有直接的答案,但我们遵循大卫·福斯特的建议,在本教程中坚持使用 MSE:

“二进制交叉熵对严重错误的极端预测处以更重的惩罚,因此它倾向于将像素预测推到范围的中间。这导致图像不太清晰。”

来源:“生成性深度学习”,作者大卫·福斯特

自动编码器

“转发:当编码器遇到解码器时”

这看起来像是 80 年代的电影名,但在我们的例子中,编码器和解码器是字面上的为彼此而生 😃

那么,自动编码器是如何工作的呢?这是一系列简短的步骤:

  • 编码器接收输入(x))将其映射到一个向量( z ) ,即潜在空间;
  • 解码器从编码器接收向量( z ) 、潜在空间,生成重构输入( x~ )。

当编码器遇到解码器时。图片作者。

很简单,对吧?我们只是把两块拼在一起。如果我们使用前面章节中的模型,它看起来像这样:

AutoEncoder((enc): Encoder((base_model): Sequential((0): Flatten(start_dim=1, end_dim=-1)(1): Linear(in_features=784, out_features=2048, bias=True)(2): LeakyReLU(negative_slope=0.01)(3): Linear(in_features=2048, out_features=2048, bias=True)(4): LeakyReLU(negative_slope=0.01))(lin_latent): Linear(in_features=2048, out_features=1, bias=True))(dec): Sequential((0): Linear(in_features=1, out_features=2048, bias=True)(1): LeakyReLU(negative_slope=0.01)(2): Linear(in_features=2048, out_features=2048, bias=True)(3): LeakyReLU(negative_slope=0.01)(4): Linear(in_features=2048, out_features=784, bias=True)(5): Unflatten(dim=1, unflattened_size=(1, 28, 28)))
)

此外,很容易看出解码器是编码器的镜像,如下表所示:

将图层“匹配”为镜像。图片作者。

现在,让我们看看如何执行…

模型训练(AE)

这是 PyTorch 中一个典型的训练循环:执行正向传递,计算损耗,使用**backward()**计算梯度,更新参数,将梯度归零。一切照旧!

Epoch 001 | Loss >> 0.1535
Epoch 002 | Loss >> 0.0220
...
Epoch 009 | Loss >> 0.0117
Epoch 010 | Loss >> 0.0109

如果您需要了解如何在 PyTorch 中培训模特,请查看:

重建示例

现在模型已经训练好了,让我们对数据集的图像#7 进行编码和解码(即重建):

一个重建的圆。图片作者。

还不错,对吧?这个圆圈被清晰地重建了,但看起来像是其他更小的圆圈也被模糊地重建了。让我们试着理解为什么会发生这种情况…

潜在空间分布

如果我们为我们数据集中的每个图像绘制相应的潜在空间( z )直方图,我们将得到左边的图。有几件事需要注意:

(1)略低于 1 的巨大尖峰,这意味着我们数据集中的许多图像被映射到一个微小的区间 [0.6,0.9];

(2)所有点都在[-0.50,1.62]内——任何超出这个区间的都是“空”的潜在空间;

左图:潜在空间的分布;右图:潜在空间与半径。图片作者。

为了进一步研究它,我们可以为每一幅图像绘制潜在空间与圆的半径的关系,这就是右边的图。

“半径?为什么?”

由于我们的圆是画在图像的中心(除了一点点抖动),最好描述圆的一个特征是它的半径。我们的自动编码器的任务是将圆映射到潜在空间中的一维空间中。所以,可能,只是可能,它可能已经学会将圆映射到一个实际代表半径的潜在空间。那会是牛逼吧?

如果是这样的话,我们应该会看到一个圆的半径和它对应的一维潜在空间之间的线性关系。但是,很明显,在右边的图中看不到这种关系,至少在半径的整个范围内(或者如果你更喜欢拉丁复数的话,在半径的整个范围内)看不到这种关系!).

我们在那里发现了什么?

(3)半径小于 0.25 左右的圆都被映射到潜在空间的[0.6,0.9]区间(图的垂直部分);

(4)对于 0.25 到 0.3 左右的半径,半径与映射的潜在空间之间存在线性关系;

(5)对于大于 0.3 左右的半径,半径与映射的潜在空间之间大致呈的负线性关系。

“实际上,这意味着什么?”

让我们重建一些图像来更好地说明它!

重建(AE)

我们正在重建五幅图像,对应于潜在空间中的五个点(-3.0,-0.5,0.0,0.9,3.0),这可能有助于我们更好地理解正在发生的事情:

潜在空间中所选点的重建图像(AE)。图片作者。

  • [-3.0]和【3.0】,处于“潜在空间”的两端,之前没有圆经过的地方,然而,自动编码器仍然在生成圆,尽管它们似乎也有一些噪声
  • [-0.5】****【0.0】,位于映射的潜在空间的边缘,分别对应大约 0.4 的半径;它们是重建圆圈的成功案例,尽管它们似乎也包含一个微弱的、更小的内圈;
  • 【0.9】,对应潜在空间的“拥堵”区域,看起来像是不同半径的小圆的“混合体”。

"大胆地去一个圈子从未去过的地方!"

皮卡德上尉

我们当然可以做得更好,但是我们需要使用更好的模型,即可变自动编码器,这将是本系列第二篇文章的主题。

额外收获:自动编码器作为异常检测器

这一部分的灵感来自于我读过的最有趣的文章之一——艾米莉·杜邦的《让一只鸡穿过 MNIST 模特》。它非常详细地说明了自动编码器作为异常检测器的作用。

我在这里只是触及这个话题的表面,所以你可以得到它的要点。但是,我决定用鸭子代替鸡,因为我更喜欢鸭子。所以,让我们从一只鸭子开始,最好是一只烤鸭子!

一只鸭子,又高又肥!改编自这张图片来自维基共享。

“那是什么鸭子?!"

显然,鸭不是圆。尽管如此,如果我们给我们的编码器输入一个 28x28 像素的鸭子图像,它会输出相应的潜在空间,无论如何。

"把一只鸭子映射到圆的潜在空间有什么意义?"

潜伏空间本身没那么有趣,但是重构鸭才是!如果我们给我们的解码器输入一个潜在空间,无论如何它都会输出一个重建图像。让我们看看会发生什么…

重建一只鸭子——或者试着去重建!图片作者。

嗯,重建图像看起来更像一个而不是一只鸭子!这不应该是一个惊喜,毕竟,我们在圆形图像上训练了我们的自动编码器。

“我不明白;这怎么‘有趣’了?”

重建图像看起来根本不像原始图像的事实相当强烈地表明原始图像(一只鸭子)不属于用于训练自动编码器的相同数据分布(圆)。

这就是有趣的部分——我们可以使用重建损失(即原始图像和重建图像的差异)将图像分类为异常

一旦你知道一幅图像不属于用于训练模型的原始数据集中的其他图像,就很少有或没有理由再使用该图像作为输入来进行预测

假设我们让我们的鸭子通过一个训练有素的 MNIST 模型来对数字进行分类(这就是 Emilien 在他的文章中所做的)。分类器可能会预测我们的鸭子是“5”或“8”,但这些预测显然是无意义的。分类器无法做得更好,因为它无法输出“我不知道”作为预测。但是,如果我们使用自动编码器来检测异常输入——在将它们提交给另一个模型之前——我们可以轻松实现这一点。

最后的想法

感谢您坚持到这篇长文的结尾:-),但是,尽管它一篇长文,但它只是一个介绍,并且它只涵盖了您需要熟悉的开始试验自动编码器的基本工具和技术。

在本系列的第二篇文章中,我们将了解变分自动编码器重新参数化技巧Kullback-Leibler 发散/损失卷积变分自动编码器(CVAEs)。

与此同时,如果你想了解更多关于自动编码器和生成模型的知识,我推荐大卫·福斯特的生成深度学习,作者是奥赖利。

还有,如果你想了解更多 PyTorch,计算机视觉,NLP 的知识,给我自己的系列丛书, 深度学习用 PyTorch 循序渐进 ,一试:-)

如果您有任何想法、意见或问题,请在下面留下评论或通过我的 个人资料链接 页面联系。

如果你喜欢我的文章,请考虑使用我的推荐页面 注册一个中级会员 来直接支持我的工作。对于每一个新用户,我从中获得一小笔佣金:-)

通过示例了解自动编码器:一步一步的教程

原文:https://towardsdatascience.com/understanding-autoencoders-with-an-example-a-step-by-step-tutorial-a79d2ea2945e

第二部分:可变自动编码器

在 Unsplash 上由Tine ivani拍摄的照片

介绍

自动编码器很酷,变型自动编码器更酷!

这是“通过示例理解自动编码器系列的第二篇(也是最后一篇)文章。在 第一篇文章 中,我们生成了一个合成数据集并构建了一个普通的自动编码器来重建圆的图像。

一个重建的圆圈,从第一个帖子开始。图片作者。

我们将再次使用相同的合成数据集,因此如果需要的话,请查看“一个类似 MNIST 的圆形数据集”部分进行复习。

在本文中,我们将关注一种不同的自动编码器:可变自动编码器(VAEs)。我们还将了解什么是著名的重新参数化技巧,以及 Kullback-Leibler 发散/损失的作用。

我们邀请您在使用 Google Colab 运行随附笔记本的同时阅读这一系列文章,随附笔记本可从我的 GitHub 的“随附笔记本”存储库中获得:

https://colab.research.google.com/github/dvgodoy/AccompanyingNotebooks/blob/main/Understanding AutoEncoders.ipynb

此外,我建立了一个目录来帮助你浏览两篇文章的主题,如果你把它作为一个迷你课程并且一次一个主题地浏览内容的话。

目录

第一部分:普通自动编码器

  • 一个类似 MNIST 的圆形数据集
  • 编码器
  • 潜在空间
  • 解码器
  • 损失函数
  • 自动编码器(AE)
  • 奖励:自动编码器作为异常检测器

第二部分:变化的自动编码器(本文)

  • 变分自动编码器(VAE)
  • 重新参数化绝招
  • 库尔贝克-莱布勒发散/损失
  • 损失的规模
  • 卷积变分自动编码器(CVAE)

变分自动编码器(VAE)

在传统的自动编码器中,潜在空间不是连续的,也就是说,不仅在“边缘”之外的处,而且在从训练集中的图像映射的点之间的处,都有空的潜在空间。这并不是说从“空的”空间中的点重建的图像只产生噪音/垃圾——在第一篇文章中我们甚至把它们弄出了一些圆圈——但是这并不能保证一个更复杂的问题。

"那么我们可以让潜在空间连续吗?"

你打赌!让我们以这样的方式修改普通的自动编码器,不是在潜在空间中产生,而是在潜在空间中产生分布。我给你介绍变型自动编码器(VAE)

使用规则采样的变分自动编码器。图片作者。

在 VAE 中,编码器不再输出潜在空间,但是产生表示标准偏差 ,每个维度一个(与您为矢量 z 选择的大小一样多)。然后,每对平均值和标准偏差用于定义对应的分布(我们使用正态分布),我们从该分布中进行采样,以获得向量 z 的值。

嘭!潜伏空间现在是连续的!

此外,还记得我们在第一部分的“编码器”部分将编码器的输出层(**lin_latent**)与模型的其余部分分开吗?现在你可以明白为什么了——在 VAE 中,我们使用基本模型的输出来馈送两个输出层: **lin_mu** **lin_var**

EncoderVar((base_model): Sequential((0): Flatten(start_dim=1, end_dim=-1)(1): Linear(in_features=784, out_features=2048, bias=True)(2): LeakyReLU(negative_slope=0.01)(3): Linear(in_features=2048, out_features=2048, bias=True)(4): LeakyReLU(negative_slope=0.01))(lin_mu): Linear(in_features=2048, out_features=1, bias=True)(lin_var): Linear(in_features=2048, out_features=1, bias=True)
)

看起来不错吧?但是,有一个小问题… 不能通过计算图中的随机节点反向传播!

上图中的“随机节点”是我们的向量 z ,因为它在表示采样红色箭头的接收端。

“这是什么意思?”

这意味着,每当分布的采样被引入到正向通道时,在该点之前使用的任何参数/权重对于反向推进程序而言都变得不可到达,因此在我们的情况下不可能训练编码器。

我知道这看起来很糟糕,但幸运的是,使用一个小技巧就可以很容易地解决这个问题…

重新参数化技巧

所以,问题是“随机节点”,对吗?如果我们从计算图中去掉随机性会怎么样?如果随机性来自输入而不是呢?它甚至不一定是用户提供的输入——从字面上看,它可能是一些内部随机输入

事情是这样的:

  • 编码器继续像以前一样产生成对的平均值和标准偏差(两个与 z 大小相同的向量);
  • 编码器的正向传递中,我们从一个标准正态( mu =0, std =1)分布中抽取样本来构建一个随机向量(ε)z大小相同,即内部随机输入
**def forward(self, x):**base_out = self.base_model(x)self.mu = self.lin_mu(base_out)self.log_var = self.lin_var(base_out)std = torch.exp(self.log_var/2)**eps = torch.randn_like(self.mu)z = self.mu + eps * std**return z
  • 我们将随机(**eps**)向量和标准差(**std**)向量相乘,并将均值(**mu**)向量加到结果上——voilà我们得到了全新的向量 z

您可能想知道编码器实际产生的是什么,因为标准偏差( **std** )不是编码器的直接输出…

编码器的输出实际上是对数方差 ( **log_var**),但是我们可以使用下面的表达式计算标准偏差(**std**):

“好,我明白了,但是为什么不直接求标准差呢?”

嗯,我们不可能标准差负值,对吧?但是线性输出层(**lin_var**)会输出满地的值,负的,正的。理论上,我们可以使用一个 ReLU 激活函数来只保持正值,当然,但这更多的是一种变通方法而不是解决方案。

如果我们将编码器 输出视为方差的对数(而不是方差本身),我们将有效地去除方差的所有负值(那些负值的取幂将总是产生(0,1)范围内的值),更好的是,我们正在平滑地进行,而不是作为截止此外,由于每次对数在公式中出现都很典型,它提高了数值的稳定性* 😃*

弄清楚这一点后,现在让我们看看这个图是什么样子的:

使用重新参数化技巧的变分自动编码器!图片作者。

随机节点不再是**,因为红色箭头(采样)指向一个输入(ε)。计算图又是完整的——因此允许我们模型中的每个参数,从解码器到编码器,在反向投影过程中更新。简单说就是再参数化绝招

在 PyTorch 中,常规采样和使用重新参数化技巧采样的区别在于用于从 正态分布:[**sample**](https://pytorch.org/docs/stable/_modules/torch/distributions/normal.html#Normal.sample)vs[**rsample**](https://pytorch.org/docs/stable/_modules/torch/distributions/normal.html#Normal.rsample)*中采样的方法。*

****def sample(self, sample_shape=torch.Size()):*** *shape = self._extended_shape(sample_shape)with torch.no_grad():return torch.normal(self.loc.expand(shape),self.scale.expand(shape))****def rsample(self, sample_shape=torch.Size()):*** *shape = self._extended_shape(sample_shape)eps = _standard_normal(shape, dtype=self.loc.dtype,device=self.loc.device)return self.loc + eps * self.scale**

**rsample**方法执行的操作与上面我们自己的正向传递完全相同。我们本可以使用 **rsample** 来代替自己编码,但是,出于教育目的,我认为最好在正向传递本身中显式地显示这个计算。

“就这些?真的吗?”

真的——不一定要复杂——这个想法相当简单优雅。

"那么,我们能对代码做这些改变并训练我们的 VAE 吗?"

别急,我们还需要讨论另一个损失,Kullback-Leibler 散度/损失(简称 KL 损失)。

库尔贝克-莱布勒发散/损失

KL 散度/损失是两个分布之间的相异度q(z) 、我们试图实现的分布,以及 p(z) ,我们实际拥有的分布。我们在这里不讨论细节和公式,但是我们仍然需要知道,p(z)越接近 q(z) 的,越低的的发散/损失。****

"好吧,但这如何适用于我们的 VAE?"

分布 p(z) 是我们使用编码器产生的一对平均值和标准差得到的正态分布,这是我们实际拥有的分布。****

另一个分布, q(z) ,是标准正态( mu =0, std =1)分布,我们试图达到的分布。

编码器产生的与标准正态(mu=0,std=1) 分布越相似的 KL 发散/损耗将越

因此,最小化 KL 损失意味着我们的编码器将为我们的潜在空间(向量 z )的每一个维度产生尽可能接近标准正态(mu=0,std=1) 的分布。****

由于两种分布 p(z)q(z) 都是正态,并且后者是标准正态分布,KL 发散/损失由下面的相对简单的表达式给出:

一些正态分布和标准正态之间的 KL 偏差/损失

或者,如果你喜欢用代码来写:

****def kl_div(mu, std):**kl_div = -0.5 * (1 + np.log(std**2) - mu**2 - std**2)return kl_div**

下图说明了两种分布的 KL 散度/损失如何变化,橙色的 p(z) (根据相应的行和列具有不同的属性),蓝色的 q(z) (标准正态分布):

N(mu,std)和 N(0,1)之间的 KL 散度。图片作者。

这是同样的数据,这一次是热图,为了更简洁的可视化:

N(mu,std)和 N(0,1)之间的 KL 散度。图片作者。

“我们为什么关心这个,我的意思是,为什么潜在空间的维度作为标准正态分布很重要?”

因为这将使我们以后的生活更容易——我们将能够使用一个众所周知和熟悉的分布——标准正态分布,从潜在空间中进行采样。此外,训练集中的图像将被映射到潜在空间的每个维度的标准法线的典型区间(-3.0 到 3.0)。这意味着如果我们的样本也在这个区间内,我们很有可能得到好的重建。****

现在,我们终于可以对编码器的代码进行修改了,variable-style!我们需要修改**forward()** 方法(如“重新参数化技巧”一节所述)并添加一个**kl_loss()** 方法来使用我们的编码器产生的平均值和对数方差计算 KL 偏差/损失:

变型编码器!

解码器和自动编码器模型与第一部分中的标准自动编码器保持一致:

可变自动编码器(VAE)

"所以,我们把 KL 损失加到 MSE(或 BCE)损失上,我们就可以训练它了?"

是的,但是我们仍然需要考虑…

损失的规模

我们很容易忽略这个问题——我们太习惯于使用默认的"均值"作为 PyTorch 损失函数(如 MSE 或 BCE)中的约简参数,以至于我们可能不会再考虑它。因此,如果我们在处理 KL 损失时简单地默认相同的程序,这不应该是令人惊讶的,对吗?

嗯,是的,但这可能不会工作训练 VAE。这就是为什么你会看到,在大多数关于 VAE 的教程中,减少损失使用的是“总和而不是“表示”,尽管大多数时候,对其背后的推理只字未提。所以让我们仔细看看!

我们将从获取一批圆形图像开始,然后对它们进行编码,并使用我们的(未训练的)模型来重建它们。

**x, y = next(iter(circles_dl))
zs = encoder_var(x)
reconstructed = decoder_var(zs)**

接下来,我们将使用**reduction='none'**获得 MSE 损失的原始值:

**loss_fn_raw = nn.MSELoss(reduction=’none’)
raw_mse = loss_fn_raw(reconstructed, x)
raw_mse.shape
**Output:
torch.Size([32, 1, 28, 28])****

不出所料,一批图像(32)中每个通道(1)的每个像素(28x28)都有一个平方误差,总共 25,088 个值。如果我们在损失函数中使用标准缩减(“表示或“总和”),他们将简单地计算所有 25,088 个值的相应缩减,例如:

**raw_mse.sum(), nn.MSELoss(reduction=’sum’)(reconstructed, x)
**Output:
(tensor(23960.8066, grad_fn=<SumBackward0>),tensor(23960.8066, grad_fn=<MseLossBackward>))****

绝配!但是我们不一定要像那样减少它,我们可以更有选择性一点,然后计算每幅图像像素的总和,然后才计算整批图像的平均值:

**sum_over_pixels = raw_mse.sum(dim=[1, 2, 3])
sum_over_pixels.mean()
**Output:
tensor(748.7753, grad_fn=<MeanBackward0>)****

看到了吗?那是完全不同的结果!我们也可以翻转减少量,首先计算像素的平均值,然后计算整批的总和。下表总结了四种可能的策略:

平方误差损失的缩减函数。图片作者。

现在,让我们对 KL 损失做同样的处理,使用在变分编码器中实现的**kl_loss()** 方法获取其原始值:

**raw_kl = encoder_var.kl_loss()
raw_kl.shape
**Output:
torch.Size([32, 1])****

由于 KL 散度/损失是为潜在空间的每个维度(向量 z )计算的,并且我们选择只有一个维度,所以批中的每个图像只有一个值。我们选择的缺点是,对向量*(dim = 1)取“ sum ”或“ mean ”将总是产生相同的结果,如下表所示:*****

KL 损失的缩减函数。图片作者。

我们将对 MSE 和 KL 损失使用组合# 4,但是我鼓励你尝试其他组合,并了解它们如何影响模型训练。出于这个原因,我们将在训练循环中手动地、明确地减少两次每个损失:除了第一个维度,在每个维度上减少一次,在批次上再减少一次

“如果这些组合都没有产生好的结果呢?怎么办?”

问得好!事实证明,通常在重建损失中包含一个乘数。这个因素是a 超参数您可以在训练 VAE 时进行调整。

****loss = loss_fn(yhat, x).sum(dim=[1, 2, 3]).sum(dim=0)
kl_loss = model_vae.enc.kl_loss().sum(dim=1).sum(dim=0)
total_loss = reconstruction_loss_factor * loss + kl_loss****

如果因子太高,则重建损失将支配训练,这就好像我们有一个普通自动编码器

如果因子太低,则 KL 损失将主导训练,并且重建图像不会有任何好处

模特培训(VAE)

我们需要修改训练循环以考虑损失、重建和 KL 以及前者的倍增因子。修改属于第二步(第 21 至 29 行,计算损失),并且仅属于第二步。

训练变分自动编码器

****Epoch 001 | Loss >> 4622.8542/4566.6690/56.1851
Epoch 002 | Loss >> 625.7960/554.5768/71.2191
...
Epoch 029 | Loss >> 269.6513/198.1815/71.4698
Epoch 030 | Loss >> 257.9317/182.5780/75.3537****

潜在空间分布(VAE)

如果我们绘制一个潜在空间的直方图( z )(左图),我们会看到,虽然它还不完全是正态分布,但它有点接近它。

左图:潜在空间的分布;右图:潜在空间与半径。图片作者。

然后,如果我们根据每个圆的相应半径绘制潜在空间,我们将得到右边的图。在半径的 0.2 标记处仍然有一个“弯头,但是垂直线已经消失了(来自在第一篇文章中训练的普通自动编码器),这意味着我们在重建图像的任何地方都不会看到圆形的“混合”。我们去看看吧!****

重建(VAE)

我们正在重建五幅图像,对应于第一篇文章中使用的潜在空间(-3.0,-0.5,0.0,0.9,3.0)中相同的五个点。

别忘了我们的潜伏空间现在是连续的

潜在空间中所选点的重建图像(VAE)。图片作者。

在使用标准正态分布的极值([-3.0]和[3.0])的潜在空间中的点生成的图像上,仍然有一些噪声;仍然有小半径([-0.5])的圆的一点点“混合”;但它看起来肯定比普通的自动编码器好。

现在,是时候去卷积了!**

卷积变分自动编码器(CVAE)

直到最近,卷积神经网络(CNN)还是计算机视觉任务事实上的标准。虽然现在变压器被冠以这个头衔,但 CNN 仍然是有用、简单和快速的架构,所以我们将使用它们来构建卷积变分自动编码器(CVAE)。

我假设您熟悉常规 CNN,以及它们如何用于简单的图像分类任务,因为我们的编码器看起来非常像典型的 CNN:

卷积变分编码器的基本模型

****EncoderVar((base_model): Sequential((0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))(1): LeakyReLU(negative_slope=0.01)(2): Conv2d(32, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))(3): LeakyReLU(negative_slope=0.01)(4): Conv2d(64, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))(5): LeakyReLU(negative_slope=0.01)(6): Conv2d(64, 64, kernel_size=(3, 3),stride=(1, 1), padding=(1, 1))(7): LeakyReLU(negative_slope=0.01)(8): Flatten(start_dim=1, end_dim=-1))(lin_mu): Linear(in_features=3136, out_features=1, bias=True)(lin_var): Linear(in_features=3136, out_features=1, bias=True)
)****

看到了吗?编码器的基本模型是 CNN,变化部分由两个线性输出层给出,一个用于均值,另一个用于对数方差,就像我们以前的 VAE 一样。

然而解码器是完全不同的,因为它是使用转置卷积构建的。要快速了解这些“逆卷积”,请查看我的博文:

****

转置卷积用于将图像从 7x7 像素一直放大到原始大小 28x28:

卷积解码器

“等等,现在有乙状结肠层了?”

接得好!是的,有一个 sigmoid 层,但我们会继续使用 MSE 作为损失函数

我鼓励您尝试移除 sigmoid 层或将损失函数更改为 BCE,或许还可以调整重建损失因子,然后训练模型,看看效果如何。

模特培训(CVAE)

尽管我们对编码器和解码器都使用了更复杂的架构,但这仍然是一个变化的自动编码器,训练循环本身与以前完全相同:

训练卷积变分自动编码器(CVAE)

Epoch 001 | Loss >> 2188.3729/2118.5622/69.8107
Epoch 002 | Loss >> 615.1194/611.0034/4.1160
...
Epoch 029 | Loss >> 172.8306/85.8559/86.9747
Epoch 030 | Loss >> 169.7600/81.1885/88.5715

潜在空间分布(CVAE)

如果我们绘制潜在空间的直方图(【z】)(左图),我们会看到,这一次,分布非常接近标准正态分布

左图:潜在空间的分布;右图:潜在空间与半径。图片作者。

更好的是,如果我们将潜在空间与每个圆的相应半径对应起来,就不再有的“肘”——在潜在空间和半径这两者之间有一个大致的线性关系。

重建的图像呢?

重建(CVAE)

如果我们从标准正态分布中抽取一维潜在空间的样本,我们会在中心得到平均大小的圆,在分布的最左侧得到小圆,在分布的最右侧得到大圆。那个有多牛逼

潜在空间中所选点的重建图像(CVAE)。图片作者。

自动编码器学会了在潜在空间中表示半径!

我们在这里的工作已经完成了:-)

最后的想法

感谢您坚持到这篇长文的结尾:-),但是,尽管它是一篇长文,但它只是一个介绍,并且它只涵盖了您需要了解的开始试验各种自动编码器的基本工具和技术。

如果你想了解更多关于自动编码器和生成模型的知识,我推荐大卫·福斯特的生成深度学习,作者是奥赖利。

如果你想了解更多 PyTorch,计算机视觉,NLP 的知识,给我自己的系列丛书, 深度学习用 PyTorch 循序渐进 ,一试:-)****

如果您有任何想法、意见或问题,请在下方留下评论或通过我的 简历链接 页面联系。

如果你喜欢我的帖子,请考虑使用我的推荐页面 注册一个中级会员 来直接支持我的工作。对于每一个新用户,我从 Medium 中获得一小笔佣金:-)

理解贝叶斯优化中的贝叶斯推理

原文:https://towardsdatascience.com/understanding-bayesian-inference-in-bayesian-optimization-cd0cd45e6098

应用于贝叶斯优化环境中的贝叶斯推理基础知识的直观指南

贝叶斯优化是使用贝叶斯方法研究优化问题的一个领域。优化旨在定位所有可能值的最优目标值(即全局最大值或最小值)或环境中最优值的相应位置。搜索过程从一个特定的位置开始,遵循一个特定的策略来迭代地引导随后的采样位置,收集相应的观察值,并刷新策略。

整体贝叶斯优化过程。该策略消化历史观测值并提出新的采样位置,并且环境控制如何向策略显示新提出的位置处的(可能被噪声破坏的)观测值。我们的目标是学习一个有效率和有效的政策,能够尽快地达到全局最优。图片由作者提供。

目标函数

对于贝叶斯优化,特定类型的目标函数通常具有以下属性:

  • 我们无法获得目标函数的显式表达式,使其成为一个“黑箱”函数。这意味着我们只能与环境(即目标函数)交互,通过在特定位置采样来执行功能评估。
  • 通过在特定位置探测而返回的值通常被噪声破坏,并且不代表目标函数在该位置的真实值。由于对其实际值的间接评估,我们需要考虑来自环境的实际观测中的这种噪声。
  • 每个功能评估都是昂贵的,因此排除了彻底探查的选择。我们需要有一个样本有效的方法来最小化环境评估的数量,同时试图找到它的全局最优。换句话说,优化器需要充分利用现有的观察结果,系统地推理下一个采样决策,这样有限的资源就不会浪费在没有希望的位置上。
  • 我们无法获得它的梯度。当函数求值相对便宜,并且函数形式平滑时,使用梯度下降类型的过程来计算梯度和优化将是非常方便的。如果无法获得梯度,我们就不清楚特定评估点的相邻曲率,从而使后续的行进方向变得困难。

三种可能的函数形式。左边是一个凸函数,它的优化很容易。中间是具有多个局部极小值的非凸函数,右边也是具有充满鞍点的宽平坦区域的非凸函数。后两种情况的优化是困难的。图片由作者提供。

观察模型

为了使我们的讨论更精确,让我们用 f(x) 来表示位置 x 处的(未知)目标函数值。我们使用 y 来表示位置 x 处的实际观察值,由于加性高斯噪声的扰动,这将与 f 略有不同。因此,我们可以基于特定位置 x 和真实函数值 f : p(y|x,f)将观察模型表示为 y 的概率分布。

假设实际观察值的正态概率分布为随机变量。高斯分布以在给定位置 x 评估的目标函数 f 值为中心,并通过噪声项的方差扩展。图片由作者提供。

贝叶斯推理

贝叶斯推理本质上依赖于贝叶斯公式(也称为贝叶斯规则)来推理几个组成部分之间的相互作用:先验分布、给定特定参数的可能性、后验分布以及数据证据。

随着更多的数据被收集,朝着后验正态分布更新先前的均匀分布。随着收集更多的数据来支持对真实基础分布的近似,先验分布的作用降低。图片由作者提供。

贝叶斯方法是一种为参数的可能值分配概率并根据观察到的数据更新这些概率的系统方法。然而,有时我们只对产生我们正在观察的数据的参数的最可能值感兴趣。这可以通过使用频率主义者方法来实现,将感兴趣的参数视为固定量,而不是随机变量。这种方法在机器学习社区中更经常采用,这种社区非常注重优化某个目标函数来定位最佳参数集。

关于感兴趣的参数,比较频率主义方法和贝叶斯方法。frequentist 方法将参数视为可以通过 MLE 估计的固定量,而 Bayesian 方法采用一种概率分布,随着收集的数据越来越多,这种概率分布会不断更新。图片由作者提供。

当我们处理多个(不止一个)变量时,事情变得更加有趣。假设我们有两个随机变量 xy ,并且我们对两个事件 X =Xy =Y 感兴趣,其中 X 和 Y 都是分别为 xy 可能取的特定值。此外,我们假设两个随机变量在某种程度上是相关的。这将引导我们进入现代机器学习和贝叶斯优化文献中常用的三种概率类型:联合概率、边际概率和条件概率。

三种常见的概率分布。联合概率分布表示两个或更多随机变量的概率分布,而条件和边际概率分布通常指一个随机变量的概率分布。条件分布通过假设/调节其他变量的特定值来表示随机变量的概率,而边际分布通过整合其他变量将联合概率转换为单个随机变量。图片由作者提供。

我们感兴趣的是它的预测分布 p(y) ,即 y 的可能值以及相应的概率。如果我们对未来未知数据的预测分布有很好的了解,我们的决策将更加明智,特别是在贝叶斯优化框架中,人们需要仔细决定下一个采样位置。

先验和后验预测分布的定义。两者都是基于先验和似然之间的加权和的相同模式来计算的。图片由作者提供。

我们可以总结在正态假设下,连续参数的概率和先验的后验预测分布的推导。

收集一些观察值后,对新数据点的后验预测分布的推导过程,假设似然性和先验均为正态分布。图片由作者提供。

有了这些现成的工具,我们就可以得到感兴趣参数的更新后验分布以及特定输入位置的随机变量的后验预测分布。

更新了参数的后验分布,表明主观偏好与现实相符。图片由作者提供。

总结

本文涵盖了贝叶斯优化框架中贝叶斯部分的一些亮点。在后面的文章中,我们将涵盖更重要的内容,如高斯过程和采集函数。关于贝叶斯优化基本知识和实现的更彻底和完整的治疗,请关注我的媒体账户,并关注我即将出版的书籍用 Python 实现实用贝叶斯优化

从贝叶斯角度理解偏差-方差权衡

原文:https://towardsdatascience.com/understanding-bias-variance-trade-off-from-a-bayesian-perspective-6c31645fcaa9

使用示例和模拟从贝叶斯角度全面理解等式 MSE =偏差+方差

在我的机器学习之旅中,我遇到过几次偏差-方差权衡,但我一直无法完全掌握它。然而,在采用贝叶斯观点后,一切都变得有意义了。在这篇文章中,我将分享我的学习。

首先我们从理论入手,做一个基本的例子;然后我们来看一个更难的贝叶斯多项式回归的例子。

什么是贝叶斯推理?

在任何一种推断问题中,我们都希望估计未知参数的值。这个参数通常用θ(θ)来表示。在贝叶斯推理中,我们假设θ是一个随机变量,具有由P(θ)表示的先验分布。我们首先构建一个模型,使得它的输出依赖于θ。我们执行实验并记录输出值。这些被称为观察。

观察值是随机变量,通常用 X 表示,其分布取决于θ。这是一个用P(X |θ表示的条件分布。

利用P(θ)P(X |θ)我们可以利用贝叶斯定理求出P(θ| X)的分布。我们将在稍后更详细地讨论这一点,但是现在,请理解P(θ| X)表示基于记录的观测值的θ的更新分布。这也称为后验分布。

从后验分布中,我们基于某些度量来估计𝜃的最优值。注: 𝜃为θ的实现值。θ是一个随机变量,而𝜃是一个实值。同样, xX 的实现值。

此外,由于现实世界中的误差,观测结果不会是完美的。它们被假设为以零为中心的正态随机变量的噪声所污染。通常用 W. 来表示

贝叶斯推理过程如下图所示:

贝叶斯推理。作者图片

例子

所有这些理论可能会让你困惑。我们来做个例子,说清楚。

由 Siora 摄影在 Unsplash 上拍摄

让我们假设你有一枚有偏向的硬币,正面的概率是未知的。你抛 n 次,记录观察到的人头数为 k,人头概率的估计值是多少?

让我们用随机变量θ来表示人头的概率。因为它的先验分布是未知的,所以让我们假设它在 0 和 1 之间均匀分布。这在数学上表示为:

*f*(Θ = θ) = 1 if 0 < 𝜃 < 1 ( f 代表概率密度函数)。

一致先验

这基本上意味着𝜃可以以相等的概率取 0 到 1 之间的任何值。

让随机变量 Xi 代表 iᵗʰ抛硬币的结果。这是一个伯努利随机变量,在数学上可以表示为:

P(Xi = 1 | Θ = θ) = θ (Heads)
P(Xi = 0 | Θ = θ) = 1 - θ (Tails)

这意味着当随机变量θ等于θ时,正面的概率为θ。这是以随机变量θ为条件的条件分布。

我们来计算一下观察到 k 个头的概率。

现在, k 也可以表示为:k = X1 + X2 + X3 + … + Xn。如果观察到头部,随机变量 Xi 等于 1,否则为 0。所以把它们加起来应该能给出我们观察到的人头数。

在 n 次投掷中,有 k 次投掷会导致正面朝上。这些 k 投掷可以通过 ⁿCₖ 方式选择。所有的投掷都是独立的。因此, k 个正面以概率 θᵏ、出现,剩余的 n - k 个反面以概率1—θ)ⁿᵏ.出现

因此,联合分布可以写成:

f(k | Θ = θ) = ⁿCₖ.θᵏ.(1 - θ)ⁿ−ᵏ

这是一个二项分布 wrt k ,看起来像这样:

接下来,让我们用贝叶斯定理计算后验概率:

贝叶斯定理

本质上,我们正在通过询问如果正面概率为 θk 正面多久会被观察一次,并通过观察到 k 个正面的总概率将其标准化,并通过先验对其进行缩放,来找出正面概率为 θ 的可能性。

这是贝叶斯定理的直觉,如下所示:

分母 f(k) 代表多长时间观察一次 k 个头。这等于 1,可以通过从 0 到 1 积分f(k |θ=θ)wrtθ来计算。

同样,我们已经看到先前的f(θ=θ)等于 1。

因此,后验f(θ=θ| k)可以写成:

f(Θ = θ | k) = ⁿCₖ.θᵏ.(1 - θ)ⁿ−ᵏ

好了,现在我们需要从后验分布中估计一个好的值 θ 。接下来让我们看看某些估算方法。

最大后验估计

这里我们选择一个最大化后验概率的值 θ 。表达式f(Θ = θ| k) = ⁿCₖ.θᵏ.(1 — θ)ⁿ−ᵏθ = k / n 处最大。这可以通过对 wrt θ 进行求导并将其设置为零来进行数学计算,如下所示:

计算 MAP 估计量

现在, k 是一个随机变量。如果你和我拿着同一个硬币,抛 n 次,我们可能会观察到不同的 k 值。因此, θ = k / n 被称为估计量,由 θ 顶上的帽子表示,如下图 :

最大后验估计量

我们将用 θhat 来表示估计量。一旦 k 被观测到,实现的值被称为估计值。

测量估计量的优良性

偏见

我们如何知道估计量足够好?首先,我们可以通过计算 θhat — θ来测量它与实际值的偏差。然而,这是一个随机变量,因为θ是随机的。为了报告单个值,我们可以取它的期望值。这就是所谓的偏见。

估计量的偏差

你可以把期望值想象成加权平均值。如果你重复几次这个实验,你可能会观察到这个值。

预期

偏差告诉我们估计值与实际值的平均偏差。

均方误差

偏差不会告诉我们偏差的。让我们通过取偏差的平方的期望值来定义另一个量。这被称为均方误差。

均方误差

进一步简化上述方程,我们得到MSE = bias² + variance,这是统计学中一个著名的方程。计算如下所示:

推导偏差-方差权衡方程

方差度量估计值如何基于观察值而变化。

模拟

让我们回到我们的问题,运行一些模拟。这里我将 θ 的实际值固定为 0.35。我模拟了 k,,这是在 n 次抛硬币中观察到的人头数。我也给 k 添加了高斯噪声来模拟真实世界。

我采用了三种估计量:第一种是我们上面推导的 MAP 估计量,第二种是随机估计 0 到 1 之间的值的随机估计量,第三种是固定值为 0.9 的固定估计量。基于这些观察,我计算了每个估算者的经验 mse、偏差和方差。

下图显示了不同的 n 值的结果。

三个估计量的 MSE、偏差平方和方差

代码可以在这里找到。

理解 MSE =偏差+方差

固定估计量

这里方差为零,因为估计值是固定的。所以 MSE =偏差。

随机估计量

这里的值在每次迭代中不断随机变化。mse 有时低,有时高。然而,方差通常很大,因为估计是随机的。在任何情况下,等式 MSE =偏差+方差得到满足,这可以从图中观察到。

MAP 估计器

这里,与其他两个估计器相比,平均图较低。此外,随着 n 的增加,mse 变为零。另一个有趣的模式是偏差接近于零,方差对 mse 的贡献最大。

我们来讨论一下为什么会出现这种情况。

MAP 估计量的偏差由下式给出:

MAP 估计量的偏差

这里,术语k / n = (X1 + X2 + X3 + … + Xn) / n称为样本均值。弱大数定律认为,当 n 接近无穷大时,样本均值接近真实均值。

所以当 n → ∞时,E[k / n] → θ,b( θhat ) → 0。

影响估计量分布的另一个参数是采样的观察值的数量。这由代码中的变量reps表示。中心极限定理表示,随着越来越多的观察值从相对较大的样本量中取样,它们的分布接近以真实平均值为中心的正态分布。

因此,从大样本量中采样的更多观察值将使 MAP 估计更准确。

好的,到目前为止我们讨论的所有理论和例子都是为了让你的大脑做好准备。接下来让我们看一个更难的例子。

一个更难的例子——贝叶斯多项式回归

在现实世界中,我们经常以[(x1, y1), (x2, y2), (x3, y3), … (xn, yn)]的形式观察数据,其中(xi, yi)由模型关联。我们的目标是估计这个模型。让我们从贝叶斯的角度来处理这个问题。

首先,我们需要定义一个模型:

给定一个观察值xi,随机变量Yi d 依赖于函数h(xi;θ)并受到噪声Wi的影响。

函数h(xi;θ)具有以下形式:

给定一个值xi,Yi的值取决于参数θ所代表的系数*(*𝛼₀, 𝛼₁, 𝛼₂, …, 𝛼ₘ*)*联合分布。与前面的例子不同, θ 在这里是一个矢量。符号h(xi;θ)表示函数 h 依赖于 θ。

让我们假设系数*(*𝛼₀, 𝛼₁, 𝛼₂, …, 𝛼ₘ*)* 的先验分布是均值为 0 且方差为𝜉的正态分布。它们的联合分布将是它们各自分布的产物,因为它们是独立的。

好吧。现在,给定一个值 θ,随机变量Yi的分布会是什么样子?型号是Yi = h(xi;θ) + Wi。给定 θ 的值,h(xi;θ)将保持不变。因此Yi将具有与噪声Wi相同的分布,但是偏移了一个量h(xi;θ)。让我们假设Wi遵循均值为 0 且方差为𝜎的正态分布。Yi的分布将是:

这是针对单次观察Yi。在现实世界中,我们记录多个观察值Y= (Y1, Y2, …, Yn)。同样,我们必须考虑观察值的联合概率分布,这将是它们的乘积,因为假设它们是独立的:

我们现在有了应用贝叶斯定理所需的条件:

一旦我们得到了分布,我们就可以通过选择使后验概率最大化的θ来找到 MAP 估计量。

分母f(Y=y)只是一个不依赖于θ的归一化因子。所以在优化过程中可以忽略。

我们已经讨论了术语f(θ)f(Y=y|θ)。他们的产品看起来像这样:

这个等式看起来很吓人。所以我们常用的一个技巧是取对数并最大化它。

经过一些计算后,该方程的对数将如下所示:

这个看起来好多了。我们需要找到使这个量最大化的θ = *(*𝛼₀, 𝛼₁, 𝛼₂, …, 𝛼ₘ*)* 的最优值。我们不能像上一个例子那样手工完成,所以我们使用了 Python 中的某些优化库。

让我们运行一些模拟,看看实际情况。

模拟

首先,我定义了一个具有以下属性的模型:

Y = 0.25 if 0 < x <= 0.5
Y = x² if 0.5 < x <= 1

基于这个模型,我用不同的噪声模拟了一些观察结果,如下所示:

然后,对于每个观察值,我通过使用 python 中的 scipy 库优化方程,找到了不同程度的 MAP 估计量m = 1 to m = 10

每个观测值和每个度数的 MAP 估计量如下所示:

我们可以看到,与低阶估计量相比,高阶估计量随观测值变化很大。最佳点在中间的某个地方。

scipy 的优化并不完美。如果我们使用了更好的优化技术,如梯度下降,我们可以清楚地观察到高阶估计器发生的过拟合。然而,为了简单起见,我在本文中只介绍了一个基本的优化器。

代码可以在这里找到。

结论

从贝叶斯角度思考的好处是,当我们试图使用估计器来拟合数据时,我们可以理解发生了什么。此外,考虑我们在上一节中看到的等式:

基于先验分布的第二项可以被认为是防止过拟合的正则化系数。通过这个镜头看到的正则化是直观的:先验越强,你需要偏离它的证据就越多。贝叶斯思维提供了这种直观的见解。

**Let's connect** Hope you've enjoyed the article. Let's connect.You can also find me on [Twitter](https://twitter.com/WingedRasengan) and [LinkedIn](https://www.linkedin.com/in/neerajkrishnadev/).

信用

本文中使用的所有数据和可视化都是作者制作的。

参考

  1. 深度学习课程作者弗朗索瓦·弗勒特
  2. 中心极限定理

理解编码面试的位掩码

原文:https://towardsdatascience.com/understanding-bitmask-for-the-coding-interview-b1643f4b0e24

一个挑战性话题的实用指南

亚历山大·辛恩在 Unsplash 上的照片

编码面试一定要知道位操作和位掩码吗?

这两个主题被认为是高级材料,在编码面试中“隐含地”不是必需的。然而,如果面试官愿意,他完全有权利给你这些问题。

这是您应该知道的处理位掩码问题的所有标准技术和模式的简要概述。

目录

位操作的基本
∘ 位操作的常见操作
∘ 位掩码的重要操作
位操作模式
∘ 统计一个数的二进制表示中的 1 位数
∘ 检查一个正数是否是二的幂
∘ 高效计算 Pow(x,n)、 其中 x > 0,n > 0
位掩码模式
∘ 通用模式:
∘ 给定一个唯一元素数组,找出所有可能的子集
∘ 统计所有可能的大小为 k 的子集,其总和为给定的目标
结论

所有代码和方程都是作者写的。

基本的

位掩码是使用数字的二进制表示来解决困难问题的思想。这种表示对于每个数字都是唯一的。

:数字 11 的位数是“1011”,因为以 2 为基数:

11 = 8+2+1 =1* 2+0* 2+1* 2+1*2⁰=“1011”

处理位掩码时:实际数字是多少并不重要。我们要关心的只是它的二进制形式和下面的运算:

位操作的常见操作

  • 左移:要将x向左移动 n 个空格,我们使用x << n。例如,二进制形式的"1011" << 3 = "1011000"11 << 3 = 88
  • 右移:同样,我们使用x >> n。例如,"10101" >> 3= "101"(注意 1 位消失)。
  • 清除最低设置位:x & (x — 1)(如"10100" -> "10000")
  • AND: a & b = 1如果a = 1b = 1,否则为 0
  • 或者:a | b = 1如果a = 1或者b = 1,否则为 0
  • XOR: a ^ b = 1如果ab中恰好有一个为 1,否则为 0
  • 对于大于 1 的数字,我们对每个比特位置迭代地应用这些运算。例如"10111" AND "1100" = "00100" = "100"

位掩码的重要操作

  • 将 i^th 位设为 1: mask = mask | (1 << i)mask |= (1 << i)
  • 翻转 i^th 位:mask = mask ^ (1 << i)mask ^= (1 << i)

大多数时候,你会操作一个叫做mask的数字。这个掩码将用于跟踪列表中“已用”或“未用”的元素。

举例:给定一个由 3 个元素组成的集合A = {1,2,3},我们如何有效地表示这个集合的所有子集?

答:使用从 0 到 7 的数字及其对应的位掩码,其中每个 1 位位置向一个选定的元素发送信号:

0 = "000" = {} = the empty set
1 = "00**1**" = {1}
2 = "0**1**0" = {2}
3 = "0**11**" = {1, 2}
4 = "**1**00" = {3}
5 = "**1**0**1**" = {1, 3}
6 = "**11**0" = {2, 3}
7 = "**111**" = {1, 2, 3}

一般来说,给定一组n元素,我们总是可以用从 0 到 **2^n — 1** 的数字来表示这个集合的所有子集。

这是 bitmask 的基本思想。我们将操作一个代表给定列表的当前子集的mask

位操作模式

计算一个数的二进制表示中的 1 位数

我们可以一直向右移动这个数,直到它变成零。在每一步,我们检查 0^th 位是否为 1,并将其加到总计数中:

我们可以进一步优化这个解决方案:我们可以删除最低设置位并相应地移位,而不是每次移位 1 位:

检查正数是否是 2 的幂

如果 n 是 2 的幂,那么对于某个正整数 k,

换句话说,n 的二进制表示形式为"100...0"。那么我们可以通过移除 n 的最低位集合来容易地验证这一点:

有效地计算幂(x,n),其中 x > 0,n > 0

以 11 为例,考虑 n 的二进制表示:

请注意,11 的二进制表示中的每个位置对应于 x,x,x⁴,…

因此,代替循环和乘以 n 次来计算答案,我们可以通过仅存储 2 的幂的幂来优化计算:x、x、x⁴、x⁸,…,并且乘以适当的值来获得我们的最终答案:

位掩码模式

回想一下,掩码代表一个子集。

例如,我有一个写着"00110011"的面具

这个十进制数是多少?

我不知道,也不在乎。

我们只需要知道这个掩码代表一个集合的状态。这个特殊的掩码意味着我们正在处理一个包含索引为 0、1、4 和 5 的元素的子集(从右到左)。

一般模式:

这段代码在做什么?

我们遍历集合中的所有索引。如果我们在索引i处遇到一个“未使用”的元素,那么相应的掩码元素将是"0"。因此索引i处的not mask"1",因此not mask & (1 << i)将返回 True。然后,我们可以将这个元素添加到我们的子集中,并接下来处理它。

给定一个唯一元素的数组,找到所有可能的子集

回想一下上一节:给定一个大小为n的集合,我们可以使用从 0 到2^n-1的数字来表示所有的子集。

对于每个数字,我们通过查看其二进制表示来构建相应的子集,并找到所有"1"的位置。然后,我们可以将子集添加到最终结果中。

示例输出:

>>> A = [1,3,5,7]
>>> subset(A)
[[], [1], [3], [1, 3], [5], [1, 5], [3, 5], [1, 3, 5], [7], [1, 7], [3, 7], [1, 3, 7], [5, 7], [1, 5, 7], [3, 5, 7], [1, 3, 5, 7]]

对给定目标的所有可能的大小为 k 的子集进行计数

这个问题是 Leetcode 上的组合和 III 。我们希望找到所有大小为k的子集,其总和为n,因此:

  • 仅使用数字19
  • 每个号码最多使用一次

这个问题可以通过回溯来解决,但是我想在这里提出一个位掩码解决方案。一旦您熟悉了位操作,您可能会发现使用位掩码更容易求解和/或编写解决方案。

我们的起始掩码是 0,代表空集(99%的情况下都是这样)。在每一步,我们选择打开位置 1 到 9 的任何位(从右到左,起始位置索引为 0)。

例如,"1010101010"在位置 1、3、5、7 和 9 有"1"。这个掩码表示集合{1,3,5,7,9},总和等于 25。

我们可以使用位掩码和 DP 来解决这个问题。一旦我们得到了所有有效掩码的集合,我们就可以解码每个掩码并得到最终的数字集合。

这里有一个解决方案,它利用了本帖中讨论的三种不同的位操作技术:

结论

位掩码不一定要吓人。

当我开始准备编码面试时,我发现位掩码是最具挑战性的话题。一开始,我会完全跳过这些问题。然而,随着我解决越来越多的问题,我最终发现它并不像我想象的那么可怕。

我的一些朋友在面试中确实被问到了关于位屏蔽的问题(在一些大型科技公司)。难度类似于“位掩码模式”一节中的两个问题。这些问题旨在通过回溯来解决。但是,用位掩码求解是给面试官留下深刻印象的好方法!

(很有可能,他们对此也不太适应。)

如果你愿意支持 Thu,你可以考虑注册成为一名中等会员。每月支付 5 美元,你就可以无限制地阅读媒体上的故事。如果你使用他的推荐链接,Thu 也会得到一小笔佣金。

https://medium.com/@tdinh15/membership

理解箱线图:数据世界的无限挑战

原文:https://towardsdatascience.com/understanding-boxplot-infinity-gauntlet-of-the-dataverse-cd57cd067711

使用 boxplots 和 Tukey 的方法消除手指(或代码)瞬间的异常值

来自 Flickr 的抄送图片

箱线图可能会让许多初学者望而生畏。

那是因为他们塞满了统计见解!

但是,如果你愿意挖掘得更深一点,它们可以揭示一个信息宝库。箱线图是统计学和数据科学中的强大工具。

有多强大?

这么说吧:

如果灭霸是一名数据科学家,那么 boxplot 将是他的 无限挑战——他有能力将数据汇总成拳头,并且用手指轻轻一弹就能消除异常值!

而就像《复仇者联盟》中,无限的宇宙力量集中在 6 个无限的石头上——将海量数据汇总的力量,浓缩成仅仅 6 个数值。

箱线图直观地表示了这 6 个值。

使用它们,您可以:

  • 获得对数字数据的深刻理解
  • 做一个快速的图形检查
  • 比较数据中的不同组
  • 使用它们来了解并消除数据中的异常值

这就是你所谓的数据超级大国!

让我们看看如何理解箱线图,它用来总结数据的 6 个值,以及如何使用它们通过快照消除异常值!

理解箱线图

既然我们如此彻底地建立了挑战类比,让我们充分利用它来理解无限宝石——呃,对不起,我指的是 6 个重要的价值。

这张图片会让它更清晰!

作者图片

如图所示,取 21 个数字的升序排序列表

  • 小指上的石头代表的最小值是列表中的最小值,在本例中为 1。
  • 最大值是最大值,即 100。
  • 中值是列表正中央的号。50%的数据位于中间值的两边。40 在上面的列表中间。
  • 还有的意思,也在中间。但不是列表的中间,而是在列表中值的 算术中间—它是所有值的总和除以列表中值的计数,在本例中是 40.8。

你们中的大多数人已经知道上面的值,因为它们是非常常用的。但是剩下的两个呢?

  • 第一个四分位数(或 Q1) 是 25%的数据点所在的值。从某种意义上说,它是前半部分数据的中位数。
  • 第三个四分位数(或 Q3)是 75%的数据点所在的值。是后半部分数据的中位数。

(注意:中位数本身也被称为第二个四分位数或 Q2 )

就是这样!

握紧你的拳头,你已经把 21 个数字压缩成 6 个数值。

而且不管是 21 个还是 10 亿,这 6 个数值足以给你很多感悟。

现在让我向您展示这些值是如何可视化表示的。

作者图片

你看这里,我们可以从这个图中得到更多有趣的信息。

  • 四分位数间距(IQR) 是第三个四分位数与第一个四分位数的差值:(Q3 — Q1)。它给出了中间一半数据的范围。

盒子两边的 T 型突起是什么?

它们被称为胡须或栅栏。他们将相关数据与离群值隔离开来。

  • 下围栏计算为 Q1 — (1.5 * IQR)
  • 上围栏按 Q3 + (1.5 * IQR)计算

任何超出这些限制的都是数据中的异常值。

唷!

这是一个图表中的大量信息!

现在,让我们使用 python 获取列表并自动生成图表。使用 Plotly 和 chart-studio 真的很简单。

我已经将该图导出到 chart studio,因此您可以看到下面的交互式版本。

作者在 Plotly Chart Studio 中创建的图表

这就对了,太简单了!

  • 一眼就能看出数据的中值、平均值、范围和异常值
  • 您可以看到 50%的数据位于值 35 和 43 之间
  • 你还可以推断出数据的更多特征。中间线在中线的右边,因此是一个 右偏分布

我希望你现在理解了箱线图及其威力。

但是,等一下,这只是故事的一半。

现在你有了比喻护手的力量,你也需要打响指了!

让我们通过一个真实的例子来看看如何使用它来消除数据中的异常值。

啪!消除异常值

现在您知道了,使用四分位范围计算的上下围栏可以方便地用于从数据中分离异常值。

但是你有没有想过为什么两边的间距是 IQR 的 1.5 倍?

答案在于统计。

根据68–95–99.7 规则,大部分数据(99.7%)位于标准分布两侧平均值的 3 个标准差(< 3σ)以内。它之外的一切都是离群值。

现在,第一个四分位数和第三个四分位数位于均值两侧的 0.675 σ处。

让我们做一些快速的数学运算。

Let X be the multiplying factor we need to calculateLower Fence = Q1 - X * IQR= Q1 - X * (Q3 - Q1)# The lower fence should be at -3σ
# Q3 is 0.675σ and Q1 is -0.675σ-3σ = -0.675σ - X * (0.675σ + 0.675σ)
-3σ = -0.675σ -1.35σXX = 2.325 / 1.35~ 1.7# Similarly, it can be calculated for upper fence too!

我们得到的值大约是 1.7,但是我们用的是 1.5。

用这种方法剔除异常值被称为图基方法。

(这种方法以约翰·图基命名,据说他说选择 1.5 是因为 1 太小,2 太大!)

这是统计学中最简单的方法之一,但是效果出奇的好。

让我们看一个现实生活中的例子,并用 python 构建一个形象化的快照。

我使用了英国 2022 年的公共住房价格数据。

# imports
import chart_studio
import plotly.express as px
import pandas as pd# Housing price data
col_names =    ["transaction_unique_identifier","price","date_of_transfer","postcode","property_type","old/new","duration","PAON", "SAON","street", "locality", "town/city", "district", "county", "PPD_category_type", "record_status_monthly_file_only"]# Read data
df = pd.read_csv('http://prod.publicdata.landregistry.gov.uk.s3-website-eu-west-1.amazonaws.com/pp-2022.txt',header = None, names=col_names)

前几列如下所示:

作者截屏

现在,让我们使用箱线图快速查看大伦敦地区的房产类型及其价格。

# Filter data for the county Greater London
df= df[df["county"] == "GREATER LONDON"]# Boxplot of the fractional data
sns.boxplot(x = df['price'], y = df['property_type'], orient = "h").set(title='Housing Price Distribution')

作者截屏

那看起来很糟糕,不是吗?

有巨大的异常值,以至于我们甚至看不到盒子图中的盒子

这甚至不足以创建一个交互式图表。

让我们利用你对 IQR 的了解创建一个简洁的小脚本。

Python 有分位数函数,获取定义分位数内的数据部分。

# Create a function to get the outliersdef get_outliers_IQR(df):q1=df.quantile(0.25)q3=df.quantile(0.75)IQR=q3-q1lower_fence = q1-1.5*IQRupper_fence = q3+1.5*IQRoutliers = list(df[((df < lower_fence) | (df > upper_fence))])return outliers

让我们将它与我们的数据框一起使用来移除异常值。

(请注意:

# Get outliers from the prices
outliers =  get_outliers_IQR(df['price'])# Remove outliers that we got in the outlier list - aka the Snap!
df_snap_outliers = df[~df['price'].isin(outliers)]

等着吧…

猛咬!

这一次,我们来看看交互式箱线图。

# Create boxplot from the list
fig = px.box(data_snap_outliers, x="price", y="property_type", color="property_type",orientation='h', template = "plotly_white",color_discrete_sequence= px.colors.qualitative.G10,title="House prices of different types of house in London - Sept 2022")# There are many quartile calculation methods. 
# The one we discussed is calculated in plotly with quartilemethod = "inclusive"
fig.update_traces(quartilemethod="inclusive", boxmean = True)# Set margins, font and hoverinfo
fig.update_layout(margin=dict(l=1, r=1, t=30, b=1), font_family="Open Sans", font_size=14,hovermode= "y")
# Show plot
fig.show()

由作者使用 Plotly Chart Studio 创建

看来这一扣成功了!

即使只看一眼,你也能从图表中推断出很多:

  • 每个类别的分布都是右偏的,这意味着更多的房子有更高的价格
  • 与其他组相比,O 类房屋具有更高的可变性和范围
  • D 类房屋通常更贵

牛逼吧?

您应用箱线图来可视化真实世界的住房数据,并使用它们来消除异常值!

改进和总结想法

在这篇文章中,你学习了箱线图和使用无限长手套的类比剔除异常值(我希望你是漫威的粉丝😉).

我已经给了初学者一个简单的解释,但是你可以更进一步,使用这些图做一个深入的分析。

然而,要记住的一件事是箱线图Tukey 方法只是统计学中许多工具和方法中的一部分。

你需要了解它们什么时候最适合使用。

例如,异常值有时可能是有用的,甚至不需要严格消除。

同样,箱线图也不能总是使用。它们有一个缺点,那就是我们无法看到组内有多少个数据点。

这可以通过使用 Plotly 的 boxplot 函数中的“所有点”参数来解决,在这里我们可以看到数据点和盒子在一起,或者通过一种完全不同的图来解决——一个小提琴图

小提琴图给出了数据密度以及箱线图,小提琴的宽度表示数据的频率。

在某种意义上,它是箱线图和直方图的结合。

查看带有小提琴图的房屋类别示例:

# Create boxplot from the list
fig = px.violin(data_snap_outliers, x="price", y="property_type", color="property_type",orientation='h', template = "plotly_white",color_discrete_sequence= px.colors.qualitative.G10,box = True,title="House prices of different types of house in London - Sept 2022")# Set margins, font and hoverinfo
fig.update_layout(margin=dict(l=1, r=1, t=30, b=1), font_family="Open Sans", font_size=14,hovermode= "y")
# Show plot
fig.show()

由作者使用 Plotly Chart Studio 创建

我们在这里可以推断出的一件事是,尽管 O 类的价格范围更大,但与数据密度更高的 F 类相比,该类别的房屋销量更少。

很酷不是吗?

那么,为什么我们从盒子图而不是小提琴图开始呢?

因为首先从盒子情节中获得基础是必要的,因为小提琴情节只是它的一个更好的变体。

不过别担心,我会在另一篇帖子里详细介绍小提琴的剧情!

我希望你喜欢阅读并学到很多东西!写这篇文章让我很开心,如果你有任何反馈,我很乐意收到你的来信。

在那之前,

快乐学习!

鳍。

来源:

http://d-scholarship.pitt.edu/7948/1/Seo.pdf

包含英国皇家土地注册局数据,皇家版权和数据库权利 2021。该数据根据开放政府许可 v3.0 获得许可。根据 OGL,HM 土地注册处允许您将价格支付数据用于商业或非商业目的。此处数据链接:https://www . gov . uk/government/statistical-data-sets/price-payed-data-downloads #单文件

通过实施了解:决策树

原文:https://towardsdatascience.com/understanding-by-implementing-decision-tree-dd395867af7e

建立你自己的模型

了解决策树是如何工作的,并用 Python 实现它

图片由作者提供。

M 任何先进的机器学习模型如随机森林或梯度增强算法如 XGBoost、CatBoost 或 LightGBM(甚至自动编码器!)依赖于一个至关重要的共同因素:T4 决策树!

不理解决策树,就不可能理解前面提到的任何高级 bagging 或梯度推进算法,这对任何数据科学家来说都是一种耻辱!😁因此,让我们通过用 Python 实现一个决策树来揭开决策树内部工作的神秘面纱。

在本文中,您将了解到

  • 决策树为什么以及如何拆分数据,
  • 信息增益,以及
  • 如何用 NumPy 在 Python 中实现决策树?

你可以在 my Github 上找到代码。

该理论

为了进行预测,决策树依靠以递归方式将数据集分割成更小的部分。

图片由作者提供。

在上图中,您可以看到一个分割示例,即原始数据集被分成两部分。在下一步中,这两个部分再次被分割,以此类推。这一直持续到满足某种停止标准,例如,

  • 如果分割导致一部分为空
  • 如果达到了某个递归深度
  • 如果(在之前的分割之后)数据集仅包含少数元素,则无需进一步分割。

我们如何找到这些分裂?我们为什么要在乎呢?让我们找出答案。

动机

让我们假设我们想要解决一个二元 分类问题我们现在自己创建:

import numpy as npnp.random.seed(0)X = np.random.randn(100, 2) # features
y = ((X[:, 0] > 0) * (X[:, 1] < 0)) # labels (0 and 1)

二维数据如下所示:

图片由作者提供。

我们可以看到有两个不同的类别——紫色约占 75%,黄色约占 25%。如果您将这些数据输入决策树分类器,该树最初会有以下想法:

“有两个不同的标签,对我来说太乱了。我想通过将数据分成两部分来清理这种混乱——这些部分应该比之前的完整数据更干净。”—获得意识的树

这棵树也是如此。

图片由作者提供。

树决定大致沿着 x 轴进行分割。这样做的效果是,数据的顶部现在是完全干净的, 意味着你只在那里找到一个单独的类(在本例中为紫色)。

不过底部还是,某种意义上甚至比以前更乱。在完整的数据集中,类的比例曾经是大约 75:25,但是在这个更小的部分中,它是大约 50:50,这是它能得到的最大混合

⚠️ 只是将 两部分中不同标签 的原始数量进行计数。

图片由作者提供。

尽管如此,对于这棵树来说,这已经是足够好的第一步了,所以它会继续下去。虽然它不会在顶部创建另一个分割,清理部分,它可以在底部创建另一个分割来清理它。

图片由作者提供。

瞧,三个独立的部分都是完全干净的,因为我们发现每个部分只有一种颜色(标签)。

现在做预测真的很容易:如果一个新的数据点进来,你只需检查它位于三个部分中的哪一个,并给它相应的颜色。这个现在工作得很好,因为每个部分都很干净。很简单,对吧?

图片由作者提供。

好的,我们正在谈论干净的杂乱的数据,但是到目前为止,这些词仅仅代表一些模糊的概念。为了实现任何事情,我们必须找到定义清洁度的方法。

清洁措施

假设我们有一些标签,例如

y_1 = [0, 0, 0, 0, 0, 0, 0, 0]
y_2 = [1, 0, 0, 0, 0, 0, 1, 0]
y_3 = [1, 0, 1, 1, 0, 0, 1, 0]

直觉上, y ₁是最干净的一组标签,其次是 y ₂,然后是 y ₃.到目前为止一切顺利,但是我们如何对这种行为进行量化呢?可能想到的最简单的事情是下面这些:

数数 0 和 1 的数量。计算它们的绝对差值。为了使它更好,通过除以数组的长度来规范化它。

例如, y ₂总共有 8 个条目——6 个 0 和 2 个 1。因此,我们自定义的清洁度得分将是|6 - 2| / 8 = 0.5。很容易计算出 y ₁和 y ₃的清洁度得分分别为 1.0 和 0.0。在这里,我们可以看到通用公式:

图片由作者提供。

这里, n ₀和 n ₁分别是 0 和 1 的个数,n=n₀+n₁是数组的长度,p₁=n₁/n是 1 个标签的份额

这个公式的问题是是专门为两个类的情况定制的,但是我们通常对多类分类感兴趣。一个非常有效的公式是基尼系数

图片由作者提供。

或者一般情况下:

图片由作者提供。

它工作得非常好,以至于 scikit-learn 将它作为其DecisionTreeClassifier类的默认度量。

图片由作者提供。

⚠️ 注: 基尼系数 脏乱度 而非洁癖。例如:如果一个列表只包含一个类(=非常干净的数据!),则总和中的所有项都为零,因此总和为零。最糟糕的情况是所有类别出现的次数完全相同,在这种情况下,基尼系数是 1–1/C,其中 C 是类别的数量。

现在我们有了一个衡量干净/脏乱的标准,让我们看看如何用它来找到好的分割。

查找拆分

有很多劈叉可供我们选择,但哪一个是好的呢?让我们再次使用我们的初始数据集,连同基尼系数杂质测量。

图片由作者提供。

我们现在不算点数,先假设 75%是紫色,25%是黄色。使用基尼系数的定义,完整数据集的杂质为

图片由作者提供。

如果我们沿着 x 轴分割数据集,如前所述:

图片由作者提供。

顶部的基尼系数为 0.0 ,底部

图片由作者提供。

平均而言,这两部分的基尼系数为(0.0 + 0.5) / 2 = 0.25,比之前整个数据集的 0.375 要好。我们也可以用所谓的信息增益来表达:

这种分裂的信息增益是 0.375–0.25 = 0.125。

就这么简单。信息增益越高(即基尼系数越低),效果越好。

注意: 另一个同样好的初始分割会沿着 y 轴。

需要记住的一个重要的事情是用零件的大小来衡量零件的基尼杂质是有用的。例如,让我们假设

  • 第 1 部分由 50 个数据点组成,基尼系数为 0.0
  • 第二部分由 450 个数据点组成,基尼系数为 0.5,

那么平均基尼杂质就不应该是(0.0 + 0.5) / 2 = 0.25 而是 50/(50+450)* 0.0+450/(50+450)* 0.5 =0.45

好吧,那我们怎么找到最好的分割呢?简单但发人深省的答案是:

尝试所有的拆分,然后选择信息增益最高的一个。这基本上是一种蛮力的方法。

更准确地说,标准决策树使用沿坐标轴的分裂,即 xᵢ = c 用于某些特征 i 和阈值c。这意味着

  • 分割数据的一部分由所有数据点 xxᵢ < c 组成
  • 所有点的另一部分 xxᵢc.

这些简单的拆分规则在实践中已经证明足够好了,但是你当然也可以扩展这个逻辑来创建其他的拆分(例如像 xᵢ + 2xⱼ = 3 这样的对角线)。

太好了,这些都是我们现在需要的原料!

实施

我们现在将实现决策树。因为它由节点组成,所以让我们先定义一个Node类。

from dataclasses import dataclass@dataclass
class Node:feature: int = None # feature for the splitvalue: float = None # split threshold OR final predictionleft: np.array = None # store one part of the dataright: np.array = None # store the other part of the data

节点知道它用于分裂的特征(feature)以及分裂值(value)。value也用作决策树最终预测的存储。由于我们将构建一个二叉树,每个节点需要知道它的左右子节点,正如存储在leftright中的那样。

现在,让我们做实际的决策树实现。我让它兼容 scikit-learn,因此我使用了一些来自sklearn.base的类。如果您对此不熟悉,请查看我关于如何构建 scikit-learn 兼容模型的文章:

来实施吧!

import numpy as np
from sklearn.base import BaseEstimator, ClassifierMixinclass DecisionTreeClassifier(BaseEstimator, ClassifierMixin):def __init__(self):self.root = Node()@staticmethoddef _gini(y):"""Gini impurity."""counts = np.bincount(y)p = counts / counts.sum()return (p * (1 - p)).sum()def _split(self, X, y):"""Bruteforce search over all features and splitting points."""best_information_gain = float("-inf")best_feature = Nonebest_split = Nonefor feature in range(X.shape[1]):split_candidates = np.unique(X[:, feature])for split in split_candidates:left_mask = X[:, feature] < splitX_left, y_left = X[left_mask], y[left_mask]X_right, y_right = X[~left_mask], y[~left_mask]information_gain = self._gini(y) - (len(X_left) / len(X) * self._gini(y_left)+ len(X_right) / len(X) * self._gini(y_right))if information_gain > best_information_gain:best_information_gain = information_gainbest_feature = featurebest_split = splitreturn best_feature, best_splitdef _build_tree(self, X, y):"""The heavy lifting."""feature, split = self._split(X, y)left_mask = X[:, feature] < splitX_left, y_left = X[left_mask], y[left_mask]X_right, y_right = X[~left_mask], y[~left_mask]if len(X_left) == 0 or len(X_right) == 0:return Node(value=np.argmax(np.bincount(y)))else:return Node(feature,split,self._build_tree(X_left, y_left),self._build_tree(X_right, y_right),)def _find_path(self, x, node):"""Given a data point x, walk from the root to the corresponding leaf node. Output its value."""if node.feature == None:return node.valueelse:if x[node.feature] < node.value:return self._find_path(x, node.left)else:return self._find_path(x, node.right)def fit(self, X, y):self.root = self._build_tree(X, y)return selfdef predict(self, X):return np.array([self._find_path(x, self.root) for x in X])

就是这样!你可以做所有你喜欢的关于 scikit 的事情——立即了解:

dt = DecisionTreeClassifier().fit(X, y)
print(dt.score(X, y)) # accuracy# Output
# 1.0

由于树是不规则的,它过度拟合了很多,因此是完美的训练分数。对于看不见的数据,准确性会更差。我们还可以通过

print(dt.root)# Output (prettified manually):
# Node(
#   feature=1,
#   value=-0.14963454032767076,
#   left=Node(
#          feature=0,
#          value=0.04575851730144607,
#          left=Node(
#                 feature=None,
#                 value=0,
#                 left=None,
#                 right=None
#          ),
#          right=Node(
#                  feature=None,
#                  value=1,
#                  left=None,
#                  right=None
#          )
#        ),
#   right=Node(
#           feature=None,
#           value=0,
#           left=None,
#           right=None
#   )
# )

作为一张图片,应该是这样的:

图片由作者提供。

结论

在本文中,我们已经详细了解了决策树是如何工作的。我们从一些模糊但直观的想法开始,然后将它们转化为公式和算法。最终,我们能够从头开始实现一个决策树。

不过需要注意的是:我们的决策树还不能被正则化。通常,我们希望指定如下参数

  • 最大深度
  • 叶子大小
  • 和最小的信息增益

还有很多其他的。幸运的是,这些事情实现起来并不困难,这让它成为你的完美作业。例如,如果您指定leaf_size=10作为参数,那么包含超过 10 个样本的节点就不应该再被分割。此外,这种实现方式效率不高。通常,您不希望在节点中存储数据集的一部分,而是只存储索引。因此,您的(可能很大的)数据集只在内存中存在一次。

好的一面是,现在你可以疯狂地使用这个决策树模板。您可以:

  • 实施对角线分割,即 xᵢ + 2xⱼ = 3 而不是仅仅 xᵢ = 3,
  • 改变叶子内部发生的逻辑,也就是说,你可以在每个叶子内部运行逻辑回归,而不仅仅是进行多数投票,这就给出了一个线性树
  • 改变分裂程序,即不要使用蛮力,尝试一些随机组合并挑选最好的一个,这给了你一个额外树分类器
  • 还有更多。

此外,检查我的基本机器学习算法的其他实现!

我希望你今天学到了新的、有趣的、有用的东西。感谢阅读!

作为最后一点,如果你

  1. 想支持我多写点机器学习和
  2. 无论如何都要计划获得中等订阅量,

为什么不做 通过这个环节 ?这将对我帮助很大!😊

透明地说,给你的价格不变,但大约一半的订阅费直接归我。

非常感谢,如果你考虑支持我的话!

有问题就在LinkedIn上写我!

了解气候变化

原文:https://towardsdatascience.com/understanding-climate-variability-d987097b5bc7

一个简单的气候模型、统计和时间序列分析能有什么帮助

为了预测和减轻气候危机的影响,我们需要了解一段时间内全球变暖的平均值以及与平均值的偏差。来自物理学和数学的工具可以帮助我们分析气候的波动,也称为气候可变性。

数据来自气候观测和气候模型模拟。不幸的是,仪器观测只能追溯到工业革命前后。大多数气候模型模拟非常复杂,需要超级计算能力。所以,我们想知道:用一个简单的数学模型,我们能多好地再现全球温度的变化?在我们最近的出版物中,我们使用物理学、统计学和时间序列分析的方法来解决这个问题。

由 NASA 在 Unsplash 上拍摄的照片

气候变率的类型

有外部驱动的(所谓的“强迫”)和内部的气候变化。外部驱动因素(“作用力”)是指影响地球能量平衡的任何过程。它们可能发生在地球外部(如太阳强度的变化),或当前气候外部(如火山爆发或人类温室气体排放)。)

相反,内部变化主要是由混乱的大气或海洋过程引起的。这包括我们的日常天气,但也包括更缓慢变化的波动,如厄尔尼诺-南方涛动。

在我们的论文中,我们提出了一种方法来区分外部驱动(“强迫”)和内部变化对全球年平均气温的影响。为此,我们开发了一个统计工具箱,也可以在 GitHub 上免费获得(R-package“ClimBayes”和论文的代码)。此外,我们讨论不同时间尺度上的可变性,并区分哪些过程与短期预测最相关,哪些过程与长期预测最相关。

我们的工具箱

我们的分析结合了三种方法:简单模型(1)、统计拟合(2)和波动分析(3)。

1)简单气候模型

第一个工具是一个非常简单的气候模型,即所谓的能量平衡模型。它根据进出辐射的平衡来计算全球年平均温度。在该模型中,地球由两层海洋组成:上层储存和释放热量的速度相当快,加上深层的热交换速度很慢。

在数学上,这个模型可以用一个微分方程来表示,只有两行。该方程包括确定性部分和随机部分。确定性部分将产生强制可变性,而随机性导致随机内部可变性。

两箱能量平衡模型。

下图展示了 1850-2000 年间模型可变性的一个例子。不同营力的时间演变作为模型的输入(图 a 和 b)。作为输出,该模型给出了强制和内部可变性的单独估计。强迫变率(图 c)显示,由于人为排放,过去 100 年有明显上升趋势。较小的下降是由火山爆发引起的——因为大气中更多的反光气溶胶导致短暂的冷却期。对内部可变性的最佳猜测不仅仅是单一的时间序列,而是随机样本,即可变性可能如何的可能性(图 d)。将两个估计值相加得出模型模拟的总可变性(图 e)。根据我们的简单模型,三条红色/橙色线中的每一条都同样可能是温度可能如何演变的选项。

根据 1850-2000 年期间的简单气候模型的可变性的例子。

2)用数据拟合简单模型

我们将简单的模型与更复杂的气候模型的模拟进行比较。我们的目标是了解我们的简单方法能在多大程度上逼近更复杂的方法。为了进行分析,我们需要一种统计方法来将简单模型与其他温度数据进行拟合。这是我们的第二个工具:我们的“拟合方法”优化简单模型中的参数,使其与数据最佳匹配。

对专家来说:我们用马尔可夫链蒙特卡罗算法来做这件事,这是一种来自贝叶斯统计的工具。它根据温度数据计算模型参数的所谓后验分布。

为了举例说明这种拟合,我们再次考虑过去 150 年(见下图)。这种情况下的温度数据是观测值(灰色),即气象站的测量值。在左图中,这个简单的模型还没有与数据相吻合,它大大低估了变暖的趋势。在右边的面板中,模型(蓝色,为了更好的可视性,只显示了强制变化)与观测数据相符合,我们看到它可以更好地再现温度的上升。

1850 年至 2000 年期间“用简单模型拟合数据”的例子。

3)调查时标上的波动

我们的最后一步是比较简单模型和复杂模型中的时间波动。为此,我们使用一种方法来研究不同时间尺度上的可变性。这里进入我们的第三个工具:光谱分析。它以一个时间序列作为输入,并将波动量化为时间尺度的函数。

光谱分析的工作原理就像一个棱镜,将白光分解成许多不同的彩色成分,与不同的波长相关联。同样,温度的光谱可以揭示不同“时间长度”的波动,如几天、几个月、几年或几十年。

通过累计一定时间间隔内的波动,我们获得了对几年、几十年、几十年和几个世纪的波动的估计。

为了介绍这个概念,我们在下一个图中展示了一个例子:从两个人工时间序列(左图)到波动(中图)及其比率(右图)。

两个人工时间序列的波动分析示例。

顶部示例中的时间序列在十年左右的时间尺度上比几十年的可变性更大。较低的时间序列是从较高的时间序列构建的,但有一个额外的缓慢变化的正弦模式。因此,它在几十年的时间尺度上具有更高的波动性。

为了比较两个时间序列的波动值,我们可以计算它们的比率:它显示在右边的图中,是顶部波动除以底部波动的比率。在这种情况下,较短时间范围内的比率大于 1,较长时间范围内的比率小于 1。这意味着我们的频谱分析成功地检测到了底部信号中缓慢变化的调制。

我们的简单模型如何描述气候变化

我们现在有一个工具箱,由三个工具组成。首先,一个简单的气候模型,它计算强迫和内部气候变化的估计。第二,将简单的气候模型与更复杂的气候模型的数据进行拟合的方法。第三,光谱分析,这有助于我们在不同的时间尺度上分解时间序列中的波动。据我们所知,我们是第一个以这种方式组合这些工具的人,所以建立这个工具箱已经很令人兴奋了。但是我们能用这些工具做什么呢?让我们最后来看看这篇论文的主要结果:

我们使用这个工具箱,并将其应用于上一个千年(850-1850)的模拟温度数据。这是我们有大量气候模型模拟和营力重建的最长时期,需要作为模型输入(不包括 1850-2020 年仅仅是技术原因)

在此期间,由于频繁的大规模火山爆发,气候变化主要是由短暂的冷却期造成的,如下例中的许多峰值所示。我们展示了一个复杂气候模型的模拟(灰色)和我们的简单气候模型(蓝色代表强迫变率,红色/橙色代表总变率样本)。在本文中,我们研究了几个复杂的气候模型,并为每个模型绘制了一个这样的图。

一个复杂气候模式的模拟和拟合简单模式的变率。参见主文件中的图 7。

为了分析波动,我们应用上面解释的频谱分析:我们计算简单模型的强制可变性(上面的蓝色曲线)、简单模型的强制+内部可变性(上面的红色/橙色曲线)以及复杂模型的不同时间尺度的值。接下来,我们将简单模型的波动除以复杂模型的波动,得到以下比率:

简单气候模型(蓝色:强迫变率/橙色:强迫+内部变率)与复杂气候模型的波动比。参见主文件中的图 4。

每个点代表一个复杂气候模型的一个实验。圆点对应于简单模式的强迫变率和复杂气候模式的比率(蓝色圆点),或者简单模式的强迫+内部变率和复杂气候模式的比率(橙色圆点)。条形图显示了各个点的平均值。

我们发现,在最短的时间尺度上,强制变量(蓝色点/条)和总变量(橙色点/条)之间的差异最大。这意味着内部变化在最短的时间尺度上最重要,这在其他研究中也有发现。

最重要的是,我们观察到强制变量+内部变量(橙色柱)的平均比率在所有时间尺度上都接近 1。这意味着我们的简单模型运行良好!最大的差异(图中点的最大分布)发生在较短的时间尺度上,此时内部变化主导了来自营力的影响。这并不奇怪,因为我们的简单模型对内部变化有一个非常简单的描述——太简单而不能正确地近似大气过程。更令人惊讶的是,对于长期波动,我们的简单模型提供了一个不错的近似值。这并不是说我们应该停止运行更复杂的气候模型。我们的简单模型仅限于全球年平均温度。对于局部效应和其他气候参数,如降雨量和风力,综合模型是不可替代的。

总之,我们的论文展示了物理学、统计学和时间序列分析工具的成功结合,以调查气候变化。使用这些,我们看到,对于过去一千年的全球年平均温度,一个简单的气候模型可以以合理的精度描述更复杂的气候模型的可变性。手稿包括更多方面,比如不同复杂程度的气候模型的比较。可以在 Chaos 上找到发表的,GitHub 上的代码(R-package“ClimBayes”和代码为论文)。去看看吧,感谢你的阅读!**

最后,感谢我的合著者 Beatrice Ellerhoff、Robert Scheichl 和 Kira Rehfeld、整个 SPACY 团队以及 Beatrice 、Jonathan 和 Jeff 对我的初稿提出了非常有用的意见。

来源:

  • 除特别注明外,所有图片均为作者所有。
  • 1850-2000 年的温度数据:C. P. Morice、J. J. Kennedy、N. A. Rayner、J. P.Winn、E. Hogan、R. E. Killick、R. J. H. Dunn、T. J. Osborn、P. D. Jones 和 I. R. Simpson,“1850 年以来近地表温度变化的更新评估:HadCRUT5 数据集”。研究:大气。126、e2019JD032361、【https://doi.org/10.1029/2019JD032361】https://doi.org/10.1029/2019JD032361 (2021)。
  • 强迫数据:G. A. Schmidt、J. H. Jungclaus、C. M. Ammann、E. Bard、p .布拉科诺、T. J. Crowley、G. Delaygue、F. Joos、N. A. Krivova、R. Muscheler、B. L. Otto-Bliesner、J. Pongratz、D. T. Shindell、S. K. Solanki、F. Steinhilber 和 L. E. A. Vieira,“最近一次气候强迫重建用于 PMIP 模拟”模型开发。5, 185–191 (2012).
  • 这里使用的复杂气候模型数据(HadCM3 模型):A. P. Schurer,S. F. B. Tett,G. C. Hegerl,“过去一千年太阳变率对气候的微小影响”,Nat。乔斯奇。7, 104–108 (2014).

理解条件方差和条件协方差

原文:https://towardsdatascience.com/understanding-conditional-variance-and-conditional-covariance-8b661067fc18

(图片由作者提供)(数据集来源:加州大学欧文分校)

以及如何使用真实数据集计算它们的教程

条件方差和条件协方差是统计建模的核心概念。在本文中,我们将了解它们是什么,并说明如何使用真实世界的数据集来计算它们。

首先,快速回顾一下什么是方差和协方差。

随机变量的方差衡量其围绕均值的变化。两个随机变量之间的协方差是对它们各自均值变化的相关程度的度量。

条件方差

随机变量 X条件方差是通过 X 与其他随机变量 YXW…等的关联来“解释掉”一部分变量后留下多少变化的度量。

用符号形式表示为Var(X|Y X W并读作×的方差

首先,让我们陈述无条件(总)方差的公式:

X 的样本方差公式(图片由作者提供)

上式中,E(X)X 的“无条件”期望(均值)。

条件方差的公式是通过简单地将无条件期望替换为条件期望而获得的,如下所示(注意,在等式(2)中,我们现在计算的是 Y (不是 X ):

X 为条件的 Y 样本方差公式(图片由作者提供)

**E(Y|X)是对因变量为 Y 、解释变量为 X 的数据集拟合回归模型预测的 Y 的值。索引 i 隐含在条件期望中,即对于数据集中的每一行 i ,我们使用E(Y= Y _ I |X= X _ I)

这里,我们对回归模型的选择很重要。模型的正确选择将导致 Y 中的大量方差由拟合的模型来解释,因此 YX 的条件方差将相应较小。另一方面,由于模型无法解释 Y 中的大部分方差,因此模型选择不正确会导致较大的条件方差。

上述条件方差公式可以扩展到多个变量,方差以使用回归模型为条件,其中 X 矩阵包含多个回归变量。

说明

让我们用一些真实世界的数据来说明计算条件方差的过程。下面的数据集包含 205 辆汽车的规格,摘自 1985 年版的沃德汽车年鉴。每行包含一组 26 个关于单个车辆的规格。

汽车数据集(来源:加州大学欧文分校)

我们将考虑这个数据集的一个小子集,它由以下六个变量组成:
City_MPG
整备质量
Vehicle _ Volume
Num _ Cylinders
Vehicle _ Price
发动机 _Size

这个 6 变量数据集可以从这里 下载

让我们画出发动机大小与气缸数的关系图。我们将使用 Python、Pandas 和 Matplotib 包将数据加载到 DataFrame 中并显示绘图:

让我们导入所有需要的包,包括我们将在本文后面使用的包。

***import** pandas **as** pd
**from** patsy **import** dmatrices
**import** numpy **as** np
**import** scipy.stats
**import** statsmodels.api **as** sm
**import** matplotlib.pyplot **as** plt*

现在,让我们将数据文件加载到 Pandas 数据框架中,并绘制引擎大小与汽缸数量的关系图。

****#Read the automobiles dataset into a Pandas DataFrame***df = pd.read_csv(**'automobile_uciml_6vars.csv'**, header=0)***#Drop all empty rows***df = df.**dropna**()***#Plot Engine_Size versus Num_Cylinders***fig = plt.figure()
fig.suptitle(**'Engine_Size versus Num_Cylinders'**)
plt.xlabel(**'Num_Cylinders'**)
plt.ylabel(**'Engine_Size'**)
plt.scatter(df[**'Num_Cylinders'**], df[**'Engine_Size'**])
*#Plot a horizontal mean line* plt.plot([0, df[**'Num_Cylinders'**].max()], [df[**'Engine_Size'**].mean(), df[**'Engine_Size'**].mean()],[df[**'Engine_Size'**].mean()], color=**'red'**, linestyle=**'dashed'**)***#Group the DataFrame by Num_Cylinders and calculate the mean for each group***df_grouped_means = df.groupby([**'Num_Cylinders'**]).mean()***#Print out all the grouped means***df_grouped_means = df.groupby([**'Num_Cylinders'**]).mean()***#Plot the group-specific means of Engine_Size*****for** i **in** df_grouped_means.index:mean = df_grouped_means[**'Engine_Size'**].loc[i]plt.plot(i, mean, color=**'red'**, marker=**'o'**)plt.show()*

这是分组平均值表,即以 Num_Cylinders 的不同值为条件的平均值。

分组均值表(条件均值)(图片由作者提供)

我们还可以看到下面的曲线图,它显示了不同数值的 Num_Cylinders 中 Engine_Size 的变化:

引擎大小与气缸数的散点图,显示引擎大小的无条件平均值和条件平均值(图片由作者提供)

红色水平线表示无条件均值值 126.91。红点表示不同 Num_Cylinders 值的平均发动机尺寸。这些是条件的意思,也称为对于 Num_Cylinders 的不同值的发动机 _ 大小的条件期望,它们被表示为 E( 发动机 _ 大小|Num _ Cylinders =x)

引擎大小的无条件(总)方差

让我们重温一下 X 的总方差公式:

X 的样本方差公式(图片由作者提供)

上式中,若X= Engine _ Size,则由E()X)表示的平均值为126.88。利用这个公式,我们计算出发动机尺寸的样本方差为 1726.14 。这是围绕 126.91 的无条件期望值的 Engine_Size 变化的度量。

在熊猫中,我们可以得到总方差的值如下:

*unconditional_variance_engine_size = df[**'Engine_Size'**].var()print(**'(Unconditional) sample variance in Engine_Size='**+str(unconditional_variance_engine_size))*

我们看到以下输出:

*Unconditional variance in Engine_Size=**1726.1394527363163***

引擎大小的条件方差

以 Num_Cylinders 为条件的 Engine_Size 的方差是 Engine_Size 中剩余的方差,其中一些已经由 Num_Cylinders 上的 Engine_Size 的回归“解释”。我们可以使用等式(2)计算如下:

发动机尺寸的变化取决于气缸数(图片由作者提供)

现在让我们看一个稍微复杂一点的例子。

假设我们希望根据整备质量、车辆体积和气缸数量计算发动机尺寸的变化。

为此,我们使用以下步骤:

  1. 构建一个回归模型,其中响应变量为发动机尺寸,回归变量为整备质量、车辆体积、气缸数量和截距。
  2. 在数据集上训练模型。
  3. 在数据集上运行训练的模型,以获得整备质量、车辆体积、汽缸数量的每个组合的发动机尺寸的预测(预期)值。这些是条件期望的集合:
    E(Engine_Size | Curb _ Weight,Vehicle_Volume,Num_Cylinders) 对应于 Engine _ Size 的观测值。
  4. 将 Engine_Size 的观测值和步骤 2 中计算的预测值代入等式(2)以获得条件方差。

让我们来计算一下!

****#Construct the regression expression. A regression intercept is included by default***olsr_expr = **'Engine_Size ~ Curb_Weight + Vehicle_Volume + Num_Cylinders'*****#Carve out the y and X matrices based on the regression expression***y, X = dmatrices(olsr_expr, df, return_type=**'dataframe'**)***#Build the OLS linear regression model***olsr_model = sm.OLS(endog=y, exog=X)
***#Train the model***olsr_model_results = olsr_model.fit()***#Make the predictions on the training data set. These are the conditional expectations of y***
y_pred=olsr_model_results.predict(X)
y_pred=np.array(y_pred)***#Convert y from a Pandas DataFrame into an array***
y=np.array(y[**'Engine_Size'**])***#Calculate the conditional variance in Engine_Size using equation (2)***
conditional_variance_engine_size = np.sum(np.square(y-y_pred))/(len(y)-1)print(**'Conditional variance in Engine_Size='**+str(conditional_variance_engine_size))*

我们得到以下输出:

*Conditional variance in Engine_Size=**167.42578329039935***

正如预期的那样,这个 167.43 的方差大大小于 Engine_Size 的总方差( 1726.13 )。

条件方差与 R 平方的关系

线性回归模型的 r 平方是解释变量能够“解释”的因变量中总方差的分数。

线性回归模型的 R 平方的定义(图片由作者提供)

我们现在知道,中的方差即X能够解释的是条件方差Var(y|X)。而 y 中的总方差,简单来说就是无条件方差Var(y)。因此,R 平方可以用条件方差和无条件方差表示如下:****

y 中的条件方差和无条件方差表示的 r 平方(图片由作者提供)

让我们为之前构建的线性回归模型计算 R 平方。回想一下,因变量 y 是发动机尺寸,而解释变量 X 是整备质量、车辆体积和汽缸数量。

发现 y 中的总方差为163。36363.666666666665

y 中的条件方差,即 y 中以整备质量、车辆体积和气缸数为条件的方差被发现为167.429039935

使用等式(4),该线性模型的 R 平方为:

R 平方= 1–167.43/1726.14 = 0.903

该值与 statsmodels 报告的值完全匹配:

线性回归模型的模型训练摘要(图片由作者提供)

条件协方差

记住,两个随机变量 XZ 之间的协方差是对 XZ 中的变量相互之间相关程度的度量。其公式如下:

X 和 Z 之间的样本协方差公式(图片由作者提供)

在这个公式中,E(X)E(Z)X的无条件手段(又名无条件期望)。

协方差XZ条件依赖于一些随机变量W 是围绕 的条件期望的XZ的变化的相关程度的度量******

XZ 之间的样本条件协方差公式(图片由作者提供)

E(X|W)E(Z | W)*是 X 的条件期望于是(X _ I—E(X|W)X 中的变异后的一部分已经用 W 解释过了。同上(Z _ I—E(Z|W)。索引 i 隐含在两个条件期望中,即对于数据集中的每一行 i ,我们使用E()X= X _ I |W= W _ I)***

因此,条件协方差是在通过【W】的存在解释了一些各自的方差之后,【Z】的变化的相关程度的度量。**

与计算条件方差的过程一样,我们可以通过回归上的X来估计条件期望E(X|W)E(Z | W)各自的回归模型对训练数据集的预测就是我们所寻求的对应的条件期望E(X|W)E(Z | W)**

说明

我们将根据车辆体积计算发动机尺寸和整备质量之间的协方差。

首先,我们将通过计算 Engine_Size 和 Curb_Weight 之间的无条件(总)协方差来确定方差的基线。这可以使用如下等式(5)来容易地完成:

发动机尺寸和整备质量之间的总协方差公式(图片由作者提供)

使用 Pandas,我们可以如下计算协方差:

**covariance = df['Curb_Weight'].**cov**(df['Engine_Size'])**

我们看到以下输出:

**Covariance between Curb_Weight and Engine_Size=**18248.28333333333****

我们还可以查看以平均值为中心的 Engine_Size 和以平均值为中心的 Curb_Weight 的散点图,以直观感受这种协方差:

*****#Plot mean-centered Curb_Weight versus Engine_Size***fig = plt.figure()fig.suptitle(**'Mean centered Curb_Weight versus Engine_Size'**)plt.xlabel(**'Mean centered Engine_Size'**)plt.ylabel(**'Mean centered Curb_Weight'**)plt.scatter(df[**'Engine_Size'**]-df[**'Engine_Size'**].**mean**(), df[**'Curb_Weight'**]-df[**'Curb_Weight'**].**mean**())plt.show()**

我们看到下面的情节:

平均中心(贬低的)整备质量与贬低的车辆发动机尺寸散点图(图片由作者提供)

我们在该图中立即注意到的一件事是,对于发动机尺寸相似的车辆,整备质量似乎有很大差异:

整备质量变化与车辆平均中心发动机尺寸的散点图,显示了具有相似发动机尺寸的车辆之间整备质量的广泛变化(图片由作者提供)

还有其他因素可以解释特定发动机尺寸内整备重量的差异。

让我们看看汽车的体积作为这样一个因素。具体来说,让我们根据车辆体积计算整备质量和发动机尺寸之间的协方差,即剔除车辆体积的影响。

发动机尺寸和整备质量之间的协方差公式取决于车辆体积(图片由作者提供)

在上面的公式中,用绿色标记的两个条件期望可以通过回归车辆体积上的发动机尺寸和车辆体积上的整备质量来获得。和以前一样,指数 i 隐含在两个期望中。

使用 Pandas 和 statsmodels,让我们如下计算这个条件协方差。在下面这段代码中,X=发动机尺寸,Z=整备质量,W=车辆体积。

*****#Carve out the X and W matrices****.* ***An intercept is automatically added to W.***X, W = dmatrices(**'Engine_Size ~ Vehicle_Volume'**, df, return_type=**'dataframe'**)***#Regress X on W***olsr_model_XW = sm.**OLS**(**endog**=X, **exog**=W)
olsr_model_XW_results = olsr_model_XW.**fit**()***#Get the conditional expectations E(X|W)***
X_pred=olsr_model_XW_results.**predict**(W)
X_pred=np.**array**(X_pred)X=np.**array**(df[**'Engine_Size'**])***#Carve out the Z and W matrices***Z, W = dmatrices(**'Curb_Weight ~ Vehicle_Volume'**, df, return_type=**'dataframe'**)***#Regress Z on W***olsr_model_ZW = sm.OLS(endog=Z, exog=W)
olsr_model_ZW_results = olsr_model_ZW.fit()***#Get the conditional expectations E(Z|W)*** Z_pred=olsr_model_ZW_results.predict(W)
Z_pred=np.array(Z_pred)Z=np.array(df[**'Curb_Weight'**])***#Construct the delta matrices***Z_delta=Z-Z_pred
X_delta=X-X_pred***#Calculate the conditional covariance***conditional_variance = np.**sum**(Z_delta*X_delta)/(len(Z)-1)print(**'Conditional Covariance between Curb_Weight and Engine_Size='**+str(conditional_variance))**

我们看到以下输出:

**Conditional Covariance between Curb_Weight and Engine_Size=**7789.498082862661****

如果我们将 7789.5 的这个值与之前计算的 18248.28 的总协方差进行比较,我们会看到发动机尺寸和扣除车辆体积影响的整备质量之间的协方差确实比没有车辆体积影响的情况小得多。

下面是本文中使用的完整源代码:

参考文献、引文和版权

数据集

汽车数据集 引用: Dua,d .和 Graff,C. (2019)。UCI 机器学习知识库[http://archive . ics . UCI . edu/ml]。加州欧文:加州大学信息与计算机科学学院。 下载链接

如果你喜欢这篇文章,请关注我的Sachin Date获取关于回归、时间序列分析和预测主题的提示、操作方法和编程建议。**

了解污染偏差

原文:https://towardsdatascience.com/understanding-contamination-bias-58b63d25d2ef

因果数据科学

多重处理的线性回归问题及解决方法

作者图片

在许多因果推理环境中,我们可能对不止一种治疗的效果感兴趣,而是对许多互斥治疗的效果感兴趣。例如,我们可能想要测试替代的 UX 设计,或者药物,或者政策。根据具体情况,我们可能有很多原因想要同时测试不同的治疗方法,但一般来说,这可以帮助减少样本量,因为我们只需要一个对照组。恢复不同治疗效果的简单方法是对不同治疗指标的感兴趣结果进行线性回归。

然而,在因果推断中,我们通常将其他可观察变量(通常称为控制变量)作为分析的条件,以增加功效,或者特别是在准实验设置中,确定一个因果参数而不是简单的相关性。存在增加控制变量会适得其反的情况,但除此之外,我们曾经认为线性回归恢复了平均治疗效果。

在一篇突破性的论文中,戈德史密斯-平克汉姆、赫尔和科勒萨尔(2022) 最近表明,在多重且互斥的处理的情况下,并且在控制变量存在的情况下,回归系数不能识别因果关系。然而,并不是所有的东西都丢失了:作者提出了一个简单的解决这个问题的方法,仍然是利用线性回归。

在这篇博文中,我将通过一个简单的例子来说明问题的本质以及作者提出的解决方案。

多重治疗示例

假设我们是一家在线商店,我们对当前的结账页面不满意。特别是,我们想改变我们的结账按钮,以增加购买的可能性。我们的 UX 设计师提出了两个可供选择的结帐按钮,如下所示。

默认选项和替代处理,作者图片

为了了解使用哪个按钮,我们运行一个 A/B 测试 ,或随机对照试验。特别是,当人们到达结账页面时,我们会随机向他们展示三个选项中的一个。然后,对于每个用户,我们记录产生的收入,这是我们感兴趣的结果。

我使用来自[src.dgp](https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/src/dgp.py)dgp_buttons()作为数据生成过程来生成合成数据集。我也从[src.utils](https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/src/utils.py)引进绘图函数和标准库。

from src.utils import *
from src.dgp import dgp_buttonsdgp = dgp_buttons()
df = dgp.generate_data()
df.head()

数据快照,图片由作者提供

我们有 1000 个用户的信息,我们观察他们的结帐按钮(defaultbutton1button2)、他们生成的revenue以及他们是从桌面还是mobile访问页面。

我们注意到随机化的问题时已经太晚了。我们更频繁地向桌面用户展示button1,向移动用户展示button2。看到default按钮的控制组是平衡的。

治疗分配不平衡,作者图片

我们做什么呢如果我们简单地比较revenuegroups会发生什么?让我们通过在group虚拟变量上回归revenue来实现。

smf.ols('revenue ~ group', data=df).fit().summary().tables[1]

回归摘要,按作者分类的图像

从回归结果来看,我们估计这两个按钮在 5%的水平上有显著的负面影响。我们应该相信这些估计吗?它们是因果吗?

我们所估计的不太可能是真实的治疗效果。

事实上,台式机和手机用户的购买态度可能会有很大的差异。由于我们没有跨治疗部门的可比较的移动和桌面用户数量,可能观察到的revenue差异是由于使用的装置而不是按钮设计

因此,我们决定对所用设备的分析进行条件化,并在回归中加入mobile虚拟变量。

smf.ols('revenue ~ group + mobile', data=df).fit().summary().tables[1]

回归摘要,按作者分类的图像

现在button1的系数为正且显著。我们应该建议实施它吗?

答案是想不到没有。戈德史密斯-平克汉姆、赫尔、科勒萨尔(2022) 表明,这种类型的回归不能确定平均治疗效果,当:

  • 有互斥的治疗分支(在我们的例子中,groups)
  • 我们正在控制某个变量 X (在我们的例子中是mobile)
  • X 中有治疗效果不一

这是真的,即使一旦我们以 X 为条件,处理“和随机一样好”。

事实上,在我们的案例中,真正的治疗效果在下表中报告。

真实平均治疗效果,图片由作者提供

第一个按钮对收入没有影响,与设备无关,而第二个按钮对移动用户有正面影响,对桌面用户有负面影响。相反,我们的(错误的)回归规范估计了第一个按钮的积极影响。

现在让我们更详细地挖掘数学,以理解为什么会发生这种情况。

理论

这一节大量借用了戈德史密斯-平克姆,赫尔,科勒萨尔(2022) 。对于这篇论文的一个很好的总结,我推荐作者之一保罗·戈德史密斯-平克汉姆的这篇精彩的 Twitter 帖子。

关于污染偏见的推特帖子

单一治疗臂

假设我们对治疗对结果的影响感兴趣。首先,让我们考虑一个单治疗臂的标准情况,这样治疗变量是二元的, D∈{0,1} 。同样,考虑单个二元控制变量 X∈{0,1} 。我们还假设治疗分配与随机分配一样好,有条件地取决于 X 。这种假设被称为https://en.wikipedia.org/wiki/Ignorability,它意味着治疗组和对照组之间可能存在系统性差异,然而,这些差异被 X 完全解释。我们正式写作

条件可忽略性假设,作者的图像

其中 Yᵢ(d) 表示当个体 i 的治疗状态为 d 时的潜在结果。例如, Yᵢ(0) 表示个体 i 如果得不到治疗的潜在结果。这个符号来自鲁宾的潜在结果框架。我们可以将个人 i个人治疗效果写成

个人治疗效果,图片由作者提供

在此设置中,兴趣回归为****

回归规范,作者图片

兴趣系数为 β

安格里斯特(1998) 表明回归系数 β 标识平均处理效果。特别地, β 标识了具有凸权重的组内 x 平均治疗效果 τ(x) 的加权平均值。在这种特殊的情况下,我们可以把它写成

系数分解,作者图片

权重 λ(1-λ) 由组内处理方差给出。因此,OLS 估计器对治疗差异较小的组给予较小的权重,即治疗更不平衡的组。对半分配治疗的小组获得的权重最大。****

按作者分组权重、图像

可以使用弗里希-沃夫-洛厄尔定理推导出权重,将 β₁ 表示为 Ydᵢ₁(⊥x的一元回归的 OLS 系数,其中dᵢ₁(⊥x是回归 D₁X 的残差。如果你不熟悉弗里希-沃-洛厄尔定理,我在这里写了一篇介绍性的博文。

使用 FWL 定理的系数分解,图片由作者提供

中心表达式的第二项消失了,因为剩余的 Dᵢ₁(⊥X) 根据构造意味着控制变量 X 的独立,即

意味着独立,作者的形象

这种平均独立性对于获得无偏估计值至关重要,在多重治疗情况下,这种特性的失败是污染偏差的来源。

多个治疗臂

现在让我们考虑多个处理分支的情况, D∈{0,1,2} ,其中 12 表示两个互斥的处理。我们仍然假设条件可忽略性,即治疗分配与随机一样好,以 X 为条件。

条件可忽略性假设,作者的图像

在这种情况下,我们有两个不同的个体治疗效果,每个治疗一个。

个人治疗效果,图片由作者提供

****回归的利息

回归规范,作者图片

线性回归系数 β₁β₂ 确定了平均治疗效果吗?

说“是”会很有诱惑力。事实上,相对于之前的设置,看起来没有太大的变化。我们只有一种额外的治疗,但潜在的结果仍然是有条件地独立于它。然而,事实并非如此。

线性回归系数不能确定平均治疗效果。

****问题在哪里?

让我们专注于 β₁ (同样适用于 β₂ )。如前所述,可以使用弗里希-沃-洛厄尔定理重写 β₁ 作为 Yᵢdᵢ₁(⊥xd₂的单变量回归的 OLS 系数,其中dᵢ₁(⊥xd₂是回归 D₁D₂X 的残差。

使用 FWL 定理的系数分解,图片由作者提供

题**是上学期。没有最后一项,我们仍然可以把 β₁ 写成个体治疗效果的凸组合。然而,最后一项通过添加一个依赖于d₂t49】、 τ₂ 的治疗效果的成分来使估计量产生偏差。为什么这个术语没有消失?

问题是, Dᵢ₁(⊥X,并不意味着独立于 Dᵢ₂ ,即

违反平均独立性,作者的形象

原因在于这些处理是互斥的。这意味着当 Dᵢ₁=1Dᵢ₂ 必须为零,不管 Xᵢ 的值是多少。因此,最后一项没有抵消,它引入了污染偏差

解决办法

戈德史密斯-平克姆,赫尔,科勒萨尔(2022) 对问题提出不同的解决方案。最简单的方法是由 Imbens 和 Wooldridge (2009) 首先提出的,只需要一个线性回归。程序如下。

  1. 去均值控制变量:x̃= xx̅
  2. 回归处理指标 D 和降低的控制变量 之间的相互作用 Y

β₁β₂ 的 OLS 估计量是平均治疗效果的无偏估计量。这个估计器对于连续控制变量 X 也是无偏的,而不仅仅是对于我们到目前为止所考虑的二元变量。

为什么这个估计量最初是由Imbens and Wooldridge(2009)提出的?让我们考虑一个具有单一处理 D 的模型,让我们分别分析两个部分:在 DX 之间的相互作用项和在相互作用项中 X去含义的事实。

首先,交互项** DX 允许我们控制 X 在治疗组和对照组中的不同效果和/或分布。事实上,这相当于为治疗组和对照组估计一个单独的 YX 的回归。**

第二,交互作用术语中的去含义** X 允许我们估计系数 β̂ 解释为平均处理效果。事实上,假设我们正在估计下面的线性模型,其中 X而不是在相互作用项中的含义。**

回归规范,自动生成图像

在这种情况下, DY 的边际效应是 β+δXᵢ ,因此平均边际效应是 β + δX̅ ,这与 β 不同。

如果取而代之,我们使用交互项中的 X 的均值,则 DY 的边际效应为β+δ(xᵢ-x̅̅),这样Y 的平均边际效应为 β

模拟

为了更好地理解问题和解决方案,让我们运行一些模拟

我们对来自数据生成过程dgp_buttons()的不同绘制运行一个估计器。请注意,这只有在合成数据的情况下才有可能,而我们在现实中没有这种奢侈。对于每个样本,我们记录估计系数和相应的 p 值。

首先,让我们用旧的估算器来试试,它对groupmobile虚拟变量都回归了revenue

ols_estimator = lambda x: smf.ols('revenue ~ group + mobile', data=x).fit()
results = simulate(dgp, ols_estimator)

我绘制了超过 1000 次模拟的button1的系数估计的分布,突出了在 5%水平上的统计显著性。我还用一条垂直的虚线突出显示了系数的真实值零。****

plot_results(results)

作者图片

如我们所见,我们拒绝了 45%的模拟中button1没有影响的零假设。由于我们设定了 5%的置信水平,我们预计最多会有 5%左右的拒绝。我们的估计量是有偏的。

正如我们在上面所看到的,问题是估计器不是移动和桌面用户的button1效应的凸组合(对两者都是零),而是被button2效应的污染了

现在让我们试试 Imbens 和 Wooldridge (2009) 提出的估计量。首先,我们需要定义我们的控制变量mobile。然后,我们根据group和非均值控制变量res_mobile之间的相互作用回归revenue

df['mobile_res'] = df['mobile'] - np.mean(df['mobile'])
smf.ols('revenue ~ group * mobile_res', data=df).fit().summary().tables[1]

回归摘要,按作者分类的图像

估计的系数现在接近它们的真实值button1的估计系数不显著,而button2的估计系数为负且显著。

让我们通过运行模拟来检查这个结果是否在样本间保持。我们重复估计过程 1000 次,并绘制button1的估计系数分布。****

new_estimator = lambda x: smf.ols('revenue ~ group * mobile', data=x).fit()
new_results = simulate(dgp, new_estimator)plot_results(new_results)

作者图片

现在button1的估计系数的分布以真值零为中心。此外,我们仅在 1%的模拟中拒绝无效的零假设,与选择的 95%的置信水平一致。

结论

在这篇文章中,我们已经看到了运行一个因子回归模型的危险,这个模型有多个互斥的治疗组和一个控制变量的治疗效果异质性。在这种情况下,因为处理不是独立的,回归系数不是组内平均处理效果的凸组合,但也捕捉了引入污染偏差的其他处理的处理效果。这个问题的解决方案既简单又优雅,只需要一个线性回归。

然而,这个问题比这个设置更普遍,通常涉及到(以下所有)每个设置

  1. 我们有多种相互依赖的治疗方法
  2. 我们需要以控制变量作为分析的条件
  3. 在控制变量中,治疗效果是不均匀的

另一个流行的例子是交错处理的双向固定效应(TWFE)估计器。

参考

[1] J. Angrist,利用社会保障数据估计志愿兵役对军事申请人的劳动力市场影响 (1998),计量经济学

[2] D. Rubin,利用潜在结果进行因果推断 (2005),美国统计协会杂志

[3] G. Imbens,J. Wooldridge,项目评估计量经济学的近期发展 (2009),经济文献杂志

[4] P .戈德史密斯-平克汉姆,p .赫尔,m .科勒萨尔,线性回归中的污染偏差 (2022),工作文件

相关文章

  • 理解省略变量偏差
  • 理解弗里希-沃-洛弗尔定理
  • Dag 和控制变量

密码

你可以在这里找到 Jupyter 的原始笔记本:

**https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/cbias.ipynb **

感谢您的阅读!

我真的很感激!🤗如果你喜欢这个帖子并想看更多,可以考虑 关注我 。我每周发布一次与因果推断和数据分析相关的主题。我尽量让我的帖子简单而精确,总是提供代码、例子和模拟。**

还有,一个小小的 免责声明 :我写作是为了学习所以出错是家常便饭,尽管我尽了最大努力。当你发现他们的时候,请告诉我。也很欣赏新话题的建议!

理解 CUPED

原文:https://towardsdatascience.com/understanding-cuped-a822523641af

因果数据科学

A/B 测试最先进方差缩减技术的深度指南

作者图片

在博士期间,我花了很多时间学习和应用因果推断方法到实验和观察数据中。然而,当我第一次听说 CUPED (使用实验前数据的受控实验)时,我完全不知所措,这是一种增加 A/B 测试中随机控制试验功效的技术。

真正让我惊讶的是该算法在业界的普及程度。CUPED 最早由微软研究人员邓、徐、科哈维、沃克(2013) 提出,并已在公司、、预订、 Meta 、、、 Airbnb 、 Linkedin、、、 DoorDash 当深入挖掘时,我注意到与我熟悉的一些因果推断方法有相似之处,例如差异中的差异或控制变量回归。我很好奇,决定深入挖掘。

你在骗谁?作者图片

TLDR; CUPED 本质上是一个剩余结果回归。除了特殊情况外,它通常既不等同于差异中的差异,也不等同于带控制变量的回归。

例子

假设我们是一家正在测试广告活动的公司,我们有兴趣了解它是否会增加收入。我们将一组用户随机分为治疗组和对照组,并向治疗组展示广告活动。与标准的 A/B 测试设置不同,假设我们在测试前也观察用户。

我们现在可以使用来自[src.dgp](https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/src/dgp.py)的数据生成过程dgp_cuped()来生成模拟数据。我还从[src.utils](https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/src/utils.py)引进了一些绘图函数和库。

from src.utils import *
from src.dgp import dgp_cupeddf = dgp_cuped().generate_data()
df.head()

数据快照,图片由作者提供

我们有关于 1000 名个人的信息,这些人由i索引,我们观察他们分别在治疗前后、revenue0revenue1产生的收入,以及他们是否暴露于ad_campaign

手段的差异

在随机化实验或 A/B 测试中,随机化允许我们使用简单的均值差异来估计平均治疗效果。我们可以比较对照组和治疗组的平均治疗后结果 Y₁ ( revenue1),随机化保证了这种差异是由于预期中的单独治疗造成的。

简单差异估计,作者图片

其中横条表示个体的平均值,下标 d 表示治疗状态。在我们的案例中,我们计算了治疗组广告活动后的平均收入,减去对照组广告活动后的平均收入。

np.mean(df.loc[df.ad_campaign==True, 'revenue1']) - np.mean(df.loc[df.ad_campaign==False, 'revenue1'])1.7914301325347406

估计处理效果为 1.79,非常接近于 2 的真值。我们可以通过对ad_campaign的治疗指标 D 回归治疗后结果revenue1来获得相同的估计值。

回归方程,作者图片

其中 β 为感兴趣系数。

smf.ols('revenue1 ~ ad_campaign', data=df).fit().summary().tables[1]

回归结果,图片由作者提供

这个估计器是无偏的,这意味着平均来说它给出了正确的估计。然而,它仍然可以改进:我们可以减少它的方差。减少估计量的方差是非常重要的,因为它允许我们

  • 检测较小的影响
  • 检测到相同的效果,但样本量更小

一般来说,方差较小的估计量允许我们以更高的 功效 进行测试,即检测较小影响的能力。

我们能提高 AB 测试的能力吗?是的,使用 CUPED(以及其他方法)。

CUPED

CUPED 的想法如下。假设您正在运行一个 AB 测试,并且 Y 是感兴趣的结果(在我们的示例中为revenue),二元变量 D 表示单个个体是否被治疗过(在我们的示例中为ad_campaign)。

假设你有另一个随机变量 X ,它不受治疗的影响,并且已知期望𝔼[x】。然后定义

转换的结果,作者的图像

其中 θ 是一个标量。对于𝔼[y】而言,该估计量是无偏估计量,因为在预期中最后两项会抵消。然而, Ŷ₁ᶜᵘᵖᵉᵈ 的方差为

转换结果方差,按作者分类的图像

注意, Ŷ₁ᶜᵘᵖᵉᵈ 的方差被最小化为

最佳 theta,作者图片

哪一个是关于 XY 的线性回归的 OLS 估计量。将 θ *代入 Ŷ₁ᶜᵘᵖᵉᵈ 的方差公式,我们得到

转换结果方差,按作者分类的图像

其中 ρYX 之间的相关性。因此 YX 的相关度越高,CUPED 的方差减少量就越高。

然后我们可以将平均治疗效果估计为对照组和治疗组之间转化结果的平均差异。

CUPED 估算师,图片由作者提供

注意𝔼[x】在取差时抵消。因此,计算就足够了

转换结果的等效公式,作者的图像

这不是【𝔼[y】的无偏估计量,但仍能提供平均治疗效果的无偏估计量。

最佳 X

控制变量 X最优选择是什么?

我们知道 X 应该有以下属性:

  • 不受治疗的影响
  • 尽可能与 Y₁ 相关联

论文作者建议使用预处理结果 Y₀ ,因为它在实践中给出了最大的方差减少。

为了总结,我们可以计算平均处理效果的最高估计值如下:

  1. Y₀ 上回归 Y₁ 并估计 θ̂
  2. 计算ŷ₁ᶜᵘᵖᵉᵈt26】=y̅₁θ̂y̅₀
  3. 计算治疗组和对照组的 Ŷ₁ᶜᵘᵖᵉᵈ 的差值

等效地,我们可以在个体水平上计算 Ŷ₁ᶜᵘᵖᵉᵈ ,然后将其回归到治疗虚拟变量 D 上。

回到数据

让我们一步一步地计算治疗效果的最大估计值。首先我们来估算一下 θ

theta = smf.ols('revenue1 ~ revenue0', data=df).fit().params[1]

现在我们可以计算变换后的结果 Ŷ₁ᶜᵘᵖᵉᵈ

df['revenue1_cuped'] = df['revenue1'] - theta * (df['revenue0'] - np.mean(df['revenue0']))

最后,我们用转换后的结果 Ŷ₁ᶜᵘᵖᵉᵈ 来估计作为均值差异的治疗效果。

smf.ols('revenue1_cuped ~ ad_campaign', data=df).fit().summary().tables[1]

回归结果,图片由作者提供

标准误差小 33%(0.2 vs 0.3)!

等效公式

获得 CUPED 估计值的另一种代数上等价的方法如下

  1. Y₀ 上回归 Y₁ 并计算残差 Ỹ₁
  2. 计算ŷ₁ᶜᵘᵖᵉᵈ=ỹ₁+y̅₁
  3. 计算治疗组和对照组的 Ŷ₁ᶜᵘᵖᵉᵈ 的差值

步骤(3)与之前相同,但(1)和(2)不同。这个过程被称为部分删除,代数等价由弗里希-沃-洛厄尔定理保证。

让我们检查一下我们是否确实得到了同样的结果。

df['revenue1_tilde'] = smf.ols('revenue1 ~ revenue0', data=df).fit().resid + np.mean(df['revenue1'])smf.ols('revenue1_tilde ~ ad_campaign', data=df).fit().summary().tables[1]

回归结果,图片由作者提供

是啊!回归表完全相同。

CUPED 与其他

CUPED 似乎是一个非常强大的程序,但它至少提醒了其他一些方法。

  1. 自回归或带控制变量的回归
  2. 差异中的差异

这些方法是相同的还是有区别的?让我们检查一下。

自回归

当我第一次看到 CUPED 时,我想到的第一个问题是“CUPED 仅仅是一个额外控制变量的简单差异吗?”。或者等价地,CUPED 是否等价于通过最小二乘法估计下面的回归(其中 γ 是感兴趣的参数)?

估计方程,作者图片

让我们看一看。

smf.ols('revenue1 ~ revenue0 + ad_campaign', data=df).fit().summary().tables[1]

回归结果,图片由作者提供

估计的系数与我们用 CUPED 得到的非常相似,标准误差也非常接近。然而,它们并不完全相同

如果你熟悉弗里希-沃-洛厄尔定理,你可能会奇怪为什么两个过程不等价。原因是在 CUPED 的情况下,我们只分离出 Y ,而当我们分离出 X 或者同时分离出 XY 时,FWL 定理成立。

差异中的差异

我想到的第二个问题是“我只是在差异中寻求差异吗?”。差异中的差异(或 diff-in-diffs,或 DiD)是一种估计器,它将治疗效果计算为双重差异而不是单一差异:前后差异和治疗控制,而不仅仅是治疗控制。

差异中的差异估计值,作者图片

这种方法最初是由约翰·斯诺(1854 年)(不,不是那个约翰·斯诺)提出来估计伦敦霍乱流行的原因。diff-in-diff 的主要优点是,当随机化不完美且治疗组和对照组不可比时,它允许估计平均治疗效果。关键假设是治疗组和对照组之间的差异随时间保持不变。通过取两个差值,我们就把它抵消了。

让我们看看 diff-in-diffs 是如何工作的。计算 diff-in-diffs 估计量的最常见方法是首先以长格式面板格式对数据进行整形(一个观察是在时间段 t 的单个 i ),然后对后处理假人𝕀(t=1和处理假人 D 之间的完全交互的结果 Y 进行回归。

估计方程,作者图片

平均处理效果的估计量是交互作用系数的系数, δ

df_long = pd.wide_to_long(df, stubnames='revenue', i='i', j='t').reset_index()
df_long.head()

长数据集快照,按作者分类的图像

长数据集现在由个体i和时间t索引。我们现在准备运行差异中的差异回归。

smf.ols('revenue ~ t * ad_campaign', data=df_long).fit().summary().tables[1]

回归结果,图片由作者提供

估计的系数接近真实值 2,但是标准误差大于用所有其他方法获得的结果(0.35 >> 0.2)。我们错过了什么?我们没有聚类标准错误

我不会在这里详细讨论标准错误聚类的含义,但直觉如下。默认情况下,statsmodels软件包会计算标准误差,假设结果在所有观测中是独立的。在我们随着时间的推移观察个人并试图利用这些信息的情况下,这个假设不太可能是真的。聚类考虑了结果变量在聚类内的相关性。在我们的例子中,在个体水平上对标准误差进行聚类是有意义的(即使不知道数据生成过程),允许结果随着时间的推移与个体 i 相关联。

smf.ols('revenue ~ t * ad_campaign', data=df_long)\.fit(cov_type='cluster', cov_kwds={'groups': df_long['i']})\.summary().tables[1]

回归结果,图片由作者提供

在个体水平上对标准误差进行聚类我们获得了与先前估计值相当的标准误差(∼0.2)。

注意,当我们假设 CUPED 系数 θ=1 时,diff-in-diffs等价于 CUPED

df['revenue1_cuped2'] = df['revenue1'] - 1 * (df['revenue0'] - np.mean(df['revenue0']))
smf.ols('revenue1_cuped2 ~ ad_campaign', data=df).fit().summary().tables[1]

回归结果,图片由作者提供

事实上,我们获得了相同的精确系数和几乎相同的标准误差!

比较

哪种方法比较好?从我们到目前为止所看到的,所有的方法似乎都提供了一个准确的估计,但简单的差异有一个较大的标准偏差。

现在让我们通过模拟来比较一下目前为止我们看到的所有方法。我们模拟数据生成过程dgp_cuped() 1000 次,并保存以下方法的估计系数:

  1. 简单差异
  2. 自回归
  3. 差异中的差异
  4. CUPED

让我们画出估计参数的分布图。

sns.kdeplot(data=results, x="Estimate", hue="Estimator");
plt.axvline(x=2, c='k', ls='--');
plt.title('Simulated Distributions');

估计值的模拟分布,按作者分类的图像

我们还可以将每个估计量的模拟平均值和标准偏差制成表格。

results.groupby('Estimator').agg(mean=("Estimate", "mean"), std=("Estimate", "std"))

汇总表,按作者分类的图像

所有的估计值看起来都是无偏的(T21):平均值都接近真实值 2。此外,除了单差估计量之外,所有估计量都有非常相似的标准差!

总是一模一样?

估计量总是相同的吗,或者它们之间有一些差异吗?

我们可以检查许多与原始数据生成过程不同的地方。为了简单起见,这里我只考虑一种:不完全随机化。我考虑的对数据生成过程的其他调整是:

  • 预处理缺失值
  • 附加协变量/控制变量
  • 多个预处理期
  • 异质处理效果

以及它们的组合。然而,我发现不完全随机是最能说明问题的例子。

假设现在随机化不是完美的并且两个组是不相同的。特别是,如果数据生成过程

数据生成过程,按作者分类的图像

假设 β ≠0。还要注意,我们有持久的个体水平异质性,因为不可观察的 uᵢ 不随时间变化(不由 t 索引)。

results_beta1 = simulate(dgp=dgp_cuped(beta=1))

让我们画出估计参数的分布图。

sns.kdeplot(data=results_beta1, x="Estimate", hue="Estimator");
plt.axvline(x=2, c='k', ls='--');
plt.title('Simulated Distributions');

估计值的模拟分布,按作者分类的图像

results_beta1.groupby('Estimator').agg(mean=("Estimate", "mean"), std=("Estimate", "std"))

汇总表,按作者分类的图像

在处理分配不完美的情况下,对于真实的处理效果,差异中的差异和自回归都是无偏的,然而,差异中的差异是更有效的。CUPED 和 simple difference 都是偏向的。为什么?

Diff-in-diffs 明确控制治疗组和对照组之间的系统差异,这些差异随时间保持不变。这正是这个估算器的用途。自回归对额外的协变量 Y₀ 执行某种匹配,有效地控制了这些系统差异,但效率较低(如果你想知道更多,我在这里写了关于控制变量和这里的相关帖子)。CUPED 控制了个体水平的持续异质性,但没有控制治疗分配水平。最后,简单的差异估计不控制任何事情。

结论

在这篇文章中,我分析了一个在业界非常流行的 AB 测试中平均处理效果的估计量:CUPED。关键思想是,通过利用预处理数据,CUPED 可以通过控制随时间持续的个体水平变化来实现更低的方差。我们还看到,CUPED 与自回归和差异中的差异密切相关,但并不等同。当我们进行不完全随机化时,这些方法之间的差异就很明显了。

未来研究的一个有趣途径是,当我们拥有大量预处理信息时会发生什么,无论是在时间段还是可观察到的特征方面。来自 Meta、【郭】、Coey、Konutgan、李、Schoener、Goldman (2021) 的科学家在最近的一篇论文中分析了这个问题,该论文利用机器学习技术来有效地使用这些额外的信息。这种方法与双/去偏置机器学习文献密切相关。如果你感兴趣,我写了两篇关于这个主题的文章(第一部分和第二部分),以后我可能会写更多。

参考

[1] A .邓,y .徐,r .科哈维,t .沃克,利用预实验数据提高在线对照实验的灵敏度 (2013),

[2] H. Xir,J. Aurisset,提高在线对照实验的灵敏度:网飞的案例研究 (2013), ACM SIGKDD

[3] Y. Guo,D. Coey,M. Konutgan,W. Li,C. Schoener,M. Goldman,在线实验中方差缩减的机器学习 (2021), NeurIPS

[4] V. Chernozhukov,D. Chetverikov,M. Demirer,E. Duflo,C. Hansen,W. Newey,J. Robins,用于治疗和结构参数的双/去偏置机器学习 (2018),计量经济学杂志

[5]我们应该在多大程度上相信差异中的差异估计值? (2012 年)《经济学季刊》

相关文章

  • 双去偏机器学习(第一部分)
  • 双去偏机器学习(第二部分)
  • 理解弗里希-沃-洛弗尔定理
  • 了解污染偏差
  • Dag 和控制变量

密码

你可以在这里找到 Jupyter 的原始笔记本:

**

感谢您的阅读!

我真的很感激!🤗如果你喜欢这个帖子并想看更多,可以考虑 关注我 。我每周发布一次与因果推断和数据分析相关的主题。我尽量让我的帖子简单而精确,总是提供代码、例子和模拟。

还有,一个小小的 免责声明 :我写作是为了学习所以出错是家常便饭,尽管我尽了最大努力。当你发现他们的时候,请告诉我。也很欣赏新话题的建议!

在不到 3 分钟的时间内了解数据结构

原文:https://towardsdatascience.com/understanding-data-fabric-in-less-than-3-minutes-b6d4a17b148e

什么是数据结构?它像听起来那样具有革命性吗?

数据管理架构的前景正在以闪电般的速度发展。每隔几个月就会有新的技术和术语出现,有时会让专业人士更难跟上发展的步伐。

法托斯 Bytyqi 在 Unsplash 上的照片

最近,一个名为“数据结构”的新概念进入市场,承诺加快用户对不同数据的访问。但是什么是数据结构呢?它像听起来那样具有革命性吗?或者它只是另一种用新术语包装起来的“旧技术”?

这个故事将试图用极其简单的解释来揭示数据结构的概念。希望能让任何级别的数据管理专业人员更好地理解。

为了开始理解数据结构的旅程,让我们先回顾一下传统的数据管理架构。这种体系结构的图示如下。

传统数据管理架构|来源:作者

正如您在上面的图表中所看到的,传统的体系结构要求将来自不同来源的所有数据整合到集中的存储库中。这些存储库通常由数据湖、数据仓库和数据集市组成。所有这些都由 ETL 管理,以确保数据存储在正确的模式中。

这种方法有其优点,因为中央存储库中的数据变得如此结构化,因此易于用于分析和报告目的。这种传统方法持续了 30 年。

然而,随着产生的数据量以几何级数增长,这种方法暴露了它的缺陷。使用传统方法,数据必须得到一致的复制,这种机制可能效率低下且耗时。此外,在新的数据安全模式下,定期移动敏感数据并不是明智之举。更不用说某些类型的数据最好留在它们原来的位置。因此,传统的整合方法虽然适用于某些类型的组织,但并不适合以惊人的速度生产、移动和使用数据的现代数据驱动型组织。

然后,数据管理专家提出了一个名为数据虚拟化的出色解决方案。这种开箱即用的方法旨在为用户提供对多种数据的访问,而不必将这些数据移动到中央存储库。为此,引入了一个抽象层。

数据虚拟化架构|来源:作者

抽象层是一组中间件,提供来自映射到它的各种来源的数据的虚拟视图。这种映射由元数据控制,因此映射数据的所有属性(列名、表结构、标记等)都是由元数据控制的。)都保留了下来。还提供了一个数据目录来帮助根据业务定义组织数据,允许用户更快地找到他们需要的内容。更重要的是,中间件还提供了管理用户访问权限的安全治理,因此只有合法用户才能访问某些数据。

数据虚拟化是一种前所未有的方法,因为突然间我们不必不断整合来自多个来源的数据。节省了宝贵的时间、资源和成本。该解决方案已经很好地服务于许多组织,从快速发展的初创企业到成熟的企业。数据虚拟化的一个值得注意的用例是一家制药公司如何利用数据虚拟化来加速向其研究人员交付数据。移除“不必要的”ETL,并将项目开发时间减半。

我们已经了解了数据虚拟化的概念,现在我们来看看下图。

数据结构|来源:作者

看看上面的架构图与数据虚拟化有多相似。没有中央存储库,有几个组件提醒我们以前的方法。此外,还出现了新的组件,如推荐引擎和知识图。

这是数据结构方法的体系结构表示。

在这一点上,我们可以有把握地推断出,数据结构是数据虚拟化的一种发展,其额外的内置功能进一步增强了它的优势。

一个特别的改变游戏规则的组件是 AI/ML 技术的引入,它能够将映射和编目过程推进到下一个级别。机器学习通过识别数据目录中内容的微妙模式来做到这一点,从而创建使用以前的虚拟化方法可能无法建立的数据关系。此外,AI 还支持推荐引擎和知识图,因此数据发现方面有了显著改善。

上面简单而又按时间顺序排列的解释希望能启发我们,数据结构中“没有什么新奇的东西”。这确实是一个开箱即用的概念,然而,为其提供动力的构件已经存在了相当一段时间。

以知识图谱为例。它是一个全面的图表,表示真实世界实体(如当事人、事实或事件)之间的相互联系。简单来说,知识图谱帮助我们全面了解我们的数据。它从 20 世纪 70 年代就已经存在了。

但 AI/ML 的出现,自动化了构建知识图的过程,留下了大部分手工任务。而随着数据结构的兴起,知识图终于找到了它的杀手级用例。

总之,数据结构是若干技术使能因素的组合,这些技术使能因素有助于所有各方访问和使用其组织可支配的所有可用数据。而不必移动数据。它不是一个单一的软件,我们可以在一次完成,但它是一个增量之旅,从启用数据虚拟化开始。

首先掌握数据虚拟化,您就可以顺利实施数据结构。

理解 DBSCAN 并使用 Python 实现

原文:https://towardsdatascience.com/understanding-dbscan-and-implementation-with-python-5de75a786f9f

无监督学习

理解 DBSCAN 并使用 Python 实现

在这篇文章中,我简要回顾了 DBSCAN 的思想及其在 Python 中的实现。

乌曼诺德在 Unsplash 上拍摄的照片

D BSCAN 代表基于密度的带噪声应用空间聚类,是一种无监督学习算法。DBSCAN 是应用最广泛的聚类方法之一,因为 DBSCAN 发现的聚类可以是任何形状,可以处理一些其他方法不能处理的特殊情况。

显示 DBSCAN 优于 K-means 聚类算法的一个最常用的例子是下图。

DBSCAN vs K-means。点是样本,X 轴是特征 1,Y 轴是特征 2。(图片由作者提供)

在上面的例子中,k-means 聚类的线性边界肯定不好用。然而,DBSCAN 不需要任何形状的聚类,而是跟踪高密度区域,这比 k-means 更适合这种情况。

在本帖中,我将谈谈如何理解这个算法,以及如何用 Python 实现它。希望文章有帮助。

了解 DBSCAN

从 DBSCAN 的名字可以明显看出,最重要的部分就是“基于密度的这个词。那么,什么是密度呢?直截了当地说,我们可以将密度理解为特定区域内数据点数量的度量。那么,如何描述一个“指定区域”?我们可以用一个中心点与它的距离的某个值来描述一个特定的区域。因此,DBSCAN 中的聚类是大量数据点的那些“指定区域”(密集区域)。

在上面的描述中,我们发现几个“术语”对于聚类过程非常重要。首先,“某个距离值”。价值是什么?我们如何衡量距离的价值?第二,“一个中心点”。定义那些“特定区域”的中心点是什么?第三,“高数据点”。如何定义「高数」?

其实 DBSCAN 中的所有参数在上面的问题中已经提到了。

首先, eps两个样本之间的最大 距离为一个样本被认为与另一个样本相连。并且距离可以由任何类型的距离函数来定义,例如“欧几里德距离”。

第二, 岩心样品 ,即处于高密度区域的样品。

第三, minPts最小 数量的样本在一个点的邻域内被认为是核心点。

这些参数共同定义了 DBSCAN 聚类中数据点的类型。这些点分为核心点可达点、离群点。好了,记住这些概念,让我们看一个具体的例子来回顾一下 DBSCAN 的思想。

DBSCAN 中的核心、核心可达点和异常值的示例。(图片由作者提供)

在上面的图中,我们将 minPts 设置为 4,这意味着如果至少有 4 个点(包括其自身)在其距离 eps 之内,则该点称为 核心点 。因此, A 是一个核心点,所有其他棕色点也是核心点,因为它们都在围绕它们的 eps 内至少有 4 个点。

即使蓝色点,如 B ,由于 eps 内的邻域点少于 4 个而不是核心点,但它们仍然可以从某些核心点直接到达。因此,它们属于这些核心点的同一群。

然而,点 C 不能从任何核心点到达,所以它被称为离群点。

从上面的例子中,我们可以看到 DBSCAN 中的集群由核心点和可从核心点到达的非核心点组成。并且每个集群包含至少一个核心点。即使非核心点也可以是聚类的一部分,但它只能位于聚类的边界,因为它们本身不能到达更多的点。

DBSCAN 算法

好了,理解了 DBSCAN 的思想之后,让我们用下面的步骤来总结一下 DBSCAN 算法,

1.对于每个数据点,在 eps 距离内的邻域内寻找点,并将核心点定义为至少有 minPts 个邻居的点。

2.将连接的核心点组定义为集群。

3.如果每个非核心点可以从相邻的核心点直接到达,则将它分配到附近的簇,否则将其定义为离群点。

大的 eps 倾向于在一个聚类中包括更多的点,因此过大的 eps 将包括同一单个聚类中的所有内容,而过小的 eps 将导致根本没有聚类。

太小的 minPts 是没有意义的,因为它会把每一个点都当成一个核心点。相对较大的 minPts 可以更好的处理噪声较多的数据。

DBSCAN 的优势

在了解了 DBSCAN 的思想和算法之后,它的优势就非常明显了。

首先,DBSCAN 不要求用户指定集群的数量

第二,DBSCAN 对异常值不敏感。

第三,DBSCAN 形成的簇可以是任何形状,这使得它对不同类型的数据都是健壮的。

但是没有一个无监督的算法是完美的。DBSCAN 确实有其局限性。例如,如果数据在集群之间的密度变化非常大,那么使用 DBSCAN 将是一个很大的问题,因为在一个数据集上只能使用一对参数, epsMinPts 。此外,如果没有数据的领域知识,定义每股收益可能会非常困难。

Python 中的实现

DBSCAN 在 Python 中的实现可以通过 scikit-learn 包来实现。对数据 X 进行聚类的代码如下,

**from** **sklearn.cluster** **import** DBSCAN
**import** **numpy** **as** **np** DBSCAN_cluster = DBSCAN(eps=10, min_samples=5).fit(X) 

其中 min_samples 是参数 MinPtseps 是距离参数。

如果你想检查数据的聚类结果,你可以使用下面的命令,

DBSCAN_cluster.labels_

就是这样!希望文章有帮助!

如果你喜欢阅读我的文章,你可以通过链接订阅我的媒体,https://jianan-lin.medium.com/subscribe。

谢谢大家!

参考

https://en.wikipedia.org/wiki/DBSCAN https://scikit-learn.org/stable/modules/generated/sklearn.cluster.DBSCAN.html https://scikit-learn.org/stable/modules/clustering.html#dbscan

了解 DeepMind 和 Strassen 算法

原文:https://towardsdatascience.com/understanding-deepmind-and-strassen-algorithms-9bdb3d8b6ea6

矩阵乘法问题的介绍,以及在 Python 和 JAX 中的应用

图片由伊万·迪亚兹在 Unsplash 上拍摄

DeepMind 最近发表了一篇有趣的论文,该论文采用深度强化学习来寻找新的矩阵乘法算法[1]。本文的目标之一是降低矩阵乘法的计算复杂度。这篇文章提出了很多关于矩阵乘法的评论和问题——你可以从戴密斯·哈萨比斯的推特中看到。

矩阵乘法是数学中一个激烈的研究领域[2–10]。虽然矩阵乘法是一个简单的问题,但计算实现有一些障碍需要解决。如果我们只考虑方阵,第一个想法是将乘积计算为三元组for-loop:

图 Python 中的简单矩阵乘法,复杂度为 O(n)

这样一个简单的计算具有 O(n ) 的计算复杂度。这意味着运行这种计算的时间随着矩阵大小的三次幂而增加。这是一个需要解决的障碍,因为在人工智能和 ML 中,我们为每个模型的步骤处理巨大的矩阵——神经网络是大量的矩阵乘法!因此,给定恒定的计算能力,我们需要越来越多的时间来运行我们所有的人工智能计算!

DeepMind 将矩阵乘法问题带到了更具体的一步。然而,在深入研究本文之前,让我们先看看矩阵乘法问题,以及哪些算法可以帮助我们降低计算能力。特别是,我们将看看斯特拉森的算法,然后我们将在 Python 和 JAX 实现它。

斯特拉森、铜匠&维诺格拉德、威廉姆斯&阿尔曼律师事务所

请记住,在本文的其余部分,矩阵的大小将是 N > > 1000。所有算法都将应用于 块矩阵

图 2:乘积矩阵 C 由矩阵 A 的第 I 个元素行和矩阵 B 的第 j 个元素列的和给出,返回矩阵 C 的元素(I,j)。[图片由作者提供]

乘积矩阵 C1 分别由矩阵 A 和 B 的行和列之和给出——图 2

图 3:矩阵 A 和 B 之间矩阵乘法的可视化方案,给出一个新的乘积矩阵 C 或 AB

正如我们在简介中看到的,标准矩阵乘法乘积的计算复杂度是 O(n ) 。1969 年德国数学家 Volker Strassen 打破了 O(n ) 障碍,将矩阵乘法减少到 7 次乘法和 18 次加法,复杂度达到o(n⁸⁰⁸)【8】。如果我们考虑一组矩阵 A、B 和 C,如图 3 所示,Strassen 推导出以下算法:

图 4: Strassen 的算法,在他的论文“高斯消去法不是最优的”中提出。[图片由作者提供]

关于这个算法,有几件事值得注意:

  • 该算法递归地作用于块矩阵
  • 很容易证明⁸⁰⁸).的复杂性 G 给定矩阵大小 n 和 7 次乘法,其结果如下:

图 5:给定矩阵大小和乘法次数,推导算法复杂度。[图片由作者提供]

图 4 中的所有步骤都是多项式,因此矩阵乘法可以被视为多项式问题。

  • 计算 Strassen 的算法是不稳定的浮动精度数字[14]。数值不稳定性是由舍入所有子矩阵结果引起的。随着计算的进行,总误差累积起来会严重降低精确度。

从这些观点出发,我们可以将矩阵乘法转化为多项式问题图 4 中的每一个操作都可以写成一个线性组合,例如步骤 I 是:

图 6:将 Strassen 算法的步骤 I 定义为线性组合。[图片由作者提供]

这里,α和β是来自矩阵 A 和 B 的元素的线性组合,而 H 表示用于加法/减法运算的一位热码编码矩阵。然后,可以将乘积矩阵 C 元素定义为线性组合,并写出:

图 7: Strassen 算法可以表示为三个元素的线性组合。[图片由作者提供]

正如你所看到的,整个算法已经简化为一个线性组合。特别地,图 7 中等式Strassen的左手侧可以由矩阵大小、 m、n、p — 来表示,这意味着矩阵 m x pn x p 之间的乘法:

图 8:矩阵乘法可以表示为张量。[图片由作者提供]

对于 Strassenn,m,p>是< 2,2,2 >。图 8 将矩阵乘法描述为一个线性组合,或一个张量——这就是为什么有时斯特拉森算法被称为张量运算。图 8 中的 a、b、c 元素组成了一个三元组。按照 DeepMind 的论文惯例,三和弦可以表示为:

图 9:将矩阵乘法张量定义为三元组,如 Deep Mind 论文[1]中所定义的。[图片由作者提供]。

这个三元组确立了寻找最佳算法以最小化矩阵乘法运算的计算复杂度的目标。事实上,三元组的最小数量定义了计算乘积矩阵的最小操作数量。这个最小数就是张量的秩 R(t) 。对张量秩的研究将有助于我们找到新的更有效的矩阵乘法算法——这就是 DeepMind 的人们已经做的。

从 Strasse 的工作开始,从 1969 年到今天,解决矩阵乘法复杂性问题的新算法不断出现(表 1)。

表 1:1969 年至今发表的所有作品的矩阵乘法复杂度。希腊字母 omega 表示复杂性指数。对于每个作者,您可以找到建议解决方案的论文。勒加尔发现了一个解,其复杂度是 2.37287,而瓦西里夫斯卡和阿尔曼是 2.37286。[图片由作者提供]

潘、比尼、舍内奇和所有的研究人员是如何得到这些辉煌的结果的?解决计算机科学问题的一种方法是从定义一个代数问题 P 开始。以矩阵乘法问题为例,代数问题 P 可以是:“找一个数学模型求一组多项式的值”。从这里,科学家开始将问题简化,并将其“转换”为矩阵乘法问题— 这里的很好地解释了这种方法。简而言之,科学家能够证明定理以及将多项式求值分解为矩阵乘法算法的步骤。最终,他们都得到了比斯特拉森算法更强大的理论算法

然而,这些理论算法不能被编码,除非有一些沉重和强大的数学假设和限制,可能会影响算法的效率。

现在让我们看看斯特拉森的算法有多强大,以及我们如何用 Python 和 JAX 实现它。

0.20 提升:别开玩笑了!Strassen 的算法有效地提高了矩阵乘法吗?

这里是回购与所有下列代码。我在 MacBook Pro 上运行这些测试,2019 年,2.6GHz 6 核英特尔酷睿 i7,16 GB 2667MHz DDR4 内存。

Python: numpy.matmul vs Strassen

图 10:标准 numpy 矩阵乘法(MatMul,蓝色)和 Strassen 算法(Strassen,橙色)的比较。[图片由作者提供]

在主代码中我们可以遵循这些步骤:

  • 我们将创建两个方阵 A 和 B,用随机整数初始化
  • 我们将测试不同矩阵大小的算法:128, 256, 512, 768, 1024, 1280, 2048
  • 对于每个尺寸,将运行numpy.matmul和 Strassen 的算法三次。每次运行时,我们都在列表中记录运行时间。从这个列表中,我们提取平均时间和标准偏差来比较这两种方法(图 10)

脚本的核心部分是递归的strassen函数:

  • 首先,我们检查输入矩阵的维数。如果维度低于给定的阈值(或不能被 2 整除),我们可以用标准numpy计算剩余的乘积,因为这不会影响最终的计算成本
  • 对于每个输入矩阵,提取左上、右上、左下和右下子矩阵。在我的代码中,我提出了一个天真而简单的解决方案,这样每个人都可以理解发生了什么。要进一步测试和理解块矩阵的创建,请尝试手动计算小矩阵(如 12x12)的索引。
  • 在最后一步,从所有计算出的子元素(图 4 中的 C11、C12、C21 和 C22)中重构乘积矩阵

图 11:计算矩阵乘法的 Strassen 函数。[图片由作者提供]

图 10 比较了标准的numpy.matmulstrassen算法。如您所见,对于维度< 2000 ( Matrix Size < 2000),Strassen 的性能优于标准矩阵乘法。真正的改进可以在更大的矩阵上看到。Strassen 在 8.16 +/- 1.56 秒内完成 2048x2048 矩阵的矩阵乘法,而标准方法需要 63.89 +/- 2.48 秒。矩阵大小翻倍,4096 列和行,Strassen 在 31.57 +/- 1.01 秒内完成,而标准矩阵乘法需要 454.37 +/- 6.27 秒

Jax 实现:在 DeepMind 的巨大肩膀上

根据图 9 中的等式,我们可以进一步以张量形式分解 Strassen 算法。张量 u、vw 可以应用于矩阵的块,以获得最终的乘积矩阵。黄(C.H. Huang)、约翰逊(J. R. Johnson)和约翰逊(R. W. Johnson)发表了一篇小论文,说明如何推导 Strassen 的张量形式[18],随后在 1994 年[19]发表了另一个公式,其中他们明确写出了 Strassen 的 u,vw 张量。详细的计算可以查看[18],而图 12 报告了张量值。

图 Strassen 算法的 U、V 和 W 张量。[图片由作者提供]

这是与 JAX 合作并将斯特拉森与标准jax.numpy.matmul进行比较的良好起点。对于 JAX 的脚本,我一直密切关注 DeepMind 的实现。

该脚本处理 4x4 块矩阵。核心函数f运行 Strassen 方法。在这种情况下,所有的 A 和 B 块矩阵都乘以 uv 张量。结果乘以张量 w,得到最终乘积(图 13)。鉴于 JAX 强大的性能,该算法在以下矩阵的维度上进行测试:8192, 10240, 12288, 14336, 16384, 18432, 20480

图 13:JAX Strassen 算法的主要功能。块矩阵元素乘以 Strassen 向量(或因子)。[图片由作者提供]

最后,在最后一步,通过连接和整形来自f函数的乘积矩阵来重构乘积矩阵(图 14)。

图 14:重建尺寸为 x 的产品矩阵的最后一步。[图片由作者提供]

图 15 比较了 JAX 数字矩阵乘法和斯特拉森实现。你可以看到 JAX 是非常强大的,因为 8192x8192 矩阵乘法可以在 12 秒内运行(平均)。对于 12000x12000 以下的维度,没有真正的改善,JAX 标准方法在我的笔记本电脑上平均需要 60 秒的计算时间,而我正在运行其他一些东西。在这个维度之上,我们可以看到令人印象深刻的 20%的改进。例如,对于 18432x18432 和 20480x20480,Strassen 算法分别在 142.31+/-14.41 秒和 186.07+/-12.80 秒内运行,这是通过在 CPU 上运行完成的。一个好的作业可能是尝试添加device_put选项的代码,并在 Colab 的 GPU 上运行。我相信你会大吃一惊!

图 15:标准 JAX 矩阵乘法(马特穆尔,蓝色)和斯特拉森算法(斯特拉森,橙色)的比较。[图片由作者提供]

结论

今天我们向前迈了一小步,以完整理解 DeepMind 的出版物《用强化学习发现更快的矩阵乘法算法》[1]。本文提出了利用深度强化学习解决矩阵乘法问题的新方法。

在这第一篇文章中,我们开始触及矩阵乘法的表面。我们学习了这个操作的计算成本,我们看到了 Strassen 算法。

从这里我们定义了 Strassen 算法是如何产生的,以及它的数学含义是什么。自其发表以来,研究人员已经找到了越来越好的矩阵乘法问题的解决方案。然而,并非所有这些方法都可以用代码实现。

最后,我们用 Python 和 JAX 玩了一会儿,看看这个算法有多强大。我们了解到,当我们必须处理非常大的矩阵时,Strassen 是一个很好的工具。我们看到了 JAX 在处理大型矩阵乘法方面的强大功能,以及在不使用 GPU 或其他内存选项的情况下实现这种解决方案是多么容易。

在下一篇论文中,我们将从 DeepMind 的论文中看到更多细节。特别是,我们将处理深度强化算法,以及论文的发现。然后,我们将实现新的 DeepMind 算法,并在 JAX 的一个 GPU 实例上运行它们。

我希望你喜欢这篇文章:),并感谢阅读。

支持我的写作:

https://stefanobosisio1.medium.com/membership

如果有任何问题或意见,请随时给我发电子邮件,地址是:stefanobosisio1@gmail.com,或者直接在 Medium 这里。

文献学

  1. 用强化学习发现更快的矩阵乘法算法。性质610.7930(2022):47–53。
  2. 布莱斯,马库斯。“快速矩阵乘法。”计算理论(2013):1–60。
  3. 宾尼达里奥。“O (n2。7799)nxn 近似矩阵乘法的复杂性。”(1979).
  4. 铜匠,唐,和史穆尔·维诺格拉。"关于矩阵乘法的渐近复杂性."SIAM 计算杂志 11.3(1982):472–492。
  5. 铜匠,唐,和史穆尔·维诺格拉。"通过算术级数的矩阵乘法."第十九届 ACM 计算理论年会会议录。1987.
  6. 计算双线性映射的各种最佳算法 II。2× 2 矩阵乘法的最佳算法理论计算机科学7.2(1978):127–148。
  7. 阿诺德·舍恩哈格。"加成链长度的一个下界."理论计算机科学1.1(1975):1–12。
  8. 斯特拉森沃尔克。"高斯消去法不是最优的."数字数学 13.4(1969):354–356。
  9. 什穆埃尔·威诺格拉德。"关于 2× 2 矩阵的乘法."线性代数及其应用4.4(1971):381–388。
  10. 先生们,w·莫文。"矩阵乘法和快速傅立叶变换."贝尔系统技术杂志47.6(1968):1099–1103。
  11. 阿尔曼,乔希和弗吉尼亚·瓦西列夫斯卡·威廉姆斯。"精确的激光方法和更快的矩阵乘法."2021 年 ACM-SIAM 离散算法研讨会会议录(SODA) 。工业和应用数学学会,2021。
  12. 弗朗索瓦·勒加尔。"张量的幂和快速矩阵乘法."第 39 届符号和代数计算国际研讨会会议录。2014.
  13. 弗吉尼亚·瓦西列夫斯卡·威廉姆斯。"矩阵乘法比 Coppersmith-Winograd 快."第 44 届 ACM 计算理论年会会议录。2012.
  14. 贝利、大卫·h、金·李和霍斯特·西蒙。"使用 Strassen 算法加速线性系统的求解."《超级计算杂志》4.4(1991):357–371。
  15. 潘诉雅。"对于构造矩阵运算的快速算法来说,Strassen 算法不是最佳的聚合、合并和消去的三线性技术."第 19 届计算机科学基础年会(sfcs 1978) 。IEEE,1978 年。
  16. 阿诺德·舍恩哈格。"部分和全部矩阵乘法."SIAM 计算杂志 10.3(1981):434–455。
  17. 戴维、亚历山大·芒罗和安德鲁·詹姆斯·斯托瑟斯。"改进矩阵乘法复杂度的界限."爱丁堡皇家学会学报 A 辑:数学143.2(2013):351–369。
  18. 黄春辉,杰里米约翰逊,罗德尼约翰逊。"斯特拉森矩阵乘法算法的张量积公式."应用数学快报3.3(1990):67–71。
  19. 带有记忆缩减的斯特拉森矩阵乘法算法的张量积公式。科学规划 4.4(1995):275–289。

理解密集段落检索(DPR)系统

原文:https://towardsdatascience.com/understanding-dense-passage-retrieval-dpr-system-bce5aee4fd40

研究论文演练

图片来自来源

在这篇博客中,我们将深入研究论文《开放领域问答的密集段落检索 T2》。我们将尝试理解它的理论方面,并做一些实践,建立我们自己的基于 BERT 的 DPR 模型。

开放领域问答系统非常依赖高效的段落检索方法。这一步有助于选择回答任何问题的相关候选上下文。开放领域问答系统通常遵循两步流水线:

  1. 上下文检索器: 上下文检索器负责获取与问题相关并可能包含答案的一小部分段落。
  2. 机器阅读器: 机器阅读器负责从这些段落集中识别正确答案。

在这篇博客中,我们主要讨论管道的上下文检索部分的改进。

开放域问答管道|作者图片

传统系统将 TF-IDF 和 BM25 的一些逻辑建模到它们的检索器中,这通常工作得很好,但问题是“我们能做得更好吗”?我就不赘述 TF-IDFBM25 如何工作的细节了,可以随意查看一下这个和这个的情况。简而言之,它们在两个文本片段的加权单词袋表示法之间执行某种稀疏向量相似性。这种系统的一个明显的局限性是不能检索单词不匹配的上下文。最近,随着围绕自然语言处理中与词袋表示相反的语义建模的大肆宣传和影响,来自脸书·艾、华盛顿大学和普林斯顿大学的这项工作表明,基于密集向量表示可以有效地实现检索,并且还被视为大大超过传统技术。

作者提出了一种方法,通过简单的双编码器框架,利用少量的问题和黄金段落对来学习这些密集表示,也称为嵌入。让我们直接看一个论文中提到的例子来理解我们到底在试图解决什么——考虑一下“谁是指环王中的坏人?”这个问题,这可以从上下文“萨拉·贝克因在《指环王》三部曲中塑造反派索隆而闻名。”基于术语的系统将难以检索这样的上下文,而密集检索系统将能够更好地将短语“坏人”与单词“恶棍”相匹配,从而获取正确的上下文。随着我们在博客中的进展,我们将会谈到这个方法的细节,但是让我们先看看一些结果。

下图显示了 top-k 准确性的趋势,我们对在不同大小的真实数据集上训练的模型的 k 值进行了缩放。因此,这里的 top-k 准确性意味着,对于给定的查询(q ),有多少得分最高的段落是正确的,并且可以提供正确的答案。

与 BM25 相比的 DPR Top-k 精度性能|图片来自来源

可以清楚地看到,与 BM25 技术相比,仅用 10k 个问题和段落黄金对的训练样本,所提出的方法就可以在 top-k 准确度数字上获得几乎 7-9%的提高。这里,“k”的值可以从非常小的 5 到非常大的 100。令人惊讶的是,如果你对一个给定的问题只有 10-20 段感兴趣,那么即使只有 1k 黄金样本的训练也比 BM25 技术在 top-k 准确率上提高了近 2-4%。

密集通道检索器(DPR)

密集段落检索器(DPR)负责基于段落和问题的高质量低维连续表示之间的相似性来检索与所问问题相关的段落。此外,由于整个系统必须合理地快速服务于用户的请求,所以包含这些表示的索引被预先计算和维护。现在,在推理期间,对于出现的任何新的查询/问题,我们可以有效地检索一些前 k 个段落,然后只在这个较小的子集上运行我们的阅读器组件。论文中的作者使用了脸书人工智能相似性搜索(FAISS) ,这是一个允许我们快速(近似最近邻居)搜索彼此相似的多媒体文档的库。这里,“k”的大小取决于几个因素,如流水线中的预期延迟、可用计算、召回等。但是一般来说,10-50 之间的任何 k 值都可以达到这个目的。

问题和段落嵌入之间的相似性通过计算它们之间的点积来表示。作者也试验了其他类似的方法,但最终选择了点积,因为它简单。相似性得分(或距离越小)越高,文章就越与问题相关。数学表示如下—

这里 q,p,Eq,Ep 分别是问题文本,段落文本,输出问题表征的 BERT 模型,输出段落表征的 BERT 模型。作者使用CLS 标记的 768 维表示作为输入文本片段的最终表示。

为了训练模型,数据集被表示为 D={ < q1,p1,p2,p3..pn >,< q2,p2,p1,p3..pn > …},这里的气是第 I 个问题,每个问题都配有一个正例和一些反例。为了简单起见,上面每个例子的第一个索引是对问题“气”的肯定回答,其余的是否定回答。并且他们将损失函数优化为正通过的负对数似然。下图显示了相同- 的数学表示

这里,qi,pi(+),pi(-)分别是第 I 个问题,第 I 个相关的段落(正样本),第 I 个不相关的段落(负样本)。优化的目标是最大化 qi 和 pi(+)之间的相似性,并降低不相关的 qi 和 pi(-)之间的相似性

通常在检索场景中,获得正面例子是简单的(你可以通过给出问题和相关段落的训练数据获得),而人们可能必须考虑有效地选择负面例子。作者在论文中实验了三种否定生成方法:(1) Random :从语料库中随机抽取任意段落;(2) BM25 :由 BM25 返回的顶部段落,其可能不一定包含答案,但是匹配大多数问题标记(3) 黄金:与训练集中出现的其他问题配对的正面段落(具体地说是出现在相同小批量中的那些)。发现第 3 点和第 2 点的组合效果最好。

至此,我们总结了对所提出模型的理论理解。现在我们开始建造我们自己的东西—

代码模板

我们将使用 [Simple Transformers python 库](/Simple Transformers is a Natural Language Processing (NLP) library designed to simplify the usage of Transformer models without having to compromise on utility. It is built on the amazing work of Hugging Face and their Transformers library.)来设置实现 DPR 模型的模板。简单变压器旨在简化变压器模型的使用,而不必牺牲效用。它建立在令人惊叹的拥抱脸及其变形金刚库的基础上。它支持 NLP 中的各种任务,可以在这里随意查看整个列表。好的,那么你可以使用下面的命令来安装它-

>> pip install simpletransformers

接下来我们要做的是定义我们将用来训练模型的所有配置。该库为您提供了使用数据类或 python 字典定义配置的选项。为了这篇博客,我们将使用 dataclass: RetrievalArgs。也可以从这个源中随意检查字典选项。

from simpletransformers.retrieval import RetrievalModel, RetrievalArgs
import torch### loading pre-trained weights of passage and question encoder ###
Eq = "bert-base-uncased"
Ep = "bert-base-uncased"model_args = RetrievalArgs()
#model_args.retrieve_n_docs
#model_args.hard_negatives
#model_args.max_seq_length
#model_args.num_train_epochs
#model_args.train_batch_size
#model_args.learning_rate

这里,Eq 和 Ep 持有将用于编码问题和段落的模型。您还可以从一些公开的预先训练好的 DPR 编码器开始,用于上下文和段落(一个例子是上下文:“Facebook/DPR-question _ encoder-single-NQ-base”,段落:“Facebook/DPR-CTX _ encoder-single-NQ-base”)。当“hard_negatives”设置为 True 时,有助于模型在批内否定的基础上学习使用 BM25 等技术生成的否定示例。如上所述,该论文还提出了批内底片的概念,并且还基于 BM25 或类似的方法提取底片样本。上面提到的所有参数都是不言自明的。该库提供了用于生成这些硬底片的代码片段。请在这里随意检查。

接下来,我们加载数据集,为了简单起见,我将手动定义一些数据点(源)。但是这将使您了解如何转换您的实际数据以适应管道。

import pandas as pdtrain_data = [{"query_text": "Who is the protaganist of Dune?","gold_passage": 'Dune is set in the distant future amidst a feudal interstellar society in which various noble houses control planetary fiefs. It tells the story of young Paul Atreides, whose family accepts the stewardship of the planet Arrakis. While the planet is an inhospitable and sparsely populated desert wasteland, it is the only source of melange, or "spice", a drug that extends life and enhances mental abilities. Melange is also necessary for space navigation, which requires a kind of multidimensional awareness and foresight that only the drug provides. As melange can only be produced on Arrakis, control of the planet is a coveted and dangerous undertaking.',},{"query_text": "Who is the author of Dune?","gold_passage": "Dune is a 1965 science fiction novel by American author Frank Herbert, originally published as two separate serials in Analog magazine.",}...
]train = pd.DataFrame(train_data
)

有了这个,你就可以继续训练 DPR 模型了,但是如果你已经将 hard_negatives 设置为 True,你就必须为每个数据点设置另一个 key (hard_negative ),格式如上所述。

cuda_available = torch.cuda.is_available()model = RetrievalModel(model_type = "dpr",context_encoder_name = Ep,query_encoder_name = Eq,args = model_args,use_cuda = cuda_available
)model.train_model(train, eval_data = eval, \output_dir = 'model/', \show_running_loss = True)model.eval_model(test, verbose=True)

接下来,我们将所有必要的参数传递给我们的模型,并通过指定保存模型的输出目录来进行训练。“eval”和“test”数据帧的格式也与“train”完全相同。这里,train、eval 和 test 是 pandas 数据帧,包含 2-3 列— query_textgold_passagehard_negative(可选)

  • query_text:查询/问题文本序列
  • gold_passage:黄金段落文字序列
  • hard_negative:BM25 中的硬否定段落文本序列(可选)

用训练好的 DPR 模型进行推理

一旦训练完成,你已经保存了你的模型。现在,您可以将问题传递给模型,指定要返回的文档数量,这样就可以了。

questions = ['What year did Dune release?'
]predicted_passages, _, _, _ = model.predict(questions, retrieve_n_docs=2)

总结想法

看到新兴技术的采用以及它在这种实际用例中的应用是非常有趣的。在本文中,我们讨论了什么是 DPR,它是如何工作的,以及它的用途。我们也使用简单的 Transformers python 库实现了它。请随意查看其他规范,您可以使用它们来训练一个高效的模型。我希望你喜欢这篇文章。此外,如果你喜欢看视频而不是阅读文本,你可以在这个查看视频论文解释。尽管你在那里找不到这里独有的实现;)

另外,如果你愿意支持我成为一名作家,可以考虑注册成为一名媒体成员。每月只需 5 美元,你就可以无限制地使用 Medium。

理解描述性统计:借助高斯、左偏和右偏数据集

原文:https://towardsdatascience.com/understanding-descriptive-statistics-with-the-help-of-gaussian-left-skewed-and-right-skewed-897b296382c6

从统计学角度重新设计你的数据,使其失真

克里斯·利维拉尼在 Unsplash 上的照片

正态分布及其特征已被广泛讨论。我们还必须很好地理解它的描述性统计。然而,您是否曾试图左偏或右偏一个完全正态分布的数据集,以测试其正态性或观察对其众多描述性统计数据的影响。如果没有,请继续,因为这是我们在这篇文章中要做的。

背景:

在我们进入 Python Notebook 并开始编辑数据之前,让我简单介绍一下正态(或高斯)分布和描述性统计。

数据集中的大部分值具有位于范围中间的正态分布聚类,其余值对称地向任一极端逐渐变小。由于大多数自然发生的事件是正态或大致正态分布,正态分布是至关重要的。这些变量的例子包括身高、出生体重、鞋码、血压、退休年龄、得分、收入分配和经济中的商品价格。

正态分布数据集的属性:

1.绘制时,正态分布数据集呈对称钟形,平均值位于中间。

2.平均值、中值和众数都相等。

3.它只有一个峰值,因为它是单峰的。

4.一半的值低于平均值,一半高于平均值,表明分布关于平均值是对称的。

5.它遵循经验法则:经验法则,也称为三西格马法则68–95–99.7法则,是一种统计法则,它规定对于正态分布,

  • 大约 68%的值落在平均值的一个标准偏差内。
  • 大约 95%的值落在平均值的两个标准偏差范围内。
  • 几乎所有的值(约 99.7%)都在平均值的三个标准差范围内。

6.偏斜系数为零。

描述性统计:

描述性统计是定量描述或列出特定数据集特征的汇总。通过观察描述性统计,我们可以对数据分布有一个很好的概念。

不同类型的描述性统计包括:

  • 集中趋势的度量—平均值、中间值和众数
  • 离差的度量-范围、IQR、方差和标准差
  • 位置统计-最小值、最大值、十分位数和百分位数
  • 对称性的度量——偏度和峰度

完全正态分布的描述性统计

让我们从正态分布的数据集开始,然后将分布向左和向右倾斜,以检查统计数据是如何变化的。我将从一个来自生物统计学领域的正态分布数据的极好例子开始。这个数据是关于“家蝇翅膀长度”的,取自 Seattlecentral.edu(Sokal,R.R .和 P.E.Hunter. 1955 )。

数据中总共有 100 条记录,只有一列——“长度”。

df = pd.read_excel("s057.xls")
print(df.shape)
print(df.info())

图一。数据集摘要。作者形象。

为了计算不同的描述性统计数据,我们可以在 Python 中定义以下函数:

现在,让我们检查数据集的正态分布的各种特征。

测试 1:常态测试

我们将使用 夏皮罗-维尔克检验 来检验数据集的正态性。这检验了数据来自正态分布的无效假设。即

  • H0:数据呈正态分布
  • H1:数据不符合正态分布

在 SciPy 实现中,p 值可以解释如下:

  • p < = alpha:剔除 H0,非正态分布。
  • p>α:拒绝 H0 失败,即正态分布

具有较高 p 值的结果通常支持我们的样本取自高斯分布的假设。

我们可以从下面的输出中观察到我们的数据集是正态分布的。

检验统计= 0.993,p 值为 0.876
数据呈正态分布(p 值为> 0.05,拒绝 H0)

测试 2:正态分布的描述性统计:

一.均值=中位数=众数

二。如果分布是单峰的

三。如果它具有以平均值为中心的钟形对称曲线

四。偏斜系数= 0

要测试正态分布的上述四个特征,请使用下面的代码块。它将使用前面为描述性统计定义的函数。

这是输出结果:

图二。正态分布的直方图描述性统计。作者形象。

查看上面的输出,我们可以发现:

  • 平均值、中值和众数都相等(45.5)
  • 分布是单峰的。
  • 我们有一个以平均值为中心的对称钟形分布。
  • 偏斜系数为零。

测试 3:检查是否遵循经验法则:68–95–99.7 法则,标准差为 1–2–3

正态分布的数据集遵循经验法则,即 68%、95%和 99.7%的值分别位于平均值的一个、两个和三个标准差内。为了测试这个属性,我们可以使用下面的代码块。

这是输出结果:

具有+/- 1 sigma 的平均值覆盖的面积为:68.27
具有+/- 2 sigma 的平均值覆盖的面积为:95.45
具有+/- 3 sigma 的平均值覆盖的面积为:99.73

测试 4:异常值检查

正态分布中可能存在也可能不存在异常值。如果没有明显的例外,一切都很好。我们将首先在正态分布的数据集上运行这个测试。然后,我们将在左偏或右偏的数据集上再次运行这个测试,以确定数据操作是否导致了任何异常值。

我们可以看到如下输出:IQR 和 3-sigma 技术都不能发现任何异常值。

图 3:异常测试的结果。作者形象。

到目前为止,我们已经检查并确认了完全正态分布的参数。现在,我们将左右倾斜分布,看看它如何影响正态性检验、描述性统计/可视化和异常值的存在。

更改数据分布,使其成为左偏(或负偏)数据集

我们知道,左偏斜或负偏斜数据集是指分布的尾部在左侧比在右侧长,并且中位数更接近第三个四分位数而不是第一个四分位数。对于这样的分布,我们也应该有众数>中位数>均值。

我们将从数据集的最高百分位中裁剪或丢弃一些值,使其向左倾斜。这将理想地留下更多来自较低分位数的数据,使分布稍微负向(或向左)倾斜。

当你打印偏斜度的系数时,你会注意到它是非零的负值(本例中为-0.83)。

46.4
产生的数据帧形状为:(50,1)
新的倾斜值为:-0.83

我们已经在开始时创建了几个函数来测试或计算各种参数。现在,我们将在新的数据集上逐个调用它们(这应该是一个左偏分布),并查看它们如何影响不同的结果。

测试 1:正态性测试

在新的数据帧 df_left_skew 上运行相同的夏皮罗-维尔克测试产生这个结果。

检验统计= 0.911,p 值为 0.001
数据不呈正态分布(p 值为< 0.05,拒绝 H0)

因此,我们现在有证据表明我们的数据集不再遵循正态分布。让我们看看它的描述性统计和分布图来验证这一点。

测试 2:变更数据的描述性统计:

调用先前定义的描述性统计函数并绘制直方图会产生以下结果:

图 4。左偏(或负偏)数据集的描述性统计和直方图。作者形象。

因此,对于左偏斜数据集,我们可以看到:

  • 均值、中值和众数不再重叠。
  • 事实上,我们可以注意到模式>中值>意味着——证明一个左偏或负偏的数据。
  • 一个长的左尾巴,它导致平均值在峰值的左边,中间值更接近第三个四分位数。

测试 3:异常值检查

在负偏差数据中,我们将对异常值进行最后一次检查。一个倾斜的数据集应该理想地包含一些异常值。对这个左偏斜数据集运行异常值测试会产生以下结果:

图 5:左偏数据集中的异常值。作者形象。

我们看到 IQR 方法发现了一个异常值,而 3-sigma 方法没有发现。此外,这种情况下的异常值低于 Q1 的最小值-1.5 * IQR-表示数据集呈负偏态。请记住, *IQR 方法只考虑+/-2.7 适马用于异常值发现(因此不太保守),而 *3 适马(或 Z 得分)方法检查+/-3 适马用于异常值检测。参考这个 博客 了解更多关于离群点检测的细节。

更改数据分布,使其成为右偏(或正偏)数据集

在最后一节中,我们将从原始数据集的底部百分位中截取或删除一些值,使其成为右偏分布。理想情况下,这将会留下更多来自上分位数的数据,从而使分布稍微偏正(或偏右)。我们可以使用以下代码块来实现这一点:

这一次,当你打印偏斜度的系数时,你会注意到它是非零的正值(本例中为 0.83)。

44.6
产生的数据帧形状为:(50,1)
新的倾斜值为:0.83

让我们快速运行正态性测试,查看描述性统计数据,并检查异常值。

测试 1:正态性测试

在新的数据帧 df_right_skew 上运行相同的夏皮罗-维尔克测试产生这个结果。

检验统计= 0.911,p 值为 0.001
数据不呈正态分布(p 值为< 0.05,拒绝 H0)

因此,我们现在有证据表明我们的数据集不再遵循正态分布。让我们看看它的描述性统计和分布图来验证这一点。

测试 2:右偏数据的描述性统计:

我们只需要将新的数据帧传递给先前定义的函数。数据帧 df_right_skew 的描述性统计数据和直方图如下:

图 6。右偏(或正偏)数据集的描述性统计和直方图。作者形象。

因此,对于右偏数据集,我们可以看到:

  • 均值、中值和众数不再相互重叠。
  • 事实上,我们可以注意到模式<中值<的意思是——证明一个正确的或正偏的数据。
  • 一条长长的右尾巴,因此的意思是也到了右边的顶峰。

测试 3:异常值检查

我们可以使用相同的代码块并传递新的数据帧 df_right_skew 来检查我们现在是否有任何异常值。对这个右偏数据集运行异常值测试会产生以下结果:

图 7:右偏数据集中的异常值。作者形象。

我们看到,IQR 方法发现了一个异常值,因为它不如上面解释的 3-sigma 方法保守。另请注意,这种情况下的异常值超出了 Q3 +1.5*IQR 的最大值,表明数据集呈正偏态。

结论:

在这篇文章中,我们从一个完全正态分布的数据集开始,并测试了它与正态分布相关的许多属性。然后我们改变数据来创建一个左偏数据集和一个右偏数据集。我们还研究了当数据向左或向右倾斜时,描述性统计数据是如何变化的。这应该有助于您理解倾斜数据集的统计数据或直方图是如何显示的。

在这里找到数据集和代码:https://github.com/kg-shambhu/Descriptive_Statistics

你可以在领英上联系我:https://www.linkedin.com/in/shambhukgupta/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/72944.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

网站中的修改在后台哪里?

对于大多数网站来说,修改内容通常需要通过后台管理系统进行。具体步骤可能因不同网站而异,但一般流程如下:登录后台:首先,你需要登录到网站的后台管理系统。这通常需要一个管理员账号或具有相应权限的账号。 找到内容管理:登录后,查找“内容管理”、“页面管理”或类似名…

TowardsDataScience-博客中文翻译-2022-四十四-

TowardsDataScience 博客中文翻译 2022(四十四)原文:TowardsDataScience 协议:CC BY-NC-SA 4.0Python 中的求根方法原文:https://towardsdatascience.com/root-finding-methods-from-scratch-in-python-84040c81a8ba使用 Python 的二分法、牛顿法和割线法数学求根算法 介绍…

TowardsDataScience-博客中文翻译-2022-四十九-

TowardsDataScience 博客中文翻译 2022(四十九)原文:TowardsDataScience 协议:CC BY-NC-SA 4.0测量误差对线性回归模型的影响原文:https://towardsdatascience.com/the-effect-of-measurement-errors-on-a-linear-regression-model-c853bb689974图片由皮克斯贝 ( 牌照)的安…

TowardsDataScience-博客中文翻译-2022-二十九-

TowardsDataScience 博客中文翻译 2022(二十九)原文:TowardsDataScience 协议:CC BY-NC-SA 4.0如何使用计算机视觉读取酒瓶上的标签?(第一部分)原文:https://towardsdatascience.com/how-to-read-a-label-on-a-wine-bottle-using-computer-vision-part-1-25447f97a761欢迎…

格点拉格朗日插值与PME算法

本文介绍了使用基于格点拉格朗日插值法的Particle Mesh Ewald算法,降低分子力场中的静电势能项计算复杂度的基本原理。静电势能的计算在Ewald求和框架下被拆分成了远程相互作用项和短程相互作用项,其中短程相互作用项关于实空间的点电荷间距快速收敛,而远程相互作用项在倒易…

公司网站怎么修改?数据库内网站密码修改?

修改公司网站通常涉及以下几个步骤:确定需求:明确需要修改的内容,如页面设计、功能增加或信息更新等。 收集相关资料和内容,确保所有需要的信息都准备齐全。选择工具或平台:如果网站是基于WordPress、Joomla等CMS系统构建的,可以通过后台直接进行编辑。 对于自定义开发的…

修改默认登录网站密码?

修改默认登录网站密码通常涉及以下几个步骤,具体操作可能会根据不同的网站或系统有所差异:登录账户:打开你需要修改密码的网站。 使用当前的用户名和密码登录你的账户。进入账户设置:登录后,找到并点击“账户”、“个人资料”或“设置”等相关链接或按钮。 在账户设置页面…

如何在后台修改网站资料?网站后台如何修改字体?

在后台修改网站资料通常涉及以下几个步骤,具体操作可能会根据不同的内容管理系统(CMS)或网站平台有所不同:登录后台管理界面:使用管理员账号和密码登录到网站的后台管理界面。导航至内容管理:在后台找到“内容管理”、“页面管理”或类似的选项,这通常是编辑网站资料的地…