如何在 QGIS 中制作假彩色卫星影像
艺术和数据一体化
所有图片均由作者提供
假彩色卫星图像对于直观分析不同的景观特征非常有用。它们看起来也很酷,用于制作非常艺术的地图和展示。
假彩色卫星图像是通过用不同的颜色显示不同的波段组合而生成的。例如,通过将来自近红外传感器的数据显示为红色,将来自红色传感器的数据显示为绿色,将来自绿色传感器的数据显示为蓝色,来创建彩色近红外图像。
彩色近红外通常用于显示植被覆盖的区域和被水覆盖的区域(见下图)。植物反射近红外(NIR)光,水吸收 NIR 和红光。这种波段和颜色的组合使得健康的绿色植被在影像中呈现出鲜红色,而被水覆盖的区域呈现出近乎黑色。
在 QGIS 中,通过改变红色、绿色和蓝色光显示的图像波段,从卫星图像中创建假彩色合成图像。这些更改是通过将图层的符号系统设置为多波段颜色并更改默认波段显示设置来实现的。
本教程将演示如何使用 Landsat 8 卫星影像在 QGIS 中创建假彩色合成影像。同样的步骤可用于使用任何多波段影像创建假彩色合成图像。视觉学习者可以通过视频观看演示。
创建多波段栅格图层
如果您的影像已经是多波段格式,您可以进入下一部分。否则,请按照下列步骤操作。
确定栅格图层中波段的数量
要检查影像中的波段数,请将其添加到 QGIS,然后转到图层属性。选择左侧的信息选项卡。该选项卡可能包含每个波段的统计数据。向下滚动,直到看到尺寸信息。您将看到栅格图层中的行数、列数和波段数。
将 Landsat 波段添加到 QGIS
在本教程中,我将使用 Landsat 8 分析就绪数据。下面的视频演示了如何免费获取这些数据。
如何下载 Landsat 8 图像?
每个 Landsat 波段将由一个单独的栅格文件(.tif)。波段 1-7(实际上是 2-7)是我们最感兴趣的。下面列出了带的描述。更多关于陆地卫星 8 号仪器的信息可以从美国地质调查局获得。
- 1 级沿海大气土壤(0.43-0.45 米)30 米
- 波段 2 蓝色(0.450-0.51 米)30 米
- 带 3 绿色(0.53-0.59 米)30 米
- 波段 4 红色(0.64–0.67 米)30 米
- 波段 5 近红外(0.85–0.88 米)30 米
- 波段 6 短波红外线(SWIR)1(1.57-1.65 米)30 米
- 波段 7 SWIR 2(2.11-2.29 米)30 米
将 Landsat 8 波段 1–7 添加到 QGIS。确保波段以数字顺序出现在内容列表中。这对于确保每个波段对应于多波段栅格的正确图层非常重要。添加后,您的目录应该类似于下图。
添加了 Landsat 8 波段 1–7 的 QGIS 目录。
将多个波段合并到一个栅格中
现在是时候将所有 Landsat 波段合并成一个单一的多波段栅格了。为此,进入 QGIS 主菜单上的“栅格”下拉菜单。展开“杂项”并选择“构建虚拟光栅”(见下图)。
打开构建虚拟栅格工具。
在出现的构建虚拟栅格窗口中,您会注意到输入图层下的一条消息,显示“选择了 0 个输入”。点击省略号(三个点。。。)在输入图层旁边。这将打开一个新窗口,显示当前加载到 QGIS 项目中的所有栅格波段。选择所有 7 个 Landsat 8 波段(这些应该是项目中仅有的 7 个图层)。您可以使用“全选”按钮。然后单击“确定”。
选择所有 7 个 Landsat 波段。
最后一步是指定输出文件。您也可以保留默认设置来创建临时图层。如果这样做,关闭 QGIS 后临时图层将不可用。指定输出文件后,单击 Run。构建虚拟栅格完成运行后,一个新图层将被添加到 QGIS 内容列表中。
构建虚拟栅格。
创建真彩色卫星图像
创建多波段栅格并将其添加到 QGIS 界面后,您会注意到它可能会以一些奇怪的颜色显示。让我们从创建真彩色影像开始,了解显示和符号化多波段影像的基础知识。
打开多波段栅格的属性,然后转到符号系统选项卡。“渲染类型”应为“多波段颜色”。如果不是,从“渲染类型”下拉菜单中将其更改为“多波段颜色”。
多波段颜色渲染器显示用于调整红色、绿色和蓝色波段的选项。默认情况下,红色、绿色和蓝色波段可能会分别设置为波段 1、2 和 3。然而,与红色、绿色和蓝色相对应的 Landsat 波段是波段 5、波段 4 和波段 3。使用下拉菜单将适当的波段分配给适当的颜色。
使用这些波段分配以真彩色显示 Landsat 8 图像:
- 红色波段:Landsat 8 波段 4
- 绿色波段:Landsat 8 波段 3
- 蓝色波段:Landsat 8 波段 2
您的符号系统窗口应该如下图所示。一旦完成,单击确定,多波段图像将以真彩色显示。
产生真彩色陆地卫星图像的波段分配
这是显示的陆地卫星 8 号真彩色图像的一部分。
基于 QGIS 的假彩色卫星图像
彩色红外线
正如本文前面提到的,彩色近红外(或彩色红外)图像以红色显示绿色健康的植被。这张图片很容易用我们正在使用的地球资源卫星数据制作。我们只需要重新分配哪些波段显示为红色、绿色和蓝色。在 QGIS 符号系统选项卡中,将波段分配更改为以下内容,以创建彩色近红外图像。
彩色近红外假彩色波段组合
- 红色波段:陆地卫星 8 号波段 5
- 绿色波段:Landsat 8 波段 4
- 蓝色波段:陆地卫星 8 号波段 3
你会得到一个与下图颜色相似的图像。你可以看到山上和农田里的植被是鲜红色的,湖水几乎是黑色的。
由 Landsat 8 数据生成的彩色红外图像。
另一个 Landsat 8 波段组合
下一个组合将绿色植被显示为亮绿色。如果你认为我们可以简单地将近红外波段显示为绿色,那你就错了!我们将把 SWIR 分配到红色波段,把绿色分配到蓝色波段。
- 红色波段:陆地卫星 8 号波段 7
- 绿色波段:Landsat 8 波段 5
- 蓝色波段:陆地卫星 8 号波段 3
这种波段组合可以帮助我们可视化植被、土地、雪和水。在下图中,你会再次注意到,水几乎是黑色的,植被是绿色的,裸露的地面是粉红色/紫色的。在这幅图像中没有雪,但是如果有雪的话,它会呈现亮蓝色(几乎是青色)。这种波段组合通常被称为“自然去除大气”。
从地球资源卫星数据创建的“去除大气影响的自然”图像。
制作自己的颜色组合
请随意尝试乐队组合。你会发现一些看起来和这里提到的相似。你会发现有些看起来真的很丑,有些看起来很奇怪。下面我举了一个有趣的例子。它不一定有特定的功能,但它确实为不同的功能呈现了一些有趣的颜色。
由地球资源卫星数据生成的有趣的假彩色图像。
结论
理解可见光如何表现不同地表的特征是遥感的基础。使用多波段影像并生成伪彩色合成影像有助于您了解影像不同波段之间的相互作用。拥有制作这些图像的技能还将提高您的数据可视化,并帮助您有效地显示相关信息。
观看视频
教程的视频演示。
最初发表于【https://opensourceoptions.com】。
如何用 Fastai 制作一个艺术模型
使用学习率查找器和渐进调整大小方法进行图像分类,以便在短时间内获得最佳结果。
Amy Hirschi 在 Unsplash 上的照片
当我第一次开始 fastai 的旅程时,我非常兴奋地建立和训练一个深度学习模型,它可以在短时间内给出惊人的结果。既然我们已经看到了 fastai 与一些 Pytorch 基本函数一起使用的一些幕后培训术语,现在是时候看看在构建模型方面多做一点努力会产生什么结果了。
在这篇文章的最后,我将链接我以前的文章,在这些文章中,我记录了我对 fastai 的学习。😃
获取数据
我们需要这些数据来开始。它来自 Kaggle 的石头剪子布数据集。这个任务变成了一个多类图像分类问题,有三个类(每个类有训练、有效、测试文件夹)包含大小为 300x300 的 RGB 彩色图像。
为 fastai vision 的所有内容指定导入函数,并设置 path 变量。
**from** **fastai.vision.all** **import** ***path = Path('/storage/RockPaperScissors/RockPaperScissors/data/')
path.ls()**Output:
(#4) [Path('/storage/RockPaperScissors/RockPaperScissors/data/test2'),Path('/storage/RockPaperScissors/RockPaperScissors/data/valid'),Path('/storage/RockPaperScissors/RockPaperScissors/data/.DS_Store'),Path('/storage/RockPaperScissors/RockPaperScissors/data/train')]# make sure to set that path to wherever you've kept your data either locally or online.
现在我们将定义一个数据块来从文件夹中获取数据。我们指定这些来确保我们的数据在编写最少的代码时对模型可用:
- 如何用 get_image_files 函数获取图像文件——这只是收集我们的火车和有效文件夹中的所有图像文件
- 获得带有 parent_label 的类,这确保我们获得直接的父文件夹名作为我们的类名,最后,
- 使用grand parent splitter进行训练和验证拆分,这将为我们提供单独的数据集,用于使用文件夹进行训练和验证,这些文件夹在层次结构中处于上一级,或者训练和有效文件夹。
**def** get_dls(bs, size):
dblock = DataBlock(blocks = (ImageBlock, CategoryBlock),
get_items = get_image_files,
get_y = parent_label,
splitter = GrandparentSplitter(),
item_tfms = Resize(size)
)
**return** dblock.dataloaders(path, bs = bs)
这将返回一个数据加载器,它将给出一个批量大小为 bs 和一个图像大小为 大小为 的图像。
什么是渐进式调整大小,我们如何应用它?
正如杰瑞米·霍华德在他的书中所说的那样:用小图片开始训练,用大图片结束训练。使用小图像进行大部分 epochs 训练有助于训练更快完成。使用大图像完成训练使得最终的准确度高得多。
这是一种实验性的技术,已经被证明是非常有用的,在获得更高的精确度方面,比在使用相同大小的图像时要有用得多。
现在让我们看看怎样才能训练出多种尺寸,好吗?
我们将得到的批量大小为 64,图像大小为更小的 128x128。
dls = get_dls(64, 128)
现在,让我们来计算一下这部分培训应该使用什么样的学习率。
找到合适的学习速度
首先,我们利用迁移学习建立一个模型,如下所示。
learn = cnn_learner(dls, resnet34, metrics=accuracy)
然后,我们绘制一个图表,看看如何找到学习率。
learn.lr_find()
输出看起来像这样,如果我们取学习率的一个特定值,我们的损失看起来会是什么样子。
学习率图
看起来采用 1e-3 左右的学习率将足以确保我们的损失随着训练而减少。我们会选择那个。
learn = cnn_learner(dls, resnet34, metrics=accuracy)
learn.fit_one_cycle(10, 1e-3)
我们在最初的几个时代里看到了相当显著的结果。
学习第 1 部分
注意:我在 GPU 上训练这个模型,这就是为什么每个历元只需要几秒钟。
如果你只在 CPU 上训练,这将花费更长的时间,有时甚至大约 10 分钟。
既然我们已经在较小的图像尺寸上训练了模型,我们可以进行训练的第二部分。
我们使用批量大小为 128,图像大小为 224,用于模型的下一次微调。
learn.dls = get_dls(128, 224)
learn.fine_tune(5, 1e-3)
学习第 2 部分
正如您可以推断的那样,这为我们的训练带来了几乎 95%的准确率,并且在 GPU 上训练只需要大约三分钟!
结束…
Fastai 使我们能够快速开发任何深度学习任务,正如我在前几周对它进行的实验一样,我发现自己越来越喜欢它超级简单的方法。如果您热衷于跟随我的旅程,请确保关注我的持续更新,因为我将使用这个神奇的库探索更多的深度学习任务。
正如我之前承诺的,这里是我为 fastai 写的其他文章。编码快乐!😁
- fastai 快速入门:FastAI 快速入门——我的经历
- 像素相似性方法—引擎盖下第 1 部分: Fastai —探索训练过程—像素相似性方法
- 随机梯度下降和从零开始训练—引擎盖下第 2 部分: Fastai —从零开始随机梯度下降的多类分类
此外,这里是 GitHub repo 链接,包含所有代码:
https://github.com/yashprakash13/RockPaperScissorsFastAI
你想每周或两周收到一封免费的、干净的电子邮件,里面包含我发表的最好的策划文章和教程吗?加入我的代码广播吧!
如何用你的(天文学)图像制作视频
从静态图像制作您自己的视频
你想过如何用你的数据制作一个视频吗?不要再想了!
你可能已经知道如何制作一个视频效果,方法是制作许多手工绘制的小图,然后一张一张地翻转它们。我采用了类似的方法,通过制作小的剪贴画并一个接一个地展示它们,从一幅大的射电天文学图像中制作了一个视频。
在我的例子中,我使用了来自 LOFAR 射电望远镜的图像。这台望远镜在低频无线电频谱中观察天空。它获得的信息可以转换成颜色,这样我们就可以将无线电波转换成人类可以理解的图像。原来宇宙中有很多正在发射无线电波的物体,比如活动星系、星系团、恒星形成星系、来自行星的磁场等等!你可以在这里找到来自 LOFAR 的惊人图片。
让我们创造吧!
但是,我们如何用这些图像制作一部电影,这样我们就可以浏览它了?这就把我们带回了图纸的翻转。在这种情况下,我们只需要将大的无线电图像切割成(许多)较小的图像(帧),每次移动图像的中心一点点,并将它们连成一行,这样我们就可以获得移动效果。
那么,你是怎么做到的呢?我使用了 Python 和命令行工具 FFmpeg。我的完整代码可以在这里找到。这是一个更大项目的一部分,在这里你还可以制作海报和互动地图。现在,你只需要关注脚本 make_movie.py.
首先用以下内容克隆回购:
git clone [https://github.com/jurjen93/advanced_astro_visualiziation.git](https://github.com/jurjen93/advanced_astro_visualiziation.git)
现在你可以运行:
python make_video.py
在这里你可以使用以下标志
-d
→选择从互联网下载特定的 fits 文件。如果需要,使用1
,否则留空。-csv
→给出一个包含源文件的特定 CSV 文件。-N
→要使用的信号源数量。-fr
→视频的帧率。建议用60
让视频流畅。-fi
→适合文件使用。(如果您没有下载您的 fits 文件)
所以最后你用了类似
python make_video.py -csv catalogue/catalogue_lockman.csv -d 1 -N 2 -f 60
的东西
要做到这一点,你需要有一个以资源为中心的目录。“目录”文件夹中有一个例子。
使用以下字段:
RA
→物体的赤经(与经度相当)DEC
→物体的赤纬(与纬度相当)imsize
→图像尺寸(度)
结果
现在,代码将帮助您从数据中生成一些精彩的视频,例如:
您还可以修改代码,将多个分辨率或大图像一个接一个地堆叠起来,这样您就可以做出类似这样的东西:
请随意修改 make_movie.py 脚本,因为它应该很容易使用,并且您可以制作自己的精彩视频。如果你有任何改进的建议,请告诉我!玩得开心!
如何让敏捷真正为分析服务
行业笔记
在所有关于将敏捷引入分析的争论中,我们可能忽略了一点
2001 年,当 17 名开发人员聚集在犹他州的 Wasatch 山区,讨论如何改进他们构建软件产品的方式时,我怀疑他们没有想到 20 年后,一个既不是软件开发人员也不是产品构建人员的社区会如此热烈地讨论他们的工作。然而,事实就是如此,几乎每周都有文章、 slack 问题或更多文章讨论敏捷分析的理论和实践。
我们是否以及如何将敏捷思想应用于数据,这可能会引起很大的分歧,因为它似乎引出了更大的、更现实的问题,即我们作为数据从业者是谁:我们真正的角色是什么——回答问题还是让其他人自己回答?数据是一种产品,一种服务,还是别的什么?商业用户是我们的合作伙伴还是我们的用户?我们应该如何最好地与他们合作?
随着承诺改变我们的数据堆栈和工作方式的技术和理论(例如,度量层和最后一英里分析)的到来,这些问题变得更加紧迫。随着我们进入这个新时代,是时候决定我们带走哪些敏捷概念,留下哪些了。
敏捷:期望与现实
在过去的 20 年里,敏捷的名声已经从一个边缘运动发展成为一个创始人所说的“工业综合体”。更好的组织、协作和结果的承诺吸引了几乎每个行业的每个团队尝试敏捷方法。
2017 年,我在一家大型科技公司的数据团队终于加入了战斗,并过渡到一个“敏捷分析”团队。也就是说,我们开始使用:
- 跟踪所有分析项目的看板
- 每天站起来回顾团队内部的进展
- 任何工作开始前无情的需求收集过程
就个人而言,前两个对我的生活几乎没有影响,除了让我嫉妒团队中其他人正在做的更酷的项目。最终,我的工作是我和企业之间的事;对我来说,每天和他们交谈会更有用。
具有讽刺意味的是,正是敏捷的第三个实现,需求收集,使得这变得不可能。通过更彻底的范围界定过程,我们预先加载了与业务的所有交互,以尽量减少迭代和更改。这种清晰让我们觉得更有控制力,但实际上我们只是把自己和伴侣进一步分开了。
很大程度上,这是我看到的当今数据团队拥抱敏捷的方式。看板帮助团队保持组织性。用户故事提供了一个框架,将模糊的问题转化为全面的项目。虽然这些以及其他敏捷方法可以提高组织性和清晰性,但我们也必须承认这是有代价的:我们和我们的商业伙伴之间的鸿沟不断扩大。
这提出了两个问题:
- 这样真的敏捷吗?
- 这种工作方式将来会对我们有用吗?
敏捷的精神
敏捷现在被工业化的过程和公司术语所识别,但是在开始的时候,它有一个简单的目标:更快地构建更好的软件。
实现这一愿景的四个关键值是:
- 流程和工具上的个人和交互
- 工作软件综合文档
- 客户协作合同谈判
- 根据计划应对变化
敏捷的真正精神是与众不同的,而且比我们今天用来与它互动的过程更有说服力。
敏捷方法的核心是将产品制造过程中固有的令人沮丧的东西——快速变化的需求——转化为竞争优势。
你可以想象这些软件开发人员会多么容易地说"嘿,伙计们,当你告诉我们我们实际上需要这个特性而不是那个特性时,我们厌倦了我们的工作每个月都变得过时,所以从现在起,在我们进行任何开发之前,你必须无情地验证需求和范围。”
但至关重要的是,他们认识到他们快速转移技术的能力是比你的竞争对手更快地开发出优秀产品的关键。因此,他们专注于创造一种工作方式,这种方式不仅能使他们的代码中枢化,还能更快地与产品团队和用户沟通。
这是敏捷的真正精神,它有更多的东西可以教我们如何在今天和未来使用数据。
分析类比
为了将敏捷的精神应用到数据环境中,我们必须首先考虑我们与软件开发人员的相似和不同之处。
产品问题
我们所做的和软件开发人员所做的最显著的区别在于我们的最终产品。在软件中,目标是得到最终用户喜欢的产品。在数据方面,我们的目标是帮助人们做出他们信任的决定,用户到达目的地的过程可能与最终结果一样重要。
最常见的是,我们用数据讲述故事的方式体现了这一点。我们使用笔记本来捕捉上下文和流程,使用演示文稿来引导用户理解。正是在这个过程中,我们建立了信任,将图表转化为见解,并使我们的数据变得有价值。
这也是我们工作中最大的难题之一背后的驱动因素:后续问题和特别请求。
这些问题和请求来自于好奇心,代表了一种渴望,那就是我们在精心制作的数据故事中获得的对数据的同样深入的理解。然而,在实践中,我们试图用前期需求收集的过程和没有为这种工作方式腾出空间的工具来消除这些问题。
忽视数据和软件产品之间的这一关键差异可能(并且已经)将我们引向通往敏捷分析的错误道路。
敏捷分析宣言 SFD
为了将敏捷精神应用到分析领域,我们应该问自己一个类似于 17 位敏捷创始人的问题:
如果我们将今天从事分析工作固有的令人沮丧的东西——来自业务的不断发展的问题——转化为优势,会怎么样?如果我们这样做是为了拉近我们与商业伙伴的距离,而不是疏远他们,会怎么样呢?
我当然不会假装知道所有的答案,但本着安妮·拉莫特的傻逼初稿想法的精神,我已经做出了回答这个问题可能需要的许多迭代中的第一个:
- 仪表板上的决策:通过关注人们希望用数据做什么,我们可以越过他们问的第一组问题,关注有价值的迭代和后续问题,建立信任,培养好奇心并推动行动。
- **对完美输出的功能分析:**为了实现快速迭代,我们将不得不花费更少的时间来制作完美的输出,并专注于尽快从一个问题转移到下一个问题。
- **共享数据超过把关数据:**我们将不得不与我们的业务合作伙伴共同承担我们的数据和数据“产品”的责任。这将有助于建立信任,并让我们所有人都对培养伟大的数据产品和数据驱动的文化负责。
- **个人和互动优于流程和工具:**当有疑问时,我们需要依靠我们与企业建立的关系,而不是我们用来帮助指导这些关系的工具。
就像最初的敏捷 17 一样,我知道我不能指望自己解决这个问题,我们作为一个分析社区必须讨论这个问题,并一起找到解决方案。你可以在这里加入辩论,我希望在我们的 比 时事通讯上分享更多关于这个话题的想法。
如何建立有效的共指消解模型
如何改进现成的共指消解库
Dariusz Sankowski 在 Unsplash 上拍摄的照片
介绍
在本文中,我们介绍了如何改进 AllenNLP 的共指消解模型,以实现更连贯的输出。我们还介绍了几个集成策略,以同时利用 Huggingface 和 AllenNLP 模型。
简而言之,共指消解(CR)是一个 NLP 任务,旨在替换句子中所有的歧义词,以便我们获得不需要任何额外上下文就可以理解的文本。如果您需要复习一些基本概念,请参考我们的介绍文章。
在这里,我们主要关注改进库如何解析发现的集群。如果你对 CR 最常见的库的详细解释感兴趣(例如什么是 AllenNLP 或 Huggingface ),我们的动机可以随意查看。
现成可用但不完整
Huggingface 和 AllenNLP 共指消解模型对许多项目来说都是一个很好的补充。然而,我们发现了几个缺点(在前一篇文章中有详细描述),这让我们怀疑我们是否真的想在我们的系统中实现这些库。最大的问题不是无法找到可接受的聚类,而是整个过程的最后一步——解析共指以获得明确的文本。
这让我们想到,也许我们可以为此做些什么。我们决定对最终文本进行几处小改动,从而带来显著的改进。因为 AllenNLP 似乎找到了更多的集群,而这些集群往往更好,所以我们决定采用一种更侧重于这种模式的解决方案。
拟议改进概述
我们已经决定将 AllenNLP 作为我们的主要模型,并利用 Huggingface 作为更多的参考,同时主要将其用作对 AllenNLP 输出的改进。我们的解决方案包括:
- 基于模型已经获得的聚类,改进 AllenNLP 替换共参照的方法,
- 引入几种策略,将两种模型的输出(聚类)合并成一个增强的结果。
为了改进相互引用的替换,有几个有问题的区域可以稍微容易地改进:
- 在一个集群中缺少一个有意义的提及,可能成为它的头
(一个跨度,我们用它替换给定集群中的所有其他提及) - 将第一个区间视为一个簇的头(这对于下一个区间尤其成问题),
- 各种复杂情况,以及由于嵌套提及而导致的无意义输出。
所有上述问题都在下面举例说明:
在聚类中缺少有意义的提及(例如名词短语)的文本。
有下指的文本—代词在名词短语之前。
具有嵌套相关提及的文本。
我们对这些问题提出以下解决办法:
- 如果一个集群不包含任何名词短语,就不要考虑它
- 将聚类中的第一个名词短语(不是第一次提到的)视为其标题
- 仅解析嵌套的共同引用中的内部范围
在下一节中,将详细解释这些方法,包括 AllenNLP 改进和组合模型的策略。此外,无论你想更深入还是跳过细节,下一章的所有代码都可以在我们的 NeuroSYS GitHub 库中找到。
深入改进
由于 Huggingface 基于 spaCy,使用起来毫不费力,并提供了多种附加功能。然而,修改起来要复杂得多,因为 spaCy 提供了许多机制,禁止您访问或更改底层实现。
此外,除了与 Huggingface 相比,AllenNLP 获得了更多数量的有效聚类之外,我们还发现前者更容易修改。
为了修改 AllenNLP 的行为,我们关注于 coref_resolved(text) 方法。它遍历所有聚类,并用第一个找到的提及替换每个聚类中的所有跨度。我们的改进只涉及这个函数和其中的嵌套方法。
下面是一个简短文本的例子,它包含了我们试图解决的上述所有三个问题,我们现在将重点关注这些问题。
冗余集群
为了让我们的解决方案简单明了,我们决定将有意义的提及定义为任何名词短语。
对于每个聚类,我们获取包含名词短语的跨度的索引(我们在下面的改进中也使用它们)。验证一个标记是否是名词的最简单的方法是使用 spaCy!事实证明,AllenNLP 也使用 spaCy 语言模型,但只是对输入文本进行标记化。
这里最让我们感兴趣的嵌套方法是 replace_corefs(spacy_doc,clusters) 。它不仅利用了 spaCy Doc 对象,还包含了实现我们的改进所需的所有逻辑。它看起来像这样:
从空间文档中,我们知道名词由两个词性(POS)标签表示:名词和属性。我们需要检查集群中每个 span 的 POS 标签(实际上是每个 span 中的每个令牌)是否是这两者之一。
下面展示了这段代码如何改进共指替换。现在,我们主要关注第一个集群,因为它显示了当前的问题。
解决下指问题
许多共指消解模型,如 Huggingface,在检测下指时存在严重问题,因为这种情况很少发生。一方面,我们可能对解决这种引起更多问题的异常情况不感兴趣。另一方面,根据文本的不同,如果我们忽略了它们,我们可能会丢失或多或少的信息。
AllenNLP 检测下指,但由于它将聚类中的第一个提及作为其头部,因此会导致进一步的错误。这是因为先行词(例如代词)在后置词(例如名词短语)之前,所以一个无意义的跨度成为一个簇的头。
为了避免这种情况,我们建议将集群中的第一个名词短语(不仅仅是任何提及)作为它的头,用它替换所有前面和后面的部分。这个解决方案是琐碎的,虽然我们可以看到其他更复杂的想法的优点,但我们的解决方案似乎对大多数情况都足够有效。
让我们仔细看看 AllenNLP 的 replace_corefs 方法中的几个关键行。
为了让我们的解决方案起作用,我们需要重新定义reference _ span变量(集群的头),这样它就表示第一个找到的名词短语。为了实现这一点,我们使用了我们的noun _ indexes列表——它的第一个元素是我们想要的。
让我们看看它是如何提高输出的。这次我们关注第二个集群。现在没有信息丢失!然而,由于 AllenNLP 根据找到的聚类的顺序返回结果,所以在我们的版本中也有一个小错误。不过不要担心,我们将在下一节中修复它。
嵌套提及
最后一个改进涉及由多个提及组成的跨度。正如我们在上一篇文章中所展示的,有几种策略可以用来解决这个问题,但没有一种是完美的:
- 仅用外部跨度替换整个嵌套提及→我们会丢失信息
- 替换内部和外部 span →在许多情况下不起作用,根据找到的集群顺序会导致不同的结果
- 仅替换内部 span →适用于大多数文本,但是,一些替换会导致无意义的句子
- 省略嵌套提及;根本不替换跨度→我们没有获得共指消解模型首先应该提供给我们的信息,但是我们 100%确定文本在语法上是正确的
我们认为,第三种策略——仅替换内部跨度*——是信息增益和最终文本中可能的错误数量之间的良好折衷。它还提供了一个额外的功能—现在,无论群集排序如何,输出都将始终相同。*
要实现它,我们只需要检测外部跨度并忽略它们。为此,我们只需检查特定的跨度索引是否包含任何其他提及。
最后,我们获得了一个包含所有改进的文本。我们意识到这个问题本来可以解决得更好,但是我们发现在解决方案的简单性和解决文本的有效性之间的权衡恰到好处。
如上图所示,我们可以获得更好的结果,同时只增强找到的聚类的解析方式。
整体策略
在所有的改进之后,我们现在可以转移到交集策略——关于如何结合 AllenNLP 和 Huggingface 集群的想法。正如我们之前提到的,我们认为 AllenNLP 产生了明显更好的集群,但它并不完美。为了在没有任何微调的情况下获得关于最终聚类的最高可能置信度,我们提出了几种合并两个模型输出的方法:
- 严格-仅保留那些在 Huggingface 和 AllenNLP 模型输出中完全相同的聚类(聚类的交集)
- 部分-仅保留那些在 Huggingface 和 AllenNLP(跨距的交点)中完全相同的跨距
- 模糊-保留 Huggingface 和 AllenNLP 中部分相同(重叠)的所有跨度,但优先考虑较短的跨度
由于 AllenNLP 通常更好,我们将其作为我们的基础,因此在可疑的情况下,我们仅根据其发现构建输出。由于代码不像讨论的改进那样短,我们给提供了一个详细的 Jupyter 笔记本,在这里只看一下策略的输出。
以下示例摘自 GAP 数据集,我们之前已经详细解释过。
为了有效地比较提议的交集策略,更容易将模型的输出视为聚类集,如下图所示。值得注意的是,AllenNLP 和 Huggingface 不仅发现了略微不同的提及,还发现了整个集群。
两个库都找到的集群— AllenNLP 和 Huggingface。
通过每个交集策略获得的聚类 AllenNLP 和 Huggingface 的集合。
我们大多倾向于模糊策略。它提供了比单一模型的输出更高的确定性,同时还提供了最大的信息增益。所有策略都各有利弊,因此最好进行试验,看看哪种策略最适合您的数据集。
让我们看看最终的结果——包含已解决的相互引用的示例,包括我们之前的改进:
尽管最终的文本听起来不太自然,但我们必须记住,共指消解的目的通常是为语言模型消除歧义。因此,他们可以更好地理解输入,并能够产生更合适的嵌入。在这种情况下,我们只缺少一条信息——他死于 1370 年——同时获得了许多正确的替换,并去掉了过长的提及。
摘要
在我们的文章中,我们解决了 NLP 中的一个主要问题——共指消解。我们解释了什么是 CR,介绍了最常见的库以及它们带来的问题,现在向展示了如何改进现有的解决方案。
我们试图用多张图片和各种例子来阐明我们可能有的一切。基本用法和我们的修改都可以在我们的 NeuroSYS GitHub 上找到。
希望现在您已经熟悉了共指解决方案,并且可以轻松地将我们提出的解决方案应用到您的项目中!
如何让人工智能清晰地表达疑问
影响人们生活的模特必须坦诚面对他们的疑虑。这种新方法给可解释的人工智能带来了平衡。
在特征空间,我们已经发表了一个方法来解释深度网络中的模型不确定性。我们的方法可以理解模型认为不符合其预测类别的输入数据中的混杂模式。这允许我们部署诚实和透明的模型,为决策提供平衡和完整的证据,而不仅仅是管理支持决策的证据。
例如,在下图中,我们观察到包含在 CelebA 数据集中的三幅名人图像。在所有情况下,名人都被标记为*“不笑”*,这可以使用广泛可用的用于图像处理任务的深度分类模型来轻松预测,如 VGG16、ResNet50 或 EfficientNet。然而,模特们很难为这些照片中的一些提供自信的分类。在下面的例子中,我们提出的方法强调了导致模型不确定的各种特征(右栏)。我们很容易注意到微笑弧、上唇弯曲和口腔走廊的存在,这些通常与微笑和咧嘴笑有关。
左*:CelebA 数据集中预测类别标签为“没有笑容”的名人脸部图像。* 右 :由我们的方法高亮显示的像素(红色)导致模型对该预测不确定。作者图片。
不确定性和贝叶斯深度学习
我们在贝叶斯神经网络(BNNs)的基础上建立了我们的不确定性归因方法。该框架彻底处理了预测中不确定性的所有来源,包括源于**模型选择和训练数据限制的不确定性,**其表现为每个神经细胞的拟合权重参数的不确定性。点击查看中的 BNNs 概述。总而言之:
- 神经元的每个拟合参数被捕获为一个概率分布,该概率分布表示在给定训练数据的情况下,我们对其最佳值缺乏确定性。
- 由于深度模型有大量参数,因此,我们在所有训练参数上有一个多元分布,称为后验分布。
- 当我们对一个新的数据点进行分类时,每个拟合参数值的合理组合都会给我们一个不同的分数!BNN 提供给我们的不是一个单一的分数,而是一种可能分数的分布,称为后验预测分布。
- bnn 通常返回后验预测分布的平均值作为它们对输出分数的估计。
这些计算通常是近似的,并在引擎盖下运行,例如,当您将脱落层添加到您的神经模型架构中时。重要的是,对于我们希望分类的每一幅图像、一段文本或金融交易,我们可以检索代表分数应该是多少的分布。MNIST 数字的一对示例分布如下所示。分布的平均值告诉我们分类有多确定,而方差告诉我们建模不确定性有多稳定。请注意,平均分数有时可能与由最可能的组模型参数产生的分数非常不同,这是非贝叶斯神经网络通常返回的分数。在第一个例子中,最可能模型与平均模型分数的偏差特别大。
两个 MNIST 数字的潜在类别患病率的后验预测分布,最可能类别的 BNN 概率相似。在顶部图像中,所示的分布(对于类别标签“8”)支持大范围的可能得分,事实上它是双峰的,有利于确定图像必须是“8”,或者确定它不可能是“8”。这意味着图像代表数据中的稀疏区域,在训练集中只有很少或没有样本。相反,底部图像中的分布在 0.5 附近达到峰值,并且相对较窄。这意味着训练集在特征空间的这一点上包含内在类别混合的证据——它已经看到了类似的例子,这些例子或者被标记为(不完整的)7,或者被标记为具有外围墨迹的杂散斑点的 1。作者图片。
解释模型不确定性
为了解释预测中的不确定性,我们的方法建立在积分梯度 (Sundararajan et al. 2017)的框架上,这是神经网络最广泛的解释技术之一。集成梯度通过对模型的输出倾向得分 p c (x) 的梯度进行集成来工作,对于类别 c. 它沿着基准起始点和被解释的点之间的特征空间中的路径执行该集成。对于不确定性的解释,我们不积分模型分数的梯度,而是积分预测熵的梯度
H(x)=-σc PC(x)。log pc(x)
该度量包含模型的总预测不确定性,包括源自后验预测分布的位置和宽度的分量。
我们在公共图像基准上测试了集成熵梯度,包括 MNIST 和 CelebA 人脸数据集。不幸的是,由普通的综合梯度产生的熵解释被证明是糟糕的、令人困惑的,而且是一种敌对的解释。这是可以理解的,积分梯度需要一个基准特征向量作为路径积分的原点,而普通算法使用空白图像作为其基准。这有合理的动机来解释为什么分数高,因为空白图像可能对所有类别具有大致相等的模型分数;然而,围绕黑色图像的不确定性通常非常高。
为了克服这个和其他问题(这些问题的细节在论文中),我们的方法结合了两个最近的想法来产生干净和有信息的熵属性——这两个方法都利用了潜在的空间学习表示,使用了变分自动编码器。
- 综合反事实解释 ( 安托万等人 2021 **)。**对于我们的路径积分的源图像,我们希望图像具有与测试图像相同的预测类别,但是预测熵为零。合成的反事实解释图像提供了这样的源图像,同时具有吸引人的属性,即源图像与测试图像非常相似——也就是说,增量限于那些增加不确定性的概念。
- 分布内路径积分 ( Jha 等人 2020 ) 积分梯度的一个问题是基准源和测试向量之间的路径可能偏离数据流形——通过不可能的图像探索轨迹。我们希望基准点和测试点之间的路径积分在数据流形内,即:两幅图像之间路径上的每个特征向量都代表了数据中可能合理出现的一些情况。
结合起来,这些想法产生了干净和相关的不确定性解释,即使是对困难的机器学习问题,如阅读人类表情。我们通过解释 CelebA 数据集中最不确定的图像来证明这一点,如微笑检测,拱形眉毛检测和眼袋检测。
与预测“拱形眉毛”(上面两行)和“没有拱形眉毛”(下面两行)的现有方法相比,我们的方法的不确定性解释。作者图片。
结论
新兴的可解释人工智能领域通常专注于收集证据,证明支持已经做出的决定,就像律师在法庭上陈述案件一样。然而,大多数人工智能模型的操作更像专家证人,而不是律师说服法官。这就明确了对更加平衡的解释方法的需求,这种方法提供了阐明模糊性、不确定性和怀疑的细致入微的解释。我们的方法所做的是用丰富而简洁的解释来补充传统的分数归因方法,解释为什么数据点的分类是不确定的。随着机器学习被更广泛地用于为对生活有重大影响的法律、金融或医疗决策提供信息,对自动化预测背后的证据权重进行诚实和公开的评估对于维护道德标准至关重要。
如何使用 matplotlib 制作带标签的条形图和 hbar 图
使用来自传奇联盟的数据的初级数据科学项目指南
简介
我第一次做条形图的时候,我不能马上出图 如何给条形图添加标签。尤其是水平条形图对我来说很麻烦。所以,如果你也不能让它工作,也不要担心。在这篇文章中,你将学习这是如何完成的,以及一些设计图表的技巧和诀窍。
N 注: 如果您只需要了解如何添加标签,您可以跳到以下部分:**创建带标签的条形图 。我已经包含了我为你写的所有代码,如果你想边读边打字的话,我可以把它们复制下来,同时还有关于代码如何工作的详细解释。
为了让我教你如何制作带标签的条形图,我需要一些数据。所以我决定从网游《英雄联盟》 中抓取一些游戏数据。这让该指南对游戏的粉丝来说更有趣,(包括我自己)。这篇文章的第一部分是为演示我如何清理数据使其成为我喜欢的格式。我鼓励你阅读这一部分以及,它教你一种方法**将数据从 JSON 格式转换成 Pandas 数据帧。
该员额的结构如下:
- 介绍
- 准备数据(JSON 到 Pandas 数据框架)
- 创建带标签的条形图
- 总结和告别
准备数据(JSON 到 Pandas 数据框架)
我有一个 JSON 文件,里面包含了英雄联盟中所有冠军(可玩角色)的基础统计(特征)。* 你可以在这里访问数据文件。 数据看起来是这样的(见截图)😗
总共有 153 个冠军在他们的字典中包含相同的关键字。
最后,我们将制作一个条形图,显示冠军的开始 hp (生命值),以了解哪些冠军开始时最健康。如果你对其他统计数据感兴趣,你可以非常容易地改变条形图显示的内容,例如,你可能有兴趣看看哪些冠军拥有最多的 ad (攻击伤害)。
接下来,我想将这些数据从目前的 JSON 格式转换成一个 Pandas DataFrame 。
我鼓励你试着编码,因为这会给你最好的理解!边做边学你懂的。
*import pandas as pd
import matplotlib.pyplot as plt
import json*
我从导入我们将使用的必要库开始。Pandas
允许我们制作DataFrames
,这是一个很好的数据科学结构。Matplotlib.pyplot
将被用来制作图表。json
将允许您将 JSON 文件中的数据加载到内存中,供我们使用。
*with open('champ_stats.json') as f:
data = json.load(f)champion = data['Aatrox']
stat_names = list(champion.keys())
columns = ['champ'] + stat_names
df = pd.DataFrame()*
我打开数据文件并将其加载到名为data
的变量中。接下来,我想有一个列表,其中包含所有不同的统计数据的名称,(冠军的统计数据/特征),因为这将是完成的数据帧的列名。统计列表包含在数据中所有的冠军字典中,因此我们只需要深入其中一个字典并从该字典中获取关键字。我们使用'Aatrox'
键,因为这是数据的第一个冠军。这给了我们保存到champion
变量的相应值。这个值是另一个字典,其中所有的键都是我们正在寻找的名字,因此我们使用.keys()
函数来获取它的键,当在list()
函数中调用该函数时,返回值被转换为一个列表。 如果这一步让你困惑,我已经写了另一个指南,解释了 如何访问 python 字典中的数据,你可以在这里找到***。***
数据帧的列名将是第一列的'champ'
,它将包含所有冠军的名字,其他列将以stat_names
中的项目命名。我们把它保存到变量columns
中。最后,我们使用pd.DataFrame()
创建一个空的数据帧,并保存到变量df
中。
***for name, stats in data.items():
values = [stats[x] for x in columns[1:]]
row = pd.DataFrame([[name] + values], columns=columns)
df = df.append(row)
df.head()***
在上一步中,我们有一个空的数据框架,现在是用数据行填充它的时候了,每一行代表一个冠军。我们现在为语句制作一个,该语句遍历数据字典中的所有键值对*,(name
是键,stats
是值)。我们想要一个所有 stat 值的列表,为此我使用 list comprehension 并将结果列表保存到变量values
。列表理解是这样工作的:对于columns[1:]
中的每个名字(x
),我们取这个名字,并把它作为从stats
中访问相应值的键,这个值被添加到列表中。*****
接下来,我们制作一个新的数据帧,保存到变量row
。这个数据帧用两个参数实例化。首先,一个列表中的列表,其中内部列表包含当前冠军的名字和所有值。其次,将形式属性columns=
设置为我们的列表变量,也称为columns
,这将按照我们的需要设置数据帧中的列名。
接下来,我们将row
附加到我们称为df
的主数据帧,这将返回一个新的数据帧,我们用它来覆盖之前的df
。
最后,我们调用df
上的head()
方法来显示 DataFrame 的第一行。如果您已经编写了代码,现在应该会得到以下输出:
数据现在已经很好地格式化为 DataFrame,下一步我们将最终创建条形图并添加标签。
创建带标签的条形图
***df_sorted_by_hp = df.sort_values('hp', ascending=False)
x = df_sorted_by_hp['champ'][:15]
y = df_sorted_by_hp['hp'][:15]***
为了改进图表,我选择了按'hp'
值对数据帧中的行进行排序,而ascending=False
按降序对值进行排序。之后,我们将冠军列保存到名为x
的变量中,类似地,将 hp 值保存到变量y
中。为了确保图表不会太混乱,我选择只包括前 15 名冠军,这是用[:15]
后缀完成的。**
***fig, ax = plt.subplots(figsize=(20,4))
bars = ax.bar(x, y, width=0.5)***
我们使用plt.subplots(figsize=(20,4))
创建一个图形对象和一个轴对象,它们被保存到变量fig
和ax
中。我们将图形尺寸设置为 20 乘 4 英寸,这可能不是绝对的最佳尺寸,但是您可以随意使用这些数字,直到找到最适合您的图表的尺寸。ax.bar(x, y, width=0.5)
使用我们的x
和y
值以及 0.5 的width
值创建条形图,同样,您可以尝试 0 到 1 之间的不同宽度大小。我将返回的对象保存在变量bars
中,我们很快就会用到这个变量。
代码将生成下面的图表,(只是其中的一部分,以便更好地适应媒体):**
…不是很酷
首先,条形的高度差非常难以区分,其次,不可能读出每个条形的精确高度。这就是标签有用的地方!
现在就让添加它们吧!
****注意:将下面的代码片段追加到前面的代码片段中。
***for bar in bars:
height = bar.get_height()
label_x_pos = bar.get_x() + bar.get_width() / 2
ax.text(label_x_pos, height, s=f'{height}', ha='center',
va='bottom')***
我们可以循环通过bars
变量来检查图表中的每一个条形。我们通过从bar.get_height()
函数中获取每个条形的高度,将其保存到一个名为height
的变量中。接下来,我们需要循环中当前条的标签的 x 位置。我们从bar.get_x()
函数中获得这个位置,然后将条形的宽度除以 2,得到条形中心的 x 值。
最后,我们使用ax.text(label_x_pos, height, s=f'{height}', ha='center')
来创建标签/文本。这个函数接受一个 x 位置的值和一个 y 位置的值,然后我们给它一个将要显示的字符串,在这个例子中是条形的高度。最后,ha='center'
进一步帮助标签在酒吧的中心对齐,va='bottom'
将标签放在酒吧的正上方。
这将产生下面的图表,(只是其中的一部分,以便更好地适应媒体):**
是啊!现在我们有了显示各自高度的条形标签!这显然更好,但仍可改进。
我认为由于尺寸的原因,条形下方的名称有点难读。此外,我想为图表的标题以及轴的描述。此外,让 y 轴从 0 开始是没有意义的。最后,我认为*这个图表可以用一些颜色来增加趣味!*******
****注意:将下面的代码片段追加到前面的代码片段中。
***for tick in ax.xaxis.get_major_ticks():
tick.label.set_fontsize(14)for bar in bars[::2]:
bar.set_color('r')plt.title('The 15 champions with the most HP')
plt.xlabel('Champion name')
plt.ylabel('Starting hp')
plt.ylim([550,650])plt.show()***
您可以通过循环从ax.xaxis.get_major_ticks()
返回的刻度来设置每个刻度的字体大小,并用tick.label.set_fontsize(14)
提供字体大小。同样,您可以使用for bar in bars[::2]:
访问每隔一个条,并使用bar.set_color('r')
设置颜色。标题、x 标签和 y 标签可以用它们对应的功能来设置。最后,你可以用plt.ylim([550, 650])
限制 y 轴(x 轴也一样)。我发现从 550 开始到 650 结束对这个图来说是可行的。
plt.show()
将显示完成的条形图,(只是其中的一部分,以便更好地适应介质):**
我认为这是一个很大的进步!
要制作一个水平条形图,我们需要做的改动很少。因此,我将在单个代码片段中显示代码,并以粗体突出显示中的变化。此外,请记住,数据与之前相同,因此我们仍然拥有x
变量中的所有名称和y
变量中的所有 hp 值。**
***fig, ax = plt.subplots(figsize=(**4, 10**))
bars = **ax.barh**(x,y, 0.5)for bar in bars:
**width = bar.get_width()** #Previously we got the height
**label_y_pos = bar.get_y() + bar.get_height()** / 2
**ax.text(width, label_y_pos, s=f'{width}', va='center'**)for tick in ax.**yaxis**.get_major_ticks():
tick.label.set_fontsize(14)for bar in bars[::2]:
bar.set_color('r')plt.title('The 15 champions with the most HP')
plt.**xlabel('Starting hp')**
plt.**ylabel('Champion name')**
plt.**xlim**([550, 650])plt.show()***
与之前一样,我们创建图形和轴对象并设置大小,但是,这一次大小为 (4,10) 效果更好,(高比宽)。为了制作水平条形图,我们使用* ax.bar**h**()
而不是ax.bar()
。接下来,我们得到的不是每个条形的高度,而是宽度。我们需要使用bar.get_y()
而不是 x 位置来获得条形的 y 位置,并且我们添加条形的高度除以 2,(注意,与之前相比,这里的高度具有不同的含义)。***
我们再次用ax.text()
设置条的标签,但是,我们用width
表示 x 位置和要显示的字符串。此外,我们使用参数va='center'
,这有助于将标签放在条的中心。试着移除它,看看有什么不同。
对于刻度,我们只需改变 y 刻度的大小,而不是 x 刻度的大小。最后,您必须切换轴的标签,并限制 x 轴相对于 y 轴。
结果:
瞧,你现在知道如何制作垂直和水平条形图,以及添加标签使它们更具可读性。
摘要
如果您遵循了整个指南,那么您现在已经创建了一个小项目,在这个项目中,您将 JSON 数据转换为 Pandas 数据框架,然后创建带有标签的条形图。
就我个人而言,我更喜欢水平条形图,因为我觉得这样更容易阅读。
记住如果你有兴趣可以进一步探索数据*。尝试找出哪个冠军的血量最少,或者在你的图表上显示更多的冠军。*****
如果你对这篇文章有任何问题、意见或建议,请随时联系我!
感谢你花时间阅读这篇文章,希望你学到了有用的东西!
坚持学习!
—雅各布·托夫加德·拉斯姆森
如何在 Python 中用 Click 制作漂亮的命令行界面
使用 Python 的 Click 制作令人惊叹的命令行界面概述
(图片由 Pixabay 上的 OpenClipArtVectors 提供)
介绍
命令行界面,或 CLI,是程序员经常用来从终端或 REPL 控制他们的软件的一个重要工具。命令行界面的问题是,虽然它们并不特别难制作,但制作起来可能会有点乏味和烦人。此外,它们很难做得很好或完美。命令行界面经常会变得杂乱无章,这对于可能有用户也在与 CLI 交互的应用程序来说是个问题。不好看的东西只会更令人困惑,我们如此习惯于以一种方式消费内容、阅读或解释事物,以至于如果它们不符合那些规格,就会导致我们大脑中的小短路。
幸运的是,对于 Python 程序员来说,有一些非常棒的工具可以用来在 Python 内部创建增强的 CLI。这类工具的一个例子是名为 Click 的 Python 模块。我在另一篇关于 Python 中令人敬畏的装饰者的文章中列出了 Click,如果你也想阅读那篇文章,你可以在这里阅读:
</10-of-my-favorite-python-decorators-9f05c72d9e33>
Click 模块是一个用于 Python 的可组合命令行工具包,由 Pallets 团队开发。如果您想了解有关托盘团队和 Click apparatus 的更多信息,您可以在此处查看 Click Github 页面:
https://github.com/pallets/click
您可能不知道,但很可能您实际上也使用了托盘团队的另一个模块。他们还负责 Flask WSGI 模块,这是 Python 程序员的行业标准。不用说,这个团队对软件的贡献是显著的,我感谢他们对开源软件的承诺。
今天,我想浏览一下 click,看看该模块提供了什么。该模块功能齐全,非常健壮,所以这里需要解释一下。我们还将参与另一篇文章中的一个项目,以展示该包的功能。
开始一个项目
点击模块的应用可能适用于任何类型的用 Python 构建的应用。这是一件很棒的事情,因为在很多情况下,点击确实可以增加趣味。首先,我创建了一个漂亮的小目录来存储我们的 Python 项目。因为这里没有太多内容,而且我已经在 Python 的全局环境中安装了 click 模块,所以我不想为这个项目创建一个虚拟环境。
让我们编写文件的基础:
import click as clkdef main(): if __name__ == "main":
main()
我们在这里所做的就是编写一个主函数,导入 click,然后做 if __name… Python 的事情来命名我们的主函数,因为我们可能会通过 CLI 调用这段代码。现在让我们来看看 click 模块实际包含了什么,这样我们就可以构建一些漂亮的命令行界面了!
为点击创造一些东西
为了利用 click,我们需要做的第一件事是制定一个明确的目标,并确定将通过 CLI 提供的用于控制该应用程序的参数。我想到了一个好主意
战斗模拟器
这将把随机性和力量考虑在内,并对战斗做出裁决。我认为这将是一个有趣的项目,因为可能会有很多这种类型的东西,也许我甚至可以为它开辟一个新的存储库,并在未来的文章中创建一些更酷的东西。假设我们正在创建一个战斗模拟器,我在这里要做的第一件事就是定义
“顶级的等级制度。”
听我说,朱莉娅程序员和所有人。我的意思是我们需要一个
“抽象类型…”
我们需要一个类,它将是其他类的父类。在这种时候,从一个范例到另一个范例真的会让你的大脑很难受。首先,让我们在我的游戏中加入一个视觉元素,一个 REPL 装订的印刷品和清晰的循环。我之所以要先这么做,是想看看这个想法是否行得通,这有助于我意识到自己是否需要一直贯彻下去。
我是说,这整件事毕竟是自发的。
我们的项目
虽然我不会详细介绍我是如何制作这个小游戏的,但您可能会在这篇文章中发现这一点:
在本文中,我们主要评估点击模块的应用。也就是说,为了更好地理解这个项目,我还会展示一些类,做一些基本的解释来揭示游戏是如何工作的。要全面、深入地了解这款游戏以及我对其组件的编程,我当然会建议阅读上面的文章!此外,这里还有一个我今天要使用的 Github 库分支的链接:
https://github.com/emmettgb/characterclash/tree/0.0.3-CLI
总之,这个程序的类结构是这样的:
- play grid([players])
play-grid 将获取一个玩家,并在上面打印一个玩家的网格。它接受玩家的完整列表,并管理视觉方面以及与环境中其他人物的交互。 - 玩家(位置,
我想我应该写几篇文章来介绍这个整洁的小项目的创建,因为我创建了一个完整的东西来把 CLI 放入其中。在未来,我们将训练一个模型来控制我们的玩家角色,但现在我们只是简单地添加一些命令行参数并稍微改变这个项目的代码,以便接受这些新数据并对其做一些事情。鉴于这基本上是一个我们编写的基于角色的 2d 游戏引擎,有一些全局定义的变量,并且肯定有很多我们可能希望能够从命令行改变的变量。我希望能够改变的主要论点是
- 网格宽度
- 网格高度
- 随机字符数
- 在未来,我想考虑让最终用户来设置角色。
事不宜迟,让我们试着用点击模块来完成这些目标吧!
使用点击
首先,我将调用 click @click.option()装饰器。我们将把下面所有的装饰器放在我们的主函数之上。
# CLIS
@clk.command()
@clk.option('--width', default = 30, help = 'Width of the draw grid.')
@click.option('--height', default = 100, help =' Height of draw grid.')
@clk.option('--players', prompt='Number to simulate',
help='The number of randomly generated players to include.')
这可以采用各种关键字参数,但是位置参数当然是最重要的,它只是一个参数名。接下来,提供帮助信息和这种性质的东西。我们还可以提示用户输入一些值。我还需要删除将全局定义这些别名的以下代码:
CHAR_W = 30
CHAR_H = 100
rplayers = 20
我们需要将这些作为变量填充到整个包中,这有点麻烦,但是真的不会那么糟糕。我们的主要功能现在看起来像这样:
click.command()
[@click](http://twitter.com/click).option('--width', default = 30, help = 'Width of the draw grid.')
[@click](http://twitter.com/click).option('--height', default = 100, help =' Height of draw grid.')
[@click](http://twitter.com/click).option('--players', prompt='Number to simulate',
help='The number of randomly generated players to include.')
def main():
players = []
players.append(Player([11, 20], 0))
players.append(Player([5, 6], 1))
players.append(Player([20, 10], 2))
players.append(Player([11, 20], 0))
players.append(Player([11, 20], 0))
players.append(Player([11, 20], 0))
players.append(Player([11, 20], 0))
players.append(Player([11, 20], 0))
players.append(Player([11, 20], 0))
game = PlayGrid(players)
for i in range(1, 50):
sleep(.5)
game.update("".join(["Iteration: ", str(i)]))
带着我们的 CLI
这里需要的是一个全面的 while 循环,它开始游戏并继续循环,直到所有的玩家都死了。我将继续这样做,尽管我们专注于 CLI,因为这将是我们最后一次需要调整这些参数,此外还有一些我希望在未来进行的升级。我们需要将所有命令行参数作为参数添加到主函数中:
def main(players, width, height):
我要做的第一件事是去掉这个大的 players.append()函数,它将被提取到一个迭代循环中,而不是在一个我还没有定义的新函数中,
players = random_players(players)
现在 main()函数看起来有点像这样:
def main(players, width, height):
players = random_players()
game = PlayGrid(players)
for i in range(1, 50):
sleep(.5)
game.update("".join(["Iteration: ", str(i)]))
我们将继续下去,去掉这个 for 循环,只关注初始化。我们还需要改变在 update()函数中处理宽度/高度的方式。下面是这个函数的一个例子:
def update(self, message):
clear()
self.empty_grid()
self.draw_players(self.grid)
self.make_moves()
print(self.draw_grid())
print("\n" + message)
我们可以在这里添加网格的宽度和高度,因为每次绘制网格时都会调用这个函数。然而,我想我会将网格尺寸定义为全局变量。这将意味着即使是玩家也可以知道网格的大小,这可能是保持工作的关键。另一个很好的方法可能是把它放在类中,但是如果是这样的话,那么每当调用播放器的任何方法时,类的初始化版本都需要作为 self 传递。这当然是有意义的,但是考虑到这更多的是关于网格是窗口的维度的讨论,保持这样的东西是全局的可能是一个好主意,这样我们总是知道它的大小。鉴于这些是 CLI,它们将先于我们是全球性的,因此我们可以忽略这些参数。
players = random_players()
game = PlayGrid(players)
for i in range(1, 50):
sleep(.5)
game.update("".join(["Iteration: ", str(i)]))
现在这三件事情中的两件已经处理好了,我们唯一要做的事情就是创建一个基本的 while 循环,它将在玩家> 1 时执行所有操作。这样游戏就不会结束,直到一个玩家消灭了所有其他人,顺便说一下,这还没有实现——所以我们将永远看着这些无意义的战斗。
永远。
round_counter = 0
while len(players) > 1:
round_counter += 1
sleep(.5)
game.update("".join(["Iteration: ", round_counter]))
现在我们只需要编写我们的随机玩家函数:
def random_players(n_p):
players = []
for p in range(0, n_p):
pos = [random.randrange(1, width), random.randrange(1, height)]
player = Player(pos, p)
players.append(player)
return(players)
Player()构造函数需要一个位置和一个 ID。ID 是它在玩家列表中的索引位置。
现在让我们试着运行这个:
players
for p in range(0, n_p):
TypeError: 'str' object cannot be interpreted as an integer
我们可以通过在传递该类型时简单地添加一个整数转换来解决这个问题。
def main(players, width, height):
players = random_players(int(players))
打字强很棒。
我还将 width 和 height 重命名为 w 和 h,这样我们可以独立于 CLI 调整全局定义。这是整个包装的外观:
import click as clk
import random
from time import sleep
from os import system, name
width = 30
height = 100
# CLIS
[@clk](http://twitter.com/clk).command()
[@clk](http://twitter.com/clk).option('--w', default = 30, help = 'Width of the draw grid.')
[@clk](http://twitter.com/clk).option('--h', default = 100, help =' Height of draw grid.')
[@clk](http://twitter.com/clk).option('--players', prompt='Number to simulate',
help='The number of randomly generated players to include.')
def main(players, w, h):
players = random_players(int(players))
game = PlayGrid(players)
round_counter = 0
width = w
height = h
while len(players) > 1:
round_counter += 1
sleep(.5)
game.update("".join(["Iteration: ", round_counter]))
好吧,让我们试试这个!:
[emmac@fedora CharacterClash]$ python3 character_clash.py.......yers
current = list(grid[newpos[1]])
KeyError: 40
[emmac@fedora CharacterClash]$
对于为什么现在会发生这种情况,我的最佳猜测是我们的范围混合了 x 和 y 值。有趣的是,我遇到了一个大问题,因为它们被翻转到绘制网格的函数内部:
def empty_grid(self):
self.grid = dict()
str = ""
for row in range(1, height):
str = ""
for i in range(1, width):
str += "`"
self.grid[row] = str
现在我得到另一个类似的错误:
File "/home/emmac/dev/CharacterClash/character_clash.py", line 55, in draw_players
current[newpos[1]] = player.symbol[0]IndexError: list assignment index out of range
我认为这里的问题可能与我在这里做的索引有关。应该是 newpos[0]而不是 newpos[1]。这一次我们错误地调用了 x,而我们应该调用 y。请允许我用代码中的一点引用来更好地解释一下:
current = list(grid[newpos[1]])
current[newpos[0]] = player.symbol[0]
current[newpos[0] + modifier] = player.symbol[1]
在这个函数中,current 是该列字符串中的一组字符。我们通过用 y 值调用网格字典来选择该列。现在,我们调用当前索引中的 x 值,以便获得位置,并用我们的字符符号替换它,希望得到合适的结果。
最后一件事,我们时常会遇到这个关键错误:
File "/home/emmac/dev/CharacterClash/character_clash.py", line 54, in draw_players
current = list(grid[newpos[1]])
KeyError: 0
这个关键错误可能会让人认为 draw_players 函数没有正常工作,但实际上数据的格式不适合该函数——没有我们需要的网格的 0 索引。我们只需要改变填充这个字典的 for 循环:
def empty_grid(self):
self.grid = dict()
str = ""
for row in range(1, height):
str = ""
for i in range(1, width):
str += "`"
self.grid[row] = str
用 0 替换 1,
for row in range(0, height):
str = ""
for i in range(0, width):
我们还需要在高度上加+ 1,这样所有的网格都会被填充。每当一个位置通过球员移动函数出现时,我们都需要检查并确保它不在界内。然而,我认为一个正确的方法是编写一些冲突,因为我们现在可以测试我们的 CLI,我认为我们应该这样做。每当我们运行新文件时,都会得到以下结果:
[emmac@fedora core]$ python3 character_clash.py
Number to simulate:
我现在会尽我所能得到每个循环的截图。为了帮助我做到这一点,我将把 sleep()计时器设置为 2 秒。接下来是屏幕上播放的动画 GIF:
[emmac@fedora core]$ python3 character_clash.py
Number to simulate: 5
(图片由作者提供)
我们拥有的
到目前为止,所有这些工作的结果都很酷,正如你在上面的动画中看到的。此外,打电话时,我们有调整高度和宽度的选项:
[emmac@fedora core]$ python3 character_clash.py --help
Usage: character_clash.py [OPTIONS]Options:
--w INTEGER Width of the draw grid.
--h INTEGER Height of draw grid.
--players TEXT The number of randomly generated players to include.
--help Show this message and exit.
[emmac@fedora core]$
如果玩家参数没有提供给我们,我们会提示用户。这非常简单,输出非常简洁。显然,还有很多事情要做。翻转并不完全正确,并且在某些区域的某些协调是错误的。此外,目前还没有任何人工智能暴力,但我很快会带来关于这个项目的另一篇文章,我们将完成这一部分,之后会有更多关于这个有趣的小项目的文章。现在,让我们再多谈谈点击。
为什么不是 ArgParse 或其他选项?
Click 的伟大之处在于,它为您提供了一个完整的数据模板打印输出,它会根据您的论点数据发出回声。此外,Click 包括所有不同类型的参数,甚至支持各种不同的调用。当然,我们可以用一些输入来启动 main()函数,但是将它解析为传入的 CLI 要酷得多。
不仅如此,这个项目的实际点击部分非常容易使用。这是另一个在 Python 中散布装饰魔法的模块,这是我非常喜欢的。所以,是的,我相信 Click 更容易一些,并且包含了很多非常好的组件,它允许你写密码,提示参数,提供选择,各种各样的其他选项所没有的东西。也就是说,它也是来自托盘,他们的软件可能值得一试。
结论
这是一个非常酷的项目,我很高兴看到它继续发展。这可能不是世界上最简单的选择,但它非常有趣,我认为我的方法非常棒。在做这个项目的过程中,我一直在想,如果有一个基于 REPL 的小引擎来为你处理所有的显示,那么你就可以编写具有视觉反馈等功能的真正交互式的 REPL,那该有多酷。这似乎是一个非常好的主意,我确信也许以前有人尝试过。
然而,老实说,如果我要写这样的东西,我可能会使用更多的类型,也用 Julia 来写。有时候项目抓住了我,我觉得我应该试着让它们变得很棒。非常感谢你的阅读,它对我来说意味着整个世界!希望这篇文章有助于让您的 CLI 变得生动,并且通过这个基于真实项目的示例,可能对如何在一些软件中实现这个模块有更多的理解。快乐编程,
还有(差不多)新年快乐!
如何让聚类变得可解释
在本文中,我将解释如何使用 SHAP 值来更好地理解聚类
聚类总是一个黑箱
为了更好地理解不同类型的客户,业务案例中经常需要聚类。但这可能已经足够了。从业务的角度来看,仅仅知道我们有多少个集群以及谁属于哪个集群是不够的。我们需要知道到底是什么形成了这些不同的星团。换句话说,我们想要解释聚类的结果,不幸的是,它总是一个黑箱。我发现自己总是在这种情况下,我也很清楚一个人属于哪个群体,但我不确定他在这个群体中的原因以及他与其他群体成员的不同之处。
这似乎并不明显。回到监督学习,SHAP 值提供了一个通用框架,赋予机器学习可解释性,无论是玻璃盒模型还是黑盒模型。我不打算详细解释 SHAP 值,因为它已经做得很好了,我们几乎可以在互联网上的任何地方找到参考资料,例如https://christophm.github.io/interpretable-ml-book/shap.html。但不幸的是,SHAP 值的计算要求我们的数据有标签。事实上,SHAP 值被定义为样本的每个特征如何对输出标注的预测做出贡献。没有标签,SHAP 很难实施。
为了在聚类和 SHAP 值之间架起一座桥梁,我想使用数据聚类的标签,并在此基础上构建 SHAP 值。我将通过研究一个例子来解释我的想法。详细代码请找到[笔记本](http://Hello, sorry for the late reply. I was sick last month. Here is the link for the notebook: https://colab.research.google.com/drive/1BgJuRxRrEcreVwfbfwnFjoFeHonVriKc#scrollTo=eGie9OGPI-QE)的链接。
葡萄酒数据集聚类
葡萄酒数据集
我将在这里使用葡萄酒数据集:https://www . ka ggle . com/Harry Wang/wine-dataset-for-clustering。这些数据是对意大利同一地区种植的葡萄酒进行化学分析的结果。分析确定了研究葡萄酒中发现的 13 种成分的数量。让我们先快速看一下桌子的头。
作者图片:葡萄酒数据集表的头
这里我也展示了所有特征的直方图。
按作者分类的图像:葡萄酒数据集所有特征的直方图
数据准备
在聚类算法之前,我们必须对特征进行归一化。我用了 MinMaxScaler。
import pandas as pd
from sklearn import preprocessingwine_value = wine_df.copy().values
min_max_scaler = preprocessing.MinMaxScaler()
wine_scaled = min_max_scaler.fit_transform(wine_value)
wine_df_scaled = pd.DataFrame(wine_scaled, columns=wine_df.columns)
这是缩放前后酒精和灰分值的两个散点图。
作者图片:归一化前后两个特征的散点图
聚类算法
我们现在准备在这个葡萄酒数据集上实现聚类算法。我将使用 K 均值算法。我们可以很容易地对一系列聚类运行 K-Means,并将失真收集到一个列表中。
from sklearn.cluster import KMeans
distortions = []
K = range(1,10)
for k in K:
kmeanModel = KMeans(n_clusters=k)
kmeanModel.fit(scaled_wine_df)
distortions.append(kmeanModel.inertia_)
绘制不同聚类值的失真,我们很清楚 3 是最合适的聚类数。
作者图片:不同聚类值的扭曲。
用 SHAP 值解释聚类结果
现在创建了 3 个集群。K-means 模型将简单地输出一个范围从 0 到 2 的数字,表示样本属于哪个聚类。仅此而已。由于数据集有 13 个特征,即使是完整的可视化也不是很明显。为了更好地解释聚类结果,下面是我要做的事情:拟合一个分类器模型,其输出正是由聚类提供的标签,并基于该分类器模型计算 SHAP 值。
我将使用随机森林分类器。最棒的是,在拟合 RandomForest 分类器之前不需要进行归一化,因此我们可以直接使用原始数据。由于分类器的目标是更好地理解聚类,并且不存在过度拟合的问题,所以我将使用所有数据集来进行拟合。
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import label_binarize
kmeanModel = KMeans(n_clusters=3)
y=kmeanModel.fit(scaled_wine_df).labels_
y = label_binarize(y, classes=[0,1,2])
clf=RandomForestClassifier()
clf.fit(wine_df,y)
现在,SHAP 终于准备好履行自己的职责了。我将让 SHAP 解释葡萄酒数据集的特性。
import shap
explainer= shap.TreeExplainer(clf)
shap_values = explainer(wine_df).values
SHAP 告诉了我们什么?
以 0 组为例。让我们看看这两个标签的概要图。
该汇总图上的每个点都是样本特征的 Shapley 值。x 轴给出了 SHAP 值,而 y 轴由按重要性排序的要素决定。
作者图片:标签 0 的 SHAP 值
我们可以看到,OD280、黄酮类化合物和色调是对定义该集群具有最积极影响的特征。我们可以想象一种葡萄酒含有相对大量的 OD280、黄酮类化合物和色素。作为一个对酒非常无知的人,这个总结图确实让我对聚类结果有了更好的理解。
在这篇文章的最后,我想说的是,聚类是一种非常好的解读无标签数据的方式。但是如何解释聚类的结果一直是个问题。借助 SHAP 值,我们可以更好地理解聚类。
如何让深度学习模型更好的泛化
脸书人工智能研究团队开发的新技术。
不变风险最小化(IRM)是一种令人兴奋的新学习范式,有助于预测模型在训练数据之外进行归纳。它是由脸书的研究人员开发的,并在 2020 年的一篇论文中概述。该方法可以添加到几乎任何建模框架中,但是它最适合利用大量数据的黑盒模型,即神经网络及其多种风格。
事不宜迟,我们开始吧。
0.技术 TLDR
在高层次上,IRM 是一种学习范式,试图学习因果关系,而不是相关关系。通过开发训练 环境、结构化数据样本,我们可以最大限度地提高准确性,同时还能保证预测器的不变性。既能很好地拟合我们的数据又不随环境变化的预测值被用作最终模型的输出。
图 1:4 倍 CV 的理论性能(上)与不变风险最小化(IRM)(下)。这些值是从论文中的模拟推断出来的。图片由作者提供。
**步骤 1:开发您的环境集。**我们没有重新排列数据并假设它们是 IID,而是利用关于数据选择过程的知识来开发采样环境。例如,对于一个解析图像中文本的模型,我们的训练环境可以按照编写文本的人来分组。
**第二步:最大限度地减少跨环境的损失。**在我们开发了我们的环境之后,我们拟合近似不变的预测器,并优化我们跨环境的准确性。详见第 2.1 节。
**第三步:更好地概括!**风险不变最小化方法比传统的学习范例表现出更高的分布外(OOD)准确性。
1.但是实际上是怎么回事呢?
让我们慢一点,理解风险不变最小化实际上是如何工作的。
1.1 预测模型的目的是什么?
从第一步开始,预测模型的目的是概括,即在看不见的数据上表现良好。我们称看不见的数据为分布外(OOD) 。
为了模拟新数据,引入了多种方法,如交叉验证。尽管这种方法比简单的训练集要好,但我们仍然局限于观察到的数据。那么,你能确定模型会泛化吗?
嗯,通常你不能。
对于定义明确的问题,如果您对数据生成机制有很好的理解,我们可以确信我们的数据样本是总体的代表。然而,对于大多数应用,我们缺乏这种理解。
举一个论文中引用的例子。我们正在寻找解决决定一幅图像显示的是一头牛还是一头骆驼的难题。
为此,我们使用交叉验证来训练二元分类器,并在我们的测试数据上观察到高准确度。太好了!
然而,经过进一步挖掘,我们发现我们的分类器只是使用背景的颜色来确定奶牛对骆驼的标签;当一头牛被放在沙色的背景中时,模型总是认为它是一头骆驼,反之亦然。
现在,我们可以假设在牧场上可以看到牛,在沙漠中可以看到骆驼吗?
大概不会。虽然这是一个微不足道的例子,但我们可以看到这一课如何推广到更复杂和更重要的模型。
1.2 为什么目前的方法不够充分?
在深入研究解决方案之前,让我们进一步了解为什么流行的培训/测试学习范例是不充分的。
经典的训练/测试范例在本文中被称为经验风险最小化(ERM) 。在 ERM 中,我们将数据汇集到训练/测试集中,在所有功能上训练我们的模型,使用我们的测试集进行验证,并返回具有最佳测试(样本外)准确性的拟合模型。一个例子是 50/50 训练测试分割。
现在,为了理解为什么 ERM 不能很好地概括,让我们看看它的三个主要假设,然后一次解决一个。很快,它们是:
- 我们的数据是独立且同分布的(IID)。
- 随着我们收集的数据越来越多,我们的样本大小 n 与重要特征数量之间的比率应该会降低。
- 完美的测试精度只有在有一个具有完美训练精度的可实现(可构建)模型时才会出现。
乍一看,这三个假设似乎都成立。然而,剧透警报他们往往不会。原因如下。
看看我们的第一个假设,我们的数据几乎从来都不是真正的 IID。实际上,必须收集数据,这几乎总是引入数据点之间的关系。例如,沙漠中骆驼的所有图像必须在世界的某些地方拍摄。
现在有许多数据“非常”IID 的情况,但重要的是要批判性地思考你的数据收集是否以及如何引入偏见。
假设#1:如果我们的数据不是 IID,第一个假设是无效的,我们不能随机打乱我们的数据。重要的是要考虑你的数据生成机制是否会引入偏差。
对于我们的第二个假设,如果我们正在建模偶然关系,我们会期望在一定数量的观察之后,重要特征的数量保持相当稳定。换句话说,随着我们收集更多高质量的数据,我们将能够获得真正的因果关系,并最终将它们完美地映射出来,因此更多的数据不会提高我们的准确性。
然而,在机构风险管理中,这种情况很少发生。由于我们无法确定一种关系是否是因果关系,更多的数据往往会导致更多虚假的相关性被拟合。这种现象被称为偏差-方差权衡。
假设#2:当符合 ERM 时,随着样本量的增加,重要特征的数量可能会增加,从而使我们的第二个假设无效。
最后,我们的第三个假设简单地说,我们有能力建立一个“完美”的模型。如果我们缺乏数据或健壮的建模技术,这种假设就会失效。然而,除非我们知道这是错误的,否则我们总是假设它是正确的。
假设#3:我们假设最优模型对于足够大的数据集是可实现的,所以假设#3 成立。
现在本文讨论了一些非 ERM 方法,但由于种种原因,这些方法还存在不足。你明白了。
2.解决方案:不变风险最小化
提出的解决方案,称为不变风险最小化(IRM),克服了上面列出的所有问题。IRM 是一种学习范式,从多种训练环境中估计因果预测因子。而且,因为我们从不同的数据环境中学习,我们更有可能归纳出新的 OOD 数据。
我们如何做到这一点?我们利用因果关系依赖于不变性的概念。
回到我们的例子,假设奶牛和骆驼 95%的时间都出现在它们各自的草地和沙地栖息地,那么如果我们适应背景的颜色,我们将达到 95%的准确率。从表面上看,这是非常合适的。
然而,借用一个来自随机对照试验的核心概念,叫做反事实,如果我们看到一个假设的反例,我们已经证明它是错误的。因此,如果我们在多沙的环境中看到甚至一头牛,我们可以得出结论,多沙的背景不会导致骆驼。
虽然严格的反事实有点苛刻,但我们通过严厉惩罚我们的模型在给定环境中预测失误的情况,将这一概念构建到我们的损失函数中。
例如,考虑一组环境,每个环境对应一个国家。假设在 9/10 的环境中,奶牛生活在牧场,骆驼生活在沙漠,但在第 10 个环境中,这种模式发生了逆转。当我们在第 10 个环境上训练并观察许多反例时,模型了解到背景不能导致标签奶牛或骆驼,因此它降低了该预测器的重要性。
2.1 方法
既然已经用英语理解了 IRM,让我们进入数学的世界,这样我们就能理解如何实现它。
图 2:最小化表达式— 来源。
图 2 显示了我们的优化表达式。如总结所示,我们希望在所有培训环境中最大限度地降低总价值。
进一步分解,“a”项表示我们在给定训练环境下的预测准确性,其中 phi (𝛷)表示数据转换,如 log 或向更高维度的核转换。 R 表示给定环境下我们模型的风险函数 e 。请注意,风险函数只是损失函数的平均值。一个经典的例子是均方误差(MSE)。
“B”项只是一个正数,用来衡量我们的不变性项。还记得我们说过严格的反事实可能过于苛刻吗?这是我们可以衡量我们想要多苛刻的地方。如果 lambda ( λ )为 0,我们不关心不变性,单纯优化精度。如果 λ 很大,我们很在意不变性,相应地进行惩罚。
最后,“C”和“D”项表示我们的模型在训练环境中的不变性。我们不需要太深究项,简单来说,我们的“C”项是我们的线性分类器 w 的一个梯度向量,默认值为 1。“d”是线性分类器的风险乘以我们的数据转换(𝛷).整项是梯度向量距离的平方。
论文对这些术语进行了大量的详细描述,所以如果你有兴趣的话,可以看看第三部分。
综上所述,“A”是我们的模型精度,“B”是衡量我们有多在乎不变性的正数,“C”/“D”是我们模型的不变性。如果我们最小化这个表达式,我们应该找到一个模型,只适合在我们的训练环境中发现的因果效应。
2.2 IRM 的下一步措施
不幸的是,这里概述的 IRM 范式只适用于线性情况。将我们的数据转换到高维空间可以产生有效的线性模型,然而,一些关系从根本上来说是非线性的。作者把非线性的情况留给未来的工作。
如果你想跟踪研究,可以看看作者的作品:马丁·阿约夫斯基、莱昂·布图、伊桑·古尔拉贾尼、戴维·洛佩斯-帕兹。
这就是我们的方法。不算太坏,对吧?
3.实施说明
- 这里有一个 PyTorch 包。
- IRM 最适合于未知的因果关系。如果有已知的关系,您应该在模型的结构中说明它们。一个著名的例子是卷积神经网络(CNN)的卷积。
- IRM 在无监督模型和强化学习方面有很大潜力。模型公平也是一个有趣的应用。
- 优化相当复杂,因为有两个最小化项。本文概述了一种使优化凸的变换,但仅在线性情况下。
- IRM 对轻度模型错误设定是稳健的,因为它关于训练环境的协方差是可微的。因此,虽然“完美”模型是理想的,但最小化表达式对小的人为错误是有弹性的。
感谢阅读!我将再写 47 篇文章,将“学术”研究引入 DS 行业。查看我对 IRM 方法的链接/想法的评论。
如何在 Python 中少犯“错误”
当可以使用枚举时,不要使用 Python 字符串
每种编程语言都必须使用字符串类型。当我们在程序中输入字符串时,错别字通常是不可避免的。如何才能消除编程时可能犯的这种错误?
枚举常用于大多数流行的编程语言中。当然,Python 也提供了灵活的枚举类。使用枚举是一种非常好的编程方式。它帮助我们将一组符号名与唯一的常量绑定在一起。因此,我们可以很容易地使用它来避免一些由错别字引起的运行时错误。
典型的定义
现在让我们考虑一个极其简单的用例。我们有一本如下的字典。
user = {
'name': 'Chris',
'age': 33,
'programming_language': 'Python'
}
然后,我们想测试用户是否喜欢 Python,并根据结果打印一些输出。
if user['programming_language'] == 'Python':
print(f"{user['name']} can use Python!")
else:
print(f"Python is not {user['name']}'s favourite programming language.")
好的。没问题。我觉得很简单,不需要解释代码:)现在,假设我们打了一个如下的错别字。这可能会导致难以定位的运行时错误。
这个 bug 会产生一个结果——没人喜欢 Python。我们知道那不是真的,当然,那是个错误:)
使用枚举的基本解决方案
每个人都会犯错。如何使用枚举来避免这种错误?让我们从定义一个枚举开始。只需导入Enum
类并定义枚举的成员。
from enum import Enumclass ProgrammingLanguage(Enum):
PYTHON = 1
JAVA = 2
C = 3
JAVASCRIPT = 4
PHP = 5
然后,我们有一个特定成员的枚举。我们可以把它放在字典里如下。
user = {
'name': 'Chris',
'age': 33,
'programming_language': ProgrammingLanguage.PYTHON
}
如果我们想要平等的测试,它可以是这样的。
if user['programming_language'] == ProgrammingLanguage.PYTHON:
print(f"{user['name']} can use Python!")
else:
print(f"Python is not {user['name']}'s favourite programming language.")
那么,使用枚举比较和普通字符串有什么区别呢?为什么它可以防止错别字运行时的错误?首先,枚举中定义的成员被视为类属性。换句话说,大多数 IDE 工具将提供自动完成功能。因此,我们出现打印错误的可能性就更小了。
最重要的是,如果我们犯了一个错误,Python 解释器会抛出一个错误。
因此,如果有错别字,代码将无法编译。运行时之前暴露的问题通常不是实际问题,而是编程的一部分:)
访问枚举的成员
图片由 Pixabay 上的 JESHOOTS-com 拍摄
好吧,如果你已经决定在必要的时候使用枚举,我猜你可能想了解更多。很高兴在本文中扩展这个主题。让我们首先讨论如何访问一个枚举的成员。
如果我们只是简单的打印成员,那么会打印原始的Class.Attribute
,并不能提供太多的信息。
然而,如果我们使用repr()
函数,它也会打印成员的值。
如果我们想得到成员的整数值,只需访问它的value
属性。对于成员的键,我们可以使用name
属性来访问它。
需要访问成员键的用例示例
访问成员键的用法提示是,我们可以从枚举成员中获取一个字符串以供进一步使用。例如,如果用户字典是从 JSON 文档中转储出来的呢?换句话说,“编程语言”的值必须是一个字符串。
user = {
'name': 'Chris',
'age': 33,
'programming_language': 'Python'
}
因此,我们可以将字符串“Python”转换为大写,然后将其与枚举成员的键进行比较。
if user['programming_language'].upper() == ProgrammingLanguage.PYTHON.name:
你可能会问为什么成员键必须全部使用大写字母。简而言之,你不必这么做。但是,当我们定义一个“常量”时,建议使用全大写。枚举中的所有成员都被视为常数。遵循 PEP 标准是一种好的方式。
与数据结构集成
你可能会意识到枚举是非常结构化和受限的。因此,它肯定可以集成不同类型的数据结构。这里我将展示 Python 列表和字典的两个例子。
将枚举转换为列表
我们可以使用 for 循环来迭代枚举。
for lang in ProgrammingLanguage:
print(lang)
因此,我们可以很容易地使用列表理解将其转换为列表。
[lang.name for lang in ProgrammingLanguage]
事实上,还有另一种方法。这并不简单,但有助于理解枚举的结构。也就是说,我们可以使用一个神奇的属性来获取成员的所有细节。
ProgrammingLanguage.__members__
类型mappingproxy
本质上是一个只读字典。因此,我们可以简单地通过调用它的keys()
函数来获得键的列表。
list(ProgrammingLanguage.__members__.keys())
可用作字典关键字
枚举成员是可散列的,这意味着我们可以将它们用作有效的字典键。
popular_use_cases = {}
popular_use_cases[ProgrammingLanguage.PYTHON] = 'Data Science'
popular_use_cases[ProgrammingLanguage.JAVA] = 'Software Development'
popular_use_cases[ProgrammingLanguage.JAVASCRIPT] = 'Web Development'
这将是非常有用的,因为它也可以防止我们对字典键的错别字!当我们想从字典中的键获取值时,我们也将使用枚举成员。
popular_use_cases[ProgrammingLanguage.PYTHON]
用字符串值枚举
到目前为止,上面所有的例子都使用整数作为枚举的值。事实上,我们可以使用字符串作为值。它在使用过程中提供了更多的灵活性。要获得基于字符串的枚举,只需用字符串定义成员。
class ProgrammingLanguage(Enum):
PYTHON = 'Python'
JAVA = 'Java'
C = 'C'
JAVASCRIPT = 'JavaScript'
PHP = 'PHP'
因此,我们总是可以使用value
属性来访问成员的字符串值。
print(ProgrammingLanguage.PYTHON.value)
请注意,我们仍然不能使用枚举成员来比较字符串,因为类型是不同的。结果永远是假的。
相反,我们需要使用成员的值。
定义枚举的最简单方法
最后,定义枚举的最“Pythonic 化”的方法可能要容易得多。我们甚至不需要写一个枚举类。Enum
类有一个构造函数,它可以接受两个参数并生成一个枚举。
ProgrammingLanguage = Enum('ProgrammingLanguage', 'PYTHON, JAVA, C, JAVASCRIPT, PHP')
在上面的代码行中,第一个参数定义了枚举类的名称。然后,第二个参数是一个字符串,包含所有由逗号、空格或两者分隔的成员键。
在这里,我们没有为每个成员显式分配一个整数,该成员将被自动分配一个从 1 开始自动递增的整数。
之后,我们得到了一个枚举,这与显式地编写一个类是一样的。
摘要
在本文中,我介绍了 Python 中的 enumeration 类。我们可以用它将符号名绑定到唯一的常量值。通过这样做,我们可以利用大多数 ide 中都有的自动完成特性,并防止由输入错误引起的运行时错误。
Python 枚举还提供了其他一些有趣的特性,比如用作字典键。除此之外,我们可以使用“Enum”类构造函数轻松定义枚举,而无需显式编写类,多么“Pythonic 化”啊!
https://medium.com/@qiuyujx/membership
如果你觉得我的文章有帮助,请考虑加入 Medium 会员来支持我和成千上万的其他作者!(点击上面的链接)
如何制作虚构的数据
当您需要数据用于测试、培训、演示或其他目的时。自己做!
大约每周我都会收到(或在网上看到)一位数据科学家同事或有抱负的数据科学家提出的一个问题。我在哪里可以得到一个数据集来玩?或者,我正在寻找一个有趣的数据集来学习,有什么建议吗?
有大量有趣的数据被公布出来。但是为什么不自己做数据呢?当您需要数据进行测试或演示时,制作自己的虚构数据也是一项有用的技能。本文将向您展示如何生成虚构的数据(这是许多方法中的一种)。
在本文的底部是额外的资源,包括一个支持 GitHub 库的链接,还有一个 YouTube 视频也说明了这个主题。
设置
在图 1 中,我展示了两种鸟类的信息。有“西方”和“东方”之分。
图 1:提供了与鸟类品种相关的概述信息。有“西方”品种,也有“东方”品种。西方的鸟更轻更小。它们有黑色或黄色的羽毛。东方品种有时有白色特征。图片鸣谢:亚当·罗斯·尼尔森(作者)。
西方的鸟品种比东方的品种小。东方品种有时有白色羽毛。图 1 中的摘要信息是虚构的。下面的代码生成的虚构数据近似于图 1 所示的汇总统计数据。
代码
首先,有标准进口。
**import** **pandas** **as** **pd**
**import** **numpy** **as** **np
from** **random** **import** random
**import** **seaborn** **as** **sns**
西方品种数据
首先,我们将为西方品种制作数据。我们计划对每个品种进行 1000 次观察。
该代码将假设鸟类分散在它们的地理范围内。此代码还假设范围是矩形的。这是虚构的,我们可以随心所欲地决定。为了保持代码简单,这些是我们正在工作的假设。
*# Western variety Latitude & Longitude.*
west_lat = np.random.uniform(81, 91, 1000)
west_long = np.random.uniform(35, 42, 1000)
下一个代码块将为鸟的重量生成虚构的数据。我们将从均匀分布开始,然后通过添加以零为中心的正态分布来修改该分布。这种方法实现了与上面图 1 中指定的虚构场景相匹配的分布。
*# Specify weight.*
west_weight = np.random.uniform(9, 19, 1000)
west_weight = np.random.normal(0, 1.25, 1000) + west_weight
既然我们有了鸟的重量,我们就可以生成翼展数据。我们预计翼展与重量有关。随着重量的增加,翼展也随之增加。为了使数据更有趣,我们将随着重量增加翼展,但速度是递减的。下面的方程将近似重量和翼展之间的关系。
wing_span = 5.9 + 1.8 * weight + -.4 * weight * weight
下面的代码产生与上面给出的方程一致的翼展数据。通过使用以 5.9 为中心的随机正态分布,截距的标准偏差为 1,代码近似于自然的随机变化。
*# Specify wing-span.*
west_wing = np.random.normal(5.979, 1, 1000) + 1.871 * \
west_weight + -.0396 * west_weight**2
生成的最后一个变量将是羽毛颜色。西方品种的新娘不是黑色就是黄色。第一步是生成一个由随机的 0 和 1 组成的熊猫系列。第二步是将 0 映射到黑色,将 1 映射到黄色。第三步是将数据指定为分类数据。
*# Specify the feather colors.*
west_color = pd.Series(np.random.randint(0, 2, 1000))
west_color = west_color.map({0:'Black', 1:'Yellow'})
west_color = west_color.astype('category')
通过链接适用的方法,可以在一行代码中制作羽毛数据。然而,为了限制行宽并使代码更易于阅读,我用三行代码演示了代码。
复活节品种数据
为东方品种生成数据的代码与为西方品种生成数据的代码几乎相同。随机数生成器、翼展与重量的关系以及羽毛颜色贴图都有所不同。我在下面的 YouTube 视频中详细解释了这些差异。
*# Eastern variety Latitude & Longitude.*
east_lat = np.random.uniform(77, 89, 1000)
east_long = np.random.uniform(34, 39, 1000)*# Specify weight.*
east_weight = np.random.uniform(10.5, 24.5, 1000)
east_weight = np.random.normal(0, .66, 1000) + east_weight*# Specify wing-span.*
east_wing = np.random.normal(24.16, .75, 1000) + -.137 * \
east_weight + .0119 * east_weight**2*# Specify the feather colors.*
east_color = pd.Series(np.random.randint(0, 3, 1000))
east_color = east_color.map({0:'Black', 1:'Yellow', 2:'White'})
east_color = east_color.astype('category')
最后的结果
图二。虚构的鸟类数据。图片鸣谢:亚当·罗斯·尼尔森(作者)。
最终结果是一个数据帧,如图 2 所示。共有六列,包括lat
(纬度)long
(经度)weight
(磅重)wing
(翼展英寸)color
(鸟羽颜色)variety
(西方或东方)。
下图 3 显示的是一个配对图,说明了这些数据之间的关系。
图 3。图片鸣谢:亚当·罗斯·尼尔森(作者)。
https://adamrossnelson.medium.com/membership
结论
下次您需要数据集进行测试、培训或演示时,可以考虑生成自己的数据。使用本文中的技术来生成您自己的。当您开始生成虚构的数据时,我建议您提前绘制一份数据概要,如图 1 所示。
如果你想了解更多这方面的信息,我准备了一个 GitHub 库,我还准备了一个 YouTube 视频(如下所示)。一个伴随视频使用这个虚构的数据来显示 K 最近邻分类。
视频鸣谢:亚当·罗斯·纳尔逊(作者)。
这是生成虚构数据的唯一方法吗?没有。还有其他选择吗?当然,是的。本文提供了一个起点。如果你使用这些技术来生成虚构的数据,请在评论中发布一个链接。
如果您有其他生成虚构数据的方法,请在评论中发布代码的链接。你可能还会发现另外三种方法让虚构的数据成为有用的读物。
感谢阅读
感谢阅读。把你的想法和主意发给我。你可以写信只是为了说声嗨。如果你真的需要告诉我是怎么错的,我期待着尽快和你聊天。Twitter:@ adamrossnelsonLinkedIn:Adam Ross Nelson 在 Twitter 和脸书: Adam Ross Nelson 在脸书。
如何用 Plotly 制作多指标索引图
包含更多见解的索引图表
在处理时间序列数据时,原始数据通常不能提供多少信息。如果你有多个大小不同的变量,较小值的变化很难看到。这个问题有一个简单的解决方案:使用索引数据的索引图表。
虽然常规索引图表工作良好,但通过使用简单的索引扩展,您可以显著改善您的视觉呈现,并帮助您的图表传达数据中的因果关系。这个扩展就是我所说的“多索引”——使用多个索引而不是一个。这让你可以从不同的角度分析数据。当您怀疑在特定时间点发生的事件对相关变量的增长有重大影响时,这种方法尤其有用。
对股票市场价格有重大影响的事件(图片由作者提供)
举个例子,如果你要比较一些股票从 1980 年到今天的股价,你会发现 1987 年的黑色星期一、2000 年的网络泡沫和 2008 年的股市崩盘都是对市场产生重大影响的事件,看起来很有趣。因此,你可以将它们添加到指数列表中,参考每个日期的数据,并比较价格相对于每个参考点的发展情况。以这种方式看价格会让你对每个事件的规模有一个更好的直觉,而不仅仅是一个单一的指数日期。
在本文中,我将向您介绍如何使用 Python Plotly 数据可视化库在时间序列图中实现多重索引。我将用完整的代码介绍三种方法,并详细解释它们,包括每种方法的优缺点以及何时使用哪种方法。为了跟上这篇文章,您应该熟悉 Python 和 Pandas,最好对 Plotly 有所了解。
如果您只关心我将要谈到的三个图表中的一个,请随意跳过其他的——这篇文章是以模块化的方式编写的,因此您不会错过任何东西。但是,请务必通读第 1 部分(设置和准备),因为它包含将在每个图表中使用的重要概念和功能定义。
你可以在这里找到这篇文章的全部内容。 ]
如果你喜欢这篇文章,请 关注我上媒 。
文章索引
作者图片
3.动态指数图
作者图片
作者图片
1.设置和准备
1.1 获取和清理数据
如前所述,数据准备工作将由熊猫完成。大多数绘图将由 Plotly graph_objects
模块处理。另外,对于一个绘图,我们将使用ipywidgets
库,它提供了用于与 Jupyter Notebook 和 JupyterLab 中的绘图进行交互的小部件。对于时间序列数据,我们将使用 Yahoo!金融 API。有一个很棒的叫做[yfinance](https://pypi.org/project/yfinance/)
的 Python 库,我们将用它来与 API 接口。
我们的目标是建立指数图表,使用户能够第一眼就比较选定股票相对于选定日期的发展。所以,我们挑几只股票来对比一下。我们将重点介绍脸书(Berkshire Hathaway)、亚马逊(Amazon)、杜邦(DowDupont)以及伯克希尔哈撒韦公司(Berkshire Hathaway)的 A 类股(BRK-A ),这是一只股票有史以来价格最高的股票。我有目的地从一个很宽的价格区间选择股票,以证明指数化的有效性。
作者图片
对yf.download()
的调用以给定的时间间隔下载给定时间段的数据,在我们的例子中是从今天开始,追溯到 3 年前,每周一次。结果存储在一个多索引的 Pandas 数据框架中。
设置group_by='column'
指定数据将如何分组,是按价格类型(列)还是按报价机(报价机)。这转化为价格类型或报价器是否会成为我们数据框架的顶级索引。
该表包含五种不同的价格。简单来说,我们只看调整后的收盘价,其他的都不看。我们也将删除空值。
df = df.filter(regex="Adj Close")
df = df.dropna()
df.head(5)
作者图片
1.2 索引:为什么和如何
现在我们的数据已经准备好了,让我们先看一下这些值。
df.plot()
作者图片
绘制原始价值对比较股票帮助不大。由于 BRKa 股的价格高得多,其他股票的变化几乎看不见。也许对数标度数据会缓解这个问题?
df.plot(logy=True)
作者图片
即使在对数尺度上,数值差异也太大,我们无法对股票进行有意义的比较。很明显,我们需要索引。
指数化很简单:选择一个日期 D 作为指数,用每个日期的价格除以日期 D 的价值。现在,所有结果值都表示为它们与 D 处的值 V 的比值,并且可以很容易地相互比较。在我们的例子中,我们需要区分不同的股票,所以对于每只股票,我们将所有的值除以相应股票在日期 D 的值。我们可以很容易地在 Pandas 中做到这一点,因为我们有一个多索引的数据框架。对特定指数调用iloc
会返回一个 Pandas 系列,其中包含该特定指数的所有股票的值。当我们用序列划分数据帧时,Pandas 用序列中相应的元素划分每一列。
我们还会添加一些化妆品。将新值乘以 100 将得到百分比值,使用起来更舒服。第二个可选的修饰是从结果中减去 100。这有效地将 100%设置为 0%,并将比率更改为相对差值。例如,下降到原始值的 30%现在将被解释为original value — 70 %
。我个人认为这种解释更容易理解。
注意,我将索引日期称为参考日期,索引值称为参考值。这样做是为了避免与 dataframe 索引混淆。
# use the first date as index
reference_value = df.iloc[0]# dividing by the series divides each column by the
# corresponding element in the series
tmp_df = df.div(reference_value) * 100 - 100tmp_df.plot()prepared_df = df.copy()
作者图片
这个看起来好多了。股票价格趋势是立即可见和可比的。
我们将更多地使用索引,所以让我们为这项技术写一个通用函数create_indexed_columns()
。该函数将采用以下参数:
date
-索引的参考日期df
-要处理的数据框架top_level_name
-(可选)规范化数据帧的顶层索引的名称
并返回具有规格化数据和新的顶层索引的新的数据帧。
一个重要的考虑是如何处理不在数据帧中的输入日期。幸运的是,dataframe indices 有一个非常有用的方法get_loc()
,可以找到给定索引的整数索引。当我们将参数method=nearest
传递给该方法时,Pandas 将搜索与所提供的输入最接近的索引。因为我们的索引是类型datetime
,所以将匹配最接近所提供日期的日期。
在我们进入第一个 Plotly 指数图表之前,让我们回顾一下**“多指数”**图表的概念。正如我们所见,将数据索引到一个时间点非常有效。然而,查看多个参考点通常是有用的。这些可能是时间序列中对我们的数据有重大影响的特定事件。从多个点查看数据使用户能够容易地看到相关变量相对于不同的参考点是如何增长的。实现这一点的最佳方式是将该功能直接嵌入图表中。
1.3 非常短的 Plotly Crashcourse
这个快速介绍只是简单地触及了 Plotly 的基本概念。如果你以前和 Plotly 一起工作过,跳过它。
在一个图中有多个索引意味着它们需要动态切换,因此我们的图必须是交互式的。Plotly 是开箱即用的交互式,扩展交互性相对简单,这就是 Plotly 是首选库的原因。
在 Plotly 中,每个图形都是一个类似 JSON 的数据结构,有三个主要属性(称为属性):数据、布局和框架。前两个是不言自明的,而帧属性包含动画中使用的可选帧。您可以先使用 JSON 或 Python 字典编写整个人物结构,然后实例化人物对象,或者先创建人物对象,然后逐步更新人物属性。
作为热身,让我们重新创建默认的 matplotlib 图表,该图表是通过调用 Plotly 中的df.plot()
生成的。
在上面的例子中,我们实例化了 Figure 对象,然后一个接一个地向该对象添加单独的轨迹。我们可以用更新属性的字典调用update_layout()
来改变图形的布局。
对 Plotly 图形的所有更改都是这样完成的,将属性字典传递给图形。Plotly 支持“神奇的下划线符号”,这意味着我们不需要逐步深入对象来访问内部属性,而是能够直接访问内部属性。例如,我们可以简单地编写fig.update_layout(yaxis_type='log')
,而不是访问yaxis
属性的type
属性——在许多情况下,这是一种可读性更好的方法。
既然我们都准备好了,我们可以开始我们的第一个图表了。
2.具有固定参考日期的索引图表
在这张图表中,我们将使用预先选定的股市衰退前后的日期,以及对我们正在分析的公司有影响的日期。下面列出的日期可能不完全准确,但它们大致在所列事件的时间范围内。
ref_dict = {
"Cambridge Analytica scandal": "2018-03-28",
"DowDuPont separation": "2019-04-15",
"Corona stock market drop": "2020-02-14"
}
我们将根据每个日期对数据进行索引,并在图表中包含所有不同的索引数据。不同索引数据之间的切换将通过使用一个下拉菜单来完成,每个参考日期都有一个项目。Plotly 将此称为“按钮”。点击一个按钮将只显示由各自的参考日期索引的数据。为了实现这一点,我们需要将我们想要显示的轨迹的可见性切换到 on,并且对于剩余的轨迹将其关闭。
首先,让我们计算每个参考日期的值,并用结果数据填充我们的数据框架。我们将为每个参考日期创建一个新的数据框架,然后将它们合并成一个数据框架。我们还将存储最接近给定参考日期的日期,以防我们没有该参考日期的值。
现在我们的表已经转换,我们可以构建我们的 Plotly 对象。我们将创建一个新的图形,并为每个新计算的列添加一个跟踪。我们将为每个跟踪提供一个额外的meta
属性。meta
属性可以存储任意的 JSON 数据,有很多应用。我们将使用它来存储一个跟踪所属的参考日期。这将允许我们识别将同时切换到可见/不可见的一组轨迹。
那可是好多痕迹啊!我们只想显示属于一个参考日期的那些。就像以前一样,我们不会为我们的精确数据硬编码按钮。最好将我们的程序一般化,这样我们就能够处理一组不同的参考日期和代号。
要显示或隐藏痕迹,我们需要设置图形的visibility
属性。因为这是图形的一个属性,而不是单个轨迹,所以默认情况下,对此属性的更改适用于所有轨迹。在内部,可见性表示为一个布尔值数组,其中特定索引处的值表示具有相同索引的跟踪的可见性。轨迹索引是轨迹在该阵列中的位置,并且因为轨迹是一个接一个地添加的,所以轨迹索引表示轨迹被添加到图中的顺序。
visibility 数组允许我们通过传递特定跟踪的索引或传递一个布尔值数组来定位单个跟踪,其中每个索引处的值指示是否将显示相应的跟踪。例如,对于 4 条轨迹,通过数组[True, False, True, False]
将只显示第一条和第三条轨迹。
可以想象,依靠添加轨迹的顺序来创建永久按钮是一种脆弱的方法。如果您以不同的顺序添加跟踪或添加更多的跟踪,则必须手动检查和更新对可见性数组的每次提及。这就是为什么我们添加了meta
属性——这样,我们就不需要依赖于轨迹的顺序来确定打开/关闭哪些轨迹。
一个简单的函数create_visibility_array
将通过比较每个跟踪的元值和传入的值来创建可见性数组。
最后,我们将编写一个生成按钮的函数。
让我们澄清一下按钮描述。method
指定按钮点击时调用的 Plotly 方法。restyle
更新数据和绘图布局。args
参数接受一个图表属性列表,该列表将通过单击按钮来更新,在我们的例子中是 visibility 属性。
最后,我们将按钮添加到图形中,并检查结果。
太好了,按钮正常工作。唯一的问题是很难看到实际的参考日期。我们可以通过在每个参考日期的 x 坐标上添加一条垂直线来解决这个问题。自然,我们需要能够用现有的按钮来切换这些参考线的可见性。
在 Plotly 中有两种方法可以做到这一点:标准方法是在图形中添加一个shape
对象。在我们的例子中,我们只需通过调用figure.add_shape()
并指定 x 和 y 坐标来添加一个线条形状。虽然这种方法看起来很简单,但是对于我们的情况来说有一些特殊的困难。因为形状不是轨迹,我们不能用现有的方法改变它们的可见性。相反,必须设置单个 shape 对象的 visibility 属性,这样做有些晦涩难懂(这个堆栈溢出问题说明了过程)。此外,似乎没有类似于 trace visibility 数组的简洁方法来一次改变多个形状的可见性。
添加参考线的第二种方法是添加将被绘制为垂直线的轨迹。为此,必须选择两个 y 坐标来设置线的边界。然而,我们需要确保该线在高度方向覆盖整个绘图,除此之外,参考线不得影响绘图的比例——这意味着我们不能简单地选择数据帧中最小值和最大值之下或之上的某个值作为边界,因为这会扭曲其余值的比例。解决方案是在绘图中添加一个不可见的辅助 y 轴,该轴独立于数据框中的值,并相对于自身缩放参考线。
虽然第二个 y 轴在一定程度上增加了图形的复杂性,但使用走线作为参考线有一个显著的好处:我们可以像处理所有其他走线一样切换参考线的可见性。因此,我们只需要添加参考线跟踪,并将其元参数设置为它所引用的参考日期。
如您所见,第二种方法更适合我们的情况,这就是我们为什么要使用它的原因。在添加参考线之前,让我们先将辅助 y 轴添加到绘图中。当我们这样做的时候,我们也可以给情节添加一些简单的样式。
属性指定了 hoverlabels 将如何在我们的绘图中显示。将此值设置为“x”将显示鼠标悬停在 x 值处的每个轨迹的值。我们将辅助 y 轴设置为固定范围。这样,该线将始终贯穿整个地块的高度。将overlaying
属性设置为‘y’非常重要,因为这将迫使相对于第二 y 轴定义的轨迹表现得好像它们是在第一 y 轴上定义的一样——具体来说,该属性和fixedrange
的组合正是允许我们看到参考线,同时使它们的比例独立于 y 轴。
现在图形已经准备好了,我们可以编写一个create_reference_line()
函数来为我们生成参考线。需要注意的一点是,当我们为 y 坐标对称地指定两个值时,我们也需要提供一个包含两个 x 值的列表。
对我们之前的散点追踪定义的唯一补充是:
mode="lines"
-指定仅将轨迹绘制为直线,而不标记其间的点yaxis="y2"
-指定将辅助 y 轴用作 y 轴line
-决定线条的样式
最后,最后一步包括将参考线轨迹添加到绘图中,并重新生成可见性按钮。搞定了。
3.动态索引图表
我们要看的第二个图表是一个动态指数图表。该图表允许用户在参考日期之间快速移动,创建一个视觉上吸引人的图表。
因为我们只使用 Plotly 图形库,所以这里最好的方法是为每个参考日期创建一个框架,然后使用 Plotly 滑块在它们之间移动。通过使用滑块,我们还可以为参考日期之间的过渡添加动画效果,这将使过渡更加平滑。
首先,我们需要将数据索引到每个日期,并为其创建跟踪。结果数据将被存储在一个框架中,这个框架只是一个带有识别名称的figure['data']
字典。我们将使用create_indexed_columns
和create_reference_line
函数创建轨迹,就像前面的例子一样。框架将由框架中的数据被索引到的日期来标识。函数create_traces_for_date
将捆绑这个功能。
接下来,我们将创建一个滑块字典来定义我们的滑块。我们使用的一些重要属性是:
yanchor
和xanchor
-指定滑块的位置pad
-滑块的填充currentvalue
-描述如何显示滑块的当前值,包括位置、字体大小和前缀。len
-描述滑块相对于图表的长度。1 表示与长度完全匹配,这就是我们想要的。steps
-是滑块将循环通过的步骤列表。我们会先将它留空,稍后再添加。
接下来要做的是创建步骤,我们将添加到我们的滑块。一个步骤由属性label
、method
和args
描述。method
指定当滑块值改变时将调用哪个 Plotly 函数。通过使用animate
,我们将获得帧间的平滑过渡。args
属性描述了将被传递到method
中指定的 Plotly 函数的数据,包括一个帧列表和一个指定如何在它们之间转换的字典。实际上,您可以为每个滑块步骤指定多个帧,当滑块移动到该步骤时,这些帧将全部循环。在我们的例子中,我们只需要一个帧,并用它的名字来标识它。之后,我们描述转换属性:
frame
-duration
指定画面将显示的总时间,redraw
图表是否应该在每一步之后重新绘制。mode
-指定对animate
函数的新调用如何与当前动画交互。设置为“下一个”意味着当前运行帧将在切换到下一帧之前结束。transition
-指定总帧持续时间过渡部分的持续时间和使用的缓动功能。
这里有一些重要的细节需要注意。首先,在我们的例子中,设置redraw=False
提供了显著的性能优势,因为我们有数百个帧,每个帧有数百个点。如果没有这个设置,图表更新会明显滞后,您很快就会看到这一点。然而,禁用redraw
也有一些缺点:例如,如果redraw
被禁用,允许您设置视图范围的 rangeslider 将不会更新。另一个重要的缺点是 y 轴的范围不会自动更新,这有时可能不会显示图表上的所有点。在本文的第三部分,我们将看到如何结合两种实现的优点,即快速更新和图表重绘,代价是一些动态性和需要一个 Jupyter 笔记本。还有一种方法可以在不影响性能的情况下实现这一切(对于合理数量的数据点),但是这种解决方案需要 Dash 服务器,这超出了本文的范围。
另一件要记住的事情是,过渡持续时间是帧持续时间的一部分,因此将其设置为大于帧持续时间不会改变总持续时间。
此外,Plotly 将在滑块步长和期间动画显示多个帧之间的过渡,以及两个相邻滑块步长的帧之间的过渡。
现在,所有的功能都设置好了,我们可以实际创建框架和步骤,并填充我们的图形。对于每个日期,我们将创建一个框架,其中包含索引到该日期的跟踪和该框架的滑块步骤。注意,我们将一个帧的名称设置为它所代表的日期的 iso 格式字符串,并立即将该字符串传递给我们的create_slider_step
函数。这样做是因为滑块使用框架名称来确定要显示哪个框架。
最后,我们塑造我们的形象。我们简单地使用第一帧作为我们图形的data
属性。我们使用style_plot
功能配置辅助 y 轴,就完成了!
我已经警告过你要启用redraw
,但是让我们实际看看它的运行情况。我们将创建与之前完全相同的索引图表,但是这次将redraw
设置为 true。
如你所见,图表明显比以前慢了。就我个人而言,我觉得这种滞后是勉强可以接受的;然而,这是一个极限:如果你将数据点的数量增加到 5000 个以上,图表将会变得很慢,任何人都不会喜欢使用它。为了保持可接受的速度,您需要将数据点的数量保持在这个数字以下。
4.JupyterLab 中可变索引的索引图
第三个图表将允许用户选择自定义参考日期。如果您计划在 JupyterLab 中展示您的工作,不需要像动态索引图表中那样的花哨动画,但仍然希望用户能够选择任何日期作为参考日期,此图表正适合您。**请注意,此图表仅适用于 JupyterLab,不适用于 Jupyter 笔记本!**图表将通过使用小部件的ipywidgets
库来实现。根据官方文件,微件是
在浏览器中具有表示形式的多事件 Python 对象,通常作为控制器,如滑块、文本框等。[…]你可以使用小部件为你的笔记本建立交互式图形用户界面。您还可以使用小部件在 Python 和 JavaScript 之间同步有状态和无状态信息。
( Source )这个想法是使用小部件通过 GUI 为我们的图表提供输入,并更新图表。这样,在选择了不同的参考日期后,图表数据将通过 Jupyter 回调而不是 Plotly 事件进行更新。
我们将使用两个不同的小部件构建两个版本的图表。一个是 DatePicker 小部件,另一个是 SelectionSlider 小部件。第二个版本将是第 2 部分中动态索引图的健壮实现。
首先,我们复制我们的初始数据帧并导入额外的库。Ipython.display
用于渲染浏览器中的小部件。
from IPython.display import display
from ipywidgets import widgets as wgdfs = df.copy()
接下来,我们创建图表对象。因为我们希望我们的 Plotly 图表与 Jupyter 小部件兼容,所以我们的图表需要是一个 FigureWidget 对象,而不是一个 Figure 对象。
我们之前定义的样式函数也适用于 FigureWidget 对象,所以我们可以简单地将它应用到我们的绘图中。
在添加交互性之前,我们需要用初始数据填充图表。我们需要为图表提供一个初始状态。在我们的例子中,state 代表选定的参考日期。我们将使用起始日期作为初始状态,用create_indexed_columns
将数据索引到该日期,并将包括黑色参考线在内的所有轨迹添加到绘图中。
我们有基础,我们的初始状态。下一步是编写update_chart
函数,该函数将接收一个日期作为小部件的输入和属性,使用create_indexed_columns
将我们的数据标准化为该日期,并用这些新值更新轨迹的 y 值。为了一次更新多个值,我们将使用[figure.batch_update()](https://www.kite.com/python/docs/plotly.basedatatypes.BaseFigure.batch_update)
上下文管理器来执行更新。对布局和with figure.batch_update()
块内的数据的每一个改变都将在一个消息中发送到图表,而不是像update
那样的多个消息,从而防止中断。
现在我们开始实现日期选择器变体和滑块变体。我们从第一个开始。
还有四个步骤:
- 创建一个 DatePicker 小部件。
- 定义一个回调函数
picker_response
,它监听小部件中的变化并调用update_chart
。 - 将回调函数连接到小部件。
- 将小部件放入容器并显示所有内容。
第一步很简单。
对于第 2 步和第 3 步,带有签名handler(change)
的特殊处理函数picker_response
必须用于回调。change
是一个保存变更信息的字典,比如旧值和新值,以及小部件已变更属性的名称。我们将使用new
键获取更改时设置的新值。为了将处理程序绑定到小部件,我们使用小部件的observe
方法。通过设置observe
的names
参数,我们指定回调函数将在小部件的哪些属性上执行。在我们的例子中,我们需要设置names='value'
,因为我们想要将滑块的值,即选择的日期传递给处理程序。
在第 4 步中,我们简单地用一个垂直框小部件调用ipython.display
函数,它包含日期选择器小部件(包装在一个水平框中)和数字小部件。这些框就像容器一样,类似于 HTML divs。就是这样!
作者图片
图表按预期工作。
现在让我们使用 slider 小部件重新实现带有滑块的动态索引图表。这里的过程在结构上与前面的图表相同。首先,我们定义一个选择滑块小部件,它存储一个选项值列表。对于我们的选项,我们将使用转换为字符串的索引中的日期。我们使用字符串,因为否则当前日期将被格式化为零小时,这不太好。
下面的部分将允许我们使这个图表成为动态索引图表的可靠替代:通过将小部件的continuous_update
属性设置为 false,回调函数将只在发布事件时执行。这将产生一个不像动态索引图那样花哨的图表,但具有相同的日期选择界面和每次日期滑块改变时重新绘制的所有好处。如果您启用了continuous_update
,图表的行为将与动态指数图表完全一样,速度相同(如果不是更慢的话)。
接下来,我们编写回调函数,用observe
将其连接到滑块。最后,我们将所有东西包装到一个 VBox 小部件中,并调用display
。瞧,图表准备好了!
作者图片
我希望这篇文章对你有用!我花了一段时间才发现如何实现多索引图表,我想让您免去这项工作。
我想问你一件事:如果你喜欢这篇文章,请 跟我上媒 。你的订阅将使媒体给我钱,我喜欢钱。谢谢😊
你可以在 GitHub 上找到整篇文章的源代码。
如何使团队的 Power BI 开发更容易
当你试图在一个团队中开发 Power BI 解决方案时,你会遇到同步变化的重大问题。让我们看看这个问题可能的解决方案是什么。
介绍
我们都知道 Power BI 是一个单片软件,它创建一个包含解决方案所有数据的文件。这使得团队中的开发工作非常困难。
通常,你开始在一个文件中记录变更,有人负责将所有变更整合到一个“主文件”中。
这种方法效率不高,并且容易出错。
ALM 工具包是一个免费的工具,它承诺解决这些问题中的一些,如果不是全部的话。
它是一个独立的工具,用于在表格模型(Power BI 或 SSAS 模型)之间同步数据模型。
另一方面, BISM 规格化器是一个免费的 Visual Studio 扩展,因此只能用于 Analysis Services 数据模型。
让我们看看这些工具以及如何使用它们。
面向 Power BI 的 ALM 工具包
用于 Power BI 的 ALM 工具包可以比较和同步表格模型。
表格模型可以是:
- 在 Power BI Desktop 中打开的 Power BI 文件
- Power BI 服务中发布的数据集
- model.bim 文件形式的表格模型
- 分析服务的数据模型
当您启动 ALM 工具包时,您必须选择源和目标。
当您在 Power BI Desktop 实例中打开两个 Power BI 文件时,您可以在下拉选择器中选择它们:
图 1 —选择两个 Power BI 文件进行比较(图由作者提供)
点击 OK 按钮后,两个模型的比较开始。
一旦两个模型的比较完成,您就会收到所有对象的列表以及源模型和目标模型之间的差异。
当您从 Visual Studio 或表格编辑器中比较兼容级别高于 Analysis Services 服务器上的目标数据模型的 model.bim 文件时,系统会提示您将目标数据库的兼容级别升级到与源模型相同的级别:
图 2 —与 SSAS 目标比较时兼容性级别的升级(作者提供的图)
现在,您可以通过单击鼠标右键来选择不同之处,并选择工具必须执行的操作:
图 3 —差异和要执行的操作(作者提供的图)
如上图所示,您可以看到每个对象的不同之处。
要仅显示有差异的对象,您必须单击功能区中的“选择操作”按钮,然后选择“隐藏跳过具有相同定义的对象”。
如下图所示,ALM 工具包在模型比较过程中考虑了注释、格式字符串和关系:
图 4 —现有对象的差异(按作者分类)
一旦您为每个变更的物件标记了动作,如果更新可行,您就可以验证您的选择:
图 5 —验证结果(作者提供的图)
正如您在上面的图片中所看到的,该工具不能将所有的更改应用到目标模型。
以下更改不能应用于 Power BI 中的目标模型:
- 计算列
- 关系
- 新表
,因为这需要在超级查询中添加一个表
当您单击“应用”按钮时,该工具会自动跳过此类更改。
将 model.bim 文件与 SSAS 上的数据模型进行比较时,可以更新计算列和关系。
使用 Power BI,您可以将更改应用到以下对象:
- 措施
- 计算组
不幸的是,我们不能使用这个工具来缩放我们的模型到 SSAS。
当我尝试将 Power BI 文件与 SSAS 模型文件进行比较时,我得到以下错误信息:
图 6 —与 SSAS 模型比较时的错误消息(作者提供的图片)
然后,我试图将 PBI 桌面上的一份 Power BI 报告与一份已发布的 PBI 服务报告进行比较。
事实证明,ALM 工具包访问 XMLA 端点,只有当您拥有每用户高级许可证或高级容量时,XMLA 端点才可用。您需要在激活高级功能的工作空间上发布报表。
当您有这些可能性之一时,您可以连接到您的工作区和报表:
图 7 —连接到 PowerBI 服务(图由作者提供)
您可以通过以下 URL 访问您的工作区:power bi://API . power bi . com/v 1.0/my org/。
只要您输入 URL,该工具就会要求您提供 Power BI 凭据。
登录后,您可以从下拉列表中选择报告。
其余的过程与上面的例子相同。
BISM 规格化器
BISM 规格化器是 Visual Studio for Analysis Services 的免费扩展。
该工具覆盖了与 ALM 工具包相同的特性,并且可以以相同的方式使用。唯一的区别是 BISM 规格化器不是一个独立的工具,就像 ALM 工具包一样,而且 GUI 略有不同。
图 8——BISM 规格化器(红色)和 ALM 工具包(蓝色)之间的比较(图由作者提供)
像 ALM 工具包一样,可以比较日期模型,验证变更,为差异创建 Excel 报告,直接部署变更,或者为变更的部署生成修改脚本。
您可以在本文末尾找到 BISM 规格化器的文档。
但是,BISM 规格化器有一些很酷的特性,可以帮助开发过程。
显色法
正如本文开头所描述的,当多个开发人员在一个表格模型上工作时,我们面临着巨大的挑战。无论您在 Visual Studio 中使用表格编辑器还是 Power BI Desktop 修改数据模型,都没有什么不同。
上面显示的两种工具都可以帮助您的团队解决这些挑战。
当一个开发人员完成了模型中的一个新特性时,他可以开始比较并更新一个目标模型,包括开发管道下一阶段的更新。
因为您可以有选择地更新变更,所以您不需要一直部署所有的变更。
这种可能性可以简化你的方法,节省大量时间。
另一种方法是使用分析服务服务器来支持开发人员。
想象以下场景:
Power BI 数据集中有一个复杂的数据模型。多个开发人员正在对数据模型进行修改。并且您希望随时记录您的更改。
现在,您可以使用 Analysis Services 作为桥梁来并行开发和测试多个更改。
不幸的是,如果不对 model.bim 文件进行许多修改,就不可能将 Power BI 数据模型直接迁移到 Analysis Services。
但是,当您从一开始就使用 Analysis Services 时,您可以使用下面描述的流程来支持您的团队:
- 开发人员在 Visual Studio 中打开数据模型(model.bim 文件)的副本
- 他更改模型,并将修改后的数据模型部署到服务器,成为新的数据模型
- 测试人员可以使用 Excel 或 Power BI Desktop 来测试新特性
- 现在,您可以使用 BISM 规范器来更新 Power BI Desktop 中的目标数据模型
您需要从 DAX Studio 获得正确的端口,以便从 BISM 规范器连接到 Power BI Desktop:
图 9 —将 BISM 归一化器连接到 Power BI Desktop)(作者提供的图片)
但是,为什么要用分析服务来管理 Power BI 的变化呢?
当您使用 Visual Studio 更新您的 Analysis Services 数据模型时,您可以保存 BISM 规范化器比较的结果,以便进行文档记录和版本控制。
正如您在 BISM 规范化器用户指南中所看到的,您可以将保存的比较文件添加到 Analysis Services 项目中。
当使用 ALM 工具包时,这种方法是不可能的。
如果您有 CI/CD 过程,那么您可以使用 BISMNormalizer.exe 来实现对您的数据模型的变更的自动部署。
不管您如何改变表格模型的开发过程,这两个工具都是对您的工具箱的一个有价值的补充,并且没有额外的成本。
结论
这两个工具都能以非常可靠的方式同步表格数据模型。
但是,您需要改变您的开发过程以及开发人员使用开发工具的方式,以便找到最有效的方式来使用您所掌握的工具。
据我所知,没有解决方案来同步电力查询代码或您的报告中的可视化。
当您想要在两个 Power BI 文件之间同步您的 Power Query 工作时,您必须手动将 M 代码从一个 Power BI 文件复制到另一个。
有趣的是,有一种方法可以在不打开 Power Query 的情况下从报告中提取整个 M 代码:
- 转到帮助功能区
- 点击关于
- 点击复制按钮:
图 10 —从 About 复制超级查询代码(作者提供的图)
当您将内容粘贴到文本编辑器时,您可以从所有表中获得完整的 M 代码。
为了观想;只要您有相同的数据模型,您就能够在 Power BI 报告之间复制/粘贴可视化效果。但是你也必须手动操作。
托尔加·乌尔坎在 Unsplash 上拍摄的照片
参考
这里是用于分析服务的模型比较和合并的 BISM 规范化器的用户指南的 URL:【Services.pdf 分析的 模型比较和合并
ALM 工具包网页的 URL:https://www.sqlbi.com/tools/alm-toolkit/
2021 年表格模型的开发工具关于 SQLBI 的文章
https://medium.com/@salvatorecagliari/membership
如何让实时机器学习为用户旅程服务
为什么结合会话和历史行为是 B2C 组织的正确方法
现场用户体验是数字企业与其客户之间最重要的接触点。这就是为什么领先的 B2C 组织经常求助于动态决策策略来优化他们的用户旅程。由机器学习(ML)驱动的动态决策引擎可以帮助产品团队满足消费者日益增长的个性化需求,同时引导他们实现有意义的业务成果,如转化、参与等。
实时机器学习可以通过在每个用户的会话中自动适应新信息来增强动态决策。与批量推断相比,实时更新预测可以提供两个实质性的好处:(1)更准确的模型,以及(2)针对匿名和首次用户的能力。然而,有许多不同形式的实时 ML,并不是所有都非常适合优化用户的旅程。在这篇文章中,我们解释了为什么 B2C 组织希望最大化其动态用户体验的价值,应该利用一种特定形式的实时 ML:由历史数据和会话中数据的组合提供动力的实时推理。
实时推理的优势
一旦训练了机器学习模型,在大多数情况下,使用该模型进行预测是一个两步过程:
- 特征工程 —将原始数据处理成更具预测性和机器可读的变量,并对模型进行训练。特征的例子包括用户行为随时间的变化、人口统计信息和一天中的时间。
- 推理 —通过模型输入这些特征以生成预测。
例如,考虑一个机器学习管道,它被设计成预测促销折扣将如何影响每个用户转化为付费订阅的可能性。为此用例收集的原始数据可能包括行为事件—点击、登录、页面浏览量等。但是为了训练一个高质量的模型,这些事件必须首先被转换成更高层次的特征,例如:“用户在过去两周内登录了多少次?”,或者“用户的会话有百分之多少发生在移动设备上?”。一旦训练完成,这些相同的特征必须在推理期间提供给模型,以便进行预测。
大多数 ML 系统通过在循环时间表上进行离线批处理来进行特征工程和推理。然后,可以非常快速地存储和访问所得到的预测,以便进行现场动态决策,但是它们不会考虑自批处理运行以来积累的任何信息。更重要的是,批量预测不适用于没有先前行为历史的首次用户或匿名用户。由于第三方 cookies 的长期压力,实时数据的缺乏越来越成问题。
实时推理试图通过利用最新的可用信息来填补这些空白。在许多情况下,实时推理会为现有用户带来更好的预测(因为会话中的数据通常对良好的性能至关重要),并使企业能够瞄准更广泛的用户群(即匿名用户)。再加上动态决策引擎,这些优势可以转化为数字产品团队更多的转换和收入。
实现实时 ML:用户旅程的挑战
在理想情况下,特征工程和推理都是实时发生的。当用户浏览你的网站时,他们的行为会自动与历史事件结合成复杂的特征,这些特征会被用来进行最新的预测。然而,实际上,在(1)可以进行多少实时处理和(2)可以多快获得预测之间存在权衡。你越快需要返回结果,你就越不需要复杂的特性工程。
例如,考虑一个特征,该特征描述了在用户的一生中,在类别“新闻”上出现的用户文章的百分比。为了实时地重新计算这个特征,必须加载并查询用户的整个行为历史。现在考虑一个 ML 模型可能需要数千个相似的特性,很容易看出这种方法对于需要低延迟和客户端实现的用例来说是不可行的。由于这种权衡,许多成功部署实时 ML 的领域是那些直接从客户端收集做出准确预测所需的所有信息的领域。
例如,考虑保护当今许多智能手机的面部识别软件。一旦一个模型被训练来识别某些面部特征,预测一张给定的脸是否属于手机的主人仅仅依靠这张脸的照片。因为所有必要的输入数据都以机器可读的形式直接可用,并且不需要与其他数据源结合,所以可以非常快速地实时做出预测。下面列出了在独立环境中运行的实时 ML 的其他示例。
然而,对于消费者数据用例,仅仅依靠当前会话的信息通常是不够的。预测用户未来的行为通常需要对他们过去的行为有更全面的了解。但是,加载这些数据并将其整合到最新功能中通常非常缓慢。因此,B2C 产品团队有时被迫在两个不完美的解决方案之间做出选择:仅基于当前会话的数据使用实时推断,或者仅使用历史数据生成批量预测。
两全其美的解决方案
为了应对这一挑战,在消费者旅程中使用机器学习的组织应该采用混合方法。通过利用历史特征(批量预先计算)和会话中特征(实时计算)的组合,企业可以获得实时推理的好处,而不会牺牲过去数据的预测价值。
在这个框架中(我们称之为具有混合特征的实时推理),每个用户的整个行为历史在做出预测时都被考虑在内。但是,为了避免延迟问题,过去和现在的数据不会合并到任何单一功能中。相反,一组特征是基于用户的当前会话构建的,而另一组特征是使用来自先前会话的数据构建的。例如,不是计算诸如“在过去 7 天内,包括这次会话,用户点击了多少次?”一个混合特征系统将产生两个独立的特征,这两个特征合在一起捕获相同的信息
- “用户在过去的 6 天里点击了多少次?”(在服务器上批量计算)
- “今天用户在当前会话中有多少点击?”(在客户端实时计算)
这种方法为动态用户旅程提供了两全其美的优势——复杂的历史功能描绘了用户行为的全面图景,最新的行为信号促进了上下文定位和匿名用户预测,以及低延迟(约数百毫秒)以确保响应迅速的用户体验。
示例:动态订阅付费墙
为了说明这个框架在实践中是如何工作的,考虑一个媒体组织,其目标是通过动态付费墙系统来增加订阅量。该系统由一组 ML 预测驱动,这些预测表明如果显示硬付费墙而不是访问 3 个额外的免费内容,每个用户订阅的可能性将如何变化。
预测一个用户的行为会如何改变是一项困难的任务。为了实现高投资回报率,组织需要准确的 ML 预测。为了生成准确的预测,模型需要访问一组丰富的要素。为了构建这些功能,我们混合使用了历史信息和会话信息。
首先,考虑历史特征。这些可以使用前一天结束时收集的信息,按批处理时间表每天重新计算。例如,在 1 月 1 日午夜,可能会运行一个批处理进程,使用截至 12 月 31 日一天结束时收集的信息来重新计算数千个分析风格的特征(如下所示)。
现在让我们假设用户 X 在 1 月 1 日登录网站。为了确定用户应该获得哪种付费墙体验,我们请求生成一个新的预测。用户的批量要素从高可用性要素存储中获取,而一组会话中的要素在客户端中实时计算。两组特征都被提供给模型,以便做出可能的最佳预测。
结论
实时推理可以极大地提高动态决策策略的效率,但对于 B2C 组织来说,采用正确的实时实现以实现其好处是很重要的。通过利用来源于过去和现在数据的混合特性工程系统,这些组织可以为更多用户生成更好的预测,以优化现场体验并推动其业务的关键指标。
如何用 R 制作 REST APIs:水管工初学者指南
立即将 R 脚本转换成 REST APIs。
REST APIs 在我们身边无处不在。软件工程师用它们来开发后端逻辑,数据科学家用它们来部署机器学习模型。今天你将学习如何用 R 和plumber
包制作一个基本的 REST API。
这篇文章的结构如下:
- REST APIs 简介
- 用 R 和 Plumber 开发一个简单的 REST API
- 结论
REST APIs 简介
REST 代表“代表性状态转移”。更简单地说,它代表了开发人员在创建 API 时遵循的一组规则。最常见的规则是,每当向特定 URL 发出请求时,都应该获得一段数据(响应)。
您的请求由四部分组成:
- 端点 —您访问的 URL 的一部分。例如,URLhttps://example.com/predict的端点是 /predict
- 方法 —您正在发送的一种请求,可以是 GET、POST、PUT、PATCH 和 DELETE。它们用于执行以下操作之一:创建、读取、更新、删除(CRUD)
- 标头 —用于提供信息(例如,考虑认证凭证)。它们以键值对的形式提供
- 正文 —发送给服务器的信息。仅在不发出 GET 请求时使用。
大多数情况下,发出请求后返回的响应是 JSON 格式的。另一种格式是 XML,但 JSON 更常见。你也可以返回其他对象,比如图片来代替。今天你将学会如何做。
r 允许你用plumber
包开发 REST APIs。你可以在这里阅读官方文档。
用plumber
可以很容易地将任何 R 脚本文件转换成 API,因为你只需要用注释来修饰你的函数。过一会儿你就会明白了。
用 R 和 Plumber 开发一个简单的 REST API
首先,创建一个空的 R 脚本文件。你需要几个包:
plumber
–开发 APIdplyr
–根据请求体(或 URL 参数)过滤数据集ggplot2
–用于数据可视化gapminder
–用于数据。这就是你将要使用的 API 的基础。
您可以放置两个类似于roxigen2
的注释来指定 API 标题和描述。这两个不是强制性的,但是你不应该跳过它们。下面是整个代码片段(导入、名称和描述)的样子:
您现在已经准备好创建您的第一个端点。
端点 1—/国家
这个端点背后的想法是,在应用了几个过滤器之后,它应该返回国家和它们各自的数据。更准确地说,这个端点接受洲、预期寿命和人口的参数值。continent 的值必须精确,另外两个参数的值过滤数据,以便只返回具有较大值的行。
如果你想把事情做好,就需要很多评论,就像你之前看到的那样。写一个简短的描述、列出参数是一个很好的实践,并且必须指定请求类型和端点。
在注释下面,您将放置一个执行必要逻辑并返回结果的函数。
让我们考虑一下参数。你需要:
- 洲 —列洲
- 预期寿命 —列 lifeExp
- 人口 —列人口
这三项都是强制性的,您可以使用dplyr
包基于参数值进行过滤。该端点将只返回最近一年的数据,即 2007 年。
以下是*/国家*端点背后的完整逻辑:
如果您现在运行 API,您将看到以下内容:
图 1——API 文档页面(图片由作者提供)
图像底部的端点(蓝框)是可点击的。点击它会展开整个另一部分:
图 2——针对/countries 端点的文档(图片由作者提供)
您可以点击“试用”按钮,直接从浏览器发出请求。然后,您必须填写参数值;让我们这样说:
图 3 —测试国家/地区终点(图片由作者提供)
一旦您点击“执行”按钮,响应将显示如下。这种情况下看起来是这样的:
图 4—/国家/地区端点示例响应(图片由作者提供)
这就是你的第一个终点!它接收数据并返回数据。但是如果你想返回别的东西呢?例如,像一幅图像。接下来您将学习如何做到这一点。
端点 2—/图
这个终点会很不一样。现在的目标是返回图像而不是原始数据。该图像将包含一个用ggplot2
制作的线形图,显示一段时间内的预期寿命。
两个参数是必需的——国家和图表标题——它们都是不言自明的。
如果你想用 R 从一个 API 返回一个图像,你必须加上下面的注释:#* @serializer contentType list(type='image/png')
。其他的都差不多。
关于可视化,从仅包含指定国家的记录的原始数据集制作子集。然后从数据集制作一个简单的线条和标记图。
问题是——你不能返回一个ggplot2
可视化。你必须用ggsave()
功能保存图像,然后用readBin()
功能返回。
以下是完整的片段:
如果您现在运行 API,一个新的端点会立即引起您的注意:
图像 5—/绘图端点(作者提供的图像)
下面是它的文档:
图像 6—/绘图终点文档(作者提供的图像)
您可以再次单击“试用”按钮来测试功能。让我们看看波兰人的预期寿命是如何随着时间的推移而变化的:
图 7 —测试/plot 终点的波兰预期寿命(图片由作者提供)
单击“执行”按钮后,您将看到以下画面:
图 8 —波兰人的预期寿命(图片由作者提供)
这就是你如何在 API 响应中返回图像。到目前为止,您只使用 GET 方法创建了端点。接下来,您将学习如何使用 POST。
端点 3—/计算国内生产总值
现在,您将学习如何使用 POST 方法(或任何其他在请求正文中发送数据的方法)。目标是创建另一个端点来计算指定国家最近一年(2007 年)的 GDP 总量。
您需要的唯一参数是国家。
一旦有了这个值,就可以使用dplyr
和summarize()
函数来计算 GDP 总量。以下是完整的代码片段:
如果您现在运行 API,您会立即看到一个新框,这次是绿色的,表示 POST 方法:
图片 9—/计算 gdp 终点(图片由作者提供)
您可以再次单击“试用”按钮来测试功能:
图 10 —测试/calculate_gdp 端点(图片由作者提供)
让我们看看 2007 年波兰的 GDP 总量是多少:
图 11 —测试波兰的/calculate_gdp 端点(图片由作者提供)
单击“执行”按钮后,您将看到以下响应:
图 12—2007 年波兰的国内生产总值(图片由作者提供)
GET 和 POST 之间的唯一区别是,您不能将参数及其值放在 POST 的 URL 中。参数和值作为 JSON 在请求体中传递。
您现在知道如何将 R 代码包装成一个简单的 REST API。接下来让我们总结一下。
结论
今天你已经学到了很多——什么是 REST API,plumber
包有什么作用,以及如何使用它在 R 编程语言中构建基本的 API。
R 中的 API 通常是为公开机器学习模型的预测功能而开发的。当然,您还可以做其他事情(正如今天所演示的),但是 R 可能不是最好的语言。如果是这样的话,你可能更擅长 Java、Go 或 JavaScript。
喜欢这篇文章吗?成为 中等会员 继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
https://medium.com/@radecicdario/membership
加入我的私人邮件列表,获取更多有用的见解。
原载于 2021 年 1 月 13 日 https://appsilon.comhttps://appsilon.com/r-rest-api/。