在 Python 中绘制维恩图
原文:
towardsdatascience.com/plotting-venn-diagrams-in-python-6c55e0d78e57
学习如何使用维恩图展示两个或更多数据集之间的关系
·发表于 Towards Data Science ·阅读时间 8 分钟·2023 年 2 月 24 日
–
图片由 Dustin Humes 提供,Unsplash
在数据可视化中,我们生成的大多数图表属于以下一种或多种类型:
-
条形图
-
饼图
-
折线图
-
直方图
-
时间序列
然而,有一种图表不常用,那就是 维恩图。维恩图 是另一种被低估的可视化形式。它实际上是一种非常有用的可视化形式,可以让你检查两个不同数据集之间的关系。例如,以下维恩图展示了两个生物集的关系——集合 A(左圈;有两条腿的生物)和 B(右圈;会飞的生物)。重叠区域包含那些既有两条腿又能飞的生物:
来源: en.wikipedia.org/wiki/Venn_diagram#/media/File:Venn_diagram_example.png
在本文中,我将向你展示如何从样本数据集中绘制一个维恩图。我还会展示如何自定义维恩图以修改其外观和感觉。
所以让我们开始吧!
安装 matplotlib-venn 包
使用 pip
命令安装 matplotlib-venn
包:
!pip install matplotlib-venn
数据集
对于数据集,我创建了一个虚构的数据文件,名为 purchases.csv,内容如下:
custid,product
1,Mac mini
17,Mac mini
1,Mac Studio
2,MacBook Pro 13
3,Mac Studio
18,Mac mini
2,MacBook Pro 13
5,Mac Studio
7,Mac Studio
6,MacBook Pro 13
4,MacBook Pro 13
8,Mac mini
9,Mac mini
5,Mac mini
6,Mac mini
19,Mac mini
8,Mac Studio
2,Mac mini
2,Mac Studio
20,MacBook Pro 13
该文件包含了不同客户通过 custid
识别的三款 Mac 产品(Mac mini、Mac Studio 和 MacBook Pro 13)的购买记录。
下一步是将其加载到 Pandas DataFrame 对象中:
import pandas as pd
df = pd.read_csv('purchases.csv')
df
所有图片由作者提供
绘制 Venn 图
要绘制一个 2 圆 Venn 图,有几种方法。最简单的方法是向venn2()
函数(在matplotlib_venn
包中)提供两个集合的值,它会自动为你绘制 Venn 图。
让我们使用这种方法来绘制一个 2 圆 Venn 图,显示购买 Mac mini、Mac Studio 及两者的数量。
首先,我会找到所有购买了 Mac mini 的客户,并提取custid
作为一个集合:
mac_mini = set(df.query('product == "Mac mini"')['custid'])
mac_mini
以下custid
集合是那些购买了 Mac mini 的:
{1, 2, 5, 6, 8, 9, 17, 18, 19}
同样,我会提取所有购买了 Mac Studio 的custid
:
mac_studio = set(df.query('product == "Mac Studio"')['custid'])
mac_studio
这些是购买了 Mac Studio 的客户:
{1, 2, 3, 5, 7, 8}
现在我们准备绘制 2 圆 Venn 图:
from matplotlib_venn import venn2
venn2([mac_mini, mac_studio],
('Mac mini','Mac Studio'))
注意,你需要提供要显示在 Venn 图上的标签。如果不提供,默认标签是 A 和 B,这可能会有点误导或混淆。
如果你想要一个 3 圆 Venn 图,只需调用venn3()
函数:
from matplotlib_venn import venn3
macbookpro_13 = set(df.query('product == "MacBook Pro 13"')['custid'])
macbookpro_13 # {2, 4, 6, 20}
venn3([mac_mini, mac_studio, macbookpro_13],
('Mac mini','Mac Studio','MacBook Pro 13'))
我非常喜欢这种方法,因为我不需要手动计算有多少人只购买了 Mac mini,有多少人购买了 Mac Studio,有多少人购买了 Mac mini 和 Mac Studio 等。
备选方法 1
第二种方法是手动传递数值到venn2()
或venn3()
函数中。对于venn2()
函数,格式是:venn2(subsets = (*Ab*,*aB*,*AB*))
,其中:
-
Ab表示集合 A 中但不在集合 B 中的项目数量
-
aB表示集合 B 中但不在集合 A 中的项目数量
-
AB表示同时包含在集合 A 和 B 中的项目数量
让我们使用数据框计算Ab、aB和AB的值。首先,找到购买了 Mac mini 但没有购买 Mac Studio 的人:
# calculate Ab
mac_mini_exclude_mac_studio = mac_mini - mac_studio
display(mac_mini_exclude_mac_studio)
# {6, 9, 17, 18, 19}
然后,找到购买了 Mac Studio 但没有购买 Mac mini 的人:
# calculate aB
mac_studio_exclude_mac_mini = mac_studio - mac_mini
display(mac_studio_exclude_mac_mini)
# {3, 7}
最后,找到购买了Mac mini和Mac Studio的所有人:
# calculate AB
mac_mini_and_mac_studio = mac_studio.intersection(mac_mini)
display(mac_mini_and_mac_studio)
# {1, 2, 5, 8}
计算了Ab、aB和AB的值后,你只需计算每个集合中的项目数,并将它们传递给venn2()
函数:
venn2(subsets = (
len(mac_mini_exclude_mac_studio), # Ab
len(mac_studio_exclude_mac_mini), # aB
len(mac_mini_and_mac_studio) # AB
),
set_labels = ('Mac mini','Mac Studio')
)
不足为奇,结果与我们之前得到的相同:
备选方法 2
下一种方法是二进制方法。你可以用字典代替传递值作为元组。对于 2 圆 Venn 图,你以以下格式传递二进制值:
-
Ab — “
10
” -
aB— “
01
" -
AB— “
11
”
对于 3 圆 Venn 图,二进制值如下:
-
Abc — “
100
” -
ABc — “
110
” -
ABC — “
111
” -
aBC— “
011
" -
abC — “
001
” -
AbC— “
101
” -
aBc — “
010
”
以下代码片段绘制了你之前绘制的相同 2 圆 Venn 图:
venn2(subsets = {
'10': len(mac_mini_exclude_mac_studio), # Ab
'01': len(mac_studio_exclude_mac_mini), # aB
'11': len(mac_mini_and_mac_studio) # AB
},
set_labels = ('Mac mini','Mac Studio'),
)
自定义 Venn 图
由于生成的 Venn 图是使用 matplotlib 创建的,因此它可以像使用 matplotlib 创建的任何图表一样自定义。
设置透明度
你可以使用 alpha
参数设置圆圈的透明度:
v2 = venn2(subsets = {
'10': len(mac_mini_exclude_mac_studio),
'01': len(mac_studio_exclude_mac_mini),
'11': len(mac_mini_and_mac_studio)
},
set_labels = ('Mac mini','Mac Studio'),
alpha = 0.8,
)
下面是将 alpha
参数设置为 0.8 的图表效果。如果你想要更浅的色调,可以将其设置为更低的值,如 0.1 或 0.2:
设置颜色
你可以使用 set_colors
参数指定圆圈的单独颜色:
v2 = venn2(subsets = {
'10': len(mac_mini_exclude_mac_studio),
'01': len(mac_studio_exclude_mac_mini),
'11': len(mac_mini_and_mac_studio)
},
set_labels = ('Mac mini','Mac Studio'),
alpha = 0.8,
set_colors=('lightblue', 'yellow')
)
设置线条样式
要绘制圆圈的轮廓,使用 venn2_circles()
函数(用于 2 圆韦恩图)与 venn2()
函数结合使用。以下代码片段展示了如何绘制带有 --
虚线和 5
线宽的轮廓:
from matplotlib_venn import venn2_circles
c = venn2_circles(subsets = {
'10': len(mac_mini_exclude_mac_studio),
'01': len(mac_studio_exclude_mac_mini),
'11': len(mac_mini_and_mac_studio)
},
linestyle='--',
linewidth=5,
)
你可以参考
matplotlib.org/3.1.0/gallery/lines_bars_and_markers/linestyles.html
了解支持的线条样式列表。
这是更新后的韦恩图:
设置字体大小
韦恩图上显示有两种类型的标签:
-
标签 — 圆圈外的文本
-
子集标签 — 圆圈内的文本
以下代码片段设置了两种类型标签的字体大小:
for text in v2.set_labels: # the text outside the circle
text.set_fontsize(20);
for text in v2.subset_labels: # the text inside the circle
text.set_fontsize(15)
自定义线条样式
你还可以在 venn2_circles()
函数之外以编程方式设置轮廓的样式和线宽:
c[0].set_lw(3.0) # customize left outline
c[0].set_ls('-.')
c[1].set_lw(2.0) # customize right circle
c[1].set_ls('--')
设置图表标题
由于这是 matplotlib,你显然可以为图形设置标题:
import matplotlib.pyplot as plt
plt.title('Customers distribution for Mac Mac and Mac Studio')
设置子集标签
如果你想自定义单独标签的外观,可以使用 get_label_by_id()
函数并传入单个圆圈的二进制值来引用标签,并设置它们的显示文本和颜色:
for text in v2.set_labels: # the text outside the circle
text.set_fontsize(20);
for text in v2.subset_labels: # the text inside the circle
text.set_fontsize(12)
text = 'Mac mini\n'
for i in mac_mini_exclude_mac_studio:
text += f'{i}\n'
v2.get_label_by_id('10').set_text(text) # Mac mini
text = 'Mac Studio\n'
for i in mac_studio_exclude_mac_mini:
text += f'{i}\n'
v2.get_label_by_id('01').set_text(text) # Mac Studio
text = 'Mac mini &\n Mac Studio\n'
for i in mac_mini_and_mac_studio: # Mac mini and Mac Studio
text += f'{i}\n'
v2.get_label_by_id('11').set_text(text)
v2.get_label_by_id('11').set_color('red')
如果你喜欢阅读我的文章,并且这些文章对你的职业/学习有帮助,请考虑成为 Medium 会员。每月 $5,你可以无限访问 Medium 上的所有文章(包括我的)。如果你使用以下链接注册,我将获得少量佣金(对你没有额外费用)。你的支持意味着我将能花更多时间写这样的文章。
[## 通过我的推荐链接加入 Medium - Wei-Meng Lee
阅读 Wei-Meng Lee 的每一个故事(以及 Medium 上成千上万的其他作家)。你的会员费直接支持…
weimenglee.medium.com](https://weimenglee.medium.com/membership?source=post_page-----6c55e0d78e57--------------------------------)
总结
就这样完成了!你学会了如何使用示例数据框绘制简单的 2 圈和 3 圈维恩图,并且了解了可以对图形进行的各种自定义。绘制维恩图很简单,更具挑战性的是整理你的数据,以便将其传递给 API 进行绘图。无论如何,希望你玩得开心!
POCS 基于的聚类算法解释
每个数据有不同的重要性
·
关注 发布于 Towards Data Science ·5 分钟阅读·2023 年 3 月 24 日
–
图片来源于 Kier in Sight 在 Unsplash
聚类分析(或 聚类)是一种数据分析技术,它探索并将一组向量(或数据点)分组,使得同一簇中的向量彼此间的相似性大于与其他簇中的向量。聚类算法在数据分析、模式识别和图像处理等众多应用中被广泛使用。
本文回顾了一种基于凸集投影(POCS)方法的新聚类算法,称为 POCS 基础的聚类算法。原始论文在IWIS2022中介绍,源代码也已发布在Github上。
凸集
凸集定义为一个数据点的集合,其中连接集合中任意两个点 x1 和 x2 的线段完全包含在该集合中。根据这种凸集的定义,空集∅、单集合、线段、超平面和欧几里得球被认为是凸集。数据点也被认为是一个凸集,因为它是一个单集合(一个只有一个元素的集合)。这指向了 POCS 概念可以应用于数据点聚类的新路径。
投影到凸集(POCS)
让我简要回顾一下 POCS 的概念(不涉及方程)。POCS 的方法大致可以分为两种形式:交替 和 并行。
交替 POCS
从数据空间中的任意一点开始,将该点交替投影到两个(或更多)相交的凸集上,将会收敛到这些集合交集中的一点。下面显示了一个图形说明。
作者提供的图片。
当凸集不相交时,交替投影将收敛到依赖于投影顺序的贪婪极限循环。
作者提供的图片。
并行 POCS
与交替形式不同,POCS 的并行形式同时将数据点投影到所有凸集上,每个投影都有一个重要性权重。对于两个非空交集的凸集,与交替版本类似,并行投影收敛到这些集合的交集中的一点。
作者提供的图片。
在凸集不相交的情况下,投影将收敛到一个最小化解决方案。POCS 基础的聚类算法的主要思想源于这一特性。
作者提供的图片。
欲了解 POCS 的更多细节,请访问原始论文和/或一些其他推荐的论文(可用 pdf 文件):
POCS 基础的聚类算法
利用并行 POCS 方法的收敛性质,作者提出了一种非常简单但有效(在某种程度上)的聚类算法。该算法的操作与经典的 K-Means 算法类似,但处理每个数据点的方式有所不同,即 K-Means 算法对每个数据点赋予相同的权重,而 POCS-based 聚类算法则对每个数据点赋予不同的权重,权重与数据点到簇原型的距离成正比。就这样!
下面显示了算法的伪代码:
图像来自论文。
实验结果
作者检查了 POCS-based 聚类算法在一些公共基准数据集上的性能,这些数据集来自网站Clustering basic benchmark。这些数据集的描述总结在下表中。
图像来自论文。
在论文中,作者比较了 POCS-based 聚类算法与其他传统聚类方法(包括 K-Means 和 Fuzzy C-Means 算法)的性能。关于执行时间和聚类误差的评估总结在以下表格中。
图像来自论文。
图像来自论文。
可视化的聚类结果也在以下图中展示。
图像来自论文。
欲了解更多细节,可以在原始论文这里查看。
示例代码
让我们在一个非常简单的数据集上试用这个算法。为了简单起见,可以使用以下命令安装发布的算法包:
pip install pocs-based-clustering
首先,我们导入几个必要的包,并创建一个以 10 个簇为中心的 5000 个数据点的简单数据集:
# Import packages
import time
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from pocs_based_clustering.tools import clustering
# Generate a simple dataset
num_clusters = 10
X, y = make_blobs(n_samples=5000, centers=num_clusters, \
cluster_std=0.5, random_state=0)
plt.figure(figsize=(8,8))
plt.scatter(X[:, 0], X[:, 1], s=50)
plt.show()
图像由作者提供。
现在,使用内置函数进行聚类并显示结果:
# POSC-based Clustering Algorithm
centroids, labels = clustering(X, num_clusters, 100)
# Display results
plt.figure(figsize=(8,8))
plt.scatter(X[:, 0], X[:, 1], c=labels, s=50, cmap='viridis')
plt.scatter(centroids[:, 0], centroids[:, 1], s=100, c='red')
plt.show()
图像由作者提供。
结论
在这篇文章中,我简要回顾了一种基于凸集投影方法(POCS)的简单而有效的聚类技术,称为 POCS-based Clustering Algorithm。该算法利用 POCS 的收敛性质应用于聚类任务,并在一定程度上实现了可行的改进。该算法的有效性已在一些基准数据集上得到验证。原始论文可以在arXiv(预印本)或IEEE Xplore(发表论文)找到。代码也已发布在Github上。
我很高兴欢迎你来到我的 Facebook 页面,分享有关机器学习的内容:深入机器学习。你还可以在这里找到我发布的其他重要内容:
-
EDN-GTM
-
MetaFormer
-
Darkeras
-
EFPN: 扩展特征金字塔网络
-
数据增强
-
数据蒸馏
-
以及其他内容在我的页面上。
感谢你的时间!
2022 年 EMNLP 上的诗歌、花卉和龙
·
关注 发表在 Towards Data Science ·8 min 阅读·2023 年 1 月 2 日
–
“诗歌、花卉、地牢与龙的结合,数字艺术 — ar 3:2 — v 4”,Midjourney
EMNLP 会议是自然语言处理领域一个备受推崇的活动,研究人员汇聚一堂,分享和讨论该领域的最新发现。今年的会议于 12 月 7 日至 12 月 11 日在阿布扎比举行。在会议上展示的众多论文中,我想特别提到三篇给我留下深刻印象的论文。这些论文可能不是最实用或最知名的,但我认为它们值得一提。两篇论文以海报的形式展示,第三篇是完整的演讲。我最喜欢的三篇论文中的一篇是 PoeLM。
PoeLM: 一种用于无监督诗歌生成的节奏和韵律可控语言模型
-
组织: 巴斯克大学、Meta AI、哥本哈根大学
-
代码:
github.com/aitorormazabal/poetry_generation
,不过这里只有数据集创建。 -
主要思想: 通过控制代码生成西班牙语和巴斯克语正式诗歌,使用在非诗歌文本上训练的语言模型。
动机
现代语言模型能写诗吗?当然能。你可以快速用ChatGPT测试一下。挑战在于强加特定的约束,比如固定的音节数或特定的韵律或节奏方案。
我们如何强迫语言模型生成正式的诗歌?一种方法是修改解码算法,这在现代语言模型中很复杂,因为它们处理的是子词,既不是词也不是音节。本文描述了另一种方法。要使其有效,你需要一个常规的文本语料库和一个能够分析音节和韵律的系统。
训练语言模型
来自论文的图,提出的方法。
你需要做的步骤是:
-
获取一个常规的非诗歌语料库,并将其拆分成短语。
-
将文本分成 N 个短语块,其中 N 是随机采样的。
-
使用结构描述符(=前缀)增强组,以包含每个短语的音节数和韵尾。
-
用将结构描述符视为普通标记的经典变换器语言模型进行训练。
来自论文的图。正式诗歌及其相关的结构描述符。
上图中的结构描述符是
<PREF>
<LEN:11><END:echo>
<LEN:11><END:ura>
<LEN:11><END:ura>
<LEN:11><END:echo>
</PREF>
这个描述符意味着四行;每行有 11 个音节;首尾两行以“echo”结尾,第 2 和第 3 行以“ura”结尾。模型将学习如何使用这些代码,因为使用这些提示生成文本比没有提示要容易。
生成
-
选择韵律方案和音节数。
-
生成一个结构描述符。作者通过从训练语料库中五种最常见的韵律声音中独立采样每种韵律声音来完成这个任务。
-
提供诗歌的第一行(可选)
-
使用训练过的语言模型生成大量诗歌候选。
-
过滤掉所有不符合韵律方案或包含错误音节数的候选。
-
使用训练过的语言模型(没有结构描述符)按一般流畅度重新排序剩余的候选,并输出得分最高的那个。
它的效果如何?
来自论文的表格。系统 S1 在人工评估中排名高于 S2 的次数百分比。
第 5 步的过滤率对于西班牙语诗歌为 30.9%,对于巴斯克语诗歌为 23.4%。37.3%的人更喜欢自动生成的诗歌而非知名诗人的诗歌,比较的是第一行相同的诗歌。
你能在你的语言中做到这一点吗?
使用描述的算法需要可靠的音节划分和押韵检测过程。虽然某些语言可能已有这样的程序,但其他语言可能具有更复杂的特征,如节奏,需要考虑。在这些情况下,可以修改结构描述符以包括额外的组件。
为什么这对我很重要?
六年前,丹尼尔·阿纳斯捷耶夫和我开发了一个俄罗斯诗歌生成系统,rupo。这是一个基于 LSTM 的语言模型,具有一些独特的特征:它从右到左预测文本,分别使用单词的标准形式及其语法特征,并且基于有限状态接受器。自那时以来,自然语言处理技术取得了显著进展,使得今天创建类似系统可能更加容易。
画一朵花:自然语言中的处理和基础抽象
-
论文: Lachmy 等人, 2022
-
机构: 巴伊兰大学, AI2
-
代码:
github.com/OnlpLab/Hexagons
,但目前还没有基准,只有数据集本身。 -
主要思想: 创建一个基于六边形网格的指令式模式绘制的自然语言基础抽象基准。
论文中的图,展示自然语言中的抽象层次
动机
我们知道大型语言模型无法正确计算或执行简便的估算。即使是简单的空间推理任务也是个问题(不过思维链有所帮助)。但是抽象呢?当你命令你假设的 AI 助手,“订三个披萨,一个 BBQ,一个意大利辣香肠,一个玛格丽塔,前两个大,最后一个中,下午 5 点”,它应该能够理解你。这不仅仅涉及省略号,还有条件、迭代、功能分解、递归和其他机制。
为了衡量模型 grasp 抽象概念的程度,我们可以将其 grounding 在各种 虚拟 世界中。在这种情况下,作者使用了一个 10x18 瓷砖和八种颜色的六边形棋盘作为 grounding 抽象的基础。
数据集
本研究的数据集是通过众包方式收集的。虽然作者提供了起始图像,但众包工作者也通过绘制额外的模式进行了贡献。注释过程分为两个阶段:第一阶段,一组注释人员根据图像编写说明;第二阶段,另一组人员根据说明尝试重建图像。任何不一致或分歧都通过人工检查解决。最终数据集包含 175 张独特的图像、620 组说明和 4177 个说明步骤。
论文中的图,画廊样本。
实验
测试了两种模型:分类模型和生成模型。分类模型使用了 DeBERTa 来预测每个瓦片的状态。生成模型则使用了 T5 来生成一系列动作。模型在各种设置下进行了测试,这些设置在历史记录和当前棋盘信息的量上有所不同:无历史记录、一个前一步、完整历史记录、预测棋盘和神谕棋盘。结果表明,这些模型的表现明显低于人类,甚至在拥有神谕棋盘和完整历史记录的情况下,也只能处理最基本的抽象。
论文中的表格。两种模型在测试集上的结果,基于动作的指标。
论文中的表格。数据集评估,人类表现。
为什么这很重要?
这是对自然语言模型面临的挑战的一个很好的视觉展示。这个基准使得可以迅速识别这些模型中缺乏哪些抽象机制。我怀疑基于代码的模型会在这个任务中表现更好,并且我对测试这一假设很感兴趣。
龙与地下城作为人工智能对话挑战
-
机构:宾夕法尼亚大学,谷歌研究
-
代码:尚未发布,应该在这里
-
主要思想:基于 D&D 对话创建一个对话系统挑战,任务是在游戏中生成下一个对话回合,并预测游戏的状态,给定对话历史。
“robots playing D&D, digital art, futuristic — ar 3:2 — v 4”,Midjourney
动机
龙与地下城(Dungeons & Dragons)是一款奇幻桌面角色扮演游戏。角色们在奇幻的环境中展开冒险。地下城主作为游戏的裁判和讲述者,同时维护冒险发生的环境,并扮演游戏世界的居民,也称为非玩家角色(NPCs)。角色们组成一个小队,与环境中的居民和彼此互动。他们一起解决难题,参与战斗,探索并收集宝藏和知识。在这个过程中,角色们获得经验值,逐渐升级并变得越来越强大,经过一系列的游戏会话。— 维基百科
许多自然语言处理数据集都高度专业化,专注于特定任务。龙与地下城(D&D)是一项需要所有参与者高度语言理解的人类活动。它涉及一系列技能,如文本生成、知识库查找、多方对话、目标设定、常识推理、意图检测、状态跟踪和问题回答,使其成为评估 NLP 模型能力的理想测试平台。
AI 在 D&D 中的其他应用包括 角色照片生成 和当然还有著名的 AI Dungeon。
数据集
论文中的图示。D&D Beyond 论坛中 3 回合的示例。
作者从 D&D Beyond 论坛上抓取了 Play-By-Post 数据,在这个论坛上,人们通过轮流在论坛上发帖来描述他们的行动。这不是 D&D 会话的唯一可能来源。例如,CRD3 数据集使用了 Critical Role 节目的转录稿。
论文中的表格,数据集统计信息。
使用基于规则的启发式方法通过正则表达式和命名实体识别(NER)从文本中提取游戏状态信息。此外,在启发式方法无法提取信息的情况下,还使用了用于文本的 CNN 分类器。数据集不仅包括角色内文本,还包括角色外帖子。
实验
LaMDA,谷歌类似于 GPT-3 的大型语言模型,用于处理两个任务:游戏状态跟踪和回应生成。作者尝试了模型的各种微调变体,包括使用当前或前几个回合的状态作为控制特征。为了评估模型的表现,招募了六名对幻想题材感兴趣并具有 D&D 经验的专业评估员,其中包括三名曾担任地下城主的人员,进行手动评估。
论文中的表格。系统和人工编写的金牌回应的平均人类评估者评分。
评估结果显示领域适应是有益的,但控制特征的影响可能更清晰。然而,这些特征使得模型能够在游戏中担任特定角色,这可能使其成为实际 D&D 游戏中地下城主或玩家的有价值替代品。
论文中的表格。GST 的平均准确率与多数类基线相比。
游戏状态跟踪任务的结果本可以更好。模型接收了所有以前对话回合及其对应的状态变量,以及当前回合的文本,并期望输出当前回合的正确状态变量。模型的联合准确率为 58%。这些结果表明,仅使用大型语言模型不足以完成此任务,可能需要进一步修改以提高性能。
结论
总结上述研究和发现突出了持续存在的挑战和改进领域。必须考虑非主流论文的价值,因为它们可能提供独特的见解和方法,这些可能在急于跟上更广为人知的作品时被忽视。
Python 依赖管理:你应该选择哪个工具?
原文:
towardsdatascience.com/poetry-a-better-way-to-manage-python-dependencies-bd7b5f1eab25
Poetry、Pip 和 Conda 的深入比较
·发表于 Towards Data Science ·10 分钟阅读·2023 年 6 月 13 日
–
作者提供的图片
最初发表于 https://mathdatasimplified.com 于 2023 年 6 月 13 日。
动机
随着数据科学项目的扩展,依赖项的数量也会增加。为了保持项目环境的可重现性和可维护性,使用高效的依赖管理工具非常重要。
因此,我决定比较三种流行的依赖管理工具:Pip、Conda 和 Poetry。经过仔细评估,我相信 Poetry 在有效性和性能方面超越了其他两个选项。
在本文中,我们将深入探讨 Poetry 的优势,并突出其与 Pip 和 Conda 的主要区别。
可用的包
拥有广泛的包选择使开发人员更容易找到最适合其需求的特定包和版本。
Conda
一些包,例如 “snscrape”,无法通过 conda 安装。此外,某些版本,例如 Pandas 2.0,可能无法通过 Conda 安装。
虽然你可以在 conda 虚拟环境中使用 pip 解决包的限制,但 conda 无法跟踪用 pip 安装的依赖项,这使得依赖管理变得具有挑战性。
$ conda list
# packages in environment at /Users/khuyentran/miniconda3/envs/test-conda:
#
# Name Version Build Channel$ conda list # packages in environment at /Users/khuyentran/miniconda3/envs/test-conda: # # Name Version Build Channel
Pip
Pip 可以从 Python 包索引(PyPI)和其他仓库中安装任何包。
Poetry
Poetry 还允许从 Python 包索引(PyPI)和其他仓库中安装包。
依赖项数量
减少环境中的依赖项数量简化了开发过程。
Conda
Conda 提供了完整的环境隔离,管理 Python 包和系统级别的依赖项。这可能导致与其他包管理器相比,包的大小更大,在安装和分发过程中可能会消耗更多的存储空间。
$ conda install pandas
$ conda list
# packages in environment at /Users/khuyentran/miniconda3/envs/test-conda:
#
# Name Version Build Channel
blas 1.0 openblas
bottleneck 1.3.5 py311ha0d4635_0
bzip2 1.0.8 h620ffc9_4
ca-certificates 2023.05.30 hca03da5_0
libcxx 14.0.6 h848a8c0_0
libffi 3.4.4 hca03da5_0
libgfortran 5.0.0 11_3_0_hca03da5_28
libgfortran5 11.3.0 h009349e_28
libopenblas 0.3.21 h269037a_0
llvm-openmp 14.0.6 hc6e5704_0
ncurses 6.4 h313beb8_0
numexpr 2.8.4 py311h6dc990b_1
numpy 1.24.3 py311hb57d4eb_0
numpy-base 1.24.3 py311h1d85a46_0
openssl 3.0.8 h1a28f6b_0
pandas 1.5.3 py311h6956b77_0
pip 23.0.1 py311hca03da5_0
python 3.11.3 hb885b13_1
python-dateutil 2.8.2 pyhd3eb1b0_0
pytz 2022.7 py311hca03da5_0
readline 8.2 h1a28f6b_0
setuptools 67.8.0 py311hca03da5_0
six 1.16.0 pyhd3eb1b0_1
sqlite 3.41.2 h80987f9_0
tk 8.6.12 hb8d0fd4_0
tzdata 2023c h04d1e81_0
wheel 0.38.4 py311hca03da5_0
xz 5.4.2 h80987f9_0
zlib 1.2.13 h5a0b063_0
Pip
Pip 只安装包所需的依赖项。
$ pip install pandas
$ pip list
Package Version
--------------- -------
numpy 1.24.3
pandas 2.0.2
pip 22.3.1
python-dateutil 2.8.2
pytz 2023.3
setuptools 65.5.0
six 1.16.0
tzdata 2023.3
Poetry
Poetry 也只安装包所需的依赖项。
$ poetry add pandas
$ poetry show
numpy 1.24.3 Fundamental package for array computing in Python
pandas 2.0.2 Powerful data structures for data analysis, time...
python-dateutil 2.8.2 Extensions to the standard Python datetime module
pytz 2023.3 World timezone definitions, modern and historical
six 1.16.0 Python 2 and 3 compatibility utilities
tzdata 2023.3 Provider of IANA time zone data
卸载包
卸载包及其依赖项可以释放磁盘空间,防止不必要的杂乱,并优化存储资源的使用。
Pip
Pip 仅移除指定的包,而不是它的依赖项,这可能导致随着时间的推移积累未使用的依赖项。这可能导致存储空间使用增加以及潜在的冲突。
$ pip install pandas
$ pip uninstall pandas
$ pip list
Package Version
--------------- -------
numpy 1.24.3
pip 22.0.4
python-dateutil 2.8.2
pytz 2023.3
setuptools 56.0.0
six 1.16.0
tzdata 2023.3
Conda
Conda 移除包及其依赖项。
$ conda install -c conda pandas
$ conda uninstall -c conda pandas
Collecting package metadata (repodata.json): done
Solving environment: done
## Package Plan ##
environment location: /Users/khuyentran/miniconda3/envs/test-conda
removed specs:
- pandas
The following packages will be REMOVED:
blas-1.0-openblas
bottleneck-1.3.5-py311ha0d4635_0
libcxx-14.0.6-h848a8c0_0
libgfortran-5.0.0-11_3_0_hca03da5_28
libgfortran5-11.3.0-h009349e_28
libopenblas-0.3.21-h269037a_0
llvm-openmp-14.0.6-hc6e5704_0
numexpr-2.8.4-py311h6dc990b_1
numpy-1.24.3-py311hb57d4eb_0
numpy-base-1.24.3-py311h1d85a46_0
pandas-1.5.3-py311h6956b77_0
python-dateutil-2.8.2-pyhd3eb1b0_0
pytz-2022.7-py311hca03da5_0
six-1.16.0-pyhd3eb1b0_1
Proceed ([y]/n)?
Preparing transaction: done
Verifying transaction: done
Executing transaction: donePoetry
Poetry
Poetry 也会移除包及其依赖项。
$ poetry add pandas
$ poetry remove pandas
• Removing numpy (1.24.3)
• Removing pandas (2.0.2)
• Removing python-dateutil (2.8.2)
• Removing pytz (2023.3)
• Removing six (1.16.0)
• Removing tzdata (2023.3)
依赖文件
依赖文件通过指定所需包的确切版本或版本范围,确保软件项目环境的可重现性。
这有助于在不同系统或不同时间点重新创建相同的环境,确保开发者之间使用相同的依赖项进行协作。
Conda
要在 Conda 环境中保存依赖项,你需要手动将它们写入文件。environment.yml
文件中指定的版本范围可能会导致安装不同的版本,可能在重现环境时引入兼容性问题。
假设我们已经安装了 pandas 版本 1.5.3 作为示例。这里是一个示例 environment.yml
文件,指定了依赖项:
# environment.yml
name: test-conda
channels:
- defaults
dependencies:
- python=3.8
- pandas>=1.5
如果新用户尝试在 pandas 的最新版本为 2.0 时重现环境,则会安装 pandas 2.0。
# Create and activate a virtual environment
$ conda env create -n env
$ conda activate env
# List packages in the current environment
$ conda list
...
pandas 2.0
如果代码库依赖于 pandas 版本 1.5.3 特有的语法或行为,而在版本 2.0 中语法发生了变化,那么使用 pandas 2.0 运行代码可能会引入错误。
Pip
相同的问题也可能发生在 pip 上。
# requirements.txt
pandas>=1.5
# Create and activate a virtual environment
$ python3 -m venv venv
$ source venv/bin/activate
# Install dependencies
$ pip install -r requirements.txt
# List packages
$ pip list
Package Version
---------- -------
pandas 2.0
...
你可以通过在 requirements.txt
文件中冻结版本来固定版本:
$ pip freeze > requirements.txt
# requirements.txt
numpy==1.24.3
pandas==1.5.3
python-dateutil==2.8.2
pytz==2023.3
six==1.16.0
然而,这使得代码环境的灵活性降低,并且在长期维护中可能变得更加困难。任何对依赖项的更改都需要手动修改 requirements.txt
文件,这可能既耗时又容易出错。
Poetry
Poetry 在安装包时会自动更新 pyproject.toml
文件。
在以下示例中,“pandas” 包被添加了版本约束 ¹.5
。这种灵活的版本管理方法确保了你的项目可以适应更新的版本而无需手动调整。
$ poetry add 'pandas=¹.5'
# pyproject.toml
[tool.poetry.dependencies]
python = "³.8"
pandas = "¹.5"
poetry.lock
文件存储了每个包及其依赖项的精确版本号。
# poetry.lock
...
[[package]]
name = "pandas"
version = "1.5.3"
description = "Powerful data structures for data analysis, time series, and statistics"
category = "main"
optional = false
python-versions = ">=3.8"
[package.dependencies]
numpy = [
{version = ">=1.20.3", markers = "python_version < \"3.10\""},
{version = ">=1.21.0", markers = "python_version >= \"3.10\""},
{version = ">=1.23.2", markers = "python_version >= \"3.11\""},
]
python-dateutil = ">=2.8.2"
pytz = ">=2020.1"
tzdata = ">=2022.1"
...
这确保了安装的包的一致性,即使包的 pyproject.toml
文件中指定了版本范围。在这里,我们可以看到安装了 pandas 1.5.3 而不是 pandas 2.0
$ poetry install
$ poetry show pandas
name : pandas
version : 1.5.3
description : Powerful data structures for data analysis, time series, and statistics
dependencies
- numpy >=1.20.3
- numpy >=1.21.0
- numpy >=1.23.2
- python-dateutil >=2.8.1
- pytz >=2020.1
开发和生产的独立依赖项
通过分离依赖项,你可以清楚地区分开发用途所需的包(例如测试框架和代码质量工具)与生产环境所需的包(通常包括核心依赖项)。
Conda
Conda 本身不支持为不同环境分别设置依赖项,但可以通过创建两个环境文件来解决这个问题:一个用于开发环境,另一个用于生产环境。开发文件包含生产和开发依赖项。
# environment.yml
name: test-conda
channels:
- defaults
dependencies:
# Production packages
- numpy
- pandas
# environment-dev.yml
name: test-conda-dev
channels:
- defaults
dependencies:
# Production packages
- numpy
- pandas
# Development packages
- pytest
- pre-commit
Pip
Pip 也不直接支持分开的依赖项,但可以使用类似的方法通过分开的需求文件来实现。
# requirements.txt
numpy
pandas
# requirements-dev.txt
-r requirements.txt
pytest
pre-commit
# Install prod
$ pip install -r requirements.txt
# Install both dev and prod
$ pip install -r requirements-dev.txt
Poetry
Poetry 通过支持一个文件中的分组来简化依赖项管理。这使你可以在一个地方跟踪所有依赖项。
$ poetry add numpy pandas
$ poetry add --group dev pytest pre-commit
# pyproject.toml
[tool.poetry.dependencies]
python = "³.8"
pandas = "².0"
numpy = "¹.24.3"
[tool.poetry.group.dev.dependencies]
pytest = "⁷.3.2"
pre-commit = "³.3.2"
要仅安装生产依赖项:
$ poetry install --only main
要安装开发和生产依赖项:
$ poetry install
更新环境
更新依赖项对于从 bug 修复、性能改进和新包版本中引入的新功能中受益是至关重要的。
Conda
Conda 允许你只更新指定的包。
$ conda install -c conda pandas
$ conda install -c anaconda scikit-learn
# New versions available
$ conda update pandas
$ conda update scikit-learn
之后,你需要手动更新 environment.yaml 文件,以保持与更新的依赖项同步。
$ conda env export > environment.yml
Pip
Pip 也只允许你更新指定的包,并要求你手动更新 requirements.txt 文件。
$ pip install -U pandas
$ pip freeze > requirements.txt
Poetry
使用 Poetry,你可以使用 update
命令来升级 pyproject.toml 文件中指定的所有包。此操作会自动更新 poetry.lock 文件,确保包规范和锁定文件之间的一致性。
$ poetry add pandas scikit-learn
# New verisons available
poetry update
Updating dependencies
Resolving dependencies... (0.3s)
Writing lock file
Package operations: 0 installs, 2 updates, 0 removals
• Updating pandas (2.0.0 -> 2.0.2)
• Updating scikit-learn (1.2.0 -> 1.2.2)
依赖解析
依赖冲突发生在项目所需的包或库具有冲突的版本或不兼容的依赖项时。正确解决冲突对于避免错误、运行时问题或项目失败至关重要。
Pip
pip 顺序地安装包,这意味着它会按照指定的顺序逐个安装每个包。这种顺序方式有时会导致当包具有不兼容的依赖项或版本要求时发生冲突。
例如,假设你首先安装 pandas==2.0.2
,它需要 numpy>=1.20.3
。后来,你使用 pip 安装 numpy==1.20.2
。尽管这会导致依赖冲突,但 pip 会继续更新 numpy 的版本。
$ pip install pandas==2.0.2
$ pip install numpy==1.22.2
Collecting numpy=1.20.2
Attempting uninstall: numpy
Found existing installation: numpy 1.24.3
Uninstalling numpy-1.24.3:
Successfully uninstalled numpy-1.24.3
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
pandas 2.0.2 requires numpy>=1.20.3; python_version < "3.10", but you have numpy 1.20.2 which is incompatible.
Successfully installed numpy-1.20.2
Conda
Conda 使用 SAT 求解器来探索所有包版本和依赖项的组合,以找到兼容的集合。
例如,如果一个现有包对其依赖项有特定的约束(例如,statsmodels==0.13.2 需要 numpy>=1.21.2,<2.0a0),而你要安装的包不符合该要求(例如,numpy<1.21.2),conda 不会立即引发错误。相反,它会仔细搜索所有所需包及其依赖项的兼容版本,只有在找不到合适的解决方案时才报告错误。
$ conda install 'statsmodels==0.13.2'
$ conda search 'statsmodels==0.13.2' --info
dependencies:
- numpy >=1.21.2,<2.0a0
- packaging >=21.3
- pandas >=1.0
- patsy >=0.5.2
- python >=3.9,<3.10.0a0
- scipy >=1.3
$ conda install 'numpy<1.21.2'
...
Package ca-certificates conflicts for:
python=3.8 -> openssl[version='>=1.1.1t,<1.1.2a'] -> ca-certificates
openssl -> ca-certificates
ca-certificates
cryptography -> openssl[version='>1.1.0,<3.1.0'] -> ca-certificates
Package idna conflicts for:
requests -> urllib3[version='>=1.21.1,<1.27'] -> idna[version='>=2.0.0']
requests -> idna[version='>=2.5,<3|>=2.5,<4']
idna
pooch -> requests -> idna[version='>=2.5,<3|>=2.5,<4']
urllib3 -> idna[version='>=2.0.0']
Package numexpr conflicts for:
statsmodels==0.13.2 -> pandas[version='>=1.0'] -> numexpr[version='>=2.7.0|>=2.7.1|>=2.7.3']
numexpr
pandas==1.5.3 -> numexpr[version='>=2.7.3']
Package patsy conflicts for:
statsmodels==0.13.2 -> patsy[version='>=0.5.2']
patsy
Package chardet conflicts for:
requests -> chardet[version='>=3.0.2,<4|>=3.0.2,<5']
pooch -> requests -> chardet[version='>=3.0.2,<4|>=3.0.2,<5']
Package python-dateutil conflicts for:
statsmodels==0.13.2 -> pandas[version='>=1.0'] -> python-dateutil[version='>=2.7.3|>=2.8.1']
python-dateutil
pandas==1.5.3 -> python-dateutil[version='>=2.8.1']
Package setuptools conflicts for:
numexpr -> setuptools
pip -> setuptools
wheel -> setuptools
setuptools
python=3.8 -> pip -> setuptools
pandas==1.5.3 -> numexpr[version='>=2.7.3'] -> setuptools
Package brotlipy conflicts for:
urllib3 -> brotlipy[version='>=0.6.0']
brotlipy
requests -> urllib3[version='>=1.21.1,<1.27'] -> brotlipy[version='>=0.6.0']
Package pytz conflicts for:
pytz
pandas==1.5.3 -> pytz[version='>=2020.1']
statsmodels==0.13.2 -> pandas[version='>=1.0'] -> pytz[version='>=2017.3|>=2020.1']
尽管这种方法提高了找到解决方案的机会,但在处理广泛环境时可能会计算密集。
诗歌
通过专注于项目的直接依赖,Poetry 的确定性解析器缩小了搜索范围,使得解析过程更为高效。它评估指定的约束条件,如版本范围或特定版本,并立即识别任何冲突。
$ poetry add 'seaborn==0.12.2'
$ poetry add 'matplotlib<3.1'
Because poetry shell depends on seaborn (0.12.2) which depends on matplotlib (>=3.1,<3.6.1 || >3.6.1), matplotlib is required.
So, because poetry shell depends on matplotlib (<3.1), version solving failed.
这种即时反馈有助于防止潜在问题升级,并允许开发者在开发过程早期解决问题。例如,在以下代码中,我们可以放宽对 seaborn 的要求,以便安装特定版本的 matplotlib:
poetry add 'seaborn<=0.12.2' 'matplotlib<3.1'
Package operations: 1 install, 2 updates, 4 removals
• Removing contourpy (1.0.7)
• Removing fonttools (4.40.0)
• Removing packaging (23.1)
• Removing pillow (9.5.0)
• Updating matplotlib (3.7.1 -> 3.0.3)
• Installing scipy (1.9.3)
• Updating seaborn (0.12.2 -> 0.11.2)
结论
总结而言,Poetry 相比 pip 和 conda 提供了几个优势:
-
广泛的软件包选择: Poetry 提供对 PyPI 上广泛软件包的访问,允许你利用多样的生态系统来支持你的项目。
-
高效的依赖管理: Poetry 只安装指定软件包所需的必要依赖,减少了环境中多余的软件包数量。
-
简化的软件包移除: Poetry 简化了软件包及其相关依赖的移除,使得维护一个干净高效的项目环境变得更加容易。
-
依赖解决: Poetry 的确定性解析器高效地解决依赖关系,及时识别和处理任何不一致或冲突。
虽然 Poetry 可能需要一些额外的时间和精力来让你的团队成员学习和适应,但长期来看,使用像 Poetry 这样的工具可以节省时间和精力。
我喜欢写关于数据科学概念的文章,并玩弄各种数据科学工具。你可以通过以下方式跟进我的最新帖子:
-
订阅我在Data Science Simplified上的新闻通讯。
R 中的泊松回归
原文:
towardsdatascience.com/poisson-regression-in-r-957752266a34
R 统计系列
·发布于 Towards Data Science ·6 分钟阅读·2023 年 2 月 10 日
–
图片由 Michael Dziedzic 提供,来源于 Unsplash
介绍
回归分析是一个广泛的领域。我们可以根据数据类型进行多种回归分析。我们在之前的文章中详细介绍了逻辑回归。在本文中,我将介绍泊松回归,并用 R 实现一个示例。
简要背景
线性回归可以用于数值数据,而逻辑回归则用于分类数据。我们可以对二分变量执行简单的二元逻辑回归,也可以进行多重逻辑回归。根据需求,我们可以选择部分比例赔率模型或广义回归模型。但在很多情况下,我们需要处理计数数据。例如,博物馆的访客数量可以通过调查收集,为了对这个计数响应变量建模,我们需要使用泊松回归。其他类型的例子包括医院的就诊次数或学生在特定时间内参加的数学课程数量。
泊松分布
一个计数响应变量的泊松分布表示为:
在这里,x = 计数变量,λ = 事件的平均数。在泊松分布中,事件的平均数等于该变量的方差。因此,λ = 方差(x)
数据集
作为此案例研究的数据来源,我们将使用UCI 机器学习库中的成人数据集。根据数据集,约 30000 人应根据他们的种族、教育、职业、性别、薪水、每周工作小时数以及每月收入等人口统计特征进行识别。
修改后的成人数据集
我们将使用以下变量来建模“vissci”变量,该变量表示在过去一年中访问科学或技术博物馆的次数
-
教育:数值型和连续型
-
婚姻状况:二元变量(0 表示未婚,1 表示已婚)
-
性别:二元变量(0 表示女性,1 表示男性)
-
家庭收入:二元变量(0 表示平均水平或低于平均水平,1 表示高于平均水平)
-
全职工作:二元变量(0 表示兼职,1 表示全职工作)
R 中的实现
实施过程与广义回归模型非常相似。在这里,我们将使用带有泊松家族的 glm()命令。在上面的代码片段中,我们定义了两个模型
model1:一个单预测变量模型。在这里,我们希望使用教育年限这一单一预测变量来建模 vissci。
model2:一个多预测变量模型。在这里,我们希望使用所有预测变量来建模 vissci。
结果的解释
模型 1 的摘要如下所示。
模型 1 摘要
它提供了类似的偏差残差统计数据,这些数据与线性回归模型非常相似,其中偏差是通过测量线性拟合线的偏差来计算的。回归模型的系数如下所示。由于这里只有教育变量,因此这里只显示一个系数。系数估计值为 0.13486,这意味着教育每增加一个单位,预期的科学博物馆访问次数的对数增加了 0.13486 倍。
还有一个术语叫做事件率比(IRR),它是用于测量独立变量每增加一个单位的发生率。这个 IRR 值可以通过系数的指数值获得。
模型 1 参数的 IRR
在这里,educ 的 IRR 值为 1.14437,这意味着教育变量每增加一个单位,预期的科学博物馆访问次数增加 14.437%。相关的 p 值为<0.05,这表明 educ 是一个重要的参数,可以用来预测科学博物馆的访问次数。
可以对多个独立变量进行类似的研究。接下来,我们希望包括剩余的变量来建模预期的科学博物馆访问次数,并确定它们是否有显著影响。模型 2 的摘要如下所示。
模型 2 摘要
乍一看,我们可以看到婚姻和全职工作状态不是确定科学博物馆访问次数的显著变量。对于性别,我们将 0 视为女性,将 1 视为男性。系数为 0.33612,这意味着性别增加一个单位(即女性到男性)时,预期到科学博物馆的访问次数的对数增加了 0.13486。对于家庭收入,估计值为 0.57499。
模型 2 参数的 IRR
观察 IRR 值,我们可以说性别在确定我们的因变量中起着重要作用。在性别变量增加一个单位(即女性到男性)的情况下,预期到科学博物馆的访问次数增加了 39.951%。这意味着男性比女性更可能访问科学博物馆。家庭收入的重要性更大。家庭收入变量增加一个单位(即低于平均水平到高于平均水平)的情况下,预期到科学博物馆的访问次数增加了 77.711%。这也意味着高收入家庭访问科学博物馆的次数多于低于平均收入的家庭。此外,如前所述,婚姻状况和全职工作状态在这里并不显著。
主要发现
教育年限、性别和家庭收入状态对于确定预期到科学博物馆的访问次数至关重要。
-
教育年限增加一个单位时,预期到科学博物馆的访问次数增加了 14.437%。
-
性别变量增加一个单位(即女性到男性)时,预期到科学博物馆的访问次数增加 39.951%。男性比女性更频繁地访问科学博物馆。
-
如果家庭收入增加到高于平均水平,预期到科学博物馆的访问次数增加 77.711%。
-
婚姻和工作状态在确定预期到科学博物馆的访问次数方面并不显著。
结论
我们已经涵盖了泊松分布的基本概念,并在 R 中实现了泊松回归模型。我们从 UCI 数据库中获取了一个数据集,并明确指出了一些预测变量对我们期望的因变量的影响。这种研究对于理解我们社会中的细微歧视是重要的。泊松回归在多个工程研究中也至关重要。
数据集的感谢
[## 通过我的推荐链接加入 Medium - Md Sohel Mahmood]
阅读 Md Sohel Mahmood 的每一个故事(以及 Medium 上成千上万的其他作者的故事)。您的会员费直接…
mdsohel-mahmood.medium.com [## 每当 Md Sohel Mahmood 发布文章时,您将收到电子邮件。
每当 Md Sohel Mahmood 发布文章时,您将收到电子邮件。通过注册,如果您尚未创建 Medium 账户,将会自动创建一个…
将你的分析团队定位到正确的项目上
“好的足球运动员会跟随足球——最好的足球运动员已经在球落地时就在那里”
·
关注 发表在 Towards Data Science · 5 分钟阅读 · 2023 年 12 月 23 日
–
你将要参与的项目对你团队的成功(以及你自己的成功)有着很大的影响。在某种程度上,这也是你每月/每季度/每天(取决于你的优先级排序流程)必须做出的最重要的决定之一。
然而——我从未真正遇到过适合我需求的分析世界的思维模型,用于帮助决定该参与哪些项目。因此,这就是我们将在本文中尝试的练习——制定一个思维模型来帮助做出更好的优先级决策。
项目优先级排序表(图片来源于作者)
任何项目都有其相关的风险。
正如我们在上一篇文章中讨论的那样,有多个宏观元素可以影响你的研究:数据可用性、技能水平、时间框架、组织准备情况和政治环境。
这些因素中的每一个都可能在你的研究中产生一些风险。如果你没有正确的数据,你很可能无法得到正确的答案。如果你没有合适的技能,或者你当前的技能与需要掌握的技能之间的差距太大,或者你的组织没有准备好实施任何结果,或者可能会有一些政治障碍阻碍实施——你的项目失败的风险就会更高。
尽管有许多不同和复杂的因素在起作用,但在你这边,你可以用简单的方法对潜在项目进行评分——使用 1-5 分评分系统(或 0-100%),基于你克服上述任何障碍的能力的信心。
影响有不同的规模和形式。
理解项目的影响力需要你正确理解你的分析项目将在实际中如何使用。在某种程度上,这是一种很好的强制因素,用来正确理解“需要完成的工作”以及你的工作如何被操作化。
在进行此项工作时,有几个方面需要考虑:
-
重要的是要考虑“第二层次”的影响。有些数据项目将会有“自身”的影响。其他的则会使其他人能够产生更大的影响(例如:仪表板)。还有一些将解锁之前“锁定”的数据分析(例如:数据管道)。
-
“价值的深度”并不是唯一需要考虑的标准——“宽度”也很重要。例如,构建一个仪表板可能会产生少量的价值,但面向大量用户——总的来说,这实际上是很多价值。
-
影响需要根据工作的时效性和需求来考虑。如果你的行业正在快速发展,或者公司战略有早期变化的迹象——在你的影响计算中考虑这些因素是很重要的。
同样地,使用 5 分制评分系统可以是对不同项目进行排序并理解哪些项目可能产生最高价值的简单方法。
你的时间有限。
时间是另一个重要的强制因素——因为要准确计算完成研究所需的时间,你必须对项目的范围有清晰的理解。这不仅仅是关于数据项目本身,而是关于所有能够使项目成功的因素:
-
确保在项目开始前,每个人对目标和交付物达成一致。
-
实际执行项目。
-
使其易于理解。
-
将其传达给你的受众。
-
在项目结束后达成对结果和行动项的共识。
请注意,项目进行到一半时总会有一个意外的挑战。这就是任何数据项目的魅力,它就像《福瑞斯特·冈普》中的巧克力盒——你永远不知道你会得到什么。考虑到这些意外的“惊喜”是很重要的,最好预留稍多的时间。如果你对所需时间毫无头绪,以下是一些建议:
-
与过去类似的项目进行基准比较
-
使用 群体智慧 并询问其他同行他们认为这样的项目需要多少时间。
-
进行一个 “思想实验”(但是真正的实验,而不是仅仅思考 2 分钟)。花些时间想象整个过程以及你需要经历的所有步骤来完成项目。从你的视觉化练习中,评估一下从开始到完成所需的时间。
综合考虑
现在你已经有了一个清晰的了解:
-
每个项目所需的时间
-
这些项目将带来的影响
-
每个项目相关的风险
你可以将所有这些信息综合起来,定义你的“投资理论”。
基本上,就像风险投资基金投资于初创公司一样——你决定将比金钱更宝贵的东西投资到哪里:你的时间。你可以选择(在合理范围内)如何平衡团队的投资组合:你对什么风险水平感到舒适?你是想选择几个冒险项目还是众多简单/经过验证的项目?你是否想加倍投入那些已经在过去显示出成功影响的项目?你决定。
我的个人投资理论:我总是尽量每季度选择一两个低信心/高回报的项目,同时还有众多较小/更容易/有保证价值的项目。
简而言之
把自己当作数据项目领域的风险投资家。你做出的每一个选择都是一种投资——不是金钱的投资,而是更宝贵的东西:你的时间和精力。
你面临着所有这些潜在项目争夺你的注意力,每个项目都有其自身的风险和回报。就像风险投资家一样,你需要选择那些承诺能带来最佳回报的项目。这意味着有时需要选择那些低信心、高回报的长期项目,这些项目可能会真正带来收益。其他时候,则是积累那些较小的成功,确保稳定的价值和进展。
最终,你负责定义自己的投资组合——并且主动承担这一活动非常重要,因为如果你不这样做,别人会。
使用 Python 和 Linux 的后量子密码学
初学者指南
·
关注 发表在 Towards Data Science ·9 分钟阅读·2023 年 8 月 15 日
–
图片由 Jean-Louis Paulin 提供,来源于 Unsplash
如果我们相信爱德华·斯诺登,密码学是“对抗监视的唯一真正保护” [1]。然而,量子技术的进步可能会危及这一保障。我们的文章讨论了量子计算为什么对数据安全构成威胁以及如何应对。我们不是进行纯理论分析,而是通过使用 Python、C 和 Linux 的代码示例来展开讨论。
量子基础
当谷歌科学家在 2019 年报告首次实现量子supremacy时,引发了极大的兴奋。量子计算可能对加密产生重大影响。要理解这个问题,我们需要讨论一些基本概念。
与经典计算机不同,量子计算机的算法不依赖于位(bits),而是依赖于量子位(qubits)。一个位只能取状态 0 或 1。当我们多次测量一个位时,总是得到相同的结果。量子位则不同。尽管听起来很奇怪,但一个量子位可以同时取 0 和 1 的值。当我们重复测量时,只能得到 0 或 1 的某种概率。在量子位的初始状态下,测量为 0 的概率通常是百分之百。然而,通过叠加,不同的概率分布可以生成。这些原因源于量子力学,遵循与“正常”生活不同的规律。
量子计算机的主要优势在于其概率特性。经典计算机在我们需要可靠的单一结果时表现出色,而量子计算机则擅长处理概率和组合问题。当我们对处于叠加状态的量子位执行操作时,它同时作用于值 0 和 1。随着量子位数量的增加,量子计算机相对于经典计算机的优势也会增加。一个具有三量子位的量子计算机可以同时处理最多八个值(2³):即二进制数 000、001、010、011、100、101、110 和 111。
科学文献一致认为,量子计算机将有助于解决以前看似难以处理的问题。然而,目前没有理想的量子计算机。当前一代量子计算机被称为噪声中等规模量子(NISQ)。这种机器处理能力有限,对错误很敏感。现代设备提供最多几百个量子位。一个例子是 IBM 在 2022 年推出的 433 量子位Osprey芯片。现在,该公司计划到 2033 年开发一台具有 100,000 量子位的机器。
我们的文章解释了为什么这一进展对数据安全构成威胁。通过代码示例,我们展示了量子计算机如何破解某些加密方法,并讨论了应对策略。源代码可以在GitHub上找到。它是在 Kali Linux 2023.2 下使用 Python 3.10 的 Anaconda 开发的。
加密与素因数
在加密消息时,相对简单的方法是应用对称算法。这种方法使用相同的密钥进行明文的加密和密文的解密。这个方法的主要挑战是安全地交换发件人和收件人之间的密钥。一旦私钥被第三方知道,他们就有机会拦截并解密消息。
非对称密码学似乎是解决这个问题的方案。像RSA这样的算法使用不同的密钥进行加密和解密。在这里,加密是使用一个或多个公开密钥,收件人将其提供给所有人。解密时,收件人使用仅自己知道的私钥。这样,发件人可以在没有风险的情况下获得公开密钥,因为它本身并不保密。只有收件人的私钥必须被保护。但是,当潜在攻击者知道公开密钥时,如何加固这样的程序?为此,非对称算法依赖于像质因数分解这样的数学问题。
质因数分解通过示例最好地理解。在 Python 中,我们可以使用库SymPy的factorint
函数来确定某个整数的质因数。
>>> import sympy
>>> sympy.factorint(10)
{2: 1, 5: 1}
>>> 2**1 * 5**1
10
>>> sympy.factorint(1000)
{2: 3, 5: 3}
>>> 2**3 * 5**3
1000
>>> sympy.factorint(55557)
{3: 2, 6173: 1}
>>> 3**2 * 6173**1
55557
>>>
上述控制台输出说明了每个自然数都可以表示为质数的乘积。这些被称为质因数。回想一下学校的日子,质数只能被 1 和自身整除。例如,数字 10 可以用术语 10=2¹ * 5¹表示。因此,10 的质因数是 2 和 5。类似地,数字 55557 可以用方程 55557=3² * 6173¹表示。所以,55557 的质因数是 3 和 6173。找到给定整数的质因数的过程称为质因数分解。
对于经典计算机,质因数分解对于小数字来说很简单,但对于大整数则变得越来越困难。每增加一个数字都会大幅增加可能组合的总和。超过某个点后,经典计算机几乎无法确定质因数。例如,考虑以下来自 RSA 因数分解挑战的数字(RSA-260),该挑战于 2007 年结束。在撰写时,它尚未被因数分解。
#!/usr/bin/env python
import sympy
rsa_260 = 22112825529529666435281085255026230927612089502470015394413748319128822941402001986512729726569746599085900330031400051170742204560859276357953757185954298838958709229238491006703034124620545784566413664540684214361293017694020846391065875914794251435144458199
print("Start factoring...")
factors = sympy.factorint(rsa_260)
# Will probably not be reached
print(factors)
像 RSA 这样的非对称算法利用质因数分解和类似问题的计算难度来确保加密。不幸的是,量子世界遵循自己的规律。
量子算法
关于密码学,有两个量子算法尤其值得关注。Shor 算法提供了一种高效的质因数分解方法。在大型量子设备上运行时,它理论上可以破解像 RSA 这样的非对称加密方法。从实际角度来看,这种情况仍在未来。一篇 2023 年的《自然》文章提到至少需要 1,000,000 个量子比特。撇开硬件不谈,找到能够在大型量子计算机上可靠扩展的算法实现也很困难。IBM 的框架Qiskit曾尝试实现这一功能,但在版本 0.22.0 时弃用了。不过,网上可以找到 Shor 算法的实验性实现。
Grover 算法对对称加密构成威胁。也称为量子搜索算法,它为对给定函数的输入进行无结构搜索提供了加速。量子计算机可以利用它加速对对称加密信息的暴力攻击。然而,与 Shor 算法不同的是,所提供的加速不是指数级的。简单来说,这意味着增加加密密钥的长度会使搜索变得极其昂贵。例如,对 128 位密钥进行暴力攻击需要最多 2¹²⁸ 次迭代。假设 Grover 的搜索将这个数字减少到 2⁶⁴,那么将密钥长度加倍到 256 位会再次增加到 2¹²⁸ 次迭代。这为可能的解决方案打开了大门。
对称加密解决方案
在某些条件下,对称加密是一种现成的、简单的方法来应对量子算法。原因在于 Grover 的搜索并不会指数级扩展,而 Shor 算法只威胁到非对称方法。根据当前的知识,高度复杂的对称算法可以被视为量子抗性。现在,美国国家标准与技术研究所(NIST)以及德国联邦信息安全局(BSI)都将AES-256纳入这一类别[2][3]。AES 是高级加密标准(Advanced Encryption Standard)的缩写,而数字 256 代表密钥的位长。在 Linux 下,AES-256 由 GNU 隐私保护工具 (GnuPG) 实现。下面的 shell 脚本展示了如何使用 AES-256 对文件进行加密和解密。
# Encrypt
gpg --output encrypted.gpg --symmetric --cipher-algo AES256 plain.txt
# Decrypt
gpg --output decrypted.txt --decrypt encrypted.gpg
上述脚本加密了文件“plain.txt”的内容,将密文写入“encrypted.gpg”文档,再次解密它,最后将输出保存到文件“decrypted.txt”。在加密之前,GnuPG 会要求输入密码短语以生成私钥。出于安全原因,选择一个强密码短语并保密至关重要。GnuPG 可能会缓存密码短语,并在解密时不再询问。要清除缓存,可以执行以下 shell 命令。
gpg-connect-agent reloadagent /bye
将 GnuPG 集成到 Python 中使用 subprocess
模块相对简单。下面的代码片段展示了使用 AES-256 的加密原型实现。
#!/usr/bin/env python
import subprocess
import getpass
# Read passphrase
passphrase = getpass.getpass("Passphrase:")
passphrase2 = getpass.getpass("Passphrase:")
if passphrase != passphrase2:
raise ValueError("Passphrases not identical!")
# Perform encryption
print("Encrypting...")
args = [
"gpg",
"--batch",
"--passphrase-fd", "0",
"--output", "encrypted.gpg",
"--symmetric",
"--yes",
"--cipher-algo", "AES256",
"plain.txt",
]
result = subprocess.run(
args, input=passphrase.encode(),
capture_output=True)
if result.returncode != 0:
raise ValueError(result.stderr)
为了获取密码短语,上述脚本使用 getpass
模块。确认后,密码短语通过标准输入传递给 GnuPG。这由参数 passphrase-fd 0
指示。或者,密码短语可以作为字符串或通过文件通过命令行参数发送给 GnuPG。然而,由于这些参数对其他用户可见,因此这两种选项在原型中被拒绝了。另一种更安全的方式是使用 GPG-Agent。选择哪种选项取决于所需的安全级别。包括加密和解密在内的概念验证可以在 这里 找到。作为 GnuPG 的替代方案,还有其他 AES-256 实现。在这里选择一个可信的来源至关重要。
非对称解决方法
寻找非对称解决方案时,NIST 后量子密码学标准化 计划 是一个很好的起点。自 2016 年以来,它评估了多个抗量子算法的候选者。获胜者之一是 Kyber。该系统实现了一种所谓的安全密钥封装机制。与其他算法类似,Kyber 依赖于一个难以解决的问题来保护两个方之间的密钥交换。与素因数分解不同,它基于一个称为“带错误学习”的问题。Kyber 提供的保护级别取决于密钥长度。例如,Kyber-1024 旨在提供“与 AES-256 大致相当”的安全级别 [4]。
用 C 语言编写的 Kyber 参考实现可在 GitHub 上找到。在 Linux 下,我们可以通过执行以下 shell 命令来克隆和构建框架。安装需要一些先决条件,这些条件在项目的 README 中有记录。
git clone https://github.com/pq-crystals/kyber.git
cd kyber/ref && make
将参考实现集成到 Python 中有几种方法。其中一种是编写一个 C 程序并调用它。下面的 C 函数使用 Kyber 在两个虚构的实体 Alice 和 Bob 之间进行密钥交换。完整源代码请参见 这里。
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "kem.h"
#include "randombytes.h"
void round_trip(void) {
uint8_t pk[CRYPTO_PUBLICKEYBYTES];
uint8_t sk[CRYPTO_SECRETKEYBYTES];
uint8_t ct[CRYPTO_CIPHERTEXTBYTES];
uint8_t key_a[CRYPTO_BYTES];
uint8_t key_b[CRYPTO_BYTES];
//Alice generates a public key
crypto_kem_keypair(pk, sk);
print_key("Alice' public key", pk);
//Bob derives a secret key and creates a response
crypto_kem_enc(ct, key_b, pk);
print_key("Bob's shared key", key_b);
print_key("Bob's response key", ct);
//Alice uses Bobs response to get her shared key
crypto_kem_dec(key_a, ct, sk);
print_key("Alice' shared key", key_a);
}
不深入细节,可以看出 Kyber 使用了多个公钥和私钥。在上述示例中,Alice 生成了一个公钥 (pk) 和一个私钥 (sk)。接下来,Bob 使用公钥 (pk) 推导出共享密钥 (key_b) 和响应密钥 (ct)。后者被返回给 Alice。最后,Alice 使用响应密钥 (ct) 和她的私钥 (sk) 生成共享密钥的实例 (key_a)。只要双方保持私钥和共享密钥的机密性,该算法就能提供保护。在运行程序时,我们会得到类似于下面的文本输出。
Alice' public key: F0476B9B5867DD226588..
Bob's shared key: ADC41F30B665B1487A51..
Bob's response key: 9329C7951AF80028F42E..
Alice' shared key: ADC41F30B665B1487A51..
为了在 Python 中调用 C 函数,我们可以使用 subprocess
模块。或者,可以构建一个共享库,并使用 ctypes
模块进行调用。第二种方法在下面的 Python 脚本中实现。在加载从 Kyber C 代码生成的共享库后,过程 round_trip
会像其他 Python 函数一样被调用。
#!/usr/bin/env python
import os
import ctypes
# Load shared library
libname = f"{os.getcwd()}/execute_round_trip1024.so"
clib = ctypes.CDLL(libname, mode=1)
print("Shared lib loaded successfully:")
print(clib)
# Call round trip function
print("Executing round trip:")
clib.round_trip()
除了 Kyber 的参考实现外,其他提供商也实现了该算法。例如,开源项目 Botan 和 Open Quantum Safe。
结论
我们的分析显示,量子技术仍处于早期阶段。但我们不应低估它对加密和其他密码学方法(如签名)构成的威胁。颠覆性创新随时可能推动发展。攻击者现在可以存储消息,稍后再解密。因此,应立即采取安全措施。尤其是,因为有可用的解决方法。正确使用时,像 AES-256 这样的对称算法被认为是量子抗性的。此外,像 Kyber 这样的非对称解决方案也在进展中。使用哪些替代方案取决于应用场景。遵循零信任模型,组合多种方法能提供最佳保护。这样,量子威胁可能会像 Y2K 问题一样,成为一种自我实现的预言。
关于作者
Christian Koch 是 BWI GmbH 的企业架构师,并且是纽伦堡技术学院 Georg Simon Ohm 的讲师。
Lucie Kogelheide 是 BWI GmbH 的后量子密码学技术主管,负责启动公司向量子安全密码学的迁移过程。
Raphael Lorenz 是 Lorenz Systems 的创始人兼首席信息安全官,专注于整体安全解决方案。
参考文献
-
Snowden, Edward: 永久记录。Macmillan, 2019。
-
国家标准与技术研究院: NIST 后量子密码学:常见问题. 2023 年 6 月 29 日。访问日期:2023 年 8 月 2 日。
-
联邦信息安全办公室 (BSI): 量子安全密码学——基础知识、当前进展和建议 (PDF)。2021 年 10 月。访问日期:2023 年 8 月 2 日。
-
CRYSTALS — 代数格加密套件:Kyber Home. 2020 年 12 月。访问时间:2023 年 8 月 2 日。
免责声明
请注意,信息安全是一个关键话题,作者对发布的内容不提供任何担保。
数据驱动讲故事中的上下文力量
原文:
towardsdatascience.com/power-of-context-in-data-driven-storytelling-b4dc48a402e
要么正确开始,要么立刻放弃
·发表于 Towards Data Science ·阅读时间 12 分钟·2023 年 9 月 5 日
–
什么是数据驱动讲故事,它为何如此重要?
数据驱动讲故事是一种通过叙述和可视化来传达数据信息的方式。其目标是吸引观众,并帮助他们更好地理解主要结论和趋势。
数据可视化有助于吸引观众。仅仅展示原始事实或信息是不够的。以一种能引起观众注意并且令人愉悦的方式展示数据是很重要的。
叙事 就是故事本身。叙事位于数据驱动故事的核心,帮助创造一个连贯的、有意义的信息。
上下文是数据驱动讲故事中的一个关键组成部分,在我看来,占据了故事成功的 80%。叙事和视觉元素占据了剩下的 20%。
今天,我们拥有丰富的数据、工具和方法。然而,即使有了这些资源,将它们付诸实践仍然是相当具有挑战性的。讲故事可以弥合这个差距。今天公司的决策者、董事会成员、总监和经理们,不应担心数据清理、分析技术或具体工具。对他们而言,重要的是解释、建议及其潜在影响。他们希望能够提出问题、提供反馈并理解答案。他们都需要数据驱动的讲故事!
但如果我们不认识到上下文,数据讲故事必然会失败!
故事的上下文有三个基本维度:情境、功能和数据。
本质上,上下文 指的是故事双方(即讲述者和观众)都需要理解的背景信息。它回答三个主要问题:谁,什么,和 如何?
谁?
这关乎于确定故事的受众是谁,并理解讲述者与听众之间的关系。
什么?
这关乎于故事的主要主题以及你希望受众在听完后做出什么决定或行动。
怎么做?
这关乎于选择正确的数据和分析工具,并决定如何将发现与受众分享。
如果我们没有做好这些功课,会发生什么?
哦,很多。我给你举几个例子。
切斯托霍瓦防守
切斯托霍瓦是波兰南部的一座城市,拥有一座可以追溯到 15 世纪的著名大教堂。它有一幅受人尊敬的圣母像,对许多波兰人来说极为神圣。这座城市还因其在 1655 年战胜强大的瑞典军队而闻名,被誉为勇气和抵抗的经典例证。
切斯托霍瓦防守,source。
当波兰足球队面对强大的对手并必须防守自己的球门(这种情况经常发生)时,我们波兰人开玩笑地称之为“防守切斯托霍瓦”。我们希望他们能像 17 世纪的防御者那样获胜,但这通常只是愿望罢了。
不幸的是,当我们不认识或不关心上下文时,这也是我们故事中相当常见的情形。大多数关于数据的批评(无论是否有效)都源于模糊性或不一致性。当数据不清晰或自相矛盾时,人们更容易质疑其有效性。而且,事实证明,捍卫我们的立场会更加困难。
回到我的切斯托霍瓦例子,那里(只有)一个对手。假设有更多的对手。两个、三个、更多,所有的对手!这时我们就陷入了
问题的交火
问题的交火。来源:由作者在 DALL-E 2 生成的图像
当有影响力或权威人士提出疑虑或担忧时,这种轰炸会显著加剧。一旦种下了怀疑的种子,其他人可能会迅速跟进,提出问题或放大初步的担忧。
这里的危险有两方面。首先,这些疑虑和担忧很容易遮蔽我们的故事和支持分析。其次,当面临无情的提问,特别是如果没有准备好,就有可能显得不够可信或知识浅薄,即使数据和故事最初是扎实且经过良好研究的。
我们自己也可能会对这种混乱作出贡献。尤其是如果我们在某些时候,可能无意中做了一些事,这会…
牵连某人
这很经典。确实,不太好。
来源:由作者在 DALL-E 2 生成的图像
在展示数据驱动的洞察时,有可能无意中让观众中的某个人处于不舒服的境地,特别是当数据揭示敏感或意外的结果时。这可能导致紧张局势并打乱讨论的整体流程。这种不适可能源于挑战现有信念的数据,暗示意外的后果,或突显某些特定领域或个人。
这些情况可能会偏离主要的数据洞察,并破坏预期的叙事。个体可能会感到被针对或防御,特别是当数据似乎在批评他们的工作或决策时。此外,其他观众成员可能会小心翼翼,因为他们担心后续的数据点可能会突显他们的领域。以同理心和策略来展示数据至关重要,以维持建设性的环境。
好的。现在我们知道了可能出现的问题。让我们看看如何避免或摆脱困难情况。
情境背景
准备的威力
在制作以数据为驱动的演示或沟通时,设定合适的背景并了解受众的动态至关重要,无论他们是同事、上级还是下属。预测可能的异议并确保数据的清晰性非常关键。与利益相关者进行预先的数据验证会议尤其重要,特别是在使用不熟悉的工具或数据集时。决定是进行详细的演示还是简短的沟通(如电子邮件)更为适宜。为沟通设定明确的目标,并始终准备好主要信息,以应对有限的注意力跨度。
考虑进行利益相关者访谈、调查、直接观察和焦点小组,以获得全面的理解。技术上的不确定性?咨询领域专家。社交媒体也可以提供公众情绪和潜在受众观点的见解。
动力是关键
在我一次关于讲故事的演示中,我构建了一个非凡的结构(在我主观看来)。我们都同意生活中的故事能够引起情感共鸣,对吧?我们认同英雄,憎恨邪恶角色,经历恐惧和快乐。这些都是情感。但是如何在企业生活中激发这些情感呢?嗯,通过动机。你看到了连接了吗?
企业生活中的动机实际上有两个方面:
-
我们让利益相关者高兴或
-
我们让他们不高兴。
当他们看到良好的结果、项目进展或战略的首次积极效果时,他们会感到高兴。如果他们看到相反的情况,他们会感到不高兴。或者他们可能会感到受到威胁:一个邪恶角色。
来源:作者在 DALL-E 2 生成的图片
在叙事中,三个常见的对手塑造了叙事的框架。第一个是竞争。这种外部挑战迫使企业或个人进行创新并保持领先。接着是全球不确定性。后 COVID 时代的持续担忧和 2022 年乌克兰战争的影响示例了此类事件如何造成广泛的忧虑。这些不可预测的情况通常需要对计划或策略进行调整。最后是内部斗争。像过时的技术这样的问题看似微不足道,但随着时间的推移可能会导致更严重的问题,从而使应对外部挑战变得更加困难。这些元素共同构成了我们故事中的核心障碍,为克服逆境和成长奠定了基础。
情景分析可以像一个“假如”游戏,帮助企业在面对坏角色时进行导航。对于竞争,它帮助我们预测对手的下一步动作并做好准备。它帮助我们思考不同的结果,并为像 COVID 或冲突这样的意外事件提前制定计划(至少在某种程度上)。对于我们的问题,比如旧技术,它展示了修复这些问题的好处或忽视它们的风险。这是一个帮助企业为不同情况做好准备的工具。
应对挑战情境
“切斯托霍瓦防御”陷阱强调了预先采取行动的必要性。数据必须以透明且连贯的方式呈现。不一致或模糊的可视化可能会损害数据的完整性,导致怀疑。强大的数据管理实践和基于数据科学原则的精确可视化技术对增强数据的可信度至关重要。必须精心准备,预见潜在挑战,并保持一致的数据驱动叙事。数据熟练的同事的审查可以发现分析中的潜在陷阱或偏见。在展示可能敏感或有争议的发现时,调整对观众反应的敏感性至关重要。迅速回应关切,提供数据支持的澄清,并在话题变得有争议时提议后续讨论。在动态的数据科学领域,营造一个开放的、基于证据的沟通环境可以确保每个人都感受到被重视,并做出数据驱动的决策。
功能性背景
上下文的功能性方面,尤其是在考虑数据和数据科学时,本质上更具“实际性”。这一务实维度强调将原始数据转化为可操作的洞察,这些洞察与利益相关者产生共鸣,并对业务决策产生直接影响。虽然更广泛的视角提供了总体概述,但功能性上下文确保数据驱动的叙事不仅仅是信息性的,而是真正有影响力、有意义的,并且与业务目标紧密对齐。这是一个不可忽视的元素。
功能性上下文是关于使数据对现实世界决策有用和相关。 它将原始数据转化为可以帮助业务的洞察。情境上下文,另一方面,是关于设定场景。 它回答了谁应该关心数据,主要信息是什么,以及如何分享。将功能性上下文视为数据故事的“实质”,而情境上下文则是故事发生的“背景”。这两者都很重要,以确保数据故事既清晰又有影响力。
数据素养
一个重要的方面是数据素养,它有助于保持功能性上下文的连贯性。
数据素养就像是能够阅读和理解数字背后的深层故事。它是关于真正理解数字所传达的信息,并确保我们分享真实且重要的信息。这项技能有助于将复杂的数据转化为人们可以轻松联系的简单故事。
当你具备数据素养时,你可以识别趋势、关系或异常值,并确保数据质量良好。这使得你用数据讲述的故事既有趣又值得信赖。简而言之,就是将数字转化为每个人都能理解并相信的清晰故事。
相关性
相关性是引人入胜的叙事的基石,尤其是如果根植于数据科学的话。这是关于确保你展示的问题、概念或机会直接影响业务。虽然不一定需要是有形的,但效果越显著,你的故事就会越有影响力。
在制定叙事时,确定与你的利益相关者产生共鸣的关键绩效指标(KPIs)和关键结果指标(KRIs)至关重要。理解这些通常并不困难。也许你的组织使用平衡计分卡,这是一个突出目标如何贯穿整个公司的工具,每个目标都有相关的指标。或者,即使没有确切的数字,了解影响某人激励措施的指标也可以增强你故事的影响力。
理解你公司的价值创造链至关重要。这将数据素养与相关性连接起来。例如,你可以向利益相关者展示特定的数据驱动方法如何提升客户旅程的效率。请看下面的图示。 教育你的听众关于特定数据分析工具在优化关键业务流程中的好处是一项值得优先考虑的宝贵投资。
来源:由作者基于[1]的灵感制作的图像。
情节要点
故事的结构和流程赋予其形式,但情节要点使其独特。这些是塑造每个场景的关键细节。它们让观众能够对呈现的数据的重要性做出自己的评估。它们帮助观众可视化预期的背景。以下是九个需要考虑的关键情节要点:
-
趋势变化: 观察趋势是否在上升或下降以及其进展。例如,即使在投资了安全措施后,生产线上的事故数量可能增加。
-
依赖性: 显示两个事物之间的关系。例如,更高的净推荐值(NPS)可能与更多的客户留存相关联。
-
交集: 这涉及一个变量超越另一个变量。它可以是正面的,比如初创企业的收入超过成本;也可以是负面的,比如一个产品的销售低于竞争对手。
-
预测: 预测未来。例如,一个国家的人口可能因移民和其他人口变化而发生的变化。
-
比较: 指出两个或多个项目之间的相似性或差异性。这可以是比较一台旧机器的效率与我们考虑购买的新机器。这在商业故事中经常使用。
-
深入分析: 将一般数据拆分为详细的细分。你可能在仪表板上看到整体区域结果,然后深入到子区域或单个商店。
-
聚合(缩小视图): 与深入分析相反。例如,我们将一个商店的结果与区域或全国平均水平进行比较。
-
聚类分析: 揭示数据集中存在的集中或分布情况。显著的聚类可能表明一个机会或问题。例如,一个诊所中最贵的病人可能都居住在特定工厂附近。
-
离群值: 这些是从其他数据点中突出的数据点。离群值可能表示问题或机会,具体取决于背景。例如,某个特定产品的购买频率可能远高于其类别中的其他产品[2]。
来源:由作者基于[2]制作的图像。
数据背景
数据背景指的是附加在原始数据旁的额外信息或“元数据”,以使数据更易于理解和有价值。 这些元数据本质上就像一张路线图,提供重要细节,比如谁收集了数据、何时何地收集的以及收集数据的初衷。了解背景有助于更准确地解读数据并进行有效的决策。
带有背景的元数据 😃。图片由作者提供。
在企业环境中,管理这些元数据需要各个角色的合作:
-
数据生产者:创建数据的个人或系统。他们负责确保在数据生成或收集时,元数据的准确性和完整性。
-
数据使用者:依赖数据进行各种任务的最终用户,如分析、报告或决策。他们需要元数据来正确解读数据,并信任其有效性。
-
元数据管理者:专门负责管理元数据的人员或系统。他们确保元数据以有序的方式存储,并且数据使用者能够方便地访问这些信息。
正确管理元数据的重要性不容忽视,尤其是在一个日益数据驱动的世界中。没有这些背景信息,数据很容易被误解或误用,导致错误的结论和不良的决策。
技术进步促使了更具动态性的元数据管理方法,包括“主动元数据管理”。在这种方法中,元数据不仅仅是静态地存储,而是不断更新和在不同系统间同步。这使得数据的无缝集成和交叉引用成为可能,提供了更全面和最新的视图。主动元数据管理在数据不断更新且需要快速、准确的解读以进行实时决策的环境中至关重要 [3]。
引用数据来源
关于背景还有最后一点值得提及,那就是引用数据来源。当你在工作中使用数据时,就像从别人那里借书一样。正如你会感谢借书的人,你也应该给予提供数据的人员应有的信用。这不仅是对他人的尊重,还帮助其他人追溯原始数据。如果他们想查看数据本身,这是双赢的:数据的提供者得到认可,使用数据的人可以展示他们的工作基于可靠的信息。为了给予信用,提到数据的创造者、数据生成时间以及数据的名称 [4]。在我看来,适当地给予信用尤其在我们之前讨论的前三种情况中可能会起到救命作用。如果数据来源于知名或可信的地方,那就更好了。
结论
用数据讲故事不仅仅是关于数字。 这涉及到用视觉效果和好的故事使这些数字易于理解。关键是背景: 了解情况,使数据有用,并了解数据来源。想象一下讲解一场著名的战役:如果你没有正确设定背景,人们就无法理解。讲好一个故事,你需要了解你的听众,选择合适的主题,并准备回答任何问题。这也是与人们的情感连接,尤其是在商业中。擅长数据意味着你的故事既令人兴奋又可信。 了解数据对公司为何重要以及发现数据中的有趣点使故事更丰富。在充满数据的世界中,适当的背景至关重要。理解数据的来源和其可信度对做出明智决策至关重要。
作者的主观评估
你喜欢这篇文章吗?考虑订阅以获取我发布的新故事通知,或关注我。
参考文献
[1] 艾薇·刘,数据策略中的连接点,2022 年 1 月 2 日
[2] 布伦特·戴克斯,《有效的数据讲述》,Wiley,2019
[3] 彼得·克罗克,增强数据背景的指南:谁,什么,何时,哪里,为什么,以及如何, 2023 年 8 月 11 日
[4] 哥伦比亚大学,引用数据来源——为什么这样做是好的,以及如何做到?
实用的预算优化方法在营销组合建模中的应用
如何使用饱和曲线和统计模型优化媒体组合
·
关注 发表在 Towards Data Science · 9 分钟阅读 · 2023 年 2 月 28 日
–
图片由 Joel Filipe 提供,来源于 Unsplash
市场营销组合建模(MMM)是一种数据驱动的方法,用于识别和分析业务结果(如销售或收入)的关键驱动因素,通过检验各种因素对响应的影响。MMM 的目标是提供有关如何优化营销活动(包括广告、定价和促销)以改善业务表现的见解。在所有影响业务结果的因素中,营销贡献(例如各种媒体渠道的广告支出)被认为对响应有直接和可衡量的影响。通过分析不同媒体渠道广告支出的效果,MMM 可以提供有价值的见解,帮助确定哪些渠道最有效于增加销售或收入,以及哪些渠道可能需要优化或淘汰,以最大化营销投资回报。
对 MMM 的简短介绍
市场营销组合建模(MMM)是一个多步骤的过程,涉及一系列独特的步骤,这些步骤由正在分析的营销效果驱动。首先,将媒体渠道的系数限制为正值,以考虑广告活动的正面效应。
如何在 Python 中使用 RPy2 接口拟合 SciPy 线性回归并调用 R 岭回归
[towardsdatascience.com
其次,应用广告库存转换,以捕捉广告对消费者行为的滞后和衰减影响。
实验先验、数据归一化,并将贝叶斯建模与 Robyn(Facebook 的开源 MMM)进行比较…
[towardsdatascience.com
第三,广告支出与相应业务结果之间的关系不是线性的,而是遵循递减效应法则。在大多数 MMM 解决方案中,建模者通常采用线性回归来训练模型,这带来了两个主要挑战。首先,建模者必须应用饱和转换步骤来建立媒体活动变量与响应变量之间的非线性关系。其次,建模者必须制定关于适用于每个媒体渠道的可能转换函数的假设。然而,更复杂的机器学习模型可能在不应用饱和转换的情况下捕捉非线性关系。
捕捉非线性广告饱和度和递减回报,而无需显式转换媒体变量
使用平滑样条建模营销组合 [## 使用机器学习方法改善营销组合建模
使用基于树的集成方法构建 MMM 模型,并使用 SHAP(Shapley 加性解释)解释媒体渠道表现
最后一步是通过估计系数以及广告库存和饱和函数的参数来构建营销组合模型。
预算优化
饱和曲线和经过训练的模型都可以用于营销组合建模,以优化预算支出。使用饱和曲线的优势包括:
-
简化可视化支出对结果的影响
-
不再需要基础模型,因此预算优化程序简化,仅需饱和转换的参数
其中一个缺点是饱和曲线基于历史数据,可能无法始终准确预测未来支出的响应。
使用训练模型进行预算优化的优势在于,模型使用媒体活动与其他变量(包括趋势和季节性)之间的复杂关系,可以更好地捕捉递减回报。
数据
我继续使用Robyn提供的 MIT 许可证数据集进行实际示例,并按照相同的数据准备步骤应用 Prophet 来分解趋势、季节性和假期。
数据集包括 208 周的收入(从 2015 年 11 月 23 日到 2019 年 11 月 11 日),包含:
-
5 个媒体支出渠道:tv_S, ooh_S, print_S, facebook_S, search_S
-
2 个也包含曝光信息(印象,点击)的媒体渠道:facebook_I, search_clicks_P(本文未使用)
-
无支出的有机媒体:新闻通讯
-
控制变量:事件,假期,竞争对手销售 (competitor_sales_B)
建模
我构建了一个完整的工作 MMM 流程,可以在现实生活中用于分析媒体支出对响应变量的影响,包含以下组件:
-
广告库存转换具有无限衰减率(0 < α < 1)
-
饱和 Hill 变换 具有两个参数:斜率/形状参数(控制曲线的陡峭度(s > 0))和半饱和点(0 < k ≤ 1)
-
来自 scikit-learn 的岭回归
关于系数的说明
在 scikit-learn 中,岭回归不提供设置部分系数为正值的选项。然而,一个可能的解决方法是,如果某些媒体系数为负值,则拒绝 optuna 解决方案。这可以通过返回一个非常大的值来实现,表明负系数是不可接受的,必须排除。
关于饱和变换的说明
Hill 饱和函数假设输入变量在 0 到 1 的范围内,这意味着在应用变换之前必须对输入变量进行归一化。这一点很重要,因为 Hill 函数假设输入变量的最大值为 1。
然而,可以通过使用以下方程式将半饱和参数缩放到支出范围,从而将 Hill 变换应用于未归一化的数据:
half_saturation_unscaled = half_saturation * (spend_max - spend_min) + spend_min
其中 half_saturation 是在 0 和 1 之间的原始半饱和参数,spend_min 和 spend_max 分别表示最小和最大支出值。
完整的变换函数如下:
class HillSaturation(BaseEstimator, TransformerMixin):
def __init__(self, slope_s, half_saturation_k):
self.slope_s = slope_s
self.half_saturation_k = half_saturation_k
def fit(self, X, y=None):
return self
def transform(self, X: np.ndarray, x_point = None):
self.half_saturation_k_transformed = self.half_saturation_k * (np.max(X) - np.min(X)) + np.min(X)
if x_point is None:
return (1 + self.half_saturation_k_transformed**self.slope_s / X**self.slope_s)**-1
#calculate y at x_point
return (1 + self.half_saturation_k_transformed**self.slope_s / x_point**self.slope_s)**-1
使用饱和曲线进行预算优化
一旦模型训练完成,我们可以使用通过 Hill 饱和变换生成的响应曲线来可视化媒体支出对响应变量的影响。下图展示了五个媒体渠道的响应曲线,描述了每个渠道的支出(按周)与 208 周期间响应之间的关系。
作者提供的图片
使用饱和曲线优化预算涉及确定每个媒体渠道的最佳支出,以在保持总预算固定的情况下实现最高的整体响应。
为了启动优化,通常使用特定时间段的平均支出作为基准。然后,优化器使用每个渠道的预算,这些预算可以在预定的最小和最大限度(边界)内波动,以进行受限优化。
以下代码片段演示了如何使用minimize函数(来自scipy.optimize包)实现预算优化。然而,值得注意的是,也可以使用其他优化包,如nlopt或nevergrad。
optimization_percentage = 0.2
media_channel_average_spend = result["model_data"][media_channels].mean(axis=0).values
lower_bound = media_channel_average_spend * np.ones(len(media_channels))*(1-optimization_percentage)
upper_bound = media_channel_average_spend * np.ones(len(media_channels))*(1+optimization_percentage)
boundaries = optimize.Bounds(lb=lower_bound, ub=upper_bound)
def budget_constraint(media_spend, budget):
return np.sum(media_spend) - budget
def saturation_objective_function(coefficients,
hill_slopes,
hill_half_saturations,
media_min_max_dictionary,
media_inputs):
responses = []
for i in range(len(coefficients)):
coef = coefficients[i]
hill_slope = hill_slopes[i]
hill_half_saturation = hill_half_saturations[i]
min_max = np.array(media_min_max_dictionary[i])
media_input = media_inputs[i]
hill_saturation = HillSaturation(slope_s = hill_slope, half_saturation_k=hill_half_saturation).transform(X = min_max, x_point = media_input)
response = coef * hill_saturation
responses.append(response)
responses = np.array(responses)
responses_total = np.sum(responses)
return -responses_total
partial_saturation_objective_function = partial(saturation_objective_function,
media_coefficients,
media_hill_slopes,
media_hill_half_saturations,
media_min_max)
max_iterations = 100
solver_func_tolerance = 1.0e-10
solution = optimize.minimize(
fun=partial_saturation_objective_function,
x0=media_channel_average_spend,
bounds=boundaries,
method="SLSQP",
jac="3-point",
options={
"maxiter": max_iterations,
"disp": True,
"ftol": solver_func_tolerance,
},
constraints={
"type": "eq",
"fun": budget_constraint,
"args": (np.sum(media_channel_average_spend), )
})
一些重要点:
-
fun — 需要最小化的目标函数。在这种情况下,它接受以下参数:
媒体系数 — 每个媒体渠道的岭回归系数,与相应的饱和度水平相乘,以估计每个媒体渠道的响应水平。
斜率和半饱和度 — Hill 变换的两个参数,用于每个媒体渠道的支出最小值和最大值,以正确估计给定媒体支出的响应水平。
目标函数遍历所有媒体渠道,并根据每个媒体渠道的个体响应水平的总和计算总响应。为了在优化函数中最大化响应,我们需要将其转换为最小化问题。因此,我们获得总响应的负值,并将其作为优化函数的目标。
-
method = SLSQP — 顺序最小二乘规划(SLSQP)算法是一种流行的约束优化问题方法,通常用于优化市场营销组合建模中的预算分配。
-
x0 — 初始猜测。一个大小为(n,)的实数数组,其中
n
是独立变量的数量。在这种情况下,x0 对应于媒体渠道的平均支出,即每个渠道的平均支出数组。 -
bounds — 指每个渠道的媒体支出范围。
-
约束条件 — SLSQP 的约束条件被定义为字典列表,其中
budget_constraint
是一个确保媒体支出总和等于固定预算的函数:np.sum(media_channel_average_spend)
。
优化过程完成后,我们可以为每个媒体渠道生成响应曲线,并比较优化前后的支出分配,以评估优化过程的影响。
图片由作者提供
使用训练模型进行预算优化
使用训练模型优化预算的过程与之前的方法非常相似,并且可以应用于有饱和度变换和没有饱和度变换的模型。这种方法为优化营销组合提供了更大的灵活性,允许在包括未来在内的各种时间周期中进行优化。
以下代码突出了当前方法和之前方法之间的差异:
每个渠道的平均支出乘以期望的优化周期。
optimization_period = result["model_data"].shape[0]
print(f"optimization period: {optimization_period}")
optimization_percentage = 0.2
media_channel_average_spend = optimization_period * result["model_data"][media_channels].mean(axis=0).values
lower_bound = media_channel_average_spend * np.ones(len(media_channels))*(1-optimization_percentage)
upper_bound = media_channel_average_spend * np.ones(len(media_channels))*(1+optimization_percentage)
boundaries = optimize.Bounds(lb=lower_bound, ub=upper_bound)
我们可以将优化结果解释为“在特定时间区间内,每个渠道的适当支出量”。
目标函数还需要两个额外参数:*optimization_period*
和*additional_inputs*
— 所有其他用于模型训练并在所选时间周期内可用的变量,如趋势、季节性、控制变量:
def model_based_objective_function(model,
optimization_period,
model_features,
additional_inputs,
hill_slopes,
hill_half_saturations,
media_min_max_ranges,
media_channels,
media_inputs):
media_channel_period_average_spend = media_inputs/optimization_period
#transform original spend into hill transformed
transformed_media_spends = []
for index, media_channel in enumerate(media_channels):
hill_slope = hill_slopes[media_channel]
hill_half_saturation = hill_half_saturations[media_channel]
min_max_spend = media_min_max_ranges[index]
media_period_spend_average = media_channel_period_average_spend[index]
transformed_spend = HillSaturation(slope_s = hill_slope, half_saturation_k=hill_half_saturation).transform(np.array(min_max_spend), x_point = media_period_spend_average)
transformed_media_spends.append(transformed_spend)
transformed_media_spends = np.array(transformed_media_spends)
#replicate average perio spends into all optimization period
replicated_media_spends = np.tile(transformed_media_spends, optimization_period).reshape((-1, len(transformed_media_spends)))
#add _hill to the media channels
media_channels_input = [media_channel + "_hill" for media_channel in media_channels]
media_channels_df = pd.DataFrame(replicated_media_spends, columns = media_channels_input)
#prepare data for predictions
new_data = pd.concat([additional_inputs, media_channels_df], axis = 1)[model_features]
predictions = model.predict(X = new_data)
total_sum = predictions.sum()
return -total_sum
目标函数通过*media_inputs*
参数接收在时间周期内受约束的媒体支出。我们假设这些媒体支出在时间周期的所有周内均匀分布。因此,我们首先将*media_inputs*
除以时间周期以获得平均支出,然后使用np.tile
进行复制。接着,我们将非媒体变量与媒体支出进行拼接,并在时间区间内的每一周使用model.predict(X=new_data)
来预测响应。最后,我们计算总响应作为每周响应的总和,并返回总响应的负值以进行最小化。
结论
在营销组合建模中优化预算支出很重要,因为它使营销人员能够以最有效的方式分配资源,最大化营销效果,并实现业务目标。
我展示了两种使用饱和曲线和训练模型优化营销组合的实际方法。
有关详细实现,请参阅我的Github repo上的完整代码。
感谢阅读!
使用 Python 进行时间序列异常检测的实用指南
原文:
towardsdatascience.com/practical-guide-for-anomaly-detection-in-time-series-with-python-d4847d6c099f
一篇关于使用 Python 和 sklearn 检测时间序列数据异常值的实践文章
·发表于 Towards Data Science ·阅读时长 13 分钟·2023 年 3 月 16 日
–
图片由 Will Myers 提供,来源于 Unsplash
异常检测是一个任务,我们希望识别出明显偏离数据大多数部分的稀有事件。
时间序列中的异常检测有广泛的实际应用,从制造业到医疗保健。异常值表示意外事件,它们可能由生产故障或系统缺陷引起。例如,如果我们监控一个网站的访客数量,数量降到 0,可能意味着服务器出现故障。
在进行预测建模之前,检测时间序列数据中的异常值也很有用。许多预测模型是自回归的,这意味着它们会考虑过去的值来进行预测。过去的异常值肯定会影响模型,因此去除这些异常值可能是一个好的主意,以获得更合理的预测。
在本文中,我们将介绍三种不同的异常检测技术,并在 Python 中实现它们。
-
平均绝对偏差(MAD)
-
隔离森林
-
局部离群因子(LOF)
第一个方法是基线方法,如果系列满足某些假设,它可以很好地工作。其他两种方法是机器学习方法。
使用我的 免费时间序列备忘单 学习最新的时间序列分析技术!获取统计学和深度学习技术的实现,全部使用 Python 和 TensorFlow!
让我们开始吧!
时间序列中的异常检测任务类型
时间序列数据中的异常检测任务主要有两种类型:
-
基于点的异常检测
-
基于模式的异常检测
在第一种类型中,我们希望找到被认为异常的单个时间点。例如,一次欺诈交易就是一个点状异常。
第二种类型关注于寻找作为离群点的子序列。一个例子可能是一个股票在许多小时或几天内以异常水平交易。
在本文中,我们将只关注基于点的异常检测,这意味着我们的离群点是在时间上的孤立点。
场景:AWS 云上的 CPU 利用率
我们在一个监控 AWS 云中 EC2 实例 CPU 利用率的数据集上应用不同的异常检测技术。这是实际数据,每 5 分钟记录一次,从 2014 年 2 月 14 日 14:30 开始。数据集包含 4032 个数据点。它通过Numenta Anomaly Benchmark (NAB)在 AGPL-3.0 许可证下提供。
本文所用的特定数据集可以在这里找到,相关标签在这里。完整源代码可在GitHub上找到。
在开始之前,我们需要格式化数据,以便将每个值标记为离群点或内点。
df = pd.read_csv('data/ec2_cpu_utilization.csv')
# The labels are listed in the NAB repository for each dataset
anomalies_timestamp = [
"2014-02-26 22:05:00",
"2014-02-27 17:15:00"
]
# Ensure the timestamp column is an actual timestamp
df['timestamp'] = pd.to_datetime(df['timestamp'])
现在,离群点被标记为-1,而内点被标记为 1。这与 scikit-learn 中的异常检测算法输出一致。
df['is_anomaly'] = 1
for each in anomalies_timestamp:
df.loc[df['timestamp'] == each, 'is_anomaly'] = -1
格式化的数据包括时间戳、值和标签,以确定其是否为离群点(-1)或内点(1)。图像由作者提供。
到目前为止,我们拥有一个格式正确的数据集,包括时间戳、值和标签,以指示值是否为离群点(-1)或内点(1)。
现在,让我们绘制数据以可视化异常。
anomaly_df = df.loc[df['is_anomaly'] == -1]
inlier_df = df.loc[df['is_anomaly'] == 1]
fig, ax = plt.subplots()
ax.scatter(inlier_df.index, inlier_df['value'], color='blue', s=3, label='Inlier')
ax.scatter(anomaly_df.index, anomaly_df['value'], color='red', label='Anomaly')
ax.set_xlabel('Time')
ax.set_ylabel('CPU usage')
ax.legend(loc=2)
fig.autofmt_xdate()
plt.tight_layout()
监控 EC2 实例上的 CPU 使用情况。两个红点表示异常点,而其他蓝点被认为是正常的。图像由作者提供。
从上面的图中,我们可以看到我们的数据仅包含两个离群点,如红色点所示。
这显示了异常检测的挑战性!由于这些事件很少,我们很少有机会从中学习。在这种情况下,只有 2 个点是离群点,占数据的 0.05%。这也使得模型评估更具挑战性。一个方法基本上只有两次正确的机会,而有 4030 次错误的机会。
鉴于以上所有内容,让我们应用一些时间序列异常检测技术,从平均绝对偏差开始。
平均绝对偏差(MAD)
如果我们的数据是正态分布的,我们可以合理地说,尾部的每个数据点都可以被视为离群点。
为了识别它们,我们可以使用 Z 分数,这是一个以标准差为单位的均值测量。如果 Z 分数为 0,则值等于均值。通常,我们设置 Z 分数阈值为 3 或 3.5,以指示一个值是否是异常值。
现在,回想一下 Z 分数的计算方法。
Z 分数的公式。作者提供的图像。
其中 mu 是样本的均值,sigma 是标准差。基本上,如果 Z 分数很大,意味着该值远离均值,接近分布尾部的一端,这也可能表示它是一个异常值。
带有 Z 分数的正态分布。我们可以看到,当 Z 分数为 3 时,我们达到了分布的尾部,因此我们可以说,超过该阈值的数据是异常值。作者提供的图像。
从上图中,我们可以直观地看到经典的 Z 分数阈值 3,用于确定一个值是否是异常值。如黑色虚线所示,Z 分数为 3 时,我们达到了正态分布的尾部。因此,任何 Z 分数大于 3(或小于 -3,如果我们不处理绝对值的话)都可以标记为异常值。
现在,这在我们假设有一个完全正态分布的情况下效果很好,但异常值的存在必然会影响均值,从而影响 Z 分数。因此,我们将注意力转向中位绝对偏差或 MAD。
强健 Z 分数方法
为了避免异常值对 Z 分数的影响,改用中位数,这在存在异常值时是更强健的指标。
中位绝对偏差或 MAD 定义为:
MAD 是值与样本中位数之间绝对差值的中位数。作者提供的图像。
基本上,MAD 是样本值与样本中位数之间绝对差值的中位数。然后,我们可以使用以下公式计算强健 Z 分数:
强健 Z 分数公式。请注意,0.6745 是 MAD 收敛的标准正态分布的第 75 个百分位数。作者提供的图像。
在这里,强健 Z 分数计算方法是:取值与样本中位数之间的差值,乘以 0.6745,然后除以 MAD。请注意,0.6745 代表标准正态分布的第 75 个百分位数。
为什么是 0.6745?(可选阅读)
与传统的 Z 分数不同,强健 Z 分数使用中位绝对偏差,这通常小于标准差。因此,为了获得类似 Z 分数的值,我们必须进行缩放。
在没有异常值的正态分布中,MAD 大约是标准差的 2/3(精确来说是 0.6745)。因此,由于我们是除以 MAD,我们乘以 0.6745 以回到正态 Z 分数的尺度。
稳健 Z 分数方法在两个重要假设下效果最佳:
-
数据接近正态分布
-
MAD 不等于 0(当超过 50%的数据具有相同值时会发生)
第二点很有趣,因为如果是这种情况,那么任何不等于中位数的值都会被标记为异常值,无论阈值如何,因为稳健 Z 分数将会非常大。
鉴于此,让我们将此方法应用到我们的场景中。
应用 MAD 进行异常值检测
首先,我们需要检查数据的分布情况。
import seaborn as sns
sns.kdeplot(df['value']);
plt.grid(False)
plt.axvline(0.134, 0, 1, c='black', ls='--')
plt.tight_layout()
我们的数据分布情况。我们已经看到数据不符合正态分布!更糟的是,许多数据点正好落在中位数(黑色虚线)上,这意味着 MAD 要么是 0,要么非常接近 0。图片由作者提供。
从上图中,我们可以看到两个问题。首先,数据接近正态分布。其次,黑色虚线表示样本的中位数,它正好位于分布的峰值上。这意味着许多数据点等于中位数,意味着我们处于 MAD 可能为 0 或非常接近 0 的情况。
尽管如此,我们还是继续应用该方法,以便了解如何使用它。
下一步是计算样本的 MAD 和中位数,以计算稳健 Z 分数。scipy包包含了 MAD 公式的实现。
from scipy.stats import median_abs_deviation
mad = median_abs_deviation(df['value'])
median = np.median(df['value'])
然后,我们简单地编写一个函数来计算稳健 Z 分数,并创建一个新列来存储分数。
def compute_robust_z_score(x):
return .6745*(x-median)/mad
df['z-score'] = df['value'].apply(compute_robust_z_score)
注意,我们得到了 0.002 的 MAD,这确实接近 0,意味着这个基准可能表现不佳。
完成后,我们决定一个阈值来标记异常值。典型的阈值是 3 或 3.5。在这种情况下,任何稳健 Z 分数大于 3.5(右尾)或小于-3.5(左尾)的值将被标记为异常值。
df['baseline'] = 1
df.loc[df['z-score'] >= 3.5, 'baseline'] = -1 # Right-end tail
df.loc[df['z-score'] <=-3.5, 'baseline'] = -1 # Left-hand tail
最后,我们可以绘制混淆矩阵,看看我们的基准是否正确识别了异常值和正常值。
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
cm = confusion_matrix(df['is_anomaly'], df['baseline'], labels=[1, -1])
disp_cm = ConfusionMatrixDisplay(cm, display_labels=[1, -1])
disp_cm.plot();
plt.grid(False)
plt.tight_layout()
基准异常值检测方法的混淆矩阵。显然,许多正常值被标记为异常值,这在预期之中,因为我们的数据没有符合 MAD 方法的假设。图片由作者提供。
毫不意外地,我们看到基准方法表现不佳,因为 1066 个正常值被标记为异常值。这再次是预期中的情况,因为我们的数据没有符合该方法的假设,且 MAD 非常接近 0。尽管如此,我还是想介绍这种方法的实现,以防在其他场景中对你有用。
尽管结果令人失望,但当假设对你的数据集成立时,这种方法仍然有效,现在你知道在有意义的情况下如何应用它。
现在,让我们转到机器学习方法,首先从隔离森林开始。
隔离森林
孤立森林算法是一种基于树的算法,通常用于异常检测。
算法首先随机选择一个属性,并在该属性的最大值和最小值之间随机选择一个分裂值。这个分区过程会重复多次,直到算法隔离了数据集中的每个点。
然后,这个算法的直觉是,离群点隔离所需的分区会比正常点少,如下图所示。
隔离一个内点。注意在点被隔离之前,数据必须经过多次分区。图像由 Sai Borrelli 提供 — 维基百科
隔离一个离群点。现在,我们看到隔离它所需的分区较少。因此,它很可能是一个异常值。图像由 Sai Borrelli 提供 — 维基百科
在上面的两个图中,我们可以看到在隔离内点和离群点时,分区的数量如何不同。在顶部图中,隔离一个内点需要很多分裂。在底部图中,隔离点所需的分裂较少。因此,它很可能是一个异常值。
所以我们看到在孤立森林中,如果隔离数据点的路径很短,那么它就是一个异常值!
应用孤立森林
首先,让我们将数据分成训练集和测试集。这样,我们可以评估模型是否能够在未见数据上标记异常值。这有时被称为新颖性检测,而不是异常检测。
train = df[:3550]
test = df[3550:]
然后,我们可以训练我们的孤立森林算法。在这里,我们需要指定一个污染水平,这只是训练数据中离群点的比例。在这个例子中,我们的训练集只有一个离群点。
from sklearn.ensemble import IsolationForest
# Only one outlier in the training set
contamination = 1/len(train)
iso_forest = IsolationForest(contamination=contamination, random_state=42)
X_train = train['value'].values.reshape(-1,1)
iso_forest.fit(X_train)
训练完成后,我们可以生成预测。
preds_iso_forest = iso_forest.predict(test['value'].values.reshape(-1,1))
再次,我们可以绘制混淆矩阵以查看模型的表现。
cm = confusion_matrix(test['is_anomaly'], preds_iso_forest, labels=[1, -1])
disp_cm = ConfusionMatrixDisplay(cm, display_labels=[1, -1])
disp_cm.plot();
plt.grid(False)
plt.tight_layout()
孤立森林算法的混淆矩阵。在这里,我们可以看到算法没有标记任何异常值。它还错误地将一个异常值标记为正常点。图像由作者提供。
从上面的图中,我们注意到算法无法标记新的异常值。它还将一个异常值标记为正常点。
再次,这是一项令人失望的结果,但我们还有一种方法需要介绍,即局部离群因子。
局部离群因子
直观上,局部离群因子(LOF)通过比较点的局部密度与其邻居的局部密度来工作。如果点和邻居的密度相似,那么该点就是一个内点。然而,如果点的密度远小于邻居的密度,那么它一定是一个离群点,因为较低的密度意味着该点更孤立。
当然,我们需要设置要查看的邻居数量,scikit-learn 的默认参数是 20,这在大多数情况下效果很好。
一旦设置了邻居的数量,我们就计算可达距离。仅用文字和图片解释这有点复杂,但我会尽力说明。
可视化可达距离,图像由作者提供。
假设我们正在研究点 A,并且我们将邻居数量设置为 3(k=3)。在保持点 A 在中间的情况下画一个圆圈,就会得到你在上图中看到的黑色虚线圆圈。点 B、C 和 D 是离 A 最近的三个邻居,而点 E 在这种情况下太远,因此被忽略。
现在,可达距离被定义为:
可达距离方程。图像由作者提供。
换句话说,从 A 到 B 的可达距离是 B 的 k-距离和 A 到 B 的实际距离之间的较大值。
B 的 k-距离只是从点 B 到其第三个最近邻的距离。这就是为什么在上面的图中,我们画了一个以 B 为中心的蓝色虚线圆圈,以体现从 B 到 C 的距离是 B 的 k-距离。
一旦计算了 A 的所有 k 个最近邻的可达距离,将计算局部可达密度。这仅仅是可达距离的平均值的倒数。
直观上,可达密度告诉我们到达邻近点需要走多远。如果密度大,则点彼此靠近,我们不需要走太远。
最后,局部异常因子仅仅是局部可达密度的比率。在上面的图中,我们将 k 设置为 3,因此我们会有三个比率需要进行平均。这允许我们将点的局部密度与其邻居进行比较。
如前所述,如果该因子接近 1 或小于 1,则为正常点。如果大于 1,则为异常值。
当然,这种方法也有缺点,因为大于 1 的值并不是一个完美的阈值。例如,LOF 为 1.1 可能意味着某个数据集中的异常值,但对另一个数据集则不适用。
应用局部异常因子方法
使用scikit-learn应用局部异常因子方法是直接的。我们使用与隔离森林相同的训练/测试拆分,以便获得可比较的结果。
from sklearn.neighbors import LocalOutlierFactor
lof = LocalOutlierFactor(contamination=contamination, novelty=True)
lof.fit(X_train)
然后,我们可以生成预测以标记测试集中的潜在异常值。
preds_lof = lof.predict(test['value'].values.reshape(-1,1))
最后,我们绘制混淆矩阵来评估性能。
cm = confusion_matrix(test['is_anomaly'], preds_lof, labels=[1, -1])
disp_cm = ConfusionMatrixDisplay(cm, display_labels=[1, -1])
disp_cm.plot();
局部异常因子的混淆矩阵。我们看到该算法成功识别了测试集中唯一的异常值。图像由作者提供。
在上面的图中,我们可以看到 LOF 方法能够标记测试集中唯一的异常值,并且正确地将其他每个点标记为正常点。
和往常一样,这并不意味着局部异常因子比孤立森林方法更好。这只是表示在这个特定情况下,局部异常因子效果更好。
结论
在本文中,我们探讨了三种不同的时间序列数据异常检测方法。
首先,我们探讨了一种使用平均绝对偏差(MAD)的强健 Z-score。这在数据呈正态分布且 MAD 不为 0 时效果良好。
然后,我们看了孤立森林方法,这是一种机器学习算法,它确定数据集需要多少次分割才能孤立一个点。如果需要的分割次数很少,那么这个点就是一个异常点。如果需要很多分割,那么这个点很可能是内点。
最后,我们看了局部异常因子(LOF)方法,这是一种无监督学习方法,它将一个点的局部密度与其邻居的密度进行比较。基本上,如果一个点的密度相对于其邻居较小,这意味着它是一个孤立点,很可能是异常点。
希望你喜欢这篇文章,并学到了新知识!
想要掌握时间序列预测吗?查看Python 中的应用时间序列预测,这是唯一涵盖统计学、深度学习和最先进模型的 100% Python 课程。
干杯 🍻
支持我
享受我的工作吗?通过请我喝咖啡来支持我,这是一种简单的方式来鼓励我,而我也可以享受一杯咖啡!如果你愿意,点击下面的按钮 👇