TowardsDataScience-博客中文翻译-2022-三十-
TowardsDataScience 博客中文翻译 2022(三十)
如何在苹果硅 M1 Mac 上管理多个 Python 版本
安装 ARM64 和 x86 Python 版本,并使用 pyenv 在它们之间无缝切换

尼克·里克特在 Unsplash 上的照片
本文介绍如何使用 pyenv 管理 ARM64 和 x86 Python 环境。如果你更喜欢环境管理的康达,请看 我的另一篇文章 。
你可能永远不会在你的所有项目中使用同一个版本的 Python。一方面,Python 是一种活跃的语言,你会想要利用最新的特性( Python 3.10 最近发布了!).另一方面,当您升级已安装的 Python 版本时,您不希望破坏所有的旧代码。作为一名数据科学家,我经常遇到这种情况——我经常需要重新运行旧的分析或项目,因此我需要一种方法在我的机器上支持多个 Python 版本。
这就是版本管理的用武之地,我的首选工具是 pyenv 。使用 pyenv,您可以在您的机器上安装多个 Python 版本并在它们之间轻松切换。
注:本文面向 Mac 用户,尤其是 Apple Silicon Mac 用户。Linux 用户可能会从 pyenv 教程中受益,但是 Windows 用户就没那么幸运了——pyenv 没有正式支持 Windows。
使用 pyenv 安装 Python 版本
使用 pyenv 安装和管理多个版本的 Python 真的很容易。参见文档了解全部细节,但这里有安装任何版本 Python 的简单说明:
1.安装自制软件
家酿是一个 MacOS 的软件包管理器。它允许你安装各种有用的工具。要安装它,请遵循这里的简单说明。
2.安装和配置 pyenv
pyenv 是一个 Python 版本管理工具。它允许您安装多个版本的 Python,并在它们之间轻松切换。
要安装,请遵循以下说明(或参见官方 GitHub 库上的完整安装说明):
- 使用 brew 安装:
brew install pyenv - 将下面几行添加到~/中。zprofile 和/。zshrc(或者/。bash_profile 和~/。bashrc 如果你还在用 bash):
##### ~/.zprofile #####
eval "$(pyenv init --path)" ##### ~/.zshrc #####
if command -v pyenv 1>/dev/null 2>&1; then
eval "$(pyenv init -)"
fi
然后退出 shell 会话,并启动一个新的会话以使更改生效。您已经成功安装了 pyenv!
3.安装 Python 版本
用 pyenv 安装 Python 很容易。例如,要安装 Python 3.9.7,您可以运行pyenv install 3.9.7(使用pyenv install -l显示可安装的版本列表)。要激活环境,运行pyenv global 3.9.7(使其成为所有地方的默认版本)或pyenv local 3.9.7(使仅当前目录使用该版本)。
苹果硅苹果电脑的其他挑战
上述步骤几乎总是足够了。然而,在较新的苹果电脑上(就我个人而言,我用的是 M1 MacBook Air ),你可能会在安装某些软件包时遇到问题。当苹果从英特尔芯片转到他们内部的苹果硅芯片时,他们从 x86 架构变成了 ARM64 架构。这在很大程度上是一件好事——你在日常使用中会注意到的唯一区别是,新芯片比旧芯片更快、更高效。
不幸的是,您可能偶尔会遇到包兼容性问题。苹果的 ARM64 架构尚不支持一些 Python 包——例如,我在使用 ortools 包时就遇到了这个问题。您将在安装期间得到错误,并且您将不能在您的代码中使用这个包。最终,当开发人员为他们的包添加 ARM64 支持时,这个问题应该会消失,但与此同时,您必须找到另一个解决方案。
幸运的是,有一个短期的解决方案:你可以在 Apple Silicon Mac 上安装 x86 架构的 Python。更好的是,您仍然可以使用 pyenv 来管理您的环境。
使用 Rosetta 和 pyenv 的 x86 环境
如果你需要使用只在 x86 架构上工作的包,只需要遵循这些步骤。首先尝试前面的步骤,只有在遇到软件包安装问题时才到这里来。否则,您会牺牲性能而没有任何好处。
1.安装 Rosetta
Rosetta 是一种软件,允许苹果硅 MAC 运行为基于英特尔的 MAC 设计的应用程序。如果你需要使用一个版本的 Python 用于 x86 架构,你将需要 Rosetta。
要安装 Rosetta,请在您的终端中运行以下命令:
softwareupdate --install-rosetta
然后按照提示同意许可协议并运行安装。
2.创建一个 Rosetta 终端
现在我们需要一种使用 Rosetta 运行命令的方法。首先,复制终端 app。导航到/应用程序/实用工具并复制终端:

作者截图。
然后将新副本重命名为类似“罗塞塔终端”的名称。接下来,右键单击新终端,单击“获取信息”并选中“使用 Rosetta 打开”框:

作者截图。
现在,您可以使用这个新的终端来执行使用 Rosetta 和 x86 架构的命令。将它用于剩余的步骤。
3.安装自制软件
遵循这里的简单说明。和在 ARM64 架构上安装 Homebrew 完全一样,只是会自动安装到不同的位置。
4.安装 pyenv
如果你已经在上面安装了它,那就万事俱备了。否则,请遵循“使用 pyenv 安装 Python 版本”说明中的步骤 2。
5.修改。zshrc
我们已经安装了我们需要的一切。现在我们需要一种方法来告诉我们的机器使用 brew 和 pyenv 的 x86 版本。我的首选方法是将下面几行代码添加到。zshrc(或者。bashrc)文件:
##### ~/.zshrc ###### rosetta terminal setup
if [ $(arch) = "i386" ]; then
alias brew86="/usr/local/bin/brew"
alias pyenv86="arch -x86_64 pyenv"
fi
命令识别我们是否正在运行一个 Rosetta 终端。brew86别名调用 x86 位置的 brew 版本(ARM64 位置为/opt/homebrew/bin)。pyenv86别名在 x86 架构下执行 pyenv。
例如,你现在可以从你的罗塞塔终端呼叫brew86 install ...或pyenv86 install ...。
5.安装 pyenv-alias 插件(可选)
默认情况下,pyenv 不允许您为 Python 版本提供自定义名称。如果您希望在 ARM64 和 x86 架构下安装相同的 Python 版本,这可能是一个问题。pyenv-alias 插件解决了这个问题。遵循此处的安装说明。
6.使用 pyenv 创建您的 x86 环境
您终于可以安装 x86 Python 版本了!在您的 Rosetta 终端中,只需运行pyenv86 install x.x.x(用真实的 Python 版本代替x.x.x)。如果您在上一步中安装了 pyenv-alias 插件,我建议您在 x86 环境中添加一个别名。比如VERSION_ALIAS="x.x.x_x86" pyenv86 install x.x.x。
现在,当您运行pyenv versions时,您将看到所有的 ARM64 和 x86 Python 版本。当您在项目中遇到包兼容性问题时,您可以很容易地切换到 x86 环境。
结论
如果你有一台 Apple Silicon Mac,有一天你可能会遇到软件包安装问题,但是本文中的步骤提供了一个无缝的解决方法(嗯,就像你将要得到的一样接近无缝)。最终,在开发人员有足够的时间将 ARM64 支持添加到他们的包中后,这些步骤可能会过时,但对于一些利基包来说,这可能需要几年时间。同时,我希望这篇文章能为您节省一些故障排除时间!
Python 版本管理可能很棘手。就个人而言,我喜欢 pyenv 的简单。还有其他解决方案(如 conda ),但在我看来 pyenv 是最容易使用的。如果你有另一个你更喜欢的工具,我很乐意听听它!
成为媒体会员访问成千上万作家的故事!
如何用 MATLAB 创建两体轨道
原文:https://towardsdatascience.com/how-to-use-matlab-to-create-two-body-orbits-7a1c2591a252
一步一步的演练使用 MATLAB 来确定航天器如何在较大物体的重力影响下移动

双体轨道动画[作者创作]
轨道力学(或天体动力学)涉及将牛顿运动定律和万有引力定律应用于航天器(如火箭和卫星)。任务规划者用它来预测宇宙飞船在重力、推力和其他力的影响下的运动。本文的题目是两体问题。这是天体动力学的一个子类,涉及一个航天器在一个单独的大质量物体的影响下,没有考虑其他的力。与更大的质量相比,航天器的质量也被认为是可以忽略的。这意味着大质量物体会影响航天器的运动,但航天器不会影响大质量物体的运动。实际上,感兴趣的物体不一定是航天器;可能是小行星,彗星,宇航员等等。
如何解决两体问题
为了在两体系统中生成轨迹,必须推导航天器(相关质量)的运动方程。有大量的资源显示这个推导和最终的运动方程,所以你不必亲自动手。我在下面的文章中演示了这个过程。我鼓励你理解这些方程以及它们是如何得到的。然而,如果你不想这样做,只是想创建一个轨道,我提供了一个图表和所需的运动方程如下,供您参考。
https://medium.com/illumination/astrodynamics-two-body-problem-ec2c5e148184 
两体问题图[作者创建]

在这些运动方程中, μ 是主质量(行星、月球、恒星等)的引力参数。). x 、 y 和 z 是定义航天器在惯性空间中位置的笛卡尔坐标。点代表时间导数,所以一个点代表速度,两个点代表加速度。我们将在本文的编码部分利用这些。
作为题外话,两体问题是轨道力学复杂领域的一个非常简化的版本。事实上,在设计任务时,人们必须考虑诸如大气阻力、太阳辐射压力、其他重力以及非球形物体的影响等干扰力。虽然不是最精确的方法,但两体问题可以用于航天器的初始任务规划,特别是如果它靠近一个单独的物体运行。当轨道远离大质量物体时,其他力将在航天器的运动中发挥更大的作用。这里不讨论额外的干扰力,但是记住它们是有好处的。
为了模拟轨道,我们将需要利用数值积分。如果你不知道这是什么,你仍然可以使用代码来创建你自己的轨道。为了对航天器的运动方程进行数值积分或求解,我们将使用 MATLAB 中的ode113函数。这是一个内置函数,将一组要积分的导数(用户定义的函数)、感兴趣的时间间隔、初始条件和可选的数值积分容差作为输入。这组导数将是我们的状态向量 Y 的时间导数,它包括向量形式的航天器的位置和速度分量。状态向量及其时间导数如下所示。

这是对两体问题运动方程的数值积分过程的简要概述。让我们开始编写模拟地球轨道卫星轨迹的代码吧!
用户定义的 ODE 函数
如前所述,为了创建我们的轨迹,我们需要对两体问题的运动方程进行数值积分。我们将使用ode113来完成这项工作。这个 MATLAB 函数要求我们创建自己的函数,这个函数将被调用来进行数值积分。我们将定义这个函数,ODE2BP。该函数使用地球引力参数mu和之前的状态向量Y来创建时间导数状态向量dYdt。该函数返回这个新向量,它将被ode113用来进行数值积分。注意:在 MATLAB 中,用户定义的函数必须放在脚本的末尾。
% User-Defined ODE Function
function dYdt = ODE2BP(t, Y)
mu = 3.986*10^5; % Earth's gravitational parameter [km^3/s^2]
x = Y(1); % [km]
y = Y(2); % [km]
z = Y(3); % [km]
vx = Y(4); % [km/s]
vy = Y(5); % [km/s]
vz = Y(6); % [km/s]
xddot = -mu/(x^2+y^2+z^2)^(3/2)*x; % [km/s^2]
yddot = -mu/(x^2+y^2+z^2)^(3/2)*y; % [km/s^2]
zddot = -mu/(x^2+y^2+z^2)^(3/2)*z; % [km/s^2] dYdt = [vx;vy;vz;xddot;yddot;zddot]; % Y'
end
数值积分
接下来,我们完成定义ode113的输入。这些输入之一是航天器的初始条件。这是本文开头的状态向量形式。注意位置和速度的分量按惯例以公里和公里/秒为单位。接下来,我们有一个以秒为单位的感兴趣的时间间隔。最后,有许多可选参数可以传递给ode113。由于轨道力学要求精度,我在数值积分的时候一般会通过一个很小的相对公差。然后,我们将这些输入和上一节定义的函数一起传递给 ODE 求解器。该函数输出变量t和Y,它们是数值积分时间步长和每个时间步长的状态向量。然后,我们可以从输出中提取位置坐标x、y和z的时间历史。
% Creating Inputs for Numerical Integration
Y0 = [20000; 0; 0; 0; 2.9; 1.8]; % [x; y; z; vx; vy; vz] [km, km/s]
tspan = [0 24*60*60]; % One day [s]
options = odeset('RelTol', 1e-13); % Setting a tolerance% Numerical Integration
[t, Y] = ode113(@ODE2BP, tspan, Y0, options);% Pulling Position Data from Output
x = Y(:, 1); % [km]
y = Y(:, 2); % [km]
z = Y(:, 3); % [km]
绘制轨迹
最后,我们可以使用x、y和z变量来创建我们的航天器的轨迹。我们首先用标签、网格和一组相等的轴初始化图形(给出物理空间中轨道的真实表示)。接下来,我们可以使用 MATLAB 中的sphere函数在我们的绘图原点创建一个地球的表示。最重要的是,可以使用plot3功能绘制轨迹。
% Creating Figure
figure; hold on
title('Two-Body Trajectory', 'Interpreter', 'Latex')
xlabel('x', 'Interpreter', 'Latex')
ylabel('y', 'Interpreter', 'Latex')
zlabel('z', 'Interpreter', 'Latex')
axis equal
grid minor
view(30, 30)% Creating/Plotting Spherical Earth
rm = 6378.14; % Radius of Earth [km]
[xEarth, yEarth, zEarth] = sphere(25);
surf(rm*xEarth,rm*yEarth,rm*zEarth, 'FaceColor', [0 0 1]);% Plotting Trajectory
plot3(x, y, z, 'k')
hold off
上面的代码产生了以下图形:

二体轨道[作者创作]
用 MATLAB 创建两体轨道或轨迹的过程到此结束。玩具周围的初始条件和时间间隔,以创造自己独特的轨道!记住,这是对大多数天体动力学问题的过度简化。如果你想要一个更好的物理表述,你将需要包括扰动力。这可能是一个相当大的挑战,所以这就是为什么最好从简单的两体问题中学习,并从那里开始构建。如果你决定走得更远,祝你旅途顺利!
如果你想制作一个类似于文章开头的视觉效果的动画,可以看看下面的文章。感谢您抽出时间阅读。如果你觉得这有用,请给我一个关注!谢谢!
如何利用熊猫进行大数据
原文:https://towardsdatascience.com/how-to-use-pandas-for-big-data-50650945b5c6
通过 Spark 上的 Pandas 运行分布式工作负载

图片来自 Pixabay
动机
由于其直观的数据结构和丰富的 API,Pandas 已经成为数据科学家和分析师事实上的 python 库。Pandas 使用内存计算,这使它成为中小型数据集的理想选择。然而,由于内存不足的错误,Pandas 处理大数据集的能力是有限的。熊猫的替代品有很多,其中之一就是阿帕奇 Spark。
Apache Spark 是一个开源的分布式计算引擎,用于处理和分析大量数据,方法是在集群之间分发数据并并行处理数据。虽然 Pyspark(python 中的 Apache Spark 接口)非常适合繁重的数据工作负载,但是学习新的 Pyspark 语法并将代码从 Pandas 重构到 py Spark 可能会很乏味。
幸运的是随着 Spark 3.2 更新,我们现在可以在 Spark 上运行熊猫 API 了。这允许我们在 Spark 中利用分布式处理的能力,同时使用 Pandas 中熟悉的语法。让我们来看一个例子,看看它是如何处理来自 UCI 数据库[1]的银行营销数据集的。代码在 Databricks 社区版运行时版本 10.2ML 上执行
例子
为了使用本地熊猫,我们通常会以下列方式进口熊猫
import pandas as pd
要在 Pyspark 中使用 Pandas API,我们只需进行以下导入,其他一切都是一样的。
import pyspark.pandas as ps
读取 CSV 文件
得到的数据帧是 Pyspark Pandas 数据帧。
df = ps.read_csv('/FileStore/tables/bank_full.csv')type(df)
>> pyspark.pandas.frame.DataFrame
检查数据帧
该数据与一家葡萄牙银行机构的直接营销活动(电话)相关。分类的目标是预测客户是否会认购定期存款(变量 y)。
df.head()

作者图片
查看栏目信息
df.info()

作者图片
分组依据和聚合
通过目标变量(y)找出平均年龄
df.groupby('y', as_index = False).agg({'age':'mean'}).sort_values('age')

作者图片
应用 Lambda 函数
让我们创建一个指示器列来指示客户的年龄是否超过 40 岁。我们可以通过使用 Pandas .apply和lambda函数对 age 列应用 lambda 函数来实现这一点。
df['age_above_40'] = df['age'].apply(lambda x: 'yes' if x > 40 else 'no')

作者图片
绘图
我们还可以用熊猫.plot功能绘制图表。图表是用 Plotly 绘制的,我们可以使用 Plotly 的.update_layout来调整图表属性。
fig = df.groupby('marital').agg({'age':'mean'}).plot.bar()
fig.update_layout(xaxis_title = 'Marital Status', yaxis_title = 'Average Age', width = 500, height = 400)

作者图片
用 SQL 查询数据帧
Pyspark Pandas DataFrame 也可以用 SQL 查询。这是本土熊猫所没有的。
ps.sql("SELECT y, mean(age) AS average_age FROM {df} GROUP BY y")

作者图片
从 Spark Pandas 转换到 Pyspark DataFrame
我们也可以选择在 Pyspark 中工作,将 Pyspark Pandas 数据帧转换成 Pyspark 数据帧。
spark_df = df.to_spark()type(spark_df)
>> pyspark.sql.dataframe.DataFrame
从 Pyspark 数据帧转换到 Pyspark 熊猫数据帧
psdf = spark_df.to_pandas_on_spark()type(psdf)
>>pyspark.pandas.frame.DataFrame
结论
我们看了如何在 Spark 上使用 Pandas API,它帮助我们使用熟悉的 Pandas 语法以分布式方式处理大数据集。Apache Spark 只是 Pandas 在 Python 中处理大数据集的众多替代品之一。查看这篇关于熊猫处理大数据集的其他替代品的文章。
参考
[1]来自 UCI 知识库的数据集。由 4.0 在 CC 下授权。c .萨卡尔&金尤美·卡斯特洛创建的数据集。
加入 Medium 阅读更多这样的故事。
如何使用 Pandas 以您需要的格式获取数据
了解长格式和宽格式数据之间的区别,以及如何在 Pandas 中进行转换。

戴维·贝克尔在 Unsplash上的照片
数据科学家都知道这样一个事实:你的数据永远不会是你想要的样子。您可能会得到一个有点条理的电子表格或合理的合理表格,但是在您准备好进行分析之前,总会有一些清理工作要做。
因此,能够在不同形式的数据之间转换至关重要。有时,这只是可读性和易于解释的问题。其他时候,你会毫不夸张地发现,除非你的数据是特定的格式,否则你试图使用的软件包或模型根本无法工作。不管是什么情况,这都是一项很好的技能。
在本文中,我将讨论两种常见的数据形式:长格式数据和宽格式数据。这些是数据科学中广泛使用的范例,熟悉它们是有好处的。我们将通过一些例子来了解这两种数据格式到底是什么样子,然后我们将了解如何使用 Python(更具体地说,是 Pandas)在它们之间进行转换。
让我们开始吧。
长格式与宽格式数据
最简单的方法是从直接的定义开始[1]:
- 宽格式数据为自变量的每个可能值提供一行,所有因变量记录在列标签中。因此,每一行中的标签(对于自变量)将是唯一的。
- 长格式数据每个观察值占一行,每个因变量作为一个新值记录在多行中。因此,独立变量的值在行内重复。
好吧,酷——但这意味着什么?如果我们看一个例子会更容易理解。假设我们有一个学生的数据集,我们存储他们在期中考试、期末考试和课堂项目中的分数。概括地说,我们的数据如下所示:

作者图片
这里,每个学生都是自变量,每个分数都是各自的因变量(因为特定考试或项目的分数取决于学生)。我们可以看到,Student的值对于每一行都是唯一的,正如我们对宽格式数据的预期。
现在让我们看看完全相同的数据,但形式更长:

作者图片
这一次,我们为每个观察值安排了一行。在这种情况下,观察对应于特定作业的分数。在上面这个数据的宽格式版本中,我们在一行中记录了多个观察值(分数),而这里每一行都有自己的分数。
此外,我们可以看到自变量Student的值以这种数据格式重复出现,这也是我们所期望的。
一会儿,我们将讨论为什么您应该关心这些不同的格式。但是首先,让我们快速看一下如何使用 Pandas 在这些不同的数据格式之间进行转换。
宽格式数据到长格式数据:Melt 函数
再一次,让我们看看上面的宽格式数据。这一次,我们将为数据帧起一个名字:student_data:

作者图片
为了将学生数据转换成长格式,我们使用下面一行代码:
student_data.melt('Student', var_name='Assignment', value_name='Score')

作者图片
以下是一步一步的解释:
melt函数被设计成将宽格式数据转换成长格式数据[2]。var_name参数指定了我们想要命名的第二列——包含我们各自的因变量的那一列。value_name参数指定了我们想要命名的第三列——包含我们正在观察的单个值的列(在本例中是分数)。
好了,现在我们有了长格式数据。但是,如果——不管出于什么原因——我们需要回到宽格式呢?
长格式数据到宽格式数据:Pivot 函数
现在,我们从上面数据的长版本开始,称为student_data_long。下面一行代码会将其转换回原始格式:
student_data_long.pivot(index='Student', columns='Assignment', values='Score')

作者图片
除了稍微更新的标签(pivot显示整个列标签'Assignment',这正是我们从上面开始的数据。
以下是一步一步的解释:
pivot函数被设计成将宽格式数据转换成长格式数据[3],但是实际上可以完成比这里显示的更多的事情 [4]。index参数指定我们希望将哪一列的值作为唯一的行(即独立变量)。columns参数指定哪个列的唯一值(长格式)将成为唯一列标签。values参数指定哪一列的标签将构成我们宽格式中的实际数据条目。
这就是全部了!
为什么重要?
最后,我想简单强调一下,虽然上面的乍一看似乎很肤浅,但实际上这是一项非常有用的技能。很多时候,你会发现以某种格式保存数据会让你的生活变得更加容易。
我用自己工作中的一个例子来说明。我经常需要用 Python 做数据可视化,我选择的模块是 Altair。这导致了一个意想不到的问题:大多数电子表格倾向于宽格式,但 Altair 的规范在长格式中使用起来要容易得多。
今年早些时候,我花了很长时间来开发一个特别的可视化。当我接受堆栈溢出时,我发现我需要做的就是将我的数据转换成长格式。如果你持怀疑态度,你可以自己去看看这个帖子。
现在,你可能不从事可视化工作,但是如果你正在阅读这篇文章,可以肯定你确实在从事数据工作。因此,你应该知道如何操作它,这只是你的工具箱中又一个有用的技能。
祝你的数据科学事业好运。
想擅长 Python? 获取独家,免费获取我简单易懂的指南 。想在介质上无限阅读故事?用我下面的推荐链接注册!
参考
[1]https://altair-viz.github.io/user_guide/data.ht
【2】https://pandas . pydata . org/docs/reference/API/pandas . melt . html
【3】https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.pivot.html
【4】https://medium . com/forward-data-science/a-comparison-of-group by-and-pivot-in-python-pandas-module-527909 e78d6b
如何使用 SQL 在 BigQuery 中使用分区和集群
原文:https://towardsdatascience.com/how-to-use-partitions-and-clusters-in-bigquery-using-sql-ccf84c89dd65
优化成本,加快查询速度

让·格柏在 Unsplash上的照片
什么是分区和集群?
分区和集群是两种非常有效的技术,可以最小化查询成本和提高性能(使用更少的资源,同时提高速度)。
这些技术背后的思想是限制运行查询时需要读取的数据量。
从本质上讲,我们不需要通读表中的每一条记录,而是读取其中的一小部分。
如果您不确定是使用分区还是集群,我仍然建议同时设置两者,因为这将提供速度和成本优势。你可以单独使用这两种技术或者结合使用来获得最佳效果。
划分
表分区是一种将大表分割成小表的技术。
下面是一个将经典表转换为按日期分区的表的示例。

分区如何将数据分成不同的块(图片由作者提供)
BigQuery 将在物理级别上分别存储不同的分区(这意味着数据将存储在不同的服务器上)。
当您对表进行分区,然后执行查询时,也是 BigQuery 决定访问哪个分区,并最小化必须读取的数据。
您可以基于一个列创建一个分区表,也称为分区键。在 BigQuery 中,可以使用不同的键对表进行分区:
- 时间单位列:基于时间值(如时间戳或日期)对表进行分区。
- 摄取时间:当 BigQuery 摄取数据时,根据时间戳对表进行分区。
- 整数范围:根据数字对表进行分区。
BigQuery 对每个表有 4000 个分区的限制。
我们的起始桌
对于我们的实验,我们有一个名为stackoverflow.question的表,它是 35GB 大的,有2200 万行。
在进行任何分区或集群之前,我们需要一个经典表上的速度(我们的查询需要运行多长时间)和容量(读取了多少数据)基准。
下面的查询,我们称之为我们的基本查询,将用于比较性能。
用于评估分区和聚类技术性能的基本查询

经典表的处理字节和执行时间(图片由作者提供)
禁用缓存结果后,我们用了12.7 秒来运行我们的查询,我们扫描了 35GB 的数据。在这种情况下,我们必须扫描表中的所有行,包括所有的标签和日期。
对表进行分区的一个具体例子
我们将使用时间单位列(在我们的例子中是日期列)进行分区。
现在让我们使用我们的creation_date列作为键来创建一个分区表:
从初始的经典表创建一个分区表
我们没有按天分区,而是用DATE_TRUNC(creation_date,MONTH)将创建日期转换为月份。我们这样做是因为它减少了该表所需的分区数量。
如果我们尝试按天分区,BigQuery 会返回一个错误:
查询生成的分区太多,允许 4000 个,查询至少生成 4967 个分区
让我们再次运行我们的基本查询,但是这次使用我们的分区表。

已分区表的已处理字节数和执行时间(图片由作者提供)
我们花了9.7 秒来运行这个查询,而只扫描了 7.1GB 的数据。我们不能提高这么多的速度,但扫描的数据量少了很多,导致更便宜的查询。
- ****partition _ expiration _ days:big query 在到期时删除分区中的数据。这意味着超过此处指定天数的分区中的数据将被删除。
- ****require _ partition _ filter:用户不在你的分区键上过滤(WHERE 子句)就无法查询。
从我们最初的经典表创建一个分区表,并添加一些选项
您始终可以通过修改表格来更改选项的值。
用新值更新分区表中的选项
关于选项和分区(以及集群)的所有信息都可以在您的表的细节部分找到。

当前表的分区细节(图片由作者提供)
簇
集群将允许 BigQuery 将相似的数据放在一起,从而允许查询扫描更少的数据。
下面是一个将经典表转换为按名称聚集的表的示例。

聚类如何将数据分成不同的块(图片由作者提供)
根据您为集群选择的列中的值,BigQuery 将自动对这些值进行排序,并决定如何将它们存储在最佳存储块中。
集群最适合具有高基数的值,这意味着列具有各种可能的值,例如电子邮件、用户 id、名称、产品类别等……****
BigQuery 允许对多个列进行集群,并且可以对不同的数据类型(字符串、日期、数字等)进行集群
BigQuery 对每个表有 4 个集群列的限制。
聚集一个表的具体例子
我们将使用字符串值(在我们的例子中是 StackOverflow 表中的tags列)进行聚类。
要创建一个集群表,步骤与分区相同,但是语法将使用一个CLUSTER BY子句。
从初始的经典表创建一个聚集表
我们现在可以验证我们的表是基于包含在tags列中的值进行聚类的。

细节部分将显示哪些字段用于聚类(图片由作者提供)
为了评估性能,我们现在使用聚集表运行与以前相同的查询。

集群表的处理字节和执行时间(图片由作者提供)
运行这个查询花费了我们 2.9 秒,我们必须扫描 265.9MB 的数据。在这种情况下,速度提高了,需要扫描的数据量大大减少。
在这种情况下,查询验证器(绿色标签表示“该查询将处理 35.8GB”)是不正确的。当使用聚簇表时,这个估计并不精确,将给出一个上限值。
划分+聚类
我们现在可以很容易地结合这两种技术来优化更多。
下面是一个将经典表转换成表的例子,表按名称聚集,表按日期分区。****

****分区和聚类如何结合将数据分成不同的块(图片由作者提供)
如您所见,它与分区表非常相似,只是现在当每个分区中的值相似时,它们会被排序。
现在要创建一个使用这两种技术的表,就像在创建表时编写这两个子句一样简单。
从我们最初的经典表创建一个集群和分区的表
我们可以在表细节中观察到,该表同时使用了分区和集群。

细节部分将显示哪些字段用于聚类和划分(图片由作者提供)
为了再次分析性能,我们执行与之前相同的查询,但是这次我们使用最近生成的表,该表同时使用了集群和分区。

一个分区+集群表的处理字节和执行时间(图片由作者提供)
运行这个查询花费了我们 3.1 秒,并且只扫描了 909.9MB 的数据。这与我们看到的两种独立技术相当,现在这两种技术被结合起来以提高速度和成本。这里,它花费了几乎相同的时间,但是处理了更多的数据(因为使用了分区,BigQuery 将不得不查看数据的几个部分,从而导致比我们只扫描一个集群分区时扫描更多的数据)。
一般来说,结合并实现这两种技术是一个很好的实践。当您使用 cron 作业或通过流从外部源接收数据时,按照接收时间进行分区可能会很有意思。
与其他数据仓库系统一样,当新数据进入时,BigQuery 将自动执行自动重新聚类,并且不需要额外的成本。
我希望这篇文章对您有用,并且您现在能够在您的项目中创建和实现集群和分区了!
额外资源
资料来源于 Google 云平台关于分区表的文档。****
**https://cloud.google.com/bigquery/docs/partitioned-tables
来源于关于集群表的 Google 云平台文档。
https://cloud.google.com/bigquery/docs/clustered-tables **
如何在回归模型中使用代理变量
原文:https://towardsdatascience.com/how-to-use-proxy-variables-in-a-regression-model-539f723ab587

梅西耶 87 核心超大质量黑洞的直接图像(来源:维基百科下 CC BY 4.0
如何使用代理来代替不可观察的变量
有时,回归模型中的一个或多个解释变量无法观察或测量。在本文中,我们将看看如何使用代理变量来代替这些不可测量的变量。我们将解释什么是好的和坏的代理。我们将研究包含代理和不包含代理之间的权衡。
考虑以下回归模型,在给定几个因素(如学业准备程度、情感准备程度、高中时期的药物使用情况、性别和种族)的情况下,估算学生的大学 GPA:

估算大学 GPA 的线性模型(图片由作者提供)
在上面的模型中,下标 i 是指进入大学的第个个学生。 ϵ_i 是第 I 次观测的误差。
学术准备取决于几个因素,如掌握和吸收新的和复杂的信息的能力,对大学专业的了解,掌握复杂的大学课程的能力和许多其他事情。情感上的准备程度包括性格特征、家庭背景和大学前的经历。人们可能会猜测,学术准备度和情感准备度本质上是不可测量的变量。
由于我们模型中的两个主要变量无法直接观察到,我们处于一个棘手的局面。在这一点上,我们基本上有三种选择:
- 我们可以从模型中去掉学术准备状态和情感准备状态,并接受由于忽略它们而导致的估计系数中的偏差,或者
- 我们可以找到合适的替代变量,它们在某种程度上可以替代学术准备状态和情感准备状态或
- 我们可以使用一种叫做工具变量的技术来解释我们无法在模型中使用学术准备和情感准备的原因。
在下一篇文章中,我们将研究工具变量的使用。在本文中,我们将看看如何使用代理变量。
代理变量
考虑学业准备情况。我们不能直接测量它,但是我们使用高中 GPA 和 SAT 分数作为两个变量,作为学业准备程度的代理怎么样?假设我们用学生的情商(情商)分数作为情感准备程度的代表?
如果我们使用这些代理,我们将估计以下模型:

估计大学 GPA 的线性模型,使用学术准备和情感准备的代理变量(图片由作者提供)
如果我们能够假设(或建立)以下关于它们的情况,我们也许能够使用这些代理:
代理应与主要变量相关联
代理变量应该与它们代理的变量相关联。这似乎是一件显而易见的事情,但在选择代理时,人们仍然应该对这一要求进行全面检查。不幸的是,由于主变量不能被直接观察到,人们只能内省并使用自己的判断来决定代理是否满足这一要求。
在我们的例子中,学生的高中 GPA 和 SAT 分数需要与他们的学业准备情况相关联。表达这种相关性的一种方式是通过以下线性模型:

回归高中 GPA 和 SAT 成绩的学业准备状态(图片由作者提供)
上述模型是不可估计的,因为学术准备是一个不可观察的变量。因此,我们只能假设该模型符合真实数据,并且系数 δ_1 和 δ_2 被发现是共同显著的(通过用于回归分析 的 F 检验),那么学术准备性的代表将与学术准备性共同相关。与所有基于代理的研究一样,在缺乏实际数据的情况下,我们必须推测这很可能是我们选择的两个代理的真实情况。
代理人不应该是内生的
在下面显示的模型中,回归其两个代理的学术准备程度,这两个代理不应是:

回归高中 GPA 和 SAT 成绩的学业准备状态(图片由作者提供)
即 HS_GPA_i 和 SAT_Score_i 不应该与模型的误差项 ϵ_i 相关联。
一个更严重的情况出现在下面的场景中:假设在一个包括代理变量而不是主要变量的模型中,代理变量被证明是内生的。在我们的示例中,它将是以下模型:

估计大学 GPA 的线性模型,使用学术准备和情感准备的代理变量(图片由作者提供)
对于可通过最小二乘法估计的上述模型,R.H.S .上的所有变量都应该是外生的,即与误差 ϵ_i. 不相关
性别 _i 、种族 _i 和吸毒 _In_HS_i 根据定义是外源性的。**
如果代理 HS_GPA_i 、 SAT_Score_i 和 EQ_Score_i 中的一个或多个是内生的,则上述模型不再能够被正确估计。
此外,内生性的存在意味着有一个或多个因素隐藏在模型的误差项 ϵ_i 中,通过内生代理影响响应变量 College_GPA_i 。看待这种情况的一种方式是,内生代理是另一个变量的代理,而另一个变量可能是主要变量的真实代理,从而使我们对代理的选择是错误的,或者至少是次优的。
为了测试上述模型中的代理是否是内生的,人们通常会运行几个可用的内生性测试之一。
代理不应该向模型中注入额外的信息
假设我们能够测量学术准备程度。如果一个模型已经包含学术准备作为一个变量,添加高中 GPA 和 SAT 分数到这个模型不应该馈入额外的信息到响应大学 GPA。换句话说,代理不应该能够解释响应变量中任何超出他们所代理的变量所能解释的额外变化。
从数学上来说,如果将以下理论模型拟合到(理论)数据,那么 High_School_GPA_i 、 SAT_Score_i 和 EQ_Score_i 的估计系数在统计上应该是不显著的(即,就总体而言,为零)。

估算大学 GPA 的超指定模型(图片由作者提供)
如果它们不为零,会发生什么?
让我们想象一下γ_1、γ_2 和γ_3 中的一个或多个不为零的情况。这意味着相应的代理变量是相关的。在忽略相关代理变量的模型中,这些被忽略的变量将通过误差项施加影响,导致模型中所有变量的系数偏离其真实值。这是经典的 省略变量偏差 的情况。
但是事情变得更糟了。由于被省略的变量也是代理变量,它们被定义为与主要变量学术准备状态和情感准备状态相关。由于省略的代理正在通过误差项施加它们的影响,我们现在处于误差项与模型的主要变量相关的情况,从而使主要变量 内生 。这违反了线性模型的基本要求(和假设)。
何时使用代理,何时不使用代理
在上面的讨论中,我们考察了包含一个选择不当或次优代理的一些影响。假设没有统计测试来验证一个“好的”代理的大部分品质,我们可能还想检验省略掉可能被证明是有用的代理的影响。
在大多数情况下,所选择的代理可能无法完全满足所有需求。例如,一个代理可能是内生的,但内生的程度可能是温和的。人们也可能会怀疑代理人向模型中注入了额外的信息,这些信息超出了它代替主模型所需的信息。但是我们也可能怀疑这种额外的影响是微小的,实际上是不重要的。
另一方面,我们的常识可能表明,在不可观察的主要变量及其可观察的替代变量之间存在明显和显著的相关性。在这种情况下,简单地忽略代理将使我们无法解释一个不可观察但重要的解释变量的影响。这种没有主要解释变量或其替代变量的拟合模型将导致比包含替代变量时更高的残差和更低的拟合优度。此外,如果被忽略的主要变量碰巧与模型中的任何解释变量相关,它将使所有估计的系数偏离它们的真实值。
总之,除非有充分的理由忽略一个代理,总的来说,在模型的估计系数中包含它并容忍可能的精度损失比完全忽略代理要好。
例子
让我们通过一个真实的例子来说明代理变量的使用。
假设我们想估计一个社区的贫困发生率。我们的估计单位将是美国的一个县。我们将该县的贫困发生率定义为该县低于联邦贫困线的家庭的百分比。
让我们假设这个百分比与以下因素相关:
- 该县居民的平均年龄。
- 县城的住房(尤其是置业)需求有多旺盛。我们将根据房主空置率来衡量这一因素,这是一项联邦政府公布的统计数据,用于衡量该县未售出的待售房屋的百分比。
- 平均而言,该县人口的受教育程度。
毫无疑问,还有许多其他因素与这个国家的贫困程度相关。但是现在,我们将考虑上述三个因素。
将上述三个因素与该县的贫困发生率联系起来的线性模型如下所示:

估计平均贫困水平的线性模型(图片由作者提供)
在上面的模型中,下标 i 表示数据是指数据集中的县 i 。
该县的年龄中位数是可以直接测量的,房主空置率也是如此。
衡量一个国家中“受过良好教育”的人口比例的百分比是无法直接观察到的。虽然人们可能普遍认为受过良好教育是实现财务独立的重要一步,但受过良好教育对不同的人来说意味着不同的事情。
此外,教育作为一种实现的质量可以说是各种可观察和不可观察的数量的综合效应,例如正规学校教育的年数、参加各种培训和学徒活动——正式和非正式的、关于个人所选职业的一般知识、关于相关主题的横向知识,以及个人为解决手头问题而带来的“软”技能。
这些因素使得教育成为人们想要用代理人来衡量的变量。在我们的模型中,我们将使用变量县内受过大学教育或更高教育的人的百分比作为变量受过良好教育的人的百分比的代理。
这样做会产生以下模型:

使用代理变量估算县级贫困的线性模型(图片由作者提供)
让我们看看我们选择的代理是否满足“好”代理的要求。
代理与主要变量相关吗?
如果我们对受过良好教育的定义包括(除其他外)拥有至少一个大学学位,那么(只有到那时)拥有至少一个大学学位的县居民的百分比越高,该县的民众受教育程度就越高,反之亦然。
代理是内生的吗?
如果代理是内生的,它与模型的误差项相关。在下面的模型中,我们必须测试代理人Percent _ With _ College _ Or _ Higher _ Educ _ I是否是内生的。

使用代理变量估算县级贫困的线性模型(图片由作者提供)
这不是一个简单的测试,它值得自己的文章。我们将在下一篇文章中展示如何执行这个测试。
代理是否向模型中注入了额外的信息?
要回答这个问题,我们需要构建一个包含主要变量和代理变量的模型,并检查它的拟合优度是否大于只包含主要变量的模型。由于主要变量受过良好教育的人的百分比本来就不可测量,这就变成了一个不可能的实验。相反,我们呼吁我们的观点是什么造就了一个受过良好教育的人。我们假设,虽然一个人没有大学学位也有可能受到良好的教育,但通常情况下,这样的人会有大学学位。如果有可能收集到正确的数据,这些数据很可能证明我们在这个概念上是错误的。但是如果我们是对的,那么添加变量受过大学或更高教育的人的百分比将不会给已经包含主要变量受过良好教育的人的百分比的模型带来额外的信息。
内省完成后,让我们转向构建回归模型的任务。
构建和训练模型
为了符合模型,我们将使用来自美国人口普查局的数据集。
具体来说,我们将使用由人口普查局收集并在县一级汇总的几个社会经济指标的数据。该数据是美国人口普查局进行的 2015-2019 年美国社区调查(ACS) 5 年估计值的子集。下表包含我们将使用的数据(点击或单击图像进行缩放):

本例中使用的数据集可以从这里 下载 。完整的 ACS 数据集可以使用公开可用的API从美国人口普查局的网站获取,或者直接从人口普查局的 社区资源管理器 网站获取。****
总的来说,我们希望将以下模型用于此数据集:

使用代理变量估算县级贫困的线性模型(图片由作者提供)
在数据集中,我们对代理变量的名称Percent _ With _ College _ Or _ Higher _ Educ _ I做了一点修改,以反映美国人口普查局对该变量使用的名称,即Percent _ Pop _ 25 _ And _ Over _ With _ College _ Or _ Higher _ Educ。
我们将使用 Python 和 Pandas 将数据文件加载到内存中,并且我们将使用基于 Python 的 statsmodels 包来构建和拟合线性模型。
让我们从导入所需的包并将数据文件加载到 Pandas DataFrame开始:
**import** pandas **as** pd
**import** statsmodels.formula.api **as** smf ***#Load the US Census Bureau data into a Dataframe*** df = pd.**read_csv**(**'us_census_bureau_acs_2015_2019_subset.csv'**, **header**=0)
用 Patsy 语法构建模型的方程。Statsmodels 会自动将回归的截距添加到模型中,因此我们不必在模型的方程中明确指定它:
reg_expr = **'Percent_Households_Below_Poverty_Level ~ Median_Age + Homeowner_Vacancy_Rate + Percent_Pop_25_And_Over_With_College_Or_Higher_Educ'**
建立和训练模型并打印训练摘要:
olsr_model = **smf**.**ols**(**formula**=reg_expr, **data**=df)
olsr_model_results = olsr_model.**fit**()
**print**(olsr_model_results.**summary**())
我们看到以下总结:

线性模型的训练总结(图片由作者提供)
我们将忽略 R 平方的值(或调整后的 R 平方),因为我们的兴趣在于估计观察到的解释变量</understanding-partial-effects-main-effects-and-interaction-effects-in-a-regression-model-54e8a127c62d'>对响应变量(即该县的贫困水平)的主要影响。****
另外,我们发现所有解释变量的系数在 p < .001 时都是显著的。
我们的模型估计了每个变量的主要影响如下:
中值年龄
*中位数 _ 年龄*的系数为-0.3317,表明一个县的中位数年龄(岁)每增加一个单位,该县的贫困线以下家庭的百分比就会下降 0.33% 。
房主 _ 空置率
**房主空置率的系数为 0.9198,表明空置率每增加一个百分点,该县贫困线以下家庭的空置率就增加一个百分点,反之亦然。这是一个显著的尺寸效应。
受过大学或高等教育的百分比
该代理变量的效果具有类似于 Median_Age 的大小和方向。系数为-0.3169,这意味着该县拥有大学或更高学位的人数每增加 3 个百分点,贫困线以下的家庭数量就减少 1 个百分点。
相关性而不一定是因果关系
请注意,我们已经小心翼翼地用相关性来表述我们所有的结果。没有因果分析的好处,根本不清楚所谓的“解释性”变量是否影响一个县贫困线以下家庭的百分比,或者影响箭头是否指向其他方向。
更高水平的教育很可能导致贫困的减少。人们还可以说,富裕家庭和一般高于贫困线的家庭比贫困线以下的家庭更容易负担大学教育。中位年龄和住房需求对贫困水平的影响甚至更加模糊。
总结和要点
- 当回归模型中的一个或多个解释变量无法观察或测量时,我们可能希望使用代理变量来代替这些不可测量的变量。
- 理想情况下,代理变量必须满足三个要求:1)它应该与主要变量相关(最好是强相关), 2)它不应该是内生的,3)除了作为主要变量的替代变量之外,它不应该给模型带来额外的信息。
- 由于主要变量无法观察到,所以构成一个好的代理的大部分要求本质上是不可测试的。因此,我们必须使用我们的判断来确定所选择的代理变量是否以及在何种程度上满足或违反这些要求。
- 面对不可观察的解释变量,总的来说,最好是包括代理,并处理模型系数中的精度损失,而不是忽略代理,并承受模型拟合不良和可能有偏差的系数估计的后果。
下面是本文中使用的源代码:
这里是数据集的链接:美国社区调查。
参考文献、引文和版权
数据集
形象
本文中的所有图片的版权归 CC-BY-NC-SA 所有,除非图片下方提到了不同的来源和版权。
如果您喜欢这篇文章,请关注我的Sachin Date以获得关于回归、时间序列分析和预测主题的提示、操作方法和编程建议。
如何在 Julia 中使用 PyCall.jl: Python 库
原文:https://towardsdatascience.com/how-to-use-pycall-jl-python-libraries-in-julia-7f0e7a47ba70
Julia 的 PyCall 包的概述,以及更多关于它的有用性和可用性的内容

这么多书,简直就像两个图书馆的总和!(图片由像素上的像素提供)
介绍
在我们今天生活的计算世界中,引入一种新的编程语言的最大问题之一将是它的生态系统。它类似于操作系统和计算机现在普遍面临的一些问题。许多技术是建立在工具之上的,这些工具要么已经过时,要么我们知道用我们新发现的知识可以写得更好。尽管这可能会有问题,但在某些情况下,当涉及到软件时,抱着“如果它没有坏,就不要修复它”的心态是有意义的,除非我们想用余生来重写为 90 年代的系统编写的代码。
我的观点是,从零开始创建一个全新的生态系统是非常困难的,特别是对于一种从无到有并试图在没有生态系统的情况下建立自己的语言。在大多数情况下,除非这种好处真的值得,否则我怀疑大多数人甚至会采用一种新语言。有很多新的东西需要学习,从语法到错误输出和调试,到如何处理包,甚至安装一种编程语言都可能令人困惑,正如我在 Fedora 系统上安装标准元语言时很好地展示的那样。这还没有触及学习如何对抗一个全新的工具生态系统的挑战。
幸运的是,对于那些用渴望的眼光看着 Julia 的人来说,你真的不需要学习 Julia 生态系统来尝试从 Python 开始。这可以通过 PyCall.jl 包来完成,正如人们所预料的那样,这个包用于调用 Python 包。Julia 处理这个问题的另一种方式是多重调度通常如何使整个生态系统更加一致,这一点我不会在本文中讨论,但肯定值得注意。我最近写了另一篇文章,详细介绍了如何在自己的代码中使用这种技术,以及它对 Julian 软件包的影响。事不宜迟,让我们来看看 PyCall.jl。
设置
我必须承认,我已经很久没有使用 PyCall.jl 包了。坦白地说,自从我上次接触这个软件包以来,Julia 的生态系统已经有了显著的增长,这种语言的用户群也是如此。为了证明这一点,我想让你们注意一下这张由洛根·基尔帕特里克提供的图片,他是一位 Julia 语言的开发者和社区倡导者(Julia info 的优秀用户)。):
我想说的是,随着 Julia 的广泛应用,PyCall.jl 包就变得不那么需要了,因为很多时候已经用 Julia 编写了某种功能的模块实现。虽然有时文档和这些东西可能有点参差不齐,但一般来说,大多数 Julia 包都相当一致且易于使用。也就是说,PyCall.jl 仍然是 Python 开发人员进入 Julia 的一个途径,而不需要学习一个全新的工具生态系统,这就是我希望这篇文章针对的目标。考虑到这种语言在过去几年中的发展,数据科学领域的人可能至少应该以某种方式熟悉这种语言。也就是说,用 PyCall 来尝试它是体验 Julia 的一种真正安全的方式,不会产生信息过载。此外,对于使用像 Tensorflow over Flux.jl 这样的软件也有一些争论,例如,在工业中仅仅是为了成熟——此外还有 Google——出错的能力。
今天我们将看看如何使用 Pandas 来管理 Julian 数据,尽管 DataFrames.jl、Julia 的内置线性代数以及 Julia 中的其他解决方案非常强大,但是叫 Pandas 有点傻,很难学习一个全新的软件包和一门全新的语言,所以让我们开始吧!
使用 PyCall.jl 其实真的很简单,第一步就是安装。我们可以通过 Julia 中的包 Pkg 的方法或者我倾向于使用的 Pkg 命令 REPL 来使用 Pkg。在朱莉娅·REPL 中按下]就可以进入 REPL,这非常方便。
using Pkg; Pkg.add("PyCall")julia> ]pkg> add PyCall
如果你不像我一样使用 Linux,你也需要在你的机器上安装 Python 的标准版本。当然,您将在 Linux 上需要它,但是您可能已经有了它。如果一切正常,您现在应该能够使用 PyCall 了。
using PyCall
如果您想指定使用特定版本的 PYTHON,您需要定义一个环境变量 Python,然后使用 Pkg 构建 PyCall.jl。
# ENV["PYTHON"] = "/usr/bin/python3.7"
bash> juliajulia> ]
pkg> build PyCall
使用 PyCall.jl
现在我们已经在机器上安装了 PyCall,让我们实际开始使用这个包。对于接下来的部分,我将在一个小示例笔记本上工作。这里有一个 Github 上的笔记本链接,如果你想自己尝试这段代码的话:
https://github.com/emmettgb/Emmetts-DS-NoteBooks/blob/master/Julia/pycall basic examples.ipynb
执行 Python
我们实际上可以使用字符串在当前的 Julia 环境下执行整个 Python 环境。为了使一个字符串成为 Python 代码,我们只需在多行字符串前添加 py,如下例所示,我创建了一个 add20 函数:
py"""
def add20(x):
return x + 20
"""
我们可以使用相同的语法,在某种程度上,通过提供 Julia 中的参数来插入它,以获得 Julia 中的返回:
py"add20"(20)40
这被视为常规返回,此外,类型保存得非常好,这令人印象深刻。
导入 Python
虽然像我们刚才做的那样交替使用 Python 和 Julia 非常令人兴奋,但这可能也不是人们试图从 Julia 中使用 Python 的原因。这个包最流行的用法可能是从 Python 生态系统中导入包。正如我之前所说的,我们将在 Julia 中制作一个熊猫数据框,这实际上非常简单。第一步是使用 pyimport()方法导入我们的包:
pd = pyimport("pandas")PyObject <module 'pandas' from '/usr/lib64/python3.9/site-packages/pandas/__init__.py'>
这给了我们一个 PyObject 类型的返回——这很好。该模块能够拥有 Python 生态系统中的许多相同的行为和语法,这一切都是通过这一行代码完成的——这简直是荒谬的声明性。我们还可以按照您的预期创建数据框:
df = pd.DataFrame(Dict(:A => [5, 10, 15], :B => [5, 10, 15]))
此外,作为新构造对象的子对象的函数可以按照您预期的方式调用:
df.head()

(图片由作者提供)
df.describe()

(图片由作者提供)
表演
人们转向 Julia 的一个重要原因是因为性能,正如这篇著名的博客文章所详述的,Julia 最初是被创造出来的。也就是说,在从 Python 切换到 Julia 的过程中,也许没有一个 Julia 包可以做你需要做的事情,一个很好的问题是,我们是否会获得任何额外的性能优势或障碍,因为我们从 Julia 调用 Python,而不仅仅是编写 Python。我想说的第一件事是内存使用。
记忆
我在使用 PyCall 时遇到的一个大问题是,当涉及到内存使用时,PyCall 真的会给你留下很多想象空间,尤其是在某种专业设置或开发环境中。也就是说,当 Julia 使用 Python 时,确实没有很好的方法来检查内存使用情况。了解这一点会很好,因为现在我们所得到的基本上是一个指向 Julia 中定义的 PyObject 的指针。这是有问题的,因为它们总是占用相同数量的内存。我觉得奇怪的是,varinfo()甚至不包含 PyCall 模块,还定义了一个不占用内存定义为 nothing 的 p。
varinfo()

(解释或更正刚说过的话)我是说..很难表达将 Python 这样的大模块加载到 8 * 8 位中是多么的不可能。对于那些不了解位和字节的人来说,给定文本文件中的每个字符就是一个字节的数据。此外,我们的 DataFrame 肯定不能是八个字节,因为它包含六个整数和两个标签,每个都是一个字节,再加上对象内部的方法,这些方法远不止一个字节。所以数据本身可能是八个字节,但对象作为一个整体肯定不是。确实有一些可以导入和使用的分析模块,但是所有这些都不会给我们任何关于 PyCall 使用了多少内存的信息,这正是我真正想要的。所以不幸的是,没有很好的方法来分析内存使用情况——这可能也是切换到 Julia 包的一个很好的原因,但是计算时间呢,这可能是 Python 的最大障碍。
口译时报
如你所料,我们将使用不同的笔记本来比较 Python 和 Julia。记住,这些将是每个计算的内核化版本,这肯定会影响时间——然而,根据我的经验,IJulia 通常能够以与 REPL 相同的速度运行,有时会落后几毫秒,所以我不确定这是一个大问题。我将使用标准的库模块 timeit 来为 Python 计算计时,并且我将为 Julia 使用@time 宏。进口也将包括在测试中。
[@time](http://twitter.com/time) begin
df = pd.DataFrame(Dict("A" => r.randn(1000000), "B" => r.randn(1000000)))
endr = pyimport("numpy.random")
重要的一点是,我重启了内核。Julia 有很多 JIT 缓存和预编译的魔法,所以当涉及到这类事情时,这绝对是我会做的事情,以获得准确的基准。这是我们的 Julia 结果:

左边是 Julia,右边是 Python,只是澄清一下。
在这种情况下,Julia 确实稍微落后于 Python。然而,在我看来,这其实是非常令人印象深刻的!Julia 在使用另一种语言的包时仍能这么快,这一事实令人印象深刻。我还剩下大约 1g 的内存(我讨厌这里),所以让我们滥用我的计算机,把那台改成 3,这样我们就可以看到 Julia 是否开始用更多的值出错。

随着我们增加值的数量,我们看到 Julia 慢慢开始赶上 Python。PyCall 显然有一些开销,而且我们使用的是 Python。老实说,这是意料之中的,老实说,我真的认为 Python 的 Julia 接口会导致比现在更多的开销。这确实说明了这个模块是多么令人印象深刻。
有人给我的电脑带来了一个除颤器。
结束语
PyCall.jl 已经存在一段时间了,我们可以看到 Julia 代码可以变得多么健壮和快速。尽管我们在这个实例中使用了 Python,但 Julia 代码运行解释后的代码的速度仍然比纯 Python 代码快几毫秒。令人印象深刻!当然,这里真正重要的可能是 FORTRAN 或 C 代码,但无论哪种方式,我们通过两层语言调用代码并从中获得如此大的收益仍然是非常激进的。
所有这些都证明了 Julia 是一门很棒的语言,而且 PyCall.jl 是入门 Julia 的好方法。在学习 Julia 生态系统时,基本上不需要做任何工作,人们可以使用他们一直使用的相同的软件包,并在 Julia 中获得一定程度的经验。我认为使用 PyCall.jl 慢慢接近 Julia 是一个非常好的主意,PyCall 的性能不会对任何试图这样做的人造成很大的阻碍。对于我的 Pythonista 读者,我希望这篇文章能让你想到在 Julia 中入门是多么容易。正如我前面提到的,这种语言正在迅速发展。老实说,我也不认为现在是学习它的最好时机,因为社区和生态系统现在都在爆炸。
最后一点,我是一名数据科学家,因此我对数据科学感兴趣,我真的不介意关于数据科学或一般技术相关主题的直接消息、回复和类似的东西(不是垃圾邮件或奇怪的提议)。如果您对让 Julia 使用您的机器感兴趣,并且不知道从哪里开始,或者只是对共享一些信息感兴趣,请随时联系我!谢谢你读这篇文章,它对我来说意味着整个世界,我真的很感激!
如何使用 Python Lambdas
原文:https://towardsdatascience.com/how-to-use-python-lambdas-8d141e378ce
Python 中 Lambda 函数的介绍

Python lambdas 是所谓的匿名函数,使用它可以快速定义有多个输入但只有一个输出的函数。这样的匿名函数不仅在 Python 中使用,在其他编程语言如 Java、C#或 C++中也使用。
Python 中普通函数是如何定义的?
在 Python 中,函数的定义用“def”标记。然后定义其名称,带名称的参数列在圆括号中。以下名为“sum”的函数只接受两个参数并将它们相加:
这些函数的优点是可以通过唯一的名称调用它们。这有助于更好地描述功能,并在以后再次引用该功能。此外,参数还可以有名称来帮助更好地理解结果。另一个优点是显式定义的函数也可以输出多个参数。
例如,我们的初始函数除了输出和之外,还可以输出两个参数之间的差:
什么是匿名函数?
然而,在 Python 中还有第二种定义函数的方式。在所谓的匿名函数的帮助下,这些可以用几行来定义,通常甚至只有一行。这些匿名函数也存在于其他编程语言中,比如 Java 或 C#。在 Python 中,参数“lambda”用于此,这也是为什么 Python 中的匿名函数经常被简单地称为 Python lambdas。
这些函数可以很容易地用参数“lambda”、变量的命名以及函数应该计算的表达式来定义:
这个例子也清楚地说明了为什么 Python lambdas 是匿名函数:函数本身不能被赋予名称,它只能存储在一个变量(“function”)中。
因此,如果我们想使用 Python Lambdas 从最初的示例中重新创建 sum 函数,如下所示:
如您所见,可以向 Python Lambdas 传递多个参数。但是,我们不能用 Python lambdas 重新创建函数“sum_difference ”,因为匿名函数只能输出一个结果。因此,必须定义两个不同的函数并调用两次:
为什么应该使用 Python Lambdas?
在一些情况下,使用 Python lambdas 会很有用:
- 当定义了只有一个输出和少量输入的简单函数时。在大型项目中,为了节省空间和避免不必要的混乱,不明确定义这样的函数是有意义的。此外,还可以避免继承等问题。在广泛的课堂上。
- 如果函数只使用一次,同样的论点也是有效的。那么没有显式定义也可以,因为无论如何没有人必须再次访问该函数。
- 此外,匿名函数可以确保函数的内容更快更容易理解,因为它是在一行中定义的。显式定义函数会导致失去一些可理解性。
- Python 中有一些函数,比如“filter”或者“map”,是把函数作为输入的。所以在这些情况下,使用 Python Lambda 是有意义的。
filter()函数如何与 Python lambdas 配合使用?
函数“filter()”可用于过滤满足特定条件的列表。结果是一个新的列表,它只包含那些满足条件的元素。如果没有显式函数,您也可以按如下方式求解:
函数“filter()”总共有两个输入,首先是用于过滤的条件,其次是需要过滤的输入列表。这个过滤条件必须是一个函数,这就是 Python lambdas 适合这个的原因:
对于这样的应用程序,Python Lambdas 当然也是一个最佳选择。
这是你应该带走的东西
- 匿名函数用于快速定义不需要名字的函数。
- 如果函数只使用一次,或者如果您希望保持变量和函数的命名空间较小,这将非常有用。
- 在 Python 中,匿名函数与参数“lambda”一起使用,这就是为什么 Python 中的匿名函数也被称为 Python lambdas。
如果你喜欢我的作品,请在这里订阅https://medium.com/subscribe/@niklas_lang或者查看我的网站* 数据大本营 !还有,medium 允许你每月免费阅读 3 篇 。如果你希望有无限制的 访问我的文章和数以千计的精彩文章,请不要犹豫,点击我的推荐链接:【https://medium.com/@niklas_lang/membership】每月花$5***获得会员资格**
*</4-basic-sql-commands-every-new-data-scientist-should-know-ba02e40bfc1a> </4-basic-commands-when-working-with-python-dictionaries-1152e0331604> *
如何使用 Python 获取地球上任何地方的温度
解释使用 Python 中的 shapefiles 裁剪网格化 NetCDF 数据的过程的教程

美国宇航局在 Unsplash 拍摄的照片
答假设您对 NetCDF 数据格式有基本的了解(如果没有,您可以在这里查看我关于 NetCDF 的介绍文章),在这篇文章中,我将展示计算和可视化地区温度的不同方法。为此,我们将使用 shapefile。 Shapefile 是一种数据格式,利用点、线和多边形等矢量特征来表示地球上的不同区域(如国家、城市、湖泊等)。
等高线和等高线
我用的是哥白尼免费下载的 NetCDF 数据(这个链接上的‘2m 温度’)。哥白尼是一个由欧盟及其机构管理的地球监测项目。他们提供了一个名为气候数据存储的 API 来下载与任何气候变量相关的任何数据。
使用 NetCDF 数据,我们可以简单地绘制如下所示的等值线图:

等高线图(作者 GIF)
我用所有 12 个月的等高线图制作了这张 GIF 图,并固定了彩条的范围。我们可以计算每个国家的平均温度,然后将其可视化为 [choropleth](https://en.wikipedia.org/wiki/Choropleth_map#:~:text=A choropleth map (from Greek,each area%2C such as population) 地图,而不是将其可视化为等高线。
choropleth 地图可视化如下所示:

Choropleth 地图(GIF 由作者提供)
在这两种情况下,较冷的地方和较热的地方可以用蓝色和红色清楚地区分。choropleth 地图使用整个国家的平均值,因此,其表示与等值线地图略有不同。不同地图的使用取决于不同的用例。
使用 NetCDF 数据和 shapefiles 导出 choropleth 图的想法很有趣,因为它可以在任何级别和任何分辨率下完成。例如,我们可以制作一张地图来比较不同的国家,一个国家中不同的州,甚至是不同的城市。

Shapefile 上的网格点(作者提供照片)
上图显示了放置在一个地区(美国及其邻国)的一堆网格点。为了计算平均温度,我们收集了位于美国境内的所有点(例如),然后简单地取所有点的平均值。
以下 python 库用于此目的:
- Xarray —读取和操作 NetCDF 文件。
- Geopandas 读取 shapefiles
- Rioxarray 和 Shapely 裁剪/收集多边形内部的网格点。
- 底图,Plotly 进行可视化。
使这一切成为可能的关键是 NetCDF 数据中温度值的插值。
插入文字
这也称为空间或地理空间插值。插值是使用采样值估计未采样区域的值的过程。
NetCDF 数据在纬度和经度之间等间距网格化。我用来绘制等高线图的数据在网格的两个方向上的分辨率都是 0.25。一度大约是 100 公里,所以我们可以说我们的分辨率是 25 公里。换句话说,我们在 X 和 Y 方向上每隔 25 公里有一个温度值。
如果我们有一个足够大的区域,我们会看到一个或多个温度值位于该区域的边界内。在我使用的 NetCDF 数据的情况下,我们希望该区域大于 25 km,至少在一个轴上,在该区域内有一个温度值。
如果该地区是一个大国,如印度、美国、中国等,这就简单了。如果国家很小,比如梵蒂冈、摩纳哥和太平洋岛屿,那就很棘手了。在绘制等高线图时,我不得不忽略一些较小的国家,因为不可能计算这些国家的平均温度。
使用插值,我们可以改变这个分辨率(增加或减少)。一种流行的方法是使用线性插值。Xarray 有一个叫做.interp_like()的方法,使得插值更容易。
在上面的代码中,我使用线性插值技术将 NetCDF 数据的分辨率从 0.25 转换为 0.05。与示例不同,您还可以使用全边界(纬度为-90 至 90,经度为-180 至 180)对整个数据集进行插值。
在 shapefile 上剪辑
为了裁剪或收集国家边界内的网格点,我们可以使用下面的代码。
世界行政边界形状文件可以在这里下载。
t2m.rio.clip(data.geometry.apply(mapping), data.crs)是我们裁剪网格点的代码部分。代码的扩展只是对所有温度值进行平均。如此复杂的过程只用几行代码就完成了。
每当国家边界对于网格来说太短时,就会出现错误。这些国家将被添加到列表中,并从 shapefile 数据框中移除。它们不会被用于可视化。如果您想包含所有内容,那么您可以使用更高的分辨率,例如 0.01(网格点相距 1 km)。

显示每月平均国家温度的数据框(df_temp)
等值区域图
由于我们有每个国家 12 个月的温度值,我们可以创建 12 个 choropleth 图,并轻松地将它们组合成 GIF。
首先,我们需要将形状文件和温度数据框合并成一个。然后,我们创建一个循环并保存所有 12 个 choropleth 图像,这些图像稍后将用于制作 GIF。
下面是创建 choropleth 地图和 GIF 的代码:
geo数据帧是这样的。我只保留了必要的列,并从 shapefile 中删除了其余的列。

追加和熔化 DFs 后的数据帧(geo)
使用相同的代码,这是我为法国制作的 choropleth 地图 GIF:

法国 Choropleth 地图(作者 GIF)
很酷,不是吗?
结论
NetCDF 数据足够强大,可以处理气候数据科学,并生成一些看起来很酷的 choropleth 图或等高线图。这个想法也可以扩展到其他气候变量。地图和用例会根据您考虑的变量而变化。
感谢阅读,干杯!
**Want to Connect?** You can reach me on [LinkedIn](https://www.linkedin.com/in/rohithteja/), [Twitter](https://twitter.com/rohithtejam), [GitHub](https://github.com/rohithteja) or my [Website](https://rohithteja.github.io/).
如何在 SQL 中使用集合运算
原文:https://towardsdatascience.com/how-to-use-set-operations-in-sql-53d57c4f7b77
了解并集、交集和例外运算符

雷纳多·凯文在 Unsplash 上的照片
数据专业人员通常需要从多个来源收集信息。虽然连接的概念被广泛教授,并经常出现在面试问题中,但它们的近亲集合运算符却不经常受到同样的关注。
这两种方法都从多个来源收集数据,可以是子查询表。联接将结果作为新列返回,并要求表之间有一个公共列。

连接的结果查询将添加新数据作为列。图由作者制作。
相比之下,集合运算符将结果作为新行返回,不一定需要表中的任何公共行。

集合运算符的结果查询将添加新数据作为行。图由作者制作。
集合运算符包括 UNION (ALL)、INTERSECT 和 EXCEPT/MINUS。为了演示如何使用它们,将使用两个表生成一个样本数据集:clients 和 transactions。客户端表将简单地列出一个客户端 ID 和它们的名称。交易表将包括交易 ID、相应的客户 ID 和交易值。
CREATE TABLE clients(
id INTEGER PRIMARY KEY,
name CHAR(255)
);CREATE TABLE transactions(
id INTEGER PRIMARY KEY,
client_id INTEGER,
value INTEGER
);
UNION 运算符
在形式数学中,并是两个集合的完全组合。类似地,在 SQL 中, UNION 操作符组合两个 SELECT 语句的结果,并删除任何重复的结果。
SELECT *column_1* FROM *source_1*UNIONSELECT *column_2* FROM *source_2;*
union 操作符的语法非常简单。第一行代码给出了一个 SELECT 语句,从一个源中提取一列,这个源可以是一个表,也可以是一个子查询。第二行提供了 UNION 运算符。第三行列出了要与第一个语句合并的下一个 SELECT 语句。
请注意,两个 SELECT 语句必须包含相同数量的列。
SELECT id FROM clients
UNION
SELECT client_id FROM transactions;
在一个简单的例子中,上面的查询返回 clients 表中的所有 id 以及 transaction 表中列出的所有客户端 id。默认情况下,UNION 运算符将删除两个 SELECT 语句之间的任何重复项。

包含所有 id 的一列
若要包含重复项,可以改用 UNION ALL 运算符。它使用完全相同的语法,可以在与 UNION 运算符相同的上下文中使用。
SELECT id FROM clients
UNION ALL
SELECT client_id FROM transactions;
请注意,UNION 操作符与所有其他集合操作符一样,不需要 select 列之间的正式关系。出于实用性考虑,选择了 id,但是在 UNION 中可以用于在同一列中返回客户名称和事务值。
SELECT name FROM clients
UNION
SELECT value FROM transactions;
虽然这是有效的语法,但结果的值是可疑的:

交易值和客户端名称都显示在同一(截断的)列中。
集合操作符组合信息而不考虑关系或缺乏关系的能力为它们提供了很大程度的灵活性,特别是在那些设计不良或维护混乱的数据库中。不幸的是,同样的灵活性也允许无意义的结果。
交集运算符
可以预见的是, INTERSECT 操作符在数据相交的地方连接行。换句话说,它只组合两个查询共有的数据。

该查询只返回 3 和 4,因为它们在两个表中是相同的。图由作者制作。
INTERSECT 运算符的语法与 UNION 运算符完全相同。
SELECT *column_1* FROM *source_1*INTERSECTSELECT *column_2* FROM *source_2*
像前面一样,第一行和第三行专用于将要组合的 SELECT 语句。在它们之间,INTERSECT 运算符连接它们。
SELECT id FROM clients
WHERE id % 2 = 0INTERSECTSELECT client_id FROM transactions
WHERE client_id % 3 = 0;
在上面的示例中,第一个 SELECT 语句查询 clients 表中所有能被 2 整除的 id。然后,INTERSECT 操作符将它与第二个 SELECT 语句结合起来,后者从 transactions 表中提取所有可被 3 整除的客户 id。
因此,该查询将返回所有可被 6 整除的客户端 id,因为所有可被 6 整除的数字也可被 2 和 3 整除。

所有 6 的倍数的 id
EXCEPT 运算符
当 INTERSECT 查询两个源之间的公共数据时,EXCEPT 运算符返回一个源独有的数据。值得注意的是,有些数据库不接受 EXCEPT 关键字,而是使用 MINUS。这两者没有区别,因为它们都具有相同的功能。

该查询返回 1 和 2,因为它们对于表 1 是唯一的。图由作者制作。
EXCEPT 的语法与其他集合运算符完全一样。
SELECT *column_1* FROM *table_1*EXCEPTSELECT *column_2* FROM *table_2*;
或者,使用减号关键字:
SELECT *column_1* FROM *table_1*MINUSSELECT *column_2* FROM *table_2*;
举一个具体的例子,可以编写一个简单的查询来确定哪些客户 id 出现在事务表中,而没有出现在客户表中。在理想情况下,事务表中的客户机 ID 不会不出现在客户机表中,但是有时糟糕或仓促的设计选择加上糟糕的维护也会产生类似的结果。
因此,编写一个快速查询来确定哪些客户机 id 需要添加到 clients 表中,这将证明是非常有益的。
SELECT client_id FROM transactions
EXCEPT
SELECT id from clients;
第一个 SELECT 语句查询事务表中的所有客户机 id,但是 EXCEPT 语句强制它排除第二个 SELECT 语句,后者提取 clients 表中已经存在的所有 id。

事务中的所有客户端 id,但不在客户端中
然后,结果提供了一个需要添加到 clients 表中的所有客户机 id 的列表(以及与数据库管理员关于实施表间关系的对话)。
结论
尽管集合运算符的使用频率不如联接,但它仍然可以通过向结果中添加行来连接数据。忽略数据源之间的任何关系,它们在编写查询时提供了无与伦比的灵活性。由于有大量的用例,它们是 SQL 中不被重视的部分。
如何在 Power BI 中使用小倍数来显示多个度量值
假设您想用相同的轴显示不同的度量,例如,地理的时间。在这里,我向您展示如何在一个视图中实现这一点。

Justus Menke 在 Unsplash 上拍摄的照片
介绍
假设您有许多度量,并且您希望并排查看它们,所有度量都使用相同的轴,例如,日期、地理、产品类别等。
您需要添加一个视觉效果并添加第一个度量值。然后复制此视觉效果,更改度量,并在报告页面的可用空间内对齐它们,使它们看起来完全相同。
它可能看起来像这样:

图 1——具有不同度量的多种视觉效果(由作者提供)
好吧,我们可以调整外观,使它看起来更好,在上面的例子,但这都是额外的工作,我不想在这里涵盖。
参见我关于信息设计的文章:
https://medium.com/cloud-workers/three-simple-rules-for-information-design-52db54864b47
因为我们是有效率的,而不是懒惰的,我们想以更好的方式做这件事。
能够使用小倍数在一个视图中显示多个度量值,如下图所示,是不是很酷?

图 2 —小多次波的视觉效果(作者提供的图片)
让我们深入探讨一下如何实现这一解决方案。
履行
我们首先需要一个表,其中包含我们希望在目标视图中显示的所有度量。
如下图所示的表格:

图 3 —测量选择表(作者提供的图)
我通过 Power BI Desktop 中的输入数据功能添加了这个表。
新表格不需要与任何其他表格有任何关系,因为它仅用于引用所需的测量。
我手工添加了 MeasureID 列。
现在,我需要一个度量来检查在当前过滤器上下文中选择了哪个 MeasureID,并选择正确的度量。
该度量可能如下所示:
Dynamic Measure =
IF ( HASONEVALUE(‘Measure Selection’[MeasureID])
,SWITCH(MIN(‘Measure Selection’[MeasureID])
,1, [Online Sales (By Order Date)]
,2, [Online Sales (By Ship Date)]
,3, [Online Sales (By Due Date)]
,4, [Online Sales (By Customer)]
,5, [Last Stock by Date Var 2]
,6, [Sales Large Amount_2]
,7, [Weighted Average Sales Amount]
,8, [Margin]
,9, FORMAT([Margin %], “Percentage”)
,[Retail Sales]
)
,[Retail Sales]
)
首先,我必须检查当前过滤器上下文中是否只有一个 MeasureID。我用 IF() 和 HASONEVALUE() 函数来实现这一点。
如果在实际的筛选器上下文中存在多个值,那么我将跳到 If()的 ELSE 分支,并显示[零售]度量值。在我的例子中,我将这个度量定义为我的“默认度量”来显示。
如果只为 MeasureID 选择了一个值,我可以检查它并显示相应的度量。
现在,我可以将列度量值名称作为一个小倍数添加到目标视图中:

图 4 —将度量选择列作为小倍数添加(作者提供的图)
改善和配置视觉效果
现在,你可以看到这样的画面:

图 5 —结果非常小或单元不匹配的视觉效果(作者提供的图片)
红色标记区域显示的测量值要么非常小,要么以百分比形式计算。这两个措施在这里不是很有用。
我们可以做些什么来消除这些结果:
- 我们可以从添加到视觉的度量中移除这些度量
- 我们可以操作过滤器设置来移除它们,而不改变测量:

图 6——排除不可用度量的过滤器设置(由作者提供)
现在,结果可能是这样的:

图 7 —排除测量后的结果(作者提供的数据)
最后,我们可以通过更改格式选项来改变小倍数的外观:

图 8 —配置小倍数(作者提供的数字)
在这里,我们可以改变列和行的数量来设置我们需要它。
我们可以为小倍数之间的填充设置一个值,或者我们可以启用自定义填充并根据需要设置行和列的填充。
添加更多度量
现在,假设我们想要查看实际值和前一年的值。
首先,我们必须为所有基本度量创建上一年的(PY)度量。
假设我们希望对以下度量执行此操作:
- 在线销售(按订单日期)
- 在线销售(按发货日期)
- 在线销售(截止日期)
- 在线销售(按客户)
- 零售
我为它们中的每一个创建了 PY 度量,并且创建了一个如上所示的度量:
Dynamic Measure (PY) =
IF ( HASONEVALUE(‘Measure Selection’[MeasureID])
,SWITCH(MIN(‘Measure Selection’[MeasureID])
,1, [Online Sales (By Order Date) PY]
,2, [Online Sales (By Ship Date) PY]
,3, [Online Sales (By Due Date) PY]
,4, [Online Sales (By Customer) PY]
,[Retail Sales PY]
)
,[Retail Sales PY]
)
将这个度量添加到视觉效果后,我得到了需要的结果:

图 9 —前一年测量的结果(作者提供的数据)
现在你可以玩你的幻想来扩展你的解决方案。
构建度量列表
构建这样一个解决方案的一个挑战是管理您的度量。
通常,您的模型不是静态的,而是随着进一步的度量不断扩展,您希望将这些度量集成到您的动态度量中。
幸运的是,我们可以使用 DAX Studio 和 Analysis Services 动态管理视图来获得所有度量的完整列表:
SELECT [Name]
FROM $SYSTEM.TMSCHEMA_MEASURES
以下查询返回所有度量值的列表:

图 10 —获得 DAX Studio 中的度量列表(由作者提供)
现在,您可以将列表复制到 Excel 中,并添加索引列。此后,可以将新表复制到度量选择表中。使用 Excel,您甚至可以通过简单的公式从表中生成 DAX 度量值。
然后您可以将生成的度量复制到数据模型中。
结论
Power BI 中的小倍数是增加报告中信息密度的一个很好的功能。
通常,我们以小倍数的形式添加属性,以便按维度元素拆分数据。
这里显示的方法有助于创新地在一个视图中显示多个度量。

里卡多·安南达尔在 Unsplash 上拍摄的照片
更积极的一点是,您不需要尝试同步度量之间的图表缩放,因为这是自动完成的。
在这一点上,天空是极限,您可以使用小倍数功能以多种方式提高您的报告能力。
参考
小倍数特性的微软文档:在 Power BI 中创建小倍数— Power BI |微软学习
SSAS 动态管理视图的参考文档:Analysis Services | Microsoft Learn 中的动态管理视图(dmv)
我使用 Contoso 样本数据集,就像我以前的文章一样。你可以从微软这里免费下载 ContosoRetailDW 数据集。
Contoso 数据可以在 MIT 许可下自由使用,如这里的所述。
https://medium.com/@salvatorecagliari/membership
下一次数据科学面试的 SQL 聚合函数
原文:https://towardsdatascience.com/how-to-use-sql-aggregate-functions-92f7244a07cb
基础知识回归| SQL 初学者基础知识

作者图片,创建于 canva
在企业的不同层级;我们经常依赖报告或可视化仪表板形式的数据来完成日常任务;比方说,对于审计和法规遵从性(内部和外部),做出决策,衡量成功与否,或者甚至支持一个新业务想法的假设(开发一个新想法)
不管你有什么数据,也不管你存储或维护数据的效率如何。事实上,您需要知道基本的编码来实际检索数据,也就是您的数据有任何价值的时候。
了解基本的 SQL 并不是真正的火箭科学,但它是游戏规则的改变者。如果团队中的每个人都熟练掌握基本的数据检索编码,这将增强整个团队的能力,并减少琐碎任务的依赖性,从而快速做出决策。
SQL 中的聚合函数非常强大,是日常分析中最常用的函数;比如,哪个产品的总收入最高,每个季度的总销售额等等。下面是最常见的聚合函数的快速回顾。
什么是 SQL 聚合函数?
SQL 聚合函数对一组值执行计算并返回一个值。嗯,大多数时候!当通过子句与 GROUP 配对时,它为每个组返回单个值。聚合函数大多是 确定性函数 。
一旦我们看完本文中的例子,这一切都有意义了。
这里,我使用classic modelsMySQL 示例数据库进行演示,它保存了一家汽车零售商的业务数据。下面是 ER 图,以便快速理解,

作者图片
📌侧注
GROUP BY 子句通常与聚合函数一起使用,按一列或多列对结果集进行分组。(基本上是将相似的数据组织成组)
ORDER BY 子句用于按升序或降序对结果集进行排序。默认情况下,它是按升序排列的。一起使用时, GROUP BY 子句总是在 ORDER BY 子句之前。
有子句与相似,其中子句有一些不同。它只能用于属于 GROUP BY 子句或聚合函数的列。我写了一篇关于 SQL 中过滤子句的详细文章,可以在这里随意参考,
DISTINCT 子句用于过滤掉重复项,只提供结果集中唯一的值。
最常见的聚合函数有:
伯爵()
‘计数’这个词的一般意思是确定某物的总数。

作者图片,创建于 canva
类似地,在 SQL 中,COUNT() 返回非空记录或与指定条件表达式匹配的记录的数量。 COUNT() 在不使用 OVER 和 ORDER BY 子句的情况下为确定性函数,在使用 OVER 和 ORDER BY 子句的情况下为非确定性函数。 COUNT() 的常用语法是,
计数(【全部|不同】表达式)
让我们快速查询表 OFFICES ,它保存了世界各地销售办事处的数据,
#offices
SELECT * FROM CLASSICMODELS.OFFICES LIMIT 10;

作者图片
让我们从简单的开始;我们需要统计在世界各地拥有办公场所的不同城市的总数,
#cities that have sales offices.
SELECT DISTINCT
COUNT(DISTINCT CITY) AS TOTAL_OFFICES
FROM
CLASSICMODELS.OFFICES;

作者图片
现在,让我们尝试查询每个国家的办事处数量,
#total number of sales offices in each country.
SELECT
COUNTRY,
COUNT(DISTINCT CITY) AS TOTAL_OFFICES
FROM
CLASSICMODELS.OFFICES
GROUP BY COUNTRY
ORDER BY COUNTRY DESC;

作者图片
第一个查询简单地提供了世界各地办事处的总数,而在第二个查询中,我们根据国家对结果集进行分组,并使用 ORDER BY 子句对结果集进行排序。两个结果集中的办公室总数保持不变,都是 7;唯一的区别是第二个结果集计数被分成不同的组。
COUNT() 可以有几种不同的用法;这里有一些简单的例子,
- COUNT()* 返回表格中记录/行的总数,包括重复值和空值。
SELECT COUNT(*) FROM CLASSICMODELS.OFFICES;

作者图片
- COUNT(1), 1 是非空表达式,因此类似于 COUNT()* 。
SELECT COUNT(1) FROM CLASSICMODELS.OFFICES;

作者图片
- COUNT(column_name) 从特定的 column_name 返回非空的记录数。
SELECT COUNT(COUNTRY) FROM CLASSICMODELS.OFFICES;

作者图片
- COUNT(DISTINCT column _ name)从 column_name 返回非空且唯一的记录数。
SELECT COUNT(DISTINCT COUNTRY) FROM CLASSICMODELS.OFFICES;

作者图片
- COUNT(x THEN y ELSE z END 的情况)返回满足条件的行数。
#USA office locations
SELECT
COUNT(CASE WHEN COUNTRY='USA' THEN CITY ELSE NULL END) AS USA_OFFICE_LOCATIONS
FROM
CLASSICMODELS.OFFICES;

作者图片
- COUNT IF 是 COUNT() 的另一个扩展,它返回匹配条件表达式的记录数。
#country wise count of office locations
SELECT
COUNT(IF(COUNTRY = 'USA', 1, NULL)) AS 'USA',
COUNT(IF(COUNTRY = 'UK', 1, NULL)) AS 'UK',
COUNT(IF(COUNTRY = 'Japan', 1, NULL)) AS 'Japan'
FROM
CLASSICMODELS.OFFICES;

作者图片
SUM( )
在数学中,求和/总和是一系列数字的相加。类似地,在 SQL 中, SUM() 返回所选集中所有非空值的总和。

作者图片,创建于 canva
语法是,
SUM(【所有|不同】表达式)
新手有时候会分不清 SUM() 和COUNT();但是请记住,他们两个是非常不同的。 COUNT() 返回列中非空记录/行的总数,而 SUM() 返回该列中所有值的总和。

作者图片,创建于 canva
表产品保存了一系列可用产品的数据,
#products
SELECT * FROM CLASSICMODELS.PRODUCTS LIMIT 10;

作者图片
比方说,我们需要库存中当前产品总量的信息,
#total quantity available in stock
SELECT
SUM(QUANTITYINSTOCK) as TOTAL_QUANTITY_IN_STOCK
FROM
CLASSICMODELS.PRODUCTS;

作者图片
在商业中,这些信息没有多大用处。现在让我们尝试查询每条生产线的当前总库存数量,并按降序对结果集进行排序。
#total quantity currently in stock for each productline grouped by productline
SELECT
PRODUCTLINE,
SUM(QUANTITYINSTOCK) AS TOTAL_IN_STOCK
FROM
CLASSICMODELS.PRODUCTS
GROUP BY PRODUCTLINE
ORDER BY TOTAL_IN_STOCK DESC;

作者图片
让我们做一些快速销售分析。表 ORDERDETAILS 中的样本数据为:
#orderdetails
SELECT * FROM CLASSICMODELS.ORDERDETAILS LIMIT 10;

作者图片
比方说,我们需要知道每个订单的总销售额,按降序排列,
#total sales for each order placed
SELECT
ORDERNUMBER,
SUM(QUANTITYORDERED * PRICEEACH) AS TOTAL_SALES
FROM
CLASSICMODELS.ORDERDETAILS
GROUP BY ORDERNUMBER
ORDER BY TOTAL_SALES DESC;

作者图片
出于演示目的,我限制了上述查询的结果集。现在,这个结果集也给出了销售额最高的订单号的概念。您可以从这里获取订单号,并进一步分析该订单中包含哪些产品、数量是多少、买方是谁、订单是何时下的、装运状态等。
如果所选集合的所有值都为空值,SUM() 将返回空值。
AVG( )
在数学中,平均值的计算方法是将一系列数字相加,然后除以这些数字的个数。

作者图片,创建于 canva
同样, AVG() 返回所选集合的平均值,空值被忽略。常见的语法是,
AVG
继续产品表,现在让我们找出平均购买价格。

作者图片
*#average buy price
SELECT
AVG(BUYPRICE) AS AVERAGE_BUYPRICE
FROM
CLASSICMODELS.PRODUCTS;*

作者图片
现在让我们对每条生产线进行分析,
*#average buy price for each product line
SELECT
PRODUCTLINE,
AVG(BUYPRICE) AS AVERAGE_BUYPRICE
FROM
CLASSICMODELS.PRODUCTS
GROUP BY PRODUCTLINE;*

作者图片
稍加修饰,将 AVG(BUYPRICE)作为 AVERAGE_BUYPRICE 替换为格式(AVG(BUYPRICE),2)作为 AVERAGE_BUYPRICE* ,结果集将为,*

作者图片
最大( ),最小( )
嗯!这个是简单的一个,顾名思义; MAX() 返回所选集合中的最高或最大非空值,而 MIN() 返回所选集合中的最低或最小非空值。

作者图片,创建于 canva
*#maximum buy price
SELECT
MAX(BUYPRICE) AS MAXIMUM_BUYPRICE
FROM
CLASSICMODELS.PRODUCTS;*

作者图片
*#minimum buy price
SELECT
MIN(BUYPRICE) AS MAXIMUM_BUYPRICE
FROM
CLASSICMODELS.PRODUCTS;*

作者图片
结论
撰写本文的想法是简单快速地回顾一下 SQL 聚合函数的基础知识。关于这些还有更多值得探索的地方,下面是一些入门资源,
- MySQL 样本数据库
- MySQL 官方文档
- 聚合函数简介
- Hackerrank 或 Leetcode 练习基础/中级/高级 SQL 聚合问题。
快乐学习!
如何在 SQL 中使用子查询
原文:https://towardsdatascience.com/how-to-use-subqueries-in-sql-da660694b8e3
了解如何使用子查询使您的 SQL 查询更加灵活,并减少代码混乱

照片由casparrubin@ unsplash . com 拍摄
【免责声明:此帖子包含一些我的 Udemy 课程的附属链接】
子查询是一个很酷的概念,我们可以在用结构化查询语言(SQL)编程时使用。从问题开始:有时我们希望访问查询上下文之外的数据,以过滤行或基于聚合度量执行一些特殊的过滤。当我们想要这样做时,我们可能会陷入创建太多检查点的陷阱,这些检查点用几个临时表相互依赖。这些临时对象不仅会占用我们服务器的宝贵空间,还会使代码更难调试,更令人困惑。
子查询是一个简单的 SQL 概念,它将帮助我们将几个查询语句压缩成一个语句。有三种主要的用例(至少我个人经常使用,但还有更多)依赖于子查询:
- 用子查询访问聚合。
- 用另一个表的上下文过滤表中的行。
- 执行双层聚合,如平均值或总和的平均值。
在本帖中,我们将研究一些如何在 SQL 中使用子查询的例子,以及它们如何为您的数据管道带来灵活性!
如果你想提高你的 SQL 游戏,使用子查询可能会有很大的不同。通过将它们添加到您的工具带上,您将编写更加有组织的查询,这些查询不依赖于大量的连续步骤,从而更容易减少您编写的代码量。不利的一面是,它们可能比它们的 join 替代品慢一点——所以,接下来让我们来研究一下它们!
创建数据
对于这个例子,我们将使用来自两个假设商店的数据。假设我们有两个表,一个用于store_a,另一个用于store_b。每个表都记录了客户的购买情况,其构建方式类似于典型的事实表,这意味着每次购买都与以下内容相关联:
- 记录它的雇员 id。
- 在交易中购买商品的客户标识。
- 对于购买的每个产品,每个购买行都要相乘。
- 每种产品都有特定的价格。
让我们为两家商店创建并填充虚拟数据:
create table sandbox.store_a (
transaction_id int,
employee_id int not null,
customer_id int not null,
product_id int not null,
price_paid numeric(19,4)
);create table sandbox.store_b (
transaction_id int,
employee_id int not null,
customer_id int not null,
product_id int not null,
price_paid numeric(19,4)
);insert into sandbox.store_a (
transaction_id, employee_id, customer_id, product_id, price_paid
) values
(1, 1, 1, 1, 9.99),
(1, 1, 1, 2, 7.99),
(1, 1, 1, 3, 3.99),
(1, 1, 1, 4, 2.99),
(2, 1, 2, 1, 9.99),
(2, 1, 2, 3, 3.99),
(3, 2, 1, 1, 9.99),
(4, 2, 1, 2, 7.99),
(4, 2, 1, 3, 3.99),
(5, 2, 2, 1, 9.99),
(6, 1, 3, 1, 3.99);
insert into sandbox.store_b (
transaction_id, employee_id, customer_id, product_id, price_paid
) values
(1, 5, 10, 1, 9.99),
(1, 5, 10, 3, 3.99),
(1, 5, 10, 4, 2.99),
(2, 1, 11, 1, 9.99),
(3, 1, 11, 3, 3.99),
(4, 6, 11, 1, 9.99),
(4, 6, 11, 2, 7.99),
(5, 7, 10, 3, 3.99);
我们的表格如下所示:

商店 A-交易表-按作者分类的图像

商店 B —交易表—按作者分类的图像
每个商店都有不同顾客购买的不同商品。为了了解这种情况下的子查询,我们将使用两个用例:
- 如何筛选在
store_b买过至少三次的产品? - 我们如何计算
store_a中交易的平均值? - 在
store_a上,我们如何过滤价值高于平均值的交易?
使用子查询作为过滤器
让我们从我们的第一个例子开始——我们如何过滤在store_b中至少购买过三次的产品的行?
如果我们必须画出一个如何做到这一点的心理计划:
- 统计每件商品在
store_b购买的次数; - 存储大于或等于 3 的
product_ids; - 过滤那些来自
store_b的产品;
在“正常”的流水线方式中,可以使用一个临时表分两步完成。例如:
create temporary table sandbox.top_products as (
select product_id
from sandbox.store_b
group by product_id
having count(product_id) >= 3
);select a.* from
sandbox.store_b as a
inner join
sandbox.top_products as b
on a.product_id = b.product_id;
创建我们的top_products临时表,并使用其域通过内部连接来限制store_b行是可能的。作为替代,我们可以在 where 子句中传递带有子查询的top_products:
select * from
sandbox.store_b
where product_id IN (
select product_id from sandbox.top_products
);
注意,我们可以通过向查询的where子句传递 select 语句,将product_ids嵌入其中。但是,如果我们可以直接将查询传递给where子句,我们现在就可以放弃临时表了!让我们看看:
select * from
sandbox.store_b
where product_id IN (
select product_id
from sandbox.store_b
group by product_id
having count(product_id) >= 3
);
搞定了。我们已经使用第一个子查询示例将所有内容压缩到同一个查询中。提示:始终使用缩进来引导读者理解查询的行为。对于子查询,将它放在缩进的块中会更容易阅读和理解。
最后,以上所有查询的结果都是相同的——我们从store_b输出product_ids 1 和 3:

子查询结果 1 —按作者分类的图像
多层聚合
在这种情况下,我们有一个非规范化表,我们可能想要对数据执行一些多级聚合。例如,如果我们想要计算所有事务的平均值,我们不能直接应用平均值,因为我们的表是面向product_ids而不是transaction_ids的。
同样,如果我们应用“正常”的数据管道规则,我们可能会将其分为两个查询:
create temporary table sandbox.average_price as (
select transaction_id, sum(price_paid) as total_value
from sandbox.store_a
group by transaction_id
);select avg(total_value) from sandbox.average_price;
通过最后一条select语句,我们知道store_a的平均交易量大约为 12.48 €。同样,这是子查询的另一个优秀用例!您能自己构建子查询语法吗?
让我们看看:
select avg(average_price.total_value) as average_transaction from (
select transaction_id, sum(price_paid) as total_value
from sandbox.store_a
group by transaction_id
) as average_price
;
这一次,我们的子查询进入了from子句!子查询非常灵活,使我们能够在最著名的 SQL clauses中使用它们。
上面查询的结果与两表方法完全相同:

子查询结果(多层聚合)-按作者分类的图像
基于聚合筛选表
另一个适合子查询的用例是在某些场景中使用 SQL 变量。例如,假设我们想要过滤值高于平均值的事务(输出必须保留原始的面向产品的行),我们能在几个查询中完成吗?
首先,让我们采用多表方法:
- 我们从计算每笔交易的总额开始。
- 我们将所有交易的平均值插入到一个变量中。
- 我们过滤总值高于所有交易平均值的交易组。
如果不使用子查询,这可以通过以下方式实现:
create temporary table sandbox.average_price as (
select transaction_id, sum(price_paid) as total_value
from sandbox.store_a
group by transaction_id
);select [@avg_transaction](http://twitter.com/avg_transaction):= avg(total_value)
from sandbox.average_price;create temporary table sandbox.transaction_over_avg as (
select distinct transaction_id
from sandbox.average_price
where total_value > [@avg_transaction](http://twitter.com/avg_transaction)
);
select a.*
from sandbox.store_a as a
inner join
sandbox.transaction_over_avg as b
on a.transaction_id = b.transaction_id;
最后的select将产生关于交易 1 和交易 2 的信息,这是唯一价值高于平均值的购买:

子查询结果(使用变量筛选)-按作者排序的图像
如果我们想使用某种版本的子查询来实现这一点,我们可以将它们与变量结合起来:
select [@avg_transaction](http://twitter.com/avg_transaction):= avg(agg_table.total_value)
from (
select transaction_id, sum(price_paid) as total_value
from sandbox.store_a
group by transaction_id
) as agg_table;select *
from sandbox.store_a
where transaction_id in (
select transaction_id
from sandbox.store_a
group by transaction_id
having sum(price_paid) > [@avg_transaction](http://twitter.com/avg_transaction)
)
如您所见,这减少了一点代码,我们在创建变量@avg_transaction和过滤原始表时都使用了子查询。有人可能会说,三级子查询是可能的,但可能会使代码阅读起来有点复杂,所以这里没有灵丹妙药,这就是为什么两个查询的解决方案是合适的。
有时,子查询甚至可能不是解决问题的最清晰的方式!理想情况下,我们希望在复杂代码和长代码之间取得平衡——一般来说,代码越短越好,但是您可能会使用太多的子查询层,这使得调试和理解变得困难(也使得您的查询变得更慢)。
在我们离开之前,反对子查询的两个论点是:
- 当您希望保存数据状态以用于调试目的时,临时表也非常有用——对于子查询,您失去了可能需要的那层额外验证。
- 子查询通常比连接选项慢,特别是在 MySQL 中。尽管可读性更强,但在某些任务上,它们可能存在一些性能问题。
感谢你花时间阅读这篇文章!希望您喜欢学习子查询,并且能够在日常工作中应用它们。对于数据科学家来说,掌握 SQL 是一项非常重要的技能,正如我在上一篇文章中详述的那样,使用它来构建更高效的数据管道已经成为一项必备技能。
子查询是一项经常被忽视的技能——如果您已经掌握了一些 SQL 的基础知识,子查询非常容易理解,并且可以在几分钟内改变查询方式。然而,要注意的是,它们通常比连接替代项慢,所以要小心使用它们!
我在Udemy上开设了一门关于从零开始学习 SQL 的课程,我在其中深入探讨了这些概念以及更多内容——这门课程适合绝对的初学者,我希望您能在我身边!

SQL 绝对初学者教程 —图片由作者提供
https://medium.com/membership/@ivopbernardo
如何在 R 中使用 Google Maps Places API
原文:https://towardsdatascience.com/how-to-use-the-google-maps-places-api-in-r-33f2b705b2c
使用 R 中的 2 行代码解锁兴趣点数据

当我了解到 Google Maps Places API 时,我对该 API 所展现的可能性感到兴奋,但作为一名非软件开发人员试图破译该文档时却感到困惑。所以,我写了一篇文章给面临同样挑战的其他人一步一步地介绍如何在 Python 中使用它。
我在文章中提到代码可以很容易地过渡到 R,但就此打住。我最近发现自己在使用 R,结果发现 R 中 API 的实现甚至比 Python 更容易。
什么是谷歌地图位置应用编程接口?
简而言之,谷歌地图位置 API 是一种在搜索谷歌地图时获取信息的方式。如果我想在北卡罗来纳州罗利市中心找到墨西哥食物,我会在谷歌地图上搜索,看看会弹出什么。Places API 是一种以编程方式获取弹出结果的方法。

在谷歌地图上搜索北卡罗来纳州罗利的墨西哥食物;作者图片
不过,需要注意的一点是,结果不会返回视图中的所有结果。考虑屏幕上的结果如何根据地图的中心点和缩放半径而变化。


具有两个缩放级别的相同搜索的示例。请注意,在左侧,地图右下方只有 4 个位置,但在右侧图像中,放大后同一区域有 10 个位置。您的缩放级别会影响您的结果,就像在 API 调用中一样。
谷歌根据搜索结果的相关性、位置、付费广告等提供了一些样本。很容易以为只要输入一个很广的搜索半径就能得到所有结果,但没有这种运气。另外,每次搜索最多只能有 60 个结果。
因此,在使用 API 时,搜索要有针对性和针对性。
如何使用谷歌地图位置应用编程接口
除了以一种易于使用的格式准备数据之外, googleway R 包极大地简化了访问地点(和其他 Google 地图)API 的过程。
用install.packages('googleway')安装软件包,用library(googleway)加载软件包。
要使用这个包和任何谷歌 API,你需要建立一个谷歌云账户并生成一个 API 密钥。以下是如何操作的说明。你将每月获得 200 美元的免费积分,用于地图相关的 API 计费,这相当于大约 11,750 次搜索,所以你必须非常努力才能超过免费限额。
当您有了 API 密匙后,您就可以用短短两行代码访问兴趣点数据了:
第 1 行:使用google_places函数调用 API 并保存结果
第 2 行:访问搜索结果
为了最有效地工作,[google_places](https://www.rdocumentation.org/packages/googleway/versions/2.7.6/topics/google_places) 函数接受搜索字符串、位置、半径和 API 键。
*google_places(search_string = 'mexican food', location=c(35.77936902425637, -78.63995745574967), radius=3218, key=gmaps_key)*
search_string就是我们想要执行的搜索。location是我们搜索的中心点(这里是罗利市中心)的纬度和经度,作为向量输入。我通常只需在谷歌地图中右键单击我想搜索的地方,它就会生成坐标作为第一个菜单结果。radius是我们要搜索的距离米。在这里,我把 2 英里换算成了 3218 米key是 API 键。我已经把它保存为一个单独的变量,gmaps_key,但是如果你只进行一次搜索,你可以直接输入它(用引号括起来)。
当您运行代码时,您会看到 R Studio 中弹出了两个框。第一个是“回复”页面,第二个是结果。
响应页面为您提供了next_page_token(稍后将详细介绍)、您的请求的状态(“OK”表示可以运行,否则会有问题),以及您的结果。

上述搜索的响应页面示例;作者图片
要访问数据,要么在第二个弹出窗口中单击数据框,要么—假设您已经保存了搜索—通过运行variable_name$results来访问它。我已经将结果保存到变量search_str中,所以我用search_str$results调用结果。

仅访问结果的数据框的示例
数据框将为您提供每个地方的丰富信息,包括地址、经纬度坐标、价格水平、星级、评级数量、类别等。其中有些是现成的,有些你需要多写一点代码才能访问。
例如,要获取纬度和经度坐标,您需要访问上面几何行中每个值内的数据框,然后访问位置内的数据框。使用代码variable_name$results$geometry$location访问坐标。

如果要将它们绑定回原始数据框,只需运行代码:
*variable_name$results %>%
cbind(., variable_name$results$geometry$location)*
这会将纬度和经度作为列添加到您的搜索结果中。同样,如果您想要访问这些类型,可以对types列使用美元符号来访问这些信息。
你可能已经注意到只有 20 个结果,即使我说它返回 60 个。这可能意味着总共只有 20 个结果,但通常这意味着其他事情。
每个 API 调用最多返回 20 个结果,一个搜索最多可以返回 60 个位置。这实际上意味着,要访问所有 60 个结果,您需要对 API 执行 3 次调用。我上面提到的next_page_token就是我们如何访问接下来的(最多)20 个结果。
为此,使用相同的搜索标准运行另一个搜索,但是这次向函数添加一个参数page_token。
*google_places(search_string = 'mexican food', location=c(35.77936902425637, -78.63995745574967), radius=3218, key=gmaps_key, **page_token = search_str$next_page_token**)*
page_token是告诉谷歌在搜索中返回接下来的 20 个结果,而不是前 20 个。如果已经保存了初始搜索,可以通过调用variable_name$next_page_token直接添加next_page_token。
如果您想返回最后 20 个结果(第 40-60 位),您可以从第二次搜索中访问next_page_token,并将其插入到page_token参数中。
如果没有next_page_token,那么要么(1)没有比已经显示的更多的结果,要么(2)你已经用完了 60 个结果。
其他需要知道的事情
需要进行几次搜索才能掌握 API 如何返回结果,但是有了 200 美元的免费积分,我鼓励您尽早测试和学习。
较小的搜索半径会给你更多的信心,所有你想要的结果都会显示出来,但是如果你试图在一个更大的区域寻找,你将不得不执行更多的搜索。这将需要一些迭代来找到适合您的用例的折衷。(如果这一点令人困惑,可以在谷歌地图上进行几次测试搜索,看看结果会随着你的缩放级别发生什么变化。API 以同样的方式工作-一些结果会出现,另一些不会,但是在更大的放大级别,您的精度将会提高。)
请务必了解服务条款如何影响您的用例,尤其是关于数据的许可和使用。
googleway R 包比 google_places 功能有更多的功能。如果您想进行地理编码、查找点与点之间的距离、获取方向或更多,这是您的最佳选择。
我怎么强调开发者使访问 Google Places APIs 的过程变得多么容易都不为过,而且更容易的访问释放了更多有趣的用例和应用。
想谈谈更多的位置分析? 在 LinkedIn 上跟我联系 。
如何使用 IBM Watson Tone Analyzer 在 Python 中执行情感情绪分析
如何使用 IBM Watson 人工智能平台来预测客户在撰写产品评论或提供自由文本反馈时的情绪

在 Unsplash 上由 Domingo Alvarez E 拍照
介绍
一个常见的用例是以自由文本的形式收集客户反馈,然后分析这些反馈,将客户的意见转化为产品和服务的改进。
执行这种类型的分析的一种方法是获取自由文本并对其执行一些特征工程,使得定性的自由文本获得更多的定量特征。
例如,如果你知道顾客是喜是怒是悲等等。当他们撰写反馈时,这将提供对客户观点的有用见解,然后可用于将数据开发为信息、情报和知识,从而导致洞察力驱动的行动和改进的业务成果。
IBM Watson 是“IBM 的业务就绪型工具、应用和解决方案组合,旨在降低人工智能采用的成本和障碍,同时优化人工智能的成果和负责任的使用”(https://www.ibm.com/uk-en/watson)。
“音调分析器”使文本的情感分析能够直接嵌入到用 Python(或其他语言)编写的机器学习应用程序中,并且有免费的定价计划可用于测试和开发目的。
入门指南
在您开始使用 IBM Watson 情感语调分析器之前,您需要注册一个帐户,并创建一个 URL 和 API 密钥…
1.创建 IBM Watson 帐户
首先导航到 https://cloud.ibm.com/login 的,然后点击“创建账户”。您将看到一个创建账户的屏幕,如下所示-

作者图片
一旦您成功注册并登录,您将看到以下 IBM Watson 仪表板

作者图片
2.为音调分析器创建一个定价计划
IBM Watson studio 环境可能很难导航,所以最好的开始方式是点击“搜索资源和产品”,然后输入“音调分析器”。这将提供一个链接,链接到音调分析仪定价计划页面,如下所示

作者图片
“Lite”计划每月免费提供 2500 个 API 调用,但你必须继续使用它,否则它会被删除,这没什么大不了的,除非你必须创建一个新的 API 密钥,如果它已被删除。
如果您每月需要 2,500 个以上的 API 调用,您可以转到标准计划,即每个 API 调用 0.0088 美元,相当于 10,000 个调用 88 美元(约 65 美元)。
3.生成 API 密钥和 URL
一旦您创建了一个 Lite 计划,您可以通过点击屏幕右侧工具栏上的“查看现有”链接,从该页面导航到该计划,进入下一步,您将在该步骤中创建您的 API 密钥…

作者图片
上面的屏幕截图显示了一个现有的 API 密钥——为了开始使用该服务,您需要复制密钥和 URL。如果您尚未创建密钥,此页面上会有一个创建密钥的按钮。
仔细记下您的 API 密钥和 URL,您已经设置好了 IBM Watson 帐户、音调分析器计划和 API 密钥,您可以进入下一阶段,我们将使用该服务执行一些情绪音调分析…
获取一些数据
1.选择数据集
接下来我们需要一些测试数据来测试 IBM Watson Emotion Tone Analyser,我已经从 ka ggle(https://www . ka ggle . com/nica potato/women-ecommerce-clothing-reviews)选择了一个 23000 条女性电子商务服装评论的数据集。
这个数据集非常适合开发和测试围绕客户反馈和文本分析的解决方案,它是在“CC0:公共领域”许可下获得许可的,这意味着“你可以复制、修改、分发和执行这项工作,即使是出于商业目的,都无需请求许可”(见https://creativecommons.org/publicdomain/zero/1.0/)。
2.加载数据集
让我们从读入数据开始,执行一些有限的数据清理、特征工程和样本选择-

作者图片
注意,IBM Watson 的 Lite 计划被限制为每月 2500 次 API 调用,因此df_reviews.sample将限制 API 被调用的次数并保持在计划内。
配置和准备 IBM Watson 音调分析器
1.安装 IBM Watson 库
您将需要使用 pip 或 conda 安装库…
pip install ibm-watson
我发现,在我一直使用的一些功能被弃用后,我必须使用以下命令手动选择版本升级…
pip install --upgrade "ibm-watson>=4.5.0"
2.初始化 IBM Watson
接下来,我们需要使用 API 密钥和 URL 进行身份验证,并启动音调分析器。我们还需要小心保持 API 密匙的私密性,我发现一个很好的方法是将 URL 和 API 密匙保存在一个名为config.py的单独文件中,并从 github 中排除这个文件,这样其他人就无法找到细节。
2.1 指示 GitHub 排除配置文件
使用文本编辑器在 GitHub 项目的根目录下创建一个名为.gitignore的文件,并在第一行输入config.py。
2.2 创建一个 config.py 文件并添加常量
用您的详细信息替换 XXX
IBM_WATSON_URL = "https://api.eu-gb.tone-analyzer.watson.cloud.ibm.com/instances/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
IBM_WATSON_API_KEY = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
2.3 验证和连接
使用 IBM Watson 音调分析仪预测和可视化情绪
基本的方法是使用apply和一个lambda函数为每行数据调用一次 API,稍微复杂一点的是,我们希望在DataFrame ( Main Emotion和Main Emotion Score)中设置两列的值。
在两个列中设置值的 Python 语法并不明显,stackoverflow 上的这个线程在解决这个问题时是非常宝贵的。
1.编写一个帮助器函数来调用单行的 API
需要注意的几点是-
- IBM Watson tone analyser 可以返回不同类别的音调,但是出于分析的目的,我们只对使用
EMOTIONAL_TONES列表中的值过滤的情感感兴趣。 - 每个返回的音调都有一个名称和一个分数,因此代码存储最高分数的情感音调名称和值。
return pd.Series...是使用 pandasapply和一个lambda函数设置两列中的值所需的语法。
为了充分理解代码,有必要看一下 json 输出是如何针对单个 API 调用进行构造的

作者图片
我已经使用了谷歌 Chrome JSON 编辑器应用来查看输出,但还有其他选项可用,例如 Visual Studio Code Json 编辑器扩展。
您可以从编辑器中看到,每个句子都有一种语气,整个文本都有一种由代码提取的语气。这里只显示了一个音调,但是更复杂的文本将导致返回多个音调,混合了情感和非情感。提供的代码处理 API 返回的所有数据组合。
2.调用助手函数
现在剩下的就是为数据集中的每一行调用 helpder 函数,并根据get_emotion函数为每一行返回的值同时设置两个特性的值..

作者图片
3.保存结果
为了避免多次调用 API 而耗尽每月限额,我们将把结果保存到 Excel 电子表格中…
4.可视化结果
最后,这里用一个饼状图展示了主要的客户情绪…

作者图片
结论
我们已经了解了如何向 IBM Watson 注册一个帐户,然后创建一个免费计划,每月向音调(情感)分析器调用 2500 次 API。
我们还开发了 Python 代码,将情感预测应用于公共许可数据集,然后存储和可视化结果。
如果我们将这种方法应用于女性电子商务服装评论数据集中的所有 23,000 个数据点,成本将为 202 美元(~ 149 美元),代码运行将需要大约 4 个小时。
总之,IBM Watson Tone Analyzer 是为中小型数据集预测客户情绪的一种有效且经济的方法,但处理时间和成本可能会妨碍它在大数据上的使用。
然而,IBM Watson 引擎已经使用 1000 个数据点中的 100 个进行了训练,因此它是一个有效的预测器,因此这种方法可以潜在地用于更大数据集中的数据样本,如本例中所做的那样,以对群体数据中的模式进行预测。
感谢您的阅读!
如果你喜欢读这篇文章,为什么不看看我在 https://grahamharrison-86487.medium.com/的其他文章呢?此外,我很乐意听到您对这篇文章、我的任何其他文章或任何与数据科学和数据分析相关的内容的看法。
如果你想联系我讨论这些话题,请在 LinkedIn 上找我—【https://www.linkedin.com/in/grahamharrison1 或者发电子邮件到ghar rison @ Lincoln college . AC . uk。
如果你想通过订阅来支持作者和世界上成千上万为文章写作做出贡献的人,请使用这个链接——https://grahamharrison-86487.medium.com/membership(注意:如果你使用这个链接注册,作者将收到一定比例的费用)。
如何使用夏洛克思维宫殿学习技术自学数据科学
这种来自古希腊的记忆技巧不同寻常,但很有效

两年前,我开始研究自学数据科学的最佳方法。我研究过微学习、超学习、费曼技巧、刻意练习、1950 年代的洗脑技巧等等。它们都是独一无二的,并且已经被证明能够产生效果。
然而,我希望带给你们数据科学自学者一些比 20 世纪 50 年代的洗脑技术更加晦涩和非正统的东西。我想给你一个直接来自最早的聪明人的学习方法:古希腊人。
进入心灵殿堂。或者,众所周知,这要归功于 BBC 的夏洛克节目:夏洛克思想宫。
什么是心灵宫殿学习技巧?
头脑宫殿技术起源于古希腊,根据神话记载,一位名叫西蒙尼戴斯的希腊诗人在参加了一场致命的宴会后发明了一种记忆信息的方法。西蒙尼德斯走出宴会厅,不料它在他身后倒塌。尽管倒塌时被困在大厅里的与会者被压得无法辨认,但西蒙尼德斯能够根据他们所坐的位置辨认出每具尸体。于是,心宫术诞生了。
心灵宫殿技术有许多其他的名字,包括轨迹法、记忆剧场、记忆艺术和记忆宫殿。
大脑宫殿技术的工作原理是这样的:想象一个复杂的地方(比如一座宫殿),你可以在那里储存记忆或信息。每个房间都是特定于你想要记住的记忆或信息片段的(阅读:在房间内存储)。因为大脑非常擅长记忆视觉记忆,它可以帮助记忆或信息片段尽可能的详细或独特。当你需要检索一段记忆或一条信息时,你只需穿过大楼,看到并记住每一段记忆。
希腊人和罗马人使用这种技术来记忆他们的演讲,每个想法都与一个复杂的建筑房间在精神上配对。在这段时间里,写下东西来记住它们是很昂贵的,所以使用精心制作的记忆技巧来省钱更有意义。中世纪时,僧侣和学者也使用“心灵宫殿”技术来记忆宗教经文。
然而,一旦印刷术成为主流,写下东西变得不那么昂贵,头脑宫殿技术很快就失宠了,因为人们不再需要记忆信息或记忆。像电唱机、低腰牛仔裤和发圈一样,记忆宫殿技术在 20 世纪的国际记忆比赛中复兴,参赛者可以用它来按照特定的顺序回忆一长串物品。例如,西蒙·赖因哈德能够用头脑宫殿技术记住 370 张卡片的准确顺序。
阿瑟·柯南·道尔将心理宫殿技术融入了夏洛克·福尔摩斯的小说中,他写道福尔摩斯向约翰·华生描述了这一技术,认为人的大脑就像一个空阁楼,需要放上重要的家具。这个想法很重要,因为每个人的心灵宫殿都会不同:一些人会选择他们长大的家,另一些人会想到一个真正的宫殿,还有一些人会选择一条上面有很多地址的道路作为组织他们信息的方式。事实上,大脑宫殿甚至不一定是一个真实的地方——一项科学测试的参与者发现,当使用真实的地方或虚拟的地方记忆一系列不相关的单词时,他们表现得一样好。
何时使用思维宫殿技术研究数据科学
头脑宫殿技术不是为了帮助你理解数据科学主题而设计的——相反,它是为了让你更容易调用信息或你已经学到的概念。头脑宫殿为你提供了一种快速回忆记忆信息的方法。当你使用思维宫殿技术来记忆数据科学中的概念时,这些记忆变成了存储在基底神经节中的程序性记忆,可以比存储在大脑其他区域的记忆更快地被访问。
因此,这种技术最适合用于记忆数据科学中的概念,您将需要定期检索这些概念以用于您的学习或工作。
例如,你可以使用头脑宫殿记住的数据科学概念是良好的数据可视化元素、统计方法以及何时使用它们、微积分公式、软件工程最佳实践、良好文档清单、或 python 代码检查清单。从个人经验来看,这种方法被证明在记忆统计方法和事物清单时是有用的,比如可视化或代码。
虽然这种学习方法不会帮助您理解不同概念之间的关系,但它仍然是一个很好的工具,可以帮助您记住成为数据科学家所需的概念。
自学数据科学时要记住的一个关键思想是,你不必总是理解某样东西为什么会工作,你只需要理解如何以及何时使用它。当我第一次上大学微积分课时,我不得不提醒自己,我不需要知道这些公式为什么有效——我只需要知道何时使用它们。这正是心灵宫殿派上用场的地方。
如何利用思维宫殿技术研究数据科学
当你可以将数据科学概念与你熟悉的地点联系起来时,思维宫殿技术就会发挥作用。人们用来储存信息的最常见的“宫殿”是他们童年的家,因为那是一个你非常熟悉的地方。例如,如果你闭上眼睛,想起你童年的家,你会立刻看到房子的平面图,每个房间墙壁的颜色,以及每个房间的家具和装饰。当你走过房子的每个房间时,你会将数据科学的概念与每个房间联系起来。然后,当你想检索这些信息时,你可以沿着房子的原路返回,直到到达那个房间。正如我之前提到的,每个概念越独特,越形象,就越容易记住。
因为现在写下一些东西不需要花费什么,所以最简单的方法就是从你宫殿平面图的视觉草图开始,然后写下你对房子里每个停留点的概念联想。这里有一个想法让你开始:
第一步:画出你心灵宫殿的平面图。
第二步:画一条穿过思维宫殿的旅行路线,在相关的位置停下来——确保你的旅行路线是直线的,这样你就不会越过你自己的路线。
第三步:在你的平面图草图旁边创建一个两栏列表——在第一栏,列出你的平面图中的相关位置;在第二列中,将数据科学概念附加到每个相关位置。例如,在我们的统计方法示例中,前门可能与 k-means 聚类相关联(因为你和你的家人一起聚集在门口进入房子),楼梯可能与线性回归相关联(因为你要么向上要么向下),后院的门可能与随机森林相关联(因为你通过后门进入森林)。
第四步:有规律地在脑海中运行你的思维宫殿,直到你进入一个房间时,知道你将要记住什么成为你的第二天性。
第 5 步:下次你练习参加数据科学面试时,使用你的大脑宫殿,制作模拟面试问题,要求你从大脑宫殿中检索信息。或者,使用您的头脑宫殿来确保您记住了您在以前的课程中学习的所有数据科学概念,例如良好的数据可视化的要求或您应该知道的编写良好代码的软件工程习惯。
最后的想法
如果我在自学数据科学时学到了什么,那就是你的学习方法越非正统,你就越有可能记住你正在学习的东西。
思维宫殿是改变你的数据科学学习体验的好方法。与其让自己沦落到在 Youtube 上阅读书籍和听视频的无聊时间,为什么不积极地使用思维宫殿来记忆数据科学中的概念呢?这种主动学习和回忆方法将为您提供一个可视化模型,您可以在此基础上体验数据科学。它不仅可以帮助你在关键时刻找回重要的概念,因为你可以轻松地回忆起你过去是如何走过童年的家的,而且它还可以通过鼓励你锻炼大脑的不同部分来重振你的学习过程。
在学习数据科学时,尝试任何事情都是值得的——尤其是一项与历史上最伟大的学习者古希腊人一样古老的技术!
订阅让我的故事直接发到你的收件箱:故事订阅
请成为会员,使用我的推荐链接获得无限制的媒体访问权限(我将免费向您收取少量佣金):媒体会员
通过捐赠来支持我的写作,以资助更多像这样的故事的创作:捐赠
同伦方法:如何使用拓扑作为你的机器学习模型的优化器
实践教程
同伦方法:如何使用拓扑作为你的机器学习模型的优化器
是的,这是“杯子是甜甜圈”的拓扑结构!

**Table of Contents**· [Another Problem Arise](#2d84)
· [Homotopy Method](#5b93)
∘ [Prediction](#4afb)
∘ [Correction](#42b5)
· [Implementation](#62b3)
∘ [Scenario 1: x₀ = [3, 17]](#287a)
∘ [Scenario 2: x₀ = [40, 3]](#64de)
· [Conclusion](#66b8)
另一个问题出现了
在之前的故事中,我们已经实现了一种高效的技术,它优化了模型的参数,以恰当地适应数据集。我们使用了一个称为残差平方和的损失函数,这与许多机器学习模型的工作方式类似。现在让我们提出另一种问题。在这个故事中,我们将使用优化方法来解决一个非线性方程组。首先,导入一些库。
具体来说,下面是x=【x₁,x₂ 】需要求解的方程:

显然,它们是非线性的。
在继续之前,让我们用好的替换法来解方程。根据第一个等式,我们有

从第二个,我们获得

因此,

仅满足 x₂ = 0 或 x₂ = 1 。既然从第二个等式我们不能得到 x₂ = 0 ,那么 x₂ 一定是 1 。反过来,我们也得到了 x₁ 是 1 。所以,我们要的解是 x* = [ 1,1 ]。
同伦方法

与球体同形的有斑点的动物。GIF 由 Keenan Crane 制作。
现在我们采用一种更通用的方法来解决这个问题,使用所谓的同伦。
同伦,在数学中,是通过研究在区域中可以画出的不同类型的路径来对几何区域进行分类的一种方法。具有公共端点的两条路径称为同伦,如果一条路径可以连续变形为另一条路径,而端点保持不变并保持在其定义的区域内。
从一个拓扑空间到另一个拓扑空间的两个连续函数称为同伦如果一个可以“连续变形”到另一个。如果你想深入同伦,有一整个数学分支叫做拓扑。

两条虚线路径相对于它们的端点是同伦的。动画展示了一个可能的同伦。图片由 Jim.belk,公共领域。
用f:ℝⁿ→ℝⁿ表示一个非线性方程组,我们的任务是找到满足f(x **)=0*的解 x ∈ ℝⁿ 。参考我们的问题,这个系统可以这样写。
为了解决这个问题,我们通过在 F 上插入一个延拓参数 λ 来构建一个同伦函数 H ( x,λ )。通常,线性同伦方程可以写成初始和目标系统的线性组合,也就是说,

用 λ ∈ [ 0,1 ], p ( x )为初始系统, F ( x )为目标系统。定义初始系统有几种方法,其中两种是:
- 定点同伦:p(x):=x—x₀,隐含

2.牛顿同伦:p(x):=f(x)—f(x₀),言下之意

用x₀∑ℝⁿ是 x 的初始估算点。在这个故事中,我们使用牛顿同伦。
注意,既然 H ( x₀,0 ) = 0 ,那么 x₀ 就是初始系统的一个解(当 λ = 0 )。还有,既然 H ( x,1)=F(x *)=0,那么 x 就是目标系统F(x)=0(当【T75 同伦方法的思想是通过将初始系统变形为目标系统来找到*x **——我们知道这个解是 x₀ 。
所以,为了找到目标系统的解 x* ,我们首先通过设置 λ₀ = 0 找到初始系统的解 x₀ 。但是找到 x₀ 很容易,因为任意的 x₀ 满足 H ( x,0 ) = 0 。这意味着我们可以选择任何起始迭代 x₀ 。然后, λ₀ 向 λ₁ 移动δλ距离 1 更近。在这个故事中,我们用δλ=0.1。一个新的同伦 H ( x,λ₁ )出现了一个解 x₁ 。为了找到 x₁ ,我们遵循两步过程:预测和校正。
预言;预测;预告

由 slidesgo / Freepik 设计
在点( λ₀,x₀ )处建立图形 H ( x,λ )的切线,然后计算斜率。设梯度为 ẋ₀ 。假设这条切线与直线 λ = λ₁ 相交于(x₁⁽⁰⁾,λ₁ )。根据定义,

以便

现在唯一的问题是,如何计算 x₁ ⁽ ⁰ ⁾?有两种方法:
- 割线预测。这个方法在 λ 的最后两次迭代中使用 x 的最后两次迭代。假设( λ₋₁,x₋₁ )是我们到达( λ₀,x₀ )之前的另一个点,那么

但是我们不希望为第一次迭代 λ 找到正确的( λ₋₁,x₋₁ )。所以,在这个故事中,我们将使用第二种方法计算下面 x₁ ⁽ ⁰ ⁾。

下一次迭代 x₁.候选的割线和欧拉预测图片由作者提供。
2。欧拉预测。我们通过微积分的透镜来利用梯度的定义,也就是说,通过移动( λ₋₁,x₋₁ )与( λ₀,x₀ )如此接近,使得割线方程变成(为了一般性,我们去掉了索引 0 ):

但是链式法则说

所以,

由于 ∂H / ∂x 只是雅可比矩阵 J ( x,λ )并且我们知道

然后 J ( x,λ)=∂h/∂x=f’(x)。具体针对我们的问题, J 可以计算如下。
项 ∂H / ∂x 用数值计算会更有效,所以我们通过设置来近似它

总而言之,我们得到了下面这个寻找 ẋ 的方程。

注意,使用欧拉预测,我们只需要当前点( λᵢ,xᵢ )而不是割线预测中的最后两点。
修正

由 Freepik 设计
让我们回到 x₀ 的迭代过程。我们从预测步骤中找到了 x₁ 的候选人,即 x₁ ⁽ ⁰ ⁾。你可能会奇怪为什么指数(0)**x₁⁽⁰⁾。你可能已经猜到了, x₁ ⁽ ⁰ ⁾可能不满足 H ( x,λ₁ ) = 0 ,所以我们需要修正一下。

计算下一次迭代 x₁.的预测和校正过程的可视化图片由作者提供。
为了做到这一点,我们将使用牛顿-拉夫森方法,即通过做以下迭代。

随着

对于 i = 0,1,2,… 。
通过最小化ǁǁ,这个迭代有希望收敛到某个 x₁。在这个故事中,迭代继续,直到达到停止条件ǁhǁ<t96】1×10⁻⁵。
如果牛顿-拉夫森方法不收敛,这意味着δλ太大,我们可以通过设置δλ:=δλ/2使用更小的δλ来重新开始最后的预测和校正步骤。如果到第 1000 次迭代时停止条件仍未达到,我们称之为迭代不收敛。
校正步骤的收敛表明我们已经找到了满足 H ( x,λ ) = 0 的 x₁ 和 λ₁ 。之后,我们重置δλ=0.1,并使用与之前相同的过程计算 x₂ 和λ₂=λ₁+δλ。如此反复进行,直到对于某些 n 来说 λₙ = 1 ,因此 xₙ = x* 就是f(x)=0的解。
下面是完整的同伦法。
履行
我们将使用不同的初始值 x₀ 模拟两种场景:
- x₀ = [ 3,17 ]
- x₀ = [ 40,3 ]
这些值完全是随机的,读者也可以尝试其他值。
在继续之前,让我们创建两个 python 函数来绘制零路径。什么是零路径?是 H ( x,λ ) = 0 的点的轨迹。
场景 1: x₀ = [3,17]
Initial condition: λ = 0.00, x = [ 3 17]Iteration: 1 λ = 0.10 x = [2.0854 8.9188]
Iteration: 2 λ = 0.20 x = [1.6078 5.4471]
Iteration: 3 λ = 0.30 x = [1.3307 3.6865]
Iteration: 4 λ = 0.40 x = [1.1606 2.6885]
Iteration: 5 λ = 0.50 x = [1.0546 2.0763]
Iteration: 6 λ = 0.60 x = [0.9911 1.6789]
Iteration: 7 λ = 0.70 x = [0.9585 1.4101]
Iteration: 8 λ = 0.80 x = [0.9507 1.2234]
Iteration: 9 λ = 0.90 x = [0.9647 1.092 ]
Iteration: 10 λ = 1.00 x = [1\. 1.]
Solution found: x = [1\. 1.]

2D 表象中场景 1 的同伦函数的零路径。图片由作者提供。

3D 表示中场景 1 的同伦函数的零路径。图片由作者提供。
我们看到同伦方法在 10 次迭代后设法找到了解 x* = [ 1,1 ],每一步δλ=0.1,找到了临时解 xₙ 。即该算法成功地最小化了ǁ H ǁ,使得每次迭代ǁhǁ<t82】1×10⁻⁵,并成功地从 λ = 0 连续通过 H 的零路径运行到 λ = 1 。
场景 2: x₀ = [40,3]
Initial condition: λ = 0.00, x = [40 3]Iteration: 1 λ = 0.10 x = [-8.7943 0.5573]
Iteration: 2 λ = 0.20 x = [nan nan]
Solution not found. Try other initial value of x.
在牛顿-拉夫森方法无法找到收敛到 x₂ 的解之前,同伦方法仅成功运行 1 次迭代,直到 λ₁ = 0.1 与 x₁ = [ -8.79,0.56 ]。这是因为雅可比矩阵 J 在第二次迭代中是奇异的。由此可见,在用同伦法求解方程组f(x)=0时,初始变量 x₀ 的选取是需要考虑的重要事情之一。
结论
我们一直在用同伦方法来寻找一个非线性方程组的解。同伦方法的思想是将一个我们知道其解的初始系统变形为我们想要的最终系统。变形过程产生同伦函数的零路径,该零路径可能不连续,这取决于初始系统。用这种方法,我们发现我们给定的方程组的解是 x* = [ 1,1 。

🔥你好!如果你喜欢这个故事,想支持我这个作家,可以考虑 成为会员 。每月只需 5 美元,你就可以无限制地阅读媒体上的所有报道。如果你注册使用我的链接,我会赚一小笔佣金。
🔖想了解更多关于经典机器学习模型如何工作以及如何优化其参数的信息?或者 MLOps 大型项目的例子?有史以来最优秀的文章呢?继续阅读:

从零开始的机器学习
View list8 stories



高级优化方法
View list7 stories



艾伯斯·乌兹拉
MLOps 大型项目
View list6 stories



我最好的故事
View list24 stories



R 中的数据科学
View list7 stories



如何使用 UMAP 进行更快更有效的异常检测
让我们抓住那些高维离群值

照片由乔·耶稣拍摄
介绍
我们都使用过这些简单的技术——绘制散点图或 KDE,离组最远的数据点是异常值。现在,告诉我——如果你要在比如说 100 维的数据集中发现异常值,你会如何使用这些方法?马上,视觉异常检测方法是不可能的。
因此,像局部离群因子或隔离森林这样的奇特机器学习算法浮现在脑海中,它们对高维数据中的离群点是有效的。
但是使用 ML 方法检测异常值有很多注意事项。一种称为椭圆包络的技术使用协方差估计,但假设数据是正态分布的(这种情况很少发生)。局部离群因子比隔离森林快得多,但精度较低。
那么,在处理如此常见的大规模、百万行数据集时,我们如何才能在离群点检测的速度和准确性方面表现出色呢?这就是 UMAP 的用武之地。
https://ibexorigin.medium.com/membership
获得由强大的 AI-Alpha 信号选择和总结的最佳和最新的 ML 和 AI 论文:
https://alphasignal.ai/?referrer=Bex
什么是 UMAP?
UMAP(Uniform Manifold Approximation & Projection)是 2018 年推出的一种降维算法。它结合了 PCA 和 tSNE 的最佳功能,可以快速扩展到大型数据集,并在速度上与 PCA 竞争,并且比 tSNE 更有效、更美观地将数据投影到低维空间:



作者图片和 UMAP 文档
UMAP python 包有一个熟悉的 Scikit-learn API。下面是将 Kaggle TPS 九月竞赛数据投影到 2D 的示例代码:
UMAP 的设计使得无论你将数据投影到哪个维度,它都尽可能地保留了变量和拓扑结构。但是这和异常检测有什么关系呢?
好吧,因为我们知道 UMAP 即使在较低的维度上也能保留数据集的所有特性和属性,我们可以使用它首先将数据投影到一个较低的空间,然后使用任何其他异常值检测算法更快!在接下来的章节中,我们将使用上面的 TPS 九月数据来看一个例子。
如果你想更多地了解 UMAP 及其令人敬畏的特色,我在以前的一篇文章中已经深入地讨论过了:
用纯隔离林设置基线
在我们继续之前,让我们建立一个基准性能。首先,我们将为基准拟合一个 CatBoostClassifier:
我们的 ROC AUC 值为 0.784。现在,让我们在输入缺失数据后,将隔离森林估计量用于数据:
尽管功能强大,隔离林只有几个参数需要调整。最重要的是n_estimators,控制要建的树数。考虑到数据集的大小,我们将其设置为 3000。
等待大约 50 分钟后,我们发现隔离林在数据中发现了 2270 个异常值。让我们从训练数据中删除这些值,并再次拟合分类器:
我们的性能略有下降,但这并不意味着我们要停止寻找异常值的努力。此时,我们并不真正知道这~2200 个数据点是否都是数据中的异常值。我们也不知道我们是否用足够的树木建造了隔离林。最糟糕的是,我们不能再次尝试这个实验,因为它太耗时了。
这就是为什么我们将开始玩聪明。
结合隔离森林的 UMAP 离群点检测
在我们发现 2270 个异常值后,我们没有仔细检查它们是否是真正的异常值。传统的可视化方法不起作用,所以让我们先尝试将数据投影到 2D,然后绘制一个散点图:

作者图片
我们可以立即看到离两个不同集群最远的单个点(它们以小尺寸显示,因为我们没有调整 UMAP 参数)。我们可以放心地将这些点归类为异常值,并相信我们从隔离林中获得的结果。
现在,我们将把 UMAP 和隔离森林结合起来。为了尽可能最大限度地保留,我们将数据投影到 5 个维度。然后,我们将只安装 500 棵树的隔离林,因为这次我们的功能要少得多:
整个转换和异常值检测只需三分之一的时间。但令人惊讶的是,这一次,Isolation Forest 在 95 万个异常值中发现了大约 16.3 万个异常值。我们在犯错误吗?让我们在删除这些异常值后,通过再次拟合分类器来进行检查:
3000 棵树似乎不足以发现数据中的所有异常值。我们看到,即使在放弃 160,000 次观察后,我们仍然得到相同的结果。
摘要
是的,在从数据集中移除异常值后,我们没有获得显著的分数提高。事实上,就实用性而言,你可以说我们所有的努力完全是浪费。
然而,本文的目的是向您展示,当结合像 UMAP 这样的强大的降维技术时,离群点检测将变得更加节省时间/资源。如您所见,我们在隔离林中用更少的资源实现了更好的性能。
离群值是 ML 世界中一个真实而严重的问题。您今天学到的技术一定会让您在面对如下场景时受益匪浅,尤其是当您的数据是海量和高维数据时:

图片来自 Sklearn 文档
您可以从 UMAP 文档中了解更多关于本文中的技术,其中包括一个使用 digits 数据的很棒的例子。
感谢您的阅读!
https://ibexorigin.medium.com/membership https://ibexorigin.medium.com/subscribe
阅读更多来自我的故事…
[## 使用 Python 和 AWS Lambda 将任何 ML 模型部署为 API 的综合指南
towardsdatascience.com](/comprehensive-guide-to-deploying-any-ml-model-as-apis-with-python-and-aws-lambda-b441d257f1ec) </25-advanced-pandas-functions-people-are-using-without-telling-you-b65fa442f0f4> </25-numpy-treasures-buried-in-the-docs-waiting-to-be-found-60d8e17931cd> https://ibexorigin.medium.com/28-weekly-machine-learning-tricks-and-resources-that-are-pure-gems-1-8e5259a93c94
如何在建模和预测中使用美国邮政编码数据?
原文:https://towardsdatascience.com/how-to-use-us-zip-code-data-in-modeling-and-forecasting-602312b5e606
简介
美国的五位数邮政编码数据是供应链和运输模型中最流行的模型粒度之一。我们有时会听到分析师和科学家抱怨“遗漏的邮政编码”或邮政编码界限不一致。许多模型都有遗漏邮政编码的问题,这导致输出的准确性很差。在本文中,我们试图为如何在规划和预测中使用邮政编码数据提供指导。

地图 1:一个邮政编码的三种数据类型的空间表示:爱达荷州博伊西市中心所有邮政编码的边界(紫色)、质心(紫色)和邮政编码点(深蓝色)。“作者提供的图像”
正如您在地图 1 中看到的,一个区域中有多个邮政编码 ID(点和面),并且不是所有的 ID 都与面相同。如果我们不区分显示为 pints 和 polygon 的 ZIP ID,这可能会影响模型输出。你可能会想什么是邮政编码,什么是邮政编码边界?让我们对邮政编码有一个明确的定义。
邮政编码定义:
邮政编码在 20 世纪 60 年代首次引入,其发展是为了帮助邮政服务改善全国范围内的邮件分发。尽管邮政编码是根据地区分类设施统计的,但地理边界在技术上并不存在。拉链实际上是标识递送点(即街道地址或邮局)的名称,而不是任何定义的边界区域。美国海军就是这种“无地”称呼的最好例子,它有自己的邮政编码,但没有固定的位置。因此,邮政编码边界可能不连续、未定义或不存在。换句话说,创建一个真正有代表性的地图相当困难,而且确实存在的邮政编码地图也不全面。更大的问题是,邮政编码会改变——但这是以后的事了。
换句话说,邮政编码区域或边界或多边形是美国邮政服务(USPS)邮政编码的近似区域表示。USPS 定期更改邮政编码,以支持更高效的邮件投递。他们将变化发布到老虎普查网站【1】上,但与实际存在的街区边界不同,邮政编码边界并不正式存在!是的,你没看错!ZIP 边界可以跨越道路、街区、河流或任何其他要素。这是邮政编码边界的性质,因为我们在空间上有新的发展。在美国,有时一个邮政编码边界可能会跨越一个大包裹,一所房子可能有两个邮政编码,城市和县纠正这些问题。在某些国家/地区,邮政编码边界表示为道路周围的缓冲区或街区内的几何形状。
这就是为什么我们有时会从供应链中的网络规划者或运输分析师那里听说“遗漏的邮政编码”实际上并不是遗漏的邮政编码,事实上它们是被识别为四种主要邮政编码类型之一的点邮政编码。你应该知道,在美国大约 42K 的邮政编码中,我们有大约 10K 点的邮政编码,只有大约 32K 的邮政编码有物理边界。因此,我们可以以表格格式显示大约 42K 的邮政编码 ID,其中大约 32K 具有来自 USPS/Census/County 的边界的空间格式。别忘了每个国家都有自己的邮政编码系统。在不知道多个国家的邮政编码系统的情况下,运行一个消耗这些国家的邮政编码 ID 的模型是不可能的。例如,英国和阿联酋与我们有非常不同的邮政编码系统。让我们回顾一下邮政编码类型。
邮政编码类型:
邮政编码有四种主要类型:邮政信箱、唯一、军事和标准。还有其他类型的邮政编码,如非唯一、唯一组织。这些邮政编码的定义如其类型中所述。邮政信箱位于邮局本身;唯一代码指的是个人地址;美国海外军事基地有国内邮寄地址;标准代码表示所有其他的东西(即“正常”的)。因此,邮政信箱、唯一和军事邮政编码可以是位于另一个邮政编码内的点。
问题解决了!
“遗漏的邮政编码”位于具有空间边界的邮政编码内。不要忘记当房地产开发商公司从城市或县获取邮政编码时的新发展,但它不是寻址系统的正式组成部分。目前~10K 点邮政编码存在于美国,它位于一个邮政编码多边形内。无论何种类型,每个邮政编码 id 都有一个“结束邮政编码”,显示该邮政编码所在的位置。我们还可以通过邮政编码边界和邮政编码点之间的空间连接来定位它们。当我们使用邮政编码时,我们应该使用邮政编码。
如何使用数据?
如上所述,我们有两个邮政编码数据集:
- 点邮政编码(~42K 记录)。该数据集包含邮政编码 ID、邮政编码类型和邮政编码结尾字段。邮政编码 Id 可以帮助您查找表格格式的数据和信息,例如卷。结束邮政编码用于定位邮政编码多边形内的邮政编码点,该多边形具有用于路由目的的物理边界。
- 多边形邮政编码(~32K 记录)。该数据集具有邮政编码的空间边界,并包含所有邮政编码结束 id。运行任何需要距离邮政编码的模型或搜索时,都必须输入它。该数据集与 Tableau 为 ZIP 映射提供的数据集相同。
注意:邮政编码可能包括客户数据,如客户地址和姓名。对于生产中使用和映射此点邮政编码的任何应用程序,都需要与您的 IT 安全团队进行讨论。如果点邮政编码被映射到一个邮政编码面,它不再被归类为红色数据!
最后,不要使用任何你在互联网上找到的邮政编码数据!谷歌地图、Zillow、Redfin 等都不是邮政编码数据的真实来源。您应该从市、县或其他地方政府或联邦机构的网站下载邮政编码数据(end。gov)。Tableau 和 ESRI 在他们的服务器上有这些数据。下面是一个参考链接,你可以找到可靠的邮政编码数据。下次,当你的客户带着他们在谷歌地图上找到的不同的邮政编码边界来找你时,只要给他们看下述资源中正确的正式邮政编码边界就行了。Medium 中还有一篇精彩的论文解释了如何下载数据,见https://Medium . com/@ sahilkashyap 64/USA-zip code-boundary-ccbdcfd 0af 8
参考文献
- https://www . census . gov/programs-surveys/geography/guidance/geo-areas/zctas . html
- https://catalog . data . gov/dataset/tiger-line-shape file-2018-2010-nation-u-s-2010-census-5-digit-ZIP-code-table-area-zct a5-na
- https://www.unitedstatesZIPcodes.org/
- 【https://www.policymap.com/
- https://www . ESRI . com/en-us/ArcGIS/products/tapestry-segmentation/ZIP-lookup
如何在 Python 函数中使用可变数量的参数
一个关于*args 和**kwargs 的小故事

作者制作的图像
在本文中,我们将学习*args 和**kwargs,这两个特殊的 Python 符号,您可能以前在一些函数签名中遇到过。
他们做什么?它们解决什么问题?您如何使用它们来提高代码的灵活性?
我们将回答这些问题。📚
Python 函数中可变数量的参数
这个标题有点像剧透: *args 和 **kwargs 允许您向函数传递可变数量的参数。但我们会谈到这一点。
现在,让我们看看一些具有这种灵活性的流行函数,并考虑一下您如何也能从中受益。
如果您熟悉 Python 编程语言,您可能知道以下两个函数:
1 — 出了名的内置**print**功能:
顾名思义,这个函数将数据打印到标准输出中。但是你知道它也可以接受任意数量的参数(来打印)吗?
**print("hello")
# hello****print("hello", "there")
# hello there****print("I", "have", 5, "dollars")
# I have 5 dollars****print("a", 2, [1, 2, 3])
# a 2 [1, 2, 3]**...
2—**os**模块的**join**功能:
这个函数连接文件系统上任意数量的路径。同样,它接受可变数量的路径作为参数。
**import os****path1 = os.path.join("/")
path2 = os.path.join("/", "Users")
path3 = os.path.join("/", "Users", "Library")****# ...**
使用接受可变数量参数的函数可能非常有用:这提供了很大的灵活性,并减少了函数签名中的混乱。此外,它没有对所需参数的数量做任何假设,这在多种情况下可能是合适的。
在很多情况下,你需要使用一个参数个数可变的函数。
让我们举一个例子:假设您想要构建一个计算数值聚合(平均值、总和、乘法等)的函数。)超过一些数字。
为简单起见,考虑乘法(这同样适用于其他聚合)。
如果你想定义一个将两个数相乘的函数,你可以简单地这样做:
**def multiply_numbers(n1, n2):
product = n1 + n2
return product**
这很好,但是把三个数相乘怎么样?还是四个?还是万一呢?
您可以想到的一个自然的解决方案是将所有数字放在一个列表中,并将其传递给函数:
**def multiply_numbers(list_of_numbers):
product = 1
for number in list_of_numbers:
product *= number
return product**
虽然这种解决方案可行,但是如果您不预先知道列表中的所有元素,它可能会有点不方便,也不太灵活。此外,它强迫你创建一个看起来不必要的列表对象。
用*args 传递非关键字参数
*args: 你可能以前在函数中见过这个奇怪的参数,但你从来不知道它是怎么回事。
嗯,在函数中使用*args 是 Python 判断这个函数将:
- 接受任意数量的参数
- 将收到的参数打包到一个名为 args 的元组中。注意 args 只是一个名字,你可以用任何你想要的东西来代替。(我们将在下面看到一个例子)
我们来扔点代码把事情说清楚。
让我们定义一个函数,它将参数打包在一个 args 变量中,并将这个变量打印到控制台。没什么特别的。
**def my_awesome_function(*args):
# do awesome job!
print(args)**
如果我们传递任意数量的参数:
**my_awesome_function(1, 2, 3)
(1, 2, 3)****my_awesome_function(1, 2, 3, 4)
(1, 2, 3, 4)**
我们会清楚地看到,它们确实都被打包在一个元组中。
即使我们没有通过任何论证,
**my_awesome_function()
()**
我们仍然得到一个(空的)元组。
这可能会让一些人有点畏缩,但是您甚至可以打包不同类型的多个参数。
**my_awesome_function(1, "2", [1, 2])
(1, "2", [1, 2])**
*运算符是做什么的?
这是一个拆包符。它出现在一个 iterable(例如一个列表或一个元组)之前,它真正做的是将它的元素作为函数的参数展开。它基本上是一种符号,其中:
**my_awesome_function(1, 2, 3)**
相当于:
**my_awesome_function(*[1, 2, 3])**
现在,回到我们之前关于乘法的例子,我们可以通过打包重写函数。
**def multiply_numbers(*numbers):
product = 1
for number in numbers:
product *= number
return product**
这个函数现在可以接收任意数量的参数,即使您有一个数字列表,您仍然可以使用它:这就是解包的用处。
**multiply_numbers(*large_list)**
用**kwargs 传递关键字参数
首先,什么是关键词论点?
当您定义如下所示的函数时:
**def sum_numbers(a, b):
return a + b**
您可以用两种不同的方式调用它,或者通过
1-传递位置参数
**sum_numbers(1, 2)**
2-或传递关键字(或命名)参数
**sum_numbers(a=1, b=2)**
正如您所料,Python 也有自己传递可变长度关键字参数(或命名参数)的方式:这是通过使用 **kwargs 符号来实现的。
使用**kwargs 时,传递给函数的所有关键字参数都打包在一个字典中。正如您所料,这个字典变量叫做 kwargs。
让我们看一个利用 ****kwargs 的小例子。**一个简单的例子,接受关键字参数并打印它们的键值对。
**def show_user_info(**data):
# data is a dict
for key, value in data.items():
print(f"{key}: {value}")****>>> show_user_info(name="Ahmed")
name: Ahmed****>>> show_user_info(name="Ahmed", job="data scientist")
name: Ahmed
job: data scientist**
使用*args 和**kwargs
现在您已经理解了这两种符号的基础,有一些适用于它们的特定规则:
- 您可以在位置参数中使用args。在这种情况下,args 必须在末尾
**def my_function(a, b, *args):
# do magic stuff**
- 如果我们在前面的例子中添加一个带有默认值的位置参数,这个参数必须在最后
**def my_function(a, b, *args, c=2)
# do more magic stuff**
- 您可以将 *args 与 **kwargs 组合起来,甚至可以添加带有默认值的位置参数。在这种情况下,顺序是: *args 、带有默认值的位置参数、 **kwargs
**def my_function(a, b, *args, c=2, **kwargs):
# make Harry Potter cry**
结论
让我们结束它。
- *args 和 **kwargs 允许你拥有可变长度的参数
- *args 允许您传递可变数量的非关键字参数,这些参数被打包到一个元组中
- **kwargs 允许您传递可变数量的关键字参数,这些参数被打包到一个字典中
- *args 和 **kwargs 让你的代码更加灵活
- *args 和**kwargs 可以与位置参数组合在一起
资源
如果您有兴趣更深入地研究*args 和**kwargs 主题,您可以查看以下资源:
- https://stack overflow . com/questions/33542959/why-use-packed-args-kwargs-inst-of-passing-list-dict
- https://www.pythontutorial.net/python-basics/python-args/
- https://trey hunner . com/2018/04/keyword-arguments-in-python/# What _ are _ keyword _ arguments?
- https://towards data science . com/10-examples-to-master-args-and-kwargs-in-python-6f1e 8 cc 30749
感谢阅读🙏
同样,如果你已经做到了这一步,我想感谢你的时间,并希望你已经学到了一些有用的东西,使你的代码更干净,更灵活。
今天就这些了。下次见!👋
新到中?您可以每月订阅 5 美元,并解锁无限的文章— 单击此处。

亚历山德拉·诺维兹卡娅在 Unsplash 上的照片
如何使用 SQL 在 BigQuery 中使用变量—第 1 部分
原文:https://towardsdatascience.com/how-to-use-variables-in-bigquery-using-sql-part-1-a10aa99d8802
通过 WITH 子句使用参数和变量向灵活性和可重用性迈进了一步

什么是变量,为什么它们有用?
变量也被称为 参数 。它们可以在 SQL 中用于设置或声明值。
变量存储您提供给它们的一个或多个值,然后可以在查询中的任何地方使用。
通常在代码开始时设置,它们在以下情况下很有用:
- 您不希望在几个地方更改相同的值,因为您的查询有多行代码
- 您希望在不影响代码逻辑的情况下轻松更改该值
- 您希望减少查询的长度和复杂性
变量可以通过命令行或使用 BigQuery API 在 Python 等其他语言中使用。但是在本文中,我们将关注如何在 SQL 查询中使用它。
在 BigQuery 中,我们有两种使用变量的方法:
- 使用带有子句的
- 使用 BigQuery 程序语言
在这篇名为“第一部分”的文章中,我们将只讨论 WITH 子句。
如果想了解更多关于过程语言(类似于脚本语言)的知识,可以参考 BigQuery 文档。
**https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language
作为一个相关主题(但不完全与本文相关),您可能也对使用存储过程感兴趣,它也可以利用过程语言。
https://cloud.google.com/bigquery/docs/procedures
使用 WITH 子句的变量
我们从一个基本数据表开始,它包括日期、国家、产品名称和与每个订单相关的收入。

我们的基本数据表。(图片由作者提供)
试验单个值(手动)
在第一个例子中,我们希望获得所有收入大于等于 250 美元的产品。
在“主查询”部分,我们使用逗号作为,在FROM子句中调用我们的表(基表和变量)之后,能够使用我们的过滤子句中的值,而无需任何连接。
但是,我明白你的意思,你是对的:我们本可以避免使用WITH子句,而只使用WHERE子句中的值。
但是,如您所见,这是将该值放在查询顶部的方便位置。让我们看一个稍微复杂一点的例子,有不止一个变量,查询两个表而不是一个。
尝试多个值(手动)
我们现在有了多个变量,我们把它们写在一个数组中(我们直接UNNEST来获得所有的值作为单独的行)。我们还有两个表,base_table和base_table_2,它们重用同一个过滤变量。
在这种情况下,在保存变量的WITH子句中添加、更改或删除值会更快。
另一个技巧是,如果您想要组合多个值,可以使用一个组合了ARRAY和STRUCT类型的格式。
保存我们的变量的子句现在有两个字段与价格和产品名称** t 相关。它可以在过滤器中使用,与我们前面的示例相同。**
既然我们已经看到了如何使用手动值,让我们看看如何使它们动态,基于从另一个表中计算出的值,或者基于在接收新数据时会改变的数据。
尝试单一值(动态)
从我们的基表开始,我们希望找到每件产品的收入是平均产品收入的三倍的所有行。
我们的WITH子句将返回 144.2 ,这是使用我们的基表上的平均产品收入乘以 3 动态计算的。请注意,您可以使用任何您想要的数据集,并且当使用新数据执行该查询时,该值可能会改变。
尝试列表(手动)
本着同样的精神,你也可以使用项目清单。假设我们希望在我们的基表中过滤特定的项目名称。
用数组创建的手动列表
您也可以在WITH子句中使用UNION ALL编写相同的查询。但是我发现数组写起来更快。
使用 UNION ALL 创建的手动列表
尝试列表(动态)
我们希望所有包含“谷歌”一词的产品。我们可以在变量语句中使用LIKE语法获得所有这些值。
试验日期(手动)
对于日期,手动添加需要将数据类型转换为DATE类型(或者在查询中使用的TIMESTAMP或DATETIME)。
试验日期(动态)
我们希望使用另一个数据集的数据(在我们的例子中是base_table_2)来获得一个动态范围,以便在我们的主 SQL 语句中进行过滤。
几句结束语
本文通过几个例子演示了在WITH子句中使用变量的技巧。我们将在“第 2 部分”中讨论 BigQuery 过程语言(也称为脚本语法)。
正如我们所见,WITH子句可能非常有用,但它并不总是理想的:它增加了更多的代码行,您可能需要修改数据类型(就像手动日期示例中一样),BigQuery 为声明和设置变量提供了更简单的语法(使用DECLARE和SET子句)。
我希望这将有所帮助,并让我知道你是否想看到更多的例子!**
如何使用 VEST:一个免费的在线视频剪辑分割工具
实践教程
如何使用 VEST:一个免费的在线视频剪辑分割工具
使用这款视频事件分割工具对您的视频片段进行定性编码,推动您的 ML 研究

香草熊电影公司在 Unsplash 拍摄的照片
分析人类和动物行为的视频记录已经成为跨学科研究人员的有力工具。
近年来,计算机视觉和机器学习的进步极大地促进了自动化视频分析(例如,对象检测、面部表情、姿势)。然而,仍然需要人工输入来识别和标记新的事件类别、交互或现象,以分析和训练新的模型。
作为一名社会心理学研究者,我花了很多时间分析社会互动的视频记录。然而,我从未对可用于标记和分割事件的视频工具感到满意(例如 ELAN )。许多工具是专有的,需要复杂的安装,并且难以使用(工具列表)。
经过多年的搜索,我终于建立了自己的视频事件分割工具(背心),这是免费的,开源的,可定制的,易于使用。下面是如何使用 VEST 进行自己研究的教程。
如何使用背心
1.找到工具
可通过您的网络浏览器(如 Chrome、Safari、Edge)在线访问 VEST。你所要做的就是导航到https://jinhyuncheong.com/vest/vest.html,你马上就有了这个工具(截图如下)。

视频事件分割工具(VEST)正在发挥作用。工具包括一个视频播放器,音频波形可视化,和右边的控制面板。图片作者。
2.加载您的视频
加载网站后,使用Choose file按钮加载视频。VEST 在幕后使用 Web Audio API 和 audio BBC 的 peaks.js 来可视化您视频中的音频波形。这种可视化有助于您更准确地识别事件的开始和结束。
3.设置您的细分标签
加载视频后,您可以使用Add segment buttons为感兴趣的事件添加标签。此按钮将要求您写出活动的名称。例如,如果您只是在不同的说话者说话时进行分段,这可能是Speaker 1(见左下图)。单击“提交”后,您会看到屏幕右侧的控制面板中出现一个带有事件标签的按钮(右下图)。
现在,您可以播放视频,并在需要标记该片段时单击 Speaker 1 按钮。添加段后,您可以微调开始和停止时间。


(左)添加事件分段按钮。(右)在控制面板上增加了按钮。作者图片。
4.保存您的注释
一旦你完成了视频编码,你可以使用控制面板上的Download results按钮将结果下载到一个标准的 json 文件中。
您可以将数据文件加载到任何分析语言中(例如 R、Python)。下面是一个 Python 示例:
常见问题
- 波形太小。使用
Amplitude scale增加波形的大小。您也可以使用Zoom in或Zoom out按钮来调整可视化波形的时间宽度。 - 我的音频无法加载。从视频中提取音频可能需要几秒钟才能显示出来。自然,较长的视频需要较长的时间来提取音频。如果还是看不出来,请开一期的。
- 我有一个很长的视频,怎么办? VEST 没有后端服务器。这意味着如果你关闭浏览器,所有的进程都将丢失。因此,我强烈建议您在处理长视频时经常保存。
- 如何定制工具? VEST 是开源的,所以你可以在https://github.com/jcheong0428/vest访问源代码。您可以克隆存储库,并根据需要进行任何修改,在本地运行网页。
- 我想对工具给出反馈。我很想得到您对如何改进该工具的反馈。请注意,该工具是非常基础的,因此它灵活且易于使用。尽管如此,欢迎通过项目库或 Twitter 提出任何建议或错误报告。
我希望你在下一个视频分析项目中发现 VEST 有用。期待所有未来将要建立的很酷的新分析和机器学习模型!
简单回顾一下,在这里使用视频事件分割工具:https://jinhyuncheong.com/vest/vest.html
这里是储存库:
https://github.com/jcheong0428/vest
如何使用维基百科作为数据来源
原文:https://towardsdatascience.com/how-to-use-wikipedia-as-a-data-source-3dfea29e6539
如何通过寻找英超最佳球队将维基百科的信息载入熊猫

温布利体育场(我想)——由米奇·罗森在 Unsplash 上拍摄的照片
维基百科是一个有用信息的宝库,但维基百科页面上的数据是供人们阅读的,因此不一定是以一种易于编程访问的形式。
但是通过使用 Pandas read_html()函数,然后进行一些清理和处理,我们可以使用维基百科表格中的数据进行有用的分析。
我们要解决一个简单的问题:英格兰最好的足球队是哪支?是曼联(可能是最有名的),阿森纳,利物浦,切尔西?还是完全不同的团队?
当然,这是一个非常主观的判断,但我们将通过分析几个维基百科页面的数据来试图找到某种答案,英超联赛(顶级球队参加的英格兰足球联赛)和2020-21 英超联赛。第一页给了我们一些关于球队的历史数据,第二页记录了 2020-21 赛季的结果。
维基百科内容的使用受知识共享署名-类似共享许可 3.0 的管辖(参见下面的注释),这意味着只要我们给出适当的署名,我们就可以自由使用它,并且我们可以在类似的许可下复制、分发和衍生原始作品的其他作品。
我们将使用的第一个表主要包含历史数据,而第二个表包含最近一季的结果。我们将看一看每一个团队,看看我们是否能找出一个团队是否明显比其他团队更好。
我将在 Jupyter 笔记本上开发程序代码,该笔记本将在我的 Github repo 中提供(见注释)。如果你想继续,那么把每个代码块放入笔记本的一个新单元中。
和往常一样,起点是导入库——或者在本例中只导入一个库。
import pandas as pd
获取数据
现在我们需要一些数据。正如我所说的,我们将从英超联赛页面中读取表格,所以我们复制 url 并使用read_html()创建一个熊猫数据帧。
这个函数将读取它在页面上找到的所有 HTML 表,并返回一个数据帧列表。Pandas 文档建议您可能需要过滤结果,因为一个页面上可能有许多表格。我们通过设置match参数来实现这一点。这告诉该函数只下载包含该字符串的表。
我们对列出 2020-21 年英超联赛中 20 家足球俱乐部的表格感兴趣,因为这为我们提供了当前顶级俱乐部的列表和一些关于它们的历史数据。该表的第二列包含单词“Position ”,因此我们将使用它作为过滤器。
url = "https://en.wikipedia.org/wiki/Premier_League"
infotable = pd.read_html(url, match = "Position")
infotable[0].head()
上面的代码返回了一个表格列表。我们感兴趣的是列表中的第一个——索引为 0 的那个——我们显示结果数据帧的头部。

图片由作者提供,数据来源:维基百科 CC BY-SA 3.0(见注 1)
如您所见,列“2020–21 position”似乎没有被正确解析。它应该包含一个显示联盟位置的字符串,例如第一、第二、第十等。解析器可能感到困惑,因为列中的每个值都以数字开头,所以它试图解释为数字。
我们并不是对这个专栏特别感兴趣,但是值得停下来看一看,因为它突出了解析器的潜在问题以及我们可以做些什么来修复它。
问题在于默认的“lxml”解析器,它很快,但我们可以使用一个更慢但更准确的解析器,即“bs4”(beautiful soup)解析器。让我们试试这个替代方案,看看我们会得到什么结果:
infotable = pd.read_html(url, match = "Position", flavor = 'bs4')
infotable = infotable[0]
infotable.head()
在这段代码中,我们更改了解析器,结果确实更加准确。

图片由作者提供,数据来源:维基百科 CC BY-SA 3.0(见注 1)
如果你运行这段代码,你可能不会注意到执行速度的不同。虽然,根据 Pandas 文档,‘bs4’解析器与‘lxml’相比非常慢,但是对于维基百科上相对较小的表,性能上的差异可能可以忽略不计。但是‘bs4’解析器给出的结果更接近原始结果。
细化数据框架
我只想从这张表上得到一点信息。这些日期可能很有趣,但与我寻找最佳团队的目标并不相关。
浏览维基百科的原始表格,我决定使用其中的三列:俱乐部名称,他们在顶级联赛(目前是英超联赛,但之前是甲级联赛)的赛季数,以及他们赢得联赛的次数。
下面是创建新数据帧并复制相关列的代码:
infotable2 = pd.DataFrame()
infotable2['Club'] = infotable['2021–22Club']
infotable2['No-of-Seasons'] = infotable['Seasonsin topdivision']
infotable2['Titles'] = infotable['Topdivisiontitles']
这是结果表——团队按字母顺序排列:

图片由作者提供,数据来源:维基百科 CC BY-SA 3.0(见注 1)
但是仍然有一些问题需要解决。最初的桌子是给人看的,不是给机器看的。
提取更多数据
第一个数据列包含俱乐部名称,但附加到该名称的是括号中的字母,表示维基百科页面中表格下方的注释。注[a]意味着该队是超级联赛的创始成员。[b]表示他们从未被降级到较低的联赛,[c]表示该队是 1988 年开始的原始足球联赛的成员之一。
人类读者可以很容易地理解这一点,但是在试图以编程方式分析数据时,在一列中包含多个数据项是没有用的。
我将在这里做出一些判断,我的观点是,一支球队从未降级到较低的级别是一个重要的事实,因为它多年来表现出了一致性。然而,一支球队是最初的足球联赛或更现代的英超联赛的创始成员,这一事实只表明他们在特定时刻的成就,因此并不特别相关。
我想提取一个球队从未降级的事实,但忽略其他注释,所以下面我创建了一个新的列来显示这一点。
infotable2['Never relegated'] =
infotable2['Club'].str.contains('[b]')
这段代码主要是在 Club 列中查找字符串“[b]”,并返回True或False.这个值然后被记录在新列中。
然后我删除了俱乐部一栏中的参考资料,按照一支球队在顶级联赛中的时间长度对数据进行排序,并显示出前 10 名。
infotable2['Club'] =
infotable2['Club'].replace('\[.\]','',regex=True)infotable2.sort_values('No-of-Seasons', ascending=False).head(10)

图片由作者提供,数据来源:维基百科 CC BY-SA 3.0(见注 1)
埃弗顿是在顶级联赛中时间最长的球队,其次是阿斯顿维拉。这可能会让现代足球爱好者感到惊讶,因为尽管他们有许多忠实的支持者,但这两支球队目前都不被视为顶级球队。
现在,让我们通过查看从未降级到低级别联赛的球队来进一步过滤数据框架,并根据他们在顶级联赛中的赛季数对他们进行排序。
infotable2[infotable2['Never relegated']==True]
.sort_values('No-of-Seasons', ascending=False)

图片由作者提供,数据来源:维基百科 CC BY-SA 3.0(见注 1)
我们可以从这里看到,虽然没有一家俱乐部降级,但在布伦特福德的情况下,这是因为他们最近才升级,而且他们和布莱顿以及霍夫阿尔比恩相比,在顶级联赛中的时间很短。
因此,基于这些年来的一贯表现,我们也将从表格中删除最后两个,并创建一个新的数据框架candidates,其中包含前六名球队。我们可以删除Never relgated列,因为这对所有人都适用。我们将把No-of-seasons和Titles列转换成整数。
candidates = infotable2[infotable2['Never relegated'] == True]
.sort_values('No-of-Seasons', ascending = False)[:6]candidates = candidates.drop(columns = ['Never relegated'])candidates['No-of-Seasons'] =
candidates['No-of-Seasons'].astype('int64')candidates['Titles'] = candidates['Titles'].astype('int64')
我们最新的表格现在看起来像这样:

图片由作者提供,数据来源:维基百科 CC BY-SA 3.0(见注 1)
让我们制作这个表格的条形图:
candidates.plot.bar(x = 'Club')

现在我们要列出一个 3 人小组的候选名单——前三名。
candidates = candidates.sort_values('Titles', ascending = False)[:3]

图片由作者提供,数据来源:维基百科 CC BY-SA 3.0(见注 1)
所以这里我们有英格兰足球历史上表现最好的三支球队。
我们暂时把它放在那里,看看当前的情况。
2020-21 年排行榜
我们现在要下载另一张表格——2020-21 赛季的最终排名表。
我们做的和以前一样,这一次使用匹配字符串“Pos”。这使得我们的排名表成为返回的表中的第二个表(即索引为 1 的表)。
url="https://en.wikipedia.org/wiki/2020%E2%80%9321_Premier_League"
leaguetable = pd.read_html(url,match="Pos")
leaguetable = leaguetable[1]
这就是了。

图片由作者提供,数据来源:维基百科 CC BY-SA 3.0(见注 1)
幸运的是,这里没有太多的编辑工作要做,我们可以直接从这个表格中绘制一些条形图,展示 20 个团队的成功。这是三个条形图的代码,代表每支球队的进球数,他们对他们的进球数,以及他们比赛的结果。
# Goals scored by team
leaguetable.plot.bar(x = 'Team',
y = 'GF',
title = 'Goals scored',
legend = False,
figsize = (15,5));# Goals scored against team
leaguetable.plot.bar(x = 'Team',
y = 'GA',
title='Goals against',
legend=False,
figsize=(15,5));# Match results - win/lose/draw
leaguetable.plot.bar(x = 'Team',
y=['W','D','L'],
stacked=True,
title='Match Results',
legend=False,
figsize = (15,5))
.legend(['Win','Draw','Lose'],
loc='lower center');
这是图表。

图片由作者提供,数据来源:维基百科 CC BY-SA 3.0(见注 1)

图片由作者提供,数据来源:维基百科 CC BY-SA 3.0(见注 1)

图片由作者提供,数据来源:维基百科 CC BY-SA 3.0(见注 1)
现在,让我们用最佳团队的前三名候选人列表来过滤该表。
leagueCandidates = leaguetable[leaguetable['Team']
.isin(list(candidates['Club']))]

图片由作者提供,数据来源:维基百科 CC BY-SA 3.0(见注 1)
我们将再次绘制这些图表。
leagueCandidates.plot.bar(x='Team',
y='GF',
title='Goals scored',
legend=False);leagueCandidates.plot.bar(x='Team',
y='GA',
title='Goals against',
legend=False);leagueCandidates.plot.bar(x='Team',
y=['W','D','L'],
stacked=True,
title='Match Results',
legend=False,)
.legend(['Win','Draw','Lose'],
loc='lower center');

图片由作者提供,数据来源:维基百科 CC BY-SA 3.0(见注 1)
从这里我们可以看到我们的短名单上的球队都表现很好,他们赢得了大部分比赛,进了很多球。然而,在这个列表中名列前茅的是曼联:他们比其他球队赢得了更多的比赛,进了更多的球——他们也比其他球队进了更多的球,但进球越多,比赛就越有趣,对吗?
结果呢
所以,考虑到过去和现在的表现,我们选择的最佳球队是曼联。
除了如果你住在伦敦,你会非常清楚最好的球队实际上是阿森纳…或者切尔西。另一方面,如果你是曼彻斯特人,那么你可能会认为我们的发现完全合理,当然,除非你是曼城的支持者。如果你来自利物浦,那么每个人都知道利物浦是最好的球队——除了那些认为埃弗顿永远是冠军的人。
但这真的不是这个练习的目的。
我们所做的是使用 Pandas read_html()函数直接从维基百科中读取数据,我们已经研究了选择快速解析器或较慢(但可能更准确)解析器的方法。我们已经清理和处理了这些数据,并将其与维基百科中的其他数据结合起来。最终,我们得出了一个结果,这个结果不会改变任何人对他们最喜爱的足球俱乐部的看法——但重点是过程而不是结果。
一如既往,感谢阅读。我希望你觉得这很有趣,并会通过我的 Github 页面上的链接来查看代码(或者在下面的注释中看到 Github 回购的链接)。
如果你对我的其他文章感兴趣,你可以在 Medium 上浏览我的简介,或者在我的网站上看到它们。你也可以订阅我的时事通讯, Technofile ,获得新文章的通知。
https://technofile.substack.com
笔记
[1]本文中的所有数据都是从以下维基百科页面下载和/或获得的:
- 英超联赛(2022 年 4 月 24 日访问)
- 2020–21 英超联赛(2022 年 4 月 24 日访问)
所有数据都在知识共享署名-相似分享许可 3.0 下使用
[2]所有图形和代码都是作者创建的,包含所有代码的笔记本可以在我的 Github 资源库中找到。
如何使用来自拥抱脸变压器库的 XLNET
如何使用 XLNET 从拥抱脸变压器库的三个重要任务

照片由 Ahmed Rizkhaan 在 Unsplash 上拍摄
在本文中,我将演示如何使用 XLNET 通过拥抱脸变压器库完成三项重要任务。我还将展示如何配置 XLNET,这样,除了它被设计用来解决的标准任务之外,您还可以将它用于您想要的任何任务。
请注意,本文写于 2022 年 4 月,因此拥抱脸库的早期/未来版本可能会有所不同,本文中的代码可能无法工作。
XLNet 快速回顾
XLNET 是一个通用的自回归模型,它使用置换语言建模来创建单词的双向上下文化表示。值得注意的是,它建立在 BERT transformer 的弱点之上,并在许多任务上优于 BERT,如问题回答、情感分析等。虽然 BERT 是一个非常强大和通用的转换器,但它的架构固有地具有两个弱点。首先,因为它使用屏蔽语言建模来生成单词的上下文化表示,所以它扭曲了输入,所以 BERT 真正使用屏蔽单词的方式是未知的。第二,当 BERT 屏蔽一个句子中的多个标记时,它不能捕获两个被屏蔽的标记之间的依赖关系,这两个标记可能拥有彼此的重要信息。XLNET 相对于 BERT 的另一个主要的、强大的优势是,与具有 512 个令牌输入限制的 BERT 不同,XLNET 是少数几个没有序列长度限制的模型之一。
XLNET 通过置换语言建模捕获单词周围的双向上下文来克服这些问题。在不屏蔽任何单词或改变输入的情况下,置换语言建模通过对句子中所有可能的单词置换训练自回归模型来捕获上下文。它最大化了一个句子所有排列的对数似然,因此,文本中的每个标记都学会了利用句子中所有其他标记的上下文信息,从而创建了强大、丰富的单词表示。
XLNet 可以解决许多任务,但是我将在本文中讨论的是多项选择问题回答、抽取问题回答和语言建模。我还将演示如何配置 XLNET 来完成除上述任务和拥抱脸提供的任务之外的任何任务。
请注意,对于我在本文中展示的所有代码/模型,我都是直接从 Hugging Face transformer 库中获取的,没有任何微调/培训。与许多其他多功能变压器一样,XLNET 在核心自回归模型的基础上增加了一个线性层,以针对特定任务进行自我微调。虽然拥抱脸确实为核心模型提供了预训练的权重,但它不为顶部的线性层提供权重。为了实现每个特定任务的最佳性能,必须针对正在解决的任务训练该线性层,因此本文中代码的结果可能不会很好。
多项选择问题回答
选择题答题简单来说就是顾名思义。我没有把这个题目叫做“问题回答”的唯一原因是因为问题回答的另一个版本:摘录问题回答。在抽取式问题回答中,该模型试图在上下文段落/文本中找到答案,而不是像选择题一样在几个答案选项中进行选择。
在运行下面的代码之前,您必须确保运行该代码,以导入代码编译所需的库。
pip install transformers
pip install sentencepiece
pip install torch
## All of these lines can vary depending on what version of
## each library you use
下面是我做选择题回答的代码:
from transformers import XLNetTokenizer, XLNetForMultipleChoice
from torch.nn import functional as F
import torch
tokenizer = XLNetTokenizer.from_pretrained("xlnet-base-cased")
model = XLNetForMultipleChoice.from_pretrained("xlnet-base-cased", return_dict = True)
prompt = "What is the capital of France?"
answers = ["Paris", "London", "Lyon", "Berlin"]
encoding = tokenizer([prompt, prompt, prompt, prompt], answers, return_tensors="pt", padding = True)
outputs = model(**{k: v.unsqueeze(0) for k, v in encoding.items()})
logits = outputs.logits
softmax = F.softmax(logits, dim = -1)
index = torch.argmax(softmax, dim = -1)
print("The correct answer is", answers[index])
拥抱脸是这样设置的,对于它有预训练模型的任务,你必须下载/导入那个特定的模型。在这种情况下,我们必须下载用于多项选择问题回答模型的 XLNET,而标记器对于所有不同的 XLNET 模型都是相同的。
我们首先对问题和 4 个答案选项进行编码。多项选择的工作方式非常简单:该模型计算每个答案选项的得分,对这些得分进行软最大化以获得概率分布,并只取最高值(张量中最高值的索引使用 torch.argmax 找到)。在 softmax 函数应用于 XLNET 的输出之前,logits 是 XLNET 模型的输出。通过将 softmax 应用于输出逻辑,我们可以获得每个答案选项的概率分布:具有较高概率的答案选项意味着它们是问题的更好/最佳答案。我们可以使用 torch.argmax 检索具有最高概率值的答案的索引。如果您想知道每个答案选项的每个概率值是多少(即模型如何对每个选项进行评级),您可以简单地打印出 softmax 值的张量。在我的例子中,这是它打印的内容(记住,这个模型顶部的线性层没有经过训练,所以值不好)。
tensor([[0.2661, 0.2346, 0.2468, 0.2525]])
在这种情况下,模型正确地预测答案是巴黎。但是,您可以看到 softmax 值非常接近。HuggingFace 提供了能够处理问题和答案的基础、预训练的架构,以及顶部的未训练的线性分类器来创建适当的输出。从这些值来看,很明显模型需要训练才能达到好的结果。
抽取式问题回答
抽取式问题回答是在给定一些上下文文本的情况下,通过输出答案在上下文中所处位置的开始和结束索引来回答问题的任务。以下是我使用 XLNET 回答问题的代码:
from transformers import XLNetTokenizer
from transformers import XLNetForQuestionAnsweringSimple
from torch.nn import functional as F
import torch
tokenizer = XLNetTokenizer.from_pretrained("xlnet-base-cased")
model = XLNetForQuestionAnsweringSimple.from_pretrained("xlnet-base-cased",return_dict = True)
question = "How many continents are there in the world?"
text = "There are 7 continents in the world."
inputs = tokenizer.encode_plus(question, text, return_tensors='pt')
output = model(**inputs)
start_max = torch.argmax(F.softmax(output.start_logits, dim = -1))
end_max = torch.argmax(F.softmax(output.end_logits, dim=-1)) + 1
## add one because of python list indexing
answer = tokenizer.decode(inputs["input_ids"][0][start_max : end_max])
print(answer)
像多项选择问题回答一样,我们首先下载用于问题回答的特定 XLNET 模型,并标记我们的两个输入:问题和上下文。HuggingFace 提供了两个 XLNET 模型用于抽取式问题回答:用于简单问题回答的 XLNET 和用于问题回答的普通 XLNET。你可以在官方的 HuggingFace transformer 库页面上了解更多关于这两者的信息。提取性问题回答的过程与多项选择略有不同。抽取式问题回答的工作方式是通过计算答案在上下文中所处位置的最佳开始和结束索引。该模型返回上下文/输入中所有单词的分数,该分数对应于它们对于给定问题的起始值和结束值有多好;换句话说,输入中的每个单词接收表示它们是答案的好的开始单词还是答案的好的结束单词的开始和结束索引分数/值。然后,我们计算这些分数的 softmax 以找到值的概率分布,使用 torch.argmax()检索开始和结束张量的最高值,并在输入中找到对应于这个 start : end 范围的实际标记,解码它们并打印出来。
语言建模
语言建模的任务是在给定句子中所有单词的情况下,预测跟随/继续句子的最佳单词。
from transformers import XLNetTokenizer, XLNetLMHeadModel
from torch.nn import functional as F
import torch
tokenizer = XLNetTokenizer.from_pretrained('xlnet-base-cased')
model = XLNetLMHeadModel.from_pretrained('xlnet-base-cased', return_dict = True)
text = "The sky is very clear at " + tokenizer.mask_token
input = tokenizer.encode_plus(text, return_tensors = "pt")
output = model(**input).logits[:, -1, :]
softmax = F.softmax(output, dim = -1)
index = torch.argmax(softmax, dim = -1)
x = tokenizer.decode(index)
print(x)
new_sentence = text.replace(tokenizer.mask_token, x)
print(new_sentence)
我们首先下载用于语言建模的特定 XLNET 模型,并标记我们的输入:不完整的句子(该句子必须像我上面所做的那样将掩码标记连接到句子的末尾)。代码相对简单:我们必须检索模型的逻辑,使用-1 索引取最后一个隐藏状态的逻辑(因为这对应于句子中的最后一个单词),计算这些逻辑的 softmax(在这种情况下,softmax 创建 XLNET 词汇表中所有单词的概率分布;具有较高概率值的单词将是掩码标记的更好的候选替换单词),找到词汇表中的最大概率值,并解码和打印该标记。在上面的代码中,我正在检索具有最高概率值的单词(即最佳候选单词),但是如果您想知道前 10 个候选单词是什么(可以是前 10 个或您喜欢的任何数字),那么您可以这样做。通过使用 torch.topk()函数而不是 torch.argmax(),可以检索给定张量中的前 k 个值,并且该函数返回包含这些前 k 个值的张量。在此之后,过程与之前相同:迭代张量,解码每个候选单词,并用候选单词替换句子中的掩码标记。下面是执行此操作的代码:
from transformers import XLNetTokenizer, XLNetLMHeadModel
from torch.nn import functional as F
import torch
tokenizer = XLNetTokenizer.from_pretrained('xlnet-base-cased')
model = XLNetLMHeadModel.from_pretrained('xlnet-base-cased', return_dict = True)
text = "The sky is very clear at " + tokenizer.mask_token
mask_index = torch.where(input["input_ids"][0] == tokenizer.mask_token_id)
input = tokenizer.encode_plus(text, return_tensors = "pt")
output = model(**input).logits
softmax = F.softmax(output, dim = -1)
mask_word = softmax[0, mask_index, :]
top_10 = torch.topk(mask_word, 10, dim = 1)[1][0]
for token in top_10:
word = tokenizer.decode([token])
new_sentence = text.replace(tokenizer.mask_token, word)
print(new_sentence)
使用 XLNET 完成任何任务
尽管问题回答、语言建模和 XLNET 可以解决的其他任务在 NLP 中非常重要,但人们通常希望使用 XLNET 这样的转换器来完成其他独特的任务,尤其是在研究中。他们这样做的方式是通过采用核心,基础 XLNET 模型,然后将他们自己的特定神经网络附加到它(通常是线性层)。然后,他们针对特定的任务,在特定的数据集上对这种架构进行微调。在 Pytorch 中,最好将其设置为 Pytorch 深度学习模型,如下所示:
from transformers import XLNetModel
import torch.nn as nn
class XLNet_Model(nn.Module):
def __init__(self, classes):
super(XLNet_Model, self).__init__()
self.xlnet = XLNetModel.from_pretrained('xlnet-base-cased')
self.out = nn.Linear(self.xlnet.config.hidden_size, classes)
def forward(self, input):
outputs = self.xlnet(**input)
out = self.out(outputs.last_hidden_state)
return out
我没有下载已经为特定任务(如问答)设计的特定 XLNET 模型,而是下载了基本的、预训练的 XLNET 模型,并为其添加了一个线性层。要获取 XLNET 模型的原始核心输出,请使用 xlnet.config.hidden_size(实际值为 768)并将其附加到您希望线性图层输出的类的数量。
我希望您觉得这些内容很容易理解。如果你认为我需要进一步阐述或澄清什么,请在下面留言。
参考
如何验证合成数据的质量
原文:https://towardsdatascience.com/how-to-validate-the-quality-of-your-synthetic-data-34503eba6da
关于如何将 ydata-synthetic 与 great expectations 结合起来的教程

由 Unsplash 上的 Pietro Jeng 拍摄
随着机器学习算法和编码框架的快速进化,高质量数据的缺乏才是 AI 行业真正的瓶颈。
VentureBeat的 Transform 2019 预测,87%的人工智能项目永远无法投入生产。根据 2017 年哈佛商业评论的一项研究,只有 3%的公司数据符合基本质量标准。
如果你在人工智能行业工作,这不会让你感到惊讶——我们都有第一手的经验。幸运的是,我们已经看到了由吴恩达开创的范式转变,从以模型为中心的方法转变为以数据为中心的方法。
如果使用得当,合成数据就是这样一种元素,可以帮助实现以数据为中心的方法。合成数据是人工生成的数据,不是从现实世界的事件中收集的。它复制实际数据的统计成分,不包含任何可识别的信息,确保个人隐私。我们针对合成数据 ydata-synthetic 的开源库只专注于此。我们的用户普遍关心的一个问题是,我们如何确保合成数据符合原始数据的所有质量标准?与保留原始数据的统计属性一样,确保原始数据遵循严格的数据质量标准也是至关重要的。
幸运的是,我们在 Great Expectations 的朋友已经建立并开源了一个他们称之为共享、开放的数据质量标准的框架。当您的数据团队调整框架时,您就知道新数据会带来什么。
在本文中,我们将带您了解一个端到端的用例,在这个用例中,您使用原始数据来训练一个合成数据的模型,使用 Great Expectations 库根据原始数据验证合成数据的质量标准。
将 YData Synthetic 和 Great Expectations 结合在一起
YData Synthetic 是一个开源的合成数据引擎。使用不同种类的生成对抗网络(GANS) ,引擎学习原始数据的模式和统计属性。它可以创建与原始数据相似的合成数据的无限样本。

用 ydata-synthetic 生成合成数据。(图片由作者提供)
远大前程是一个工具,用于验证、记录、分析您的数据,以保持质量并改善团队之间的沟通。它允许用户基于良好的数据样本创建“期望”,并使用这些期望来验证新数据是否符合数据质量标准。

带着巨大期望的数据验证。(作者截图)
当然,在本文的范围内,我们保持了简单的介绍,还有更多关于这些库的内容,正如我们所说的,它们正在发展。这个想法是:两个图书馆都在朝着同一个目标努力,在不同的战线上提供大规模的高质量数据。
10 步指南:如何在你的下一个项目中同时使用这两种工具

综合项目流程图。绿色是远大前程,红色是 YData 合成。(图片由作者提供)
在本指南中,我们选择了一个用例示例“信用卡欺诈数据集——综合少数类。”我们旨在合成具有高度不平衡性的信用卡欺诈数据集的少数类。
不平衡的类数据是构建模型时的常见问题,为少数类生成合成数据有助于开发更准确的模型。但是,我们需要确保新生成的数据点保持与原始少数民族类数据点相同的质量标准。
我们将通过使用 YData Synthetic 生成合成数据并通过 Great Expectations 进行验证来解决这个问题。
在我们开始之前,为了跟随教程,我们建议您创建一个虚拟环境,并通过在您的终端上运行以下命令来 pip 安装 ydata-synthetic 和 great_expectations。
pip install ydata-synthetic great-expectations
现在您已经准备好了环境,让我们开始吧。如果你想一步一步地跟随我们,你可以从 ydata-synthetic GitHub 下载资料。
步骤 1:通过数据上下文设置项目结构
在很大程度上,您的数据环境管理项目配置。有多种方法可以创建数据上下文;然而,最简单的方法是使用安装 great_expectations 包时附带的 CLI。
打开您的终端,导航到项目目录并键入以下内容:
great_expectations init
按回车键完成数据上下文的创建,仅此而已。

设置数据上下文。(作者截图)
如果你对修改后的项目结构感兴趣,这里有一段 GE 文档的摘录:
- great_expectations.yml 包含您的部署的主要配置。
expectations/目录将您所有的期望存储为 JSON 文件。如果您想将它们存储在其他地方,可以在以后进行更改。plugins/目录保存您作为部署的一部分开发的任何定制插件的代码。uncommitted/目录包含了不应该存在于版本控制中的文件。它配置了一个. gitignore 来从版本控制中排除它的所有内容。
步骤 2:下载/提取我们用来创建合成数据的实际数据集
我们可以从 Kaggle 下载我们在这个例子中使用的数据。如果您检查这些类,您会注意到“欺诈”类比“非欺诈”类少得多,这是现实生活中的情况。
因此,我们可以仅提取欺诈类数据点,并将其视为实际数据集。我们的目标是创建类似欺诈事件的合成数据点。
步骤 3:配置数据源以连接我们的数据
在很大程度上,数据源通过管理配置和提供一致的、跨平台的 API 来引用数据,从而简化了连接。
让我们配置第一个数据源:一个到 repo 中提供的数据目录的连接。相反,这甚至可以是一个数据库连接等等。
great_expectations datasource new

创建新的数据源。(作者截图)
如上图所示,你会看到不同的选项。选择文件系统上的文件(用 Pandas 或 Spark 处理)和 Pandas。最后,输入目录为data(这里有我们的实际数据)。
一旦你输入了详细信息,一个 jupyter 笔记本就会打开。这正是 Great Expectations 赋予模板化代码的方式,这有助于我们通过一些代码更改来创建期望。
让我们将数据源名称改为更具体的名称。
编辑第二个代码单元格如下:datasource_name = "data__dir"
然后执行笔记本中的所有单元格来保存新的数据源。如果成功,最后一个单元格将打印所有数据源的列表,包括您刚刚创建的数据源。
步骤 4:使用内置的 Great Expectations profiler 创建一个期望套件
预期只不过是一个关于数据的可证伪的、可证实的陈述。期望提供了一种谈论数据特征和数据质量的语言——人对人,人对机器,机器对机器。
这里的想法是,我们假设实际数据具有我们想要合成的数据的理想质量,因此我们使用实际数据来创建一组期望,稍后我们可以使用这些期望来评估我们的合成数据。
CLI 将帮助创建我们的第一个期望套件。套房只是期望的集合。我们可以使用内置的分析器自动生成一个名为creditcard.quality的期望套件
在您的终端中键入以下内容:
great_expectations suite new
再次选择如上图所示的选项。我们使用自动分析器创建期望,并将其指向实际的数据集。
另一个 jupyter 笔记本将再次打开,其中包含用于创建新的期望套件的样板代码。代码相当标准;但是,请注意,所有列都被添加到第二个单元格中被忽略列的列表中。我们希望验证示例中的每一列;因此,我们应该从 ignored_columns 列表中删除这些列。
执行笔记本将创建一个针对实际信用卡欺诈数据集的预期套件。
步骤 5:转换真实数据用于建模
现在我们已经创建了期望套件,我们将焦点转移回创建合成数据。
在训练 GAN 之前,我们遵循标准的数据转换流程。我们正在应用 PowerTransformation —使数据分布更像高斯分布。
请随意尝试更多预处理步骤,因为这会产生更好的结果。
步骤 6:训练合成器并创建模型
既然我们已经对数据进行了预处理,那么是时候让我们先进的 ydata-synthetic GAN 模型发挥作用了。
对于这个例子,我们训练一种 GAN,称为 WGAN-GP ,它提供了急需的训练稳定性。
步骤 7:从合成器采样合成数据
既然我们已经建立了模型,现在是时候通过添加噪声来采样所需的数据了。这一步的美妙之处在于,您可以根据需要不断生成数据。当您想要生成可共享和可销售的不同数据副本时,这一步非常有用。
在我们的例子中,我们生成与实际数据相同数量的样本。
# use the same shape as the real data
synthetic_fraud = synthesizer.sample(492)
步骤 8:逆变换数据以获得原始格式
这里我们注意到,生成的合成数据仍然是变换后的形式,需要逆变换为原始结构。
synthetic_data = inverse_transform(synthetic_fraud , preprocessor)
步骤 9:创建一个新的检查点,根据真实数据验证合成数据
对于 Great Expectations 的常规用法,验证数据的最佳方式是使用检查点。检查点将成批数据与相应的期望套件捆绑在一起进行验证。
从终端运行以下命令:
great_expectations checkpoint new my_new_checkpoint
这将再次打开一个 Jupyter 笔记本,允许您完成我们的检查点的配置。编辑 data_asset_name,将我们要验证的数据引用到我们在步骤 8 中编写的文件名中。确保 expectation_suite_name 与我们在步骤 4 中创建的相同。
完成后,继续执行笔记本中的所有单元格。
步骤 10:使用数据文档评估合成数据
如果您一直这样做,您应该已经创建了一个新的检查点来验证合成数据。最后一步是取消 checkpoint 记事本的最后一个单元格的注释并执行它。
这将打开一个名为数据文档的 HTML 页面。我们可以检查数据文档中最近的检查点,并看到预期已经失败。通过单击检查点运行,我们可以得到一个详细的报告,显示哪些列的哪些期望失败了。
基于此输入,我们可以执行以下任一操作:
- 回到我们的数据转换步骤,修改转换,改变合成器或优化参数,以获得更好的合成数据。
- 回到期望套件,编辑一些不重要的期望(可能是针对特定的列)。是的——期望是可定制的,下面是你如何做到这一点的。
结论
在本教程中,我们已经成功地演示了 ydata-synthetic 和 great expectations 的用法。
介绍了一个 10 步指南,从配置数据上下文到使用数据文档评估合成数据。我们相信集成这两个库可以帮助数据科学家释放合成数据的力量和数据质量。
您可以在我们的 GitHub 资源库中找到本文使用的所有代码。你不会想错过他们 YouTube 频道上《远大前程》的信息丰富的研讨会系列。
还有问题吗?合成数据社区和我们在远大前程的朋友们总是愿意提供帮助。加入 slack 社区,与其他用户合作,并直接向我们的开发人员提问!
用改进的数据加速 AI。
如何使用 JSON 模式验证您的 JSON
原文:https://towardsdatascience.com/how-to-validate-your-json-using-json-schema-f55f4b162dce
Python 中 JSON 模式的简明介绍

费伦茨·阿尔马西在 Unsplash 上的照片
想象以下场景:你和你的队友正在开发一个新功能。你的任务是创建一个包含一些结果的 JSON 并发送给你的队友。她的任务是获取这个 JSON,解析它并保存在数据库中。你们口头上同意了键和类型应该是什么,并且你们每个人都实现了他们的部分。听起来很合理,如果 JSON 结构简单,它确实可以工作。但是有一天你出了个 bug,把钥匙送错了。您吸取了教训,决定创建一个 API,并将其记录在您团队最喜欢的文档平台中。现在,你们都可以看看这个 API,以确保正确地实现了它。
但这就足够了吗?假设您确实正确地实现了它。假设另一个队友做了一个改变,现在它返回一个数字数组而不是单个数字。你的队友不知道你的 API,所有东西都坏了。
如果在发送和解析 JSON 之前,可以直接在代码中验证 JSON,会怎么样?这就是我们拥有 JSON 模式的目的!
在这篇文章中,我将介绍 JSON Schema,为什么它如此强大,以及我们如何在不同的场景中使用它。
什么是 JSON 模式?
JSON 模式是一种基于 JSON 的格式,用于定义 JSON 数据的结构。它为给定的应用程序需要什么样的 JSON 数据以及如何与之交互提供了一个契约。它可以用于 JSON 数据的验证、文档、超链接导航和交互控制。
该模式可以在 JSON 文件中定义,并加载到您的代码中,也可以直接在代码中创建。
如何验证我们的 JSON?
轻松点。
validate(instance=your_json, schema=schema)
例如:
**from** **jsonschema** **import** validate
**>>>** *# A sample schema, like what we'd get from json.load()*
**>>>** schema = {
**... ** "type" : "object",
**... ** "properties" : {
**... ** "price" : {"type" : "number"},
**... ** "name" : {"type" : "string"},
**... ** },
**...** }
**>>>** *# If no exception is raised by validate(), the instance is valid.*
**>>>** validate(instance={"name" : "Eggs", "price" : 34.99}, schema=schema)
**>>>** validate(
**... ** instance={"name" : "Eggs", "price" : "Invalid"}, schema=schema,
**...** )
Traceback (most recent call last):
...
ValidationError: 'Invalid' is not of type 'number'
我为什么要使用 JSON 模式?
每个 JSON 对象都有一个基本的键值结构。键是一个字符串,值可以是任何类型 — 数字、字符串、数组、JSON 等。
在某些情况下,该值只能是特定的类型,而在其他情况下,该值更加灵活。我们的 JSON 中有些键是必需的,有些是可选的。还有更复杂的场景。例如,如果我们得到了某个密钥,那么第二个密钥必须出现。一个键值可以依赖于第二个键值。
所有这些场景以及更多场景都可以使用 JSON Schema 进行本地测试和验证。通过使用它,您可以验证自己的 JSON,并确保它在与其他服务集成之前满足 API 要求。
简单 JSON 模式
在这个例子中,我们的 JSON 包含了关于狗的信息。
{
"breed": "golden retriever",
"age": 5,
"weight": 13.5,
"name": "Luke"
}
让我们更仔细地看看这个 JSON 的属性,以及我们希望对每个属性实施的要求:
- 品种——我们只想代表三个品种:金毛寻回犬、比利时马利诺犬和边境牧羊犬。我们想证实这一点。
- 年龄—我们希望将年龄四舍五入到年,因此我们的值将表示为整数。在本例中,我们还希望将最大年龄限制为 15 岁。
- 权重—可以是任何正数、整数或浮点数。
- 名称—始终是字符串。可以是任何字符串。
我们的计划是-
{
"type": "object",
"properties":
{
"breed": {"type":"string", "enum":[
"golden retrievers",
"Belgian Malinois",
"Border Collie"
]
},
"age": {"type": "int", "maximum":15, "minimum":0},
"weight": {"type":"number", "minimum":0},
"name": {"type":"string"}
}
}
这样,只能添加 0 到 15 之间的年龄值,没有负体重,并且只有三个特定的品种。
简单数组模式
我们还可以验证数组值。
例如,我们想要一个具有以下属性的数组:2 到 5 项,唯一值,仅字符串。
['a','b','c']{
"type": "array",
"items": {"type": "string"},
"minItems": 2,
"maxItems": 5,
"uniqueItems": true
}
更复杂的功能
必需的属性
有些属性是必需的,如果它们缺失,我们会提出一个错误。
可以添加**required**关键字。
{
"type": "object",
"properties":
{
"breed": {"type":"string"},
"age": {"type": "int", "maximum":15, "minimum":0}
}
"required":["breed"]
}
在这种情况下,如果缺少“品种”属性,将会引发错误。其他属性如“年龄”仍然是可选的。
需要家属
如果给定的属性存在于对象中,关键字**dependentRequired**有条件地要求某些属性存在。
{
"type": "object",
"properties": {
"name": { "type": "string" },
"credit_card": { "type": "number" },
"billing_address": { "type": "string" }
},
"required": ["name"],
"dependentRequired": {
"credit_card": ["billing_address"]
}
}
在这种情况下,如果出现“信用卡”属性,则需要“帐单地址”。
一个/任何一个
到目前为止,每个属性只有一种类型。如果我们的财产可以有几种不同的类型呢?
示例 1 — **anyOf** —要根据anyOf进行验证,给定的数据必须对任何(一个或多个)给定的子模式有效。
{
"anyOf": [
{ "type": "string"},
{ "type": "number", "minimum": 0 }
]
}
在这种情况下,我们的数据可以是字符串,也可以是大于或等于 0 的数字。
示例 2 — **oneOf** —为了对oneOf进行验证,给定的数据必须对其中一个给定的子模式有效。
{
"oneOf": [
{ "type": "number", "multipleOf": 5 },
{ "type": "number", "multipleOf": 3 }
]
}
在这种情况下,数据只能是数字,可以是 5 的倍数,也可以是 3 的倍数,但不能两者都是!
摘要
JSON 模式是一个强大的工具。它使您能够验证您的 JSON 结构,并确保它满足所需的 API。您可以根据需要创建任意复杂和嵌套的模式,您所需要的只是需求。您可以将它作为附加测试或在运行时添加到您的代码中。
在这篇文章中,我介绍了基本结构,并提到了一些更复杂的选项。有很多可以探索和利用的东西,你可以阅读。
我认为任何将 JSONs 作为工作一部分的人都应该熟悉这个包及其选项。仅仅通过简单地验证您的 JSON 结构,它就有可能节省您大量的时间并简化您的集成过程。我知道自从我开始使用它以来,它节省了我很多时间。
DVC 的数据版本控制:了解其他数据科学家忽略了什么
Python 中 DVC 数据版本控制的完整教程

菲奥娜·阿特摄于 Pexels
数据科学中的大问题
当数据集很大时,它会造成更大的混乱。为什么?数据科学家和 ML 工程师在大规模数据集和模型上进行许多实验,它们的庞大规模给协作和软件工程最佳实践带来了巨大的麻烦。
传统上,软件工程师通过制作中央代码库的副本并通过拉请求提出修改建议来进行协作。然后,请求被审查、测试,如果被批准,就合并到主代码库中。这个过程可能在一天内发生多次。
像 Git 这样的工具已经成熟了近二十年,使得上述过程对程序员来说轻而易举。但是,Git 只是为轻量级代码脚本设计的,而不是我们用来训练昂贵的 CNN 的成千上万的图像。
是的,有像 GitLFS 这样的替代品,但是设置起来太麻烦了;它不允许对大文件进行安全的分支、提交和实验,而这些是必备的特性。
因此,现在有许多工具可以解决这些问题。其中之一是 DVC(数据版本控制)。
什么是数据版本控制和 DVC?
数据版本控制是对数据集和模型更改进行跟踪和版本控制。一个好的数据版本控制系统必须具备以下特征:
- 像 Git 处理脚本一样跟踪数据/模型变化。
- 易于安装和使用:您应该能够用一个命令安装它。
- 与 Git 等现有系统的兼容性,所以它不应该重新发明轮子。
- 支持分支和提交:必须支持单独创建分支、提交和试验。
- 再现性:允许其他团队成员快速、轻松地再现 ML 实验。
- 共享功能:与其他用户无缝共享数据和模型以进行协作。
具有上述所有特性的一个工具是 DVC,它模仿 Git 的大文件特性。
Git 在 GitHub 或 GitLab 等托管服务上存储代码库,而 DVC 使用远程存储来上传数据和模型。远程存储可以是任何云提供商,如 AWS、GCP、Azure,甚至是本地机器上的一个普通目录。一个遥控器将是整个项目的唯一真实来源,由所有团队成员使用,就像 GitHub 存储库一样。
当 DVC 跟踪一个文件时,它会将它添加到远程存储中。然后,创建一个轻量级的.dvc文件(点 DVC ),作为原始大文件的占位符。它将包含 DVC 如何从遥控器下载文件的说明。
你会在教程中学到什么?
通过完成本教程,您将拥有一个用于图像分类项目的 GitHub 存储库。其他人只需两个命令就可以获得您的所有代码、数据、模型和实验:
这篇文章将教你运行dvc pull命令所需的一切,并理解几乎所有的东西。让我们直接跳进来吧!
设置项目和环境
让我们从创建一个conda环境开始:
接下来,转到您的 GitHub 帐户,并分叉这个库。这将在您的帐户下创建确切的回购版本。然后,在终端上克隆它,并更改到工作目录。
$ git clone https://github.com/YourUsername/dvc-tutorial.git
$ cd dvc-tutorial
现在,让我们创建带有一些依赖项的requirements.txt文件并安装它们。
如果你还没有安装支持 GPU 的 TensorFlow,我在这里为你准备了一个指南。
运行带有
-e标签的echo命令可以让它检测特殊字符,比如换行符(\n)。
我们安装了几个标准数据库:scikit-image用于图像处理,而tensorflow用于构建模型。最后一个是dvc,这是文章的主要重点。
现在,让我们构建项目的树形结构:
$ mkdir data notebooks src data/raw data/prepared data/prepared/train
我们将把脚本存储在src中,而data和notebooks将保存我们以后可能创建的图像和分析笔记本。
下载并设置数据
现在,我们将为项目下载数据集。GTSRB 德国交通标志识别基准数据集包含 50k 多幅图像,分为 40 个道路标志类别。我们的任务是建立一个卷积神经网络,可以准确地对每个类别进行分类。
$ curl "the_link_inside_quotes" -o data/traffic_signs.zip
下载完成后,将图像解压到data/raw目录。然后,我们可以删除不必要的文件和目录,比如图像和元数据的副本。这将只给我们留下data/raw中的train和test文件夹。
最后,我们也删除了原始的 zip 文件。
train文件夹有 43 个文件夹,每个类一个。请记住这个目录结构,因为我们将在训练模型时使用它。
正在初始化 DVC
本节将展示 Git 和 DVC 如何协同工作的基础知识。
要将 DVC 跟踪添加到您的项目中,我们需要调用dvc init。DVC 只在 Git 库上工作,所以如果您将它用于其他项目,请确保您已经运行了git init命令。我们已经从 GitHub 派生了 repo,所以它已经初始化了 Git。
dvc init命令将添加一个保存 DVC 配置的特殊的.dvc目录。我们将在后面的章节中仔细研究 DVC 内部。
$ git status -s
A .dvc/.gitignore
A .dvc/config
A .dvcignore
该命令创建了.dvcignore文件,可以用来列出 DVC 应该忽略的目录。Git 存储库已经预先填充了.gitignore文件。
一旦 DVC 被初始化,它需要一个叫做远程存储的地方来上传数据和大文件,这样 Git 就不会跟踪它们。DVC 远程可以是任何云存储提供商,如 AWS、Azure、GCP,或者只是你机器上的任何其他目录。
为了简单起见,我们将这个项目的远程存储设置到主目录中一个名为dvc_remote的新目录。
$ mkdir ~/dvc_remote
$ dvc remote add -d remote ~/dvc_remote
remote命令用于控制远程存储。在这里,我们将我们的远程存储简单地命名为remote。标签告诉 DVCdvc_remote是你默认的远程存储路径。
运行这些命令后,您可以查看.dvc文件夹中的config文件:
如您所见,远程名称被列为remote,而url被设置为我的主目录中的一个路径。如果我们的遥控器是基于云的,它将是一个网址。
使用 DVC 添加要跟踪的文件
要开始用 DVC 跟踪文件和目录的变化,您可以使用dvc add命令。下面,我们将整个data文件夹添加到 DVC 中,因为它包含了成千上万的图片,如果添加到git中,无疑会导致崩溃:
$ dvc add data
当运行add命令时,下面是发生的情况:
- 目录被置于 DVC 的控制之下。
data目录被添加到.gitignore文件中,因此它永远不会被git跟踪。- 创建一个轻量级的
data.dvc文件,作为原始data目录的占位符。
这些轻量级的.dvc(点 DVC)文件被 Git 持续跟踪。当用户克隆我们的 Git 存储库时,.dvc文件将包含关于原始大文件存储位置的指令。
请记住,在
.gitignore文件中的新行上添加文件或文件夹会使它们对git命令不可见。
现在,由于data目录被添加到了.gitignore中,我们可以安全地用git存放所有其他文件并提交它们:
$ git add --all
$ git commit -m "Initialize DVC and add the raw images to DVC"
所以,下面是如何结合使用 Git 和 DVC 的总结:
- 每当您对代码或其他轻量级文件进行更改时,使用
git add filename或git add --all跟踪这些更改。 - 每当用
dvc跟踪的大文件有变化时,通过运行dvc add file/or/dir来跟踪它,这会更新相应的.dvc文件。因此,您用git add filename.dvc将.dvc文件中的更改添加到git中。
例如,运行python src/preprocess.py将调整raw/train中所有图像的大小和比例,并将它们保存到data/prepared/train:
你可以从这里复制/粘贴以上脚本的完整版本。
resize函数获取一个图像路径,并使用imread函数作为 NumPy 数组读取它。它被调整到target_size并保存到prepared目录中的新路径。
在__main__上下文中,我们收集所有图像路径,并使用并行执行来同时调整和保存多个图像。
一旦脚本完成,您可以使用dvc status查看 DVC 跟踪的文件是否有变化。您应该会看到类似下面的输出:
因此,我们用dvc add跟踪新的变更,用git add --all暂存对data.dvc所做的变更,并提交变更。
$ dvc add data
$ git add --all
$ git commit -m "Save resized images"
上传文件
现在,让我们推送所有使用git和 DVC 跟踪的变更进行的提交。我们运行git push,然后是dvc推送。
git push会将代码和.dvc文件上传到 GitHub,而dvc push会将原始的和调整后的图像发送到remote,也就是你机器上的~/dvc_remote目录。
$ git push
$ dvc push
一旦大文件存储在遥控器中,您就可以删除它们:
$ rm -rf data/raw/train
如果你想重新下载这些文件,你可以调用dvc pull:
$ dvc pull
dvc pull将检测工作目录和远程存储器之间的任何差异并下载它们。
当一个新用户克隆您的 Git 存储库时,他们也将使用dvc pull命令用存储在您的遥控器中的文件填充工作目录。
构建图像分类模型
是时候建立一个基线模型并和 DVC 一起追踪它了。在src/train.py中,我们有下面的脚本,它使用ImageDataGenerator类训练一个基线 CNN。由于本文的重点不是 TensorFlow,你可以从文档中了解[ImageDataGenerator](https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/image/ImageDataGenerator)如何工作。
你可以在的资源库找到完整的脚本。
脚本的关键部分是main函数。在内部,我们使用joblib.dump在新创建的models和metrics目录中拟合和保存模型及其度量。
我们运行脚本:
$ python src/train.py
完成后,我们将models目录添加到 DVC:
$ dvc add models
$ git add --all
$ git commit -m "Baseline model with 0.2192 accuracy"
然后,我们再次运行git add --all来暂存models.dvc文件和metrics.dvc文件。用git标记每个实验也是一个很好的做法:
$ git tag -a baseline -m "Baseline model with 0.2192 accuracy"
最后,我们推送提交,DVC 更改,并用以下内容进行标记:
$ dvc push
$ git push
$ git push origin --tags
现在,如果我们想通过尝试不同的 CNN 架构来提高准确率,我们修改train.py脚本,运行它,并跟踪新的model.joblib和history.joblib文件。我们还创建了总结模型性能的提交和标记。最后,我们用 Git 和 DVC 推送更改和标记。
尽管这个实验工作流简单而有效,但在文章的下一部分,我们将看到一种更好的方法来跟踪我们的实验。使用 DVC 管道和 VSCode DVC 扩展,我们将能够在 ide 中可视化我们的度量和模型运行。
DVC 内部
现在你知道如何跟踪和上传文件到 DVC 远程,是时候深入了解 DVC 内部。
我们已经讨论过 DVC 远程,它类似于 GitHub,在那里你可以存储你用dvc push上传的数据和模型的最新官方版本。
但是,就像 Git 在将文件提交到 GitHub 之前先将文件添加到暂存区一样,DVC 有一个名为 cache 的暂存区。
当调用dvc init时,cache目录被添加到.dvc文件夹中。每次调用dvc add,文件都会被复制到缓存中。
现在,您会问——这难道不会重复文件和浪费空间吗?是啊!但是就像您可以配置远程存储的位置一样,您也可以配置缓存。
在大型项目中,许多专业人员共用一台功能强大的机器,而不是笔记本电脑或个人电脑。因此,让每个团队成员在自己的工作目录中都有一个缓存是没有意义的。一种解决方案是将缓存指向共享位置。
如果您一直在关注,我们的项目缓存在.dvc/cache下。但是我们可以使用以下命令指向另一个目录:
$ dvc cache dir path/to/shared_cache
$ mv .dvc/cache/* path/to/shared_cache
mv命令将旧缓存中的文件移动到新的缓存位置。
共享一台开发机器时,确保所有团队成员都有读/写权限path/to/shared_cache。
如果是自己一个人在工作,就没必要按照这一步来。
结论
以下是与 DVC 合作的总结:
- DVC 项目是在 Git repo 之上用
dvc init初始化的 - 您应该使用
dvc remote add -d remote_name path/to/remote为项目设置一个遥控器 - 要开始跟踪文件,请使用
dvc add dvc add将指定的目录或文件复制到.dvc/cache或shared_cache/you/specified,为每个被跟踪的文件夹或文件创建.dvc文件并添加到.gitignore.dvc等文件用git add --all跟踪- 要推送提交和 DVC 跟踪的文件更改,请同时使用
git push和dvc push dvc push将文件从缓存上传到远程存储器- 用标签标记每个 ML 实验运行,并对每个更改的文件重复
dvc add/dvc push和git add/git push。
这个循序渐进的教程已经足以解决您在数据科学项目中关于协作和可再现性的大多数问题。在文章的下一部分,我们将讨论用 DVC 简化机器学习实验(是的,它可以变得更简单)!
感谢您的阅读!
文章的第二部分:
第三部分:
https://pub.towardsai.net/how-to-create-highly-organized-ml-projects-anyone-can-reproduce-with-dvc-pipelines-fc3ac7867d16 https://ibexorigin.medium.com/membership https://ibexorigin.medium.com/subscribe
数据集的引用:
J.Stallkamp、M. Schlipsing、J. Salmen 和 C. Igel。德国交通标志识别基准:多类别分类竞赛。在IEEE 神经网络国际联合会议记录中,第 1453-1460 页。2011.
以下是我的一些故事:
💔-step-feature-selection-guide-in-sklearn-to-superchage-your-models-e994aa50c6d2>
如何用交互式有向无环图可视化因果推理模型
如何用一行 Python 代码生成交互式有向无环图来可视化和理解因果推理模型

Photo by 愚木混株 cdd20 on Unsplash
背景
因果推理是目前非常热门的话题,因果模型开始成为更传统的回归、分类和预测模型的非常有用的补充。
越来越多的客户希望能够可视化和理解模型预测背后的潜在原因和影响,以帮助回答“为什么?”,“如果呢?”以及“下一步是什么?”类型问题。
这个困境促使我编写了一个 Python 代码库,提供了 3 种可视化有向无环图(Dag)的可选方法,作为满足这些客户需求的解决方案的一部分。
你将学到什么
到本文结束时,您将能够用一行 Python 代码生成嵌入在 Jupyter 笔记本单元格中的完全交互式有向无环图,并且您将能够完全访问源代码和文档!

作者图片
在我们深入 DAG 观想之前,请考虑…
通过我的推荐链接加入 Medium(如果你使用此链接注册,我将收取一定比例的费用)。
访问我的数据科学网站— 数据博客。
…如果你对因果推理感兴趣,这是本系列的其他文章-
入门指南
我们需要的第一件事是一些测试数据,这些数据可以用来构建因果推理模型和演示 Dag。
我选取了一些意大利 1960 年到 2019 年之间二氧化碳排放的数据。数据来源于世界数据库,该数据库根据知识共享 4.0 国际许可公开提供数据集。

作者图片
数据以百分比表示增长,并以四分位数表示。例如,1960 年世界人口增长率在 0.606%和 1.994%之间。
特征说明(流行音乐、URB 音乐等。)如下-

作者图片
构建有向无环图
DAG 的结构可以通过使用几种程序性因果发现技术中的一种或者通过利用领域专家的专业知识来推断。
这个过程超出了本文的范围,所以现在我们假设意大利温室气体 DAG 的结构可以定义如下
在这个阶段,我们已经通过创建一个保存节点之间链接的BayesianNetwork定义了 DAG,但是很难想象节点是如何连接的以及因果链接是什么。
“愚蠢”的网络图
我的第一个想法是利用直接在pgmpy库中的BayesianNetwork类中实现的“愚蠢”的图表,因为它们快速且易于使用,其外观和感觉非常类似于 Judea Pearl & Dana Mackenzie 的《为什么之书》中的示例

作者图片
到目前为止,一切都很好,但是看看当一个更复杂的网络,比如意大利温室气体的例子,用 daft 渲染时会发生什么…

作者图片
daft 图表的默认布局是“圆形”。可以更改默认值,也可以更改节点位置,但是无论您如何修改参数,结果都不适用于复杂的网络。
下面的代码片段是作为对我的
dag_tools库的调用编写的。本文末尾给出了对该库以及所有源代码和文档的完整访问,因此您可以自己轻松地为任何因果推理模型创建 Dag。
网络 x 图
愚蠢的网络图的缺点导致我探索另一个图形库来呈现 DAG—【https://networkx.org/】()—

作者图片
对于温室气体数据,默认的“弹簧”布局将一些节点放置在彼此的顶部,使得阅读和解释非常困难。
这导致了我的dag_tools库的进一步改进,使节点位置能够被显式指定...

作者图片
指定节点位置意味着节点不会互相重叠,因此我们可以真正开始观想和理解因果。
然而,仍有改进的余地。
为了能够指定节点的位置,我们几乎需要在可视化发生之前理解节点是如何连接的。
这个缺点导致了我用自己的布局算法对dag_tools库的进一步扩展,该算法可以自动将节点放置在指定数量的列中...

作者图片
这是 DAG 的更好的可视化,不需要对节点之间的链接进行任何预先分析。
例如,在这种安排中,很容易看出 FFEC(化石燃料消耗)导致 CO2(二氧化碳排放)、CH4(甲烷排放)和 N2O(一氧化二氮排放)。
然而,这种方法有一个很大的缺点,它不是交互式的。
皮维斯图
我在网上看过各种与pyvis网络图相关的教程,这让我想知道我是否可以使用pyvis来使 Dag 能够被交互式地探索和定位。
这导致了将display_pyvis_model添加到我的dag_tools库中,这花了很长时间才做好,特别是因为我真的希望能够将网络图完全嵌入到 Jupyter 笔记本的单元输出中。
以下是最终结果…

作者图片
这提供了一种直观、简单的方式来理解节点之间的关系,只需拖动它们,直到箭头没有交叉或重叠。
但是仍然有一些问题-
pyvis很难控制,文档可能很复杂(使用dag_tools.display_pyvis_model可以解决这个问题)。- 初始布局不一致,不可控;每次运行代码时,节点位置都会不同。
- 能够保存节点的最终位置是非常好的,但是目前在
pyvis中没有办法做到这一点。
然而dag_tools.display_pyvis_model提供了一个可视化 Dag 的好方法,我已经在生产项目中成功地使用了很多次,客户真的很喜欢最终的结果。
源代码
如果你决定下载并使用代码,如果你能考虑给我买杯咖啡就太好了-
结论
构建因果模型的一个重要步骤是可视化和理解有向无环图,该图表示数据中的因果关系流。
本文展示了daft、networkx和pyvis的优缺点,并提供了dag_utils库,使得任何因果模型的开发人员只需要一行 Python 代码就可以创建一个完全交互式的 DAG。
如果你喜欢这篇文章,请考虑…
通过我的推荐链接加入 Medium(如果您使用此链接注册,我将收取一定比例的费用)。
https://grahamharrison-86487.medium.com/membership
访问我的数据科学网站— 数据博客。
作者图片
如何用直观的条件概率表可视化因果推理模型
如何在一行 Python 代码中生成直观和全面的条件概率表来可视化和理解因果推理模型

艾米丽·莫特在 Unsplash 上的照片
背景
因果推理目前是一个热门话题,但现有的各种库可能会因不一致的文档和示例而变得复杂,并且大多数可用的文章和帖子都专注于因果推理的特定方面,而没有涵盖数据科学家需要知道的所有事情。
这促使我撰写了一系列文章,其中最新的一篇深入探讨了“条件概率表”以及如何以直观且有意义的格式轻松生成它们。
你将学到什么
到本文结束时,您将能够只用一行 Python 代码就生成视觉上丰富的条件概率表,并且您将能够完全访问源代码和文档!
在我们深入 CPT 观想之前,请考虑…
通过我的推荐链接加入 Medium(如果你使用这个链接注册,我将收取一定比例的费用)。
每当我发表新故事时,订阅一封免费电子邮件。
访问我的数据科学网站— 数据博客。
…如果你对因果推理感兴趣,这是本系列的其他文章-
因果推理模型的快速复习
因果推理模型包括两个关键部分-
- 描述什么导致什么的“有向无环图”(DAG),有时称为因果推断图。
- 描述从一个节点移动到另一个节点的概率的一组“条件概率表”(CPT)。
下面的例子展示了因果推理模型中的两个部分是如何协同工作的(单元格中的数字是概率)。

作者图片
我的上一篇文章(https://towards data science . com/how-to-visualize-causal-inference-models-with-interactive-directed-acyclic-graphs-8dd 648 a 64915)讨论了 DAG 的可视化,本文的其余部分将展示如何以一种视觉丰富和直观的方式生成 CPT。
入门指南
让我们从选择一些数据来建模开始。
我选择的数据与拥有研究生学位对工资的影响有关,这些数据是从 https://archive.ics.uci.edu/ml/datasets/census+income 的 UCI 机器学习库获得的,该库可以免费使用,并附有说明(参见参考资料部分)。

作者图片
构建因果模型
我选择使用pgmpy库(https://pgmpy.org/)构建一个因果模型,如下所示...
将因果关系和结构形象化是非常有用的。要获得dag_tools.py的全部细节和源代码,请阅读我的文章“如何用交互式有向无环图可视化因果推理模型”。

作者图片
建立模型的最后一步是如下拟合数据
条件概率表
使用pgmpy库中的本地功能来快速浏览 CPT 是非常容易的...
+---------+-------------+
| age(17) | 0.0128214 |
+---------+-------------+
| age(18) | 0.0179637 |
+---------+-------------+
| age(19) | 0.0225917 |
+---------+-------------+
| age(20) | 0.0236202 |
+---------+-------------+
| age(21) | 0.0223174 |
+---------+-------------+
| age(22) | 0.0234145 |
+---------+-------------+
| age(23) | 0.0264313 |
+---------+-------------+
...
+---------+-------------+
| age(88) | 0.000102845 |
+---------+-------------+
| age(90) | 0.00126843 |
+---------+-------------+
+--------------------------+-----+---------+---------------------+
| age | ... | age(88) | age(90) |
+--------------------------+-----+---------+---------------------+
| hasGraduateDegree(False) | ... | 1.0 | 0.8918918918918919 |
+--------------------------+-----+---------+---------------------+
| hasGraduateDegree(True) | ... | 0.0 | 0.10810810810810811 |
+--------------------------+-----+---------+---------------------+
+-----------------------+-----+-------------------------+
| age | ... | age(90) |
+-----------------------+-----+-------------------------+
| hasGraduateDegree | ... | hasGraduateDegree(True) |
+-----------------------+-----+-------------------------+
| greaterThan50k(False) | ... | 0.25 |
+-----------------------+-----+-------------------------+
| greaterThan50k(True) | ... | 0.75 |
+-----------------------+-----+-------------------------+
…但是输出很不理想!
- 代表
age概率的 CPT 垂直展开(因为年龄有许多阶段,17 到 90 岁之间的每个年龄有一个阶段) hasGraduateDegree概率的 CPT 甚至更糟。因为这个表是水平展开的,pgmpy已经截断了年龄为 17 - 87 的所有列,只在显示中留下年龄为 88 和 90 的列。这可能适合单元格中的表格,但由此产生的截断使得无法理解正在发生的事情。greaterThan50k的 CPT 存在与hasGraduateDegree相同的问题。- CPT 的
pgmpy输出的最后一个问题是它们“上下颠倒”。如果你是朱迪亚·珀尔的读者,他已经出版了许多关于因果关系的开创性著作(包括“为什么之书”),你会读到一些例子,珀尔用“概率”表示列,用“给定”条件表示行...

作者图片
https://www.amazon.co.uk/Book-Why-Science-Cause-Effect/dp/0141982411
一个更好的解决方案…
所有这些问题使得很难想象因果模型中发生了什么,这导致缺乏理解,进而导致无法使用这些模型为客户解决现实世界中的问题。
所以pgmpy不直观的输出让我开发了自己的cpt_tools库来解决所有问题(下面提供了完整源代码的链接)。
让我们看看使用cpt_tools生成的输出...

作者图片
这在来自cpt_tools库的一行 Python 代码中看起来更好!
表格以 pandas 数据帧的形式返回,并针对 Y 轴(行)进行截断,以在可读性和空间利用率之间实现最佳折衷。
如果你想看到没有水平截断的整个 CPT,只需改变 pandas display.max_rows参数,然后使用cpt_tools.display_cpt如下-
源代码
完整的源代码可以在这里找到-
https://gist . github . com/grahamharrison 68/1187 c53d 078 C3 c 899 b 534852 Fe 8 EDF 9 c
…文档可在此处找到-
如果你决定下载并使用 cpt_tools 或 dag_tools 代码,如果你能考虑给我买杯咖啡就太好了
作者图片
最后,如果你想把笔记本中的所有代码和数据放在一个地方,请点击这个链接…
https://Github . com/grahamharrison 68/Public-Github/tree/master/Causal % 20 推论
结论
因果推理是数据科学工具包中的一个很好的工具,但是要使用因果推理来解决业务问题,您需要能够可视化有向无环图和条件概率表。
pgmpy库全面且易于使用,但是可视化模型的功能可以从扩展和改进中受益。
本文展示了如何在一行 Python 代码中以视觉上强大、直观且易于理解的方式可视化条件概率表。
如果你喜欢这篇文章,请考虑…
通过我的推荐链接加入 Medium(如果你使用此链接注册,我将收取一定比例的费用)。
https://grahamharrison-86487.medium.com/membership
每当我发表新故事时,订阅一封免费电子邮件。
访问我的数据科学网站— 数据博客。
作者图片
参考
Dua d .和 Graff c .(2019 年)。http://archive.ics.uci.edu/ml 的 UCI 机器学习库。加州欧文:加州大学信息与计算机科学学院。
如何使用 R 可视化足球数据
原文:https://towardsdatascience.com/how-to-visualize-football-data-using-r-ee963b3a0ba4
关于创建快照、过程和热点图的教程

介绍
近年来,足球分析发展迅速。有了数据,我们可以从不同的角度去理解游戏。
在本文中,我将向您展示如何使用 r 可视化足球数据。在本文结束时,您将能够创建如下可视化:



所有图片均由作者创作。
事不宜迟,让我们开始吧!
履行
数据源
我们将使用来自 StatsBomb 的 开放数据 ,我已经得到许可使用这些数据作为例子。StatsBomb 是一家足球分析公司,为足球俱乐部提供赛事数据和分析服务。
由于事件数据不同于常规的表格数据,StatsBomb 提供免费数据来帮助我们了解如何使用数据分析足球。
我们可以从公开数据中选择的联赛有欧冠、世界杯、印超等等。
对于这篇文章,我们将基于 2012/13 赛季拜仁慕尼黑和多特蒙德之间的冠军联赛决赛来创建可视化效果。
StatsBomb 数据格式类似于 JSON 文件,因此使用它来分析数据将是一个挑战。但值得庆幸的是,我们可以通过使用一个名为 StatsBombR 的函数轻松检索数据。
安装并加载库
我们需要几个库来帮助我们创建可视化。这些库是:
- StatsBombR = >检索 StatsBomb 数据
- Tiduverse = >一个库,它编译用于预处理和可视化数据的库
- Ggsoccer = >一个在可视化界面上生成足球场的库
让我们安装库。下面是实现这一点的代码:
**# Install the necessary libraries**
install.packages('devtools')
devtools::install_github("statsbomb/SDMTools")
devtools::install_github("statsbomb/StatsBombR")
install.packages('tidyverse')
install.packages('ggsoccer')
之后,您可以通过运行以下代码行来加载库:
**# Load the libraries**
library(tidyverse)
library(StatsBombR)
library(ggsoccer)
现在我们准备好动手了。
预处理数据
由于 StatsBomb 提供了不同联赛的公开数据,我们需要指定比赛 ID 和要分析的赛季。
首先,我们需要查看 StatsBomb 提供的所有比赛。下面是实现这一点的代码:
**# Retrieve all available competitions**
Comp <- FreeCompetitions()
从这段代码中,你会看到一个包含所有联赛的数据框。
正如我之前提到的,我们将分析欧冠决赛拜仁和多特蒙德的比赛。对应的比赛 ID 和赛季分别是 16 和 2012/2013。
让我们通过运行下面的代码来过滤数据:
**# Filter the competition**
ucl_german <- Comp %>%
filter(competition_id==16 & season_name=="2012/2013")
接下来,我们检索对应于该联赛和赛季的所有比赛。下面是实现这一点的代码:
**# Retrieve all available matches**
matches <- FreeMatches(ucl_german)
获得所有匹配后,我们可以通过运行这行代码来检索事件数据:
**# Retrieve the event data**
events_df <- get.matchFree(matches)
最后,我们清理数据。下面是实现这一点的代码:
**# Preprocess the data**
clean_df <- allclean(events_df)
现在我们有了数据。让我们创建可视化!
通行证地图
第一个是通行地图。传球地图显示了一个球员或一个球队创造的所有传球。在本例中,我们将创建 Thomas Muller 的通行证地图。
让我们通过穆勒的所有传球来过滤数据。下面是实现这一点的代码:
**# Passing Map**
muller_pass <- clean_df %>%
filter(player.name == 'Thomas Müller') %>%
filter(type.name == 'Pass')
现在有趣的部分来了。为了创建 viz,我们将使用一个名为 ggplot 和 ggsoccer 的库。下面是创建基本 viz 的代码:
ggplot(muller_pass) +
annotate_pitch(dimensions = pitch_statsbomb) +
geom_segment(aes(x=location.x, y=location.y, xend=pass.end_location.x, yend=pass.end_location.y),
colour = "coral",
arrow = arrow(length = unit(0.15, "cm"),
type = "closed")) +
labs(title="Thomas Muller's Passing Map",
subtitle="UEFA Champions League Final 12/13",
caption="Data Source: StatsBomb")
如果你是 R 用户,相信你对 ggplot 已经很熟悉了。
ggsoccer 扩展了 ggplot 库,因此我们可以在包含开始和结束坐标的事件数据上构建可视化。
这个库提供了 annotate_pitch 来创建足球场,提供了 geom_segment 来创建传球路线,这可以从上面的代码中看到。剩下的就是你在 ggplot 上看到的那个了。
这是结果:

这个形象是作者创造的。
嗯,好像视觉上不美观。让我们添加主题功能来定制 viz 的外观。下面是完整的代码,以及主题函数:
ggplot(muller_pass) +
annotate_pitch(dimensions = pitch_statsbomb, fill='#021e3f', colour='#DDDDDD') +
geom_segment(aes(x=location.x, y=location.y, xend=pass.end_location.x, yend=pass.end_location.y),
colour = "coral",
arrow = arrow(length = unit(0.15, "cm"),
type = "closed")) +
labs(title="Thomas Muller's Passing Map",
subtitle="UEFA Champions League Final 12/13",
caption="Data Source: StatsBomb") +
theme(
plot.background = element_rect(fill='#021e3f', color='#021e3f'),
panel.background = element_rect(fill='#021e3f', color='#021e3f'),
plot.title = element_text(hjust=0.5, vjust=0, size=14),
plot.subtitle = element_text(hjust=0.5, vjust=0, size=8),
plot.caption = element_text(hjust=0.5),
text = element_text(family="Geneva", color='white'),
panel.grid = element_blank(),
axis.title = element_blank(),
axis.text = element_blank()
)
这是结果:

这个形象是作者创造的。
现在看起来棒极了!
镜头地图
在这一部分,我们将为两个团队创建拍摄地图。但是我们会在不同的坐标上绘制每个俱乐部产生的镜头。
因此,我们将创建拜仁和多特蒙德射门数据的两个数据框。让我们运行这几行代码:
dortmund_shot <- clean_df %>%
filter(type.name == 'Shot') %>%
filter(team.name == 'Borussia Dortmund') %>%
select(player.name, location.x, location.y, shot.end_location.x, shot.end_location.y, shot.statsbomb_xg)bayern_shot <- clean_df %>%
filter(type.name == 'Shot') %>%
filter(team.name == 'Bayern Munich') %>%
select(player.name, location.x, location.y, shot.end_location.x, shot.end_location.y, shot.statsbomb_xg)
创建射击贴图也类似于创建过程贴图。我们用 geom_point 函数改变 geom_segment 函数。如果你知道,那就是绘制散点图的函数。
我们将该函数应用于每个数据帧。而对于多特蒙德的投篮数据,我们通过将数值减去 120 来反映 x 坐标。
看看这段代码:
ggplot() +
annotate_pitch(dimensions = pitch_statsbomb, colour='white', fill='#021e3f') +
geom_point(data=dortmund_shot, aes(x=location.x, y=location.y, size=shot.statsbomb_xg), color="red") +
geom_point(data=bayern_shot, aes(x=120-location.x, y=location.y, size=shot.statsbomb_xg), color="yellow") +
labs(
title="Borussia Dortmund vs Bayern Munich",
subtitle = "Shots Map | UEFA Champions League Final 2012/2013",
caption="Data Source: StatsBomb"
) +
theme(
plot.background = element_rect(fill='#021e3f', color='#021e3f'),
panel.background = element_rect(fill='#021e3f', color='#021e3f'),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
text = element_text(family="Geneva", color='white'),
plot.title = element_text(hjust=0.5, vjust=0, size=14),
plot.subtitle = element_text(hjust=0.5, vjust=0, size=8),
plot.caption = element_text(hjust=0.5),
plot.margin = margin(2, 2, 2, 2),
legend.position = "none"
)
让我们看看代码的结果:

这个形象是作者创造的。
压力热图
最后,我们将创建拜仁慕尼黑进行的压力热图。让我们使用下面几行代码来过滤数据:
# Pressure Heat Map
bayern_pressure <- clean_df %>%
filter(team.name == 'Bayern Munich') %>%
filter(type.name == 'Pressure')
为了生成 viz,它与前一个类似。请看看这段代码:
ggplot(bayern_pressure) +
annotate_pitch(dimensions = pitch_statsbomb, fill='#021e3f', colour='#DDDDDD') +
geom_density2d_filled(aes(location.x, location.y, fill=..level..), alpha=0.4, contour_var='ndensity') +
scale_x_continuous(c(0, 120)) +
scale_y_continuous(c(0, 80)) +
labs(title="Bayern Munich's Pressure Heat Map",
subtitle="UEFA Champions League Final 12/13",
caption="Data Source: StatsBomb") +
theme_minimal() +
theme(
plot.background = element_rect(fill='#021e3f', color='#021e3f'),
panel.background = element_rect(fill='#021e3f', color='#021e3f'),
plot.title = element_text(hjust=0.5, vjust=0, size=14),
plot.subtitle = element_text(hjust=0.5, vjust=0, size=8),
plot.caption = element_text(hjust=0.5),
text = element_text(family="Geneva", color='white'),
panel.grid = element_blank(),
axis.title = element_blank(),
axis.text = element_blank(),
legend.position = "none"
)
我们使用 geom_density2d_filled 更改 geom_point 函数以生成热图。此外,我们添加了 scale 函数来指定热图范围。
下面是代码的结果:

这个形象是作者创造的。
结束语
干得好!你已经学习了如何使用 r 可视化足球数据。我们已经创建了传球,射门和压力热图。
我希望你能在这里学到很多东西。此外,请将这些知识应用到您最喜欢的团队中。
如果你对这篇文章感兴趣,请查看我的 中 简介,获取更多足球分析学和数据科学相关教程。
谢谢你看我的文章!
如何以一种全面的方式可视化每月开支:用 R 开发一个 Sankey 图
绘制桑基图,对你的财务状况有更深入的了解

如果你使用像 Mint/Personal Capital/Clarity 这样的个人预算/财务工具,可视化每月现金流并不新鲜。所有这些工具主要提供三种类型的图表:饼图、条形图和折线图。然而,你有没有想过图表是否足够好,可以让你更好地了解你的月收入和支出?有什么方法可以全面的可视化每月的开销?在这篇文章中,我将与你分享如何在 R 中创建一个桑基图,以帮助你更好地了解你的财务状况。
什么是桑基图
来自维基百科:桑基图是流程图,其中箭头的宽度与流速成正比。著名的桑基图之一是拿破仑对俄罗斯的入侵。下图清楚地显示了部队离开的时间和数量。

密纳德关于拿破仑入侵俄罗斯的经典图表——维基
回到个人财务每月现金流,采用桑基图来展示现金流以及资金来自或流向哪个账户是一个完美的用例。
Mint 应用程序中的饼状图和条形图不会显示现金流如何,而是显示一个类别中花费/赚得的钱数,这是通过现金流深度挖掘个人财务的限制之一。
这是 Reddit 上的一张漂亮的桑基图。我们在本文中的目标是从 Mint 这样的个人金融应用程序中使用数据转储重新创建一个类似的应用程序。

桑基图— 来自 Reddit 的收入
先决条件
虽然它要求您安装 R 来获得更好的定制结果,但是您可以自己添加很少的代码,并且不要求您对 R 有很深的理解。你可以复制粘贴这篇文章中的代码。
如何从 Mint 下载您的每月交易
我们将以 Mint.com的为例下载交易。从 web 应用来说很直接,手机 APP 没有这个选项。登录 Mint 后,转到事务,滚动到按钮,您应该会看到一个选项“导出所有 xxx 事务”当您点击该选项时,将会下载一个 CSV 文件。
下载的 CSV 文件包含以下字段:日期、描述、原始描述、金额、交易类型、类别、帐户名、标签和注释。我们将使用金额、交易类型、类别和账户名称来构建桑基图。
在 R 和 ggplot2 中创建 Sankey 图
我们将用来构建桑基图的一个库是gg 冲积。设计和功能最初受到了冲积包装的启发。gg 冲积的一个优点是它建立在 ggplot2 之上,你可以从图形的语法中获益
我也写过一篇关于为什么 ggplot2 对数据可视化这么好的文章?
下面是从 mint 下载的 CSV 中提取数据、执行一些基本转换并使用 ggplot 可视化为 Sankey 图的代码。
library(ggplot2)
library(dplyr)
library(ggthemes)
library(ggalluvial)
df = read.csv("~/Downloads/transactions.csv")
df <- df %>%
select(Date, Amount, Category, Transaction.Type, Account.Name) %>%
mutate(Date=as.Date(Date, format="%m/%d/%Y")) %>%
filter(Date > as.Date('12/01/2022', format="%m/%d/%Y")) %>%
group_by(Category, Transaction.Type, Account.Name) %>%
summarise(Expense = sum(Amount)) %>%
filter(Expense > 100, Transaction.Type == "debit", !Category %in% c("Transfer", "Paycheck", "Credit Card Payment", "Mortgage & Rent", "Investments"))
ggplot(df, aes(axis1 = Transaction.Type, axis2 = Account.Name, axis3 = Category, y = Expense)) +
scale_x_discrete(limits = c("Transaction.Type", "Account.Name", "Category"), expand = c(.2, .05)) +
geom_alluvium() +
geom_stratum() +
geom_text(stat = "stratum", aes(label = after_stat(stratum))) +
theme_economist() +
scale_colour_economist()
上面的代码可以分为三类:
- 导入库并读取下载的 CSV 文件
- 转换:我们只选择需要的字段;已将时间戳从字符串转换为日期;按交易类型、帐户名称和类别对美元金额组求和。根据条件,该小组将帮助建立桑基图阶段。
- 构建数据可视化:我们所要做的就是将分组条件放入轴中,然后在
scale_x_discrete中命名我们希望看到流程的字段,然后调用ggalluvial开始构建图表。
现在我们可以将图表形象化如下,它清楚地显示了每个类别的现金流来自哪个账户以及每个类别是如何分配的。

桑基图薄荷由 R |图片由作者
无需编码即可创建桑基图
还有一个叫做 sankeymatic.com的网站,它提供了一个不用编码就能画出漂亮桑基图的选项。它要求用户以某种方式格式化输入,然后你应该得到和上面 Reddit 帖子一样的结果。

桑基图|图片来自https://sankeymatic.com/
最后的想法
个人财务应用程序提供了快速、简单的数据可视化,但它仅限于高级的综合用途,如现金流分析。我希望这篇文章能对你的个人理财分析有所补充。请在下面留下评论,让我知道你对桑基图和使用 R 构建这样一个优秀图表的看法。
希望这个故事对你有帮助。本文是我的工程&数据科学系列的部分,目前包括以下内容:

数据工程和数据科学故事
View list47 stories


你也可以 订阅我的新文章 或者成为 推荐媒介会员 可以无限制访问媒介上的所有故事。
如果有问题/评论,请不要犹豫,写下这个故事的评论或通过 Linkedin 或 Twitter 直接联系我。
如何用 Python 可视化神经网络架构
原文:https://towardsdatascience.com/how-to-visualize-neural-network-architectures-in-python-567cd2aa6d62
使用 Jupyter 或 Google Colab 创建神经网络图示的快速指南

Image Credit —由作者使用 visualkeras 和 Jupyter Notebook 开发。
1.介绍
通常,在使用人工神经网络或其他变体(如卷积神经网络或递归神经网络)时,我们希望可视化并创建编译模型的图形表示。这可以解决两个目的:
- 而定义和训练多个模型允许我们可视化我们的模型的深度,并比较不同的层以及它们是如何顺序放置的。
- 允许更好地理解模型结构、每层中使用的激活函数、每层的形状(神经元的数量)以及需要训练的参数
python 中有几个现成的包可以创建我们的神经网络模型的可视化表示。前三个包甚至可以在模型训练之前使用(模型只需要定义和编译);然而,Tensor Boards 要求用户在可视化架构之前,根据准确的数据训练模型。
2.安装快速指南
pip install visualkeraspip install ann_visualizerpip install graphviz
我们不需要单独安装“张量板”和“Keras 模型图”。这将随 Tensorflow & Keras 的初始安装一起提供。
3.设置 Tensorflow 包
我们可能只使用下面列出的几个库。 大多数库可以将 TensorFlow 模型转换为图表,而无需对数据进行显式训练。你可以认为这是真理的单一来源。一些库,如 Pandas、Skimage 和 OpenCV,在读取结构化数据或图像时会派上用场。
# Import necessary librariesimport pandas as pdimport numpy as np # Import numpyfrom skimage import data, io # Import skimage library (data - Test images and example data.# io - Reading, saving, and displaying images.)from skimage.color import rgb2grayimport matplotlib.pyplot as plt # Import matplotlib.pyplot (Plotting framework in Python.)%matplotlib inlineimport os # This module provides a portable way of using operating system dependent functionality.os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'import warnings# Suppress warningswarnings.filterwarnings('ignore')from IPython.display import displayimport cv2 as cv
from sklearn.metrics import confusion_matrixfrom sklearn.model_selection import train_test_splitfrom tensorflow.keras import utilsfrom tensorflow.keras.models import Sequential,load_modelfrom tensorflow.keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPool2D, BatchNormalizationfrom tensorflow.keras.optimizers import Adamimport tensorflow as tfimport seaborn as sns
一些有助于从头开始探索 ANNs & CNNs 更多内容的链接。
4.建立卷积神经网络模型
我们将用不同的超参数定义三个不同的 CNN 模型。理想情况下,在现实世界中,考虑到我们正在为多类别分类任务构建模型,我们的目标是实现不同的架构,以最大化准确性或任何相关指标。我们对问题类型的选择不会对如何使用可视化包产生任何影响。
我们创建了用户定义的函数,用不同数量的 CNN 层、最大池和密集层分别构建三个不同的模型。
4.1 -架构 1 -浅层 CNN 层+ ANN 层
def construct_model():model = Sequential()model.add(Conv2D(filters=64, kernel_size=(3, 3), input_shape=(128, 128, 1), activation=’relu’))model.add(Conv2D(filters=64, kernel_size=(3, 3), activation=’relu’))model.add(MaxPool2D((2, 2)))model.add(Flatten())model.add(Dense(256, activation=’relu’))model.add(Dense(12, activation=’softmax’))model.compile(loss=’categorical_crossentropy’, optimizer=’adam’, metrics=[‘accuracy’])return model
4.2 —架构 2 —深层 CNN +浅层 ANN 层
def sconstruct_model():smodel = Sequential()smodel.add(Conv2D(filters=64, kernel_size=(3, 3), input_shape=(128, 128, 3), activation=’relu’))smodel.add(Conv2D(filters=64, kernel_size=(3, 3), activation=’relu’))smodel.add(MaxPool2D((2, 2)))smodel.add(Conv2D(filters=128, kernel_size=(3, 3), activation=’relu’))smodel.add(Conv2D(filters=128, kernel_size=(3, 3), activation=’relu’))smodel.add(MaxPool2D((2, 2)))smodel.add(Conv2D(filters=128, kernel_size=(3, 3), activation=’relu’))smodel.add(Conv2D(filters=128, kernel_size=(3, 3), activation=’relu’))smodel.add(MaxPool2D((2, 2)))smodel.add(Flatten())smodel.add(Dense(256, activation=’relu’))smodel.add(Dense(12, activation=’softmax’))#optimizer = Adam(lr=0.001)smodel.compile(loss=’categorical_crossentropy’, optimizer=’adam’, metrics=[‘accuracy’])#model.summary()return smodel
4.3 —架构 3 —深层 CNN 和 ANN 层
def cconstruct_model(learningRate):smodel = Sequential()smodel.add(Conv2D(filters=32, kernel_size=(3, 3), input_shape=(128, 128, 1), activation=’relu’))smodel.add(Conv2D(filters=32, kernel_size=(3, 3), activation=’relu’))smodel.add(MaxPool2D((2, 2)))smodel.add(Conv2D(filters=64, kernel_size=(3, 3), activation=’relu’))smodel.add(Conv2D(filters=64, kernel_size=(3, 3), activation=’relu’))smodel.add(MaxPool2D((2, 2)))smodel.add(Conv2D(filters=128, kernel_size=(3, 3), activation=’relu’))smodel.add(Conv2D(filters=128, kernel_size=(3, 3), activation=’relu’))smodel.add(MaxPool2D((2, 2)))smodel.add(Flatten())smodel.add(Dense(256, activation=’relu’))smodel.add(Dense(256, activation=’relu’))smodel.add(Dense(12, activation=’softmax’))optimizer = Adam(lr=learningRate)smodel.compile(loss=’categorical_crossentropy’, optimizer=optimizer, metrics=[‘accuracy’])smodel.summary()return smodelmodel=cconstruct_model(0.001)
5.使用人工神经网络可视化工具进行可视化
一个名为 ANN Visualizer 的 Python 模块使得用几行代码可视化人工神经网络成为可能(Gheorghiu,2022)。它使用 Keras 和 Python 的 Graphviz 模块来生成一个整洁而有吸引力的神经网络图。你可以灵活地可视化整个深度学习网络,或者只是你在深度学习进展的驱动下创建的卷积神经网络(Shah,2018)。
使用下面的结构生成神经网络的 pdf 表示。但是,在此之前,我们需要对上述模型进行编译。
model=construct_model()
ann_viz(model,view=True,filename="network.gv ",title="MyNeural Network")
- 模型— 时序模型来自 Keras
- 视图— 调用 ann_viz()后,可视化图形
- 文件名— 文件的名称
- 标题— 表示图形的任何特定标题
from ann_visualizer.visualize import ann_vizann_viz(model, view=True, filename=”cconstruct_model”, title=”CNN — Model 1 — Simple Architecture”)

图 1:使用 ANN Visualizer 创建的 construct_model()的快照。作者使用 Jupyter 笔记本开发的图片。原始输出太大,不适合作为图像放在这里,因此这里使用了底层的快照。
6.使用 Visual Keras 的可视化
一个名为 Visualkeras 的 Python 工具可以更容易地查看 keras 神经网络设计(单独或作为 TensorFlow 的一部分)。大多数造型需求都可以轻松满足。卷积神经网络(CNN)从开发分层式架构中受益匪浅。大多数模型,包括简单的前馈网络,都从该模块支持的图形风格架构的生成中受益匪浅(Gavrikov,2022)。
model1=construct_model()model2=sconstruct_model()model3=cconstruct_model(0.009)import visualkerasfrom PIL import ImageFontvisualkeras.layered_view(model1, legend=True)visualkeras.layered_view(model2, legend=True)visualkeras.layered_view(model3, legend=True)



图二。展示了三种不同模型架构的图示。作者使用 Jupyter 笔记本开发的图片。
7.Keras 模型图
keras.utils.plot_model 提供了内置函数来绘制通过 keras 使用 Graphviz 和 pydot 包定义和编译的模型。从图形上看,它不如上面使用的包直观,但它概述了顺序模型的基本架构。
tf.keras.utils.plot_model(model1,to_file="model.png",show_shapes=True,show_dtype=False,show_layer_names=True,rankdir="TB",expand_nested=True,dpi=96,layer_range=None,show_layer_activations=True,)
需要考虑的几个超参数:
- 模型:Keras 编译的模型或模型对象的实例
- to_file: 图像的文件名
- show_shapes: 显示神经网络中各层的尺寸和形状
- 显示 _ 层 _ 激活: 显示神经元内使用的激活功能

图 3。阐释了 tf.keras.utils.plot_model()的输出。作者使用 Jupyter 笔记本开发的图片。
8。张量板
TensorBoard 是一个仪表板界面,允许用户可视化不同模型运行的日志。日志的范围可以从跟踪不同时期的任何模型验证度量,如准确性、召回率、RMSE、MAPE 和 MSE,到创建模型架构的概念图。它是一个强大的工具,可以可视化预期的模型是否与预期的设计相匹配,同时推动对如何使用操作级图来更改模型的深入了解。
为了利用这个功能,我们需要首先加载 TensorBoard,然后创建一个日志目录。
%load_ext tensorboardfrom datetime import datetimefrom tensorflow import keras# Define the Keras TensorBoard callback.logdir="logs/fit/" + datetime.now().strftime("%Y%m%d-%H%M%S")tensorboard_callback = keras.callbacks.TensorBoard(log_dir=logdir)
在训练之前,创建 Keras TensorBoard 回调并指定日志目录。通过向 model.fit()提供此回调,可以确保为 TensorBoard 可视化记录图形数据。一旦你调用 TensorBoard,它可能需要几分钟来加载。
model.fit(X2_train, Y2_train,batch_size=64,epochs=5,callbacks=[tensorboard_callback])
注:X2_train 和 Y2_train 是未反映在上述代码中的训练数据集。您可以用自己的任何训练数据来替换它。
TensorBoard 默认总是显示 op 级图形。(“默认”标签在左侧突出显示。)该图与代码相比是颠倒的,因为数据是从下往上流动的。但正如你所见,该图大致类似于 Keras 模型的描述,带有通向其他计算节点的附加边(Tensorflow,2022)。

图 4。显示了 TensorBoard 的输出。作者使用 Jupyter 笔记本开发的图片。
更多关于如何使用 TensorBoard 的内容可以在参考中找到。
9.结论
从商业角度来看,用户必须向一组利益相关者展示他们的分析,这些图表增加了讨论的复杂性。它们可能不是极端增值。然而,从学术和学习的角度来看,利用这些包来展示这些深度学习架构是有价值的。对我来说,ANN visualizer 比其他产品都突出,因为它很容易理解图表,而 TensorBoard 的图形输出需要更多的技术细节才能得到结果。
10.参考
Gavrikov,P. (2022,4 月 13 日)。针对 Keras / TensorFlow 的 visualkeras】。GitHub。https://github.com/paulgavrikov/visualkeras
t . gheorghiu(2022 年 10 月 21 日)。安可视化器。GitHub。https://github.com/RedaOps/ann-visualizer
沙阿,A. (2018 年 4 月 28 日)。只用一行代码可视化人工神经网络。中等。https://towards data science . com/visualizing-artificial-neural-networks-ann-with-just-one-line-of-code-b 4233607209 e
堆栈溢出。(未注明)。机器学习——如何可视化神经网络架构?数据科学栈交换。检索于 2022 年 10 月 29 日,来自https://data science . stack exchange . com/questions/12851/how-do-you-visualize-neural-network-architectures
小组,k .(未注明)。 Keras 文档:模型绘图实用程序。keras . io . 2022 年 10 月 29 日检索,来自https://keras . io/API/utils/model _ plotting _ utils/# modeltodot-function
张量流。(2022 年 1 月 6 日)。检查张量流图|张量板。张量流。https://www.tensorflow.org/tensorboard/graphs
关于作者:高级分析专家和管理顾问,帮助公司通过对组织数据的商业、技术和数学的组合找到各种问题的解决方案。一个数据科学爱好者,在这里分享、学习、贡献;你可以和我在 上联系 和 上推特;
如何用 TensorBoard 可视化文本嵌入
原文:https://towardsdatascience.com/how-to-visualize-text-embeddings-with-tensorboard-47e07e3a12fb
从文本数据中轻松创建引人注目的图表,并检查嵌入内容的质量

图片作者。
目录
1.介绍
单词嵌入是将单词转换成数字的任何方法,它是任何涉及文本数据的机器学习(ML)工作流的首要任务。
独立于所面临的问题(分类、聚类等),利用输入文本的有效数字表示对 ML 模型的成功至关重要。
但是什么是文本的有效数字表示呢?基本上,我们希望将一个单词嵌入到一个能够传达单词含义信息的数字或向量中。
直观理解这一概念的一种方式是通过词语类比,即以下形式的关系:“ word₁之于 word₂,如同 word₃之于 word₄ ”。这就允许解决诸如“男人之于国王如同女人之于..?"通过矢量加减法,如下图所示:

最接近 w(国王)- w(男人)+ w(女人)的嵌入是女王的嵌入。图片来自[1]。
现在,让我们设想有一个通用数据集,其中每个文本样本都与一个类(或标签)相关联。例如,我们可能面临一个情感分析任务(带有诸如:中立、消极、积极等标签)或者一个对话代理的意图检测问题。在这种情况下,我们将训练一个分类模型,并使用准确性、精确度、召回率、F1 分数等指标来评估其性能...但是,我们怎样才能容易地观察到嵌入文本的图形表示,如上图所示?
在本帖中,我们涵盖了从一个文件导入的通用数据集开始,访问文本嵌入的吸引人的动态可视化所需的所有步骤。为了实现这个目标,我们将使用 TensorFlow(一个流行的开源 ML 库)和 TensorBoard。
2.数据建模
2.1 导入数据集
在之前的 post⁴中,我们描述了一个由罗马图书馆读者的评论组成的数据集的制造,从由“istituzione biblioteche di Roma”⁵.公开的公开数据开始
数据集由两列组成,一列为纯文本格式的读者评论(他们的语言是意大利语),另一列为他们各自的标签。这些标签标识了 review⁶.的主题我们希望创建一个多类文本分类器,能够预测输入评论的主题,并观察文本嵌入。
我们从导入所需的库开始:
我们从文件中加载数据集。值得注意的是,我们将描述可以应用于任何数据集的步骤,该数据集至少包含输入文本样本及其各自的标签,任何语言中的:

图片作者。
主题分布如 follows⁴:
- 关于女性在社会中的地位的评论,或者有着强势女性主角的小说(n=205,25.5%)
- 专辑和音乐会的评论,或音乐家的传记(n=182,22.64%)
- 关于经济和社会政治状况的书籍和论文的评论(n=161,20.02%)
- 与日本或日本文化相关的评论(n=134,13.67%)
- 科技泄密论文综述(122 篇,15.17%)

图片作者。
出于分类的目的,我们需要数字标签。因此,我们将主题描述映射到整数:
2.2 训练和验证集中的分割
我们将训练集和验证集中的数据拆分如下:
2.3 标记器
此时,我们的训练集和验证集仍然是由文本组成的。为了通过将每个评论转化为整数序列来对文本语料库进行矢量化,我们利用了tf.keras.preprocessing.text.Tokenizer ⁷类。
我们任意选择一个词汇大小,即要保留的最大单词数,然后实例化 tokenizer 类并使其适合训练集。之后,我们为训练集和验证集生成数字序列:
标记化的文本序列看起来怎么样?我们可以观察到如下随机处理的序列:

图片作者。
从上图中,我们可以看到输入句子中的每个单词是如何映射到一个不同的整数上,从而创建一个数字序列的。对于出现不止一次的单词,我们注意到它们总是得到相同的数字表示(例如," molto = > 39 ")。
我们最后创建一个包含单词和整数之间关联的字典:
2.4 填充
我们有效地将文本转换为向量,但是我们仍然不能将它们用作 ML 模型的输入,因为任何句子都可能有不同的长度。我们现在必须找到一种方法,使它们一致(即大小相同),而不产生不需要的噪声。
获得相同大小的序列的一种方法是通过填充它们,即向较短的序列添加相同的便利值(通常为零),允许它们匹配所需的长度:

填充示例。图片作者。
在张量流中实现这一点的一种方法是使用tf.keras.preprocessing.sequence.pad_sequences ⁸:

图片作者。
值得注意的是,pad_sequences接受一个输入参数(padding)来指定是否应该在每个序列的(默认行为)之前添加或者在之后添加。我们可以通过提示data_train[0]来打印和检查一个填充序列:

填充序列。图片作者。
2.5 创建并拟合模型
我们定义一个由Dense ⁹和Dropout ⁰层组成的简单模型。用户可以随意修改参数和层,但请务必记住:
- 最后一个
Dense层的输出空间维度必须等于我们想要预测的类的数量。 - 当我们面临多类分类问题时,我们利用
softmax激活函数。它估计目标类的离散概率分布。 Embedding层应该具有等于词汇表大小的输入维数,即最大整数索引+1:
我们选择训练 50 个时期的模型,但是我们也使用EarlyStopping回调以便在训练期间监控validation loss:如果度量在至少 3 个时期(patience = 3)没有改善,则训练被中断,并且来自validation loss显示最佳值(即最低值)的时期的权重被恢复(restore_best_weights = True):
我们可以从由fit方法返回的History对象中观察到分类的准确性:

图片作者。
多类文本分类器的创建对于我们的最终目标是必不可少的,即提供从通用模型中提取文本嵌入的指导,并可视化地探索它们。因此,我们不会将精力集中在进一步的模型改进和性能评估上。
3.导出并可视化嵌入
3.1 配置张量板
TensorBoard 是“ TensorFlow 的可视化工具包”。这是一个工具,提供有用的测量和可视化,以监测 ML 工作流程。例如,人们可以使用 TensorBoard 来跟踪损失和准确性等指标,观察模型图,探索权重和偏差,并将嵌入投影到更低维度的空间。
为此,在前面的章节中,我们引入了 TensorBoard 嵌入式投影仪:
from tensorboard.plugins import projector
我们加载 TensorBoard 笔记本扩展:
%load_ext tensorboard
我们创建一个目录来存储所需的信息。然后,我们将单词到整数的字典和来自Embedding层的权重保存在目录中,最后设置projector.ProjectorConfig() 配置:
我们最后通过logdir参数指定我们刚刚创建的日志目录来启动 TensorBoard:
%tensorboard --logdir /logs/fit/
3.2 玩嵌入
为了可视化嵌入,我们从 TensorBoard 仪表盘右上角的下拉菜单中选择PROJECTOR:

图片作者。
在左下方,我们注意到该界面提供了多种维度缩减技术供选择( UMAP、t-SNE、PCA、自定义),以便直观地检查二维或三维投影维度中的高维向量。
我们可以使用右上角的菜单来搜索特定的单词,并使用余弦相似度或欧几里德距离来突出显示它们最近的邻居。
例如,“ pianoforte ”(钢琴)这个词更接近于“巴赫”的名字,这可能是因为这位著名的德国作曲家对钢琴改编曲的评论非常流行:

图片作者。
如果我们搜索单词" Giapponese "(日语),三个最接近的单词依次是:"宫崎骏"、" urbanistica "(城市规划)和"香蕉",表明读者对作者宫崎骏和吉本芭娜娜的可能偏好,以及对日本城市规划概念的兴趣(例如町村):

图片作者。
4.参考
[1]卡尔·艾伦(Carl Allen),蒂莫西·霍斯佩达莱斯(Timothy Hospedales),《类比解释:走向理解单词嵌入,2019, arXiv:1901.09813 。
【www.bibliotechediroma.it/it/open-data-commenti-lettori
[7]tensor flow . org/API _ docs/python/TF/keras/preprocessing/text/Tokenizer
[8]tensor flow . org/API _ docs/python/TF/keras/预处理/sequence/pad_sequences
[9]tensorflow.org/api_docs/python/tf/keras/layers/Dense
tensorflow.org/api_docs/python/tf/keras/layers/Dropout
[11]tensorflow.org/api_docs/python/tf/keras/layers/Embedding
tensorflow.org/tensorboard/tensorboard_projector_plugin
如何在 Python 中对网格和点云进行体素化
原文:https://towardsdatascience.com/how-to-voxelize-meshes-and-point-clouds-in-python-ca94d403f81d
本文介绍了使用四个广泛流行的 Python 库生成点云和网格的体素表示的步骤,这四个 Python 库是:【open 3d】、 Trimesh 、 PyVista 、和 pyntcloud 。体素化是许多 3D 深度学习模型的重要预处理步骤。这篇文章展示了如何计算体素级别的特征,如颜色、点密度和占用率等。最后,还演示了如何创建简单的交互式体素化和阈值处理示例。谁说体素化要复杂?


Open3D 中点云的体素化示例—不同的体素大小(左)和体素网格的构建(右)|作者的图像
3D 数据的深度学习正在成为机器学习和理解我们周围世界越来越重要的一部分。随着深度相机和激光雷达等新的 3D 数据提取硬件在 CCTVs、相机和智能手机中变得越来越常见,越来越多的人正在使用它提供的额外维度。此外,摄影测量和基于运动的结构正成为 3D 重建和建模管道的正常部分,并且提取和操纵大型 3D 数据集正变得必要。用于 3D 深度学习的非结构化数据可以有不同的表示:
- 点云[3,4,11]
- 体素和体素网格[1,8,9]
- 深度图[2]
- 图[5,10]
这些远不是所有可能的 3D 数据表示,还有其他像参数化 CAD 模型、多视图图像、体积等。要想更好地了解其中的一些内容,你可以阅读 Florent Paux 撰写的关于“如何表示 3D 数据?”。
在本文中,我们将着重于将 3D 数据表示为体素。但是首先什么是体素?最简单的比较就是体素就是 3D 像素。体素被排序到体素网格中,这可以被视为图像的有序结构的 3D 等价物。当点云或网格变成体素表示时,它与体素网格相交。然后,点云或网格中的点落入某些体素中。留下这些体素,而不与任何点相交的所有其他体素要么被丢弃,要么被置零,留给我们的是物体的雕刻表示。体素化可以仅仅是表面级别的,或者贯穿整个网格/点云体积。


作者使用 PyVista | Image 将左侧的天使雕像转换为右侧的体素表示的体素化示例
构建网格和点云的体素化表示是许多深度学习方法的数据预处理中的重要步骤。体素化也广泛用于处理点云,包括二次采样、特征提取和占用分析等。最后,生成网格的体素表示对于创建游戏资源和简化模拟表面也很有用。
在文章中,我们将探索四个 Python 库的体素化能力——【Open3D】Trimeshpy vista和 pyntcloud 。我选择了这些库,因为它们提供了相对简单的体素化功能,以及用于分析所创建的体素的内置解决方案。其他较小的库,如 PyVoxelizer 、 Voxelizer 、 simple-3dviz 和 DepthVisualizer 也提供了体素化功能,但它们被认为过于有限。另一个提供体素化功能的库是 Vedo ,但是在最初的测试之后,我发现它们是针对 3D 体数据的,这限制了它们的使用。如果您想了解更多关于如何使用这些库创建漂亮的可视化效果的信息,您可以看看我以前的文章“用于网格、点云和数据可视化的 Python 库”(第 1 和第 2 部分)。我们将使用的唯一一个以前没有讨论过的库是 pyntcloud,所以我们将更详细地讨论如何安装和设置它。**
**
为了演示点云和网格上的体素化,我提供了两个对象。首先是中的一个兔子雕像点云。txt 格式,包含每个点的 X、Y、Z 坐标,以及它们的 R、G、B 颜色,最后是 Nx、Ny、Nz 法线。第二,一只公鸡雕像网在一个里。obj 格式,加上一个。mat 文件和一个纹理在中。jpg 格式。这两个物体都是使用运动摄影测量的结构创建的,并且可以在商业、非商业、公共和私人项目中免费使用。这些对象是一个更大的数据集[6]的一部分,并已用于噪声检测和检查方法的开发[7]。为了跟随教程,除了使用的库和它们的依赖项,您还需要 NumPy 和 SciPy。所有代码都可以在 GitHub 库这里获得
使用 Open3D 进行体素化

Open3D 使用不同大小的体素网格进行体素化的结果|图片由作者提供
pen3D 是功能最丰富的 Python 库之一,用于 3D 分析、网格和点云操作以及可视化。它包含了从点云和网格生成体素的优秀工具。该库还支持大量 3D 图元,如球体、立方体、圆柱体等。这使得从体素生成 3D 网格变得容易。最后,体素网格也可用于二次采样点云并生成分割。关于如何安装和使用 Open3D 的分步教程,可以看看我之前的文章“网格、点云、数据可视化的 Python 库(第一部分)”。我们将使用大量相同的可视化和回调代码。
我想再次指出 Florent Poux 博士的一个优秀教程—“如何使用 Python 自动进行 3D 点云的体素建模”,其中他给出了使用 Open3D 从点云生成体素的很好的概述和用例。在我的文章中,我以教程中提供的信息为基础。
为了更好地理解体素化过程,我们将使用 Open3D 构建两个示例—一个可视化多个体素网格级别以及它们如何影响体素化模型,另一个显示体素化过程。
我们首先使用 NumPy 的loadtxt()函数加载兔子雕像点云。我们将数组分为点、颜色和法线,并通过首先使用Vector3dVector函数将数组转换为矢量来创建一个 Open3D 点云。从这里开始,通过调用VoxelGrid.create_from_point_cloud()并给它点云和体素尺寸参数,创建一个体素网格是很简单的。如果我们从一个网格开始,我们可以用create_from_triangle_mesh()做同样的事情。如果我们在 Open3D 中使用体素网格来操作和分析点云/网格,我们可以在此停止。我们还可以通过调用将体素网格作为几何图形添加到 Visualizer 对象来快速可视化它。下面给出了代码。
我们也将继续处理体素网格,并将其转换为网格,因为我们想用它来创建一个动画,截至编写时,体素网格不能被转换。我们使用 Florent Poux 博士提出的方法。我们通过调用geometry.TriangleMesh()创建一个空的三角形网格,然后通过调用voxel_grid.get_voxels()从网格中提取所有体素,并获得所创建体素的真实大小。这样,我们可以在每个体素位置创建一个 Open3D box 图元,将这些框缩放到体素的大小,并通过调用voxel_grid.get_voxel_center_coordinate()将它们定位在体素中心。最后,我们将这样创建、缩放和定位的盒子添加到 TriangleMesh 对象中。
为了演示不同的体素网格大小如何改变最终的体素输出,并使示例更有趣一些,我们将制作体素输出之间的旋转过渡动画。为此,我们通过调用register_animation_callback()向可视化工具注册一个新的回调。在回调函数中,我们生成体素网格,并从中创建一个体素网格。我们将这样创建的网格旋转 360 度,破坏网格,并重复不同大小的体素网格。我们还利用一个小类来保存在回调更新循环中会发生变化的所有变量。下面给出了完整旋转可视化的代码。
为了更好地理解创建体素和体素网格的过程,我们将通过动画对其进行可视化。这个可视化的代码与我们已经展示过的非常相似,但是这次我们通过添加每个新的体素作为回调函数循环的一部分来可视化创建体素网格的过程。为了得到兔子雕像的轮廓,我们还使用体素函数get_voxel_center_coordinates来提取体素中心,并使用它们来生成点云。这样,我们以迂回的方式生成输入点云的二次采样和均匀采样版本。整个可视化的代码如下所示。

作者在 Open3D| Image 中从输入点云生成体素网格的可视化表示
使用 Pyntcloud 的体素化

Pyntcloud 体素化的结果,体素基于密度着色|作者的图像
Pynt cloud 是一个轻量级的强大的 Python 3 库,用于点云和网格的分析和预处理。它包含用于体素化、特征提取、网格平滑和抽取、点云二次采样、离群点检测等工具。它直接用作 jupyter 笔记本的一部分,包含用于 threejs 、python reejs、 PyVista 和 matplotlib 的可视化绑定。它可以很容易地集成到其他大型库(如 Open3D 和 PyVista)的工作流中。
pyntcloud 的体素化功能非常强大,体素表示已经包含许多预先计算的特征,如体素到点的对应关系、体素网格段信息、每个体素中的点密度和二进制掩码等。
Pyntcloud 可以在 Linux、Mac 和 Windows 上使用 Python 3。它需要 NumPy 和熊猫作为先决条件。该库在不断发展,并且每月更新。它可以使用 pip 或 Anaconda 安装。我们通常将每个库安装在一个新的 Anaconda 环境中,以保持一切整洁,但在这种情况下,我们将 pyntcloud 与 Open3d 一起安装,以利用前者的可视化功能。我们这样做是因为 pyntcloud 中的体素和网格可视化需要 threejs 或 pythreejs,而这又需要 jupyter 笔记本或启动本地服务器。这些超出了本文的范围,但是我仍然会给出如何在 pyntcloud 中直接可视化体素的例子。
conda create -n pyntcloud_env python=3.8
conda activate pyntcloud_env
pip install open3d
pip install pyntcloud
OR
conda install pyntcloud -c conda-forge
一旦我们安装了所有的东西并且没有错误,我们就可以测试两个库,首先一起调用import open3d as o3d和print(o3d.__version__),然后一起调用from pyntcloud import PyntCloud和print(PyntCloud.__version__)。如果两个库都导入了,我们就可以继续学习教程了。
pyntcloud 的 I/O 模块无论好坏都相当简化。它使用PyntCloud.from_file()来加载网格和点云。该函数根据需要加载的文件的扩展名来决定调用什么内部函数。要获得 pyntcloud 支持的所有文件类型的列表,您可以在这里阅读它们的 I/O 页面。对于包含点云的 ASCII 文件,pyntcloud 直接调用 Pandas,并需要额外的输入参数,如分隔符、列名、是否有标题部分等。在本教程的情况下,我们导入兔子雕像点云,并需要显式指定所有列的名称。一旦我们加载了点云,我们需要调用.add_structure("voxelgrid", grid_size_x, grid_size_y, grid_size_z)在点云的顶部创建一个体素网格。该函数的输出是点云中的点与网格相交的体素网格 id。然后,我们通过调用name_of_point_cloud.structures[voxelgrid_ids]使用这些 id 来创建点云的已占用体素表示。从这里开始,我们可以使用这个对象来提取体素的信息,并可视化一切。下面给出了导入点云并提取体素的代码。
一旦我们创建了点云的体素表示,我们可以通过调用voxelgrid.plot(cmap="hsv", backend="threejs")使用 pyntcloud 直接可视化它。这里可以调用不同的后端,但是对于网格和体素,只支持threejs和pythreejs。为了能够在不需要运行服务器的情况下更容易地可视化一切,我们将把创建的体素网格转移到 Open3D,并使用 Open3D 部分中显示的技术来可视化它。通过这种方式,我们还展示了结合这两个库,利用它们的优势是多么容易。
我们首先创建一个空的 Open3D geometry.TriangleMesh(),它将被体素填充。然后,我们循环遍历 pyntcloud 体素网格上的所有体素。我们可以从voxelgrid.voxel_n数组中获得当前的体素 id,并且我们可以通过调用voxelgrid.voxel_centers[voxel_id]使用这个 id 来获得它的体素中心。为了获得我们将用作体素颜色的密度值,我们使用体素网格中体素的 X、Y 和 Z 位置,这是我们已经提取的。从这里开始,一切都与 Open3D 的例子相同。我们给盒子图元着色,缩放它,并将其平移到体素中心。最后,我们初始化一个 Open3D 可视化工具,并将创建的体素网格作为几何图形添加到其中。下面给出了完整的 pyntcloud/Open3D 可视化代码。




使用圆锥体(左上)、圆柱体(右上)、八面体(左下)和球体(右下)的体素网格|作者图片
为了更有趣,我们可以在创建体素网格时用其他图元替换长方体图元。Open3D 有大量的图元——球体、圆柱体、圆环体、八面体、圆锥体等。我们创建一个小的选择函数,它采用图元的名称并返回 3D 对象。那么我们唯一要改变的就是将primitive = o3d.geometry.TriangleMesh.createbox()改为新的函数调用primitive = choose_primitive("cylinder")。该函数的代码如下所示。
使用 Trimesh 的体素化



在 Trimesh 中创建彩色体素化网格的过程-带有纹理的输入网格(左),带有映射到每个顶点的颜色信息的网格(中),以及彩色体素化网格(右)|作者的图像
T rimesh 是使用最广泛的 Python 库之一,用于处理网格。大量其他库和独立项目使用它或者直接构建在它之上。它提供了大量的点云和网格分析和转换工具。有关 Trimesh 的安装说明和可视化使用示例,您可以查看文章“ Python 库用于网格、点云和数据可视化(第 1 部分)”,其中我们展示了如何处理网格和点数据集。
对于体素化,Trimesh 有许多方法来创建体素网格,在内部挖空它们,以及使用形态学操作(如闭合和二进制膨胀)填充它们。它也有直接的方法,通过分别调用trimesh.voxel.morphology.surface 和trimesh.voxel.morphology.fill来提取体素网格的表面或填充。该库还提供了快速分析体素网格的工具,如提取边界,检查网格中体素空间是空的还是填充的,以及计算被占用体素的比例和体积。用于执行行进立方体计算的功能也是容易获得的。
Trimesh 的一个非常棘手的功能是提取网格的纹理颜色信息,并将其作为颜色提供给体素表示。为此,我们首先提取纹理信息,并将其作为颜色信息映射到网格的每个顶点。这是通过调用name_of_the_imported_mesh.visual.to_color().vertex_colors来完成的。然后,我们可以将它转换成一个 NumPy 数组,并为每个网格顶点自由访问它。然后我们可以通过直接调用name_of_the_imported_mesh.voxelized().hollow()从网格中创建一个体素网格,在这里我们可以指定体素网格的分辨率。我们还将体素化转换为空心表示,因为我们需要计算网格和体素表示之间的距离。
由于 Trimesh 不包含将网格颜色直接映射到体素表示的体素的功能,我们首先需要调用trimesh.proximity.ProximityQuery(name_of_the_mesh).vertex(voxel_grid_name.points)来计算网格的每个顶点和已经创建的体素表示点之间的距离。然后,我们需要遍历每个点,提取最近的网格顶点的颜色,并将其映射到大小为(X,Y,Z,4)的颜色数组中,其中 X,Y 和 Z 是生成的体素网格的大小,3 是颜色的 R,G,B 和 A 分量。一旦我们有了映射,我们可以调用voxel_grid_name.as_boxes(colors = mapped_color_array),这使得体素网格成为一个三维网格对象,体素/框的颜色等于初始网格的颜色。运行代码时,读者会发现体素化的网格上有洞。这很可能是因为公鸡雕像的网格由于运动过程的结构而不是完全防水的。体素化过程的代码如下所示。
使用 PyVista 的体素化

PyVista 网格的多点可视化(左上)、体素化表示(右上)、代替立方体的圆锥体表示(左下)以及到网格的每体素距离可视化(右下)|作者图片
P yVista 是一个全功能的网格可视化和分析库,构建于 VTK 之上。它可以用来创建包含多个窗口、GUI 元素以及通过鼠标、键盘和 GUI 进行交互的复杂应用程序。该库使用过滤例程来转换点云和网格,并提取任何所需的信息。它可以用作物理模拟、计算机图形、GIS、建筑可视化、光线跟踪和数学计算展示的一部分。有关 PyVista 的安装说明和可视化使用的示例,您可以查看文章“ Python Libraries for Mesh,Point Cloud,and Data Visualization (Part 1) ”,其中我们展示了如何处理网格和点数据集。
由于 PyVista 建立在 VTK 之上,它包含了一个简单的接口,用于将网格转换成UniformGrid对象,以构建体素表示。要提到的重要一点是 PyVista 只能用于网格的体素化。如果给定一个点云,首先需要将它三角化成网格。对于更简单的点云表面,这可以在 PyVista 中使用delaunay_3d三角测量直接完成。另一个要点是,PyVista 在体素化时默认期望无缝网格。如果网格有洞、重叠边或其他不正确的几何体,尝试对其进行体素化将会导致错误。体素化是通过调用pyvista.voxelize(input_mesh)完成的。如果这个方法抛出一个错误,说明使用的网格不是防水的,有两种方法可以继续——要么在另一个软件如 Blender 中修复网格,要么添加参数check_surface=False。在网格中有小瑕疵的情况下,第二种方法将产生良好的结果,没有任何噪声或伪影。请注意,如果有大洞或几何缺陷,跳过表面检查可能会导致不正确的体素化和噪声。
一旦voxelize被调用,结果是一个包含所有体素的统一网格对象。然后可以从表示中提取附加信息,如体素中心、每个体素的 X、Y、Z 边界坐标、包含网格部分的体素的凸包等。PyVista 的示例中给出了对体素表示的有用分析。一旦我们有了体素,我们可以调用compute_implicit_distance来计算体素和网格之间的距离。这对于检查体素化过程是否有任何不一致是有用的,并且用于过滤掉网格内部的体素以创建中空体素表示。另一个有用的转换是使用不同于立方体的图元对象来表示体素。这可以在调用voxelize时直接设置,也可以稍后通过调用voxelized_object.glyph(geom = type_of_primitive())创建。最后,对于这一部分,我们还将在单独的子窗口中可视化所有内容,并将它们链接起来。这与 matplotlib 的方法相同,在设置绘图仪pyvista.Plotter(shape=(num_horiz_windows,num_vertical_windows))时给出了窗口的数量。一旦设置完毕,就可以通过调用pyvista.subplot(horizontal_num, vertical_num)来调用每个子窗口。下面给出了用于产生输入网格的四个子窗口可视化、具有立方体的体素化表示、具有锥体的体素化表示以及网格和体素之间的最终距离的代码。

PyVista 阈值化体素网格基于每个体素中的点密度,使用 GUI 部件|作者提供的图片
我们将使用体素化上下文来展示如何在 PyVista 中创建一个简单的 GUI,并为生成过程添加交互性。首先,一个简短的例子演示了如何为每个体素生成点密度。之所以选择这一步,是因为这是一个有用的分析步骤,PyVista 中没有这一步。为此,需要 SciPy 的KDTree功能。我们用它来得到每个体素中心的所有相邻网格顶点。SciPy 有一个函数query_ball_point,它以一个点为圆心,以指定的半径制作一个球体,并将所有相邻的点都放到这个球体上。当然,当我们在一个体素立方体形状内部寻找点时,使用一个球体会导致不完美的结果。对于本教程,我们将使用这种简化。为了计算球体的半径,使其具有与体素立方体相同的体积,我们使用已知的立方体侧面 x 来首先找到立方体的体积:

接下来,我们得到球体的体积:

从那里如果想让两个体积相等,我们可以计算半径为:

这可以用 Python 写在一个小的帮助器函数中,如下所示。
一旦我们有了每个体素中心附近的所有顶点,我们就对它们进行计数,并用最大计数将它们除以,以便在 0 和 1 之间对它们进行归一化以便可视化。最后,我们将这些值作为场添加到体素中。代码被分成下面给出的另一个函数。
PyVista 包含一个现成的内置可视化工具,用于对网格和点云进行阈值处理,名为add_mesh_threshold。这直接将对象添加到场景中,并创建一个滑块小部件,该小部件连接到最近的活动字段或指定字段。完整可视化的代码如下所示。

PyVista 自定义 GUI 小部件,用于交互式更改体素化体素大小|作者图片
最后,我们将创建一个自定义的 slider 小部件来演示它在 PyVista 中是多么简单。移动滑块将改变体素的大小,并重新计算整体。为了更简洁,我们将把所有东西添加到一个类中。要创建一个 slider 小部件,我们只需要调用add_slider_widget(name_of_callback_function, [min, max], start_value, title_of_widget, how_often_should_the_widget_call_callback)。最后一个输入决定了当我们单击鼠标按钮、释放鼠标按钮或者每次移动鼠标时是否会调用回调函数。在我们的例子中,我们使用最后一个选项。在回调函数中,我们进行体素化计算,并将其添加到绘图仪中。这里,在绘图仪中指定网格对象的名称很重要。这样 PyVista 就知道我们在更新同一个网格,而不是每次移动滑块都创建一个新的。下面给出了整个 GUI 交互性的代码。
结论
V 当使用点云和网格时,oxelization 可以是你的武器库中最强大的工具之一。使用 Python 生成和处理体素非常简单。在本文中,我们探索了如何创建体素网格,从体素中提取特征,以及将图元从立方体更改为球体、圆柱体、圆锥体等。我们还展示了如何完成体素构建过程,以及如何创建交互式体素化和阈值工具。
一旦有了体素表示,就可以很容易地将其插入到现有的深度学习或资产创建管道中,以实现所需的结果。在接下来的文章中,我们将探索更多通过邻接分析、PCA 特征提取、RANSAC 实现等从点云和网格中提取特征和有用信息的方法。
如果您想了解更多关于从点云和网格中提取特征的信息,请阅读我的一些关于三维表面检测和噪声检测的文章[11]和[7]。您可以在我的 页面 上找到这些文章和我的其他研究,如果您看到有趣的东西或只想聊天,请随时给我留言。继续关注更多!
参考
- Qi,C. R .,Su,h .,Nieß ner,m .,Dai,a .,Yan,m .,& Guibas,L. J. (2016)。用于 3d 数据对象分类的体积和多视图 cnns。在IEEE 计算机视觉和模式识别会议记录(第 5648-5656 页);https://open access . the VF . com/content _ cvpr _ 2016/html/Qi _ volume _ and _ Multi-View _ CVPR _ 2016 _ paper . html
- Su,h .,Maji,s .,Kalogerakis,e .,& Learned-Miller,E. (2015)。用于三维形状识别的多视图卷积神经网络。《IEEE 计算机视觉国际会议录》T11(第 945-953 页);https://www . cv-foundation . org/open access/content _ iccv _ 2015/html/Su _ Multi-View _ 回旋 _ neuro _ ICCV _ 2015 _ paper . html
- Qi,C. R .,Su,h .,Mo,k .,& Guibas,L. J. (2017)。Pointnet:针对 3d 分类和分割的点集深度学习。摘自《IEEE 计算机视觉与模式识别会议录》T15(第 652-660 页);https://open access . the VF . com/content _ cvpr _ 2017/html/Qi _ point net _ Deep _ Learning _ CVPR _ 2017 _ paper . html
- 齐春日,易,李,苏,h .,,吉巴斯,刘杰(2017)。Pointnet++:度量空间中点集的深度层次特征学习。神经信息处理系统的进展,30;https://www . cs . Toronto . edu/~ bonner/courses/2022s/CSC 2547/papers/point _ nets/point net++、_qi、_nips2017.pdf
- 王,孙,刘,S. E .萨尔马,布朗斯坦,M. M .,&所罗门,J. M. (2019)。用于点云学习的动态图 cnn。Acm Transactions On Graphics(tog), 38 (5),1–12;https://dl.acm.org/doi/abs/10.1145/3326362
- 尼科洛夫一世;麦德森,C. (2020),“GGG——粗糙还是嘈杂?SfM 重建中的噪声检测指标”,门德利数据,V2;https://doi.org/10.17632/xtv5y29xvz.2
- 尼科洛夫,I. ,&马德森,C. (2020)。粗暴还是吵闹?SfM 重建中噪声估计的度量。传感器、 20 (19)、5725;https://doi.org/10.3390/s20195725
- Poux 和 r . Billen(2019 年)。基于体素的三维点云语义分割:无监督的几何和关系特征与深度学习方法。国际摄影测量与遥感学会国际地理信息杂志。8(5), 213;https://doi.org/10.3390/ijgi8050213
- 徐俊杰,张,r,窦俊杰,朱,孙俊杰,&蒲,s(2021)。Rpvnet:用于激光雷达点云分割的深度有效的距离-点-体素融合网络。在IEEE/CVF 计算机视觉国际会议论文集(第 16024–16033 页);https://open access . the CVF . com/content/iccv 2021/html/Xu _ RPVNet _ A _ Deep _ and _ Efficient _ Range-Point-Voxel _ Fusion _ Network _ for _ LiDAR _ ICCV _ 2021 _ paper . html
- 豪勒姆,J. B .,阿拉哈姆,M. M .,林奇,M. S .,亨里克森,K. S .,尼科洛夫,I 。默斯伦德,T. B. (2021 年 2 月)。使用合成点云的下水道缺陷分类。在 VISIGRAPP (5: VISAPP) (第 891–900 页);【https://www.scitepress.org/Papers/2021/102079/102079.pdf
- 尼科洛夫,I. ,&马德森,C. B. (2021)。使用砂纸粒度量化风力涡轮机叶片表面粗糙度:初步探索。在第 16 届计算机视觉理论与应用国际会议(第 801–808 页)。科学出版社数字图书馆;https://doi.org/10.5220/0010283908010808**
如何与数据科学家合作
原文:https://towardsdatascience.com/how-to-work-with-a-data-scientist-211260b9ba69

萨姆·穆卡达姆在 Unsplash 上拍摄的照片
给产品经理、工程师和其他队友的建议
想象一下这个——你是成长中的初创公司的第一位数据科学家,或者你是一位产品经理,刚刚有一位新的数据科学家加入团队,并渴望开始与他们合作。无论您的情况如何,当您的团队知道如何与他们的数据科学家有效地协作、工作和合作时,他们会做得最好。
如何有效地与数据科学家合作? 没有简单的答案——这是一个非常宽泛的问题,很难准确定义,因为没有两个数据科学家、项目、团队或公司是相同的。也就是说,我想我可以分享一些在所有情况下都有用的技巧。本文面向数据科学家以及与他们密切合作的人—该列表包括产品经理、工程师、设计师、营销人员和操作员,但也广泛适用于对发展更多数据驱动思维感兴趣的任何人。
提示 1:语境是王道

布雷特·乔丹在 Unsplash 上拍摄的照片
在许多大型科技公司中,数据科学家直接嵌入产品团队,以确保他们拥有最大可能的背景——他们了解产品的愿景是什么,业务目标是什么,以及设计和工程试图提供什么。
这不是唯一的方法——在光谱的另一端,数据科学家更多地扮演了一个顾问的角色,他们只是被请来做一个特定的项目,然后带着他们所有新发现的产品知识消失,与另一个团队/项目合作。
根据公司规模和行业,一种模式可能比另一种更有意义。从我过去在脸书和 Doordash 的工作经历来看,我越来越喜欢嵌入式模型,因为它让我获得了产品领域的专业知识,并建立了更牢固的工作关系。这让我有时间了解我的团队的领域,这使我有更强的能力来识别机会,解释实验,并帮助找出产品的策略。
总结这一部分—当团队中有了新的数据科学家时,您应该尝试了解他们在团队中的角色或背景—他们是将被直接嵌入、咨询还是介于两者之间。一旦你理解了这一点,尽可能多地分享你自己工作的背景,这样他们就能成功地完成自己的角色。
秘诀 2:偏向长期思考

数据科学家的优势之一是帮助制定长期计划。为了确保我们不会不断追逐短期问题,请考虑数据科学家在三个时间周期中的工作:
- 每周时间段 —解释 A/B 结果或跟踪产品表现(趋势变化、指标下降、产品故障)
- 每月时间框架 —深入了解取得最大成功所需的产品特性,或找出正确的指标进行跟踪和预测
- 季度时间框架—找出未来投资的最大机会,或了解产品生态系统的长期健康状况
当我们比较上面的三个时间段时,季度时间段通常是最关键的— 数据科学家的最大贡献通常是战略性的,而不是战术性的。理想情况下,我们正在寻找一个平衡点,在这个平衡点上,我们可以花大部分时间“提前一个季度”思考我们接下来要做什么,同时确保我们做好战术决策。
将我们所有的时间都花在当前的工作流程上是短视的——这将有助于您在本地执行,但会导致未来缺乏战略理解,并导致错过关键问题或机会。
技巧 3:多问业务问题,少问数据问题

业务问题的范围可以从战略到运营:
- 战略:我们怎样才能成为一个让车门开关司机觉得他们得到了公平报酬的地方?我们怎样才能让脸书用户更有安全感?
- 操作:理解实验结果,预测,理解关键指标中的意外变化,评估错误的影响
数据问题更直白:能帮我拉一下这个指标吗?你能为我写这个查询吗?这是我遇到的两个最常见的请求。
询问业务问题能让每个人更好地理解,从而做出更好的决策和路线图。问数据问题恰恰给了你想要的东西:一个数字。数据科学家喜欢更广泛的业务问题,因为它允许我们更深入地挖掘问题,并让我们感觉自己是决策制定的关键部分。
技巧 4:无情地划分优先级

由 Alexander Schimmeck 在 Unsplash 上拍摄
数据科学家可以花时间做无数的事情。我们总是可以花更多的时间来尝试更准确的预测,更深入地分析实验,或者获得更多的基础知识。
这意味着我们需要分清主次。产品团队需要决定什么是至关重要的,而不是什么是好东西——什么将改变产品的方向而不是什么将验证或保证。我们需要优先关注最重要的问题,而不是最有趣或最困难的问题。
当你有疑问时,考虑一下这个问题:我们要做的最重要的决定是什么?如果您不知道该决策是什么,数据科学家应该能够帮助您弄清楚它是什么(即应该调查哪些杠杆?)。如果您确实知道决定是什么,数据科学家应该能够帮助您做出正确的决定。
总结想法
一个产品的用户通常比员工多很多倍——Meta 有 7 万名员工,但有数十亿用户,Doordash 有 6000 名员工和数百万消费者。数据科学是联系这些人的主要方式之一,以了解他们的经历,并确定他们希望我们下一步做什么。
如果我们作为数据科学家什么都不做,我们就必须实现这个目标。数据是大规模用户的声音,我们的工作是弄清楚他们在告诉我们什么。
如何处理经过审查的数据
原文:https://towardsdatascience.com/how-to-work-with-censored-data-26b7b6e2c7f
Tobit 模型在零工经济中的应用

线性回归可能是统计学习中最基础的话题。几乎所有机器学习、统计学、计量经济学的课程都是从普通最小二乘法的基础开始的。为什么不呢?它的定义方程y =β₀+β₁**x+e看起来足够简单。但是你为什么会关心 OLS 方程呢?诚然,在大型语言模型和无人驾驶汽车的时代,谈论线性回归听起来几乎很神秘。然而,正如我们将在本文中看到的,有时线性回归正是您所需要的。
线性回归的优点之一是可解释性。在我们上面的等式中,我们可以说 x 对 y 的影响是,平均而言, β₁ ,考虑到一些随机噪声, e ,以及一个常数项, β ₀ 。然而,这个简单的解释是基于对数据如何产生的强有力的假设:在 x 和 y 之间的关系实际上是线性的吗?我们是否正确测量了 x 和 y ?我们看到的是目标人群的代表性样本吗?你可能已经猜到,在现实生活中,这些问题的答案往往是不。这就引出了我想在本文中展示的用例:删失数据的 Tobit 模型。
零工经济遭遇数据审查
**Note**: This article uses simulated data. This data is meant to be representative of a real-life scenario, but it does not contain any real transactions from any company mentioned in the article nor does it reflect their actual state of operations.
假设你是 gig 行业公司的一名数据科学家。假设你的公司运行一个骑行应用,比如优步。(这适用于任何配对业务,从送餐到家政服务或运输。)您的经理想知道优惠价格对司机一天完成的乘车次数有什么影响。您有一个数据集,其中包含驾驶员在登录应用程序后一天内完成的乘车次数,包括那些没有完成任何乘车次数的驾驶员。该数据集还包含一个收入变量,该变量已按小时标准化,因此乘车次数具有可比性。“简单——你对自己说——我只要运行一个线性回归,然后取β值就行了”。你确定吗?看下面的图表。它显示了一个城市中有多少司机完成了每小时收入的函数。有什么奇怪的吗?

作为小时收入的函数完成的乘坐次数。来源:作者
乍一看,不尽然。虽然差异很大,但每小时收入和完成的乘坐次数之间似乎存在正相关关系。换句话说,如果司机得到更多的报酬,他们就会做更多的工作。经济学 101。但是让我们更深入地分析一下数据。看下面的直方图。它显示了每位驾驶员每天完成的乘坐次数的分布。如您所见,0 次乘车有大量积分。

每位驾驶员每天完成的乘坐次数分布。来源:作者
现在再来看看散点图。是不是感觉云想让继续在 x 轴的负侧?“但这没有意义——你想——你不可能在一天之内完成消极乘坐!”这是真的,你不能。但是你的数据也不一定是错误的。相反,这是经济学领域众所周知的现象。我们在这里看到的是一个数据审查的例子:司机只有在他们愿意以特定的速度完成一次骑行时才会完成。然而,其他一些司机会登录该应用程序,查看当时支付的费率,并决定不值得他们花时间。这些是零骑的。
托比特模型
“什么叫少于一无所有?”威尔伯回答道。“我不认为有比一无所有更少的东西。无绝对是虚无的极限。这是你能出的最低价了。这是这条线的终点。有东西怎么会比没有东西少呢?如果有比无更少的东西,那么无就不是无,它会是有,即使只是很小的一点点。但是如果没有什么就是没有什么,那么没有什么就没有比它更少的东西。”
——詹姆士·托宾 1958 年引用埃尔文·布鲁克斯·怀特的《夏洛特的网》 (1952)
这个问题最早是由詹姆斯·罗宾提出的,他在 1958 年的论文[1]中研究了个人在耐用品上的支出。特别是,他注意到他的美国家庭样本显示,大多数美国家庭报告汽车或耐用品的零消费,因为他们买不起。换句话说,他的样本必然有一个零下限。他在论文的图 1 中巧妙地描述了这个问题,如下所示。

托宾(1958)的截图——复古,对吧?
请注意这对 OLS 估计量的重要影响。如果你要运行一个简单的线性回归,你的 β₁ 会不一致,因为你观察到的数据在图表上不是线性分布的。如果我们取上面托宾的图像,你会想要一个从 a 到 b 的直线斜率的估计β₁——换句话说, x 和 y 之间的无条件平均关系。但是,因为您的数据是经过审查的,所以观察到的关系(条件平均值)不是线性的,而是类似于 O 和 b 之间的线。基本上,您的模型对于某些小时费率会看到太多的零,如果我们允许“负乘”来保持线性关系(即,如果我们让数据云继续进入 y 轴的负值),则实际上应该是负值。因此,因为我们的样本在零行程时被删截,所以数据不允许我们画出代表 x 和 y. 之间线性关系的真正直线
在他的论文中,Tobin 提出了一种使用截尾数据估计 β₁ 的方法,这种方法后来被称为 Tobit 模型。他的想法是通过考虑观测值被删截为 0(或任何其他值,从下往上)的概率来修正我们估计β₁的方法。你会问,为什么是托宾模型而不是托宾 T21 模型?这个术语是由另一位经济学家亚瑟·戈德伯格创造的。它是单词 Tobit and 和 probit,的组合,这是一种分类技术,用于计算观察结果被删截的概率。
虽然模型的推导超出了本文的范围,但是让我们快速地看一下这个问题的完整性。下面的函数显示了我们必须通过最大似然估计来解决的最大化问题,以得到我们真正的 β₁ (在向量符号中显示为β,与 β ₀) 。托宾解暗示误差是独立且正态分布的,标准差σ。在函数中,如果 y = 0 ,则 dᵢ 取值为 0,否则取值为 1。因此,总和的左边(乘以 dᵢ ,在顶行)相当于 OLS 似然函数,而右边(乘以 (1-dᵢ) ,在底行),说明观察值 i 被删除的概率。

Tobit 模型的似然函数。来源:作者
履行
虽然很酷,但据我所知,Python 中没有使用 Tobit 模型的 Python 包(至少我在pip或conda上没见过)。然而,我发现 James Jensen 的实现非常有用。我分叉了我自己的版本,你可以在这里找到,我强烈鼓励你也这样做!
还记得我说过,如果我们用简单的 OLS 估计,β₁会不一致吗?用数学术语来说,这意味着我们对 β 的估计不会收敛到它的实际值。更简单地说,我们的线性关系的斜率将是 off。为了说明这一点,下图中的绿线显示了我们的驱动程序示例中的 OLS 拟合值。另一方面,使用 Tobit 模型估算 β 给出了一个稍高的值(0.35对0.33),用蓝线表示。

小时收入和乘坐次数之间的最佳拟合线已完成:OLS(绿色)和托比特模型(蓝色)。来源:作者
OLS 估计值和托比特估计值之间的差异会因您的模型规格而异,有时会更大,有时会更小。请注意,我们的等式中没有包括任何控制变量,也没有任何可靠的识别策略来支持它。这意味着我们的模型可以改进,这可能会影响我们的估计。因此,我们不应该试图从这些特定的数字中推导出任何意义。
边际效应
现在你已经运行了你的 Tobit 模型,并得出了对 β₁、的估计,你可能会忍不住说,每增加一美元,每位车手将完成 β₁ 更多的旅程。但事情没这么简单!我们来看看为什么。
一般来说,我们的目的是估计收入变化对完成的游乐设施的平均影响。这就是经济学家所说的“边际效应”。虽然我们将跳过数学细节,但这相当于等式中 y (乘车次数)相对于 x (收益)的导数。在 OLS 模型中,这就是简单的 β₁.但是,在 Tobit 模型中, x 对 y 的边际效应稍微复杂一些。因为我们在推导 β₁ 的过程中包括了一个观察被删节的概率,所以我们在解释它的时候也需要考虑它。

Tobit 模型中 x 对 y 的边际效应。来源:作者
上面的等式显示了收入的变化如何转化为平均完成的乘坐次数——因此在左手边有条件的期望。在右边,我们得到了我们的 β₁ ,乘以正态分布的累积分布函数(CDF,用φ表示),在我们的模型对 y、的估计值处评估,即β₀+β₁**x,并通过我们的误差标准偏差σ归一化。
我上面描述的可能听起来很复杂,但是你会发现实际上并不复杂。边际效应等式的意思是,每小时收入变化的平均影响确实取决于β₁.然而,该系数由个人愿意以当前小时工资率工作的概率加权,如 CDF 所示。换句话说,提供额外一美元的边际效应将会不同,这取决于那次乘坐最初是 10 美元还是 40 美元。如果你想一想,这是有道理的:有人为 40 美元工作的概率可能高于为 10 美元工作的概率!
为了了解这在实践中是如何工作的,让我们回到我们的 Python 实现。为了计算边际效应,我创建了一个名为margins的函数,它建立在詹姆斯·詹森的解决方案之上。根据我们估计的0.35的 β₁ 和 10 美元的起始时薪,估计的边际效应将是0.17。然而,将起步价改为 40 美元会产生边际效应0.32。如果我们把每小时的收入进一步提高,比如说 100 美元,边际效应已经是0.35。如你所见,收入越高,我们的边际效应就越接近 β₁,因为司机更有可能愿意为了更高的时薪完成一次驾驶。
结论
在这篇文章中,我们已经看到,如果我们的观察数据被审查,一些表面上简单的回归问题实际上可能变得棘手。对于零工经济行业的公司来说,情况就是这样,他们可能想知道给员工提供多少工资来增加他们在平台上的参与度。为了解释这个删失数据问题,我们引入了 Tobit 模型,这是一个由詹姆士·托宾在 1958 年开发的计量经济学模型。
你可以在这个 Github repo 中找到跟随这篇文章的代码。
参考
[1]托宾·詹姆斯(1958)。“有限因变量的关系估计” (PDF)。计量经济学。26(1):24–36。
如何在 MySQL 中使用 JSON 数据
原文:https://towardsdatascience.com/how-to-work-with-json-data-in-mysql-11672e4da7e9
在 MySQL 中学习“NoSQL”
MySQL 支持原生的 JSON 数据类型,该数据类型支持 JSON 文档的自动验证、优化存储和访问。尽管 JSON 数据应该更好地存储在 NoSQL 数据库中,比如 MongoDB ,但是您仍然会不时地遇到包含 JSON 数据的表。在本文的第一部分,我们将介绍如何用简单的语句从 MySQL 的 JSON 字段中提取数据。在第二部分,我们将介绍如何将 MySQL 表中的数据聚集到 JSON 数组或对象中,以便在您的应用程序中方便地使用。

图片来自 Pixabay 。
要设置的系统类似于上一篇文章中介绍的关于如何用 Python 执行 SQL 查询的系统。如果您已经根据该文章中的说明设置了系统,那么您可以继续下一部分。如果没有,您可以按照下面的简化说明来设置您的系统。有关命令和选项的详细解释,请参考上一篇文章。
本质上,我们将在 Docker 容器中启动一个本地 MySQL 服务器:
您可以直接在上面启动的控制台中执行 SQL 查询。或者,如果您更喜欢使用图形界面,您可以安装并使用 DBeaver ,这是一个非常棒的图形数据库管理器,适用于所有类型的数据库。如果你一直在纠结 MySQL workbench,那真的值得一试。关于如何安装和设置 DBeaver 的更多细节,本文有一个简短但有用的总结。
让我们首先探索一下可以用来从 JSON 字段中提取数据的常见 MySQL 函数和操作符。
MySQL 中有两种主要类型的 JSON 值:
- JSON 数组—用逗号分隔并用方括号([])括起来的值列表。
- JSON object——一个字典/hashmap/object(在不同的编程语言中名称是不同的),它有一组由逗号分隔的键值对,并包含在花括号({})中。
JSON 数组和对象可以相互嵌套,我们将在后面看到。
我们可以使用JSON_EXTRACT函数从 JSON 字段中提取数据。基本语法是:
**JSON_EXTRACT(json_doc, path)**
对于 JSON 数组,路径用$[index]指定,其中索引从 0 开始:
mysql> **SELECT JSON_EXTRACT('[10, 20, 30, 40]', '$[0]')**;
+------------------------------------------+
| JSON_EXTRACT('[10, 20, 30, 40]', '$[0]') |
+------------------------------------------+
| 10 |
+------------------------------------------+
对于 JSON 对象,路径用$.key指定,其中key是对象的一个键。
mysql> **SELECT JSON_EXTRACT('{"name": "John", "age": 30}', '$.name')**;
+-------------------------------------------------------+
| JSON_EXTRACT('{"name": "John", "age": 30}', '$.name') |
+-------------------------------------------------------+
| "John" |
+-------------------------------------------------------+
如果上面使用的JSON_EXTRACT只有两个参数,我们可以使用->操作符,它是JSON_EXTRACT的别名。为了演示这个操作符的用法,我们需要一个带有 JSON 字段的表。请复制以下 SQL 查询,并在 MySQL 控制台或 DBeaver 中执行它们:
特别是,MySQL 使用utf8mb4字符集和utf8mb4_bin排序规则处理 JSON 上下文中使用的字符串。一个字符集是一组符号和编码,一个校对是一组比较字符集中字符的规则。最好使用相应的字符集和排序规则创建带有 JSON 字段的表。
因为utf8mb4_bin是二进制排序规则,所以键是区分大小写的,我们需要用正确的大小写来指定它们:
现在我们可以使用->操作符从 JSON 字段中提取数据:
正如我们所见,->只是JSON_EXTRACT的一个快捷方式或别名。
有趣的是,test_name和test_id的引号仍然存在。这不是我们想要的。我们希望引号被删除,类似于name字段。
要删除提取值的引号,我们需要使用JSON_UNQUOTE函数。由于JSON_UNQUOTE(JSON_EXTRACT(…))如此常用,该组合也有一个快捷操作符,即->>。让我们在实践中看看:
证明->>和JSON_UNQUOTE(JSON_EXTRACT(...))结果相同。由于->>很少打字,所以在大多数情况下它是首选。
但是,如果想从嵌套的 JSON 数组或 JSON 对象中提取数据,就不能使用 chained ->或->>。你只能使用->和->>作为顶层,需要使用JSON_EXTRACT作为嵌套层。让我们提取每个学生的分数:
干杯!它像预期的那样工作。
从 MySQL 的 JSON 字段中提取数据的关键要点:
- 使用
$.key从 JSON 对象中提取键值。 - 使用
$[index]从 JSON 数组中提取元素的值。 - 如果值不是字符串,使用
->作为JSON_EXTRACT的快捷方式。 - 如果值是一个字符串,并且您想要删除提取的字符串的引号,使用
->>作为JSON_UNQUOTE(JSON_EXTRACT(...))的快捷方式。 - 如果要从嵌套的 JSON 数组或 JSON 对象中提取数据,就不能使用链式
->或->>。您只能对顶层使用->和->>,需要对嵌套层使用JSON_EXTRACT。
在 MySQL 中有很多其他的函数用于处理 JSON 数据。但是,如果您需要使用这些函数来验证/搜索您的 JSON 字段或对其执行 CRUD 操作,您应该认真考虑使用 MongoDB 来存储 JSON 字段。MongoDB 在处理非结构化数据(文档)方面要专业和方便得多。
上面我们介绍了如何从 MySQL 的 JSON 字段中提取值。现在我们将学习相反的内容,并探索如何从 MySQL 表中选择 JSON 数据。为了继续这一部分,我们需要一些虚拟数据来玩。请复制以下 SQL 查询,并在 MySQL 控制台或 DBeaver 中运行它们:
对于该表,使用默认字符和校对。通过这两个查询,我们创建了一个存储从第一部分提取的数据的表。这是数据管道和分析的常见任务,即在数据清理之后执行一些数据分析。实际上,您可能希望将分数存储在一个单独的表中,这样这些表就更加规范化。然而,为了简化演示,这里将数据放在同一个表中。
我们现在可以用JSON_ARRARYAGG函数将数据聚集到一个 JSON 数组中:
我们还可以使用JSON_OBJECTAGG函数将数据聚合到一个 JSON 对象中:
然后,可以在您的应用程序中直接使用聚合的数据。JSON_ARRARYAGG和JSON_OBJECTAGG可以节省您在应用程序中聚集数据的努力,有时非常方便。例如,您可以使用json.loads()方法将 JSON 字符串转换成 Python 中的数组或字典。
如果需要在 Python 中执行JSON_ARRARYAGG和JSON_OBJECTAGG的普通 SQL 查询,可以使用 SQLAlchemy 包,如本文中的所示。
在本文中,我们介绍了如何在 MySQL 中处理 JSON 数据。在第一部分中,通过简单的例子讨论了用于从 JSON 字段中提取数据的函数和操作符。在第二部分中,我们反其道而行之,将规范化数据聚合到 JSON 数组或对象中,然后可以直接在您的程序中使用。通常我们应该避免在 MySQL 中存储非结构化数据(文档)。但是,如果无法避免,本文中的知识应该对你的工作有所帮助。
相关文章:
如何在 Python 中执行 JSON 转换、序列化和比较
原文:https://towardsdatascience.com/how-to-work-with-json-in-python-bdfaa3074ee4
通过简单的例子学习基本的 JSON 操作

JSON(JavaScript Object Notation)是一种 text 格式,它与语言无关,通常用于不同应用程序之间的数据交换。一个很好的例子是,来自 API 的响应通常是 JSON 格式的,因此后端和前端可以自由地交换数据,而不需要知道彼此的技术细节。在这篇文章中,我们将介绍 Python 中 JSON 的常见用例,Python 是一种用于后端开发和数据工程/分析的流行语言。
JSON 和字典
首先,我们应该知道 JSON 是一种字符串格式。因此,它不同于 Python 中的字典数据类型。JSON 字符串可以用任何现代编程语言解析成相应的数据。通常,JSON 字符串可以被解析成两种数据类型,即 object 和 array。对象是一组无序的键/值对,对应于 Python 中的字典数据类型,而数组是值的有序集合,对应于 Python 中的列表数据类型
JSON 字符串和数据之间的转换
如上所述,在所有现代编程语言中,JSON 字符串可以被解析为对象或数组,反之亦然。在 Python 中,json库可以用于这种类型的转换。我们使用loads函数将 JSON 字符串转换成对象或数组,并使用dumps函数执行相反的转换。注意loads中的s和dumps代表 s tring,这意味着它们在 JSON 字符串上工作。如果没有指定s,那么这些函数将会处理 JSON 文件,这将在后面介绍。
下面这段代码演示了 JSON 字符串和对象/数组之间的常见转换。
有趣的是,当我们将数组转储回 JSON 时,结果与原始结果不同。如果你仔细检查,你会发现细微的差别。当我们不指定分隔符时,将在项目分隔符后面添加一个空格,默认情况下是逗号。我们可以指定一个自定义分隔符来使结果相同。请注意,我们需要指定项目分隔符和键分隔符,即使我们只想更改其中之一:
实际上,separators参数更常用于定制 JSON 对象的表示。我们可以使用不同的分隔符使转储的字符串更紧凑或更易读:
indent参数用于在每个键之前插入一些空格,以提高可读性。而sort_keys参数用于按字母顺序对键进行排序。
为无法序列化的值添加自定义序列化程序
在上面的例子中,目标字典(dict_from_json)的所有值都可以序列化。实际上,有些值是无法序列化的,尤其是Decimal和date / datetime类型:
在这种情况下,我们需要创建一个定制的序列化函数,并将其设置为default参数:
请注意,在自定义序列化程序中,我们在 f 字符串中使用[!r](https://lynn-kwong.medium.com/special-python-string-formatting-in-logging-and-pint-unit-conversion-fddb51f3d03a) 来显示值的表示,这对于调试来说非常方便。如果取消对其中一个if/elif条件的注释,并再次运行json.dumps命令,您将看到相应的错误:
比较两个 JSONs 的区别
有时候我们需要比较两个 JSON 对象的区别。例如,我们可以检查和比较一些可以导出为 JSON 的表的模式,如果一些重要表的模式发生了变化,就会发出一些警报。
[jsondiff](https://github.com/xlwings/jsondiff) 库可用于比较 Python 中两个 JSON 对象之间的差异:
如果我们想要控制结果应该如何显示,我们可以使用syntax、marshal和dump参数来自定义结果。
我们可以使用syntax字段来指定值和动作应该如何显示。
我们可以使用load参数从 JSON 字符串中加载数据,并类似地使用dump参数将结果转储到一个 JSON 字符串中,该字符串可以直接写入一个文件,这将很快介绍。
读写 JSON
我们可以用json.dump函数将一个 JSON 字符串写到一个文件中。注意,函数名中没有s。带有s ( json.dumps)的那个是用来处理字符串的,不是文件。JSON 文件只是一个纯文本文件,默认扩展名是.json。让我们将由jsondiff返回的两个模式之间的差异写到一个名为schema_diff.json的文件中:
将创建一个名为schema_diff.json的文件,该文件将包含变量result中的 JSON 字符串。如果字典/列表的值包含不可序列化的数据,我们需要用序列化函数指定default参数,如本文开头所示。
最后,我们可以使用json.load函数从 JSON 文件中加载数据:
在这篇文章中,通过简单的例子介绍了 JSON 的基础知识以及如何在 Python 中使用它。我们已经学习了如何从字符串或文件中读写 JSON 对象。此外,我们现在知道如何为 JSON 对象编写定制的序列化程序,这些对象包含默认序列化程序无法序列化的数据。最后,我们可以使用jsondiff库来比较两个 JSON 对象之间的差异,这对于数据监控来说非常方便。
相关文章:
如何编写定制的 Keras 模型,以便可以部署它来提供服务
如何将定制层、建模、丢失、预处理、后处理改编成可服务的 API
如果您编写的唯一 Keras 模型是带有预建层(如 Dense 和 Conv2D)的顺序模型或功能模型,则可以忽略本文。但是在你 ML 生涯的某个时候,你会发现你是在子类化一个层或者一个模型。或者自己编写损失函数,或者在服务过程中需要自定义预处理或后处理。在这一点上,你会发现你不能很容易地使用
model.save(EXPORT_PATH)
即使您能够成功保存模型,您可能会发现模型无法成功部署到 TensorFlow 服务或包装 TF 服务的 Vertex AI 或 Sagemaker 等受管服务中。即使您成功地部署了它,您可能会发现结果不直观或者完全错误。
不幸的是,解释您必须做什么的文档分散在多个页面中。一些推荐的方法将会工作,如果你做的一切都是正确的,但不会报告错误,一些方法会导致训练中的戏剧性减速,另一些方法在服务中不灵活,一些方法会导致模型保存花费几个小时,并且通常错误消息可能很难理解。

这篇文章是关于如果你在 Keras 中有一个自定义的东西(层,模型,Lambda,Loss,预处理器,后处理器)你必须做什么。
示例模型
为了说明,我将使用 Keras 示例中的命名实体识别 (NER)模型。基本上,一个被训练来识别名字和位置的 NER 会使用以下形式的句子:
约翰去了巴黎
并返回:
说出位置
这个模型本身如何工作并不重要。只是它涉及自定义 Keras 层和自定义 Keras 模型(即,它们涉及子类层。层和 keras。型号):
class TransformerBlock(layers.Layer):
...class TokenAndPositionEmbedding(layers.Layer):
...class NERModel(keras.Model):
...
本文附带的完整代码在 GitHub 上。
这种 NLP 模型必须对输入文本进行一些定制的预处理。基本上,输入的句子被拆分成单词,小写,然后转换成基于词汇表的索引。该模型在词汇 id 上被训练:
def map_record_to_training_data(record):
record = tf.strings.split(record, sep="\t") tokens = tf.strings.split(record[0])
tokens = tf.strings.lower(tokens)
tokens = vocab_lookup_layer(tokens) tags = tf.strings.split(record[1])
tags = label_lookup_layer(tags)
return tokens, tags
输入 tf.data 管道是:
train_dataset = (
tf.data.TextLineDataset('train.csv')
.map(map_record_to_training_data)
.padded_batch(batch_size)
)
并且用定制损失来训练模型:
loss = CustomNonPaddingTokenLoss()
ner_model.compile(optimizer="adam", loss=loss)
ner_model.fit(train_dataset, epochs=10)
你可以明白我为什么选择这个例子——它有自定义一切。
出口
当我们试图导出这个模型来部署它时会发生什么?通常,这是它将涉及的内容:
model.save(EXPORT_PATH)
然后,我们将保存的模型交给 Sagemaker 或 Vertex AI 等服务,它会:
model = saved_model.load_model(EXPORT_PATH)
model.predict(...)
不幸的是,由于上面所有的自定义层和代码,这种 stratightforward 方法行不通。
当我们这样做时:
ner_model.save(EXPORT_PATH)
我们得到一个错误:
Unknown loss function: CustomNonPaddingTokenLoss. Please ensure this object is passed to the `custom_objects` argument. See https://www.tensorflow.org/guide/keras/save_and_serialize#registering_the_custom_object for details.
问题 1:未知损失函数
我们如何解决这个问题?我将在本笔记本的后面向您展示如何注册自定义对象。但是首先要意识到的是我们不需要导出损失。损失只是训练需要,部署不需要。
所以,我们可以做一件更简单的事情。只是消除损失:
# remove the custom loss before saving.
ner_model.compile('adam', loss=None)
ner_model.save(EXPORT_PATH)
成功!一句话:在导出 Keras 模型进行部署之前,消除定制损失。这是一行程序。
问题 2:错误的输入形状
现在,让我们做 TensorFlow Serving(或包装 TensorFlow Serving 的托管服务,如 Sagemaker 或 Keras)所做的事情:调用我们刚刚加载的模型的 predict()方法:
sample_input = [
"Justin Trudeau went to New Delhi India",
"Vladimir Putin was chased out of Kyiv Ukraine"
]
model.predict(sample_input)
不幸的是,我们得到一个错误:
Could not find matching concrete function to call loaded from the SavedModel. Got:
* Tensor("inputs:0", shape=(None,), dtype=**string**)Expected:
* TensorSpec(shape=(None, None), dtype=tf.**int64**, name='inputs')
捕捉预处理
这是因为我们试图发送一个完整的句子(一个字符串),但我们的模型是在一组词汇 id 上训练的。这就是为什么期望的输入是一组 int64。
当我们调用 tf.strings.split()、tf.strings.lower 和 vocab_lookup_layer()时,我们在 tf.data()管道中做到了这一点:
def map_record_to_training_data(record):
record = tf.strings.split(record, sep="\t") tokens = tf.strings.split(record[0])
tokens = tf.strings.lower(tokens)
tokens = vocab_lookup_layer(tokens)...
在预测过程中,我们也必须重复这种预处理。
怎么会?
嗯,我们可以在顶点 AI 上使用一个预处理容器或者一些类似的功能。但是这有点违背了我们拥有一个简单的、全方位部署的 Keras 模型的目的。
相反,我们应该重组我们的 tf.data 输入管道。我们想要的是有一个函数(在这里,我称之为 process_descr ),我们可以从 tf.data 管道和我们导出的模型中调用它:
def process_descr(descr):
# split the string on spaces, and make it a rectangular tensor
tokens = tf.strings.split(tf.strings.lower(descr))
tokens = vocab_lookup_layer(tokens)
max_len = MAX_LEN # max([x.shape[0] for x in tokens])
input_words = tokens.to_tensor(default_value=0, shape=[tf.rank(tokens), max_len])
return input_words
这使得我们的训练代码可以像以前一样工作。当我们准备好保存它时,我们需要用模型中包含的预处理代码创建一个层。
另一种方法是定义一个调用预处理函数的预测签名。然而,这是有问题的,因为如果您的定制模型中有错误,您将不会知道这些错误(问我我是如何知道的)。
带预处理层的新模型
一个更简单的方法是,告诉你错误并给你一个机会去修正它们,就像我们对损失函数所做的那样。定义一个新的标准模型,它有一个 lambda 层,在将它提供给定制模型之前进行预处理,并将其写出。
temp_model = tf.keras.Sequential([
tf.keras.Input(shape=[], dtype=tf.string, name='description'),
tf.keras.layers.Lambda(process_descr),
ner_model
])
temp_model.compile('adam', loss=None)
temp_model.save(EXPORT_PATH)
!ls -l {EXPORT_PATH}
不幸的是,前面提到的未报告的错误现在出现了。什么错误?
问题 3:未追踪张量
我们得到的错误消息是:
Tried to export a function which references 'untracked' resource Tensor
这是怎么回事?这里的问题是:当你写一个自定义的 Keras 层或 Keras 损失或 Keras 模型,你是在定义代码。但是,当您导出模型时,您必须用它创建一个平面文件。代码会发生什么变化?丢了!那么预测是如何进行的呢?
你需要告诉 Keras 如何传入所有的构造函数参数等等。然后,Keras 将处理代码,恢复对象,并做正确的事情。
方法是定义一个 getConfig()方法,该方法包含所有的构造函数参数。基本上,自定义图层如下所示:
class TokenAndPositionEmbedding(layers.Layer):
def __init__(self, maxlen, vocab_size, embed_dim):
super(TokenAndPositionEmbedding, self).__init__()
self.token_emb = keras.layers.Embedding(
input_dim=vocab_size, output_dim=embed_dim
)
self.pos_emb = keras.layers.Embedding(input_dim=maxlen, output_dim=embed_dim)
def call(self, inputs):
maxlen = tf.shape(inputs)[-1]
positions = tf.range(start=0, limit=maxlen, delta=1)
position_embeddings = self.pos_emb(positions)
token_embeddings = self.token_emb(inputs)
return token_embeddings + position_embeddings
必须是这样的:
**@tf.keras.utils.register_keras_serializable() # 1**
class TokenAndPositionEmbedding(layers.Layer):
def __init__(self, maxlen, vocab_size, embed_dim, ****kwargs): # 2**
super(TokenAndPositionEmbedding, self).__init__(****kwargs) # 3**
self.token_emb = keras.layers.Embedding(
input_dim=vocab_size, output_dim=embed_dim
)
**#4 save the constructor parameters for get_config()
self.maxlen = maxlen
self.vocab_size = vocab_size
self.embed_dim = embed_dim**
self.pos_emb = keras.layers.Embedding(input_dim=maxlen, output_dim=embed_dim)
def call(self, inputs):
maxlen = tf.shape(inputs)[-1]
positions = tf.range(start=0, limit=maxlen, delta=1)
position_embeddings = self.pos_emb(positions)
token_embeddings = self.token_emb(inputs)
return token_embeddings + position_embeddings
** def get_config(self): # 5
config = super().get_config()
# save constructor args
config['maxlen'] = self.maxlen
config['vocab_size'] = self.vocab_size
config['embed_dim'] = self.embed_dim
return config**
有 5 个变化:
- 添加注记以向 Keras 注册自定义图层
- 将**kwargs 添加到构造函数参数中
- 向超级构造函数添加一个**kwargs
- 将构造函数参数保存为实例字段
- 定义一个保存构造函数参数的 get_config 方法
一旦我们对我们的自定义层和模型类这样做了(查看 GitHub 中的笔记本以获得完整代码),我们仍然无法保存。错误消息与之前完全相同。我们有一个未追踪的张量。但是我们只是完成了所有的定制课程,做了正确的事情。发生什么事了?
问题 Lambda 层中未跟踪的资源
我们还有一个问题。即使我们检查并修复了所有的定制层和模型,仍然有一段用户定义的代码。
我们用于预处理的 Lamda 层!它使用词汇表,并且 vocab_lookup_layer 是一个未被跟踪的资源:
def process_descr(descr):
# split the string on spaces, and make it a rectangular tensor
tokens = tf.strings.split(tf.strings.lower(descr))
tokens = **vocab_lookup_layer**(tokens)
max_len = MAX_LEN # max([x.shape[0] for x in tokens])
input_words = tokens.to_tensor(default_value=0, shape=[tf.rank(tokens), max_len])
return input_words
仅仅修改函数并不能解决这个问题。
一句话:Lambda 层是危险的,很难意识到我们忘记了什么资源。
我建议你去掉所有的 Lambda 图层,用自定义图层来代替。
让我们这样做吧,确保记住每个自定义层都需要的 5 个步骤:
[@tf](http://twitter.com/tf).keras.utils.register_keras_serializable(name='descr')
class PreprocLayer(layers.Layer):
def __init__(self, vocab_lookup_layer, **kwargs):
super(PreprocLayer, self).__init__(**kwargs) # save the constructor parameters for get_config() to work properly
** self.vocab_lookup_layer = vocab_lookup_layer** def call(self, descr, training=False):
# split the string on spaces, and make it a rectangular tensor
tokens = tf.strings.split(tf.strings.lower(descr))
tokens = **self.vocab_lookup_layer(tokens)**
max_len = MAX_LEN # max([x.shape[0] for x in tokens])
input_words = tokens.to_tensor(default_value=0, shape=[tf.rank(tokens), max_len])
return input_words def get_config(self):
config = super().get_config()
# save constructor args
**config['vocab_lookup_layer'] = self.vocab_lookup_layer**
return config
现在,我们的临时储蓄模型变成了:
temp_model = tf.keras.Sequential([
tf.keras.Input(shape=[], dtype=tf.string, name='description'),
PreprocLayer(vocab_lookup_layer),
ner_model
])
temp_model.compile('adam', loss=None)
temp_model.save(EXPORT_PATH)
!ls -l {EXPORT_PATH}
当然,您应该返回并修复您的 tf.data 输入管道,以使用层而不是 process_descr 函数。幸运的是,这很容易。只需将 process_descr()调用替换为对 PreprocLayer()的调用:
PreprocLayer(vocab_lookup_layer)(['Joe Biden visited Paris'])
做的事情和:
process_descr(['Joe Biden visited Paris'])
后加工
现在,当我们加载模型并调用 predict 时,我们得到了正确的行为:
model = tf.keras.models.load_model(EXPORT_PATH)
sample_input = [
"Justin Trudeau went to New Delhi India",
"Vladimir Putin was chased out of Kyiv Ukraine"
]
model.predict(sample_input)
不过,这会返回一组概率:
array([[[7.6006036e-03, 4.3546227e-03, 9.7820580e-01, 1.3501652e-03,
5.0268644e-03, 3.4619651e-03],
[6.8284925e-03, 1.7240658e-02, 9.1373536e-04, 9.6674633e-01,
5.9596724e-03, 2.3111277e-03],
真烦人。我们能对输出进行后处理吗?当然可以。我们必须找到这个数组的 argmax,然后查找对应于这个索引的标签。例如,如果第二项是最大概率,我们将得到 B-NAME 映射[1]。
既然我们在 Keras down 中有了自定义代码,那么应用自定义层方法就很简单了:
[@tf](http://twitter.com/tf).keras.utils.register_keras_serializable(name='tagname')
class OutputTagLayer(layers.Layer):
def __init__(self, mapping, **kwargs):
super(OutputTagLayer, self).__init__(**kwargs) # save the constructor parameters for get_config() to work properly
self.mapping = mapping # construct
self.mapping_lookup = tf.lookup.StaticHashTable(
tf.lookup.KeyValueTensorInitializer(
tf.range(start=0, limit=len(mapping.values()), delta=1, dtype=tf.int64),
tf.constant(list(mapping.values()))),
default_value='[PAD]') def call(self, descr_tags, training=False):
prediction = tf.argmax(descr_tags, axis=-1)
prediction = self.mapping_lookup.lookup(prediction)
return prediction def get_config(self):
config = super().get_config()
# save constructor args
config['mapping'] = self.mapping
return config
但是那个代码呢?那是什么@#\(@R\)@#啊?
在 Python 中,我们可以简单地做:
mapping[ np.argmax(prob) ]
我们必须进行映射。查找:
prediction = tf.argmax(descr_tags, axis=-1)
prediction = self.mapping_lookup.lookup(prediction)
mapping_lookup 本身就是一种资源,它是一个与 dict:
self.mapping_lookup = tf.lookup.StaticHashTable(
tf.lookup.KeyValueTensorInitializer(
tf.range(start=0, limit=len(mapping.values()), delta=1, dtype=tf.int64),
tf.constant(list(mapping.values()))),
default_value='[PAD]')
在撰写本文时,TensorFlow 在存储关键字为整数的 dicts 时有一个 bug,所以我正在入侵对 tf.range()的调用。抱歉。
但是这样做的好处是,您可以简单地采用这个模型并部署它。没有额外的预处理容器、后处理容器等。代码全部运行在 GPU 上(加速了!)而且速度超快。
也很直观。我们请求一个字符串并返回识别出的单词。发送内容:
model = tf.keras.models.load_model(EXPORT_PATH)
sample_input = [
"Justin Trudeau went to New Delhi India",
"Vladimir Putin was chased out of Kyiv Ukraine"
]
model.predict(sample_input)
回馈:
array([[b'B-NAME', b'I-NAME', b'OUT', b'OUT', b'B-LOCATION',
b'I-LOCATION', b'I-LOCATION', b'[PAD]', b'[PAD]', b'[PAD]',
b'[PAD]', b'[PAD]', b'[PAD]', b'[PAD]', b'[PAD]', b'[PAD]'],
[b'B-NAME', b'I-NAME', b'OUT', b'OUT', b'OUT', b'OUT',
b'B-LOCATION', b'I-LOCATION', b'[PAD]', b'[PAD]', b'[PAD]',
b'[PAD]', b'[PAD]', b'[PAD]', b'[PAD]', b'[PAD]']], dtype=object)
即所提供的句子中每个单词的标签。
尽情享受吧!
推荐阅读:
- 我的完整代码在 GitHub 上。为了可读性,我省略了本文中的一些细节。请务必查阅笔记本。您可以在 Colab 或任何 Jupyter 环境中运行它。
- Keras 示例中的命名实体识别 (NER)模型。很好的说明模型。但是您将无法部署它。
- 点击此处阅读关于 Keras 定制图层的所有信息。不要错过关于序列化的“可选”部分。如果您想要部署自定义层,这是强制性的。
- 一旦你写了你的自定义层,你必须做自定义对象注册。但是注册定制对象很容易出错。您可能会忘记一些,错误消息不会告诉您错过了哪一个。最好使用标注快捷方式。希望你看到了——这是那一页上讨论的第三个选项。
- 在这里阅读关于λ层的内容。没有任何关于导出带有 Lambda 层的模型的陷阱,这些 Lambda 层中有全局对象。希望您阅读了前面关于序列化模型的章节,并意识到它们同样适用于 Lambda 层包装的函数!
抱歉,打扰了。
如何用 Python 写一个电报机器人
原文:https://towardsdatascience.com/how-to-write-a-丨t丨e丨l丨e丨g丨r丨a丨m丨s丨-bot-with-python-8c08099057a8
没有比这更容易的了

Python + 丨t丨e丨l丨e丨g丨r丨a丨m丨s丨 =一个胜利的组合!作者图片
我 不记得上一次遇到一个使用零消息服务的人。一些人使用 WhatsApp 或 Signal,一些人使用 Twitter DMs 或 LinkedIn messages,其他人再次使用 Instagram 或抖音。
我的几个朋友没有社交媒体,但即使是他们也使用老式短信。信息传递很重要。
我的设备上有几个信使,但我最喜欢的肯定是电报。它很好用,你可以参加大型聊天小组,隐私得到很好的保证,你可以关注人们的公共频道,最棒的是,它对 gif 和愚蠢的噱头有令人难以置信的支持。
丨t丨e丨l丨e丨g丨r丨a丨m丨s丨 基本上类似于 WhatsApp,但在开发方面领先两年。它是独立的,没有像 Meta 这样的大型科技巨头的氛围。
我想造电报机器人的原因,简单来说就是乌克兰战争。在东欧,电报是使用最多的信使(这些家伙品味不错!),我是志愿者和非政府组织网络的一员,他们帮助受战争影响的平民获得食物、医疗用品和心理支持等基本物资。
电报机器人将成为受影响个人的生命线,并帮助我们简化工作,以便他们需要的东西尽快到达。
在战争开始时,我们已经建立了一个非常基本的电报机器人,它与后端客户支持系统集成在一起。这意味着我们团队的所有成员都能够访问与机器人的聊天,并努力让人们得到他们需要的东西。
在早期,这已经足够好了,因为每个人的情况都是独特而复杂的,需要人的直接参与。事实上,它不是一个真正的机器人,它是一种人类客户支持系统。
随着战争的持续,人们的要求越来越相似。许多人找到了一个安全的住处,但需要经济援助来购买食物或修缮房屋。这些不再是复杂的决定,而是相对简单的行动过程。
于是,我心中的工程师觉醒了,他说:让我们自动化吧!
如何在 丨t丨e丨l丨e丨g丨r丨a丨m丨s丨 中创建机器人
首先,你需要联系电报员。如果你有电报账号,只需搜索 BotFather,按/start。这是你将会看到的:

作者截图。
然后,你猜对了,用/newbot创建一个新的机器人。机器人父亲会询问这个机器人的名字(必须以“bot”结尾),并给你一个机器人的令牌。
这个令牌是由字母和数字组成的序列,你有责任保护它的安全。任何拥有令牌的人都可以控制这个机器人,所以让它远离窥探的眼睛。
然后,通过/setdescription,用户可以设置在用户按下/start之前出现的描述。它可能包含如何使用机器人的基本说明。
使用/setabouttext,用户可以更改出现在机器人页面上的文本。就像你的电报状态线。与/setuserpic一个改变个人资料照片。
这是结果看起来像什么(对不起,低劣的机器人名称;是原型!):


作者截图。
一旦这样做了,你就可以使用 Python 并研究机器人的内部逻辑了!
Python 如何升级电报机器人
有几个 Python 包让编写电报机器人变得非常简单。也有其他语言的软件包。但是 Python 是我的日常编程语言,所以我决定坚持使用它。
基本设置
我选择 AIOGram 包只是因为似乎有足够的教程来帮助我开始使用它。其他软件包可能更复杂,但我想要一些足够简单的东西,我最多在几个下午就能完成一些基本代码。
在创建了一个新的 Python 文件之后,我用我需要的 AIOGram 片段和一些关于机器人的基本信息填充了文件头:
首先,出于安全原因,我已经更改了这个文件中的 bot 令牌。我不会把我对机器人的控制权交给整个互联网😯
来自 AIOGram 的部分是帮助与电报机器人 API 交互的Bot,处理所有消息的Dispatcher,确保定期检查消息状态的executor,以及我们可能需要控制用户体验的types对象。
在这些types中,我们需要为用户的响应添加自定义键盘的ReplyKeyboardMarkup,以及指定哪些按钮将进入自定义键盘的KeyboardButton。
原则上,AIOGram 有更多的部件,以及更复杂的键盘。还有一个内部内存处理器,它应该比我用来保存用户响应的answers = []数组更有效。
但是我想快速地写一些东西,我们的机器人不会被每天成千上万的请求轰炸。这就是我现在要做的。
内在逻辑
许多使用机器人的人更喜欢用乌克兰语交谈。所以我们的机器人会问的第一个问题是他们更喜欢哪种语言。用户可以选择一种语言,如下所示:

作者截图。
在我的代码中,我需要创建上面显示的自定义键盘,并且我需要告诉机器人在用户一按下/start就询问语言。看起来是这样的:
你可以看到不同的键盘按钮,以及它们是如何在ReplyKeyboardMarkup中组合在一起的。选项resize_keyboard=True确保它总是看起来很好,选项one_time_keyboard=True确保一旦不再需要它就消失。
人们可以将命令.add(lang1).add(lang2).add(lang3)链接起来,使按钮一个接一个地出现,或者可以编写.add(lang1,lang2,lang3)使按钮一个接一个地出现。
Bot 消息是通过使用函数装饰器中的message_handler来启动的。人们可以回复命令或特定的消息。
我还添加了一条help消息,它对命令/help做出反应,以防用户不确定他们在做什么或者意外地开始使用机器人。
如果用户在语言提示后选择了英语,我们接下来会询问他们需要什么。因此,我们需要一个新的键盘,我们需要一个对English答案做出反应的消息处理器。看起来是这样的:
当然,如果用户选择乌克兰语作为语言,也会发生同样的情况——对话将只使用乌克兰语。
键盘按钮中的傻表情符号不是一个噱头。在测试期间,我很快意识到这个问题你需要什么?出现在用户可能在回答中提到英语的任何时候,无论是现在还是以后。在最坏的情况下,这将使机器人循环运行并迷惑用户,这显然不是我想要的。
我认为用户回复英语是不太可能的👍在任何回答时他们都在键入自由文本,所以这就是输入键盘按钮的内容,而regexp会触发问题你需要什么?
所有其他键盘按钮和触发问题同上。
当然有更好的解决方案,但是我想要一个快速而有效的版本。
从这个问题开始,我还将所有回答存储在一个名为answers的数组中,因为有时机器人需要组合用户提供的不同信息,以便将他们引导到满足他们需求的正确的非政府组织。
逻辑是这样的(整个脚本大约有 600 行长),但是我会把剩下的留给你。
部署机器人
现在,我们需要做的就是运行脚本。这和进入一样容易
python3 lgbtukraineshitbot.py
当然是在你的终端里,同时把我的文件名换成你的。
只有一个问题。我的电脑很旧,并不总是有一个最佳的响应时间,我在晚上关机。这对于应该全天候可用的应急机器人来说并不理想。
所以我把这个机器人部署在云服务上。我选择了 Heroku,因为那里也是我们处理后端软件的地方。但是你可以使用任何你喜欢的云服务。
在 Heroku 中,我需要创建一个新项目,添加 Python 脚本,将 buildpack 设置为 Python,并添加上面的代码行,以便 Heroku 知道如何运行脚本。我不会详细说明这一点,因为不同的云服务会有所不同,而且他们的文档通常都很好。
我使用的是 Heroku 免费版的有限功能,因为它还是一个原型。好奇的读者可能已经注意到了,我的机器人仍然没有一直响应,但这比在我的笔记本电脑上运行要好。
一旦我们将这些变化部署到人们正在使用的主要机器人上,并将其与我们的后端系统集成,我们将有更少的工作要做——脚本将完成所有多余的任务,当一个人的情况复杂到有必要时,我们可以进行干预。而且机器人会反应更快,因为不像人类,它不需要睡觉和休息。
吹牛
许多人使用信使,但没有多少人知道如何在需要时自动对话。
这是软件开发者享有的众多特权之一。我们像其他人一样与软件互动,但我们也对它有足够深入的了解,可以在它的基础上进行构建。
对于我们为应对战争而构建的 bot,我希望它能为我们的团队节省大量时间,并加速它为我们的用户获得帮助所需的时间。
它不是最复杂的聊天机器人,但它能完成任务。这才是最重要的。
当我有更多的时间时,我可能会为世界上的其他地区开发类似这个机器人的东西。乌克兰不是唯一陷入严重困境的国家;我想到了巴基斯坦、阿富汗和埃塞俄比亚等许多国家。
愿未来是快速的、自动化的、有益的。
成为 中等会员 对我的内容进行完全访问。
如何用 Python 写出更好的科学代码
原文:https://towardsdatascience.com/how-to-write-better-scientific-code-in-python-9349adc7c7e2
关于如何使用 python 和 numpy 编写更好的数据科学代码的一些技巧。我们用一个定制的案例来展示好的模式。

晚上拍照取乐,波兰 2021。(作者供图)。
介绍
任何科学工作的很大一部分在于编写代码。无论是典型的机器学习建模、分析,还是参与数据项目,都有很大一部分时间用于构建新功能的原型。由于是探索性的,预计计划的几个部分将被替换或修改,经常超出最初的计划。
与“消费者软件”不同,变更通常不是由客户的需求引起的,而是由过程的结果引起的。由于这个原因,如果实验证据表明有不同的路径,那么以不需要“完全重建”的方式来设计它是非常有价值的。
编写科学代码伴随着两个(额外的)特殊挑战:第一个与数学错误有关。计算中的错误通常很难追踪,尤其是当代码在语义上是正确的时候。没有发现错误。不会引发任何异常。一切看起来都不错,但(数字)结果是错的。特别是,在实现概率模型时,结果有时可能看起来不错,这取决于一些初始条件或随机因素。
第二个来自前面描述的事实。总会有实验部分,而且一直在变。所以关键是要设计好每一个组件,这样大部分工作才能留下来,作为下一阶段开发的坚如磐石的基础。
在本文中,我们将重点放在可以使代码更健壮、更容易理解、总体上更容易处理的模式上。您将看到简单的改进是如何导致更多的可重用代码和更少的错误的。
玩具任务
为了便于演示,我们的任务是计算随机过程结果的期望值。数学上,它归结为这个公式:

其中是概率质量函数(PMC),或者对于连续变量:

其中是概率密度函数(PDF)。
正如你可能想知道的,这里的挑战是你想让它与任何分布一起工作:连续的或离散的。或者如果不合理,您希望至少认识到问题的本质,这样您就可以顺利地修改代码。
错误代码——起点
让我们从一个不太好的代码开始。说你想掷六面骰子。由于每个结果的概率相等,这归结为计算采样结果的平均值。
import random
import statistics
def die(sides = 6):
return random.randint(1, sides)
def expected_value(n_samples):
samples = [die() for _ in range(n_samples)]
return statistics.mean(samples)
这个代码有什么问题?几件事…
首先,die函数一次返回一个样本。需要调用N次才能得到N样本,速度很慢。
其次,expected_value函数强烈依赖于产生样本的die函数。很明显,一旦你考虑使用不同的模具,比如 12 面的。使用这种设计,您需要“打开”T2 来接受一个额外的参数 T3,只需将它传递给 T4 来扩展它,使其适用于更一般的情况。虽然这可以工作,但它使expected_value的接口违反直觉,但解决方案仍然依赖于使用die作为样本源,因此很难考虑其他分布。
补救措施…
让我们考虑一下您的选择:
想法一
您可以使用外部变量来存储样本:
def expected_value(samples):
return statistics.mean(samples)
samples = [die(12) for _ in range(n_samples)]
ev = expected_value(samples)
这是显而易见的,但是你只是把问题移到了别处…现在,samples成为了一个新的实体,存储数据(甚至是非常大的数据),并且它是相当匿名的。expected_value函数期望接收它,但是准备它是你的责任。
想法二
另一种方法是通过将die作为对象传递给expected_value来将它保存在内部。
from functools import partial
twelve_sided_die = partial(die, 12)
def expected_values(die, n_samples):
samples = [die() for _ in range(n_samples)]
return statistics.mean(samples)
ev = expected_values(twelve_sided_die, 10000)
该想法使用准备好的die的“版本”,并让expected_value将其用作样本源。然而,一个新的问题出现了:expected_value只与die兼容。它不能用任何其他“样本生成器”计算结果,或者至少不能保证它能正确地这样做。
想法三
第三个想法是在更抽象的层面上认识问题,设计更好的界面。
在抽象层次上,我们有两个概念:
- 存在一个概率分布,我们可以从中取样。(可以是骰子、硬币、正态分布——无所谓)。
- 有一个数学运算消耗和转换数据。(例如,计算平均值、方差等。).
让我们更加关注如何构建正确的抽象以及如何控制它们的相互兼容性。
分布(数据)
数学上,概率分布可以是函数——连续的或离散的,有限的或无限的,从中我们可以抽取样本。根据问题的不同,该功能的“处方”可能会有很大不同。我们可以使用“现有”公式,如高斯或泊松分布,但它也可以是从直方图等导出的“自定义”公式。
考虑到这一点,让我们建立以下抽象:
from abc import ABC, abstractmethod
class Distribution(ABC):
@abstractmethod
def sample(self):
...
履行
由于@abstractmethod,我们的发行版强制要求我们对从这个抽象派生的任何子类实现sample方法。对于我们的死亡,这可以是:
import numpy as np
class Die(Distribution):
def __init__(self, sides = 6):
self.sides = sides
def sample(self, n_samples):
return np.random.randint(1, self.sides + 1, size=n_samples)
这里,通过调用特定于掷骰子的方法sample:Die(12).sample(10000)来交付样本。此外,由于numpy,我们可以通过用np.ndarray替换列表理解来非常快速地创建大量数据。
事实上,事情可以进一步改善。目前,调用Die()会返回类似这样的<__main__.Die at 0x7f43f4448400>的东西,这并不具有信息性。同样Die() == Die()的值为False,对于 python 来说,它们是同一个类的两个不同的对象实例。要修复它,我们需要实现另外两个方法:
def __repr__(self):
return f"{self.__class__.__name__}(sides={self.sides})"
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.sides == other.sides
return False
__repr__方法使得对象的渲染很好看,只有骰子“等边”的情况下__eq__才会返回True。
数据类
每次实现这四种方法可能会很繁琐。此外,Die的当前实现并不能防止对象的改变,即使是偶然的,通过像die.sides = 20这样将属性分配给一个已存在的对象。
因此,我们可以使用 python 的dataclasses重新定义Die类。
from dataclasses import dataclass
@dataclass(frozen=True)
class Die(Distribution):
sides: int = 6
def sample(self, n):
return np.random.randint(1, self.sides + 1, size=n)
这个例子的行为和前面的例子是一样的。此外,设置frozen=True,给die.sides分配一个新值将引发一个异常。如果我们想要一个新的骰子,我们应该创建一个新的对象。
现在,我们的expected_value函数可能会将die作为一个分布对象,并通过调用它的sample方法来进行计算。
def expected_value(distribution, n):
return distribution.sample(n=n).mean()
打字
上面的例子很简洁。我们确切地知道expected_value是做什么的,并且很容易测试。然而,n 面骰子并不是我们想要计算期望值的唯一分布。例如,对于掷硬币来说,结果不是数字的(除非我们建立一个约定并坚持下去)。自然,提供一些关于哪些接口可以一起使用以及如何使用的提示是有意义的。
对于像 python 这样的动态类型的语言,你不必坚持变量的类型。然而,使用各种 ide 和工具,如mypy,键入可以帮助您发现潜在的失败点,并使代码更加透明。
让我们使用typing重新定义我们的类,并创建两个更具体的发行版。
from typing import Generic, Sequence, TypeVar
D = TypeVar("D")
class Distribution(ABC, Generic[D]):
@abstractmethod
def sample(self, n: int) -> Sequence[D]:
...
@dataclass(frozen=True)
class Die(Distribution[int]):
sides: int = 6
def sample(self, n: int) -> Sequence[int]:
return np.random.randint(1, self.sides + 1, size=n)
@dataclass(frozen=True):
class Coin(Distribution[str]):
outcomes: tuple = ("H", "T")
fairness: float = 0.5
def sample(self, n: int) -> Sequence[str]:
p = (self.fairness, 1.0 - self.fairness)
return np.random.choice(self.outcomes, size=n, p=p)
@dataclass(frozen=True):
class Gaussian(Distribution[float]):
mu: float = 0.0
sigma: float = 1.0
def sample(self, n: int) -> Sequence[float]:
np.random.normal(loc=self.mu, scale=self.sigma, size=n)
这里发生了几件事。感谢D = TypeVar("D"),我们现在可以定义一个新的变量类型,通过它我们可以参数化每个分布的类型。你可以注意到Distribution不仅继承了抽象基类,还继承了Generic[D],这也将它变成了一个新的(参数化的)类型。现在,它变成了一种身份,构成了一种新的数据类型。
每个版本的sample都被期望返回一个特定类型的序列,该序列对每个单独的发行版的上下文有意义。这样,我们就有了一个参数化的统一界面。我们可以用这个来确保expected_value的正确行为:
def expected_value(
distribution: Distribution[float | int], n: int
) -> float:
return distribution.sample(n=n).mean()
虽然传递例如die = Die()或gaussian = Gaussian()到expected_value将会工作(因为int和float都是数字),但是传递coin = Coin()将会被例如mypy标记出来,声明
> error: Argument 1 to "expected_value" has incompatible type "Coin";
> expected "Distribution[Union[float, int]]"
这可以在我们运行代码之前给我们一个早期的警告。
提高纯度
正如你所看到的,使用typing设计接口有助于形式化意图并尽早发现错误。你甚至可以通过利用numpy的dtype把它带到下一个层次。这样,您不仅可以确保不同的元素相互匹配,还可以更加关注数据的内存占用。
例如:
import numpy.typing as npt
class Distribution(ABC, Generic[D]):
@abstractmethod
def sample(self, n: int) -> np.NDArray[np.generic]:
...
class Die(Distrinution[int]):
sides: int = 6
def sample(self, n: int) -> npt.NDArray[np.uint8]:
return np.random.randint(
1, self.sides + 1, size=n, dtype=np.uint8
)
这样,如果die.sample方法返回不同于严格无符号 8 位整数的数字,您甚至会得到通知。问题是你想不想去那么深的地方?这是值得思考的事情。
计算
让我们回到设计计算部分。到目前为止,我们已经准备好使用数字分布。自然,我们可以计算Die和Gaussian的期望值,但不能计算Coin的期望值。目前的设计不会。
要解决这个问题,有两个选择:
- 我们可以通过映射创建一个代理分布,例如
("H", "T") -> (0, 1),或者 - 我们在
expected_value中加入了一个映射,给出了一个可能的“适配器”。
第一种方法创造了一个人造物体,它的想法依赖于惯例。它不阻止任何人用("H", "T") -> (1, 0)定义另一个代理,导致一个难以察觉的 bug。
相反,我们可以修改expected_value,让它有可能使用定制适配器。
def expected_value(
d: Distribution[D],
f: Callable[[D], Any] = lambda x: x,
n: int = 1000
) -> float:
return np.mean(np.apply_along_axis(f, axis=0, arr=d.sample(n)))
expected_value的第二个参数是可调用的(一个函数),我们可以随意使用它来翻译结果,例如Coin()分布。然而,默认情况下,它将保持结果不变。
die = Die(12)
expected_values(die, n=10000)
gaussian = Gaussian(mu=4.0, sigma=2.0)
expected_value(gaussian, n=100000)
# but
coin = Coin(fairness=0.75)
expected_value(coin, f=lambda x: np.where(x == "H", 1.0, 0.0))
在这里,我们不仅避免了创建代理分布,还设法避免将expected_value与任何特定的数据转换方式联系起来。expected_value函数只做它承诺要做的事情:计算期望值。如果需要任何调整或转换,则由外部提供。注意,这里我们还有一个选项:我们可以定义一个命名函数(例如coin_conversion),以防我们计划重用它,或者当一个单独的定义没有增加价值时坚持使用lambda。
合成和迭代
事实证明,抽象出数学计算是非常有用的,尤其是在设计迭代算法时。通常,除了主要的计算,我们还必须关注一些附带的结果,比如收敛、提前停止(最大迭代次数)、度量等等。
让我们以常数为例。从数学上讲,我们可以通过以下限制来获得它的值:

越高,近似值越精确。
怎样才能充分解决?
不是最好的方式…
先说一个穷人用循环的方法。
def approximate_e(
initial_guess: float,
max_iter: int = 10,
epsilon: float = 0.01
) -> float:
e_value = initial_guess
for n in range(1, max_iter + 1):
new_e_value = (1.0 + 1.0 / n) ** n
if abs(new_e_value - e_value) < epsilon:
return new_e_value
e_value = new_e_value
return new_e_value
同样,这种方法有什么问题?
首先,该函数做三件事,而不是一件。行8.是计算的绝对本质。然而,由于早期停止和收敛条件,我们留下了大量的代码开销,这与实际计算紧密相关。虽然这两个条件看起来更一般,但如果我们选择替换近似值的主题(例如,替换为平方根),我们将不得不复制粘贴这些额外的代码,并确保它不会破坏新的算法。
其次,关于参数化这两个条件,我们唯一的选择是硬编码max_iter和epsilon的值,或者允许用户提供它们作为参数。它破坏了界面,使测试更加困难。
最后,算法“急切地”生成数据。它不是专注于数学并“在被问及时”提供数值,而是将数据抛给你。对于大量数据,这可能会导致内存问题。
计算上的抽象
现在,让我们通过划分不同部分的责任来同时解决这三个问题。我们有三样东西:
- 我们期望返回正确的数字(实际的计算),
- 如果通过了足够数量的迭代,就停止该过程(提前停止),
- 如果数值不再显著提高(收敛),则停止该过程。
from typing import Iterator
import itertools
def approximate_e() -> Iterator[float]:
n = 1
while True:
yield (1.0 + 1.0 / n) ** n
n += 1
def early_stop(
values: Iterator[float], max_iter: int = 100
) -> Iterator[float]:
return itertools.islice(values, max_iter)
def convergence(
values: Iterator[float], epsilon: float = 0.01
) -> Iterator[float]:
for a, b in itertools.pairwise(values):
yield a
if (a - b) < epsilon:
break
这种设计使用迭代器,迭代器实现了“懒惰”加载。数据项只有在被请求时才被一个接一个地返回(因此有了关键字yield)。多亏了这一点,我们(几乎)不必担心记忆。
此外,这三种功能中的每一种都可以单独存在。它们有特定的接口,可以单独进行单元测试。
最后(也是最重要的),最后的结果可以通过把链接在一起得到!
values = approximate_e()
values = early_stop(values, max_iter=50)
values = convergence(values, epsilon=0.001)
for value in values:
print("e becomes:", value)
旧 python
对于 3.10 之前的 python,可以将itertools.pairwise写成:
def pairwise(values: Iterator[D]) -> Iterator[Tuple[D, D]]:
a = next(values, None)
if a is None:
return
for b in values:
yield a, b
a = b
结论
科学编程带来了额外的挑战。这是一个令人兴奋的学科,但是陷阱的数量似乎至少与问题的复杂性成二次方增长。
在本文中,我们讨论了似乎在每个科学编程任务中反复出现的两个主要组成部分:数据和计算。希望通过正确的方式抽象它们,你不仅可以提高代码的效率,减少错误,还可以让编码成为一种更好的体验。为了你和你的团队!
像专家一样写代码的 10 个简单技巧
原文:https://towardsdatascience.com/how-to-write-code-like-a-pro-659760804724
帮助你按照专业标准编写代码的 10 个简单技巧

凯文·Ku 在 Unsplash 上的照片
背景
我已经写了 20 年的代码,在这段时间里,我建立了一套 10 条原则,我相信程序员、开发人员和数据科学家可以采用这些原则来帮助他们按照专业标准编写代码。
这些方法通常适用于任何软件开发环境,但是由于这些天我所有的编码都是在 VS 代码开发环境中用 Python 语言编写的,所以我将重点放在这些工具上,以获得特定的示例。
在我们深入了解技巧之前,请考虑…
通过我的推荐链接加入 Medium(如果你使用这个链接注册,我将收取一定比例的费用)。
每当我发表新故事时,订阅一封免费电子邮件。
1.林挺
帮助编写专业代码的第一个简单方法是使用林挺。
“林挺强调你的 Python 源代码中的语法和风格问题,这通常有助于你识别和纠正微妙的编程错误或可能导致错误的非常规编码实践。”(https://code.visualstudio.com/docs/python/linting#)
要在 VS 代码中启动林挺,使用 Ctrl+Shift+P 进入命令面板,输入“选择 linter”,然后选择你想要使用的 Linter。
有一系列的 linters 可用,选择将取决于个人和团队的偏好,但我个人最喜欢的是“pylint ”,因为我喜欢它呈现错误和警告的方式,也因为它易于设置和配置。
一旦打开林挺,VS 代码将在问题窗口中显示建议,当你编写新代码和改进现有代码时,它将动态更新

作者图片
解决所有的 pylint 错误和警告将很快使您的代码反映专业的、一致的标准并遵循最佳实践。
2.注释、类型提示和文档
让我们从类型提示开始。Python 是一种“动态类型”语言。变量的类型不是必需的,大多数在线例子省略了变量的类型、参数和返回类型。
缺点是客户端或调用者不容易知道函数期望的是什么类型。
但是,如果可选地将变量类型添加到代码中,可读性和可理解性会显著提高,因为客户端或调用者会立即知道函数期望的是什么类型…

作者图片
一旦包含了类型提示,下一步就是对代码进行良好的注释和记录,这是专业代码与众不同的地方之一。
除了提高可读性和可维护性之外,当您添加注释、类型提示和文档时,它使您从不同的角度思考您的代码,从而导致反思性的自我反馈和改进。
一旦在 VS 代码中安装了 docString 扩展,只需在函数声明下按 return 键,输入三个双引号,然后填写为您创建的存根…

作者图片
将一般注释留在代码体中以增加可理解性和可维护性也很重要,将注释块包装在一个区域中可以使它们在 VS 代码编辑器中整齐地折叠起来…

作者图片
当类型提示和文档字符串在整个模块中完成后,创建一整套专业的文档就变得非常容易了。简单地调用 pydoc 如下…
python -m pydoc -w "..\lib\crypto_tools.py"
…将会自动创建一个文档网页…

作者图片
3.项目结构
许多较小的项目可以在包含运行项目所需的所有代码和配置文件的单个文件夹中创建。
然而,用不了多久,一个文件夹就会变成一个无组织的垃圾场,导致混乱和不专业的项目。
我已经看到了几个关于 Python 项目的标准文件夹布局的建议,并得出结论,只要它提供了一个合理的、逻辑的、直观的、离散的和一致的项目资源组织,选择哪一个并不重要。
这是我在项目中采用的标准…

作者图片
“数据”子文件夹包含与项目相关的任何数据文件,如果我的项目清理或转换数据,我通常会添加“输入”和“输出”子文件夹。
“docs”是我存储 pydoc 从 docStrings 和一个批处理文件创建的文档的地方,该批处理文件调用 pydoc 来实现一次点击文档制作。
“keys”是这个特殊项目的一个特殊项目,它表明我不反对根据项目的需要扩展我的标准方法。
“lib”是我存储任何可重用代码库的地方。在这种情况下,我将所有具有潜在未来重用价值的代码移到 crypto_tools.py 中,并对其进行重构,以最大化可用性和可维护性。
“笔记本”是所有 Jupyter 笔记本被分开存放的地方。我通常使用笔记本来创建一个样本用户界面,并演示一个项目是如何工作和使用的。
“src”是我存储其他不可重用的 Python 代码的地方。
“单元测试”是存储所有 pytest 单元测试的地方。在一个大中型项目中,可能会有许多单元测试文件,这些文件确实需要移动到一个离散的位置以保持整洁。这需要一点额外的配置,我将在以后的文章中记录。
一个组织良好、结构合理的项目会立即给程序代码增加专业声望。
4.单元测试
花时间开发单元测试对于像专业人员一样编码是至关重要的,我个人偏爱的单元测试框架是 pytest。
一旦创建了单元测试(参见 https://code.visualstudio.com/docs/python/testing的了解更多细节),只需使用 VS 代码中的烧瓶图标来发现所有的单元测试,然后点击 play 来执行它们

作者图片
如果一切正常,它们会亮绿色,如果任何单元测试失败,它们会亮红色。
全面的单元测试的强大之处在于,未来的更改和更新可以在这样的信心中进行,即如果有任何东西被无意中破坏了,单击一次测试运行就会立即突出问题。
通过这种方式,可以专业地开发和维护代码,并且当前和未来的代码质量将会很高。
5.面向对象编程
面向对象编程(OOP)的 4 个主要概念是
继承 —一个类从另一个类继承属性和方法。
封装 —数据通过私有属性隐藏在类中并受到保护。
多态性——方法可以有相同的名字,但实现不同——想想len(int)和len(list)。
抽象——标准接口的自动执行——想想 scikit-learn 中的.fit()和.fit_transform()。
这里可以找到一些 Python 例子的详细解释—https://www . analyticsvidhya . com/blog/2020/09/object-oriented-programming/。
与传统的过程式编程相比,面向对象编程有很多好处。包括代码重用、可维护性、安全性、生产率、更容易的故障排除等。
然而,对我来说,OOP 的主要优势是类和对象的消费者和客户有一个更加直观和可用的接口,导致前端代码更加紧凑、可维护和可读。
例如,下面的代码片段显示了一个直观而简单的单向哈希接口,否则它可能会变得混乱而复杂
这就是 OOP 的强大之处,也是为什么我总是把我的代码写成类和对象,这是提高程序代码专业性的关键方法之一。
6.避免代码重复
重复相同或非常相似的代码会导致项目容易出错。如果一段类似的代码在一个项目中重复了 10 次,然后需要修复一个 bug 或添加一个增强功能,那么它需要做 10 次,这是低效和费力的,并且为错误提供了 10 个机会。
考虑这些简单的功能——
每一个都只是替换一行代码,为什么要这么麻烦呢?这个代码片段取自一个包含数百个解码和编码实例的项目,在项目中的某一点上,编码必须从“utf-8”改为“ascii”。
这需要许多更改,不知何故,其中一些被遗漏了,导致直到代码投入生产后才被发现的 bug 和错误。
通过将所有实例转移到两个简单的函数中,代码看起来更干净,重复被消除,并且可以快速和自信地对编码进行任何未来的更改。
在编程术语中,这被称为“干”方法——“不要重复自己”(更多细节见https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)。
7.代码重构
重构是一种迭代的方法,用来检查和改进现有的代码,直到它变得尽可能的干净、整洁和专业。
以下是一些问题和注意事项,有助于审查过程…
1.几行代码可以用更少的行代替吗?
2.相反,代码是否需要更详细一点来提高可读性?
3.在现有的库可以做同样工作的地方,是否已经编写了定制代码?
4.重复代码可以消除吗?
5.几个相关的函数和数据可以重写为一个类(OOP)吗?
6.可以扩展代码以增强和改善未来的可靠性和可重用性吗?
7.是否考虑并包括了异常和错误处理?
8.拥有“pythonic 式”方法,如 lambda 函数、列表理解等。被充分利用了吗?
9.代码执行起来是否高效快速?
10.代码是可重复运行和重用的吗?
考虑这些问题,痴迷于迭代重构代码,直到它接近完美,这是帮助你像专家一样编码的关键技术之一。
8.构建代码库
总是有来自雇主和客户的压力,要求快速工作,这有时会导致草率的编码,但是通过构建可重用的代码库,您可以在不牺牲质量的情况下快速工作。
我个人的方法是维护两个目的略有不同的库。
第一个叫做“样本代码”。它是我在网上和书中遇到的所有有用的代码片段的垃圾场,我知道我将来会想要参考这些代码片段,但后来却找不到了!
我的第二个库是我的“实用工具”库。为了获得这个库的资格,代码必须被重新分解、整理、测试、文档化和结构化,以使它在未来的项目中普遍有用和可重用。
这里有一个例子。我需要一些合成数据来测试分类算法。谷歌很快就帮我找到了一些代码,但是有点乱,也没有文档记录。经过一些额外的工作,我的工具库获得了一个有用的新方法,如下所示
您需要做的唯一其他事情是将您的库导入到未来的项目中,如下所示
sys.path.insert将 utilities 文件夹添加到项目的 Python 路径中,然后make_classification_data被导入并可以使用。
构建一个实用程序库的样本代码历史将真正帮助你以专业程序员的速度和质量进行编码。
9.撰写编码博客
被保护人效应是一种心理现象,其中…
教导…或准备教导他人信息有助于一个人学习这些信息(https://effectiviology . com/protege-effect-learn-by-teaching/)。
这只是定期写关于编码的博客的众多好处之一。
https://grahamharrison-86487.medium.com/
准备一个博客包括用一种新的批判的眼光审查代码;毕竟你不希望任何错误,使之成为一个公开的文章!
此外,向他人解释你的代码将帮助你完全理解它,并在这个过程中提高你的知识和专业技能。
相信我,你未来的读者和学生之一就是你自己!在 6 个月或一年的时间里,你会忘记你发现的真正有用的编码技术的细节,你会回到你自己的文章来帮助你记忆。
最后,在像 medium 和 towards data science 这样的知名平台上写博客将有助于在网上建立你的职业形象,这有助于你的同行、编程社区和潜在的未来雇主了解你的职业标准和能力。
10.阅读和挑战
尽可能多地阅读相关资料,以帮助你提高专业编码技能和专业知识。
注册真正的 Python(https://realpython.com/)这样的邮件列表,并确保你加入 medium.com…
https://grahamharrison-86487.medium.com/membership
注意:如果你使用这个链接注册,作者将收到一定比例的费用。
…然后把这个应用程序放在你智能手机的最前面,这样每当你排队购物或者等着水壶烧开的时候,你就可以阅读你的主题了…

作者图片
还可以注册“走向数据科学”播客,这样你就可以在开车或坐在沙发上的时候提高自己的专业技能——https://open.spotify.com/show/63diy2DtpHzQfeNVxAPZgU?si=224fbdb47b6f4c1f 。
除了这些免费或低成本的资源,有时购买一本好的、老式的书也是值得的。以下是我最近读过的提高技能的最佳书籍——
https://www.amazon.co.uk/Hands-Machine-Learning-Scikit-Learn-TensorFlow/dp/1098125975 https://www.amazon.co.uk/Python-Machine-Learning-Example-scikit-learn/dp/1800209711 https://www.amazon.co.uk/Deep-Learning-Coders-fastai-PyTorch/dp/1492045527 https://www.amazon.co.uk/Practical-Blockchains-Cryptocurrencies-Application-Applications/dp/1484258924
本节的最后一个建议是利用丰富的在线工具和资源挑战自我。
如果你想要一般的 Python 编码挑战,你可以使用像“Python 原理”这样的资源——https://pythonprinciples.com/challenges/。
如果你想要一个更大的数据科学类型的挑战,并衡量你与同行的表现,为什么不在 Kaggle 上选择一个你喜欢的数据集,看看你能在排行榜上走多远。
我目前在下面的比赛中排名第四,为什么不看看你是否能打败我?…
https://www.kaggle.com/competitions/credit-default-prediction-ai-big-data/leaderboard
结论
成为一名专业的程序员确实需要努力工作和奉献,但是如果像我一样,编码让你兴奋和热情,如果你热爱你的主题,你就会达到目标。在这篇文章中,我探索了 10 种可以加速这个旅程的工具和技术。
除了写作,我还辅导过许多程序员,如果你正在考虑辅导,为什么不通过ghar rison @ Lincoln college . AC . uk与我联系呢?
如果你喜欢这篇文章,请考虑…
通过我的推荐链接加入媒体(如果你使用这个链接注册,我将收取一定比例的费用)。
此外,我很高兴收到您的来信,了解您对我的文章或任何与数据科学和数据分析相关的内容的看法。
请随时联系 https://www.linkedin.com/in/grahamharrison1 的 LinkedIn 或发电子邮件至 GHarrison@lincolncollege.ac.uk。
如何使用 Jinja2 编写 dbt 宏
原文:https://towardsdatascience.com/how-to-write-dbt-macros-using-jinja2-ff021d535bf3
编写第一个 dbt 宏的教程和备忘单

我最近遇到了一个难倒我的问题。那些不是最好的吗?他们立刻让我们变得卑微。
这个问题导致了我写的代码如何工作的许多挫折。这是我第一次和 dbt 宏一起深入挖掘。我以前使用过它们,但从未添加定制逻辑,我只是复制了我在 dbt 文档中找到的内容。
我没有纠结,而是将这个架构问题作为一个机会来学习更多关于 dbt 宏和 Jinja 函数的知识。并且,通过写这篇文章,我希望填补关于宏如何工作的知识空白。
什么是 dbt 宏?
如果你不知道, dbt 宏基本上就是嵌入在你的 SQL 代码中的函数。逻辑非常类似于一个简单的 Python 函数。他们利用一种叫做 Jinja 的语言来编写这些函数。虽然对于那些用 Python 编写函数的人来说,Jinja 非常简单,但是你仍然需要通读文档来理解它是如何工作的。
让我们复习一些基本的 Jinja 语法:
变量
如果你想设置一个变量,你可以这样写:
{%- set default = development -%}
default是变量的名称,development是被设置为等于变量的对象。记得用正确的语法将它包装起来:{%- -%}。
如果你想引用一个变量,你需要用两个弯曲的大括号把它括起来。像这样:
{{ default }}
If 语句
编写 if 块 is 几乎与 Python 相同,只是用适当的 Jinja 语法包装它们:
-- if
{%- if default = development -%}-- else if
{%- elif default = production -%}-- else
{%- else -%}-- closing the function
{%- endif -%}
确保用{%- endif -%}块关闭中频功能。
记录
记录变量是调试你编写的任何函数的重要部分。为了在 Jinja 中做到这一点,只需使用以下语法插入您希望打印到控制台的变量:
{% do log(node, info=true) %}
在这里,您将把节点的信息记录到您的控制台。请记住,您需要设置info=true来将信息记录到您的控制台上,而不仅仅是记录在日志文件中。
编写宏
现在,如何将所有这些放在一起编写 dbt 宏呢?您可以像定义 Python 函数一样定义它。
首先,必须用 word macro 打开它,并用适当的 Jinja 语法包装它。像这样:
{% macro -%}
然后,在单词macro之后,必须指定宏的名称,类似于指定 Python 函数的名称。
{% macro generate_schema_name() -%}
如果函数接受任何变量作为输入,则将这些变量添加到括号中。
{% macro generate_schema_name(custom_schema_name, node) -%}
同样,类似于 Python(我听起来像一个坏掉的记录),您可以为这些变量设置默认值,以防在引用函数时没有变量传入。
{% macro generate_schema_name(custom_schema_name=none, node=none) -%}
最后但同样重要的是,当您在宏中编写完逻辑后,必须用下面的代码块关闭它:
{%- endmacro %}
不算太坏,对吧?
要引用的 dbt 变量
文档太多总比太少好。但是,有时候,当有这么多好的信息时,你不可能找到你所需要的。纠结了几天,终于找到 dbt 的 Jinja 函数文档。
这在尝试创建自定义宏时非常有用。它会给你一个好主意,告诉你如何利用现有资源实现你的愿景。
不要逐一查看每个人,你可以自己完成,让我们回顾一下我发现最有帮助的。
结节
节点引用特定 dbt 模型的所有配置设置。这在提取模型的名称或路径时经常被引用。我强烈建议像我们上面所做的那样记录节点,以查看您可以从中提取用于宏的所有不同信息。
{% do log(node, info=true) %}
例如,我经常在宏代码中使用node.path,以便获得我的模型的文件路径。如果一个模型在某个文件夹中,我写代码做一件事。如果它在另一个文件夹中,我写代码做另一件事。
目标
这是宏中另一个常用的变量。如果您熟悉 dbt,那么您应该知道目标是用来设置您的开发环境的。你的大多数项目可能都有一个dev目标和一个prod目标。每种模式下的数据库和模式信息都有所不同。
目标变量是您希望如何引用这些不同的信息。您可以通过调用target.name使用当前目标名称,通过调用target.database使用当前目标的数据库。
这在根据您的工作环境设置不同条件时特别有用。您可以通过在 if 语句中使用target.name来查看您是在 dev 还是 prod 中。
我们写个宏吧!
现在,是时候用我们刚刚学过的东西来写你的第一个宏了。虽然 dbt 已经在每个项目中内置了一些宏,比如create_schema_name和drop_old_relations,但是知道如何定制这些宏来满足您的需求也很重要。就我个人而言,我重新配置了create_schema_name来匹配我想要如何命名我的 dbt 模型。
我来分享一下我的做法。
首先,我从宏的基本外壳开始。这包括起始行和结束行,起始行包含函数的名称。
{% macro generate_schema_name() -%}{%- endmacro %}
接下来,我添加了两个 if 语句和一个 else 语句。我知道我想要包含两个条件和一个 else 块来捕捉任何不满足任一条件的情况。不要忘记你的结尾部分!
{% macro generate_schema_name() -%} {%- if -%} {%- elif -%} {%- else -%} {%- endif -%}{%- endmacro %}
现在,我要设置什么条件呢?让我们根据目标中设置的环境来执行每项操作。一个用于开发场景,另一个用于生产场景。
{% macro generate_schema_name() -%} {%- if target.name == 'dev'-%} {%- elif target.name == 'prod' -%} {%- else -%} {%- endif -%}{%- endmacro %}
注意,我们的函数名是generate_schema_name()。这意味着我们希望根据环境设置不同的模式。命名 dev 和 prod 中的模式有什么意义?
这都是个人喜好。这并不是我的数据库和模式实际上是如何建立的,只是在这个例子中我将如何使用它们。
我想编写一个宏,将开发中的所有模型的模式设置为data_mart_dev。然而,我希望所有的生产模型都是我的dbt_project.yml中定义的。
注意,这个宏generate_schema_name()是一个已经由 dbt 编写的宏,它接受一个变量custom_schema_name作为输入。Custom_schema_name指项目文件中指定的内容。此外,我们还想传入模型的节点。
{% macro generate_schema_name(custom_schema_name, node) -%} {%- if target.name == 'dev'-%} {%- elif target.name == 'prod' -%} {%- else -%} {%- endif -%}{%- endmacro %}
现在,我们希望将data_mart_dev设置为宏中第一个 if 块内的模式,因为我们希望所有的开发模型都在这里构建。字符串被简单地写成不带任何引号的普通文本。
{% macro generate_schema_name(custom_schema_name, node) -%} {%- if target.name == 'dev'-%} data_mart_dev {%- elif target.name == 'prod' -%} {%- else -%} {%- endif -%}{%- endmacro %}
接下来,我们需要在第二个 if 块中将模式设置为custom_schema_name。用{{ }}引用变量。在管道后面添加trim将在调用变量时消除任何空白。
{% macro generate_schema_name(custom_schema_name, node) -%} {%- if target.name == 'dev'-%} data_mart_dev {%- elif target.name == 'prod' -%}
{{ custom_schema_name | trim }} {%- else -%} {%- endif -%}{%- endmacro %}
最后,我们需要定义当这些条件都不满足时,模式应该是什么。让我们创建一个默认变量,并将其设置为等于目标模式。
{% macro generate_schema_name(custom_schema_name, node) -%} default_schema = target.schema {%- if target.name == 'dev'-%} data_mart_dev {%- elif target.name == 'prod' -%}
{{ custom_schema_name | trim }} {%- else -%} {{ default_schema | trim }} {%- endif -%}{%- endmacro %}
现在我们的宏完成了!您选择运行的每个 dbt 模型都会自动调用这个函数。您的dev模型将在模式data_mart_dev中创建,您的prod模型将在您的dbt_project.yml文件中定义的模式中创建。
结论
恭喜你。您已经编写了第一个 dbt 宏,它为不同的开发环境设置了不同的模式名。dbt 宏可能很难理解,但是一旦你理解了,你可以用它做很多很酷的事情。定制您的项目,使它们更有效,以及测试您的模型的可能性是无穷无尽的。
但是,要确保不要过度。使用 dbt 的好处是它使您的代码模块化。您的代码应该被存储起来,以便可以在多个地方重复使用。宏也是如此。你不想为一个非常小的用例编写宏。您希望宏应用于项目中的多个位置。当您决定是否应该编写新宏时,请记住这一点。
如果你有兴趣学习更多关于 dbt 宏的知识,一定要订阅我在 Substack 上的新闻简报。很快我将分享一篇关于我最近为一个非常具体的用例编写的宏的独家文章。它将包含大量有价值的信息,您可能希望将这些信息应用到您自己的 dbt 模型中!快乐编码。
如何编写异常函数
原文:https://towardsdatascience.com/how-to-write-exceptional-functions-c809bc58761a
我用来使函数编写更容易和更好的技术

介绍
功能。
T 他们是编程世界中的一股巨大力量,也是数学和高级计算的基础构件之一。函数允许我们定义一组指令供计算机执行,有时通过它们传递不同的数据,然后在我们需要的任何时候调用该函数。从这个意义上来说,函数是计算的核心组成部分,但是制作它们肯定是具有挑战性的。函数总是寻求解决一个问题,有时这些问题很难解决。
由于软件工程是使数据科学成为数据科学的重要组成部分,所以认为更好的功能有助于更好的数据科学是很有意义的。数据科学也带来了机器学习,在这种情况下,性能是一个问题,写得不好的函数性能很差。此外,任何科学的一个重要部分都是可复制的,当要复制的代码看起来糟糕透顶时,事情就变得不那么可复制了。我们经常处理复杂的数学和线性代数,如果没有组织的话,会很混乱。如果有一个领域中编写伟大的函数是一个伟大的想法,我会认为这是一个很好的例子,特别是在数据科学中编写易读且性能良好的函数。
尽管在试图创建一个函数时很容易迷失方向,但是有一些有用的方法可以使编写函数变得更容易。虽然函数一开始可能令人生畏,但它们通常只是输入和输出的基本操作。我使用了一些很棒的技术来编写看起来很棒、功能也很棒的函数。这些通常伴随着更多的经验,但我认为分享其中的一些可能是有意义的,因为它们在我编写函数的过程中经常会派上用场。如果您想查看我在本文中为演示这些内容而编写的函数的代码,笔记本在这里:
https://github.com/emmettgb/Emmetts-DS-NoteBooks/blob/master/Julia/Better functions.ipynb
开始一项功能
所有功能都是从一个目的地开始实现的。我们无法在不知道函数输出的情况下编写函数。如果我们不知道函数的回报是什么,那么我们怎么给它命名呢?记住,当着手编写一个令人生畏的函数时,我们首先需要考虑的是输出。我们希望从函数中得到什么。下一个问题是,我们需要在函数中输入什么,才能得到这样一个东西?对于这个例子,我将编写一个 r 函数,这是一个常用于数据科学领域的连续机器学习模型的验证指标。计算起来有点麻烦,所以它非常适合概述在编写函数时应该做什么和不应该做什么。在统计学中,r 代表(皮尔逊)相关系数。因此,我们需要得到相关系数,并平方它。这是相关系数的公式

这里有些值我们需要计算,
- n
- xy、x、y、x 和 y 的总和
我们需要提供这些值中的哪些作为参数,这是我们在启动函数时首先需要考虑的..所有其他的值,n,x,xy 等等。,它们都是可以计算的,但是我们至少需要一个 x 和一个 y,记住这一点,我们将构造我们的函数来模拟它。一个我深信不疑的信念是,写一个坏函数比写一个好函数要容易得多。也就是说,很多程序员开始会被一个好的函数所困扰。只要你的功能正常,以后还可以重新运行。文本并不是静态的,所以编写函数的第一个目标应该是获得输出,而不一定是最有效的最漂亮的函数——至少在函数实际工作之前是这样。
function r2(x, y)end
因为我使用的是 Julia,所以我还会确保传递每个参数的类型,这样这个方法在不同的上下文中都是可扩展和可用的。顺便说一下,这些好处是在文档和方法错误的好处之上的。如果这些听起来都不熟悉,但您希望如此,我有一整篇文章讨论 MethodError 及其各种功能,因此您可以在这里了解更多信息:
function r2(x::Array, y::Array)end
接下来,我们需要获得公式中使用的计算值。一旦我们有了这些值,将它们代入这个公式实际上应该是相对简单的。表示的一个小技巧是 x 的和与 x 的平方和的差。它们读出来完全一样,但是括号意味着求和需要在前面发生。换句话说,
σx!=(σx)
为了开始这个函数,我将得到 n 和 xy 的值,这只是 x 和 y 之间的元素乘法。
n = length(x)
xy = x .* y
我将得到这三者的和。
sx = sum(x)
sy = sum(y)
sxy = sum(xy)
然后我要对 x 和 y 求平方,像之前一样得到总和。
x2 = x .^ 2
y2 = y .^ 2
sx2 = sum(x2)
sy2 = sum(y2)
获得相关系数的最后一步,基本上就是现在用这些值将公式编码。
((n*sxy) - (sx * sy)) / (sqrt((((n*sx2)-(sx^2)) * ((n*sy2)-(sy^2)))))
这将给我们相关系数。为了使相关系数平方,我们当然只是平方它。所有这些合在一起就形成了这样一个函数:
function r2(x::Array,y::Array)
n = length(x)
xy = x .* y
sx = sum(x)
sy = sum(y)
sxy = sum(xy)
x2 = x .^ 2
y2 = y .^ 2
sx2 = sum(x2)
sy2 = sum(y2)
((n*sxy) - (sx * sy)) / (sqrt((((n*sx2)-(sx^2)) * ((n*sy2)-(sy^2))))) ^ 2
end
提取,血统
这个函数需要做的第一件也是最明显的事情是提取。我们这里的 r 函数是计算 r 的整体,我们需要考虑,我们完全有可能需要在其他地方使用 r。通过计算这个值,并且只在这个函数中使用它,我们将不得不重写这段代码,或者对这个函数的结果求平方根——这是没有意义的。更明智的做法是在不同的函数中计算 r,然后用 r 调用该函数,这将变成一个非常简单的内嵌函数。使用提取技术,我将提取这段代码中负责计算相关系数的部分。
function r(x::Array, y::Array)
n = length(x)
xy = x .* y
sx = sum(x)
sy = sum(y)
sxy = sum(xy)
x2 = x .^ 2
y2 = y .^ 2
sx2 = sum(x2)
sy2 = sum(y2)
((n*sxy) - (sx * sy)) / (sqrt((((n*sx2)-(sx^2)) * ((n*sy2)-(sy^2)))))
endr2(x::Array, y::Array) = r(x, y) ^ 2
这项技术非常有用,它的使用展示了一些需要理解的关于函数的东西。函数应该简单直接,并且只有一个目的。当然,总有这样的情况,你可能调用带有很多参数的函数,或者可能没有输出,事情可能会略有不同,但在大多数情况下应该是这样的。无论如何,虽然在这种特殊情况下,函数看起来可能不会更好,但这仍然会使我们辛辛苦苦编写的代码更加适用和有用。实际上,我也有一整篇关于提取的文章,非常简洁;所以这里有一个链接!:
[## 更多的方法意味着更好的代码
towardsdatascience.com](/more-methods-means-better-code-1d3b237f6cf2)
修订本
好了,现在你的函数已经写好了,可以运行了。你把它发给你的朋友,他取笑你做了一件比你应该做的更复杂的事情。这就是下一步要做的,修改。修订步骤通常是对代码进行审查和修改,以使其更加美观和高效。在这个例子中,调用非常简单直接。在这个特定函数的情况下,可能适合的一件事是减少获取公式值的行数。
function r(x::Array, y::Array)
n , xy = length(x), x .* y
sx,sy, sxy = sum(x), sum(y), sum(xy)
x2, y2 = x .^ 2, y .^ 2
sx2, sy2 = sum(x2), sum(y2)
((n*sxy) - (sx * sy)) / (sqrt((((n*sx2)-(sx^2)) * ((n*sy2)-(sy^2)))))
end
我认为这个版本看起来更好,但这取决于个人喜好。这里您可能想做的另一件事是添加您的文档。那里也可能有一些性能上的调整,因为我的函数相当简单,对我来说可能不是这样。
结论
编写函数可能是一项令人望而生畏的任务,尤其是对于那些编程新手来说。幸运的是,有一些思考问题的方法和编写函数的方法实际上对编写函数非常有帮助。这些是我在这方面的一些方法,我希望它们能在您的软件之旅中很好地为您服务。感谢您的阅读!
如何为数据科学家编写好的代码文档
原文:https://towardsdatascience.com/how-to-write-good-code-documentation-for-data-scientists-c9940aebb4f0
关于最佳实践的速成课程,你需要确保每个人都理解你写的代码。

编写良好代码文档的数据科学家很像一个工程师,他要确保支撑桥梁的柱子能够承受桥梁本身和使用桥梁的乘客的重量。
代码文档不仅提供了代码做什么的想法,而且还为其他人提供了对编码者的思维过程以及为什么代码必须以某种方式编写的洞察力。
每个人都有这样的经历,他们被扔给一段旧的代码,并被告知要重做它,或者更糟,一段需要与现有过程集成的代码。在这些场景中,如果有好的代码文档可用,就很容易完成任务。然而,如果没有代码文档,这可能成为一个不可能的任务,导致时间、金钱的损失,甚至可能破坏系统。因此,代码文档与支撑承重桥梁的支柱一样重要。
考虑到许多数据科学家不是来自基于编程的领域,或者不相信代码文档是一种基本的实践,因为数据科学家通常与组织的其他部分隔离,所以数据科学家甚至可能根本不编写任何代码文档是完全正常的。
因此,为了在将来从事代码工作的数据科学家和有必要进行代码集成的工程部门之间保持和平,请查看下面的最佳实践,您可以使用它们来编写良好的代码文档。
编写分步说明,描述代码如何工作以及使代码工作所需的步骤。
您在编码过程中写的笔记将是您开发代码文档的基础。
最初处理代码时,您希望从编写描述过程中每一步的详细注释开始。
这些笔记然后被用来写你的文档,并将提供你的思维过程的最准确的一瞥。这对于避免历史上每一个程序员都遇到的问题也很重要:周末远离代码回来,不记得你从周五开始的原始思维过程,这在当时是完全有意义的,但现在对你来说毫无意义。
这些注释可以写在您的代码中(非常有用,尽管在生产时间临近时要记得清理它们)或者写在一个单独的文档文件中。
这里的关键是写下写代码时想到的所有事情,尤其是那些你可能认为无关紧要的事情,尽管这些事情可能会在以后的使用中改变游戏规则。这包括描述每个变量代表什么,每个函数做什么,以及代码应该产生的结果。您还需要包含一些注释,说明为什么代码必须以某种方式编写,以及函数的使用和调用顺序。
假设阅读你的代码的人什么都不知道。
假设阅读您的代码的人对代码做什么、如何工作以及为什么工作一无所知。
我遇到过太多次这样的情况,一段代码几乎没有文档,编写它的人非常明显地认为下一个人会知道关于它的一切,从它是如何工作的,到为什么它必须以某种方式编写以避免崩溃,甚至到它做了什么。
因此,在编写代码文档时,你能做的最好的事情之一就是把它写得好像下一个人对你的代码一无所知。
这涉及到将你在开发代码时写的笔记拼凑成一个完整的代码解释,这个人将会看到。如前所述,这些信息可以驻留在代码中(同样,如果一致的话最有帮助),也可以驻留在外部文档中。
准则中应包含简短的注释,而详细的解释或描述最好留在外部文件中,尽管个人偏好或公司政策应是决定性因素。
使用编码约定和最佳实践。
当你写的代码甚至没有遵循适当的编码惯例和最佳实践时,帮助人们理解你写的代码是很棘手的。
虽然这种讨论超出了本文的范围(您可以在这里查看我关于数据科学家软件工程实践的完整文章),但主要思想是确保您正在编写遵循适当软件工程标准的干净、易读的代码。这不仅有助于您之后的人理解代码,而且当那个时候到来时,它还使代码尽可能地为生产做好准备。
当谈到编码惯例和最佳实践时,需要关注的关键思想是:
- 使用描述性的变量名。
- 使用函数。
- 使用注释(如上所述)。
- 使用一致的编码风格。
- 使用您所使用的语言的语法约定。
- 使用图书馆。
- 保持你的代码干燥。
你明白了。
使用编码约定和最佳实践可以确保,如果您的代码文档不够好,熟悉代码的人可能能够理解您所写的内容以及它应该如何工作。
此外,这确保您的代码文档是有意义的,因为它将遵循您正确编写的代码的形式。
提供您的联系信息。
你会惊讶地发现,能够给你正在编写的代码的作者打电话,让他们用简单的语言解释它是如何工作的是多么有用。
有时候,文档是不够的。或者文档在某个特定的领域不是特别清楚。或者你需要和作者确认他们制作的东西应该如何工作。
在代码或文档的末尾提供您的联系信息是一种简单而有效的方法,可以确保任何问题都可以直接提交给您,这有助于节省时间、金钱和减少挫折。
使用流程图。
以图形方式查看流程是理解其工作原理的一种非常有效的方式。
我在大学时就学到了流程图,直到今天我还在使用它来确保我理解我的代码应该如何工作。虽然有些人可以通过阅读来理解,但我通过观看插图或图形来理解。
这些流程图可以包含在代码的外部文档中,并且应该提供一步一步的说明,说明函数如何工作,应该返回什么,如果出错或条件不满足应该发生什么。
这些图形不仅会帮助下一个使用你的代码的人,还会帮助你产生更干净、更有效的代码。
练习描述你的代码是如何工作的,这样一个 5 岁的孩子也能理解。
在完成代码文档时,您希望确保能够向一个 5 岁的孩子解释代码是如何工作的。如果你没有一个 5 岁的孩子来解释你的代码,仙人掌或者橡皮鸭也可以。
这种做法背后的想法是确保当某人不可避免地有一个问题,而你的文档没有回答时,你可以完整而简洁地回答他们的问题。此外,这有助于您检查是否遗漏了文档中的任何关键点,或者是否需要重新编写特定场景、功能或结果的描述。
通过在提供代码文档之前对其进行几次审查,您可以确保通过您的文献尽可能简单地解释您的代码,并且如果出现问题,您可以更详细地解释它。
关键要点
- 在整个编码过程中,编写详细的注释,作为文档的基础。
- 在编写文档时,假设读者对您的代码一无所知,不知道它是如何工作的,也不知道它为什么工作。这将为您提供编写任何人都能理解的全面文档的最佳机会。
- 使用语法约定和最佳实践编写您的代码,以便在您的文档没有提供所有答案的情况下,下一个用户通常能够理解您的代码。
- 请提供您的联系信息,以便下一位用户可以直接向您提问。这为每个人节省了时间、金钱和挫折。
- 使用流程图来提供代码工作方式的图形化描述。
- 练习向一只 5 岁的/仙人掌/橡胶鸭子解释你的代码是如何工作的,以确保你已经在你的文档中包含了所有的要点,这样如果有人带着问题回来,你可以提供进一步的解释。
作为数据科学家如何编写高质量的 Python
原文:https://towardsdatascience.com/how-to-write-high-quality-python-as-a-data-scientist-cde99f582675
学习需求技能!
你应该学习的一套具体技能!

您的旅程概述
- 为什么高质量的代码很重要
- 没有风格,你什么都不是
- 变量名——比听起来更难!
- 模块化你的代码
- 设计原则?没听说过!
- 代码质量的进一步资源
- 包装
1 —为什么高质量的代码很重要
当作为一名数据科学家起步时,人们会听到关于代码质量的相互矛盾的故事。有人说代码质量真的很重要。其他人说,数据科学家不是软件工程师,并采用以下口头禅:
谁在乎呢。如果成功了就成功了,对吧?
当面对关心或不关心代码质量的选择时,很容易选择阻力最小的途径。学习编写高质量的代码需要时间和精力。为什么不干脆忽略代码质量,少一件担心的事情呢😌
嗯,正如你可能从这篇博文的标题中怀疑的那样,我在这里告诉你数据科学家的代码质量实际上很重要。实际上相当多!这里有三种情况,高质量的代码可以节省你无数的时间:
- 如果代码质量不好,错误和可疑的边缘情况很容易被忽视。这导致后来耗时的错误修复,最糟糕的是,生产失败。高质量的代码可以让你尽早失败,快速失败。
- 当新人开始一个项目时,他们必须了解代码库是如何工作的。数据科学和软件工程都是如此。当代码随意编写时,入职流程会变慢。代码的某些部分将是神秘的,除了编写代码的人之外,其他任何人都无法真正理解。甚至对于编写代码的人来说,几个月后这可能会变得毫无意义。高质量的代码是未来可以理解的。
- 假设你有一个非常成功的数据科学项目。要求您扩展项目以满足不断增长的需求。通过数据管道的数据量和数据速度将会更高。如果你的代码很糟糕,那么事情可能不像看起来那么简单。当代码具有高质量时,应该可以更顺利地扩展到新的环境。高质量的代码可以扩展到新的情况。
希望您现在已经确信高质量的代码是有用的。然而,学习编写高质量的代码是一个渐进的过程。你的代码质量在今天和明天之间不会有很大的变化。没关系。如果你努力改进,那么从长远来看,你的劳动成果将会被所有人看到。
我建议你采取这样的心态:高质量的代码是你引以为豪的东西。几乎每个杰出的作家都为自己的作品感到自豪。同样,作为一名数据科学家,你应该为你的代码感到自豪。它是你创造的东西。它是传递价值的东西。因此,代码应该是高质量的。就这么简单。
在这篇博文中,我将向您展示一些用 Python 编写高质量代码的要点。把博客文章看作一个开始,而不是一幅完整的图画。最后,我会给你一些很酷的资源,它们可以帮助你在编写代码的道路上走得更远🔥
2 —没有风格你什么都不是
问题是
在大多数编程语言中,有许多方法可以解决同一个问题。这鼓励了创造力和解决问题的新方法。但是,也有看似无限的方法来完成以下任务:
- 选择函数定义中的参数之间是否有空格。你应该写一个函数作为
def my_function(a, b)或者def my_function(a,b)或者def my_function( a, b )。 - 函数名应该怎么写?也许就一个字
def myfunction(a, b)?也许在def my_function(a, b)蛇案中?或者也许在骆驼案T5?或者也许帕斯卡-凯斯T6?
对于每一行代码,都有这样的考虑。如果您对所做的选择采取随机行动,您的代码会变得像这样混乱:
def MyFUNCTION( a,b) :
return a+b
呸!那又怎样?你的公司是否应该召开内部会议,让你了解每个案例应该如何处理?听起来非常无聊。
解决方案
不要害怕!在 Python 中有 PEP8。这是官方建议的代码风格约定。关于 PEP8 的精彩摘要,你可以看看肯尼斯·雷茨的时尚版。
每个 Python 开发者都应该(除非你的公司有自己的风格指南)遵守 PEP8。这一开始看起来可能有点麻烦。相反,一旦你接受了 PEP8 作为大多数风格决策的权威,你就可以把精力集中在代码的其他方面。您现在不需要考虑上面代码片段的正确风格版本。正确的格式是:
def my_function(a, b):
return a + b
仍然有大量的决定留给代码的作者。你应该使用类型提示来指示参数的数据类型吗?您可能应该编写一个 docstring 来解释该函数的作用。逻辑怎么实现还是 100%看你。你根本不需要花精力去考虑风格的选择。
我建议你花 10-15 分钟浏览一下 PEP8 风格指南以获得一些改进。当你不确定具体的选择时,你可以查阅 PEP8。
自动代码格式化程序
还有像 Black 这样的自动代码格式化器,它会自动为你格式化代码。这些太棒了!但是,你要知道,它们是有局限性的。
举个例子,PEP8 建议用单词Error结束 Python 中的一个自定义异常类。因此,表示数据处理错误的类的开头应该写成例如
class DataProcessingError(Exception):
---
这是像 Black 这样的自动格式化程序无法解决的问题。无论如何,使用像 Black 这样的自动格式化程序。但是一定要记住 PEP8 的大部分内容。
但是我不想!

有些读者会认为代码格式和代码风格不重要。如果你真的看不到 PEP8 的重要性,那么这是完全正确的。要知道,如果你不坚持你的代码,很多人会认为你的代码质量很差。格式糟糕的代码会让人产生期望,就像充满拼写错误的邮件一样。
3 —变量名—比听起来更难!
用 Python 命名变量、函数、类、模块和包是一项艰苦的工作。事实上,菲尔·卡尔顿有以下名言:
计算机科学只有两个硬东西:缓存失效和事物命名。—菲尔·卡尔顿
幸运的是,大多数数据科学家不必处理缓存失效。然而,你被事物的命名所困扰。Python 包和模块应该有简洁的全小写名称。我们来讨论一下如何给我们的宝贝起个好名字;变量、函数和类。
简单的惯例
有几个简单的惯例,一个人应该永远遵守。
首先,永远不要覆盖内置。例如,创建以下变量可能很有吸引力:
a = 5
b = 2
min = 0
if a < b:
min = a
else:
min = b
上面的代码用变量min覆盖了内置函数min()。现在,如果你试图使用内置的min()函数,你的程序就会崩溃。引用教父的话,“看看他们是如何屠杀我的孩子的!”😧
其次,对于变量、函数和类,有一些 PEP8 约定你应该遵守。变量名和函数名都应该小写,不同的单词用下划线隔开:
my_variable = 5def my_function(a, b):
return a + b
如果希望一个变量在整个程序中保持不变,那么可以用大写字母来表示:
MY_NONCHANGING_VARIABLE = 5
对于类,它们的名称应该用大写的单词连接起来,如下所示:
class MyAwesomeClass():
---
上面对变量名的具体选择并没有提供很多信息。这是我们现在要讨论的命名事物的困难部分。
硬性主观性
命名事物的困难之处在于不遵循上面描述的惯例。困难的部分是选择以简洁的方式表明实体是什么(对于变量和类)或者它是做什么的(对于函数)。以下是我想强调的一些准则(适用于变量):
- ****仔细挑长度:变量名不能太短也不能太长。如果变量名太短,你最终会使用不是每个人都能理解的缩写。你能说出变量
c = 7或vprice = 5代表什么吗?断章取义,很明显这些变量名有多烂。但是通过编写:count_of_how_many_steps_our_pipeline_has = 7和variance_of_the_column_price_in_our_dataset = 5,它们也可以扩展得太长。现在可以理解,但是读起来就是噩梦。尝试找到一个合适的中间地带:pipeline_steps = 7和variance_price = 7怎么样? - ****不要提到数据类型:将变量的数据类型隐藏在名称中往往很有诱惑力。我们很多人都有把熊猫称为数据帧的罪过。这个想法是,通过将
df作为数据帧的缩写,可以向读者表明这是一个数据帧。这种方法被称为匈牙利符号,并且在很大程度上不被提倡。在 Python 中,你可以用type()函数轻松检查某个东西的类型。如果你也使用类型提示,匈牙利符号就变得毫无用处。我建议把你可用的“变量名 milage”花在更有用的东西上。 - ****使用直观的领域语言:您已经存储了变量
broccoli、celery和beets,这些变量计算每种蔬菜的销售数量。您现在需要一个容器类型(比如字典)来存储所有这些变量。你把这本字典叫做什么?别担心,这不是一道难题。我不知道你怎么想,但我会称之为vegatables。尽可能使用直观的领域语言。唯一的例外是,如果您希望您的代码扩展到领域知识不再有意义的情况。
对于函数,上面的指导原则同样适用(请不要在函数名中嵌入“函数”一词)。唯一值得注意的是,函数通常做一些事情,而不是包含一些东西。因此,功能通常被赋予动作词(动词)来强调它们做什么。prune_tree()、recommend_item()等名字都很棒。
希望我已经让你相信花些时间给你的变量命名是值得的。在某些情况下,打破上述准则将是你最好的行动。例如,缩写 API(应用程序编程接口)是众所周知的。由此(私!)名为api_password的变量完全没问题。使用你自己的判断!
4 —模块化您的代码
并非所有代码都是平等的。有些代码很难扩展。有些代码不容易被重用。有些代码很难检查错误。在所有这些方面做出改进的一个方法是模块化你的代码。
这在实践中意味着什么?你的代码应该分成函数和类。这些函数和类应该再次分组到 Python 模块中。为什么这样做?
考虑没有以任何方式分组的代码(您可能认为代码是“自由浮动的”)。这当然方便写在开头。然而,随着代码的增长,它变得越来越麻烦。
你必须复制和粘贴代码来做一些小的调整。如果你想扩展它,现在你必须在所有重复的地方修改代码。天知道如果一个同事要求你代码的一个特性你会怎么做——一切都是完全交织在一起的。不要让我开始讨论如何检查代码中的错误。
开始时的便利变成了一场噩梦😧
解决方法是什么?您应该定期将代码重构为函数和类。让我们以函数为例。函数是一段可重用的代码。当您的代码按逻辑分组到函数中时,您可以:
- ****轻松重用一些代码:因为一个函数是一个独立的部分,你可以将那个特定的函数导出到其他设置中。也许你已经开发了一个很棒的函数
clean_missing_values()来清除数据集中的缺失值。现在,您也可以在其他项目中使用该功能(只需进行最小的调整)。 - ****轻松检查代码中的错误:可以通过单元测试来测试函数中的错误。在编写 Python 时,最常见的编写单元测试的库是 Unittest 和(我的最爱) Pytest 。
- ****轻松添加文档,更加清晰:对于函数,您可以添加文档字符串来解释它们的功能。您还可以将类型提示添加到向其他开发人员和数据科学家解释输入和输出的数据类型的函数中。你会发现,一旦项目的复杂性增加,模块化的代码就更容易理解了。
特别是对于数据科学家来说,代码经常被留在 Jupyter 笔记本单元中,完全没有模块化。如果不从长远来看加以解决,上述所有问题都会变得更加突出。
不要误解我。Jupyter 笔记本是数据探索和测试机器学习模型的伟大工具。但是,请确保您留出时间将代码重构为可维护的部分(函数和类)。这样做,从长远来看,你会省去很多痛苦。此外,你将成为团队中测试人员/开发人员最喜欢的人,这总是一个优势😅
5—设计原则?没听说过!
你不用设计原则?
通常, 设计原则 是软件开发人员全神贯注的事情。许多数据科学家的印象是,设计原则并不真正与他们有太多的关系。对于这种说法,我想指出亚当·贾奇关于一般设计的名言:
“好设计的替代品总是坏设计。没有所谓的没有设计。”亚当·贾奇
Adam Judge 说的绝对是视觉角度的设计。然而,我认为结论仍然代表着编写代码时的设计原则。通过编写代码,你在使用设计原则。你可能只是没有好好利用它们。
让我们从一些设计原则开始,以提高您的一般代码质量!
可爱的缩写😍

加里·本迪格在 Unsplash 上拍摄的照片
首先,有一些可爱的首字母缩略词正在被到处乱扔。在编写代码时,您应该将这些原则视为指导原则:
接吻:保持简单,傻傻的。尽可能降低复杂性。我们举个例子。比如你想算出 1 到 1000 之间有多少个非平方数。回想一下,非平方数n只是一个不能用其他数m的n = m**2来表示的数。下面的代码解决了我们的问题:
non_square_numbers = len(set(range(1, 1001)).difference(set(map(lambda x: x ** 2, range(1, 1001)))))
要诚实。虽然上面的代码解决了这个问题(甚至使用了很酷的功能,如 set 和 map 函数),但它看起来很可怕。你可能花了一些时间去读它。这太复杂了。考虑以下简化:
from math import sqrtnon_square_numbers = list(range(1, 1001))for n in range(1, int(sqrt(1001))):
non_square_numbers.remove(n ** 2)
新代码不仅更清晰,而且更快。我只需要运行这些数字直到 1001 的平方根,因为我是平方数字。从 KISS 中得到的教训是,如果让你的代码更容易理解,有时候让它长一点是值得的。
****干:不要重复自己——这个原则鼓励将你的代码模块化成函数。这样做可以避免代码重复。事实上,每当您发现自己重复代码时,您可能应该模块化您的代码。
YAGNI:你不会需要它的——这个原则鼓励你不要过度设计解决方案。我们来做一个具体的例子。
假设您正在编写一个数据管道,其中第一步是一个从 SQL 数据库读取数据的函数。也许在未来,你还需要阅读 CSV 文件。所以你也要实现它。也许在将来,你也需要阅读 XML。或者 JSON。或者拼花文件。
在这种情况下,YAGNI 原理很简单。不要。您可能不需要所有的扩展,并且您正在编写永远不会被使用的代码。相反,请确保您的代码可以扩展。但是请将实际的扩展留到您真正需要这样做的时候。
遵循 YAGNI 原则,你的代码库会更小,你需要担心的松散线程也会更少。
坚实的设计原则
一组完善的设计原则(尤其是在 OOP 中)是坚实的原则。它们形成了一组久经考验的设计原则,在过去的几十年中被大量使用。SOLID 中的五个设计原则是:
并非所有坚实的原则在数据科学中都同样有用。事实上,我在数据学科中很少使用利斯科夫的替代原理。不要误解我,这是一个伟大的设计原则,我非常尊重芭芭拉·利斯科夫。但是,在大量使用 OOP 的软件工程中,它肯定更有用。我认为数据科学家最重要的坚实原则是单一责任原则。
单一责任原则声明函数(以及类)应该有单一的责任。让我们用一个例子来探讨这意味着什么。
假设您有一个函数process_data(),它加载一个 CSV 文件作为 Pandas 数据帧,删除丢失的值,选择一些特征,然后最终绘制这些特征。您的函数process_data()现在有四个职责:导入数据、清理数据、特征提取和绘图。为什么这样不好?
首先,现在很难将代码导出到其他设置。您可能已经在函数process_data()中编写了顶级缺失值清除代码。你如何在其他地方使用这些代码,比如说,不能进行特征提取的地方?没那么简单。
测试你的代码是否有缺陷也更加困难。通过做多种不同的事情,有更多的地方让臭虫藏身。
单一责任原则建议将功能process_data()分解为四个功能。这些功能可以称为import_csv_data()、clean_missing_values()、extract_features()和plot_features()。这样,每个功能都有一个单独的职责😃
6 —代码质量的更多资源
我已经给了你一些提高代码质量的起点。关于这个主题的更多信息,您应该从哪里获得?
- ****代码风格:除了熟记 PEP8 并使用有效的代码格式化程序(如 Black 或 autopep8 )之外,真的没什么别的了。如果您想确保您执行了这一点,那么就请做代码评审的人提供关于代码风格的反馈。您可能还想研究一下在 PEP257 中编写适当的文档字符串的 Python 约定。
- ****变量名:我在这里最好的建议是注意你选择的变量名。除此之外,我在 Al Sweigart 的书中发现了许多有趣的点,超出了 Python 的基本内容。
- ****模块化代码:编写模块化代码需要付出努力。如果您不确定何时(以及如何)使用类,请考虑学习更多关于 OOP(面向对象编程)的知识。我可以推荐免费的 YouTube 系列 Python OOP 教程作为一个很好的起点。除此之外,我建议您熟悉 Python 函数的各个方面,以便能够有效地使用它们。例如,如果您不知道 Python 函数默认返回值
None,那么您的代码可能会反映出这种知识的缺乏。 - ****设计原则:我只是给了你一点设计原则的味道。有关于这个主题的经典书籍,也有专门针对数据科学的书籍。然而,我怎么推荐都不为过。它们具有极高的质量,并对许多设计原则进行了 Python 特有的介绍。
7 —总结

希望你正在成为一名高质量的代码制作者。如果你的代码不是一夜之间完美的,也不用担心。获得高质量的代码是一个漫长的旅程,我每天都在努力。
****喜欢我写的?查看我的其他帖子,了解更多 Python 内容:
- 用漂亮的类型提示使你罪恶的 Python 代码现代化
- 用 Python 可视化缺失值非常简单
- 使用 PyOD 在 Python 中引入异常/异常值检测🔥
- 5 个能在紧要关头救你的牛逼数字功能
- 5 个专家提示,帮助你提高 Python 的词典技能🚀
如果你对数据科学、编程或任何介于两者之间的东西感兴趣,那么请随意在 LinkedIn 上加我,并向✋问好
如何不用代码写 Python
原文:https://towardsdatascience.com/how-to-write-python-without-code-cbd536046fa9
简要介绍在分析数据时生成代码的 python 包

Joshua Woroniecki 在 Unsplash 上的照片
各行各业对 ython 的使用持续增长。其中一个原因是分析大型数据集的需求日益增加。许多 Python 新用户都有电子表格的背景,他们发现虽然 Python 可以更快地处理大得多的数据集,但他们错过了 Excel 或 Google Sheets 中电子表格的可视化特性。
米托是一个 Python 包,它将电子表格的好处带给了 Python。这个包允许您将一个电子表格调用到您的 Python 环境中,在这里您可以传入一个 DataFrame 或一个 Excel 文件— 您在电子表格中所做的每一次编辑都会自动写入相应的 Python 代码。

您在电子表格中所做的每一次编辑都会自动写出相应的 Python 代码(图片由作者提供)。
关于自动代码生成的一个伟大的事情是,你不需要花很多时间去栈溢出或谷歌来获得正确的语法。你所需要做的就是完成电子表格中的操作,代码已经为你写好了。
要安装米托,请在终端中使用以下命令:
python -m pip install mitoinstaller
python -m mitoinstaller install
然后打开 Mitosheet 界面:
import mitosheet
mitosheet.sheet()
以下是完整的安装说明的链接。
探索您的数据
在 Python 中,电子表格界面简化了探索性数据分析。米托提供了电子表格中的自定义功能,但将它们带到了 Python 环境中,并生成了等效的代码。
你可以用两种方法之一把你的数据输入米托。第一种是通过将现有数据帧添加到 mitosheet.sheet()调用来传入该数据帧。
以下是将一些示例数据帧传递到米托的示例:
# import Python packages
import mitosheet
import pandas as pd# create some simple data to displaytrain_stations = pd.DataFrame({'Zip': [21001, 97321, 49224, 87102, 24910, 22301], 'City': ['Aberdeen', 'Albany', 'Albion', 'Albuquerque', 'Alderson', 'Alexandria'], 'State': ['MD', 'OR', 'MI', 'NM', 'WV', 'VA'], 'Ticket_Office': ['N', 'Y', 'N', 'Y', 'N', 'Y']})demographics = pd.DataFrame({'Zip': [21001, 97321, 49224, 87102, 24910, 22301], 'Median_Income': [53979.0, 112924.0, 37556.0, 28388.0, 30914.0, 54087.0], 'Mean_Income': [66169.0, 147076.0, 50371.0, 39529.0, 40028.0, 64068.0], 'Pop': [18974.0, 11162.0, 14900.0, 22204.0, 5383.0, 19504.0]})# render the Mitosheet with the data
mitosheet.sheet(train_stations, demographics)
第二种方法是使用导入模式直接传递 Excel 或 CSV 文件:

将数据导入米托(图片由作者提供)。
一旦数据集位于米托境内,诸如以下要素:
- 汇总统计数据
- 过滤
- 分类
- 数据透视表
- 电子表格公式
此外,允许您快速浏览数据,而无需编写任何代码。

用米托过滤数据(图片由作者提供)。
值得指出的一个简单特性是,在米托,你可以通过编辑单元格来更改数据集中的任何值——就像在电子表格中一样。像这样的特性提供了可视化分析和编辑数据的能力,这增强了 Python 的体验。
可视化您的数据
米托拥有内置于工具中的 Plotly Python 包的绘图能力。通过单击工具栏中的 graphing 按钮,您可以创建完整的 Plotly 图形,并为该图形生成等价的代码。

用米托可视化你的数据(图片由作者提供)。
Plotly 图形非常适合 Python 中的分析,因为它们是可共享的,并且具有交互功能,这使得它们可以供技术和非技术受众使用。
在米托,您可以在菜单中配置您的图形并导出等效的代码。这是一个巨大的时间节省,因为获得正确的语法来绘制代码在 Python 中是众所周知的乏味。

用米托导出数据(图片由作者提供)
结论

伊恩·施耐德在 Unsplash 上拍摄的照片
这是一个可怕的米托包简要介绍。你知道米托也是开源的吗?你可以在这里查看 Github。
想了解更多,请看我关于米托的其他故事。
你是视觉学习者吗?这是一个在米托绘图的视频指南。
如何编写 Pythonic 代码
原文:https://towardsdatascience.com/how-to-write-pythonic-code-208ec1513c49
充分利用这美丽的语言

在 Unsplash 上由 Hitesh Choudhary 拍摄的照片
E 每种编程语言都有自己的习惯用法,由用户定义。在 Python 社区中,Python是一个描述代码的词,它不仅语法正确,而且以预期的方式使用语言。从可维护性、可读性和效率的角度来看,它提高了整体代码质量。广义地说,它还为整个开发团队创建了一个代码模式,以关注问题的真正本质。要使库成为 Python 库,就要让 Python 开发人员很自然地在他们的代码库中使用它。记住,读代码比写代码更频繁。
但是它实际上是什么意思呢?这听起来像是一个模糊的概念。我该如何通过向他们展示‘T9’可信的‘T10’Python 代码来破解 Python 采访呢?在本文中,我想告诉你 8 个广泛推广的 Pythonic 特性,它们将把你的代码带到下一个层次。它们主要是为希望快速提高技能的 Python 初学者设计的,但也有一些针对中间用户和专家的技巧。最后,我会给你一些关于编写一个 Pythonic 库或框架的技巧,以及一些有助于你自学的免费资源。
我知道这是一篇长文章。为了给你一些期望,这里是内容。请随意跳过您已经知道的内容。
- 蟒蛇的禅
- PEP8
- 价值交换&多重分配
- 传递多个参数(*args 和**kwargs)
- 理解
- 强调
- 上下文管理器
- 发电机
- 命名空间和范围
- 可变默认参数
- 编写一个 Pythonic 库
- 其他免费资源
蟒蛇的禅
如果我不从蟒蛇的禅开始,这篇文章就不完整了。您可以通过键入import this在任何给定的时间找到它。这是编写 Python 代码的 19 个“指导原则”的总结。我更愿意将其视为一种思维方式,而不是实际的语法指南。然而,这首诗中的哲学已经影响了全球成千上万的 Python 开发人员。

蟒蛇之禅(摄影:高)
我稍后将向你们展示的例子肯定是遵循这一理念的。请通读一遍。我将向您传达一些核心概念,以便您为示例做好准备。
简单明了&可读性
我把这三个特征放在同一个桶里,因为总的来说,这意味着编写每个人都能理解的简单和干净的代码。你可以用许多不同的方式来解释它。这首诗中的一个例子是flat is better than nested,意思是在你的项目中不要有太多的子类别(模块/包)。sparse is better than dense意味着不要在一行代码中塞进太多的函数 (79 个字符的规则无论如何都会换行)。
没事打破规则
就结构而言,Python 不如 Java 等其他编程语言严格。您可以编写纯过程(如脚本)或面向对象范例(如 Java ),或者两者兼而有之。关键是你不必把你的代码放进不适合你的鞋子里。过分遵循规则会导致高度抽象和样板代码。
注意错误处理
错误不应该被忽略。快速失败并捕获它们比隐藏错误并继续程序更好。当 bug 远离原来的位置时,它们变得更难调试,因此现在而不是以后引发异常。
应该有一种——最好只有一种——显而易见的方法来做这件事
虽然是作为指南写的,但是我感觉用 Python 真的很难做到这一点。Python 被认为是一种灵活的编程语言,受到大型社区的支持,这意味着人们每天都可以对现有的解决方案提出新的想法。然而,它试图传达的主要信息是,不值得花力气去学习每一种可能的方法。社区已经做了一些努力来标准化这些格式,我马上会谈到。
PEP8
正如我前面提到的,Python 是一种灵活的语言,在格式上没有太多限制。这就是 PEP8 出现的原因。欢迎您以任何方式编写 Python 代码,只要它是有效的。然而,使用一致的风格使你的代码更容易阅读和维护。PEP8 提供了丰富的项目列表。绝对值得一查。
一些著名的 linters,如 Flake8 和 Pylint 可以在你提交代码之前发现问题,从而为你的同事节省审查时间。像 Black 这样的库甚至可以自动修复格式问题。常见的做法是将这些工具集成到您的 IDE(例如 vscode)和 CI/CD 管道中。
价值交换和多重分配
你可能以前见过这个问题:如何交换两瓶水?答案是得到第三个空瓶子。大多数语言都是这样处理的,需要一个额外的变量来交换值。
然而在 Python 中,生活变得更容易了。您可以像这样交换两个值:
a = 1
b = 2
a, b = b, a
看起来太神奇了。第a,b=b,a行被称为赋值,其中右边是一个表达式,左边是几个变量。右边的表达式b,a其实就是一个元组。不信?在终端中尝试一下:
>>> 1,2
(1, 2)
括号在元组中并不是真正必要的。
此外,Python 支持多重赋值,这意味着左侧可能有多个变量,每个变量都被赋值给元组中的一个值。这也称为拆包分配。另一个解包赋值的例子是 list:
fruits = ["apple", "banana"]
f1,f2 = fruits
结果会是f1="apple",f2="banana"。
通过这样做,您可以轻松、优雅、自然地分配变量,而无需样板代码。
传递多个参数(*args 和**kwargs)
与前一点相关,Python 允许向函数传递多个参数,而无需在函数中定义它们。一个例子可以是一个将一些数字相加的函数,但是数字的大小是未知的。
一种简单的方法是创建一个列表变量作为函数的输入。
def calculate(values):
for val in values:
....
calculate([1,2,3,4])
然而,在 Python 中,你可以有一个接口而不提供列表。
def calculate(*values):
for val in values:
....
calculate(1,2,3,4)
calculate(*[1,2,3,4]) # this works too
*values等于(1,2,3,4)是一个元组(一个可迭代的),函数内部的逻辑可以保持不变。
与*args类似,**kwargs接受命名参数,并将它们解包成键、值对。当你有一堆有不同含义的可选参数时,这很有用。在这个例子中,房子可以由不同类型的房间组成。如果你不喜欢有太多的争论,你可以提供一本字典来代替。
def build_house(**kwargs):
for room,num in **kwargs:
...
build_house(bedroom=2,kitchen=1,bathroom=1,garden=1)
build_house(bedroom=2,kitchen=1,bathroom=2,storage_room=1)
解包的另一个有趣的事情是你可以很容易地合并两个列表或字典。
first = [1,2,3]
second = [4,5,6]
result = [*first, *second]
# [1,2,3,4,5,6]first = {"k1":"v1"}
second = {"k2":"v2"}
result = {**first, **second}
# {"k1":"v1", "k2":"v2"}
理解
理解很酷。这是我对它的第一印象。理解用于在单个指令而不是多个操作中创建数据结构。一个经典的例子是将一个 for 循环转换成一行代码。
result = []
for i in range(10):
result.append(i**2)# use list comprehension
result = [i**2 for i in range(10)]
一般来说,理解的表现更好,因为它的操作更少,因此不需要对每一项都执行.append()。在复杂的函数中,理解可以明显减少代码的行数,使读者容易理解。另一个可比较的方法是使用λ表达式。同样的表达式可以写成这样:
result = list(map(lambda x:x**2, [i for i in range(3)]))
但是,如果代码创建了复杂的表达式,不要强迫它成为一行代码。我读过《Python 中的干净代码》一书,其中有一个很好的例子。collect_account_ids_from_arns函数接收一个值列表,然后解析、匹配并最终将它们添加到collected_account_ids中。
这是 for 循环的简单解决方案。
def collect_account_ids_from_arns(arns):
collected_account_ids = set()
for arn in arns:
matched = re.match(ARN_REGEX, arn)
if matched is not None:
account_id = matched.groupdict()["account_id"]
collected_account_ids.add(account_id)
return collected_account_ids
这是有领悟的版本。
def collect_account_ids_from_arns(arns):
matched_arns = filter(None, (re.match(ARN_REGEX, arn) for arn in arns))
return {m.groupdict()["account_id"] for m in matched_arns}
另一个更紧凑的版本是使用 walrus operator。该示例将代码推送到实际的一行程序中。但这并不一定比第二种方法更好。
def collect_account_ids_from_arns(arns):
return { matched.groupdict()["account_id"] for arn in arns if (matched := re.match(ARN_REGEX, arn)) is not None }
理解可以简化代码并提高性能,但是考虑可读性也是必须的。
强调
Python 中使用下划线的方式不止一种。每种类型代表属性的不同特征。
默认情况下,对象的所有属性都是公共的。没有 private 关键字阻止你访问一个属性。Python 在函数名前使用下划线(如def _build())来划定对象的接口。以下划线开头的属性应该被认为是私有的,不能从外部调用。类的私有方法/属性只能在内部调用。如果这个类获得了太多的内部方法,这可能是这个类违反了单一责任原则的标志,也许你想把一些责任提取给其他类。
下划线的另一个 Pythonic 特性是所谓的 魔法方法。神奇的方法被双下划线包围,如__init__。好玩的事实,根据 原黑客的字典 ,神奇的意思是
一个没有公开的功能,它允许一些本来不可能的事情。
Python 社区在 Ruby 社区之后采用了这个术语。它们允许用户访问语言的核心特性,从而创建丰富而强大的对象。成为魔术方法的专家可以让你的客户拥有干净的代码。听起来很抽象?让我们看一个例子:
class House:
def __init__(self, area):
self.area = area
def __gt__(self, other):
return self.area > other.areahouse1 = House(120)
house2 = House(100)
通过覆盖魔法方法__gt__,使用House的客户端可以用house1 > house2比 2 个房子,而不是类似house1.size() > house2.size()的东西。
另一个例子是改变类的表示。如果你打印house1,你会得到一个带有 id 的 Python 对象。
print(house1)
# <__main__.House object at 0x10181f430>
有了魔法方法__repr__,打印语句变得更加不言自明。神奇的方法对客户隐藏了实现细节,同时给了开发人员改变其原始行为的能力。
def __repr__(self) -> str:
return f"This house has {self.area} square meters."print(house1)
# This house has 120 square meters.
尽管使用下划线很常见,但是不要使用双下划线来定义属性,也不要定义自己的神奇方法。它不是 Pythonic 式的,只会让你的同行感到困惑。我写了一篇关于这个话题的文章。你可以在这里查看。
</5-different-meanings-of-underscore-in-python-3fafa6cd0379>
上下文管理器
上下文管理器值得单独写一篇文章。这是一个非常有用的特性,可以帮助你在某些动作之前和之后运行一些东西。资源管理是它的一个很好的用例。您希望确保文件或连接在处理后关闭。
在 Python 中,可以使用两种方法来分配和释放资源:
- 使用
try .. finally挡块 - 使用
with构造
例如,我想打开一个文件,阅读内容,然后关闭它。这就是使用try .. finally的样子。finally语句保证无论发生什么情况,资源都被正确关闭。
f = open("data.txt","r")
try:
text = f.read()
finally:
f.close()
尽管如此,您可以使用with语句使它更加 Pythonic 化。如您所见,许多样板代码被删除了。当您使用with语句时,您进入了一个上下文管理器,这意味着当程序块完成时,文件将被关闭,即使发生了异常。
with open("data.txt", "r") as f:
text = f.read()
这是怎么发生的?任何上下文管理器都由两个神奇的方法组成:__enter__和__exit__。with语句将调用方法__enter__,它返回的任何内容都将被赋给as之后的变量。在该块中的最后一行代码完成后,Python 将调用__exit__,其中资源被关闭。
一般来说,我们可以自由地用自己的逻辑实现上下文管理器。我想向您展示实现上下文管理器的 3 种不同方法(是的..我们正在打破Python 的禅的规则。假设我想为备份创建一个数据库处理程序。数据库应该在备份前脱机,并在备份后重新启动。
- 创建上下文管理器类。在这个例子中,在
__enter__扇区不需要返回任何东西,这是可以的。__exit__扇区接收从块中产生的异常。您可以决定如何处理异常。如果您什么都不做,那么在资源被正确关闭后,异常将被引发给调用方。或者您可以根据异常类型处理__exit__块中的异常。但是总的原则是不要默默地接受错误。另一个通用提示是不要在__exit__块中返回True,除非你知道你在做什么。返回True将忽略所有的异常,它们不会被提交给调用者。
def stop_db():
# stop databasedef start_db():
# start databasedef backup_db():
# backup databaseclass DatabaseHandler:
def __enter__(self):
stop_db()def __exit__(self, exc_type, ex_value, ex_traceback):
start_db()with DatabaseHandler():
backup_db()
- 使用
**contextmanager**装饰器。你不必每次都创建一个类。想象一下,您希望将现有的功能转变为上下文管理器,而不需要过多地重构代码。在这种情况下,您可以使用装饰器。装饰者本身是另一个话题。但它本质上做的是把原来的函数变成一个生成器。yield之前的所有内容都是__enter__的一部分,产生的值成为as之后的变量。在这个例子中,不需要放弃任何东西。一般来说,如果您只需要一个上下文管理器函数而不需要保留太多状态,这是一个更好的方法。
import contextlib@contextlib.contextmanager
def db_handler():
try:
stop_db()
yield
finally:
start_db()with db_handler():
db_backup()
- 基于
**contextlib.ContextDecorator**创建一个装饰类:第三个选项是创建一个装饰类,它是前两个选项的混合。您不用使用仍然可以使用的with语句,而是将它用作函数顶部的装饰器。这样做的好处是,通过简单地将装饰器应用于其他函数,您可以任意多次地重用它。
class db_handler_decorator(contextlib.ContextDecorator):
def __enter__(self):
stop_db() def __exit__(self, ext_type, ex_value, ex_traceback):
start_db()@db_handler_decorator()
def db_backup():
# backup process
哇,对于一个项目来说,这是一个相当长的部分。我不会在上下文管理器上深入讨论太多。但是总的建议是,即使你是一个初学者,你也应该至少理解它的工作原理。作为一名中级或专家,您可以尝试从头开始创建一些上下文管理器,以发现它更多的本质。
发电机
在上一篇文章中,我提到了一个叫做生成器的概念,这也是区别于 Python 的一个独特的特性。生成器是一个 iterable,它定义了一个next()方法。但特别的是你只能迭代一次,因为它们不会把所有的值都存储在内存中。
生成器被实现为一个函数,但是它没有像常规函数一样使用return,而是使用了yield。
def generator():
for i in range(10):
yield i**2print(generator)
# <function generator at 0x109663d90>
你会看到这在asyncio中被大量使用,因为协程本质上是一个生成器。然而,它的优势之一是减少内存使用,这可能会对大数据集产生巨大影响。假设我想对 1M 的记录进行一些计算。
在了解yield之前,你会这样做。问题在于,你必须将所有 100 万条记录的结果存储在内存中。
def calculate(size):
result = []
for i in range(size):
result.append(i**2)
return resultfor val in calculate(1_000_000):
print(val)
这是一个使用yield的替代方案。只在轮到它的时候才计算结果,从而节省了大量的内存使用。
def calculate(size):
for i in range(size):
yield i**2for val in calculate(1_000_000):
print(val)
生成器也是懒惰求值背后的秘密,对此我写了另一篇文章。请随意检查。
命名空间和范围
作为Python 的禅的最后一行,我们来谈谈 Python 中的命名空间和作用域。名称空间是 Python 中的一个系统,用于确保所有名称(属性、函数、类、模块)在程序中是唯一的。在 Python 中,名称空间作为一个字典来管理,其中键是对象名,值是对象本身。
一般来说,Python 中有 4 种类型的名称空间:Python 内置的、全局的、封闭的和按层次排序的局部的。这个图也被称为 LEGB 规则。解释器首先在本地搜索名称,然后是封闭的,然后是全局的,最后是内置的,这意味着低级别的名称(例如本地的)将覆盖高级别的相同名称(例如封闭的)。

由创作高
它如何影响我们的编码?大多数时候,如果你只是遵循 LEGB 规则,你不必做任何特别的事情。这里举个例子。在继续前进之前想一想。产量是多少?
val = 1def outer():
val = 2
def inner():
val = 3
print(val)
inner()print(outer())
print(val)
根据 LEBG 规则,较低的级别应该覆盖较高的级别。在函数inner()中,val的值为 3,所以调用函数outer()将返回 3。然而,如果你只是像print(val)一样打印出val,你将得到 1,因为你目前在函数之外,并试图访问全局值val = 1。
但是如果你想从较低的级别修改一个全局值,这可以用global关键字来实现。你需要的是在你想要改变全局值的地方加上global val。
val = 1def outer():
val = 2
def inner():
**global val** val = 3
print(val)
inner()print(outer()) # 3
print(val) # 3
这只是一个声明,像global val = 3这样的语法是不正确的。另一个选择是globals()[“val”] = 3。
可变默认参数
最后但同样重要的是,我想向您展示一个 Pythonic 警告,您可能认为这是一个 bug ,但实际上是一个特性。尽管它令人困惑,但它仍然是每个人都必须与之相处的 Pythonic 特性。
考虑下面的例子。功能add_to_shopping_cart将food加到shopping_cart上。如果没有提供,shopping_cart默认为空列表。在这个例子中,在没有提供shopping_cart的情况下调用这个函数两次应该会得到两个列表,每个列表有一个元素。
def add_to_shopping_cart(food, shopping_cart = []):
shopping_cart.append(food)
return shopping_cartprint(add_to_shopping_cart("egg"))
# ["egg"]
print(add_to_shopping_cart("milk"))
# ["egg","milk"]
但这是实际发生的事情。解释是——变量shopping_cart是在函数的定义上只创建一次,也就是这个函数第一次被调用的时刻。从那时起,Python 解释器将在每次调用函数时使用相同的变量,这意味着每当值改变时,Python 将把它传递给下一次调用,而不是用默认值重新创建它。
修复很简单——使用None作为缺省的 sentinel 值,并在函数体中分配实际的缺省值[]。由于名称空间和本地范围的原因,shopping_cart将在每次None时被重新创建。
def add_to_shopping_cart(food, shopping_cart=None):
shopping_cart = shopping_cart or []
shopping_cart.append(food)
return shopping_cartprint(add_to_shopping_cart("egg"))
# ['egg']
print(add_to_shopping_cart("milk"))
# ["milk"]
我的经验是不要改变可变的默认参数,除非你知道你在做什么。
写一个 Pythonic 库
到目前为止,我们讨论的都是每一个 Python 特性。当谈到编写 Python 库或框架时,我们还应该考虑如何设计 Python API。除了遵循常见的 Python 习惯用法,旨在被其他人使用的接口通常比其他语言更小、更轻量级。如果这个库过多地重复发明轮子,就会被认为不是 Pythonic。考虑到“只有一种方法”,最好将另一个第三方软件包安装到您的库中。
另一个通用的提示是,不要仅仅为了遵循像 Java 这样的设计模式而编写样板代码。一个例子是如何用 Python 写一个 singleton。
其他免费资源
我没有提到的是一些基本的 Pythonic 表达,比如用for i, color in enumerate(colors)代替for i in range(len(colors))。这里有一些很棒的 Youtube 视频,让你重温一下知识。
https://www.youtube.com/watch?v=OSGv2VnC0go(将代码转换成漂亮的、惯用的 Python)
https://www.youtube.com/watch?v=x-kB2o8sd5c(一条蟒蛇?美学:美丽和我为什么是蟒蛇)
你也可以看看 2003 年的这个帖子,当时人们在讨论什么是 Pythonic。很有趣的一个!我喜欢这一段:
Unpythonic 正在进行大量的类型检查,或者非常努力地使
成为私有/受保护的东西。或者使用索引来循环遍历列表
,而不仅仅是“在我的列表中查找项目”。基本上,人们做的任何事情都是因为他们在其他语言中是这样做的,认为它已经尽善尽美了。
结论
谢谢你能来这里。感谢您的宝贵时间!这篇 15 分钟的文章只是关于python特性的冰山一角。除了每一项和文章中没有包括的许多其他项目之外,还有很多要说的。无论如何,我希望这能启发你重新审视你的 Python 代码,并能给你的同行更多有价值的评审意见。任何想法都欢迎在评论区发表!
如何将 SQL Server 查询结果写入 CSV 和 JSON 文件
SQL、SQL Server 和数据工程
这种技术既快速又简单

Pixabay 的照片:https://www.pexels.com/photo/stock-exchange-board-210607/
有时,当我在 Tableau 中创建数据可视化仪表板时,Tableau 直接从关系数据库中读取源数据表。但这并不总是可能的。例如,Tableau 服务器环境可能不具备访问数据库的凭证。或者数据很少改变,使得对文件的访问更有效。在这种情况下,我可能会从 Excel 电子表格、CSV 文件或 JSON 文件中获取数据。本教程将向您展示如何对 Microsoft SQL Server 运行选择查询,并将结果数据写入 CSV 或 JSON 文件。
本文中使用的工具
为了准备这篇文章,我使用了下面列出的工具。如果您想继续学习,但没有所需的工具,可以参考安装免费版本的说明。
- 微软 SQL Server——我用的是这个数据库管理系统(DBMS)的免费版本,叫做 SQL Server 2019 Express,你可以在这里下载。
- 微软 SQL Server Management Studio(SSMS)—我使用 SSMS 管理 SQL Server 数据库,并创建、测试和运行 SQL 查询。其他工具可能可用,但这个工具是免费的,在这里可用。
- SQL Server 数据库—SQL Server 的实例必须包含一个数据库,该数据库包含要对其运行 SQL SELECT 查询的数据。对于我的示例查询,我使用微软名为 AdventureWorks2019 的示例数据库。要设置它,您可以从这里下载 AdventureWorks2019.bak 文件并在您的 SQL Server 实例中恢复它。您可以使用 SSMS 来执行此任务。
- JSON 在线验证器—JSON 格式的数据可以粘贴到这个免费在线工具中,以验证其格式是否正确。
示例查询
对于本演示,我编写了一个 SQL SELECT 查询,该查询获取 Adventure Works 数据库中所有客户的唯一 ID 和位置(城市、州或省、邮政编码以及国家或地区)。如下所示,它从vindividalcustomer视图中选择数据。请注意,可以取消对“TOP (5)”子句的注释,以限制返回测试的行数。
将查询结果写入 CSV 文件
要对 AdventureWorks2019 数据库运行示例查询并将其结果保存到 CSV 文件,请按照下列步骤操作:
1.打开 SSMS 并连接到您的 SQL Server 实例。
2.在对象资源管理器中,右键单击 AdventureWorks2019 数据库名称。然后,单击[新查询]。
3.将上一节中显示的 SQL 查询粘贴到查询窗口中。
4.确保将查询输出设置为查询下方的网格。为此,在主菜单中,在查询窗口中单击。然后,从主菜单中点击[查询]、[结果到],最后点击[结果到网格]。
5.单击功能区菜单中的[执行]运行查询。
6.查看结果网格中的数据。它应该看起来像下面的截图。
7.右键单击结果网格中的任意位置,如下所示。点击[将结果另存为…]。出现提示时,选择您选择的输出文件夹。然后,输入文件名(在本例中,我输入了“数据”),并选择文件类型“CSV(逗号分隔)”(*。csv)。点击[保存]保存文件。

从 SSMS 查询结果网格中保存 CSV 文件。图片由 Randy Runtsch 提供。
接下来,在文本编辑器(如记事本或 Excel)中打开文件,检查其内容。它应该看起来像下面显示的记事本截图中显示的数据。

查询结果集以 CSV 格式存储在文件 data.csv. Image 中。
如果一切正常,文件已正确保存到指定的 CSV 文件中。它可以用于输入到另一个工具,如 Tableau,用于数据可视化,甚至 Excel。例如,它也可以导入到另一个数据库中。
将查询结果写入 JSON 文件
将 SQL 查询结果写入 JSON 文件就像将它们写入 CSV 文件一样简单。但它还涉及到一个步骤。按照上述 CSV 文件说明中所列,执行步骤 1 至 5。然后,将子句“FOR JSON PATH”添加到查询的末尾,如下面的代码所示。继续第 5 步和第 6 步。运行查询应该只返回两行输出行;一个以“JSON”开头的隐藏标题,后跟一个数据行(包含 JSON 格式的所有数据)。
将输出写入 JSON 格式的 SQL。由 Randy Runtsch 编写的代码。
现在,按照以下说明将输出保存到一个 JSON 文件:
在结果网格中,仅单击以“JSON”开头的 cyptic 值下面的行中的结果集,如下面的屏幕截图所示。点击[将结果另存为…]。出现提示时,选择您选择的输出文件夹。然后,输入扩展名为“”的文件名。例如,名为“data.json”的文件是有效的。接下来,选择文件类型“全部(。)."最后,点击[保存]保存文件。

以 JSON 格式存储在文件中的查询结果集。图片由 Randy Runtsch 提供。
要检查文件中的 JSON 数据结构,请取消查询中的“TOP (5)”注释。然后,重新运行它,只返回前五行。复制 SSMS 结果网格中的结果数据,如下所示。然后粘贴到 JSON 在线验证器工具中,点击【验证 JSON】。数据应该是 JSON 格式的,如下面的第二个截图所示。

右键单击以“JSON”开头的行下面的灰色行,复制 JSON 格式的数据。然后,单击[复制]。图片由 Randy Runtsch 提供。

使用免费的 JSON 在线验证器来确保 SQL Server 正确格式化来自 SQL 查询的记录。图片由 Randy Runtsch 提供。
摘要
本教程描述了如何将 SQL Server SELECT 查询的输出保存到 CSV 文件和 JSON 文件中。从 SQL Server 数据库获取数据的人,如数据工程师、数据分析师、数据科学家和软件开发人员,可能会受益于这种将数据从 SQL Server 数据库移动到其他工具的技术。
如何编写自己的 GitHub 动作
原文:https://towardsdatascience.com/how-to-write-your-own-github-action-59cc4746a57a
使用 GitHub 工作流完善您的 CI/CD 工具箱

照片由 pix abay:https://www . pexels . com/photo/aroma-aromatic-distriction-bottles-531446/
GitHub 简要介绍时间表
自从微软接手以来,GitHub 已经发生了很大的变化,在大公司的集团性质中,或者说尽管如此。在 GitHub 中,CI/CD 管道得到了极大的改进。 GitHub 提供了许多好的工作流程和其他由开源社区在最近几年开发的工作流程,随着技术的发展,还会有更多的工作流程出现。
这为许多机会打开了大门,因为许多公司已经将他们的源代码保存在 GitHub 中,GitHub 是市场上的第一批玩家之一[source]!
虽然这听起来很可怕,但让一家公司比其他公司更强大也可以带来许多可能性和创新,因为社区的牵引力将推动创造越来越多的价值,最终将使每个人受益。
什么是 GitHub 动作?
尽管对许多工程师来说这很琐碎,但对初学者来说,定义这个术语是至关重要的。GitHub Action 是您在 GitHub 中拥有一个或多个存储库的帐户后获得的 CI/CD 管道,每个存储库都有其管道工作流。
它使您能够定义您希望在一组指定的触发器上发生的所有步骤。以下是这种“行动”的一些例子:
- 当一个拉请求进来时,在你的存储库上运行测试。
- 在每次推送时,在您的项目上运行特定于语言的 linters。
- 发布新版本时部署到开发/生产。
这些只是 GitHub Action 的几个例子,根据项目的需求,您还可以想到更多的例子。

照片由 nappy:https://www . pexels . com/photo/man-weaking-white-sweet-and-black-shorts-about-to-run-936094/
为什么要编写定制的 GitHub 动作?
我们生活在 21 世纪,几乎每个软件问题都已经解决了;您只需要足够努力地寻找问题领域,将材料结合在一起,并最终以符合您需求的方式配置整个平台。
对于 GitHub 动作来说都是一样的,因为已经有许多工作流定义可以放入项目的 CI/CD 定义中,传入正确的值并得到您期望的结果。
这将我们带到一个点,你可能不需要从头开始重写东西。
但即使如此,也可能有这样的情况:
- 可用的解决方案不适合你的问题
- 问题的复杂性和可用的解决方案不一致
- 该解决方案在配置方面不够灵活
所有这些以及更多的理由都足以让你编写自己的定制 GitHub 动作,这也是本文的主旨。
我们将深入研究编写自定义 GitHub 动作的不同方法,并提供每种方法的实际例子。
如何编写自定义 GitHub 动作?
撰写本文时,有三种方法可以编写 GitHub Action。下面的方法可以用来创建和发布 GitHub 动作,可以是开源的,对社区公开,也可以是你自己的。
可用的 GitHub 操作类型有:
- JavaScript:如果你是 1750 万 JS 开发者中的一员,或者如果你是 7 万 Stackoverflow 调查对象中的 65%中的一员,你很可能会喜欢这种方法,因为你可以用你喜欢的语言编写你的逻辑。
- Docker:如果你和我一样不是 JavaScript 开发人员,你会发现这个非常有趣,因为任何可以被容器化的东西都可以作为 GitHub Action 发布;如今,由于许多伟大工程师的努力,这意味着任何应用。
- 复合:最后但并非最不重要的,我非常喜欢的一个,允许你将多个 GitHub 动作合并成一个,也是作为“可重用工作流”出售给公众的。
由于我不是 JavaScript 开发人员,不仅仅是能够阅读并在一定程度上理解它的功能,我不会把更多的注意力放在那里,而是放大我感觉最强的地方:Docker & Composite。

Victoria Akvarel 的照片:https://www . pexels . com/photo/two-boys-playing-soccer-ball-side-cars-1564868/
GitHub 动作通用定义
选择任何一种编写 GitHub 动作的方法,你都需要一个关键的文件作为 GitHub 动作的定义。
该文件的名称必须精确到action.yml,并且它应该位于您的项目的根目录下。除了许多其他事情之外,下面是您在该定义文件中指定的内容:
- 输入是什么
- 如何调用操作,即执行什么文件或二进制文件
- 人们可以期望得到什么样的输出
像许多其他伟大的定义文件一样,这个定义是 YAML 格式的,是最有争议的格式之一。这里有一个取自 GitHub 文档的样本action.yml。
该文件有三个主要部分:所有其他选项都是可选的。
name:这是分配给动作的唯一名称。这个名字作为 ID,要求是唯一的,因为会给它一个 URLgithub.com/marketplace/actions/MY_GITHUB_ACTION,不能重复;这只适用于打算发布到 GitHub Marketplace 的公共操作,否则不需要是唯一的。inputs:动作将接收的一组名称作为其参数。想想kubectl get pod --output=yaml,在这种情况下,“输出”是kubectl二进制的一个参数。- 任何动作的基本部分是你定义动作的“类型”,上面描述的三种类型之一。该“类型”在键
using下指定,并告诉“跑步者”如何跑你的动作。使用docker时,需要用属性image指定 docker 文件的路径。args是不言自明的,因为它将把inputs传递给底层实现。
这个定义,当正确指定时,将告诉 GitHub Runner 如何运行您的动作。它充当入口点;没有它,存储库中定义的逻辑将不会被执行,因为运行者不知道如何执行。
现在我们已经定义了 GitHub 动作的轮廓,让我们通过一些实际的例子来完成这个论证。
第 1 幕:使用 Docker 编写您的第一个 GitHub 动作
容器化的应用程序在最近几年获得了很大的发展,这是有原因的。它们允许你用你选择的编程语言编写你的应用程序,把它放在自己的“胶囊”里,并把它运送到任何地方,至少几乎 。
即使有特定于平台的映像构建的限制,您仍然能够创建内容并提供给更广泛的受众;与裸机是唯一选择的时代相比,例如,需要保持安装在所有机器上的 JVM 版本的同步!
现在让我们进入实际的代码,在这里我们将开发一个实际的 GitHub 动作。
要求:在每次 GitHub 发布时发送电子邮件通知
随着介绍、历史和定义的结束,是时候写一些代码了。在这段代码中,我们将向每一次 GitHub 发布的人员列表发送一封电子邮件。
实现逻辑是语言不可知的,但实现不是。这里我们将编写一个 Python 应用程序,使用 SendGrid 的 SDK 来完成这项工作。
如您所见,代码并不复杂;它将接收所需的输入,准备电子邮件,并相应地发送它。几个调试行帮助用户理解我们所处的阶段,一个异常处理除了在标准错误上打印出来之外什么也不做。
使用 Markdown 库是因为我们将获取发布说明并以 Markdown 格式将其传递给 GitHub 动作。
容器化应用程序
编写一个Dockerfile是让我们能够在一个隔离的环境中,随时随地,在任何操作系统上运行应用程序的关键。
如果你对如何写一个合适的 docker 文件的提示和技巧感兴趣,看看下面的链接。
https://medium.com/skilluped/10-tips-on-writing-a-proper-dockerfile-13956ceb435f
如上所述,应用程序的定义可能如下所示:
当然,send-email-with-sendgrid.py必须用正确的命令来执行。要使它可执行,您将运行chmod +x FILENAME。
定义 GitHub 动作
到目前为止,我们只编写了我们的逻辑,但是如果没有一个很好的和适当的定义action.yml,它就不能用于 GitHub 动作,这就是下一步要做的事情。
我们将需要一个 YAML 格式的文件,它将定义动作的名称,输入,以及如何运行它,即如何传递参数。
这是这样一个文件的样子。
在action.yml中定义的args是作为 [cmd](https://stackoverflow.com/a/21564990) 传递给 docker 容器的。这些参数与上面 Python 应用程序中用argparse定义的参数相同。
发布到 GitHub 市场
我们已经编写了将应用程序作为 GitHub 动作运行所需的所有逻辑实现代码。然而,作为最后一步,如果我们想在market place中发布我们的应用并使其可被搜索,我们将需要创建一个新版本,这可能是最容易的一步。
作为演示,下面提供了截图。



最后一个复选框非常重要,因为它表示您希望在市场上发布您的 GitHub 操作,有效地允许其他人使用它。最终,你会在市场上看到你的 GitHub 行为,类似于下图。

GitHub 发布后,您和其他人将能够使用 GitHub 操作,使用下面的 YAML 格式的工作流,该工作流位于存储库的.github/workflows/目录下。
这是最简单不过的了。当然,你可以变得更有创意,做更多新奇的东西!
源代码
所有文件都可以通过下面的链接获得。
https://github.com/licenseware/send-email-notification/
第 2 幕:使用 Shell 编写第二个 GitHub 动作
理解 GitHub Runners 在每次被触发时都包含在一个新的虚拟机中是至关重要的&它们将在完成时被处理掉[ source ]。这意味着您在之前的运行中所做的工作将不可用,除非您保存了结果,例如 GitHub 工件或 GitHub 缓存。
也就是说,您可以组合工作流文件的多个阶段,并将其发布为单个 GitHub 操作,允许您作为一个统一的整体“重用”它,因此得名“可重用工作流”
这种类型的 GitHub 动作被称为“复合”顾名思义,它由其他步骤组成。对于多个存储库需要不同的 CI/CD 步骤的场景,这是非常理想的。您不希望到处重复相同的步骤,而是希望维护一个存储库并相应地更新。
需求:将 Helm 版本部署到 Kubernetes 集群
我们希望在每次对默认分支进行新的推送时,将带有一组可配置标志的 Helm Chart 部署到特定的 Kubernetes 集群。这需要以下步骤:
- 安装一个特定的头盔版本,最好是可配置的。
- 获取 Kubeconfig 和 Helm 值文件,作为 YAML 编码的字符串或作为底层操作系统中的文件路径,例如,从 AWS S3 获取。
- 根据用户请求更改 Kubeconfig & Helm 值文件的文件权限。
- 使用可配置的发布名称部署实际图表。
- 出于安全原因,清理用户请求,即删除 Kubeconfig 和 Helm 值文件!
下面是 GitHub 动作定义文件中这样一个需求的样子:
注意.runs.using下的值是composite,这意味着我们将在同一个action.yml文件中定义可运行的步骤。
这些“步骤”与您放在.github/workflows/下的任何文件中的步骤是一样的,唯一的区别是shell属性,它是每个步骤的必需键。它告诉跑步者命令应该如何运行。此处记录了可用外壳的列表。
该文件的语法是不言自明的,也是人类可读的。您在每个步骤的run属性下看到的一切都是一组 bash 可执行的命令。
您还可以将任何 Python 或 JavaScript 文件放在同一个存储库中,并从同一个action.yml文件中调用该文件,使您的 GitHub 操作更加强大和灵活。
这个怎么用?
使用上面的 GitHub 动作,您将有效地拥有一个类似于下面的工作流定义。
源代码
所有文件都可以通过下面的链接获得。
https://github.com/licenseware/helm 
照片由 Nout Gons 拍摄:https://www . pexels . com/photo/road-in-city-during-sunset-248159/
结论
CI/CD 使我们的运营变得更加轻松,使我们能够灵活地进行开发,并消除了手动和可重复部署的负担。在 21 世纪,随着许多旨在自动化可重复工作的工具和技术的兴起,CI/CD 已经成为最成功的工具之一,是当今每个公司不可或缺的一部分,使它们不再是一种奢侈品,而是将可维护性保持在最大水平的要求。
GitHub Actions,最著名的 CI/CD 工具箱之一,提供了很多功能。为了与当前的 CI/CD 解决方案竞争,GitHub Actions 在提供可扩展性方面取得了长足的进步,这样人们就可以为特定的需求创建和发布他们的解决方案。您将能够找到许多现成的工作流程,为您的生产之战做好准备。
在本文中,我们给出了实际的例子,说明当您找不到现成的动作时,如何编写一个实际的定制 GitHub 动作。有了这些知识,你将会不可阻挡地为自己、公司和社区创造价值。
祝你今天休息愉快。敬请期待,保重!
参考
如果你喜欢这篇文章,看看我的其他内容,你可能也会觉得有帮助。
https://medium.com/skilluped/what-is-iptables-and-how-to-use-it-781818422e52 https://medium.com/amerandish/clean-architecture-simplified-223f45e1a10 https://medium.com/amerandish/a-tmux-a-beginners-guide-7c129733148 https://medium.com/geekculture/patch-your-dependencies-like-a-boss-de757367010f https://medium.com/skilluped/stop-writing-mediocre-docker-compose-files-26b7b4c9bd14
跟踪数据如何帮助我坚持锻炼了 2 年
自由复制 R 代码,开始跟踪并通过个人项目扩展您的数据科学组合,同时变得更加健康。

按活动类型分类的过去 2 年中每月每天的平均持续时间(按作者分类的图片)
用詹姆斯·克利尔的话来说,你的日常行为为你的身份提供了证据。多亏了追踪,我几乎改变了我的身份。定期锻炼现在是我生活中不可或缺的一部分。
这篇文章是写给每一个希望开始一个新的个人数据科学项目来跟踪他们的锻炼的人的。所有的代码都可以在 Github 上免费获得,你可以复制和修改。
如果你是一个有经验的程序员,即使你有不同的设备,你也可以很容易地跟上(你需要做适当的调整)。如果你是一个很少或没有编程经验的初学者,你仍然能够使用你的 Fitbit 数据实现你看到的一切。
以下是我从 2020 年 4 月 1 日到 2022 年 5 月 31 日两年旅程的总结。开始日期是疫情的开始。这与我们迎来一个女婴来到这个世界的时间不谋而合。由于国内的不确定性和重大变化,我担心这会给个人和职业带来沉重的打击。两年过去了,由于数据科学和跟踪,我对进展感到满意!

两年总结(图片由作者提供)
我总共进行了 998 次锻炼,相当于锻炼了 638 个小时。我的大部分锻炼是由椭圆训练组成的。总的来说,这相当于两年内每天坚持锻炼 48 分钟。
该数据也证实了(在各种活动中)跑步是消耗卡路里最省时的方法(每分钟消耗 13.7 卡路里)。然而,总的来说,全功能训练器要好得多,因为你更有可能做得更多更久。
Fitbit 分析深度剖析演练
如果你已经拥有一台 Fitbit 设备,那么你需要做的第一件事就是下载所有的 Fitbit 数据。每个国家都有自己的司法管辖区,但通常情况下,根据法律,你有权使用你的数据,并可以要求完全访问。以下是你如何登录到你的 Fitbit 账户,并请求下载你所有的数据。
首先,去https://accounts.fitbit.com/login用你的账户信息登录。点击右上角的设置齿轮图标(如下所示)。

登录 Fitbit 后点击设置(图片由作者提供)
进入“设置”后,点击“数据导出”并选择“请求数据”,这将下载 Fitbit 保存的所有数据(如下所示)。

点击 Fitbit 仪表盘中的“数据导出”和“请求数据”(图片由作者提供)
按照指示,它将触发 Fitbit 的自动电子邮件(如下截图)。

确认 Fitbit 电子邮件中的导出请求(图片由作者提供)
一旦您确认您确实发起了请求,数据生成过程将开始。

Fitbit 数据导出处理(图片由作者提供)
Fitbit 的数据结构
从 Fitbit 下载的数据将以单个 zip 文件的形式出现。一旦你解压它,它将有 12 个不同的文件夹。您可以在每个文件夹中找到的相关自述文件中找到有关每个文件夹内容的更多信息。根据设备的用途,许多文件夹可能是空的(这完全没问题!).
出于本文的目的,我们将关注名为“身体活动”的文件夹中的选定文件。该文件夹包含几乎所有与身体活动相关的数据,包括以下数据:
-活动区域时间(每月一个文件,CSV 格式)
-练习类型和持续时间(每 100 节课一个 JSON 格式的文件)
-心率(每天一个 JSON 格式的文件)
-VO2 最大数据(以 JSON 格式存储)
-不同心率区间的持续时间(JSON 格式,每天一个文件)
正如您所注意到的,大多数数据都是以 JSON 格式存储的。
JSON Lite 库和 JSON 数据格式
JavaScript Object Notation(缩写为 JSON)是一种用于存储结构化数据的开放标准文件格式。这种格式是人类可读的,由属性-值对和数组组成。JSON 文件格式以扩展名”存储。json”。现在设备将数据存储在 JSON 中非常普遍。这种格式与语言无关,您使用的大多数软件都可能包含现有的代码库来生成和读入(解析)JSON 文件。
这里有一个 JSON 文件的例子(run_vo2_max- date ),你可以在你的身体活动文件夹中找到。

JSON 格式示例(图片由作者提供)
在我们可以分析 Fitbit 数据之前,我们需要将它读入 r。我们将使用 r 免费提供的“jsonlite”库。如果您以前没有使用过这个库,请首先从 RStudio 中安装它。安装完成后,加载库。
我们将使用的另外三个库是 lubridate、dplyr 和 ggplot2。dplyr 和 ggplot2 是 tidyverse 生态系统的一部分,一旦你加载“tidyverse”,它们就会自动加载。
rm(list=ls())
library(jsonlite)
library(tidyverse)
library(lubridate)
dplyr 用于数据角力,ggplot2 用于数据可视化,lubridate 是一个方便的操作日期的库。您将很快看到帮助跟踪锻炼数据的实际例子!
导入数据并存储在数据帧中
现在让我们看一下实际的代码。所有这些代码都可以从 GitHub 下载。
我们将查看位于“体育活动”文件夹中的练习文件。Fitbit 自动在每个练习文件中存储 100 个会话。您可以直观地查看文件名,以确定您拥有的全部练习文件。就我而言,我总共进行了 1400 多次会话。
folder_path <- 'D:/Personal/My-2Year-Review/OxguruSomeone/'
folder_name <- 'Physical Activity/'
file_name_part_start <- 'exercise-'
file_name_part_end <- seq (from = 0, to=1400, by=100)
file_extension <-'.json'
下面的 for 循环遍历文件总数,然后逐个加载它们。最后,你会得到一个包含所有练习的单一数据框架。
for (k_part in file_name_part_end){
file_selected <- paste0(folder_path,folder_name,file_name_part_start,k_part,file_extension)
data_loaded<-fromJSON(file_selected)
if(k_part == file_name_part_end[1])
{
data_loaded<-data_loaded%>%
select(all_of(sel_names))
data_all <- data_loaded
rm(data_loaded)
}
else
{
data_loaded<-data_loaded%>%
select(all_of(sel_names))
data_all<-rbind(data_all,data_loaded)
rm(data_loaded)
}
}
生成一个汇总表
我们现在可以使用数据框架来获得练习会话的高级摘要。这可能包括诸如每种锻炼类型的总次数、总持续时间和卡路里燃烧率。
time_start=ymd('2020-04-1')
time_end=ymd('2022-03-31')
activity_type=c('Elliptical','Run','Walk','Outdoor Bike')data_selected<-data_all%>%
filter(activityName %in% activity_type)%>%
mutate(startTime=mdy_hms(startTime))%>%
filter(startTime>=time_start)%>%
filter(startTime<=time_end)%>%
arrange(startTime)data_summary<-data_selected%>%
group_by(activityName)%>%
mutate(total_duration=sum(duration)/(1000*60*60))%>%
mutate(total_sessions=n())%>%
mutate(longest_session=max(duration)/(1000*60))%>%
mutate(shortest_session=min(duration)/(1000*60))%>%
mutate(total_calories=sum(calories))%>%
mutate(mean_heartRate=mean(averageHeartRate))%>%
select(-averageHeartRate,-calories,-startTime)%>%
filter(row_number()==1)
堆积条形图,每天一根
让我们绘制一个堆积条形图,显示整个两年期间每天锻炼的总持续时间。这样的图表有助于你观察你的日常锻炼。看这样的图表,你可以很容易地确定非常活跃的时期和安静的时期。
为了生成这样一个图,我首先使用了 dplyr 函数和 lubridate 来为现有的 dataframe (data_all)创建额外的列。由于一天中可能会有多次训练,我们需要合计某一天所有训练的持续时间。我们通过创建一个新的列(四舍五入到最近的日期“单位”)来实现这一点,然后对这个新创建的列使用 group_by(参见下面的相关片段)。
data_activity<-data_all%>%
filter(activityName %in% activity_type)%>%
mutate(startTime=mdy_hms(startTime))%>%
filter(startTime>=time_start & startTime<=time_end)%>%
mutate(startTime_round=round(startTime,unit="day"))%>%
group_by(startTime_round,activityName)%>%
mutate(duration_per_day=sum(duration/(1000*60)))%>%
mutate(total_calories=sum(calories))%>%
mutate(mean_heartRate=mean(averageHeartRate))%>%
filter(row_number()==1)
这是显示每日锻炼持续时间的堆积条形图。

显示疫情第一年每天锻炼持续时间的堆积条形图(图片由作者提供)
当然,你可以得到类似的卡路里和心率图。
堆积条形图,每月一张
每日堆积条形图有助于了解你的每日最大产能。然而,为了更好地了解我们在更长时间范围内的能力,获得一个更加综合的衡量标准也是值得的。
下图显示了两年内每月的总持续时间。我在 2020 年 6 月得到了一个交叉训练器,接下来的一个月,我在一个月内锻炼了 60 个小时。这显然是不可持续的,于是我逐渐减少了每月的持续时间。

前两年每月锻炼的总持续时间(图片由作者提供)
除了每月的总持续时间之外,我们还可以计算日平均持续时间(如下所示)。从图中可以看出,我在 2 年的时间里,有 8 个月超过了日均 60 分钟,几乎都超过了日均 30 分钟。

前两年每月平均锻炼时间(图片由作者提供)
识别锻炼时间的极坐标图
为了更好地了解你更有可能锻炼的时间,你可以绘制一个直方图,计算每小时的总次数。与 60 分钟的单个会议相比,如果两个短会议(比如每个 10 分钟)被显示为高标准,这没有多大意义。为了解决这个问题,您可以很容易地使用颜色/阴影按长度对会话进行分类。
下面显示了一个 10 周期间的直方图,其中会话按持续时间进行了分类。直方图用极坐标显示,我觉得这更直观。

极坐标上的直方图可以确定我一天中最喜欢锻炼的时间!(图片由作者提供)
从图中,我可以看到我的大部分会议发生在清晨或夜间。上图涵盖了 2020 年 6 月期间,我平均每天约 2 小时。在这里,我也在午夜时分进行了相当多的治疗。这显然是不可持续的,我后来默认了更可持续的东西(如下所示)。我的大部分疗程都在早上。

极坐标上的直方图确定了一天中我锻炼较少的最喜欢的时间!(图片由作者提供)
相关图
这里有一些额外的情节,以帮助回答更多的问题。下图调查了每次持续时间和平均心率之间的相关性。缺乏相关性表明你可以长时间保持相同的强度,最长可达 60 分钟。

持续时间与强度(图片由作者提供)
下图调查了卡路里和持续时间之间的关系。有很强的相关性。也许并不奇怪,你运动的时间越长,燃烧的卡路里就越多。

持续时间与卡路里(图片由作者提供)
下图描绘了两年期间的最大摄氧量。最大摄氧量是衡量心血管健康的一个替代指标,可以衡量我们在剧烈运动时身体消耗氧气的情况。Fitbit 设备根据步速和心率之间的关系来估计这一指标。因为这个测量值只是在你户外跑步时估计的,所以我有很长一段时间没有数据点。然而,即使你从事不同的活动,而不是跑步,你的健康指数仍然会提高。下图清楚地证明了这一点。

一段时间内的最大摄氧量(图片由作者提供)
最后的想法
本文中的分析并不详尽。这里还有更多我没有呈现的数据(比如每分钟的心率和睡眠追踪数据)。
你实际上不需要知道所有你能先验承担的可能的分析类型。你可以开始追踪你自己的数据,让你自己的环境和好奇心来驱动和塑造这个项目。
2009 年(那时我才 20 出头),我的室友出去跑步。我满怀改变生活的动力,加入了他的行列,并决定开始定期锻炼。5 分钟后,我气喘吁吁,我只是停下来,并得出结论,定期,持续的运动对我来说是不可能的。那时候,我很无知,对自己的能力没有自知之明。13 年后,随着超过 1000 次会议的跟踪,我是一个不同的人。
一旦你开始跟踪你的疗程,随着时间的推移,你会对自己的能力有一个敏锐的认识。你会知道你一天、一周、一个月、一年能锻炼多少。你还会知道一天中什么时候你可以锻炼,适合你的日常习惯,以及你喜欢的锻炼类型。你将能够更好地设定现实的、可实现的目标来维持或发展。
https://ahmarshah.medium.com/membership
第一类误差、置信区间、第二类误差和功效是如何相互关联的
比较正态分布及其度量

作者图片
统计学的一个基本部分取决于正态分布,考虑到由于中心极限定理几乎所有分布都收敛于正态分布,这是有意义的。
如果你学过统计学 101 课程,你应该熟悉一个 t 检验,并且知道它比较两个正态分布。然而,你可能没有深入了解我在下面介绍的 4 个指标(尤其是它们是如何可视化的),因为在典型的统计学 101 课程中,它们经常被跳过或忽略。我希望这篇综述能帮助你更深入地理解这些基本的统计学概念。
你在统计测试中比较什么?🤔
当您对数据进行 t-test(一种统计测试)时,您到底在比较什么?您正在检查数据是否来自某个正态分布。一个简单的统计测试将设置零假设等于零。常见统计检验的示例包括实施 t 检验,其中零假设是总体均值等于零,或者另一个示例是线性回归,其中零假设是斜率等于零。
理解错误的概率对于解释统计测试的结果至关重要。当比较你的结果是正确的还是不正确的时,用两个选项(即类别)形象化这个决定的一个常用方法是使用混淆矩阵。典型的混淆矩阵可能如下所示:

混乱矩阵。作者图片
如果我们在进行统计比较时更新混淆矩阵以应用于正在进行的比较,它将看起来像这样:

带有统计项的混淆矩阵。作者图片
这种视觉效果将帮助你记住 I 型和 II 型错误之间的区别。这有助于获得对不同度量的直观理解,这些度量通常用于描述统计测试的输出。就个人而言,这种网格可视化对我来说比“你怀孕了”这样的统计迷因更有帮助。
对于我使用的例子,我将在一个假设的 t 检验中展示两个正态分布。我的假设可以写成:

无效假设和替代假设。作者图片
零假设是总体均值等于 0,另一个假设是总体均值大于 0。
下面是正在比较的两种正态分布:

作者图片
平均值以 0 为中心的蓝线是代表零假设的分布,橙线是代表替代假设的分布。分布的宽度或高度的具体值对于我要表达的观点来说并不重要,因为我一般是在谈论这些视觉效果。
第一类错误可视化
在学习 t 检验时,你可能会看到的最常见的形象化的例子就是这个。通常,当描述如何知道是否拒绝零假设时,会显示这种可视化。要拒绝零假设,p 值必须小于显著性水平,在本例中,显著性水平是淡蓝色阴影虚线上方的区域。简而言之,p 值是在假设样本属于零假设分布的情况下,从样本中获得结果的概率。我不会花太多时间来解释 p 值,因为我假设你已经对它们有所了解,但是如果 p 值对你来说是新的,这里的是一个可以参考的资源。

作者图片
阴影部分是 I 型错误的概率,也就是实际为真时拒绝零假设的概率(有人错误地说阴影部分是 p 值*)。🤦♂
*注意:如果虚线代表 p 值,这将是正确的,因为 p 值也基于曲线下的面积,但更常见的是,这些图像中的虚线代表显著性水平——这就是为什么有些人会感到困惑。在这种情况下,我们用它来表示显著性水平,而不是 p 值。
以下所有可视化中的阴影部分代表曲线下的区域。在这种情况下,曲线下的阴影面积是正态分布曲线下总面积的 5%(如果你曾经对计算曲线下面积的数学感兴趣,这里的https://medium.com/geek-physics/3-ways-to-find-the-area-under-a-curve-and-why-you-would-want-to-do-that-949434a727d3是一个有趣的阅读)。
虚线代表显著性水平或α(读作 alpha)。由于α通常设定为 5%,我们可以说有 5%的几率我们拒绝零假设是错误的。这也被称为第一类错误的概率。
为了进一步解释这个问题,让我们来比较两个国家的高度:日本🗾和澳大利亚🇦🇺.我们的无效假设可能是两国之间没有身高差异,另一种可能是澳大利亚人的平均身高更高。对一个重要结果(p 值小于α)的解释是,我们拒绝了国家之间没有身高差异的零假设,接受了澳大利亚人更高的替代假设。然而,我们的结论有 5%的可能性是不正确的。
置信区间可视化
当你告诉别人你的统计测试的 95%置信区间时,你到底在说什么?要回答这个问题,需要进一步定义置信区间。置信区间的定义是 1-α。因此,当α设置为 5%时,置信区间也因此设置为 95%。
在比较统计测试的两个正态分布的上下文中,置信水平看起来是这样的:

作者图片
如果我们因为 p 值大于 0.05 而接受了零假设,那么对这个置信区间(alpha 为 5%)的解释是,我们有 95%的概率是正确的,我们的数据点来自零假设分布。
如果我们拒绝零假设,那么解释就是有 95%的几率数据来自备选假设分布。然而,上图是一个简化的例子,代表零假设被接受的情况。如果它被拒绝,置信区间范围将不包括零,因为我们将得出存在显著差异的结论(即,替代假设是差异大于 0)。*
继续我们关于日本人和澳大利亚人身高的例子,如果我们拒绝零假设,我们会说澳大利亚人有 95%的可能性比日本人高。
*注意:置信区间以样本平均值为中心,因此为了真正准确,我们将绘制第三个分布,它代表样本的分布,置信区间在样本平均值的两侧有对称的阴影区域。
第二类错误可视化
前两个例子在你的统计 101 课上已经讨论过了,但是大多数情况下,第一类错误视觉是唯一显示的(如果有任何视觉显示的话)。接下来的两张图片显示的频率要低得多,但我觉得理解它们很重要,因为 p 值和置信区间只是故事的一部分!
第二类错误被定义为β(读作 Beta ),或者如果你回头参考混淆矩阵,它是当假设错误时接受零假设的概率。在我们的身高例子的上下文中说,这是说日本人和澳大利亚人之间没有身高差异的概率,而实际上有一个差异。这就是在统计测试的背景下,第二类错误的概率是如何可视化的:

作者图片
从这个直观的例子中可以看出,第二类错误的概率大于第一类错误的概率。那是因为第一类错误和第二类错误之间存在反比关系。当我减少第一类错误的机会时,第二类错误的机会就会增加,反之亦然。您可以通过向左或向右移动虚线来直观地看到这一点。如果我们将 alpha 降低到 1%,那么虚线将移动到右侧,这将增加备选假设分布中的阴影区域。如果我们将α增加到 10%(接受更多的 I 型错误),这将使虚线向左移动,这将降低 II 型错误的概率。
继续我们的示例,假设当类型 I 误差设置为 5%时,类型 II 误差为 20%。假设第二类误差为 20%,那么你会说,如果你说日本人和澳大利亚人的身高没有差异,那么你有 20%的可能性是错的。
权力可视化
统计检验的“功效”这一概念在你的统计学导论课程中可能不会讨论。这被定义为 1 — β。这是你的统计测试的可视化的力量:

作者图片
权力被解释为当另一个假设为真时,你正确拒绝零假设的概率。功效和类型 II 误差取决于数据中的平均值(分布的距离/位置)和标准差(分布的宽度)。如果数据点与 0 非常不同,您可以对统计测试的重要结果更有信心。这将被视为一个与零假设分布重叠较少的分布。
如果你的分布如此不同,以至于完全没有重叠,那就意味着假设的第二类错误的概率是 0%,功效是 100%。这是它看起来的样子:

作者图片
功效主要是样本大小、效应大小(即差异的大小)和显著性水平的函数。功效测试的主要应用是找到达到特定功效概率(通常设置为 80%)所需的最小样本量。前往此链接了解更多关于能力测试及其在统计中的应用。
为了总结这一节,我将把它带回我们的例子。如果我们的第二类误差假设是 20%,那就意味着我们的功率是 80%。我们可以说,我们有 80%的信心,我们正确地拒绝了零假设,即澳大利亚人和日本人之间没有身高差异。
另一种说法是,如果我们取了 100 个样本,并发现了真正的身高差异,我们只会在 100 个样本中的 80 个中发现显著差异。
注意:你可能会想,等一下,难道 I 型误差和 p 值不能告诉你存在高度差的概率吗?不,它不是——所以澄清一下,第一类错误是当你得到一个重要结果时,它是一个假阳性的概率。它不会告诉你多久会找到一个有意义的结果,它只告诉你你的结果不是真实的(即有意义的)概率。所以当α为 5%时,你可以有 95%的信心得到一个显著的结果。当功率为 80%时,您会在 80%的情况下在您的样本中发现显著的结果。
最后的想法
总的来说,置信区间和功效描述了你对自己选择正确的自信程度。第一类和第二类错误描述了你选择错误的概率。希望混淆矩阵有助于解释这些指标之间的差异,以及它们之间的关系!理解这 4 个指标将有助于你对统计数据有一个很好的直观理解,这将是你学习更高级概念的基础。请让我知道你在评论中的想法,以及我是否遗漏了什么。谢谢!
如果您是中级新手,请考虑使用下面的“我的推荐”链接订阅:
*https://medium.com/@andreasmartinson/membership
此外,如果喜欢阅读,可以看看我最近发表的另一篇与统计相关的文章:
参考
- 中心极限定理,维基百科
- R.贝文斯,t 检验介绍|定义、公式和例子 (2020),Scribbr
- 页(page 的缩写)埃利斯,我总是搞不清第一类和第二类错误。你能给我看一些东西来帮助我记住区别吗? (2010),效果尺寸常见问题
- B.比尔斯, P 值:它是什么,如何计算,为什么它很重要 (2022),Investopedia
- R.艾伦。求曲线下面积的三种方法(以及你为什么要这么做) (2020),中
- 页(page 的缩写)班达里,统计能力及其重要性|简单介绍 (2022),Scribbr*
我们如何创建即席分析流程
原文:https://towardsdatascience.com/how-we-created-an-ad-hoc-analytics-process-32706ccb192
将业务和数据科学利益相关方聚集到一个集中的分析请求队列中

照片由达里娅·内布里亚希娜·🇺🇦在 Unsplash 上拍摄
您如何管理临时到来的分析请求?几年前,我们开始使用共享电子邮件列表服务器,现在已经发展成为一个成熟的流程,可以捕获、审查、分配和共享所有“即席分析”的结果(啊哈!)给我们的数据科学团队。它现在补充了一个前端,供利益相关者提交请求,以及一个后端,供分析团队筛选传入的票证。多个团队参与该流程,对他们来说,该流程是所有新的或计划外的分析的切入点。
随着我们的数据科学家团队在项目开发中设计功能、实验或模型,他们成为辅助数据和主题领域的专家。成为某些领域事实上的专家意味着其他团队会向我们寻求建议和 EDA,当在那些表面上很小的查询之间切换时,很容易让我们的长期战略优先事项受到影响。但我们也不想忽视这些问题——数据是随着时间的推移而积累的竞争优势,我们渴望作为内部数据专家为所有团队提供支持。
我们的请求流程是一种工具,可帮助我们的数据科学团队平衡短期调查需求与承诺项目和长期战略优先事项。随着时间的推移,我们对这种集中式队列的效用的想法已经发生了变化。在这里,我们将回顾我们的数据科学团队建立并继续支持这种流程的原因,以及我们学到的在管理我们的“啊哈!”排队。
为什么要有分析请求流程?
最初,我们简陋的电子邮件收件箱是对我们羽翼未丰的数据科学功能的不同和断断续续的研究请求的总称。随着我们的数据科学家和业务分析师团队在过去几年中的成长,我们接收流程的范围和效用也发生了变化。如今,它不仅是我们主要内部客户的工具,还为我们的分析团队提供核心功能。以下是我们坚持这一流程的一些主要原因:
1)入口门户是我们不断涌入的分析请求的唯一入口
如果你作为一名数据科学家的经历和我们一样,那么电子邮件、口头请求和零星的松散消息会不断产生大量的问题。请求很少像我们——更不用说非技术利益相关者——希望的那样简单或直接,因此离开项目去跟进一个看似无关紧要的调查会迅速消耗数据从业者的时间。仅仅跟踪这些无情的问题很快就变成了一份全职工作。

我想象有人认为我们回答了一个“快速问题”
最初,我们的请求流程是出于新兴数据团队管理不断增长的请求的需要而诞生的。没有一个一致的着陆点,只有几个数据科学家,从业者很容易迷失在不断变化的大小问题中,每个问题都产生了兔子洞和后续分析的分形。领导层很难看到所提要求的全部范围以及谁在提要求,很难对要求进行优先排序,也很难知道需要哪些资源来支持这些要求。队列为我们提供了一个地方,将所有的请求分流到一个位置,我们可以在那里进行排序、分类,并跟踪到底发生了什么。
这仍然是我们今天队列的主要功能——它捕获我们所有新的、未计划的和/或未预算的工作。无论范围如何,所有请求都可以被重定向到一个入口点。这使数据科学家和分析师能够在需要时保护自己的时间,而不是必须整理他们正在进行的工作流,向同事提出请求,或者等待与他们的经理交谈。分析主管可以全面了解收到的请求,并根据上下文对其进行优先级排序。回顾过去,我们评估了这些请求的数量和范围,这有助于我们规划未来的支持。换句话说…
2)它有助于量化潜在的分析需求
我们满足的要求越多,收到的就越多。有时,请求会衍生出后续分析或可操作报告形式的进一步请求。口碑的有机增长以及幻灯片和松弛渠道中的一些自我宣传广告增加了发行量。新提交的内容汇集到一个论坛,对请求进行分类和跟踪,我们的积压工作急剧膨胀,因为我们试图赢得一场永无止境的分析打地鼠游戏。
需求一直存在,但在我们创建流程之前,我们无法量化对额外分析的需求。随着时间的推移,评估和计划所需工作的努力得到了改进。我们对来自业务的不同角落的请求的频率和数量的理解变得成熟,并且我们的过程的回顾现在帮助团队计划适当的人员编制以满足特别的需求,而不牺牲对新项目的支持。在某些情况下,我们可以将这些数字直接与具有最迫切或最经常性分析需求的产品或利益相关方联系起来,这意味着…
3)这有助于我们了解谁是我们的利益相关者
一些业务团队最终会比其他团队更多地使用这个过程。无论组织的那个角落有更深的潜在分析需求,缺乏自己的分析资源,还是对我们的数据资产有特殊兴趣,它都有助于我们了解谁是我们最大的客户。与这些超级用户保持密切联系可以简化对队列支持的规划。在某些情况下,这是以指定预算的形式来支持数量增加的请求。它还为数据科学家提供了整合和发现新数据产品机会的空间,以满足重复需求,将团队与公共资源和指标联系起来。更广泛地说…
4)它为业务部门之间的知识共享提供了机会
集中接收流程的复合效应之一是连接来自业务不同角落的相关请求。通常,一个请求可能会被之前为其他人完成的工作产品直接回答。即使一个新的请求与之前的请求不完全相同,理解利益相关者需求的第一次尝试通常包括传播相关结果以回应新的提交。这减少了我们的响应时间,并且在现有的度量标准已经足够的时候,避免了创建新的度量标准。相关材料的快速周转通常会满足用户需求,如果仍然需要额外的调查或新的指标,我们的数据从业人员可以利用和增强现有的数据产品。
发现跨部门的请求趋势并了解谁投资于功能定义,意味着我们可以优先考虑和操作那些在业务中提供最大价值的功能,最大化团队的影响。最后,它还有助于我们不同的数据团队连接和共享内容。换句话说…
5)它为分析团队内部交流知识和最佳实践创造了机会
同样地,我们通过共同的特性定义和提交请求的共享空间将利益相关者聚集在一起,该过程形成了连接数据从业者的焦点。在招募新员工时,我们会迅速让他们接触到许多业务领域和相关的数据资产。内部审查过程保持质量,并为其他从业者和团队创建一个接触面,以跟踪每次分析产生的新知识。最后,新请求的偶然伪随机分配意味着没有人需要被限制支持任何特定风格的分析。通过混合设计,我们创建了一个强制功能,在个人和团队中传播领域知识、技术、工具和代码。
一路上我们学会了做什么(和不做什么)
这些年来,我们的流程发生了巨大的变化,达到了今天的水平。下面的指导方针强调了我们在这个过程中学到的一些该做和不该做的事情,包括问什么,避免什么,以及不允许什么。

不要询问指标
我们曾经询问请求者他们需要分析什么样的输出/度量/数据。但是,由于大多数业务请求者对我们的分析平台中的所有资产都没有深入的了解,所以答案往往令人困惑(他们指的是哪些日志?)或者不可能(我们只是不捕捉那种类型的数据)。
请利益相关者描述他们试图解决的潜在问题
我们希望从高层次了解请求者来自哪里,包括他们试图解决什么,他们正在做出什么决定,或者他们正在解决什么问题。这需要我们的团队做更多的工作来解释请求,但事实证明,这比强迫涉众直接阐明测量什么或使用什么数据更有成效。有时候,利益相关者确切地知道他们想要什么,以及要分析什么数据,这很好,这有助于轻松取胜。偶尔他们认为他们很清楚自己想要什么,或者要分析什么数据,但他们可能是错的。了解了潜在的原因,我们就可以仔细检查请求,并在需要时提出修改建议。通常情况下,我们的数据科学家和分析师对我们的数据(及其局限性)有更好的理解,我们流程的一部分涉及提出正确的指标、可视化或其他数据产品,以满足利益相关者的需求。
一定要问他们将如何使用输出,或者根据答案做出什么决定
这种提示有助于进一步了解请求的背景,并抓住某人寻求帮助的核心“原因”。它不仅有助于更好的度量——抓住利益相关者需求的根本——而且为优先级排序提供了关键信息。我们通常从信息将如何被使用来推断请求的紧急程度和相对优先级。
不要询问截止日期
我们过去常常在新的提交中询问利益相关者需要结果的日期。这个魔鬼般的日历选择器导致了很多昨天到期的事情和错误管理的期望。我们已经离开了这个(现在是可选的)领域,可能会完全抛弃它。
这并不意味着截止日期不适用,而是请求者没有默认设置截止日期。由于这一过程是针对未编入预算的、未计划的工作,因此它不是一个快速响应的待命火警。即使对于非紧急的请求,涉众也倾向于选择他们认为“合理”的,但是没有任何上下文来做出决定(像队列中的其他优先级、正在进行的项目、数据的复杂性等等)。)时间轴是任意的。相反,在我们对新的请求进行分类之后,我们会带着我们的建议交付日期回到请求者那里,这允许我们的团队管理期望,而不会因为不断改变优先级而破坏我们的冲刺。
一定要问他们,如果他们没有(按时)得到答案会怎么样
类似于询问将如何处理输出,这有助于将需求与驱动优先级联系起来。这是对请求进行分类和优先排序的更强大的工具。如果有人懒得去证明没有分析会发生什么,他们可能现在不需要它。
不要把它用于消防演习
由于队列用于计划外的工作,并且与所有其他传入的请求相平衡(加上我们正在进行的、已提交的项目优先级),我们避免将它用于极其紧急的请求。一个小型核心团队大约每周审查一次新的请求和待定状态,一个典型的请求可能需要 1-2 周才能完成,一旦完成审查并获得资源。任何更紧急的事情都留给拥有专职待命人员或更合适流程的团队。
在适当的时候广泛传播结果
我相信包括彻底透明在内的管理哲学,这包括在合理的情况下广泛提供结果。我们在内部对所有结果进行分类,因此我们可以根据需要重新共享、回收或更新结果。有时一个相关的分析足以满足涉众的需求。在其他场景中,无论是谁提出请求,都有一个现有的工作要继续进行,或者有一个与处理过类似数据资产的领域专家联系的点。
不要用它来计划或确定项目级工作的范围
虽然我们的 AHA 流程有助于针对我们的团队确定潜在的杂项分析需求的范围,但我们并不是在每个项目或流程中都使用它。请求的范围和规模仅限于计划外的工作,通常与任何已经分配了分析资源的资本项目无关。这仅限于个人数据从业者能够处理的事情。如果它需要来自多个团队的跨职能支持(比如,将一些模型投入生产),那就是项目级的工作,需要更正式的计划。它也有足够的范围限制,因此一个从业者可以在不超过 1-2 周的时间内解决它。有时我们会开发一个原型作为 AHA 请求的一部分,如果利益相关者对结果满意,它会减少投入更多努力的风险。
持续改进
我们的流程还很年轻,还在不断发展,总有改进的空间。我们仍在研究但没有在此讨论的一些内容包括:
- 关闭反馈回路。收集流程反馈很重要,但我们希望针对每个请求自动收集反馈。这将给涉众一个评论工作产品的机会,并为过程改进创造更多的机会。它还可以帮助我们理解工作产品对“生产”的真正影响(例如决策、出版物等)。).
- 使结果可被发现。今天,我们的 AHA 团队在内部对每个请求的输出进行了分类和管理。将涉众与过去的产出联系起来或者通过过去的分析进行搜索是一个大部分是手工的过程,我们希望向一个对实践者和请求者都是自助式的、可搜索的模型靠近。我们正在将我们所有的工作产品迁移到一个更加用户友好、可搜索的平台上,以增加可发现性,并为那些“生产中”的一次性工作产品提供一个更好定义的表面
- 整合更多团队。我们正在寻找其他从业者在 AHA 团队中“轮流”服务的模式。随着更广泛的参与,我们可以支持更多的请求,并为更多团队在其独特的领域带来上述相同的好处。与此同时,我们可以通过在整个企业范围内发展对分析需求的共同理解来加强我们的内部分析社区。
随着我们不断完善我们的流程,我每年都会学到更多东西,希望这些知识能够帮助您构建您的分析功能。让我知道什么对你有用。
[1]随着时间的推移,人们对业务问题的第一反应会变成“让我们问问数据团队。”这是个好问题,既讨人喜欢又让人疲惫。
【2】“好问题!这是我们申请表的链接…”是一些请求的结尾。如果不值得花 5 分钟来完成一份表达需求的表格,那么数据科学家花几个小时来寻找答案也是不值得的。
其中表达的观点是我个人的观点,不代表任何组织、其附属机构或员工的观点。
我们如何将 PostgreSQL 查询优化 100 倍
原文:https://towardsdatascience.com/how-we-optimized-postgresql-queries-100x-ff52555eabe
我们在 PostgreSQL 中用来加速 SELECT-s 的一些技巧:带冗余条件的左连接、值、扩展统计、主键类型转换、集群、pg_hint_plan + bonus

由 Richard Jacobs 在 Unsplash 上拍摄。
关于 PostgreSQL 优化的帖子数不胜数:兔子洞很深。当我几年前开始编写分析后端时,我已经有了使用 MySQL 和 SQL Server 等其他数据库的经验。尽管如此,我从未如此关注表演。我之前参与的项目要么对处理时间要求不严格(DS/ML),要么不涉及一次处理许多行(常规的 web 应用程序)。然而这一次,我的疑问是:
- 由 3–10 个
JOIN通过相关键组成。 - 产生 10 到 1,000,000 行。
- 必须在 UX 规定的时限内执行。
- 无法提示——直到 2021 年底 Cloud SQL——Google Cloud 中托管的 PostgreSQL 支持 pg_hint_plan。
- 不允许直接访问服务器进程,例如,黑掉一些
[perf](https://ardentperf.com/2022/02/10/a-hairy-postgresql-incident/)。因为 PostgreSQL 是托管的。
在一个 API 端点获取一百万行听起来不是一个好主意,这表明算法或架构存在问题。当然,一切都可以重写和重新设计,但总要付出代价。不幸的是,现在对我们来说太高了。
就像对 API 服务器的 Python 代码的优化一样,没有一个神奇的咒语可以解决我们所有的 SQL 性能问题。尽管如此,我还是要提几个对雅典人产生影响的好主意。希望其中一些能对读者有所帮助。当然,我不会在这里泄露神圣的秘密;我希望当我们开始的时候,我已经读了像下面这样的东西。
左连接的隐藏优势
编写 SQL 的每个人都应该知道INNER JOIN和LEFT JOIN的区别。当公共列相关时,这些书经常忽略连接类型如何影响查询规划器。假设我们有两张表:

pull_requests 通过 merge_commit_id 引用提交。图片作者。
pull_requests包含关于 GitHub 上 pull 请求的记录。commits包含关于 GitHub 上提交的记录。pull_requests中的merge_commit_id引用commits中的{0,1 }–1id。所有的id都有很高的选择性。给定表之间的内部连接,
**SELECT** pr.*, c.sha **AS** merge_commit_sha
**FROM** pull_requests pr
**INNER JOIN** commits c **ON** pr.merge_commit_id = c.id
**WHERE** pr.repository_id **IN** (...)
PostgreSQL 规划器可能会预测少量的结果行,并发出一个嵌套循环连接。发生这种失败是因为 PostgreSQL 不知道我们的 commit id -s 是相关的,并在连接的行计数估计公式中乘以它们的选择性。因此,如果我们处理大约 10k 行,我们的查询性能会严重下降。让我们考虑一个左连接:
**SELECT** pr.*, c.sha **AS** merge_commit_sha
**FROM** pull_requests pr
**LEFT JOIN** commits c **ON** pr.merge_commit_id = c.id
**WHERE** pr.repository_id **IN** (...)
规划器可能会预测与pull_requests中过滤的行数量相同的结果行,并且会正确地在大约 10k 上调度一个散列左连接。为了避免散列整个commits,我们可以应用我们的领域知识,PRs 和 commits 总是在同一个存储库中。
**SELECT** pr.*, c.sha **AS** merge_commit_sha
**FROM** pull_requests pr
**LEFT JOIN** commits c **ON** pr.merge_commit_id = c.id
**AND** pr.repository_id = c.repository_id
**WHERE** pr.repository_id **IN** (...)
第二个连接条件是人为的,不会改变结果。然而,PostgreSQL 足够聪明,可以散列由与pull_requests相同的repository_id -s 预过滤的commits。我们的查询应该执行得更快,拥有所有必要的索引。
这种方法的真正优势体现在多层 JOIN-s 上。PostgreSQL 缓存预先过滤的哈希表,合并它们的成本很低,而不可能缓存嵌套循环。结果,当我在生产中从内连接切换到左连接时,性能提高了 10-100 倍。重要提示:如果不能保证不同的连接返回相同的结果,就必须对空值进行后置过滤。
散列值
考虑一个典型的“胖入”查询:
**SELECT** *
**FROM** pull_requests
**WHERE** repository_id **IN** (...over 9000 IDs...)
这通常计划为索引或位图扫描。我们可以使用VALUES表达式重写它:
**SELECT** *
**FROM** pull_requests
**WHERE** repository_id = **ANY**(**VALUES** (101), (102), ...)
PostgreSQL 产生了一个不同的计划,带有值扫描的 HashAggregate 和可能的 Hash Join(如果预测的行数足够大)。这对性能有影响吗?有时确实如此。我发现它在多连接查询中很有用,但只是在计划器没有在所有连接之后安排它的时候。尽管可以使用提示来提升它。我稍后会介绍它们。在我们的产品中,当“它工作”时,典型的加速是 10-100 倍,但同时,当计划者感到困惑时,速度会慢 10-100 倍。
附注:你不应该直接在 SQL 主体中插入超过 9000 个 id。
**WHERE** repository_id = **ANY**($1::text::bigint[])**WHERE** repository_id = **ANY**(**SELECT** * **FROM** unnest($1::text::bigint[]))-- where $1 is an argument passed via the binary protocol$1 = '{...over 9000 IDs...}'
注意双重造型$1::text::bigint[]。由于推断出 asyncpg ( a sized iterable container expected (got type 'str'))中的参数类型不正确,直接转换为bigint[]可能会失败。此外,如果用= ANY(ARRAY)替换IN (...),规划器应该会得到行数的提示,因为后一个构造目前还不清楚。
扩展统计
继续前面的 SQL,让我们在WHERE中添加另一个条件:
**SELECT** *
**FROM** pull_requests
**WHERE** repository_id **IN** (...) **AND** merge_commit_id **IN** (...)
repository_id和merge_commit_id都具有高选择性。对于 PostgreSQL 来说,这两列是黑盒,因此它可能会大大低估最终的行数。悲观的行计数预测会导致糟糕的下游计划决策,比如嵌套循环而不是LEFT JOIN commits的散列连接,我们的性能会下降。
这个问题有一个貌似合理的解决方案:扩展统计。
**CREATE STATISTICS** ids_correlation **ON** repository_id, merge_commit_id **FROM** pull_requests;
以ids_correlation为动力,PostgreSQL ≥13 会意识到repository_id和merge_commit_id相关,会调整行数估算。
事实证明,扩展的统计数据对于纠正规划者的预测特别有用,在这种情况下,我们通过客户账户 ID 进行软分段。同样,我们得到了散列连接而不是嵌套循环和 10-100 倍的加速。
主键类型很重要
我们曾经有一个稍微不同的模式。

具有 varchar 类型的拉请求 id 的旧数据库模式。图片作者。
GitHub 将所谓的节点标识符分配给每个 API 对象,例如 pull 请求。这是一个不透明的字符串:例如,雅典人/api-spec#66 是[PR_kwDOFlTa5c4zPMUj](https://api.github.com/repos/athenianco/api-spec/pulls/66)。所以我们很自然地决定使用节点 id 作为主键。一切都很好,直到…
直到 GitHub 改变了节点 ID 格式。迁移对我们来说压力很大,我们最终切换到映射到节点 id 的全局整数 id。从我们的错误中吸取教训:不要依赖外部 id,因为你无法控制它们。
当我们的主键变成整数而不是字符串时,我们很高兴这些列的连接速度提高了 2-5 倍。整数消耗的内存更少,比较和哈希的速度更快。毫无疑问,性能提高了这么多。
串
让我们继续拷问pull_requests。
**SELECT** *
**FROM** pull_requests
**WHERE** repository_id **IN** (...) **AND** number > 1000
假设我们已经有了一个合适的索引:
**CREATE INDEX** pull_requests_repository_id **ON** pull_requests (repository_id, number)
我们还能做些什么来加快查询速度吗?我知道两个选择:
- 将
SELECT *中提到的列放入覆盖指标的INCLUDE部分。我们会进行索引扫描。 - 通过
pull_requests_repository_id对表进行聚类。
如果额外列的数量适中,第一个选项是可以的。第二个选项更高级。它利用了通常如何访问表的领域知识。值得注意的是,我们可能对存储库存储的 PRs 感兴趣,并且可能只获取最新的 PRs(由number > 1000建模)。因此我们将索引声明为CLUSTER:
**CLUSTER** pull_requests **USING** pull_requests_repository_id
该命令完成后,表记录将根据索引列的顺序在磁盘上重新排列。我们的查询返回的行越多,由于加载的相关数据页面越少,我们节省的 IOPS 就越多。
像许多其他人一样,我们在将CLUSTER ... USING集成到产品中时遇到了障碍。必须定期执行该命令,因为 PostgreSQL 无法自动维护集群状态。不幸的是,CLUSTER获得了一个独占的表锁和挂起的读写块。我们的救星是pg _ re pack——一个没有锁的轻量级替代品。云 SQL 支持它,我们已经在周末推出了它,并取得了巨大的成功。
在CLUSTER之后,我们的生产速度提高了 2-5 倍;特别是,它有助于必须从磁盘读取缓冲区的冷查询。
pg _ 提示 _ 计划
PostgreSQL 权威一直反对 SQL 提示。情况类似于 Go 中禁止泛型,只不过 13 年后 Go 终于加入了泛型,PostgreSQL 超过 36 还没有加入提示。幸运的是,可以通过 GitHub 上的日本项目 pg_hint_plan 插入提示。云 SQL 从 2021 年末开始支持 pg_hint_plan。
当我确信他们的警告与我的背景无关时,我总是发现做一些作者强烈阻止我做的事情非常令人满意。这种感觉类似于在接受保修损失时越狱你的手机。或者遇到 web 服务的问题时,直接与技术人员交谈,而不是与第一支持热线联系。像政治家一样,软件开发人员喜欢无条件地限制事情,部分原因是他们会发现迫在眉睫的混乱。
pg_hint_plan 允许很多很酷的技巧。以下是我的荣誉提名。
多余的条件在哪里
如果我们给查询添加额外的约束,我们可以加快散列连接的速度。它们不会改变结果,但会减少索引读取。
考虑关于左连接部分的查询。
**SELECT** pr.*, c.sha **AS** merge_commit_sha
**FROM** pull_requests pr
**LEFT JOIN** commits c **ON** pr.merge_commit_id = c.id
**AND** pr.repository_id = c.repository_id
**WHERE** pr.repository_id **IN** (...)
我们可以这样重写:
**SELECT** pr.*, c.sha **AS** merge_commit_sha
**FROM** pull_requests pr
**JOIN** commits c **ON** pr.merge_commit_id = c.id
**WHERE** pr.repository_id **IN** (...) **AND** c.repository_id **IN** (...)
正如我之前提到的,规划器很可能会错误地预测行数,因为它不知道repository_id -s 是相关的。然而,我们有一个超级武器,可以纠正这个预测。
/*+
Rows(pr c *100)
*/
**SELECT** pr.*, c.sha **AS** merge_commit_sha
**FROM** pull_requests pr
**JOIN** commits c **ON** pr.merge_commit_id = c.id
**WHERE** pr.repository_id **IN** (...) **AND** c.repository_id **IN** (...)
实际的乘法因子应该是几个典型查询的平均值。所描述的方法有利也有弊。
👍明确的INNER JOIN更好地表达了意图。
👍当我们不得不编码INNER JOIN时起作用。
👎当表中的统计数据发生变化时,我们应该检查乘法因子。实际上,安全总比事后后悔好,所以当行数较低时,过大的值不会降低性能。
甲骨文预测
如果我们使用给定的合并提交散列来搜索 PRs,我们将会遇到常见的错误的行计数估计。
**SELECT** pr.*, c.sha **AS** merge_commit_sha
**FROM** pull_requests pr
**JOIN** commits c **ON** pr.merge_commit_id = c.id
**WHERE** c.sha **IN** (...100 items...)
我们可以利用我们的领域知识
IN仅包含合并提交。- 每次合并提交映射到一个拉请求。
因此,我们将行数设置为IN的大小:
/*+
Rows(pr c #100)
*/
**SELECT** pr.*, c.sha **AS** merge_commit_sha
**FROM** pull_requests pr
**JOIN** commits c **ON** pr.merge_commit_id = c.id
**WHERE** c.sha **IN** (...100 items...)
超越 set join_collapse_limit=1
联接几个表的顺序可能很关键。官方文档提供将连接级参数join_collapse_limit设置为 1,除非你通过 pgbouncer 连接并且物理连接是共享的,否则有效。或者,您不希望锁定整个连接序列,而只锁定几个种子表。或者嵌套顺序不是线性的。继续前面的查询,假设我们通过许多合并提交进行搜索,并决定强制哈希表查找:
**SELECT** pr.*, c.sha **AS** merge_commit_sha
**FROM** pull_requests pr
**JOIN** commits c **ON** pr.merge_commit_id = c.id
**WHERE** c.sha = **ANY**(**VALUES** ...) **AND** ...other clauses...
other clauses可能会混淆 PostgreSQL 规划器,所以它在联接两个表后调度 HashAggregate。届时,性能影响将是灾难性的。让我们解决这个问题。
/*+
Leading(*VALUES* c pr)
*/ **SELECT** pr.*, c.sha **AS** merge_commit_sha
**FROM** pull_requests pr
**JOIN** commits c **ON** pr.merge_commit_id = c.id
**WHERE** c.sha = **ANY**(**VALUES** ...) **AND** ...other clauses...
或者,如果other clauses对pull_requests非常严格,并且我们期望 PRs 少于合并提交散列的数量,我们可以在使用过滤的commits连接之前独立地应用other clauses:
/*+
Leading(pr (*VALUES* c))
*/ **...**
Leading hint 是 dope,至今一直是我们的超级英雄。
无情的指数覆盖
pg_hint_plan 给了我们一把大枪:IndexScan和IndexOnlyScan。这两个因素推翻了计划者使用指数的决定。不幸的是,如果 SQL 或统计数据发生变化,规划器就会偏离正轨,并显示“未定义的行为”示例:
**CREATE INDEX** impossible **ON** pull_requests (merge_commit_id);/*+
IndexScan(pr impossible)
*/
**SELECT** *
**FROM** pull_requests pr
**WHERE** repository_id **IN** (...) **AND** number > 1000;
计划者将发布顺序扫描。如果 autovacuum 跟不上表的写入,那么计划可能看起来完全不可思议,例如,使用正确的索引但忽略索引列并从头开始过滤。无论如何,自动真空滞后是有害的。
额外奖励:视觉解释
当我需要优化另一个查询时,这是我的谋生之道。我试过几个 web apps,最喜欢的是 explain.tensor.ru 。

explain.tensor.ru 的推荐。图片作者。
优势:
- 关于修复或调整什么的建议。他们不是微不足道的,并证明对初学者非常有帮助。
- 其他视图,如瓶颈饼图。
- 非常健壮的计划解析器。
- 链接可以存活很多年——对于延期研究非常有用。
请在回复中建议其他酷的解释者!
额外收获:云 SQL 洞察
Google Cloud 推出嵌入云 SQL 的新工具:云 SQL Insights 。我数不清这个工具救了我们多少次:
- 当我们的后端 DDoS-ed 数据库查询缓慢时,我们不得不在恐慌中尽快修复它。云 SQL 洞察立即指出了杀手级 SQL。
- 当我们的数据检索子系统(镜像来自 GitHub、JIRA 等的数据的子系统。)写得太快,autovacuum 失败,一些表“腐烂”同样,云 SQL Insights 很快指出了什么需要紧急处理
VACUUM ANALYZE。 - 增强 哨兵的常规性能监控。

云 SQL Insights —我们生产中过去一小时内最具影响力的查询。我们应该回顾一下我们如何获取释放,该死的。图片作者。

云 SQL 洞察—查询详细信息。应该有人添加一个 Rows 提示来消除嵌套循环。图片作者。
摘要
我介绍了一些 PostgreSQL 查询性能技巧,它们将我们的查询速度提高了 100 倍:
LEFT JOIN代替了INNER JOIN,帮助规划者做出更准确的行数预测。添加冗余的ON子句改进了散列连接。= ANY(VALUES ...)而不是IN可以强制一个有很多元素的哈希聚合。- 扩展的统计信息通知规划者有关列的相关性。
- 把表主键做成
varchar是个馊主意。 CLUSTER当查询返回许多相关行时,会出现异常。- pg_hint_plan 提供了强大的提示,包括估计行数修正
Rows,连接序列实施器Leading,以及索引覆盖IndexScan。尽管后者可能会反击。 - 我喜欢 explain.tensor.ru 来可视化
EXPLAIN-s。 - 如果你是云 SQL 用户,云 SQL 洞察是必须的。
我将感谢任何反馈和更正。请在媒体上关注我,以获得我下一篇帖子的通知。我写的是应用于软件开发工件的 ML/DS,Python,PostgreSQL。
另见:我们如何优化 Python API 服务器代码 100x 。
如果你是一个渴望建立持续改进软件开发文化的工程领导者,请查看我们的 SaaS 产品网站。
我们如何优化 Python API 服务器代码 100 倍
原文:https://towardsdatascience.com/how-we-optimized-python-api-server-code-100x-9da94aa883c5
我们用来加速调用用 Python 编写的分析 API 的一些技巧:用 asyncio 玩,用 SQLAlchemy 捣乱,深入研究 asyncpg,用 Cython 重写部分,找到更好的数据结构,用 pure numpy 替换一些 pandas。

根据 CC0 许可的图像。鸣谢:最大像素贡献者。
Python 代码优化看起来容易或困难,取决于性能目标。如果目标是“尽最大努力”,仔细选择算法并应用众所周知的常见实践通常就足够了。如果目标是由 UX 决定的,有时你必须深入几个抽象层,侵入系统。或者重写底层库。或者换个语言,真的。
这篇文章是关于我们在 Python 代码优化方面的经验,无论你做什么都不够快。我个人非常乐于接受挑战,并将 API 响应时间压缩在一秒钟之内。事实上,这太有趣了,我们已经为一系列博客帖子准备了足够的笔记。
那些淘气的花冠
API 请求处理通常在两个极端之间平衡:CPU 和 IO 等待。它们之间没有明确的界限;就像阴阳一样,它们以复杂的关系携手共舞。如果你分析一个请求,你会看到一堆杂乱的函数调用,这些调用会投射到 CPU 和 IO 占用轴上。让我们考虑这个简化的代码:
await asyncio.gather(query_sql_a(), query_sql_b(), query_sql_c())
我们启动了三个从 SQL DB 请求数据的协同例程。雅典人使用 PostgreSQL,那么让我们假设我们使用 PostgreSQL。每个协程都经过三个阶段:
- (CPU)准备对 PostgreSQL 的请求并发送它。
- (IO wait)等待 PostgreSQL 响应。
- (CPU)读取响应并将其转换为 Python 对象。
让我们假设(1)和(3)对于每个协程都用了一秒钟,PostgreSQL 是无限强大的,总是需要 5 秒钟来计算query_sql_a的响应,3 秒钟计算query_sql_b的响应,1 秒钟计算query_sql_c的响应。这并不意味着,例如query_sql_a会一直在 IO wait (2)中花费 5 秒,因为 Python 在每个时刻只能执行三个协程中的一个。
asyncio.gather按照传递参数的顺序启动协程。这并没有写在文档中,必须是一个实现细节,但是考虑这一点很重要:我们将首先执行query_sql_a的(1),然后是query_sql_b的(1),然后是query_sql_c的(1),然后在 PostgreSQL 繁忙时等待一秒钟,然后以相反的顺序执行(3)。

A、B、C 协同程序的执行计划。CPU 时间是 86%,IO 等待时间是 14%。图片作者。
根据执行计划,我们在 CPU 中遇到瓶颈:86%的操作系统线程时间 CPU 在做一些有用的工作。现在考虑启动协程的不同顺序:
await asyncio.gather(query_sql_c(), query_sql_b(), query_sql_a())

C,B,A 协同程序的执行计划。CPU 时间是 60%,IO 等待时间是 40%。图片作者。
第二个执行计划演示了如果我们不猜测启动协程的最佳顺序,事情会变得多么糟糕。墙壁时间从 7 秒增加到 10 秒,增加了 43%。我们不再有严重的 CPU 瓶颈(60%对 86%)。query_sql_c阶段 3 与query_sql_a阶段 1 竞争,胜负取决于事件循环内部。
我在这里写的是 Python 代码优化,所以我不会讨论 SQL 性能和减少单个 IO 等待这样的突出问题。因此我的建议是
尝试传递按预期 IO 等待时间降序排序的
asyncio.gather()中的协同程序。也就是说,第一个参数应该是具有最高预期 IO 等待的协程,依此类推。
真实的例子:我们在代码中有一个地方放了 10 个协程。当我按照前面提到的启发式方法排序时,平均总执行时间减少了 x2 。
假设thread_gather启动并加入线程而不是协程,对其参数排序有意义吗?当然不是。在我的例子中,启动线程比启动协程更快吗?实际上,考虑到 GIL,协程的性能会更好:

A、B、C 线程的执行计划。CPU 时间是 66%,IO 等待时间是 33%。图片作者。
在联盟中共享
我们的 API 使用 SQLAlchemy 核心来生成 SQL(没有 ORM)。WHERE中有些条件重复的地方不少。一个例子是用UNION ALL代替OR;而不是
SELECT * FROM table WHERE ((a = 1 and b = 2) OR (a = 2 and b = 1)) AND c = 3
我们写作
(SELECT * FROM table WHERE a = 1 and b = 2 and c = 3)
UNION ALL
(SELECT * FROM table WHERE a = 2 and b = 1 and c = 3)
为什么在这种情况下UNION ALL通常更好是另一篇博文的主题。让我们关注一下UNION ALL在 SQLAlchemy 中的样子:
union_all(select([table]).where(and_(a == 1, b == 2, c == 3)),
select([table]).where(and_(a == 2, b == 1, c == 3)))
想象有一个大的表达式代替了c = 3,还有变量IN,等等。—两次构建这样的对象是很昂贵的。相反,我们可以写:
shared_cond = c == 3
union_all(select([table]).where(and_(a == 1, b == 2, shared_cond)),
select([table]).where(and_(a == 2, b == 1, shared_cond)))
这并不适用于每一个 SQLAlchemy 引擎,尤其是 SQLite,因为 SQLAlchemy 在那里生成?, ?, ?作为参数占位符,而不是索引引用$1, $2, $3。然而,随着从 SQLAlchemy 1.3 升级到 1.4,他们改进了对大 T4 的处理,我们得到了 1.5-2 倍的 SQL 编译速度。
从行到列
我们通过 asyncpg 查询 PostgreSQL。asyncpg 像几乎所有其他关系数据库驱动程序一样获取返回行。然而,我们的分析 API 需要构建列式的pd.DataFrame -s:每个返回列的值存储在一起。此外,在 pandas 2.0 之前,相同 dtype 的几个列一起存储在同一个 numpy 数组(也称为块)中。
使用DataFrame.from_records()天真地构造 DataFrame 是非常低效的。假设 PostgreSQL 敲 asyncpg 的门。接下来是:
- 解析 PostgreSQL 网络协议并创建 Python 对象。
- 将这些 Python 对象插入到创建的
asyncpg.Record-s 中。 - 迭代行并将 Python 对象插入 dtype
object的 numpy 数组。 - 推断更好的数据类型(例如 int、datetime 等。)并将 Python 对象转换成它们。
- Consolidate — BlockManager 是 pandas 1.x 的特别之处,它将相同数据类型的 numpy 数组合并在一起。
给定纯object列(例如,带有 SQL 空值),我们接触它们的引用计数器 4 次:在(1)、(3)、(4)和(5)中。asyncpg.Record用作辅助容器,可以排除。此外,我们不必执行(4 ),因为我们已经从 SQL 查询中知道了正确的 dtypes。因此,在现代 x86 CPU 上,从 pgproto 到 ready DataFrame 的端到端转换需要超过 500 毫秒,包含大约 20 个对象、大约 20 个类型化列和 300,000 行。
理想的管道是:
- 解析 PostgreSQL wire 协议,直接写入 numpy 数组,无需具体化严格类型的对象。
- 构建没有冗余整合的块管理器。
纯object值只会增加一次参考计数器。仅仅估计一下,整个过程就要过去大约 5 毫秒。然而,不幸的是,解析 pgproto 和构造asyncpg.Record -s 驻留在 Cython 甚至asyncpg的 C 代码深处,所以制作理想的管道意味着分叉项目。我们肯定会在征服世界之前叉掉它,但同时必须找到一个折中的办法。
我们当前的折衷渠道:
- 解析 PostgreSQL 网络协议并创建 Python 对象。
- 将这些 Python 对象插入到创建的
asyncpg.Record-s 中。 - 迭代行并将 Python 对象插入到两个 dtype
object的 numpy 数组中——一个用于 dtyped,一个用于object列。 - 给定 SQL 查询中的先验知识,转换数据类型。
- 构造块并将它们包装在块管理器中。
现在,纯object值将 refcounters 递增两次:在(1)和(3)中。我们不再试图猜测类型。内存复制膨胀显著减少。我们的测量显示转换时间至少比快 10 倍,约为 50 毫秒。
实际的源代码可以在我们的 API 所在的存储库中找到:雅典人/雅典人-api-open 。它不是通用的,也没有足够的意愿使它成为一个合适的开源库。请随意根据您的需求进行调整!我们在麻省理工学院的许可下分发这些文件。
更新(2020 年 6 月):我们通过使用 asyncpg-rkt 达到了理想的流水线。
让我通过给出一般性的建议来结束这一部分。
尽可能避免 pandas DataFrame-s 中的
object列。使用它们的操作比使用正确类型的要慢得多。
迭代没有 GIL 的元组列表
一个非常具体的目标:在原始的asyncpg.Record -s 上优化迭代。确实有可能在 GIL 发布后直接与他们合作。Cython 代码如下:
cdef extern from "asyncpg_recordobj.h":
PyObject *ApgRecord_GET_ITEM(PyObject *, int)cdef extern from "Python.h":
# added nogil -> from cpython cimport ...
# these are the macros that read from the internal ob_items
PyObject *PyList_GET_ITEM(PyObject *, Py_ssize_t) nogilcdef nogil_iter(rows: list[asyncpg.Record]):
cdef:
Py_ssize_t i, size
PyObject *record
PyObject *value size = len(rows)
with nogil:
for i in range(size):
record = PyList_GET_ITEM(<PyObject *>rows, i)
value = ApgRecord_GET_ITEM(record, 0)
asyncpg_recordobj.h是 asyncpg 中真实 recordobj.h 的简化:
typedef struct {
PyObject_VAR_HEAD
// asyncpg specifics begin here
// if they add another field, we will break spectacularly
Py_hash_t self_hash;
PyObject *desc; // we don't care of the actual type
PyObject *ob_item[1]; // embedded in the tail, the count matches len()
} ApgRecordObject;
#define ApgRecord_GET_ITEM(op, i) (((ApgRecordObject *)(op))->ob_item[i])
根据类型value的不同,nogil的攻击可能很方便,也可能看起来毫无用处。例如,如果value是一个字符串,并且您的 CPython 将 Unicode 字符串存储在 UTF-8 内部,<const char *>PyUnicode_Data(value)将会工作。如果value是整数,PyLong_AsLong(value)也可以。但是处理复杂的类需要 GIL。
加速应该是 ~10x。
如果我们使用元组而不是asyncpg.Record -s,我们可以稍微修改上面的代码以保持功能性:
cdef extern from "Python.h":
# added nogil -> from cpython cimport ...
# these are the macros that read from the internal ob_items
PyObject *PyList_GET_ITEM(PyObject *, Py_ssize_t) nogil
PyObject *PyTuple_GET_ITEM(PyObject *, Py_ssize_t) nogilcdef nogil_iter(rows: list[tuple]):
cdef:
Py_ssize_t i, size
PyObject *record
PyObject *value size = len(rows)
with nogil:
for i in range(size):
record = PyList_GET_ITEM(<PyObject *>rows, i)
value = PyTuple_GET_ITEM(record, 0)
你最好不要同时索引asyncpg.Record和元组,否则你会立刻在本机代码中抓住一条龙。
零拷贝(反)序列化
我们目前在 PostgreSQL 中存储各种预先计算的数据。我们根据来自应用程序逻辑的许多过滤器来获取它。在 Sentry 中收集的配置文件和跟踪明确显示,在INSERT INTO … VALUES和反序列化期间,我们有时在数据序列化上花费了太多时间——在解析 pgproto 时创建 Python 对象,我在前面的一节中提到过。
我们能够通过使用基于结构化 numpy 数组的特殊的、有限的、不可变的数据结构来优化这个热点。简而言之,它是一个围绕bytes的数组包装器。那是__slots__里唯一的一项,真的。
当我们想从结构中提取一些字段"foobar"时,我们执行:
@property
def foobar(self):
return self._array["foobar"][0]
我们的序列化是零拷贝:
def serialize(self):
return self._array.view(np.uint8).data
反序列化也没什么:
def __init__(self, data: bytes):
self._array = np.frombuffer(data, dtype=self.dtype, count=1)
dtype看起来像np.dtype([("foobar", int), ("baz", "datetime64[ns]")])
我们结构的秘密武器是非常有效地转换成熊猫数据帧:
concat_bytes = b"".join([x.serialize() for x in rows])
boss = np.frombuffer(concat_bytes, dtype=dtype, count=len(rows))
pd.DataFrame({"foobar": boss["foobar"], "baz": boss["baz"]})
使用 Cython 中的nogil可以进一步优化字节的连接。
实际的实现更加复杂。它支持:
- numpy 具有的标量非
object字段,包括 unicode 字符串和 blobs。 - 这些类型的可变长度数组。
- 属性会自动生成。
- 可选可变附加字段(未序列化)。
这是一个例子:
[@numpy_struct](http://twitter.com/numpy_struct)
class PullRequestFacts:
class Immutable:
created: "datetime64[s]"
first_commit: "datetime64[s]"
...
class Optional:
repository_full_name: str
...
很难比零拷贝和 O(1)快。与将字段存储在单个 SQL 表列中的pickle相比,@numpy_struct至少为我们带来了10–50 倍的性能提升。
然而,也有缺点:
- 我们不能处理任意的类型。
- 我们不能将字段上的过滤器下推到 SQL。
所以@numpy_struct并不是所有问题的万能解决方案。
没有熊猫的熊猫
熊猫 1.x 是微性能垃圾箱火。那是官方。同时,pandas 非常方便,总的来说是一个很棒的、有据可查的工具。
我们不得不重写 API 代码的某些部分,以支持低级的 numpy 数组操作。让我举几个例子。第一个是通过列上的一些条件提取子数据帧。
df = pd.DataFrame({"a": [...], "i": [...]}).set_index("i")
df[df["b"] > 10]
我们做得更详细但更有效:
df.take(np.flatnonzero(df["a"].values > 10))
如果我们在一个循环中重复调用这个函数几百次,并且数据帧的大小小于 100 行,那么我们的提取速度会快一个数量级。发生这种情况是因为df[...]通过索引值进行选择,因此执行了不必要的索引查找,还因为我们没有执行大量底层粘合代码。
第二个例子是对由列“a”的值分组的列“b”的值执行一些function。
df = pd.DataFrame({"a": [...], "b": [...]})
df.groupby("a")["b"].apply(function)
这是另一种更快捷的方法:
arr_a = df["a"].values
arr_b = df["b"].values
keys, indexes, counts = np.unique(
arr_a, return_inverse=True, return_counts=True)
order = np.argsort(indexes) # better when arr_a's dtype is S or U
offsets = np.zeros(len(keys) + 1, dtype=int)
np.cumsum(counts, out=offsets[1:])
for i, key in enumerate(keys):
grouped_b = arr_b[offsets[i]:offsets[i + 1]]
function(key, grouped_b)
这段代码利用了[np.unique](https://numpy.org/doc/stable/reference/generated/numpy.unique.html)的强大功能,它可以有效地计算数组中的唯一值(return_counts=True),还可以找到第一次遇到的值(return_index=True)或将每个值映射到一个唯一的索引(return_inverse=True)。我们对arr_a的元素进行排序,并在知道每个组的大小的情况下迭代这些组。

DataFrame.groupby 替换为 np.unique. Image by Author。
Pandas 对groupby使用了一种散列表技术,因此比排序具有更好的 big-O,然而,高度的抽象和糟糕的微性能增加了巨大的线性损失。实际的加速取决于数据帧的大小以及列“a”和“b”的性质。在我们的生产中,典型的提升是 20 到 50 倍。
可以类似地替换groupby之上的许多其他操作,例如idxmin()或count(),甚至可以通过 NaN-s 和 NaT-s 解决丢失的值
我们过去常常遵循另一种方法:
df.groupby("a").grouper.groups.values()
np.unique方式避免了具体化每个组的可变长度数组索引的整个列表,因此速度更快。
所有这些都值得吗?
我不会把性能优化和剃牦牛比,而是和训练跑马拉松比。你开始时的状态非常糟糕,然后慢慢地进步,一周接一周,每次都会产生稍微好一点的结果。直到有一天你达到了体能要求,跑了马拉松。比赛的每一公里都会提醒你,为了能够向前跑,你经历了什么。
Athenian API 处理由 10 个不同属性过滤的数十万个项目,在几毫秒内将几个软件开发活动逻辑地加入到一个巨大的可查询 DAG 中。两年前,我们从一个非常慢的 MVP 代码库开始,当时公司刚刚成立 4 个月。我为那段代码感到羞耻,这是一件好事:我们没有矫枉过正。两年后,同样的 API 查询执行速度提高了约 1000 倍。我几乎触及了我们达到 1000 倍所做的事情的表面,我们绝对没有完成!下面这篇博文应该总结了我们的 PostgreSQL 查询优化经验。仅考虑 Python 代码性能,提升了~ 100 倍。
TL;速度三角形定位法(dead reckoning)
我考虑了一些 Python 代码技巧,帮助我们提高分析后端性能。他们是:
asyncio.gather按 IO 等待时间排序的自变量。- SQLAlchemy 核心中的共享过滤器。
- 从
asyncpg.Record-s 定制构建熊猫数据帧。 - 在 Cython 中迭代没有 GIL 的列表。
- 零拷贝(解)序列化数据结构。
- 用纯 numpy 替换熊猫
groupby。
这些技巧让我们的工作负载性能提高了两个数量级。示例源代码在 GitHub 上。关于我们如何决定优化什么,我写了后续的帖子:HTTP API 的持续性能提升。还有类似的关于 PostgreSQL 优化的:我们如何优化 PostgreSQL 查询 100x 。
如果我们正在构建的产品听起来像是你的工程组织需要的东西,去看看 Athenian.com。
我们如何改变可视化调试工具的设计
原文:https://towardsdatascience.com/how-we-transformed-the-design-of-a-visual-debugging-tool-93d16068944f
我们如何通过用户研究改变笨重的可视化调试工具的设计

WhyFlow 转型概述。图片作者。
数据流接收多个数据源,如逗号分隔值文件、表格等。,并通过几个代码模块转换和处理数据。数据最终被合并和连接,并输出这些数据。
在科学界可以找到数据流的例子,他们有几种数据源,如地理数据、温度数据和气候数据,然后他们通过数据流将这些数据源组合在一起。输出是一个合并的表,工具可以很容易地读取它,并为希望分析数据的科学家可视化。
数据流动的另一个例子是在政府部门,他们收集了数千家公司的文件,以及它们的纳税申报和收入。这些数据来自几个来源,如美国国税局、美国证券交易委员会的文件和公司本身。数据必须通过复杂的数据流接收和连接。数据的输出可以在其他地方使用,通常存储在数据库中。

图片作者。
在几种情况下,数据流的输出中会出现错误。在数据流中追溯错误的来源是很困难的:
- 它通常是通过反复试验来完成的。
- 错误在整个数据流中传播,因此人类必须分析每个中间输出,以便找出哪里出错了。
- 不是错误源但传播错误的代码模块可能被误识别为错误源。
为了帮助用户调试通过数据流中的几个代码模块传播的错误,我们构建了一个名为 WhyFlow 的可视化交互式调试工具。WhyFlow 帮助用户识别数据流中传播错误的模块。
我们首先构建了 WhyFlow 的初始版本,然后进行了一项用户研究,要求用户识别小玩具数据流中的错误。我们从用户那里得到反馈,然后创建了一个改进版的 WhyFlow。在本文中,我们将讨论如何根据用户反馈改造我们的可视化调试工具。
用户研究设置
当用户调试数据流时,WhyFlow 支持三个主要任务:
- 使用工作流视图了解数据流
- 通过使用数据面板分析数据流中的模块如何影响错误流来检查错误流
- 借助解释面板中的解释诊断错误
我们进行了一项用户研究,告诉我们如何重新设计 WhyFlow 的每个部分。用户收到的数据流在其中一个代码模块中有错误。他们被要求描述数据流。然后,他们被要求尽力解释错误,描述错误数据点的共有特征,并识别传播错误的代码模块。
他们没有被给予任何关于代码模块实现的细节,以便在更大的数据流上模拟真实生活的场景(其中并非代码模块的所有实现细节一开始都是已知的)。相反,只给他们代码模块的输入和输出及其标签,即数据点是正确的还是错误的。
我们将介绍 WhyFlow 的每个主要部分,以及用户对每个部分的看法和我们是如何重新设计它的。
完善总体布局
初始版本
在最初的版本中,工作流视图位于左侧,因为它是我们希望用户检查的第一件事。工作流视图让他们对数据流以及代码模块如何相互连接有了一个大致的了解。
数据面板和解释面板都被放在了右边,因为我们希望用户在从工作流视图中选择了要检查的代码模块之后检查这两个面板。

概述了我们的调试工具在我们运行用户研究之前和之后是如何转变的。(1)工作流视图,(2)数据面板,(3)解释面板
精致版
在最初的版本中,我们注意到用户为了继续他们的调试任务,会先看数据面板,而不是解释面板。这分散了他们对调试任务的注意力,使他们更关注输入和输出关系,而不是使用解释来帮助他们快速发现数据中的错误模式。在改进后的版本中,我们对布局进行了垂直重新排序,首先是工作流视图,其次是解释面板,最后是数据面板。
优化工作流视图
初始版本
在最初的版本中,数据流是垂直可视化的。数据流的代码模块由节点表示,并由边连接,边表示从一个代码模块流向另一个代码模块的数据点。每条边由每个数据点的标签进行颜色编码,其中绿色表示正确的数据点,红色表示错误的数据点。边缘的宽度代表通过的数据点的数量。宽度越大,表示通过的数据点越多。

在 WhyFlow 的初始版本中,具有 9 个代码模块的大型数据流是可视化的。图片作者。
数据从上到下流动,这意味着代码模块的输入位于其上,输出位于其下。
有些数据流可能很长。如果一个分支是一长串只有一个输入和输出边的代码模块,WhyFlow 允许用户将它们折叠成一个模块。

长长的模块链倒塌了。图片作者。
用户的反应
用户发现数据流可视化直观且易于导航。然而,我们发现对于较长的数据流, 很难在一个垂直视图中显示所有内容,这使得用户需要花费时间来浏览它们。 也很难把长名字的代码模块装进盒子里。
精致版

工作流视图的改进版本,使用 9 个代码模块可视化相同的大型数据流。图片作者。
由于数据流从一个方向增长,即使在折叠时导航也会变得笨拙,所以我们旋转视图,使其水平。数据流从左向右读取,这与数据从输入到输出的方向相匹配。矩形节点被小圆代替。放置关联的代码模块名称,以便大部分文本不会被截断。这个视图提供了数据流的最佳视图,而不需要太多不必要的放大和缩小。注意,我们能够在数据流中包含 9 个代码模块,而不会浪费太多的空白空间。
我们还改变了颜色编码方案。正确数据点的颜色编码从绿色变为灰色,这样用户的注意力就集中在错误的数据点上。此外,我们还在红色上添加了阴影:浅红色表示误差较小,深红色表示误差较大。具有较少暗红色错误分支的分支指示错误贡献的分散,暗红色分支指示更多可疑分支。这允许用户容易地消除要检查的分支。
细化数据面板
当用户点击模块或边缘时,使用数据面板。然后,数据面板以表格形式显示模块的输入和输出数据点。
初始版本

图片作者。
在最初的版本中,数据点被可视化在一个表格中,其中每一行都是一个数据点。它的右侧是输入数据点,这些数据点构成了数据点。左侧是没有任何从输入到输出发生变化的值的列。

添加的列和删除的列的示例。图片作者。
该表还包括关于代码模块的输入和输出之间的每列关系的信息。输入到输出列的关系类型由图标表示。以下是输入到输出列关系的类型:
- 增加列:只出现在输出中而没有出现在输入中的列被认为是代码模块创建的新列。该列用加号图标表示,并且只出现在输出表中。上图中的计数列就是一个例子。
- 删除列:只出现在输入中而没有出现在输出中的列被认为是删除列。该列用垃圾桶图标表示,并且只出现在输入表中。
- Key column :对于每条记录,同时出现在输入和输出中并且它们的值在输入和输出之间没有变化的列,则该列被认为是 Key 或 join 列。该列由钥匙图标表示。
- 被操纵的列:在输入和输出中都出现的列,对于某些记录,它们的值在输入和输出之间发生变化,则该列被认为是被操纵的列。该列由三角形或三角形图标表示。

键列和操作列的示例。图片作者。
用户的反应
模块输入和输出的并排详细表示提高了用户理解特定模块行为的性能。然而,我们的用户观察显示, 用户花费了太多的时间筛选和滚动表格 ,尤其是对于访问和产生大量列的模块,这分散了他们对错误诊断过程的注意力。
精致版

数据面板的改进版本。直方图包含在每列的顶部。图片作者。
我们相信,更有效的起源数据可视化应该能够在错误诊断期间指导最终用户。在界面的优化版本中,我们根据怀疑造成错误的程度从左到右对列进行排序。在本文的中,我们有一个关于如何计算列的可疑误差贡献的详细公式。
此外,为了帮助用户更专注于错误诊断,我们在每列上方添加了一个直方图。直方图将数据点的分布可视化,对误差有贡献的数据点用红色表示,对误差有未知贡献的数据点用灰色条表示。这使得用户可以更容易地发现最有可能导致传播错误的列。

柱轮廓由图标表示。图片作者。
我们还保留了指示输入如何影响列中输出的列图标。我们对图标进行了如下更改:
- 追加栏:出现在输出中,但输入用加号图标表示的栏。
- 键/连接列:作为键连接多个输入的列用一个键图标表示。对于从输入到输出值保持不变的列,我们不包括此图标,除非它们在连接操作中专门用作键。
- 被操作的列:其值从输入到输出被修改的列用一个 delta 图标表示。
我们还删除了用于指示输出中已删除的输入列的垃圾桶图标,因为我们已将数据面板设计为仅显示代码模块的输出数据点,而不是包含输入数据点。简而言之,这些注释允许用户在高层次上理解代码模块的行为及其输入和输出,即使用户没有看到模块的代码。
优化解释面板
解释面板显示错误的解释。它们是捕捉错误数据点的模式的谓词,这些错误数据点从不会导致错误的数据点中脱颖而出。
我们将解释公式化为谓词,因为我们观察到调试数据流涉及识别代码模块输出中的异常。这些异常通常与预期的数据点相对照。作为谓词的解释帮助用户完成比较错误输出和非错误输出的核心任务。

为 FilterByState 代码模块生成的解释。图片作者。
例如,具有异常的数据点可能在 ca_state 列中具有所有值 TX 。解释面板将显示一条解释,说明等于(Output.ca_state,TX) ,如上图中突出显示的。因此,对于每个代码模块,WhyFlow 都会对代码模块的输出进行解释。注意,WhyFlow 的解释本质上是谓词。你可以在这篇文章中读到更多关于 WhyFlow 的解释语言。
初始版本
在最初的版本中,用户必须点击代码模块才能在解释面板中看到任何内容。解释面板将显示代码模块输出中生成的解释。
这种设置促使用户首先探索工作流视图中的代码模块,并且仅在他们怀疑代码模块是错误的来源时检查解释。
用户的反应
在用户研究中,每个用户都必须在有解释面板(完整的 WhyFlow 界面)和没有解释面板(基线界面)的情况下调试数据流中的错误。基线接口模拟了真实世界场景中的调试数据流,其中不存在对错误的解释。用户必须亲自检查输入和输出数据点,并经常假设误差如何通过数据流传播,然后进一步检查误差数据点的模式,以证实他们的怀疑。
用户研究证实了为用户在其错误诊断任务中生成的解释的有用性。一个用户解释说,“我不知道(基线)发生了什么,但是解释给了我一些提示。”许多用户表示,他们使用解释来验证他们对错误原因的直觉。一位用户说:“我先在脑子里调试,然后我选择了一个解释。”另一个用户说“当我不是 100%确定的时候,解释特别有用”。
此外,90%的用户认为 WhyFlow 生成的解释简单易懂。
然而,我们观察到用户花在 WhyFlow 中选择解释的时间和识别错误的时间一样多。一些模块生成了多达 60 个解释。三名用户评论说,“有很多解释。”一个用户说“看到一千个解释就好像有几千个不同类型的错误。”四个用户发现很难辨别建议解释的质量,因为他们中的许多人有相同的表现,或者相同的精确度和回忆。
此外,我们观察到一些最终用户对他们的直觉缺乏信心,他们 花了大量的时间调查每个模块 的最佳解释。我们不得不重新思考如何向用户展示解释,这样他们就不会浪费时间一个接一个地检查每个代码模块的解释。
精致版
在优化版本中,解释面板显示所有代码模块的所有解释。最上面的解释是为什么 Flow 与错误源最相关。当用户从工作流视图中选择一个代码模块时,解释面板将只显示该特定代码模块的解释。

解释面板包含所有代码模块的所有解释。图片作者。
除了显示所有模块的解释,解释面板还根据相同的列对解释进行分组。这里 WhyFlow 显示了每个聚类的所有顶级解释。
第一个解释是在模块 FilterByState。这个解释说,大多数错误在列 ca_state 处具有值 CT 。正如你所看到的,为什么 Flow 把与错误最相关的解释排在第一位。

图片作者。
注意,有了改进的界面,用户会注意到第一个解释是关于状态的。它表示所有错误数据点的值都是 CT 。点击此解释会将数据面板分为采集的数据点和未采集的数据点。分割显示所有错误数据点的值为 CT 。这证实了用户的怀疑:FilterByState 并没有像预期的那样掉 CT 。所以,这里的错误在于这个滤波器,它没有过滤掉状态值 CT 。
结论
利用我们的用户研究结果,我们改变了 WhyFlow 的每个面板,以帮助用户更专注于调试任务,而不是被其他元素分散注意力。虽然这是一篇概述文章,但您也可以在文章中阅读更多关于具体细节的内容。
视频参考
初始界面:
最终接口:
网络写作如何帮助你找到一份数据工作
原文:https://towardsdatascience.com/how-writing-on-the-internet-can-help-you-get-a-data-job-fc364871aed1
以及为什么你应该现在就开始

照片由 Aaron Burden 在 Unsplash
在你学习编程、数据科学和机器学习的道路上,你可能已经从互联网上的博客和教程中学到了很多。他们无处不在,而且很棒。
虽然我也完全赞同推断课程,自己寻找答案,阅读博客,论坛等。,我在这里告诉你,你不仅应该阅读和学习,而且应该写作和教学!
我开始写数据科学是为了好玩。我读过很多很有帮助的文章,我也想分享一些东西。我想成为互联网上数据社区的一员,在博客上发表文章感觉很棒。但后来我意识到我写的那些文章对我的职业生涯有多么大的帮助(现在仍然如此)。
当然,我说的是数据领域,因为我的经验来源于此。但是这些想法可以延伸到任何专业领域。
为什么这么做?
一旦你的文章出现在博客上,它就开始以多种方式为你服务。首先:他们在给你做广告。人们迟早会无意中发现你的文字,要么是因为你写了他们正在学习的东西,或者更好的是,因为他们正在雇用具有你刚刚展示的能力的人,要么是全职数据工作,要么是自由写作工作(是的,你可以通过写作获得报酬)。
确保在你写的每一篇文章中插入你的 LinkedIn 个人资料,博客会吸引人们加入你的网络。我仍然从那些发现我两年前写的东西的人那里得到很多联系。
此外,当你得到一个面试机会时(你最终会得到),拥有一个发表博客文章的作品集可以让一切变得对你有利。当然,这会让你有更多的东西展示给面试官,让你和他谈论,但这也会给你和你的工作创造一种权威感。更不用说它会提高你的沟通技巧,这对于任何数据科学家或数据分析师都是至关重要的。
如果你完全是初学者,该如何开始
到现在为止,你已经注意到我是一个互联网写作的狂热爱好者。但是当我把这个告诉别人时,我通常会听到这样的话:
"但我自己完全是个初学者,我能教些什么或谈些什么呢?"
我曾经读过一篇对我有帮助的文章,当时我也有和你类似的想法:不管你是多么初级的初学者,总会有人比你更低级。
现在数据科学和编程非常热门,所以总有新人开始他们的旅程,寻找基本的内容。所以你不用怕你的文章太基础。你写的不是火箭科学、最先进的、改变世界的项目,你也没必要写。
是的,我知道开始并不容易。也许你认为自己不是一个好作家,或者你不知道该写些什么。以下是克服这一点的方法:
1.不觉得自己擅长写作?写。又来了。继续写。看似矛盾,其实不然。你写得越多,就越好。不要羞于将你的文章提交到博客或发表在你的社交网络上。这个社区非常热情,仅仅通过发布一些东西,你就已经在做超过 90%的和你竞争同样工作的人。
2.不知道写些什么?写代码。你自己的代码。如果你还没有这样做,开始创建你自己的项目。找到你喜欢的东西,看看如何将数据科学应用于其中。每次你做了什么,就写下来。我就是这样开始有了写作的连贯性。再说一次,它不一定是一个巨大的、复杂的、火箭科学项目。你会惊讶地发现,仅仅用 25 行代码就能写出这么多东西。
从哪里开始
当我过了第一个问题,第二个问题总是这样:
如果我刚开始写作,我会把我的作品放在哪里?谁会接受我的文章?
从中级开始是一个很好的选择,因为它非常容易开始。Medium 提供了一个非常直观和干净的布局,你只需要建立一个帐户并开始写作,这意味着你可以只专注于你的目标:写作。
您可以在您的个人资料上发布您的作品,无需任何人的批准。一开始你可能不会有很多观众,至少在你有一些追随者之前,但是 Medium 在分发你发布的内容方面做得很好,你最终会接触到一些人。
但是你可以把它提交给出版社。Medium 上有大量与技术相关的出版物,你不应该害怕向它们提交你的故事。几个很好的例子是:
除了媒体之外,你还有其他很棒的博客,比如 Hackernoon 和 freeCodeCamp 。
你可能认为因为这些是大刊物,他们只会接受非常先进的文章,但事实并非如此。只要你写得好,展示了一些有趣的作品或想法,他们就有很大的机会出版它。
现在就开始,但要知道会发生什么
花些时间写下你所知道的或正在学习的东西绝对没有坏处。这是双赢的局面。
但是,请记住,你并没有试图改变世界,你的第一篇文章也不会成为热门文章。知道你为什么要这样做很重要。你只是分享一些知识,提高你的沟通技巧,创建一个投资组合,扩大你的在线存在,建立一些新的联系,并试图脱颖而出。专注于那些话题。这些必须成为你的目标。
最后,记住这并不容易,但肯定是值得的。如果您需要任何帮助,请随时联系。
编写代码和编写文本比你想象的更接近,这种结合可以让你成为更好的数据从业者。记住:你的文章将为你 24/7 工作,而且是免费的。利用这一点!
有了 NLQ,编写 SQL 变得容易多了
原文:https://towardsdatascience.com/how-writing-sql-could-get-a-whole-lot-easier-with-nlq-3ffeb84b435d
机器学习
自然语言查询将会更好地改变你的数据职业

莫妮卡·西尔维斯特在 Pexels 拍摄的照片
问问题最直观、最有效、最不耗费精力的方式是什么?它是用你自己语言中最简单的词语。现代搜索引擎,如谷歌,使得使用简单的句子在线搜索信息变得很平常。这有助于创造我们的现代社会,改善全球信息的获取;很难夸大搜索引擎的出现真正带来了多大的变革。然而,在互联网上搜索信息并没有真正变得大众化和流行,直到我们可以用自然语言问互联网问题,就像我们可以和另一个人交谈一样。
对于数据专业人员来说,使用自然语言搜索信息的能力可能很快就会成为现实。不,我不是说需要每天在谷歌上搜索一百万次来复制和粘贴代码。而是键入一个句子来查询数据库,并以表格的形式返回一个答案,该答案与用来创建它的 SQL 查询配对。
这整个过程通过自然语言处理(NLP)是可能的。尽管所有关于最新和最棒的语言模型的新闻稿都导致了 AGI,但使用 NLP 驱动自然语言查询(NLQ)还不是一个解决的问题,但我相信这种能力可能比我们想象的更近。
自然语言查询简史(NLQ)
我想简单谈谈我们是如何走到这一步的,因为这与我要说的观点有关,即互联网并没有真正变得对大众“可用”,直到提问变得像说话一样直观。另外,我认为历史很有趣。
在 20 世纪 70 年代,互联网处于 ARPANet 的早期阶段,你必须浏览 FTP 文件目录。这最终导致了 1990 年发布的第一个搜索引擎 Archie。Archie 是第一个 web 索引,搜索请求是通过文本匹配来完成的,匹配方式有四种:精确匹配、正则表达式、sub 和 subcase。
20 世纪 90 年代引发了一场搜索引擎的竞赛,早期的搜索引擎只能找到一个网站,如果你知道该网站的确切名称 ⁴.慢慢地,搜索引擎变得越来越类似于日常用语,这也是互联网真正变得大众化和“可用”的时候。第一个成功做到这一点的搜索引擎变得如此受欢迎,以至于高互联网流量和没有足够的服务器的混合使网站如此之慢,以至于它实际上是 unusable⁴.
剩下的就是历史了。AltaVista 和 Ask Jeeves(后来 Ask.com)⁴在自然语言搜索方面做得比任何人都好,直到谷歌推出了他们的 PageRank 算法,用自然语言返回相关结果,彻底击败了竞争对手。
我记得上小学的时候,在谷歌上输入关键词,但仍然没有找到我想要的东西。现在,谷歌使用 BERT 语言模型进行搜索,我输入完整的句子,就像我在和另一个人说话一样,大多数时候都能得到我想要的东西。
自然语言查询能为 SQL 做什么?
就像搜索引擎被用来查询万维网一样,SQL 是任何想要与数据打交道的人的通用语。这是所有数据职业的共同点,直接访问数据库信息的民主化已经酝酿了很长时间。
SQL 语言是在互联网出现的大约同一时间创建的,那是在 70 年代。在 SQL 创建之前,一位创建了关系数据库模型的计算机科学家 ⁵提出了使用如下数学符号的查询:

图片来自SQL⁶的早期历史
SQL 的创始人唐纳德·张伯伦(Donald Chamberlin)和雷·博伊斯(Ray Boyce)在谈到创建这种语言时表示:“我们相信,应该有可能设计出一种关系语言,让没有接受过数学或计算机编程正式培训的用户更容易理解。”⁶
这导致了一种语言,通过使用熟悉的英语单词与数据库进行交流,降低了进入的门槛。对语言的持续兴趣以及它仍然是最流行的数据检索语言的事实证明了 SQL 语言的影响力和简洁性。
我相信与数据库的交互可以进一步改进,查询数据库将会有一些方面与我们今天使用搜索引擎的方式相似。这是一个非常困难的问题,但一旦解决,它将极大地提高数据专业人员的工作效率,并进一步降低外部用户访问源数据的门槛。事实上,这已经发生了。
数据库的自然语言查询已经出现了
你有没有注意到拥有人工智能辅助的自然语言查询能力已经成为可视化仪表板公司的标准?Tableau、Power BI 和 Pyramid Analytics(仅举几例)等公司都在他们的仪表板解决方案中添加了自然语言查询。Tableau 已经在 2019 年 2 月 ⁹发布了 AskData,同年晚些时候,Power BI 在他们的 Q & A 产品 ⁰.中添加了自然语言查询金字塔分析公司最近有一篇文章提到,“(金字塔的)无代码方法使公司内的非技术用户能够找到复杂商业问题的答案。这包括支持自然语言查询和人工智能支持的“直接针对数据源工作”的分析。你可以去这个链接看看他们的自然语言查询功能的视频。
作为 Tableau 用户,AskData 公布的时候我觉得真的很有意思。这是向数据访问民主化和改进自助分析迈出的又一步。我相信这是一个更大趋势的一部分,不仅改善我们分析产品的最终用户对关系数据库的访问,而且改善数据专业人员的访问便利性。
向外行人打开数据库
Tableau、Power BI 和金字塔分析是用于查询单个表的自然语言查询的示例。这很有趣,而且肯定很有帮助,但是我认为这项技术在不久的将来会提供更多。我认为真正具有革命性的是,不仅使用自然语言来查询仪表板,而且首先开发查询来创建仪表板。
Text-to-SQL,更正式的说法是数据库的自然语言接口 (NLIB) ,是一个迷人的问题,在被认为“解决”之前还有一段路要走。然而,在这一领域已经实现了一个巨大的里程碑。2019 年,人工智能能够以高于人类水平的熟练程度⁰.对单个表格进行查询甚至有像 AI2SQL 这样的网站,在那里你可以亲自尝试一下 ⁴.
尽管 AI 可以为单个表创建查询令人印象深刻,但它可能仍然不会为数据专业人员节省太多时间。对于数据专业人员来说,真正困难的是他们必须编写数百甚至数千行 SQL 代码来创建仪表板或报告。不仅需要编写大量的代码,每个查询还需要:
- 避免重复
- 移除空值
- 清理数据
- 转换数据
- 转换数据时避免错误
- 等等…
我的观点是有很多要考虑的!这个过程真正模拟的是将业务逻辑翻译成代码。
文本转 SQL 只是白日梦吗?
文本到 SQL 的想法可能看起来像是在计算机屏幕前花了太多时间的分析师的沉思,但是不管你是不是那个分析师,让你自己想象一下下面的场景。
想象一下,能够让一个主题专家在一个文档中写出业务逻辑,以及最终表格输出的一个示例,他们将这些输入到一个人工智能中。然后,人工智能会将这些方向转换成一组 SQL 查询,以产生所需的输出。在整个过程中,将进行主键检查,并避免重复。数据专家的角色将从花几个月的时间编写代码转变为审核人工智能代码输出,这可能需要几周时间。如果这是可能的,将会有巨大的效率增益。
这听起来像是白日梦,但是我相信这是这个领域的发展方向,并且有一天这将成为可能。Aerin Kim 是微软开发文本到 SQL 人工智能团队的研究员。这是她对此的看法,
就个人而言,我发现将自然语言翻译成可执行的 SQL 是一个令人着迷的问题,我认为这个项目不仅仅是一次登月。Text-to-SQL 是一个有潜力被解决的项目,一旦它被解决,它将被广泛应用,因为关系数据库在每个领域都被广泛使用。⁵
Github Copilot 不是已经在做这个了吗?
文本到 SQL 将是一个了不起的工具,数据专业人员可以用它来提高生产率。它可能会让你想起 Github Copilot,虽然它很相似,但也有一些关键的区别。
Github Copilot 将根据您的文档字符串、函数名和代码中的其他文本来建议代码。Github Copilot AI(名为 Codex)已经使用 Github 上的代码进行了训练。因此,它建议的代码本质上是基于其他程序员在⁶. github 网站上创建的模板这与文本到 SQL 不同,因为文本到 SQL 试图获取句子,而不是建议模板,它将句子翻译成代码。将文本翻译成代码的术语叫做语义解析。
两者之间最大的相似之处是,都使用语言模型为两个应用程序创建了最先进的(SOTA)模型。Codex 建立在 GPT-3 之上,当前的 SOTA 语义解析器基于 Google 的 T5 语言模型。
文本到 SQL 的当前功能是什么?
这个研究领域仍然面临的挑战是创建具有多个表的 SQL 查询,以及泛化模型,以便它即使在未经训练的数据库上也能很好地执行。
为了理解文本到 SQL 的当前状态,我想介绍目前用于基准测试的三个不同的数据集:WikiSQL、Spider 和 SPArC。
1。WikiSQL
WikiSQL 数据库 ⁷于 2017 年发布,包含 80K 手 ⁸注释问题和 SQL 查询。该数据集是从⁸.的 24K 维基百科表格中生成的为了鼓励对这一领域的探索,Salesforce 收集了数据并使其可访问。他们还创建了一个基于 RNN Seq2Seq 模型的基线模型,该模型通常用于翻译。该模型能够在 59%的情况下提供正确的文本问题输入输出(执行准确性),并且能够在 48%的情况下获得正确的 SQL 格式(逻辑形式准确性)。⁷
在一篇名为“如何与您的数据库对话”的博客文章中,Salesforce 团队解释了他们的基线 Seq2Seq 模型(名为 Seq2SQL)如何能够将文本输入转换为 SQL 查询。Salesforce 团队专注于为三件事生成 SQL 模型:聚合、select 语句和 where 子句。⁹
这是一个很好的开端,但自那以后已经取得了很大进展。⁰.发布 WikiSQL 数据集仅两年后,该数据集就实现了超人的性能 2019 年,Hwang 等人发表的一篇论文。艾尔。说,
我们介绍了 SQLova,它是第一个自然语言到 SQL (NL2SQL)的模型,在 WikiSQL 数据集上实现了人类的性能。我们回顾并讨论了 NL2SQL 文献中各种流行的方法,充分利用了 BERT……我们特别指出,我们的模型的性能接近 WikiSQL 的上限,我们观察到很大一部分评估错误是由于错误的注释造成的,我们的模型在执行精度上已经超过人类性能 1.3%。⁰
他们的模型实现了 89%的执行准确率和 84%的逻辑形式准确率。从那以后,排行榜上最高的型号在两个精度指标上都又提高了 4%。⁷
2.蜘蛛;状似蜘蛛的物体;星形轮;十字叉;连接柄;十字头
研究人员很快意识到需要另一个数据集来推进 NLQ 场,因此研究人员在 2018 年 9 月创建了蜘蛛数据集。根据蜘蛛论文摘要的解释,蜘蛛不同于 WikiSQL,因为它是在不同于测试数据的数据库上训练的,并且它引入了连接作为寻找问题答案的要求的一部分。这些是与 WikiSQL 数据集的主要区别,并且更接近于数据专业人员日常使用 SQL 的方式。
以下是 Spider 要求模型生成的查询类型的几个示例:
SELECT name , capacity FROM stadium ORDER BY average DESC LIMIT 1 concert_singerSELECT count(*) FROM concert WHERE YEAR = 2014 OR YEAR = 2015 concert_singerSELECT T2.name , count(*) FROM concert AS T1 JOIN stadium AS T2 ON T1.stadium_id = T2.stadium_id GROUP BY T1.stadium_id concert_singer
带注释的问题和 SQL 查询对示例如下所示:
问题
2013 年以后演唱会最多的球场叫什么名字,容量是多少?
SQL 查询
Spider 数据集上的最高逻辑形式精度目前为 75% (它们不使用执行精度,因为没有提供预测值)。这是一个巨大的进步,从最初的基线模型精度只有 12%。然而,要想有用,精确度必须提高,至少要达到人类可以达到的水平。
3.平流层过程及其在气候中的作用
SParC 是一个首字母缩写词,在⁴.代表semanticparsing数据集建立在蜘蛛数据集之上,其目的是关注上下文查询。这意味着您的模型可以从连续询问的多个问题中学习。
与蜘蛛数据集相比,这一挑战的难度增加了,这在排行榜得分中显而易见;此数据集的当前 SOTA 模型仅为 45% ⁴.精度测量使用与蜘蛛数据集相同的框架,该数据集被分解成 1 个 0 个不同的组件。
根据逻辑形式的准确性对查询进行评估,尽管有生成值的能力,但没有提供执行准确性。
自然语言查询的未来会是什么样子?
到目前为止,我提到的所有数据集仍然受到一个限制,即它们是特定于上下文的,这意味着它们专注于“单个任务、域或数据集”⁸.例如,本文主要关注使用关系数据库将自然语言翻译成 SQL。但是,如果关系数据库的文本到 SQL 的熟练程度达到了人类水平,那么当应用于知识图形式的数据或除关系数据库之外的任何其他形式的数据时,这些相同的模型可能就不太有用了。也就是说,大部分数据存储在关系数据库中,这仍然是一个巨大的成就。
为了解决特定上下文模型的问题,帮助开发蜘蛛数据集的同一团队创建了统一结构化知识基础框架(统一 SKG)来测试模型。他们的项目页面将结构化知识基础定义为,“[基础化]结构化知识中的用户请求[产生]各种输出,包括计算机程序(例如,SQL 和 SPARQL)、单元格值和自然语言响应。”⁹
结构化知识基础是解决该问题的元方法,采用“10,000 英尺的视角”,是语义解析和其他知识提取查询的超集。下图显示了该框架可以使用超越语义解析的 NLP 处理的文本类型的示例:

来源于 UnifiedSKG Github repo,Apache 2.0 许可证 ⁸
他们的最新型号于 2022 年 3 月发布,基于谷歌的 T5 变压器。他们的新方法使用文本作为输入和输出,以便跨任务、领域和数据集进行归纳。该团队认为,多任务学习是推动该领域发展的途径。在他们的 Github 页面上,他们说,“UnifiedSKG 是一个具有挑战性的零射击和少射击学习的试验台,T0,GPT-3 和 Codex 都在其中挣扎。”⁸
结论
这个领域在最近几年经历了令人兴奋的发展,我确信我们将很快看到巨大的进步。在你的数据库中输入自然语言查询,并让人工智能帮助你进行 SQL 查询,可能比你想象的要快。
感谢阅读!
https://medium.com/@andreasmartinson/membership
附加推荐阅读/资源:
- 如果您对编写自己的文本到 SQL 模型感兴趣,请查看这篇关于走向数据科学的文章,这篇文章将带您完成以下步骤:使用 TensorFlow 从自然语言到 SQL。
- 创建蜘蛛数据集的研究人员之一在他的关于自然语言查询的中型文章的底部有一堆很棒的链接:蜘蛛:向数据库的自然语言接口再迈进一步
- 在 2017 年 WikiSQL 数据集发布之前,研究人员一直在努力改进语义解析和文本到 SQL。最早的数据集之一是 1990 年在⁶.发布的 ATIS 这个研究领域的另一篇重要的早期论文是 1996 年在⁷.发表的 Geoquery
参考文献
- J.莱登, FTP 庆祝红宝石周年纪念 (2011),注册
- D.媒体史上的今天:第一个互联网搜索引擎于 1990 年 (2014),波因特发布。
- 和阿奇一起在 ftp 线上搜索数据 (1992),实用人士的私人技术
- 南沃恩-尼科尔,在谷歌之前:搜索的历史 (2017),惠普企业
- 埃德加·f·科德,维基百科
- D.Chamberlin,SQL 的早期历史 (2012),计算历史年鉴
- A. Kulkarni ,SQL 为何击败 NoSQL,这对数据的未来意味着什么 (2017),Medium
- 堆栈溢出趋势,堆栈溢出
- Tableau 发布 Ask Data,用自然语言分析数据的全新直观方式 (2019),Tableau
- 南 N. Karthikeshwar, Q & A 你的数据 in Power BI (2019),visualbi
- 页(page 的缩写)索沃斯(Sawers),决策情报(Decision intelligence)平台金字塔分析(Pyramid Analytics)筹资 1.2 亿美元 (2022),VentureBeat
- 自然语言查询,金字塔
- E.盘古,自然语言到 SQL 从零开始用 Tensorflow (2022),中
- AI2SQL
- A.金,【文本转 SQL】学习用自然语言查询表格 (2020),中级
- J.Howard,GitHub 副驾驶是福,还是祸?,fast.ai
- Victor Zhong、Xiong 和 Richard Socher
- Victor Zhong,Xiong 和 Richard Socher, Seq2SQL:使用强化学习从自然语言生成结构化查询。 (2017),arxiv
- 动词 (verb 的缩写)钟,如何与您的数据库对话,销售人员
- W.黄等人。艾尔。,对 WikiSQL 进行全面的探索,具有表格感知的单词上下文化 (2019),arxiv
- 陶宇,蜘蛛,Github
- 陶喆等。艾尔。, Spider:用于复杂和跨领域语义解析和文本到 SQL 任务的大规模人工标注数据集 (2018),arxiv
- 陶喆等。艾尔。,蜘蛛 1.0:耶鲁语义解析和文本到 SQL 的挑战,Github
- 陶喆等。艾尔。,SParC 1.0:Yale&sales force 语义解析和文本到 SQL 的上下文挑战,Github
- 陶喆等。艾尔。, SParC:上下文中的跨域语义解析,Github
- 页(page 的缩写)j .普莱斯,口语系统的评估:ATIS 领域 (1990),aclanthology.org
- J.M. Zelle 和 R. J. Mooney,学习使用归纳逻辑编程解析数据库查询 (1996),aaai.org
- 陶喆等。al, UnifiedSKG:基于文本到文本语言模型的统一和多任务结构化知识
- 陶喆等。阿尔,结构化知识基础介绍,Github
- 陶宇,蜘蛛:向数据库自然语言接口再迈进一步 (2018),中型
如何让熊猫加入速度提高 5 倍
原文:https://towardsdatascience.com/how-you-can-make-pandas-joins-5-times-faster-3ef0d157e70b
pd.merge()的更快替代方案

乔纳森·彼得森在 Unsplash 上拍摄的照片
处理真实世界的数据集一直吸引着数据科学家去大规模构建优化、高效和准确的系统。毫无疑问,优化是构建现实世界软件解决方案成功的关键。虽然我知道不是每个人都在大规模地构建解决方案,但是了解优化技术仍然是有帮助的,甚至适用于一般的数据科学用例。
因此,在这篇文章中,我将考虑 Pandas 中最受欢迎的函数之一,即连接、,并通过实验证明分享如何优化 Pandas 合并操作近 5 倍。如果你对熊猫的加入不熟悉,我强烈推荐这篇文章,以便在继续下一步之前了解它们。
实验 1:使用 pd.merge()

pd.merge 的代码片段(作者使用 snappify.io 创建的图像)
这里,我们创建两个虚拟数据帧,并使用 pd.merge() 函数合并它们——显式地指定对哪些列执行合并操作。
实验 2:索引列并使用 df.join()

pd.join 的代码片段(作者使用 snappify.io 创建的图像)
类似于实验 1,我们创建两个虚拟数据帧。然而,我们不是直接进行连接,而是首先索引数据帧,然后采用 J OIN 方法来合并数据帧。
实验设置:
- 在两个实验中,即实验 1 和实验 2,两个数据帧都具有相同数量的行和两列,其中一列用于连接数据帧。
- 我考虑了不同大小的数据帧,从 100 万行到 1000 万行,每次实验都将行数增加 100 万。
- 我在固定数量的行上重复了十次实验性试验,以消除任何随机性。
- 下面,我报告了这十次试验中合并操作的平均运行时间。
结果:
现在,是时候给出令人难以置信的结果了。

描述连接操作运行时的实验结果(图片由作者创建)
- 上面的图描述了实验 1 中执行合并操作和实验 2 中执行(索引+合并)操作所花费的时间(以毫秒为单位)。
- 正如我们从图中看到的,两个实验的运行时间有很大的不同——实验 2 的速度接近实验 1 的 5 倍。
- 随着数据帧大小的增加,两个实验的运行时间之间的差异也增加。这表明索引应该始终是您处理较大数据集的方式。
- 两个连接操作几乎都随着数据帧的大小而线性增加。然而,实验 2 的运行时间增加的速率远低于实验 1。
最后,我强烈建议阅读这篇文章的每个人立即停止使用 pd.merge()来执行连接操作,尤其是如果您正在处理大量数据的话。
感谢阅读。我希望这篇文章是有帮助的。

作者创造的模因
如何准备现场编码数据科学家面试
找到真正的面试问题,并使用在线工具,这将帮助你粉碎你的下一个现场编码面试。

来自像素的图像
现在,大多数数据科学职位都要求你懂一两门编程语言,而现场编码是面试过程中的一个重要部分。这里有一些建议可以帮助你准备下一次面试。
准备面试
确保你问你的招聘人员他们是否有面试指南或者你可以浏览的示例编码问题。
他们通常建议解决一些 leetcode 或 hackerrank 问题。如果您想熟悉从基础到高级的语法,这些问题非常有用。除此之外,你还可以上玻璃门、盲人或 reddit 网站,尽可能多地收集面试问题,然后把它们全部放在一个文档里,解决所有问题。
我记得大约一年前我在 Meta 面试的时候,我从和我一起面试的人那里收集了大约 30 个编程(python 和 SQL)问题,我试着在给自己计时的同时解决所有的问题。
我用的是 python 的 jupyter notebook(py charm 或者其他任何工具也可以)。glassdoor 上分享的一些问题写得不好,但很容易得到上下文,所以你可以重写它。

作者图片
对于 SQL,我用 coderpad 来练习。Coderpad 有一个免费版本,你可以创建一个小数据库,在那里你可以尝试做一些查询。

作者图片
只要能运行代码,也可以用其他工具来练习。我建议尝试 coderpad,因为大多数公司都使用这个工具(或类似的工具,但外观相同)进行现场编码面试——你可以熟悉一下。
在现场编码采访中
慢慢来
不要埋头解决问题,退一步,花时间讨论给出的问题。确保你理解被询问的内容和数据。这真的很重要,尤其是如果你面试的是数据科学或数据工程的职位。
例如,如果要求您创建一个按产品列出的总销售额表,并且您有像产品价值和订单价值这样的列,如果您对这些列的理解是正确的,您可以首先确认它们(即,产品价值是否为您提供了产品的价格,订单价值是否为您提供了包括产品和其他一些东西在内的整个订单的价值?)一旦你确认了这些,就更容易理解如何找到正确的解决方案。
最糟糕的情况:你变得紧张,无法集中注意力
当你紧张时,很难集中注意力并理解手头的问题,最糟糕的情况是——它会在你的大脑中引起轻微的恐慌,从而导致精神障碍。确保你随身带着笔和纸。当面试官提问时,记下问题(比如做笔记)。另外,用要点写下关于这个问题的额外信息。这将有助于你更好地处理信息,也有助于你在陷入沉思时记住被问到的问题。
我个人总是随身带着笔和纸,因为我发现复杂的东西读两遍后更容易理解。之后勾画出我的想法也有助于我更有条理和验证我的解决方案。
你的面试官是你的朋友
你的面试官是你的编程伙伴——可以随意问他任何关于语法的问题。如果你不确定如何解决问题,你可以寻求解决问题的建议。
面试后
他们通常会问你是否有问题要问他们。我通常会准备一两个与他们的角色、他们使用的工具或他在采访中提到的任何我感兴趣的问题。
在你发完感谢邮件后,去点餐或烹饪你最喜欢的食物,因为你应该为你所有的努力工作得到奖励!
推特:【https://twitter.com/itskathleenlara】T4
网址:https://www.kathleenlara.com/
拥抱脸刚刚发布了扩散器库
原文:https://towardsdatascience.com/hugging-face-just-released-the-diffusers-library-846f32845e65
使 DALL-E 2 和 Imagen 等扩散器型号比以往任何时候都更容易使用

由作者编辑,背景照片由 Michael Dziedzic 在 Unsplash 上拍摄
拥抱脸,变形金刚库的创建者,刚刚发布了一个全新的库,用于构建扩散器模型。如果你不确定扩散器模型是什么,就把它们想象成今年一些最受关注的人工智能模型背后不那么秘密的秘密。
你在互联网上看到的那些美得令人发狂的、有创意的、几乎人性化的艺术作品?它们是来自 OpenAI 的 DALL-E 2、Google 的 Imagen 和 Midjourney 的图像。所有这些服务都使用扩散器模型生成它们的图像。

一些 DALL-E 2 生成的图像[1]。
现在拥抱脸发布了一个专注于扩散器的开源库。有了它,我们只需几行代码就可以下载并生成图像。
新的扩散器库使得这些极其复杂的模型变得直观而简单。在本文中,我们将探索新的库作品,生成一些我们自己的图像,并看看它们与上面提到的最先进的模型相比如何。
如果您喜欢视频,可以观看本文的视频演示:
入门指南
首先,我们需要pip install diffusers并初始化扩散模型或管道(通常由预处理/编码步骤和扩散器组成)。我们将使用文本到图像的扩散管道:
现在,我们需要做的就是创建一个提示,并通过我们的ldm管道运行它。从拥抱脸的介绍性笔记本中获得灵感,我们将尝试生成一幅松鼠吃香蕉的画。
我们走吧。这简直太容易了。现在图像不像 DALL-E 2 那样令人印象深刻,但我们用五行代码做到了这一点,而且是免费的,在新库的第一个版本中。如果这不酷,我不知道什么是酷。
这是另一幅松鼠吃香蕉的画:
也许是现代艺术?
快速工程
自三大扩散模型(DALL-E 2、Imagen 和 Midjourney)发布以来,出现了一个有趣的趋势,即人们越来越关注所谓的“即时工程”。
快速工程师就是罐头上写的。字面上的“工程”的提示,以达到预期的结果。例如,许多人发现添加“在 4K”或“在 Unity 中渲染”可以增强三巨头生成的图像的真实感(尽管它们都不是 4K 分辨率的)。
如果我们用简单的扩散器模型做同样的尝试会发生什么?
每张图片都有这样或那样的怪异之处,这些香蕉的摆放位置当然也有问题。但是,你得给模特加分;一些松鼠的细节相当不错,看看图片 1 中香蕉的倒影。
摄影师和画家们,注意了,你们来了。
在罗马时
我目前住在罗马,这在盛夏是个糟糕的主意。尽管如此,比萨是首屈一指的,没有比罗马圆形大剧场更具标志性的建筑了,我想,太好了,如果一个意大利人在那个标志性建筑上吃比萨会是什么样子?
当然,我们并没有坐在竞技场的顶端,但是我很感激你的努力。斗兽场本身看起来很棒,尽管拱门之间的天空颜色不匹配。
除了奇怪的热狗手和小披萨,我们的意大利披萨看起来很棒。太阳镜的选择给他一种 90 年代父亲的感觉。
很难从这个单一的提示中做出任何有把握的判断,但我认为有趣的是,该模型没有生成任何女性吃披萨的图像,尽管约 51%的意大利人是女性[2]。该模型也没有生成非白人男性的图像——然而,我没有充分运行该模型来确定后者是否具有统计学意义。
这种模型和其他未来拥抱脸托管模型中的偏见的影响无疑将是图书馆现在和未来的一个重要焦点。
Squirrelzilla
回到松鼠,试图生成更抽象的图像,如“一只巨大的松鼠摧毁了一座城市”,会导致混合的结果:
对我来说,这个模型似乎很难融合两个典型的不相关的概念,一只(巨大的)松鼠和一座城市。从同一个提示生成的这两个图像似乎强调了这种行为:
在这里,我们可以看到一个城市的天际线,或者在一个更常见的与松鼠相关的环境中看到一个类似松鼠的物体。在运行这些提示几次后,我发现它在两者之间切换,并且从未将两者合并在一起。
只是为了好玩,下面是 DALL-E 2 从提示"a dramatic shot of a giant squirrel destroying a modern city"中产生的内容:

由作者使用 OpenAI 的 DALL-E 2 生成。
这些都非常令人印象深刻,但正如已经提到的,我们不能指望这两个选项之间的可比性能,目前。
这就是第一次看拥抱脸的最新图书馆。总的来说,我非常兴奋看到这个图书馆的发展。现在,最好的扩散器模型都被锁在紧闭的门后,我认为这个框架是一把钥匙,可以释放人工智能推动的创造力的一些令人敬畏的水平。
这并不是说这个框架即将取代 DALL-E 2、Imagen 或 Midjourney。事实并非如此,随着商业和开源产品之间的选择越来越多,世界变得越来越美好。
这些开源模型允许像你我这样的普通人获得深度学习的一些最新进展。当许多人自由尝试新技术时,很酷的事情就会发生。
我很期待这一天的到来。如果你有兴趣看到更多,我会定期在 YouTube 上发帖,并和其他许多对 ML 感兴趣的人一起活跃在 Discord here 上。
感谢阅读!
参考文献
[2] 意大利人口统计数据 (2019),联合国《世界人口展望》
拥抱面孔推断终点
原文:https://towardsdatascience.com/huggingface-inference-endpoints-8984e7d8d8d4
变压器模型的快速生产级部署

图片来自 Unsplash 由 Towfiqu barbhuiya
我的文章中一个不变的主题是你的机器学习模型的部署。随着机器学习越来越受欢迎,用户的模型部署选项也越来越多。特别是 HuggingFace 已经成为机器学习领域的领导者,对于数据科学从业者来说,你很可能在过去使用过变形金刚模型。
HuggingFace 与 AWS 和 Azure 都有合作关系,并提供了跨云提供商的部署选项。虽然在这些云提供商上部署 Transformers 模型是一个相对容易的过程,但它确实需要一些关于他们的生态系统的知识。HuggingFace 如何为模型托管提供生产级基础设施,同时让用户专注于他们的模型?
引入 HuggingFace 推理端点。这个托管选项仍然集成了两家云提供商提供的基础设施,但抽象出了他们的 ML 服务所需的工作,如 Amazon SageMaker 和 Azure ML 端点。
在这篇文章中,我们将看看如何旋转你的第一个 HuggingFace 推断端点。我们将设置一个示例端点,展示如何调用端点,以及如何监控端点的性能。
注意:对于这篇文章,我们将假设对 HuggingFace/Transformers 和 Python 有基本的了解。对于本文,您还需要创建一个 HuggingFace 帐户并添加您的账单信息。确保删除您的终端,以免产生更多费用。
目录
- 设置/端点创建
- 端点调用/监控
- 其他部署选项
- 其他资源和结论
设置/端点创建
如前所述,确保创建一个 HuggingFace 帐户,您将需要添加您的账单信息,因为您将创建一个由专用计算基础架构支持的端点。我们可以到推理端点主页开始部署模型。
使用推理端点创建,需要考虑三个主要步骤:
- 型号选择
- 云提供商/基础设施选择
- 端点安全级别
要创建一个端点,您需要从拥抱面部中枢中选择一个模型。对于这个用例,我们将采用一个 Roberta 模型,它已经在 Twitter 数据集上进行了调整,用于情感分析。

型号选择(作者截图)
选择了端点部署模型后,您需要选择一个云提供商。对于这个实例,我们将选择 AWS 作为我们的提供商,然后我们可以看到哪些硬件选项可用于 CPU 和 GPU。

更高级的功能是设置自动缩放配置。您可以设置最小和最大实例数,以便根据流量负载和硬件利用率进行伸缩。
除此之外,在高级配置中,您还可以控制您的模型的任务,源框架,以及一个定制容器映像。此映像可以包含您可能安装的其他依赖项或您在映像上安装的其他脚本。您可以指向 Docker Hub 上的图像,也可以指向您的云提供商图像注册表,如 AWS ECR。

高级配置(作者截图)
最后,您还可以定义端点背后的安全级别。对于私有端点,您必须使用 AWS PrivateLink ,对于端到端指南,请遵循朱利安·西蒙的示例此处。为了简单起见,在这个例子中,我们将创建一个公共端点。

端点的安全级别(作者截图)
现在,您可以创建端点了,应该在几分钟内就可以完成配置。

端点运行(作者截图)
端点调用/监控
为了调用我们的端点,推理端点 UI 通过提供一个自动化的 curl 命令使之变得简单。

测试终点(作者截图)
curl https://ddciyc4dikwsl6kg.us-east-1.aws.endpoints.huggingface.cloud \
-X POST \
-d '{"inputs": "I like you. I love you"}' \
-H "Authorization: Bearer PYVevWdShZXpmWWixcYZtxsZRzCDNVaLillyyxeclCIlvNxCnyYhDwNQGtfmyQfciOhYpXRxcEFyiRppXAurMLafbPLroPrGUCmLsqAauOVhvMVbukAqJQYtKBrltUix" \
-H "Content-Type: application/json"
使用 curl 命令转换器,我们可以获得等效的 Python 代码来测试本地开发环境中的端点。
import requests
import time
headers = {
'Authorization': 'Bearer PYVevWdShZXpmWWixcYZtxsZRzCDNVaLillyyxeclCIlvNxCnyYhDwNQGtfmyQfciOhYpXRxcEFyiRppXAurMLafbPLroPrGUCmLsqAauOVhvMVbukAqJQYtKBrltUix',
# Already added when you pass json=
# 'Content-Type': 'application/json',
}
json_data = {
'inputs': 'I like you. I love you',
}
def invoke_ep(headers, json_data):
response = requests.post('https://ddciyc4dikwsl6kg.us-east-1.aws.endpoints.huggingface.cloud', headers=headers, json=json_data)
return response.text
我们可以通过长时间发送请求来进一步对端点进行压力测试。
request_duration = 100 #adjust for length of test
end_time = time.time() + request_duration
print(f"test will run for {request_duration} seconds")
while time.time() < end_time:
invoke_ep(headers, json_data)
我们可以使用推理端点分析 UI 来观察这些请求和端点性能。在这里,分析仪表板为我们提供了请求计数和延迟指标,以便我们了解我们的流量和相应的端点性能。

如果您需要调试您的端点,您也可以在 UI 上查看容器日志。在这里,我们还可以跟踪单个请求的持续时间,您在自定义推理处理程序或自定义容器映像中添加的任何日志记录都会在这里得到反映。

容器日志(作者截图)
要更新或删除端点,请根据需要转到“设置”选项卡来管理您的资源。

其他部署选项
在 HuggingFace 中,您也可以实现不同的托管选项。有免费的托管推理 API ,在采用推理端点之前,您可以用它来测试您的模型。此外,还有一个 SageMaker,HuggingFace 与它紧密集成。HuggingFace 支持容器图片,你可以在 Amazon SageMaker 上使用它们进行训练和推理。除此之外,还有 HuggingFace Spaces ,你可以利用它通过 Streamlit 和 Gradio 框架为你的 ML 模型构建快速 UI。
其他资源和结论
https://github.com/RamVegiraju/HuggingFace-Examples
有关示例的代码,请单击上面的链接。如需进一步了解与 HuggingFace 相关的内容,请点击此处的列表。要开始使用 HuggingFace 推断端点,请遵循官方文档。我希望这篇文章对那些开始接触 HuggingFace 推断端点的人来说是一个有用的指南,请继续关注这个领域的更多内容。
如果你喜欢这篇文章,请在LinkedIn上联系我,订阅我的媒体 简讯 。如果你是新手,使用我的 会员推荐 报名。
人在回路系统—您需要知道的一切
原文:https://towardsdatascience.com/human-in-the-loop-systems-all-you-need-to-know-c260920b8acf
关于 HITL 系统你需要知道的一切

今天,机器学习系统已经在每个行业取得了进展,无论是医学、考古学、购物、物流等。随着使用的增加,开发人员需要确保他们的系统能够很好地处理不断变化的数据、不同的地理位置以及各种各样的客户或最终用户。以及最近在机器学习研究中获得势头的良好性能、可解释性和数据隐私。
由于模型的所有参数都是使用训练数据来优化的,因此该模型可以被认为是数据的高级汇总。确保良好的培训数据是一项挑战,尤其是当这项任务在 ML 行业相对较新的时候。数据也随地区而变化;例如特定语言的口音(对于音频分类问题)、温度和压力之类的物理参数(对于天气预报)、消费者行为的变化等等。
考虑到所有这些问题,人在回路(HITL)系统已经被开发团队引入并采用。它帮助他们将人类知识和监督引入他们的 ML 系统,从而提高系统的整体性能和可靠性。
人在回路(HITL)系统

典型的 HITL 系统。来源:作者图片
HITL 系统让人类参与到改进 ML 系统的过程中,从而打破了计算机在最终决策过程中的自主性
拥有大量处理和数学的人工智能系统旨在为人类客户服务。如果最终客户不满意,没有什么算法是好的。HITL 将人类智能与机器智能相结合,以解决机器学习任务。手动标记数据实例是最简单的一种 HITL 活动,可以在 ML 开发团队中看到。
人类和机器知识的组合也可以用于提供增强的结果,因为人类和机器都可以解决彼此的限制,从而最大化系统的整体性能。在某些情况下,在事先有高质量数据集的情况下,该模型可以被训练来制定人类行为。我们将涵盖 HITL 系统的各个方面,并探索它们在解决复杂问题方面的真实潜力。
HITL 系统有什么帮助?
ML 系统中的透明度
当我们在 ML 系统中引入一个人时,这个系统需要能够被人类自己解释。引入人工操作员可以增强系统的可解释性。在对人的监视中采取了关键步骤,这使得系统透明。深度神经网络的可解释性已经取得了进展,深度神经网络通常被称为、【黑盒】、,因为很难理解某个预测是如何做出的。
处理边缘用例

在数据中发现异常值。来源:作者图片
通常,在机器学习中,我们的目标是训练一个对异常值具有鲁棒性的模型。离群值是与其他数据点显著不同的数据点。但是在某些情况下,特别是对于随时间演变的数据,离群值可能有助于理解用户/消费者行为中可能出现的某些趋势。
这使得开发人员在将新的 ML 系统部署到产品中时缺乏信心。作为第一次迭代,ML 系统可能还不够成熟,不足以处理边缘情况。这种情况可以由人类审查员来处理,以便纠正模型所犯的错误。这些极端情况可以进一步分析,并可以并入 ML 模型的训练中。这确保了安全性和不断改进的 ML 系统。
确保更安全的 ML 系统
ML 系统可用于安全性和可靠性是必须的情况。在实验室和诊所使用 ML 系统必须安全,因为结果直接关系到患者的健康。让人类专家作为系统的一部分确保了安全性和精确性。监控生产线上产品质量的机器人也需要高效,因为任何故障都可能导致工业损失。
在有限数量的样本上训练的 ML 系统在一些真实世界场景中可能表现不佳。在这种情况下,人类可以纠正系统的预测,从而避免任何不幸。从另一个角度来看,人类专家也会犯错误,或者对同一问题有不同的看法。这种复杂情况可以用 ML 系统来处理,ML 系统被训练成在类似的情况下执行相同的操作。因此,ML 系统纠正人类,人类反过来纠正 ML 系统,从而产生更精确和安全的系统。
现成任务的数据标签

数据标签。来源:作者图片
对于像识别数字、英语到法语的翻译、人体姿势估计这样的任务,管理数据集是很容易的,因为它们已经在学术和工业用例中广泛使用。
考虑到语言翻译,可能无法获得一小部分人使用的语言的数据。类似地,对于人类姿态估计,指示关节和身体部位的位置的姿态数据点对于某些姿态可能是不可用的,这些姿态是不常见的,但是对于要解决的问题是必不可少的。
在没有数据来训练(或测试)ML 系统的任务中,开发人员需要收集数据并标记。让人类来标记数据将提供高质量的训练样本,这将产生高效的 ML 系统。
HITL 系统的方法
亚马逊土耳其机械公司
亚马逊机械土耳其人(abbrev。MTurk)是一种为数据标记、内容审核、调查等提供分布式劳动力的服务。他们的劳动力分布在世界上的几个国家,这有助于消除分配给他们的任务中的任何形式的偏见。
该服务可在 HITL 系统中发挥重要作用,通过按需临时劳动力构建高质量数据集。他们还可以分析模型的预测,并指导开发人员如何解决特定的问题或异常值。
文本分类
文本分类是自然语言处理(NLP)中最基本的任务之一,其中用户给定的句子被分成两个或多个类别。HITL 系统是由 Karmakharm 等人【1】在谣言分类任务中引入的,恰好是文本分类的一个用例。他们获得了一个谣言新闻文章或帖子的数据集,然后在其上训练了一个谣言分类系统(文本分类器)。接下来,记者被要求回顾模型的预测,并相应地进行修正。然后,将这些校正/人工注释合并到数据集中,并重新训练模型。
由于文本分类系统大多是深度神经网络,它们缺乏可解释性,因此被称为“黑盒”。使用 HITL 方法,确保了洗钱系统的透明度。
图像恢复
图像恢复指的是那些提高噪声/损坏图像质量的技术。通过在过程中引入人类,HITL 方法可用于改进模型的预测。 Weber 等人【2】在他们的研究“跟我一起画:人在回路中的图像恢复”中实现了这样一种方法,其中人类知识可以嵌入到模型的预测中,以改善每次迭代的图像恢复。在他们的方法中,图像首先通过图像恢复模型进行初步恢复。然后,这些恢复的图像被传递给操作员,操作员可以通过预先设计的用户界面对图像进行微调。微调后的图像再次被传递给图像恢复模型。
因此,循环继续,直到产生满意的图像。这样,考虑到用户的需求,人类的知识可以用来为模型提供关于如何恢复图像的先验信息。
[1] T. Karmakharm,N. Aletras,K. Bontcheva,“记者-in-loop:作为谣言分析服务的持续学习”,自然语言处理实证方法会议(EMNLP),2019 年,第 115-120 页。
[2] T. Karmakharm,N. Aletras,K. Bontcheva,“记者-in-loop:作为谣言分析服务的持续学习”,自然语言处理实证方法会议(EMNLP),2019 年,第 115-120 页。
结束了
希望你喜欢这个故事。如果你有任何疑问/建议,请在下面的评论中告诉我。感谢您的阅读,祝您有美好的一天!
人在回路中的文本提取系统
原文:https://towardsdatascience.com/human-in-the-loop-text-extraction-system-a5f32631142c
机器如何学习从人类给出的文本亮点中提取文本
假设一位金融分析师希望分析几年来犯罪率的变化。她有一个公司财务新闻稿的数据集。她必须从数据集中提取所有的收入和收益。她想制作一个类似这样的表格,以便进一步分析。
此外,她可能还希望自动化这一过程,这样,如果她的新闻稿数据集增加,她就可以轻松地提取新的收入和收入数字。

信息提取。图片作者。
简而言之,我们的财务分析师必须执行一项信息提取任务。信息提取或 IE 自动从大量文档中提取数据。
在现实生活中,IE 还有其他场景,通常涉及不一定具有编码背景的人,这些背景将帮助他们快速编写代码以帮助他们执行 IE:
- 社交媒体分析:从 Yelp 评论中提取用户评级。
- 金融分析:从 SEC 数据中提取关键人物及其与美国大公司的关系。
- 医疗保健分析:从健康表格中提取数据,以了解某些药物的副作用。
在本文中,我们将深入讨论一种叫做 SEER 的交互式人在回路工具。SEER 帮助使用这种文本数据集的用户从中提取相关数据。
使用 SEER 中用户突出显示的示例提取数据

突出显示要提取的文本示例。图片作者。
SEER 中的用户将突出显示他们希望提取的文本示例。正面的例子是他们希望提取的文本。反面例子是他们不想提取的文本。
SEER 学习一个模型后,SEER 会给出一组提示和一些提取规则,这些规则是将代表用户执行其余提取任务的程序。

通过对提取回答“是”或“否”来过滤规则。图片作者。
用户对提示回答“是”或“否”,然后根据用户的反馈过滤出提取规则。
用户选择提取规则以便预览提取。然后,选定的规则将突出显示文档中的其余提取,并将数据集中所有文档的提取制成表格。然后,用户可以使用突出显示的文档或表格来决定所选的提取规则是否正确地执行了所需的提取任务。
否则,可以通过突出更多正面和负面的例子来完成进一步的微调。
SEER 中的提取规则
在设计 SEER 时,我们考虑了许多机器学习技术。然而,很难找到一种方法,该方法可以从用户的少量文本高亮示例中准确地学习语法文本模式,而不需要用户具有某种程度的技术知识来微调机器学习模型或编写代码来对输出中的任何错误进行后处理。然而,我们的目标用户不需要任何编码背景或机器学习专业知识,例如记者、调查人员、金融分析师等。
我们决定使用基于规则的模型,它本质上是执行提取的程序。在 SEER 中,我们将它们称为提取规则。SEER 将自动生成初始提取规则,如果用户需要微调规则,则用户可以突出显示其他示例以提供给 SEER 学习。他们还可以选择导出规则来直接编辑它们。
提取规则语言
SEER 从用户高亮显示的文本示例中学习视觉提取规则。提取规则是一系列的原语。原语提取一个或多个令牌。记号是由空格和换行符分隔的单词。例如,以粗体显示,5.4%被标记为单独的标记。5、点、4 和百分比。有 5 种类型的基元可以形成规则:

图片作者。
5 种类型的原语,以及原语可以提取的文本的样本标记。图片作者。
- 预置提取实体,比如组织、整数、百分比、电话号码等等。
- 文字提取精确的字符串。因此 literal percent 只提取数据集中单词 percent 的实例。
- 字典提取出现在字典条目中的文本。
- 正则表达式或简称 regex 在 SEER 中通过一个预定义的正则表达式库得到支持。包含[A-Za-z]+的黄色框是捕获包含小写或大写字母的标记的正则表达式。
- 令牌缺口可以抽取任何令牌。包含“0 到 1”的令牌间隙意味着它将跳过提取 0 到 1 的单词,直到令牌间隙之后的下一个匹配原语。令牌间隙用在原语序列的上下文中。
以下是 SEER 可以提取的提取规则示例:

图片作者。
这个原语序列可以捕获百分比。它从一个预先构建的整数开始,跳过 0 到 2 个标记,直到下一个原始匹配出现,在这种情况下是文字百分比。这个规则可以捕获文本“5.4%”。
为什么学习提取规则具有挑战性
虽然提取规则的语言相当简单,但是自动生成提取规则存在许多挑战。详情可在这里找到。但以下是为什么它具有挑战性的总结:
- 用户可以提供有限数量的示例&反馈:规则学习算法被限制为从少数示例中学习,最少 2 个示例到最多平均 6 个示例。
- 有限数量的用户输入导致有效规则的大搜索空间。该算法必须遍历候选规则的指数级大空间。因此,我们将规则存储在一个树形结构中。然后,我们根据人类开发人员如何手工制作规则,通过学习和排列规则来有效地搜索空间。此外,我们通过用户界面利用用户反馈来进一步修剪搜索空间。
- 用户可以提供遵循稍微不同模式的例子。SEER 还必须学习捕获多个变量的规则,它通过学习多组提取规则来做到这一点。
【SEER 如何学习抽取规则?
鉴于这些挑战,SEER 如何学习规则?概括地说,算法如下:
- 生成规则: SEER 的学习算法生成捕获正例的有效规则序列。捕捉反面例子的规则被丢弃。
- 归纳规则:然后归纳规则。一般化意味着交叉特定于一个例子的规则,并创建一个尽可能多地捕捉正面例子的规则。
- 等级规则:此外,SEER 为规则的原语分配分数,这些分数反映了人类开发人员在手工制作提取规则时如何选择和偏好某些原语。
- 选择规则: SEER 将相似的规则分组在一起,并为每个组选择一个规则。然后向用户显示这些规则。SEER 也呈现出多样的规则,意味着大多数规则并不仅仅包含一种类型的原语。这有助于 SEER 捕捉各种用户意图。
- 提出规则,获得用户反馈。
算法的细节可以在一篇更长的论文中找到。
1。生成规则

作者 Yannis Katsis 的图片。
第一步包括为每个例子生成候选规则。在这个场景中,有两个积极的例子:“300 万美元的收入”和“40 亿美元的收入”。除了上图中显示的,肯定还有更多规则。

图片作者。
规则的数量可能是指数级的。对于正面示例中的每个令牌,最多有 5 个基元可以捕获它。候选规则存储在树形结构中,以便节省空间。从根节点到叶节点的路径代表一个候选规则。这种结构允许规则的任何相似前缀由相同的初始路径表示。
2。归纳规则

作者 Yannis Katsis 的图片。
然后,预言家归纳出规则来捕捉所有积极的例子。概化是通过交集运算完成的。在上图中,我们展示了从“收入 300 万美元”和“收入 40 亿美元”生成的规则的交集产生的规则子集。
仅当第一个正例的规则和第二个正例的规则的基元序列相交时,它们才相交:
- 预构建的图元彼此相交。
- 如果文字值相等,则文字原语与文字相交。
- 如果正则表达式相同,则它们相交。
- 字典和其他字典有交集。
- 除了规则的开头或结尾,可以在任何地方插入标记间隙。
此外,文字和字典原语可以与相互合并:
- 文字原语相交形成字典规则。
- 字典和文字可以合并成一个组合字典。
- 字典可以互相合并,组成一个更大的字典。
假设用户还突出显示了“500 万美元的收入”和“60 亿美元的收入”。规则将而不是 与“收入 300 万美元”和“收入 40 亿美元”产生的规则相交。在这种情况下,SEER 维护几组规则,每组规则对应一组交叉规则。

作者 Yannis Katsis 的图片。
这使得 SEER 能够捕捉到示例中的任何细微变化。在这种情况下,在正面示例的开头,货币金额有一个变化,例如“300 万美元”。第二个变量在正例的末尾有货币量。
3。等级规则
在算法的这一点上,仍然有许多候选规则。由于我们无法将它们全部显示给用户,因此该算法会为等级规则分配分数。
给定某种类型的令牌,规则根据人类开发人员如何偏好某种类型的原语而不是其他类型来排序。有两种类型的令牌:
- 语义记号:具有自然意义的记号。这些通常是实体,如学校名称、组织、货币金额等。SEER 识别一个语义标记,如果它被一个预先构建的原语捕获。
- 语法标记:表示语法的标记,例如破折号、冒号和填充冠词,如‘a’或‘the’。

评分函数,一个用于语义标记,另一个用于语法标记。图片作者。
根据令牌的性质或种类,我们应用两个评分函数之一:
- 在第一个评分函数中,如果令牌是语义类型的,我们分配分数,使得预构建优先于文字和字典,预构建优先于令牌间隙和正则表达式。例如,如果令牌具有自然含义,例如,令牌“迪拜”指的是一个城市,那么预建的“城市”将比字面上的“迪拜”或正则表达式排名更高。
- 在第二个评分函数中,如果标记表示语法,我们分配分数,使得标记间隙或正则表达式优先于文字和字典。

作者 Yannis Katsis 的图片。
这些评分函数来自我们对人类开发人员在创建规则时如何选择原语的观察。基于这种偏好,我们将其映射到数值作为原始分数。我们进行了一项研究,表明我们的评分试探法和人类开发人员对不同原语的偏好之间有很强的一致性。你可以在这里阅读更多关于 T4 研究的内容。
4。 选择规则
一旦将分数分配给图元,并且计算出规则的分数(作为图元分数的平均值),就选择一些规则呈现给用户。虽然只选择排名前 k 的评分规则可能更好,但我们也需要规则多样化。换句话说,选择 top-k 评分规则可能会导致一组主要包含预构建或主要包含正则表达式的规则,这是不希望的。这是因为用户可能想要一个特定的规则,例如包含文字和预置的组合的规则,或者用户可能想要一个通用的规则,例如主要包含正则表达式的规则。但是选择 top-k 评分规则可能只偏向一个方向,并且可能违背用户的意图。
很难真正知道用户的意图,例如,是特定的规则还是通用的规则,因为用户给出了少量的文本示例(平均 6 个正面示例)。为了捕捉用户意图的多样性,SEER 生成了一组不同的规则。它是按如下方式完成的:
- 将相似的规则组合在一起。不管规则中的顺序如何,如果规则由相同的原始类型组成,则规则彼此相似。
- 从每组中选择得分最高的规则。

图片作者。
在上面示出了生成的一组规则的情况下,每个规则具有唯一的一组原语,并且该组规则被认为是多样的。该规则集将呈现给用户。规则将按照规则得分的顺序呈现给用户。
5。展示规则并获得用户反馈。
最后,用户可以通过接受或拒绝提取来过滤掉规则:

作者 Yannis Katsis 的图片。
结论
在本文中,我们描述了一个名为 SEER 的人在回路的文本提取系统。SEER 学习一个基于规则的模型,并与用户进行交互,以帮助他们快速准确地完成提取任务,而无需了解代码。如果你想了解更多关于我们与 SEER 一起进行的用户研究,请点击阅读这篇更长的论文。
人类与机器:将我在 Wordle 中的表现与算法进行比较
基于统计和线性规划的比较

鸣谢:布雷特·乔丹在 Unsplash (照片在 Unsplash 许可下免费使用)
对于过去几个月没有上过网的人(或者只是没有玩过这个游戏),Wordle 是一个你有六次机会猜一个五个字母的单词的游戏。在每一次尝试中,游戏都会给你最后一个单词的信息。例如:

Wordle 规则解释(鸣谢:图片由作者提供)
为了找到这个词,一个人应该有某种策略。就个人而言,为了做出正确的预测,我通常会用我的前两次尝试来“探索”字母表,并试图找到尽可能多的关于最终单词组成的信息(重要的是要提到,因为我来自阿根廷,所以我用西班牙语玩Wordle)。一旦我这样做了,我用我的其他尝试来猜测最后的单词。
考虑到这种方法和我是一个好胜的人的事实,我想挑战自己,看看我是否能够创造一种算法,以尽可能最好的方式做到这一点(或者看看我是否能够击败机器 ) 。
我应该如何构建这个算法?我怎么能做出这样的猜测呢?我怎样才能做出正确的猜测?如何找到可能的单词?这些问题反映了这样一个事实,即有许多事情需要解决。这样,最终的解决方案应该有几个步骤:生成一个单词语料库,找到将用于探索阶段的好单词,然后尝试使用 Wordle 结果来过滤可能的猜测。我还想尝试一种不同的方法,远离信息论(正如我看到的最流行的视频一样,使用了这种技术)。我将在下面的段落中解释整个过程;然而,我在这个流程图中总结了我的算法的方法:

鸣谢:作者图片
步骤 1:创建一个单词语料库
为了在 Wordle 中拥有可能是潜在猜测的单词,需要有一个“数据库”或语料库:为此,我使用了 Reese 等人在 2010 年使用的多个维基百科文章(Samuel Reese,Gemma Boleda,Montse Cuadros,Lluís Padró,German Rigau。维基百科:词义消歧的多语言维基百科语料库。《第七届语言资源与评估会议论文集》(LREC'10),拉瓦莱塔,马尔他。2010 年 5 月),可以从 Kaggle 下载。值得一提的是,维基百科的文章内容可以根据以下文章中的许可下载。浏览每篇文章的内容,我现在可以有一个由五个字母组成的西班牙语单词的代表性样本。
步骤 2:预处理和计算每个单词的统计数据
一旦我们有了一系列可以用来计算统计数据的文本,就有必要考虑如何给单词定性。为此,我创建了两个不同的指标(我不想破坏太多,但这些将在以后使用)
- 频率:一个词在文章中出现了多少次
- letter_score :出现在特定单词中的字母的频率总和
在这个意义上, frequency 谈论的是单词,而 letter_score 谈论的是单词中的字母。因此,我们可以有一个在西班牙语中不常见的单词,但有常见的字母(低频率,但高字母分数)或反之亦然。
为了做到这一点,我们首先使用下面的函数计算每个单词和字母的频率(注意,我做了一些预处理,从元音中去掉重音,并将每个单词转换为小写):
现在我们有了这个,我们使用创建的字典来创建 word_stats_dict ,它有 letter_score :
第三步:找到用于探索的两个初始单词
让我们回想一下,在我采用的策略中,我使用了我在 Wordle 中的前两次尝试来探索“搜索空间”,试图获得尽可能多的关于最终单词的信息(这里,获得信息很简单:不是猜测最终单词,而是组成它的字母)。现在,怎样才能设计出好的探索呢?我用来开发这个的主要假设有两个:
- 如果我们找到尽可能多的字母信息会更好(在这两个单词中重复一个字母是不明智的)
- 如果我们能找到在最后一个单词中的字母就更好了
现在,第一点是关于我将首先尝试的单词的条件:它们必须是两个五个字母的单词,有十个不同的字母。然而,第二点有点棘手,因为不可能直接知道答案(这是游戏的重点!).
然而,我们可以尝试找到一个尽可能好的解决方案:如果我们考虑一下,最终的单词更有可能有频繁的字母(即,一个单词更有可能有字母“a”,而不是字母“x”)。因此,前两种尝试应该由常用的字母组成。这很好,因为我们有一个统计数据可以测量这个值:字母分数!因此,我们要寻找的是两个单词,有十个不同的字母,同时最大化所获得的选择的 letter_score 。
考虑到我们正在尝试最大化一个度量值( letter_score 的总和),同时满足一些条件(只选择两个单词并有十个不同的字母),我们可以使用线性编程来实现这一点(大英百科全书将线性编程定义为“一种数学建模技术,其中线性函数在受到各种约束时被最大化或最小化”)。
我们以数学方式表示线性模型,如下所示:

鸣谢:作者图片
为了实现这一点,我们使用或工具 Python 库,如下面的代码所示。我不会涉及太多细节,但是如果你想查看关于线性编程模型实现的更深入的文章,你可以查看下面的文章。
模型运行后,选取的词是“leido”(等于“read”的过去式)和“trans”。有趣的是,“雷多”这个词在文章中只出现了 616 次,而“反”这个词却出现了 733 次。然而,它们被选中是因为,正如我们提到的,它们是由常见的字母组成的。最后,我必须澄清,在我运行算法的每一天,我在我的第一次尝试中使用了同样的两个词,因为模型的最优值每天都在变化。此外,值得一提的是,可能有几个最佳单词:如果它们具有与“leido”和“trans”相同的字母组成,任何两个单词的组合都可以使用(只要它在 Wordle 的语料库中)。
步骤 4:猜测和过滤语料库
一旦我们有了初步的猜测,我们必须找到一种方法来过滤我们的语料库,根据 Wordle 的结果只保留合适的单词。首先,我们如何与 Worldle 互动?我没有找到一个 API 或任何可以给出结果信息的东西,所以基本上,我是手动完成的:
- 用户通过命令行书写单词
- 用户通过命令行写入结果
我们如何写结果?由于有三种可能的结果,我们为每一种结果使用一个代码:
- 1 表示字母不在单词中
- 2 表示字母在单词中,但顺序不同
- 3 表示字母在单词中,并按顺序排列
这可以这样来说明:

鸣谢:作者图片
为了给出一个更" pythonesque "的例子,交互看起来是这样的:

鸣谢:作者图片
您还可以看到代码给出了五个建议单词的列表。玩了几次 Wordle 之后,我注意到,一般来说,最后一个单词是一个非常常见的单词,所以在这里,使用频率来计算建议。显然,在第一次尝试中,我们有完整的语料库,所以建议只是出现频率最高的词。
有趣的部分是我们如何使用结果来过滤我们的语料库并提出更好的建议。要做到这一点,我们需要将我们尝试的每个字母与其结果对应起来,并使用它来找到“有资格”成为最终解决方案的单词。首先,我们所做的是检查我们输入单词的每个字母(即我们的猜测),看看它是出现一次还是两次。这是因为 Wordle 的工作方式:假设最后一个单词是“PANEL”,我们的尝试是“PAPEL”(西班牙语中“paper”的意思),由于字母“P”出现了两次,Wordle 会将第一个涂成绿色,而将下一个涂成灰色,因此我们需要在代码中考虑这一点,考虑每个不同结果的最佳和最差结果:
既然我们已经在尝试中评估了每个字母的频率,我们就准备用硬规则评估来过滤结果,这很简单
对于我们语料库中的每个单词,我们都会进行评估,比较 input_word 中的字母和结果,创建一个标志来指示该单词是否有资格作为潜在的最终解决方案,这是一种“该单词有资格,直到被证明不是这样”的方法。重要的是,我假设没有一个字母可以在 input_word 中出现两次以上。对于 input_word 中的每个字母,我们评估如下:

鸣谢:作者图片
给定一个由变量 text 定义的单词,通过以下函数进行过滤:
现在我们有了一种过滤语料库的方法,游戏的方式很简单:尝试一个单词(对于我们的前两次尝试,我们已经知道我们将使用哪个-trans 和 leido-),写下结果,申请过滤器,然后用最频繁的合适的单词再试一次,直到你赢了!
评估结果
重要的是不仅要创建一个算法,还要评估它的性能。这就是为什么我将自己的性能与我的算法进行了整整一个月的比较(不用说,我澄清,即使在运行线性编程模型之后,我也没有改变我最初的两个词,用于探索搜索空间)。同样重要的是,考虑到所使用的策略,每天最好的可能结果是在 3 次尝试中获胜(因此,如果我们的算法在该尝试次数中赢得更多游戏,它将被认为更成功)。
首先,评估总体结果,我们看到,平均来说,该算法比我少尝试一次就能找到正确答案,这相当于减少了大约 20%。

鸣谢:作者图片
此外,虽然我的尝试次数的分布具有对称形状(大多数获胜需要 4 到 5 次尝试,尽管有时我会尝试更少或更多),但算法的分布似乎是指数的:尝试次数越多的概率不断降低:

鸣谢:作者图片
当尝试查看我有多少次机会“击败机器”时,我失望地看到我只能这样做两次(7%的次数),然而我得到了 10 次(33%的次数),而机器赢得了 60%的次数:

鸣谢:作者图片
此外,为了更好地理解算法的性能,我玩了 30 个级别的游戏 Wordly 和 Wordling(你可以在谷歌 Play 商店上获得的应用程序),以查看算法的执行情况,并评估 Wordle 是否有任何差异。重要的是要提到,在这些游戏中,我并没有拿算法和自己的表现进行对比。
当评估平均表现和每场比赛尝试次数的分布时,3 场比赛的结果非常相似(Wordle 似乎稍难一些,因为平均尝试次数高 4%)。然而,有趣的是看到了如此相似的分布:

鸣谢:作者图片

鸣谢:作者图片
考虑到这些结果和我们的策略,我们可以说该算法在大约 66%的情况下运行良好,因为它能够在 3 次尝试中猜出最终的单词。如果我们允许一次额外的尝试,这个数字上升到 88%。
最后,评估算法有效性的另一种方法是评估每次尝试时我们的语料库中还剩多少单词。如果我们的线性规划模型足够好,那么在尝试 3 中我们将只剩下几个单词。平均评估结果,我们可以看到为什么我们的策略包含两个初始单词:当只使用一个初始单词时,我们剩下大约 930 个选项,这可能很多;然而,当使用额外的尝试来探索时,我们剩下大约 28 个单词,这是更容易管理的东西(考虑到我们能够将潜在单词的数量减少 97%)!

鸣谢:作者图片
然而,值得一提的是,当我们评估一些额外的统计数据(而不仅仅是平均值)时,我们还可以看到,总的来说,剩余单词的数量有很大的可变性(特别是在第二次尝试中)。然而,我相信这进一步证明了为什么使用两个开头的单词可能是个好主意:

鸣谢:作者图片
就这些,希望你喜欢这篇文章!如果你想更深入地了解这些代码,你可以在下面的回购协议中查看:
https://github.com/nicogarciaara/play_wordle
Power BI 中的混合表:超越与时间相关的场景
混合表是 Power BI 中最强大的特性之一!而且,这不仅仅是关于分离“热”和“冷”数据…您基本上可以在整个业务场景中利用混合表

如果你经常关注我的博客,你可能已经注意到我已经写了 Power BI 中的混合表特性。从我的角度来看,混合表是最强大的特性之一,因为它们提供了一系列可能的实现。在微软的官方公告中,有一个展示如何自动打开混合表功能,在 DirectQuery 模式下保持“热”数据(在增量刷新功能的帮助下),同时在导入模式下保持“冷”历史数据。
通过利用这一概念,您应该可以从两个方面获得最佳效果——vert ipaq column store 数据库的超快性能,同时从 DirectQuery 分区获得最新数据。我将不再赘述,再次解释这是如何工作的,因为我假设你已经阅读了我关于这个主题的上一篇文章。如果没有,请在继续下一步之前参考它…
扩展场景#2
在前面提到的文章中,我已经在场景#2 中向您展示了如何调整原始特性,并使用 DirectQuery 在原始数据源中保存历史数据,而不是使用 DirectQuery 对最新数据进行分区(逻辑是历史数据在报表中不会被频繁查询),同时对“热”数据使用导入模式(假设大多数查询都针对最新数据)。
然而,我们为什么要把自己局限于仅仅分离“热”和“冷”数据呢?!能够在一个表中创建多个分区,并混合这些分区的存储模式,这为现实生活中可能的实现开辟了一个完整的范围。
想象这样一个场景:基于不同的标准,例如,他们花了多少钱,下了多少订单,等等,你可能想要区分不同“级别”的客户——其中一些对你的业务更有价值,让我们把他们视为“VIP”客户。为了简单起见,我将把一定数量的客户放在“VIP”类别中,而所有其他人将保持在“常规”类别中。
贵宾 vs 常客
现在,我们的想法是让“VIP”客户获得更全面的洞察力,比如说,如果您的 VIP 客户遇到订购/付款问题,您希望立即做出反应,而您让普通客户继续排队。在引入混合表之前,唯一可行的解决方案是使用导入模式,然后尽可能频繁地刷新数据。但是,数据集刷新仍然有局限性,这可能会使该解决方案不够好。另一个解决方案是只为 VIP 客户创建一个单独的事实表,同时在另一个事实表中保留“regulars ”,并利用复合模型特性将 VIPs 事实表保留在 DirectQuery 模式下,将常规事实表保留在 Import 模式下,将维度保留在 Dual 模式下。然而,这种解决方案带来了其他警告和潜在的缺点…
使用混合表,我们可以在同一个事实表中创建两个分区——“regulars”将保持导入模式,而 VIPs 分区将处于 DirectQuery 模式,从而使业务分析师能够实时洞察 VIP 数据!
让我快速向您展示它是如何做到的!
搭建舞台
以下是将返回所有不属于 VIP 类的客户的原始查询:
SELECT o.*
,c.category
FROM Sales.Orders o
INNER JOIN Sales.Customers c ON c.custid = o.custid
WHERE c.category <> 'VIP'
我将使用该查询将数据导入 Power BI。当我进入 Power BI 桌面时,我将创建两个度量值—第一个度量值将计算老客户下的订单总数…
Regular Orders = CALCULATE(
COUNT(Orders[orderid]),
Orders[category] <> "VIP"
)
…而另一个人将计算我们贵宾的订单总数:
VIP Orders = CALCULATE(
COUNT(Orders[orderid]),
Orders[category] = "VIP"
)
请注意,唯一的区别在于 CALCULATE 函数的过滤器中使用的逻辑运算符。我还将在我的数据模型中添加 Customer 维度,使报表用户能够深入客户数据(查找电话号码、电子邮件地址等)。).此维度表应该处于双重模式,因此它可以为 DirectQuery 和导入分区提供服务:

作者图片
如果切换回您的报表,您可能会惊讶地发现没有显示 VIP 订单,即使您知道它们存在于源数据库中!别担心,我们会解决这个问题,我们只是在热身:)
这里没有 VIP 订单是完全可以理解的——不要忘记,我们对数据集使用了 SQL 查询,排除了 VIP 客户的所有订单。
让我们将此报告发布给 Power BI Service:

作者图片
有我们新公布的数据集(顺便说一句,我喜欢刷新的日期,哈哈)。让我们看看报告本身是什么样的:

作者图片
所以,我们可以确认它看起来和桌面上的一模一样(没有 VIP 订单)。现在,是时候变点魔法了:)
让我们变些魔术吧…
正如我在上一篇文章中解释的那样,您不能依赖 Power BI Desktop 来创建定制分区(默认情况下,数据模型中的每个表只有一个分区)。我们需要使用外部工具通过 XMLA 端点来操作 TOM(表格对象模型)。像往常一样,我将使用表格编辑器,您应该使用它进行 Power BI 开发,尽管这是一个特殊的例子。

作者图片
让我暂时停在这里,解释上面插图中的步骤。在表格编辑器的“文件”选项卡下,我将依次选择“打开”、“从数据库”,然后选择“混合客户数据集”。混合表工作的关键是(这更多是对我未来自我的提醒)将表格模型的兼容级别从 1550 更改为 1565。在 TOM Explorer 中单击 Model,在 Database properties 下将兼容级别设置为 1565!如果不改变它,你会得到一个错误。
然后,我将默认分区重命名为:常规订单—导入:

作者图片
您还可以看到用于创建默认分区的源查询。现在是设计混合桌解决方案的关键部分:

作者图片
让我们一步一步地解释我们刚刚做了什么:
- 我复制了原始的默认分区(复制/粘贴)
- 然后,我把它重新命名为:VIP 订单— DQ
- 我修改了源查询:现在我想只检索 VIP,而不是检索非 VIP 客户
- 我已经将这个分区的存储模式切换到了 DirectQuery
保存更改后,让我们再次查看报告并刷新它:

作者图片
是啊!我们现在可以在报告中看到我们的 VIP 订单了!但是,不仅仅是这样:我们应该能够实时看到变化!!!我将模拟这样一个查询,它将每 5 秒钟在 orders 表中插入一次 VIP 订单数据,而普通客户的订单将每 12 秒钟插入一次:
WHILE 1=1
BEGIN
WAITFOR DELAY '00:00:05' -- Wait 5 seconds
INSERT INTO sales.Orders
(
custid,
empid,
orderdate,
requireddate,
shippeddate,
shipperid,
freight,
shipname,
shipaddress,
shipcity,
shipregion,
shippostalcode,
shipcountry
)
VALUES
( 7, -- custid - int VIP customer
1, -- empid - int
GETDATE(), -- orderdate - datetime
GETDATE(), -- requireddate - datetime
NULL, -- shippeddate - datetime
1, -- shipperid - int
DEFAULT, -- freight - money
N'', -- shipname - nvarchar(40)
N'', -- shipaddress - nvarchar(60)
N'', -- shipcity - nvarchar(15)
NULL, -- shipregion - nvarchar(15)
NULL, -- shippostalcode - nvarchar(10)
N'' -- shipcountry - nvarchar(15)
)
END
WHILE 1=1
BEGIN
WAITFOR DELAY '00:00:12' -- Wait 12 seconds
INSERT INTO sales.Orders
(
custid,
empid,
orderdate,
requireddate,
shippeddate,
shipperid,
freight,
shipname,
shipaddress,
shipcity,
shipregion,
shippostalcode,
shipcountry
)
VALUES
( 10, -- custid - int Regular Customer
1, -- empid - int
GETDATE(), -- orderdate - datetime
GETDATE(), -- requireddate - datetime
NULL, -- shippeddate - datetime
1, -- shipperid - int
DEFAULT, -- freight - money
N'', -- shipname - nvarchar(40)
N'', -- shipaddress - nvarchar(60)
N'', -- shipcity - nvarchar(15)
NULL, -- shipregion - nvarchar(15)
NULL, -- shippostalcode - nvarchar(10)
N'' -- shipcountry - nvarchar(15)
)
END
看看我们的报告发生了什么:

作者图片
如您所见,我们有实时更新的 VIP 订单数据!太神奇了!
看幕后
我想测试的最后一件事是,如果我有一个 customer 类别的切片器,并选择只查看来自常规客户的订单,那么会发生什么情况,这些订单应该从导入模式分区提供:

作者图片
让我们打开 SQL Server Profiler(提示:在选项窗口中插入数据集名称)并捕获 Power BI 生成的查询:

作者图片
您可能会注意到,尽管报告中只需要来自导入模式的数据,但是 DirectQuery 查询仍然会生成。虽然它运行得非常快,但老实说,我的数据集非常小,所以不确定它在较大的数据集上会如何表现。
结论
我再重复一遍——混合表是 Power BI 中最强大的特性之一,尤其是在扩展标准数据建模功能时。
这不仅仅是将“热”和“冷”数据分别保存在同一个表中,混合表基本上为您提供了无限的可能性,可以将更重要的数据(需要实时管理和分析的部分)与不太重要的数据(或者更好的说法是不需要近实时分析的数据部分)分开。
由于混合表仍处于公开预览阶段,我预计一旦该特性普遍可用,它将会更加完善(如果只需要导入分区数据,希望不会生成 DirectQuery 查询)。
感谢阅读!
成为会员,阅读 Medium 上的每一个故事!
Power BI 中的混合表——终极指南!
原文:https://towardsdatascience.com/hybrid-tables-in-power-bi-the-ultimate-guide-bfe07d480275
导入模式和 DirectQuery 在同一个表中???是的,借助 Power BI 中的最新混合表功能,可以从两个世界中获得最佳效果!

你知道这些漂亮的新车吗?它们将使用电力带你穿过城市拥堵,而在高速公路上,它们通过利用传统的汽油系统,帮助你释放发动机的全部功率。有时电动马达做所有的工作,有时是汽油发动机,有时它们一起工作。结果是燃烧的汽油更少,因此燃油经济性更好(当然,环境也更清洁)。在某些情况下,增加电力甚至可以提高性能。
如果你有混合动力汽车的想法,你是对的…
现在,混合动力汽车与 Power BI 有什么共同之处?!嗯,最近以来,他们做到了——不是“汽车”本身,而是这种混合逻辑。
混合表—简短回顾
微软在 5 月的商业应用峰会上宣布了混合桌的概念。而且,我必须承认,这是我从那时起就热切期待的功能之一。不要误解我,所有那些很酷的迷你图,新的格式选项,人工智能内置功能都很好…但是,我只是一个更关心性能而不是好看的仪表盘和视觉效果的人:)
免责声明: 在撰写本文时,混合表仍是预览功能,因此一旦它普遍可用,可能会发生一些功能不同的情况。
那么,最初的混合表是什么呢?
好了,在回答这个简单的问题之前,我们需要回过头来重申一下 Power BI 中的一个基本概念——存储模式 。一旦开始构建您的解决方案,您就可以为您的表选择三种不同的存储模式: Import 模式(这是默认的,也是最常用的), DirectQuery (在查询执行之前、期间和之后,数据驻留在 Power BI 表格模型之外),或者 Dual (数据被导入到缓存中,但也可以在查询时直接从数据源提供)。
这个具体考虑:“我应该使用导入模式还是 DirectQuery?”总是在性能和报告中实时数据的要求之间进行权衡。
并且,现在让我们来回答原来的问题: 混合表在单个表 中结合了 Import 和 DirectQuery 模式!在我们深入研究并试图理解混合表在多种不同场景中的行为之前,让我们解释一下混合表如何帮助您在性能和实时数据需求之间实现最佳平衡。
场景#1 —导入模式中的“冷”数据,DirectQuery 中的“热”数据
使用这种方法,您基本上是在优先考虑实时需求,但是混合表应该可以帮助您获得比“纯”DirectQuery 场景更好的性能比。这是怎么回事?好吧,假设你有一个有 5 亿行的巨型事实表。而且,业务要求数据的最大延迟为 1 分钟——显然,即使能够将 5 亿行数据加载到内存中,也无法如此频繁地设置计划刷新!
因此,唯一可行的选择是将表保持在 DirectQuery 模式,忍受用户抱怨报告非常慢。不再是了!以下是混合桌可以帮您节省时间的方法:

作者图片
让我们简单地检查一下上图背后的逻辑。想象一下,您的大多数分析查询都是针对过去 7 天的数据,但是还额外要求实时获得最新数据!历史数据不是特别感兴趣,只是偶尔需要。
在这种情况下,混合表有什么好处?首先,不是在 DirectQuery 模式下保留整个“5 亿行”表,而是只有“最热”的数据保留在将使用 DirectQuery 模式提供服务的分区中。而且,我猜您可以假设需要扫描 10.000 行而不是 5 亿行的查询的性能会有什么不同…
接下来,您可以获得查询“最近”数据(本月最后 X 天的数据)的额外性能提升,因为您可以为该数据配置增量刷新,并从缓存中提供查询…我们稍后将了解如何配置混合表以用于该场景,但我有一个好消息:除了选中几个复选框之外,您基本上不需要做任何特别的事情!
场景#2 —导入模式中的“热”数据,DirectQuery 中的“冷”数据
第二个场景和上一个是 180 度!而且,这个场景让人想起我们现在正在用聚合和复合模型来获得两个世界的最佳效果。本质上,我们在原始的 DirectQuery 事实表中保留历史的、低级别的数据,同时聚合“热”数据并将其导入 Power BI 中,以获得 VertiPaq 引擎提供的最佳性能。

作者图片
通过这种设置,我们优先考虑性能和减少数据模型大小而不是实时需求。这个场景适合于大多数分析查询针对最近的数据,但是没有具体要求实时数据的情况!这样做是为了减少表的内存占用,因为大部分数据不会导入到 Power BI 中,而是继续“存在”在 SQL 数据库中。
对于这个需求,没有现成的解决方案可以为您处理混合场景,并且需要一些额外的工作来配置表。请继续关注,稍后我将向您展示这是如何做到的…
为场景#1 做好准备
好了,理论到此为止,是时候动手实践一下混合表在现实中是如何工作的了。让我们首先检查场景#1。但是,在我们配置混合表之前,我将首先在“常规”表上运行一些测试查询,这样我们可以稍后比较性能。
我将使用示例 Contoso 数据库中的 FactOnlineSales SQL Server 表,该表包含 1260 万行。假设我们的报表用户想要实时查看最新的数据,那么我们将让我们的表处于纯 DirectQuery 模式。
我将创建一个简单的 DAX 度量来计算总行数:
总行数= COUNTROWS(FactOnlineSales)
我还将打开性能分析器工具来捕获 Power BI 生成的时间,因为我们的表使用“纯”DirectQuery 存储模式。

作者图片
如您所见,表中有 1260 万行,Power BI 需要大约 0.5 秒返回结果。由于我的用户需要看到实时数据,我将打开自动页面刷新功能,并设置我的页面每 30 秒刷新一次。

作者图片
同时,我将对 FactOnlineSales 表运行以下 SQL 查询:
WHILE 1=1
BEGIN
WAITFOR DELAY '00:00:05' -- Wait 5 seconds
INSERT INTO dbo.FactOnlineSales
(
DateKey,
StoreKey,
ProductKey,
PromotionKey,
CurrencyKey,
CustomerKey,
SalesOrderNumber,
SalesOrderLineNumber,
SalesQuantity,
SalesAmount,
ReturnQuantity,
ReturnAmount,
DiscountQuantity,
DiscountAmount,
TotalCost,
UnitCost,
UnitPrice,
ETLLoadID,
LoadDate,
UpdateDate
)
VALUES
( GETDATE(), -- DateKey - datetime
1, -- StoreKey - int
1, -- ProductKey - int
1, -- PromotionKey - int
1, -- CurrencyKey - int
1, -- CustomerKey - int
N'', -- SalesOrderNumber - nvarchar(20)
NULL, -- SalesOrderLineNumber - int
0, -- SalesQuantity - int
10, -- SalesAmount - money
0, -- ReturnQuantity - int
NULL, -- ReturnAmount - money
NULL, -- DiscountQuantity - int
NULL, -- DiscountAmount - money
5, -- TotalCost - money
NULL, -- UnitCost - money
NULL, -- UnitPrice - money
NULL, -- ETLLoadID - int
NULL, -- LoadDate - datetime
NULL -- UpdateDate - datetime
)
END
这个语句基本上模拟了一个真实的事务数据库,每 5 秒钟插入一个新行。请不要注意语句中提供的所有值,因为这只是一个虚拟表。一旦我执行了这条语句,并将自动页面刷新设置为每 30 秒一次,在三次运行中的每一次中,一个查询花费大约 0.5 秒来返回总行数。您可能会注意到行数在可视化中是如何变化的:

作者图片
是时候配置我们的混合表了,看看会发生什么。第一步是将表从 DirectQuery 切换到 Import 模式,因为混合表只适用于 Import 模式——本质上是通过使用 DirectQuery 为最新数据在其上添加“实时”分区。然后,我将数据集发布到高级工作空间(在本演示中我使用 PPU)。完成后,我将返回并为我的表选择增量刷新:

作者图片
我将我的数据集发布到 Power BI 服务,并等待第一次刷新完成(这可能需要一段时间,因为需要创建表分区并加载“冷”历史数据)。一旦完成,这是我们的报告:

作者图片
您可能会发现,数据是实时变化的,因为我们将自动页面刷新设置为 30 秒,DirectQuery 分区只扫描最新的数据来满足这个查询请求。
这里的关键要点是:通过利用混合表,我们设法从两个世界中获得最佳效果——保持 VertiPaq 引擎的超快性能,同时使报告消费者能够实时更新数据!
配置场景#2
在第二个场景中,我们不能依赖 Power BI 在导入数据的分区上自动应用 DirectQuery 分区。正如已经提到的,在这种情况下,我们需要一些额外的工作来设置东西。总体思路是翻转需求,将“热”数据保留在导入模式中,而将“冷”数据保留在 DirectQuery 中。
这里的第一个警告是,您不能在这种情况下使用 Power BI Desktop,因为 Power BI Desktop 不允许您创建自定义分区。但是,这里的好消息是,我们可以使用外部工具通过 XMLA 端点操纵 TOM(表格对象模型)。为此,最流行的工具是表格编辑器。
简而言之,我们希望将“热”和“冷”数据分割到不同的分区中:
SELECT *
FROM FactOnlineSales
WHERE DateKey >= DATEADD(MONTH, -3, CONVERT(DATE,GETDATE()))
这是将包含前 3 个月的“热”数据的分区。你可以根据需要调整这个门槛。
这是我的分区定义在表格编辑器中的样子:

作者图片
我已经将现有分区重命名为热数据。下一步是复制这个分区,并将源查询调整为只针对“冷”数据(在我的例子中,是超过 3 个月的数据):

作者图片
我现在将这个查询的模式属性从 Import 切换到 DirectQuery:

作者图片
我将保存包含所有更改的数据集,并打开 SQL Server Profiler 来检查它在不同的日期分片要求下的表现:

作者图片
即使我们对过去 3 个月的数据进行切片(“热”数据),仍有 DirectQuery 在后台运行!感谢微软的王南钧·萨考斯基的提醒,他警告说这种事情还在发生!
因此,我真的希望不要编写复杂的 DAX 来获得最高性能,就像我在这里解释的一样,通过利用混合概念,我们将能够在数据模型的性能和内存占用之间获得最佳平衡。但是,如果对“热”或“冷”数据进行切片,DirectQuery 查询似乎仍然会运行。
考虑和限制
无论看起来多么酷和强大(它们确实很酷和强大),实现混合表都有一些限制。
首先,这是(至少目前是)仅 高级特性 ,这意味着您需要一个 P/EM/A SKU,或 PPU(每用户高级)来利用混合表。
接下来,由于所有场景中都涉及到 DirectQuery,您应该知道适用于 DirectQuery 模式的所有一般限制。这意味着,没有复杂的幂查询转换,没有复杂的 DAX 计算——或者简单地说——忘记任何不能转换成 SQL 的东西!
此外,无论您是否仅筛选来自导入分区的数据,DirectQuery 分区仍将包括在扫描中。我真诚地希望微软的工程师们能够提出一个解决方案,在查询不需要的时候消除 DirectQuery 分区。
此外,在您选择“混合动力”之前,还有一些事情需要记住:
- 一旦您将数据集发布到 Power BI 服务,您就不能再次从 Power BI Desktop 重新发布相同的数据集,因为这将删除数据集中已经存在的任何现有分区和数据
- 将数据集发布到 Power BI 服务时,不能将带有数据集的 PBIX 下载回 Power BI Desktop
感谢阅读!
数据管道中的超驱动步骤
原文:https://towardsdatascience.com/hyperdrivestep-in-data-pipelines-fe48a69ec7c7
使用 tqdm 和 Azure 机器学习扩展 Python 流程。

托马斯·凯利在 Unsplash 上的照片
欠开发和过度工程之间的平衡是如此之弱,以至于工程经常在这两个对立面之间摇摆。在本文中,我将解释几种数据管道扩展技术,我们可以应用这些技术来应对业务需求,并帮助您避免越界。
方案
你是一名数据科学家,在一家制作商店销售预测的新公司工作。在建立机器学习模型的过程中,需要对输入数据进行预处理。这个数据以这样一种方式建模,即有一个国家的集合,每个国家有许多商店,每个商店需要以自己的方式处理。
以下代码显示了处理所有商店的简单方法:
def etl_preprocess(int shop_id) -> pd.DataFrame:
# do your work
pass
country_ids = get_countries()
for country_id in country_ids:
shop_ids = get_country_shops(country_id)
for shop_id in shop_ids:
etl_preprocess(shop_id)
开始时一切都很好,但是随着公司因最终的成功而不断发展,这个过程在开始时只需要 30 分钟就可以为数百家商店执行,现在随着商店数量达到数百万,这个过程在几天内就会停滞不前。
你知道“死于成功”吗?当一个企业无法处理过度增长的需求时,就会出现这种情况,因此它会屈服于这种需求。
如何扩大规模
我们将从简单快速的胜利开始。所以首先要检查的是程序本身。回到理论上来,看看是否可以使用数据类型和/或不同的算法来重构这个过程。想的更聪明,不要更努力。
一旦达到算法性能的峰值,我们就可以开始考虑增加更多的金属(硬件资源)来提高程序的吞吐量。默认情况下,Python 阻止多个线程在同一个进程中同时执行。这是使用异步函数获得性能提升的基础,因为当当前线程被阻塞时,大量 IO 进程可以使用额外的线程。但是对于 CPU 密集型操作,您的代码可能会在一个强大的多核 CPU 中执行,然而由于 GIL 的原因,Python 解释器被限制在一个 CPU 内核中执行(要更好地理解这个问题,请查看Python 中的线程和进程简介)。
如果你使用云基础设施,你要为每一个 CPU 支付全价,所以很可能你会送钱。除非你有充分的理由,否则不要向你的经理提及此事。也不要责怪开发人员,因为 Python 通过设计(通过前面提到的 GIL)阻止了多个线程访问同一个进程。但是我们可以通过使用用于多处理的核心 python API 来创建多个进程,幸运的是还有第三方库提供更好的支持。
挤出所有的核
我发现(到目前为止)Python 中多处理的最佳方法是使用 tqdm 。在大多数情况下,由于这个库在 Python 社区中的成功,这个库很有可能已经是项目需求的一部分。
当安装了tqdm之后,您就可以访问这个隐藏在其 contrib 名称空间中的小东西了:
from tqdm.contrib.concurrent import process_map
从那里,您可以执行:
country_ids = get_countries()
for country_id in country_ids:
shop_ids = get_country_shops(country_id)
results = process_map(etl_preprocess, shop_ids)
这段代码按照您的设想工作:它将为每个调用创建一个新的进程(每个调用使用不同的参数执行名为etl_preprocess的函数,这些参数取自shop_ids变量)。默认情况下,它会使用所有可用的内核,process_map会一直等到所有进程执行完毕。在这种情况下,对etl_preprocess的每个调用都返回一个单独的pandas数据帧,因此process_map将收集这些函数的返回,并返回一个包含所有熊猫数据帧的列表(返回数据的顺序与调用的执行顺序无关)。
使用这种方法的一些缺点:
- 考虑到如果由
process_map调用的函数运行另一个多进程函数,它可能会以死锁场景结束。当使用xgboost或TensorFlow训练一个模型时,我发现自己陷入了这个陷阱(但是你的运气可能会因所用库的版本和特性而异) - 在发生异常的情况下,这些可以被
process_map屏蔽,所以我建议小心处理您的日志记录 - 尽量避免依赖父执行(例如,避免使用共享变量)。
process_map执行的方法应该是完全自治的(使用依赖注入设计模式),或者公共数据应该被酸洗;否则,您可能会遇到竞态条件和意外的错误 - 有时这种方法对测试库来说并不好用。解决这种情况的一种方法是使用
process_map(...,max_workers=1)参数或模仿process_map方法
可供选择的事物
如果你的项目中不使用 tqdm,可以给 verstack 或者 mpire 一个机会。我没有测试过这些库,但是 API 是相似的,除此之外,verstack包含了常见机器学习任务的助手。
分配你的工作
增强您的过程的另一个选择是将它变成一个完全分布式的作业,并在集群中启动它。在我们深入探讨之前,我先简单定义一些基本概念:集群是计算机的集合。在集群世界里,计算机被称为节点;大多数时候,存在一个名为驱动的节点,它管理其余节点(名为工作节点)的生命周期(节点初始化/关闭/监控),驱动还负责向每个工作节点发送工作负载。在我们的上下文中,一个作业包括整个程序的执行,因此对所有国家/商店执行etl_preprocess。
综上所述,之前我们使用 CPU 内核来倍增性能,现在我们将使用集群(反过来可以由 p 节点和 m 内核组成)。我希望你能了解这个规模。
Azure Machine Learning 为设置集群提供了一个优秀的 API(甚至你可以使用 UI 来完成)。复杂的部分是你如何访问集群的被管理部分,并让它为你的分布式目的服务。有两种方式: ParallelRunStep 和 HyperDriveStep 。在我的例子中,基于HyperDriveStep的流程更容易实现(尽管我建议你自己测试ParallelRunStep并检查,DYOR 原则总是适用的)。
HyperDriveStep通常用于超参数优化,可配置为简单的网格搜索。这是间接路径到达管理部分并使其执行我们的工作负载。
最终的代码比之前稍微复杂一点,因为实现需要嵌入到 Azure 机器学习中。幸运的是,基本实现很容易:
run_config = ScriptRunConfig(source_directory=source_directory,
script="./etl_pre_process.py",
arguments=[],
compute_target=build_compute_target(ws),
environment=build_environment())
country_ids = get_countries()
param_sampling = GridParameterSampling({'country_id': choice(*country_ids)})
hyperdrive_config = HyperDriveConfig(run_config=run_config, hyperparameter_sampling=param_sampling,
primary_metric_name='count', primary_metric_goal=PrimaryMetricGoal.MAXIMIZE,
max_total_runs=len(ids))
step = HyperDriveStep("etl_pre_process", hyperdrive_config, allow_reuse=False)
现在,etl_pre_process 模块将被执行,传递参数country_id:
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--country_id', type=str, dest='country_id')
args = parser.parse_args()
shop_ids = get_country_shops(args.country_id)
results = process_map(etl_preprocess, shop_ids)
在这种情况下,我们需要使用一个可执行的 Python 模块作为基本的执行功能,Azure 机器学习将负责繁重的工作:
- 将代码库签出到工作节点中
- 安装依赖项,并将所需的 Python 模块复制到工作节点。一旦节点准备就绪,节点将开始执行指定的 Python 模块
- 集群初始化需要一些时间,所以使用不同的集群大小进行一些测试,以减少作业的总执行时间
- 集群管理:例如,如果您的集群设置为最多 10 个节点,但是当前的执行只使用了 5 个国家,那么它将只启动 5 个节点。集群本身将在所有节点之间协调/划分作业,并在作业完成时逐渐关闭节点
这种解决方案的缺点是:
- 侵入集群(例如,在配置
HyperDriveConfig时,我们必须使用primary_metric_name和primary_metric_goal等参数,否则这些参数对于简单的数据争论过程毫无意义) - 从单台机器到集群范例的转换需要额外的工作,这些工作并不总是直接/琐碎的并且应该提前计划:如何注入每次执行所需的参数?如何平衡均匀的工作量?或者最终如何收集数据?
摘要
在 Python 中扩展流程不是一件容易的事情。大多数时候,扩展至少需要对现有代码进行一些重构,甚至从头开始开发代码。有许多选项唾手可得:核心 Python 多线程/多处理函数,像 dask 或 ray 这样的库,基于 Spark 的完全分布式解决方案(如 Databricks 或 Snowflake ),最后,还有像 Azure Machine Learning 这样的 IaaS 解决方案,你可以使用本文中解释的过程来轻松扩展你的过程。
远视去神秘化
原文:https://towardsdatascience.com/hyperopt-demystified-3e14006eb6fa
如何使用 HyperOpt 自动调整模型
你喜欢调模型吗?如果你的答案是“是”,那么这篇文章对你来说是而不是。

我爷爷的一幅漫画——网站。
在这篇博客中,我们将介绍非常流行的自动超参数调整算法,称为基于树的 Parzen 估计器 (TPE)。开源包 HyperOpt 支持 TPE。通过利用 HyperOpt 和 TPE,机器学习工程师可以快速开发高度优化的模型,而无需任何手动调整。
事不宜迟,我们开始吧!
技术 TLDR
HyperOpt 是一个开源 python 包,它使用一种称为基于树的 Parzen Esimtors (TPE)的算法来选择优化用户定义的目标函数的模型超参数。通过简单地定义每个超参数的函数形式和边界,TPE 彻底而有效地搜索复杂的超空间以达到最优。
TPE 是一种顺序算法,它利用贝叶斯更新并遵循以下顺序。
- 用几组随机选择的超参数训练模型,返回目标函数值。
- 根据某个阈值γ,将我们观察到的目标函数值分成“好”和“坏”组。
- 计算“承诺度”得分,正好是P(x |好)/P(x |差)。
- 通过混合模型确定最大化承诺的超参数。
- 使用步骤 4 中的超参数拟合我们的模型。
- 重复步骤 2-5,直到达到停止标准。
这里有一个快速代码示例。
1 —什么是超参数调谐?
好吧,这是很多大词。让我们慢下来,真正明白发生了什么。
1.1 —我们的目标
数据科学家很忙。我们希望生产出真正好的模型,但是要以一种有效的、理想的不干涉的方式来实现。
然而,ML 建模生命周期中的某些步骤很难自动化。例如,探索性数据分析(EDA)和特征工程通常是特定于主题的,并且需要人类的直觉。另一方面,模型调整是一个迭代过程,计算机在这个过程中表现出色。
我们在这篇文章中的目标是理解如何利用算法来自动化模型调整过程。
为了帮助我们思考这个目标,让我们打个比方:我们是寻找宝藏的海盗。同样重要的是要注意到我们是非常高效的海盗,他们希望尽量减少我们寻找宝藏的时间。那么,我们应该如何最大限度地减少搜索时间呢?答案是用地图!

图1:3D 超参数搜索空间示例。藏宝箱的位置是全局最优的。图片作者。
在图 1 中,我们有一个虚拟的地图,显示了我们的宝藏所在的位置。经过大量的攀爬和挖掘,找到宝藏并不困难,因为我们知道它的确切位置。
但是当我们没有地图时会发生什么呢?
当我们负责调优一个模型时,不幸的是没有给我们一张地图。我们的地形,对应于超参数搜索空间,是未知的。此外,对应于最优超参数集的我们的宝藏的位置也是未知的。
有了这个设置,让我们来讨论一些有效探索这个空间和找到一些宝藏的潜在方法!
1.2 —潜在解决方案
模型调整的原始方法是“手动的”——工程师实际上会手动测试许多不同的配置,并查看哪个超参数组合产生最佳模型。虽然信息丰富,但这一过程效率低下。一定有更好的方法…
1.2.1 —网格搜索(最差)
我们的第一个优化算法是网格搜索。网格搜索迭代地测试用户指定网格内超参数的所有可能组合。

图 2:网格搜索布局的例子。作者图片
例如,在图 2 中,你看到红点的地方就是我们重新训练和评估模型的地方。这个框架是低效的,因为它重用了坏的超参数 T4。例如,如果超参数 2 对我们的目标函数几乎没有影响,我们仍然会测试它的值的所有组合,从而将所需的迭代次数增加 10 倍(在本例中)。
但在继续之前,重要的是要注意网格搜索仍然相当流行,因为它保证在给定正确指定的网格的情况下找到最优解。如果你决定使用这种方法,确保你转换你的网格来反映你的超参数的函数形式。例如,随机森林分类器的 max_depth 是一个整数——不要让它搜索连续的空间。它也不太可能具有均匀分布,如果您知道超参数的函数形式,请变换网格以反映它。
总之,网格搜索受到维数灾难的影响,并且在评估之间重新计算信息,但是仍然被广泛使用。
1.2.2 —随机搜索(好)
我们的第二个算法是随机搜索。随机搜索在用户指定的网格中尝试随机值。与网格搜索不同,我们不需要测试每个可能的超参数组合,这提高了效率。

图 3:随机搜索的例子。图片作者。
这里有一个很酷的事实:随机搜索将在 60 次迭代中找到(平均)前 5%的超参数配置。也就是说,和网格搜索一样,你必须转换你的搜索空间来反映每个超参数的函数形式。
随机搜索是超参数优化的良好基础。
1.2.3 —贝叶斯优化(更好)
我们的第三个候选算法是我们的第一个基于序列模型的优化(SMBO)算法。与现有技术的关键概念差异是我们迭代地使用先前的运行来确定未来的勘探点。

图 4:贝叶斯优化示例— src 。图片作者。
贝叶斯超参数优化试图开发我们的超参数搜索空间的概率分布。从那里,它使用一个获取功能,如预期改进,来转换我们的多维空间,使其更“可搜索”最后,它使用优化算法,例如随机梯度下降,来寻找使我们的获取函数最大化的超参数。这些超参数用于拟合我们的模型,并且重复该过程直到收敛。
贝叶斯优化通常优于随机搜索,但是它有一些核心限制,例如需要数字超参数。
1.2.4 —基于树的 Parzen 估值器(最佳)
最后,我们来谈谈这个节目的明星:基于树的 Parzen 估计器(TPE)。TPE 是另一种 SMBO 算法,通常优于基本贝叶斯优化,但主要卖点是它通过树结构处理复杂的超参数关系。

图 TPE 的层次结构示例— src 。图片作者。
让我们用图 5 来理解这个树形结构。这里我们正在训练一个支持向量机(SVM)分类器。我们将测试两个内核:linear和RBF。一个linear内核没有宽度参数,但是RBF有,所以通过使用嵌套字典,我们能够编码这个结构,从而限制搜索空间。
TPE 还支持传统贝叶斯优化所不支持的分类变量。
快速免责声明在继续之前,有许多其他超参数调整包。每个都支持各种算法,其中一些包括随机森林、高斯过程和遗传算法。TPE 是一种非常流行的通用算法,但不一定是最好的。
总的来说,TPE 是一个真正强大而高效的超参数优化解决方案。
2 —基于树的 Parzen 估计器(TPE)是如何工作的?
现在,我们已经对一些流行的超参数优化算法有了大致的了解,让我们来深入了解一下 TPE 是如何工作的。
回到我们的类比,我们是寻找宝藏的海盗,但是没有地图。我们的船长需要尽快找到宝藏,所以我们需要在有很高可能性找到宝藏的战略地点挖掘,利用之前的挖掘来确定未来挖掘的位置。
2.1 —初始化
首先,我们定义空间的约束。如上所述,我们的超参数通常具有函数形式、最大/最小值以及与其他超参数的层级关系。利用我们对最大似然算法和数据的了解,我们可以定义我们的搜索空间。
接下来,我们必须定义我们的目标函数,它用于评估我们的超参数组合有多“好”。一些例子包括经典的 ML 损失函数,如 RMSE 或 AUC。
太好了!现在我们有了一个有限的搜索空间和一个衡量成功的方法,我们准备开始搜索…
2.2 —迭代贝叶斯优化
贝叶斯优化是一种顺序算法,根据目标函数在超空间中找到“成功”概率高的点。TPE 利用贝叶斯优化,但使用一些聪明的技巧来提高性能和处理搜索空间的复杂性…
2.2.0 —概念设置
第一个技巧是建模 P(x|y) 而不是 P(y|x)…

图 TPE 寻求解决的条件概率。图片作者。
贝叶斯优化通常着眼于模型P(y | x),这是给定超参数( x )的目标函数值( y )的概率。TPE 做相反的事情——它观察模型 P(x|y),,这是给定目标函数值( y )的超参数( x ) ,的概率。
简而言之,TPE 试图找到最佳的目标函数值,然后确定相关的超参数。
有了这个非常重要的设置,让我们进入实际的算法。
2.2.1 —将我们的数据分为“好”和“坏”组
记住,我们的目标是根据某个目标函数找到最佳的超参数值。那么,我们如何利用 P(x|y) 来做到这一点呢?
首先,TPE 将我们观察到的数据点分成两组:好的,记为g(x)和差的,记为 l(x) 。好与坏之间的界限由用户定义的参数 gamma (γ)确定,该参数对应于分割我们的观察结果的目标函数百分点( y* )。
因此,当γ = 0.5 时,分割我们观察值的目标函数值( y* )将是我们观察点的中值。

图 7:将 p(x|y)分解成两组。图片作者。
如图 7 所示,我们可以使用上面的框架形式化 p(x|y) 。用海盗的比喻来说…
海盗视角:看看我们已经探索过的地方,l(x)列出了宝藏很少的地方,g(x)列出了宝藏很多的地方。
2.2.32—计算“承诺性”得分
第二,TPE 定义了我们应该如何通过“承诺度”得分评估未观察到的超参数组合。

图 8:承诺分数定义。图片作者。
图 8 定义了我们的承诺分数( P ),这只是以下几个部分的比率…
- 分子:观察到一组超参数的概率( x ,给定对应的目标函数值为“好”
- 分母:观察一组超参数的概率( x ,给定对应的目标函数值为“坏”
“承诺”值越大,我们的超参数 x 产生“良好”目标函数的可能性就越大。
海盗视角:许诺性显示了在我们的地形中某个给定的位置有多少可能拥有大量的宝藏。
在继续之前,先说一句,如果你熟悉贝叶斯优化,这个等式就像一个获取函数,与预期改善(EI) 成比例。
2.2.3—创建概率密度估计值
第三,TPE 希望通过混合模型评估“承诺度”得分。混合模型的思想是取多个概率分布,用一个线性组合——src把它们放在一起。然后,这些组合的概率分布被用于开发概率密度估计。
一般来说,混合物建模过程是…
- 定义我们积分的分布类型。在我们的案例中,如果我们的变量是分类的,我们使用重新加权的分类分布,如果是数值的,我们使用高斯(即正态)或均匀分布。
- 迭代每个点,并在该点插入一个分布。
- 对所有分布的质量求和,以获得概率密度估计值。
请注意,该过程是针对两组 l(x) 和 g(x)单独运行的。
让我们看一下图 9 中的例子…

图 9:适合 3 个超参数观测值的截断高斯分布示例。图片作者。
对于每个观察值(x 轴上的蓝点),我们创建一个正态分布~N(μ,σ),其中…
- μ (mu) 是我们正态分布的均值。它的值是我们点在 x 轴上的位置。
- σ (sigma) 是我们正态分布的标准差。其值是到最近邻点的距离。
如果点靠得很近,标准差会很小,因此分布会很高,反之,如果点分散,分布会很平坦(图 10)

图 10:标准偏差对正态分布形状影响的例子。图片作者。
海盗视角:不——海盗不擅长混合模型。
在继续之前,另一个快速旁白:如果您正在阅读文献,您会注意到 TPE 使用“截断”高斯,这仅仅意味着高斯受我们在超参数配置中指定的范围限制,而不是扩展到+/-无穷大。
确定下一个探索点!
让我们把这些碎片放在一起。到目前为止,我们已经 1)获得了目标函数观察值,2)定义了我们的“前景”公式,以及 3)通过基于先前值的混合模型创建了概率密度估计。我们有所有的片段来评估给定的点!
我们的第一步是为 g(x) 和 l(x) 创建一个平均概率密度函数(PDF)。

图 11:给定 3 个观察点的平均概率密度的叠加。图片作者。
图 11 显示了一个示例流程,红线是我们的平均 PDF,是所有 PDF 的总和除以 PDF 的数量。
利用平均 PDF,我们可以得到任意超参数值( x )在 g(x) 或 l(x) 中的概率。
例如,假设图 11 中的观察值属于“好的”集合, g(x) 。基于我们的平均 PDF,3.9 或 0.05 的超参数值不太可能属于“好”集。相反,一个~1.2 的超参数值似乎很可能属于“好”集。
这只是图片的一半。我们对“坏”集合应用相同的方法, l(x)。因为我们希望最大化 g(x) / l(x) ,有希望的点应该位于 g(x) 高而 l(x) 低的地方。
很酷,对吧?
有了这些概率分布,我们可以从我们的树形结构超参数中进行采样,并找到最大化“承诺性”的超参数集,从而值得探索。
海盗视角:我们挖掘的下一个地点是最大化(拥有大量宝藏的概率)/(拥有少量宝藏的概率)的地点。
3-代码
现在您已经知道了它是如何工作的,下面是一些通过开源包 HyperOpt 实现 TPE 的实用技巧。
3.1 —超视 App 的结构
通常,利用 HyperOpt 时有三个主要步骤…
- 定义搜索空间,搜索空间就是你要优化的超参数的范围和函数形式。
- 定义拟合函数,该函数调用给定训练/测试分割的
model.fit()函数。 - 定义目标函数,它是任何经典的准确性度量,如 RMSE 或 AUC。
不幸的是,这些自动调优方法仍然需要数据科学家的设计输入——这不是完全免费的午餐。然而,有趣的是,TPE 对超参数错误设定相当稳健(在合理范围内)。
3.2—提示和技巧
- HyperOpt 可以通过 Apache Spark 和 MongoDB 并行化。如果您正在使用多个内核,无论是在云中还是在本地机器上,这都会大大减少运行时间。
- 如果您正在通过 Apache Spark 并行化调优过程,那么对于单节点 ML 模型(sklearn)使用一个
SparkTrials对象,对于并行化 ML 模型(MLlib)使用一个Trails对象。代码如下。 - MLflow 是一个追踪模型运行的开源方法。它很容易与远视集成。
- 不要过早缩小搜索空间。超参数的一些组合可能会有惊人的效果。
- 定义搜索空间可能很棘手,尤其是如果您不知道超参数的函数形式。然而,从个人经验来看,TPE 对于那些函数形式的错误指定是相当健壮的。
- 选择一个好的目标函数大有帮助。在大多数情况下,误差是不相等的。如果某种类型的错误更有问题,请确保将该逻辑构建到您的函数中。
3.3—代码示例
下面是以分布式方式运行 HyperOpt 的一些代码。它改编自书中的代码,机器学习工程在行动——这里是 git repo 。
这个代码片段的一些不错的特性包括通过 Apache Spark 的并行化和通过 MLflow 的模型日志。还要注意,这个代码片段优化了一个 sk learn RandomForestRegressor——您必须更改模型和拟合函数以适应您的需要。
最后,原始代码非常有价值,并且没有被广泛利用。它有从SparkTrials对象中提取运行信息的方法,这些信息可以用来显示精度随时间的提高。真正高 ROI 的活动是为您的数据科学团队调整和模块化原始代码的功能,这大约需要一天时间。如果我错了,留下评论,我会删除这一段。
4 —摘要
这就是你要的——远视的荣耀!
为了强调关键点,让我们快速回顾一下。
超参数调整是 ML 模型生命周期的必要部分,但是非常耗时。基于序列模型的优化(SMBO)算法擅长于在复杂的超空间中搜索最优解,并且它们可以应用于超参数调整。基于树的 Parzen 估计器(TPE)是一种非常有效的 SMBO,其性能优于贝叶斯优化和随机搜索。
TPE 重复以下步骤,直到达到停止标准:
- 根据一些超参数γ,将观察点分为“好”和“坏”组。
- 将混合模型拟合到“好”和“坏”集,以开发平均概率密度估计。
- 选择最大化“承诺”分数的点,这利用步骤 2 来估计在“好”和“坏”集合中的概率。
最后,我们有一个非常酷的代码片段,展示了如何通过 SparkTrials 并行化 HyperOpt。它还将我们的所有迭代记录到 MLflow 中。
感谢阅读!我会再写 9 篇文章,把学术研究带到 DS 行业。如果你有兴趣,可以随时 关注我 🙂。
使用 Keras 调谐器对深度学习模型进行超参数优化
可扩展的超参数优化框架解决了优化人工神经网络模型的难题

为了调整机器学习算法,Scikit-learn 包提供了 GridSearchCV、RandomSearchCV 的实现,以找到 ML 模型的最佳超参数集。但在训练深度学习(ANN)模型时,数据科学家往往难以找到正确的隐藏层数、每层神经元数量、辍学率和其他超参数。为了训练最优的深度学习模型,人们需要找到最佳的超参数集,这是一项非常繁琐的任务,需要硬编码或随着超参数的变化重新运行实验。
KerasTuner 是一个易于使用、可扩展的超参数优化框架,解决了超参数搜索的痛点,并轻松搜索 ANN 模型的最佳配置。在本文中,我们将使用 Keras Tuner 为图像分类应用程序执行超调。
设置:
数据集:我们将使用 Keras Tuner 框架为一个 ANN 模型找到最佳的超参数集,该模型对来自 时尚 MNIST 数据集**(在* 麻省理工学院许可 ) 的服装图像进行分类。数据集是开源的,可以从[tf.keras.datasets.fashion_mnist](https://www.tensorflow.org/api_docs/python/tf/keras/datasets/fashion_mnist)访问。*
安装: KerasTuner 需要 Python 3.6+ 和 TensorFlow 2.0+,确保按要求升级。Keras Tuner 可以从 PyPI 安装,使用:
***!pip install -q -U keras-tuner***
Keras 调谐器:
KerasTuner 是一个易于使用、可扩展的超参数优化框架,它通过运行定义的语法为您的深度学习模型搜索最佳的超参数集。Keras Tuner 提供 4 种调谐器或算法,包括**RandomSearch**、**Hyperband**、**BayesianOptimization**和**Sklearn**,可执行超参数优化,并可扩展以试验新的搜索算法。
在本文中,我们将使用 Keras Tuner 框架优化一个 ANN 模型的 学习率 、 层数 、 每层神经元数、 和 辍学率 。
现在,让我们从人工神经网络模型的实现开始,并使用 KerasTuner 软件包优化其超参数。
1.下载并加载数据集:
在定义深度学习模型之前,让我们从加载数据集开始:
(代码由作者编写),下载并加载训练和测试数据
2。定义人工神经网络模型:
深度学习模型架构是使用 Keras 实现定义的。我们可以使用 Keras Tuner 来调整:
- 致密层数(第 6 行)
- 每个致密层中的神经元数量(第 9、10 行)
- 辍学率(第 13、14 行)
- 学习率(第 20、21 行)
在建立深度学习模型时,可以定义超参数搜索空间进行优化。你为超调设置的模型叫做 超模 。我们将定义**model_builder()** 函数来定义超模。
(由作者编写代码),使用由运行定义的 KerasTuner 语法定义 ANN 模型
3.实例化 Keras 调谐器:
Keras 调谐器提供**RandomSearch**、**Hyperband**、、调谐器来优化超参数。要实例化调谐器,可以指定 hypermodel 函数和其他参数。
超波段:
超波段调优算法使用自适应资源分配和提前停止来快速收敛到高性能模型。
(代码由作者编写),实例化 Hyperband 调谐器
遵循 KerasTuner 文档获取 Hyperband 参数的描述。
随机搜索:
RandomSeach 算法遵循随机方法来选择超参数。
(作者代码),实例化 RandomSearch 调谐器
按照 KerasTuner 文档获取 RandomSearch 参数的描述。
3.运行超参数搜索:
**tuner.search()** 功能可用于在超模上运行超参数搜索。search 方法的参数与用于tf.keras.model.fit函数的参数相同。
(作者代码),运行 hyperband 调谐器
4.训练优化的模型:
tuner.get_best_hyperparameters()函数返回超模的最优超参数集。此外,可以传递同一组超参数来训练优化的 ANN 模型。
(由作者编码),用最佳超参数集训练 ANN 模型
输出:
下面的结果是通过在具有 60k 训练和 10k 测试样本 的 时尚 MNIST 数据集上训练 ANN 模型而获得的。* 优化的人工神经网络模型和具有下述硬编码超参数的人工神经网络模型的性能:*
**
****
(图片由作者提供),左:在优化的超参数上训练的 ANN 模型的训练和验证精度,右:在硬编码的超参数上训练的 ANN 模型的训练和验证精度** 
(图片由作者提供),人工神经网络/优化人工神经网络模型的训练测试损失和准确性
通过优化人工神经网络超参数,我们可以观察到约 3%的测试精度差异。
结论:
Keras Tuner 框架可以帮助数据科学家通过优化超参数来训练一个强大的深度学习模型。在定义超模型时,数据科学家需要为每个超参数预先指定参数范围。
对于计算机视觉应用,还可以使用两个预定义的HyperModel类- 超例外和超结果。
参考资料:
[1] KerasTuner 文件:https://keras.io/keras_tuner/
[2]数据集(时尚 MNIST):https://github.com/zalandoresearch/fashion-mnist
感谢您的阅读
超参数调整和采样策略
原文:https://towardsdatascience.com/hyperparameter-tuning-and-sampling-strategy-1014e05f6c14
利用流水线和超参数调优寻找最佳采样策略
处理不平衡机器学习问题的关键步骤之一是对数据进行重新采样。我们可以对多数类进行欠采样和/或对少数类进行过采样。然而,有一个问题需要解决:我们应该将多数阶级减少到多少,和/或增加少数阶级?一种简单但耗时的方法是逐个改变多数类和少数类的重采样值,以找到最佳匹配。得益于不平衡学习库和超参数调整,我们可以设计出一种高效且相对简单的方法来确定最佳重采样策略。

迪伦·麦克劳德在 Unsplash 上的照片
建造管道
选择欺诈检测数据集进行实验,该数据集 CC 许可并可从 OpenML 平台访问。数据集有 31 个特征:其中 28 个特征(V1 到 V28)是数字特征,使用 PCA 进行转换以保持机密性;“时间”描述了每个事务经过的秒数;“金额”表示交易金额;“类别”表示交易是否是欺诈性的。数据非常不平衡,因为所有交易中只有 0.172%是欺诈性的。
为了确定最佳采样率,我们需要建立一个机器学习管道。许多数据爱好者更喜欢 scikit-learn 的管道,因为它提供了一种建立机器学习管道的简单方法。然而,欠采样和过采样不能使用常规 sklearn 流水线来完成,因为采样将在拟合和变换方法期间发生。这可以通过不平衡学习(imblearn)库实现的管道类来弥补。imblearn 的流水线确保重采样只发生在拟合方法期间。
https://imbalanced-learn.org/stable/references/generated/imblearn.pipeline.Pipeline.html
首先,我们将加载数据。然后,从数据中获取特征和标签,并创建训练测试分割,因此测试分割可用于评估使用训练分割训练的模型的性能。
一旦创建了训练集和测试集,就可以实例化管道。流水线由一系列步骤组成:转换数据、重采样和以模型结束。为了简单起见,我们将使用一个数值缩放器(来自 sklearn 的 RobustScaler)来缩放数值字段(数据集中的所有特征都是数值);然后是欠采样方法( RandomUnderSampler 类)、过采样方法( SMOTE 算法),最后是机器学习模型(我们使用的是 LightGBM ,一个实现梯度提升算法的框架)。作为初始基准,多数类欠采样到 10,000,少数类过采样到 10,000。
初始建模
在进行超参数调整之前,先训练一个初始模型。上一节中构建的管道在列车分裂上进行训练,然后在测试分裂上进行测试。由于我们选择的数据是高度不平衡的(在重采样之前),只测量精度是无效的。因此,通过使用 sklearn 的分类报告,我们将监控两个类的精确度、召回率和 f1 分数。

评估基础管道的分类报告。图片由作者提供。
测试结果表明,虽然该模型在对非欺诈性交易进行分类方面工作得很好,但在检测欺诈方面却很差,因为精确度和 f1 分数都非常低。有了这个基准,我们将看到超参数调优如何帮助我们找到更好的采样率。
调优寻找最佳采样率
在本文中,我们将只关注欠采样和过采样技术的采样策略。最初,创建两个列表,其包括用于欠采样和过采样方法的不同采样策略。这些列表将用于从给定的采样策略中找到最佳的采样策略。
GridSearchCV 和 RandomizedSearchCV 是 sklearn 的两个超参数调优类,前者遍历提供的所有参数值以找到最佳值集,后者随机选择超参数值并运行,直到达到用户指定的迭代次数。在这个实验中,我们将使用 GridSearchCV 进行超参数调优。GridSearchCV 需要两个参数:估计器和 param_grid 。估计器是模型(在我们的例子中是管道),而 param_grid 是一个字典,其中的键代表需要调整的参数,值代表参数的值集。
使用管道重新采样和构建模型可以更容易地执行超参数调整,并找到最佳采样策略。由于我们已经在管道中指定了欠采样和过采样技术的名称(如“欠采样器”和“过采样器”),我们可以使用“__”来访问它们的参数,以使用该参数进行超参数调整(例如,“欠采样器 _ _ 采样策略”)。
一旦超参数调优完成,我们就可以从指定列表中获得欠采样和过采样的最佳采样策略集,然后用它们来训练流水线,评估其性能。

评估 hp 调整管道的分类报告。图片由作者提供。
使用超参数调整来寻找最佳采样策略是有效的,因为管道在检测欺诈交易方面有了显著的改进。
这篇文章的工作原理可以在这里找到。
https://github.com/Vaseekaran-V/HP_Tuning_Sampling_Strategy [## GitHub-Vaseekaran-V/HP _ Tuning _ Sampling _ Strategy:Repo 使用超参数调优来查找…
github.com](https://github.com/Vaseekaran-V/HP_Tuning_Sampling_Strategy)
最后的话
在重采样时找到采样率的最佳点是耗时且复杂的,但机器学习管道和超参数调整可以提供一种简单的解决方案来缓解该问题。
本文研究了超参数调整对欠采样和过采样技术确定最佳采样比的影响,该解决方案相对容易实现。
我希望这篇文章对你有用,我希望听到你对这篇文章的反馈/批评,因为它将帮助我提高写作和编码技能。
干杯!
深层 RL 中超参数
原文:https://towardsdatascience.com/hyperparameters-in-deep-rl-f8a9cf264cd6
RL 实践课程—第 6 部分

深层 RL 中的超参数对于训练成功的代理是至关重要的。在今天的课程中,我们将学习如何找到让你成为快乐的深度 RL 开发者的方法。
欢迎来到❤️课程
欢迎来到强化学习实践课程的第 6 部分,它将带你从零到英雄🦸♂️.
这是目前为止我们所做的:
在第 5 部分中,我们使用深度 Q 学习构建了一个完美的代理来解决 Cart Pole 环境。
我们使用了一组超参数,我和你们分享过。然而,我没有解释我是如何得到它们的。
如果你想成为强化学习的真正专家,你需要学习如何调整超参数。为此,你需要使用正确的工具。

孔子知道得很清楚!(图片由作者提供)
今天我们将使用 Python 生态系统中最好的超参数搜索开源库:Optuna。
本课所有代码在 本 Github repo 中。 Git 克隆它来跟随着今天的问题。
想分享爱吗?请 给它一个 Github 里的⭐吧!
实践学习课程 Github repo
第六部分
内容
- 问题
- 解决方案:贝叶斯搜索
- 使用 Optuna 进行超参数搜索
- 重述✨
- 作业📚
- 下一步是什么?❤️
1.问题是
机器学习模型有参数和超参数。
这两者有什么区别?
参数是你在训练你的模型后找到的数字。例如,将状态映射到最佳 q 值的神经网络的参数。
另一方面,超参数是在训练模型之前需要设置的数字。你不能通过训练找到它们,但是你需要事先正确地猜测它们。一旦设置了这些参数,就可以训练模型并找到剩余的参数。
超参数存在于机器学习的周围。例如,在有监督的机器学习问题中(就像我们在第 5 部分中解决的问题),您需要设置学习率。数值太低,模型会陷入局部最小值。数字太大,模型将振荡太多,永远不会收敛到最佳参数。
在深度强化学习中,事情变得更加具有挑战性。
为什么?
因为
- 深度 RL 算法比监督机器学习模型有更多超参数。
- 更重要的是,深度 RL 中的超参数对最终的训练结果有着巨大的影响。换句话说,深度 RL 算法对您预先设置的超参数非常敏感。环境越复杂,超参数越关键。
在第 5 部分中,我们看到了两组超参数,与 q 值网络的相同参数化相结合,如何导致两个非常不同的代理,具有非常不同的性能。其中一个代理是 okayish(平均奖励 180 左右),另一个是完美解决(平均奖励 500)。
那么问题是…
如何才能找到** 好的** 超参数?🤔
为了找到好的超参数,我们采用试错法。
本质上,这是 4 个步骤:
- 我们选择一组超参数,
- 培训代理人,
- 评估代理。
- 如果我们对结果满意,我们就完成了。否则,我们选择一组新的超参数,并重复整个过程。

找到好的超参数的 4 个步骤(图片由作者提供)
网格搜索
如果超参数的数量很少(例如 2-3 个),我们可以尝试所有可能的组合,并选择效果最好的一个。这个方法叫做网格搜索,对于很多有监督的 ML 问题都很管用。
例如,如果我们的算法只有 2 个超参数,每个超参数取 5 个可能值中的 1 个,我们最终得到 5×5 = 25 个组合。我们可以使用每个超参数组合训练代理 25 次,并找到最好的组合。

一个非常小的超参数空间(图片由作者提供)
另一方面,在深层 RL 问题中,有更多的超参数,例如 10–20 个。并且每个超参数可以取许多可能的值。
这创建了一个巨大的网格,我们需要在其中进行搜索,其中组合的数量相对于超参数的数量和每个超参数可以取值的数量呈指数增长。在这种情况下,网格搜索不再是可行的搜索方式。
例如,如果我们有 10 个超参数,并且每个超参数可以从 10 个可能值中取 1,则网格大小为 10,000,000,000(1 后跟 10 个 0)。如果我们的训练循环需要 10 分钟来完成(这是一个非常乐观的估计),它将花费我们
10 分钟 x 10,000,000,000 = 190,258 年😵💫
用简单的话来说就是……不可能。
您可以并行搜索,并将这个数字减少几个数量级。例如,如果您有一个可以运行多达 100,000 个并行进程的大型计算机集群,这将花费您大约 2 年的时间…
尽管如此,这是一个非常低效的解决问题的方法,你不觉得吗?
随机搜索
网格搜索的另一种选择是随机搜索,常用于有监督的 ML 问题。
想法很简单:不是检查每一个N可能的超参数组合(其中N是一个非常大的数字,例如 1,000,000,000),我们随机地尝试它们的一个子集,大小为T(其中T比N小得多,例如 100),并且训练和评估代理T次。从这些T试验中,我们选择效果最佳的超参数组合。
随机搜索是一种更有效的搜索方式,在速度和解决方案的质量方面比网格搜索好得多。
然而,使用随机搜索(顾名思义),您实际上是在每次迭代中旋转轮盘来决定下一步尝试什么超参数。这似乎是一种很愚蠢的搜索方式,不是吗?
一定有更聪明的办法,对吧?🤔
2.解决方案:贝叶斯搜索
为了更好地搜索(无论你在生活中搜索什么),通常一个好主意是记住你过去尝试了什么,并使用这些信息来决定下一步最好尝试什么。
这正是贝叶斯搜索方法所做的。
贝叶斯搜索方法跟踪过去的迭代结果,以决定接下来在超参数空间中最有希望尝试的区域是什么。
使用贝叶斯搜索,你在一个代理模型的帮助下探索空间,这给你一个估计每个超参数组合有多好。随着你运行更多的迭代,算法更新代理模型,这些估计变得越来越好。
最终,您的代理模型足以将您的搜索指向好的超参数。瞧,这就是你得到它们的方式!
贝叶斯搜索方法优于随机搜索,是在深度学习中使用的完美选择。
不同的贝叶斯搜索算法在构建代理模型的方式上有所不同。最流行的方法之一是树形结构的 Parzen 估计器(又名 TPE)。这就是我们今天要用的方法。
说👋去奥普图纳
幸运的是,有一个令人惊叹的开源 Python 库叫做 Optuna ,它实现了贝叶斯搜索方法。
Optuna 有一个干净的 API,抽象出 TPE 和其他贝叶斯搜索方法背后的所有细节。这是一个完美的即插即用库,我们可以在没有深入理解贝叶斯方法背后的数学知识的情况下开始使用。
如果你想深入了解贝叶斯搜索和树形结构 Parzen 估计器背后的本质细节,我推荐你阅读 Will Koehrsen 的这篇精彩文章:
📝机器学习的贝叶斯超参数优化的概念解释
有了贝叶斯方法和 Optuna,我们就可以找到解决问题的超参数了。
所以,让我们开始吧!
3.使用 Optuna 进行超参数搜索
👉🏽notebooks/09 _ hyperparameter _ search . ipynb
让我们从加载环境开始:
为了可视化每个超参数运行的参数和评估指标,我喜欢使用 MLflow 。
MLflow 是一个非常模块化的库,旨在实现 ML 模型的操作化。其中一个组件叫做 MLFlow Tracking ,顾名思义,它可以帮助我们跟踪模型开发过程中需要的一切。当您运行大量超参数实验,并且想要记录每次运行中使用的确切配置时,这一点非常重要。
我们将在名为hyperparameter_search的实验中将 MLflow 指标记录到MLFLOW_RUNS_DIR
🔎 超参数搜索结果可视化
要查看 MLFLow 仪表板,您需要进入命令行,并使用 cd 进入今天课程的根目录。
这是我电脑里的样子
*$ cd ~/src/online-courses/hands-on-rl/03_cart_pole*然后你启动 MLFLow 跟踪服务器:
*$ mlflow ui --backend-store-uri mlflow_runs/*并点击控制台上打印的 URL,在我的例子中是http://127 . 0 . 0 . 1:5000
为了用 Optuna 优化超参数,我们封装了
- 超参数采样
- 培养
- 以及 RL 代理的评估
在一个objective()函数中,它返回我们想要优化的指标。
目标函数
在我们的例子中,objective()函数通过调用sample_hyper_parameters()对超参数进行采样,训练 200 集的代理,并对 1000 集的新集进行评估。这个函数的输出就是这 1000 集中的平均奖励。这是我们想要最大化的度量。
函数sample_hyper_parameters()根据下式采样并返回超参数值
- 我们指定的范围
- 而 Optuna 使用的采样器,默认是 TPE。
如果您查看[**src/optimize_hyperparameters.py**](https://github.com/Paulescu/hands-on-rl/blob/00a8061d322c4aa2c666f84b10ad12c6c334811d/03_cart_pole/src/optimize_hyperparameters.py#L17) 中的函数sample_hyper_parameters()定义,您将会看到我们只提供了对超参数有意义的宽区间或宽范围的值。我们不需要实现任何贝叶斯抽样方法,因为 Optuna 会为我们做。
下一步是创建一个 Optuna 研究对象。
观察我们如何将storage参数设置为本地sqlite文件。这很重要,因为它允许我们在运行代码的计算机或进程由于任何原因崩溃时继续搜索。
最后,我们用study.optimize()开始搜索,并告诉 Optuna 最多尝试 100 次。
在我的 MacBook 中,达到 500 分的超参数大约需要 15 分钟,也就是完美代理。
我建议你在 MLflow 仪表板上监控结果,一旦你看到一个代理达到 500 奖励,就中断 Optuna 搜索。
这是我看到的结果:

agent_id = 298 是我的赢家(图片由作者提供)
最后,为了让你相信你已经完美地解决了CartPole问题,我鼓励你加载你最好的代理(在我的例子中是【T1)……
…并再次评估它。你应该每集都得 500 分!

满分。!(图片由作者提供)
答对了。
祝贺你成功来到这里。我们最终学会了如何调整超参数,以最大限度地提高 Deep Q 代理的性能。
这是一次漫长的CartPole冒险之旅。
是时候坐下来,放松一下,回头看看我们刚刚走过的路。

秘鲁帕拉卡斯(图片由作者提供)
4.重述✨
这些是今天的关键要点:
- 深层 RL 的超参数很关键。你需要实验来找到好的,如果你不遵循智能搜索策略,这可能会非常耗时。在大多数情况下,单靠蛮力(即网格搜索)是不可行的。
- 贝叶斯搜索方法是解决这个问题的最佳工具之一。他们探索并了解下一步在超参数空间中最有希望尝试的领域。它们比随机搜索收敛得更快。
- Optuna 是一个非常棒的用于超参数搜索的开源库。这是一个即插即用的解决方案,抽象出贝叶斯搜索背后的所有细节。通过几行代码,您可以构建一个强大的超参数实验管道。
5.家庭作业📚
是时候把手弄脏了:
- Git 克隆 把 repo 到你的本地机器上。
- 设置 本课的环境
03_cart_pole - 打开
[**03_cart_pole/notebooks/10_homework.ipynb**](https://github.com/Paulescu/hands-on-rl/blob/main/03_cart_pole/notebooks/10_homework.ipynb)
今天的挑战是找到一个更小的网络来完美地解决 CartPole 问题。
如果你仔细观察函数sample_hyper_parameters(),你会发现我保持了神经网络结构的固定(即nn_hidden_layers = [256, 256])。
你能用只有一个隐藏层的 Q 网络找到一个完美的代理吗?
6.下一步是什么?❤️
在接下来的讲座中,我们将介绍新的深度 RL 算法。但是,在我们到达那里之前,我想分享一个技巧来加快你的训练循环。
在下一课中,我将分享我用来在 GPU 上训练深度 RL 模型的一个技巧,价格合理… $0。
想知道怎么做吗?
敬请关注。
在那之前,
和平、爱和学习。
你想成为机器学习专家,接触机器学习和数据科学的顶级课程吗?
👉🏽订阅 【数据机】通迅 。
👉🏽 跟着我 上媒。
👉🏽 给科目 GitHub 回购 一个⭐
祝你愉快,🧡❤️💙
避寒胜地
机器学习模型集成的超参数调整
一个漂亮的优化小研究

预览图像(按作者)
大家好!先说集成学习中的调优超参数(大多是 blending)。在这种集成中,来自一个机器学习模型的预测成为另一个(下一级)的预测器。下图显示了数据从左向右传输的集合的一些变体。在本文中,这样的集合也将被命名为管道或复合模型(复合管道)。

图一。具有不同结构和内容的集合示例(检查节点中操作的名称)(图片由作者提供)
使用这种复杂的复合结构可以减少预测误差。有时模型会变得更加稳健。然而,有一个缺点:高效率是以“集合消耗”为代价的(集合比单机模型需要更多的时间和计算资源)。此外,信号群越复杂,配置就越困难。让我们讨论一下如何在这样的系综中调整超参数。
D isclaimer:我们不是在讨论具体的优化技术,比如贝叶斯优化、随机搜索或者进化算法(或者其他任何算法)。这里讨论的是独立于内核中使用的算法来调整超参数的高级方法。这篇文章中的例子使用了使用 hyperopt 库的贝叶斯优化,但是如果必要的话,可以用一个更合适的(对你或我们)来代替。
业内如何解决这个问题
关于用机器学习模型调优集成的材料相对较少。让我们从“ML 模型集合的超参数调整(简单的 Python 例子)(视频)开始。然后我们继续用“用 Python 融合集成机器学习”来研究题目(这里顺便说一下没有超参数调优),通过互联网查找,最后通过 kaggle 比赛获胜者和参与者的现成解决方案(例如超参数调优/集成方法)。
从上面的项目中,我们可以总结出下面的经典方法。首先,第一层的所有单个模型被训练和调整,然后是集合模型。然后重复这个过程——从左到右反复运动。在“超参数调优 Python 中的加权平均集合”中采用了相同的方法,其中只调优了集合权重。
在 kaggle 笔记本“数据科学框架:实现 99%的准确性”中,方案是相同的:为每个模型单独调整超参数。然而,即使是冠军篮球运动员也指出,在系综中调整超参数并不是微不足道的:“我们也可以在我们的检测模型中试验更多的超参数。我真的不喜欢超参数调优(从来没有真正为它开发出一个好的策略),并且经常尝试通过将不同的模型组合在一起来进行补偿。”【第一名】解决方案概述&代码。
这个问题在科学上是如何解决的
这里有大量的信息和方法。它出现在科学论文中,这些论文通常与算法的软件实现没有任何联系,也没有与实验库有任何联系(老实说,这是个大问题)。
值得注意的方法是“什么都不做”——使用这种方法,集合权重(如果是加权集合)根本不调整。所有第一级模型都包含在具有相同权重的集合中。然而,这种方法并不总是最佳的(参见堆叠集成结合模糊匹配用于生物医学命名实体疾病识别)。显然,这种方法不能归类为超参数调优方法,所以让我们转到真正的方法。
让我们从经典的方法开始“首先每个模型被单独调整,然后是整体权重”。例如,在优化回归问题的机器学习模型的总体权重和超参数中描述了这种方法,其中批评了其最优性。事实上,即使在本文的框架内,作者也提出了一个更有前景的替代方案——同时调整模型的超参数和集合权重。
用于软件工作量估算的集合模型的超参数调整使用完全相同的方法,只是针对叠加进行了推广,其中最终预测由模型而非权重进行组合。由于在他们的情况下搜索空间变得相当大,粒子群方法和遗传算法被用于优化。
总的来说,上述几种方法有相当多的变化,但只有两种根本不同的方法:分别调整基本模型和同时调整集合中的所有模型。
我们最终确定了哪些方法
请注意,我们做了上述所有研究,以便改进我们的开源 AutoML 框架 FEDOT 中的超参数调优模块。这是 R & D 工作的一个典型例子:我们想要改进超参数调整,并且需要选择一个最合适的方法。在这种情况下,有必要 1)确定竞争解决方案,2)实施原型方法,3)评估和 4)选择最佳方案(根据给定的标准), 5)将选择的方法集成到项目中。当我们回顾这些方法时,我们在选定的分支中实现了三种超参数调优方法。然后我们进行实验来决定最成功的方法。我们强调成功方法的两个主要标准:调谐算法必须快速且准确地工作,即调谐超参数后的集合误差必须小于默认参数。
我们做了以下三个小可爱(名字都是作者编的):
- 孤立调谐;
- 顺序调谐;
- 同步调谐。
现在让我们分别讨论每种策略。我们将使用动画来使它更清楚。第一个将是孤立的(动画 1)。

动画 1。使用隔离方法演示超参数调整(作者制作动画)
调整超参数的节点以红色突出显示,箭头显示数据通过哪些节点传递。动画显示每次迭代中只有一个节点被优化。误差度量由来自该节点的预测来测量。这意味着在一次迭代中,数据只通过调优模型的祖先节点进行传输。该策略遵循“一次迭代,一个模型训练”的原则。
现在让我们继续分析。上述方法的优点之一是其计算效率。事实上,在每次迭代中,没有必要再次训练所有的管道模型。
尤其值得仔细看看缺点。第一个也是最重要的限制是算法的模型特异性——它只对那些输出可以与目标变量匹配并获得度量的节点有效,即只对模型有效。如果流水线结构中存在预处理操作(例如特征选择算法或主成分分析),则不能应用这种方法。由于一些节点的输出不能直接匹配到用于误差估计的目标变量,所以这样的操作不能被调整。这个问题可以通过包含附加规则来部分解决:例如,可以忽略这种情况,或者将预处理超参数与后代模型的参数一起调整。然而,这些修改需要时间来实现,所以我们决定推迟它们,直到我们确定这种方法是最有前途的——原型没有这样的修改。第二个缺点是算法贪婪。在每一步,我们试图为一个大管道的每个子图获得局部最优的超参数配置。不能保证在每一步追求最优的过程中,我们不会错过整个管道的全局优化。
第一种方法的缺点(至少是其中的一些)可以通过第二种顺序调整来克服(动画 2)。

动画 2。使用顺序策略演示超参数调整(作者制作动画)
在这种方法中,尽管仍然只有一个操作优化了其超参数,但是现在在每次迭代中,全部数据都通过整个管道传递(红色箭头表示整个管道)。
该方法的优点:
- 可以优化任何结构的管道,包括包含预处理操作的管道;
- 在每次迭代中,匹配整个复合模型的输出。因此,模型在最终预测而不是中间预测时得到优化。
但也有不利之处。由于我们从左到右开始,结果是在超参数优化期间,先前模型被调整到后续模型(后代)的一个配置。并且已经在下一个节点处,后代配置改变为新的配置。也许这不是一个大问题,因为如果任何其他组合没有导致度量的改进,那么在后代节点上保持超参数的初始值总是可能的。
然而,通过顺序移动,我们缩小了搜索空间,因为先前优化的节点不能改变分配的超参数。因此,我们可以将自己逼入“非最优角落”,尽管我们仍然可以优化最终的系综模型,但什么也不会得到改善。我想到的关于蛋糕的比喻是,人们沿着队列走过,一次咬下一块。队伍中的最后一个人可能什么也得不到——尽管他可能会发现做蛋糕的秘密,这样我们就再也不用排队买蛋糕了……
同事:那么如何克服以前方法的问题呢?我:我们把所有的东西都堆起来吧!
这就是我们所做的。动画 3 显示,可以将整个管线优化为一个大“黑盒”,其中管线的超参数集等于其结构中模型参数集的并集。

动画 3。超参数同步调整算法演示(作者制作动画)
优点:
- 优化整个管道的度量值;
- 在优化过程中,搜索空间不会减少——可以同时改变根模型和前一个模型的超参数。
该方法的优点带来的缺点:
- 计算量最大的方法;
- 非常大的搜索空间,特别是对于大型系综。
因此,第三种方法对于在集合中寻找超参数的最佳组合问题似乎是最有效的——让我们检查一下!
实验
因此,让我们为其集成选择最合适的方法。为此,我们设置了如下实验(表 1)。重要说明:最终指标是在延迟的样本上测量的,在优化过程中调谐器无法访问该样本。

表 1。准备实验。由于贝叶斯优化可以在不同的运行中收敛到不同的解决方案,因此每次运行都要重复 30 次,以便在指标和运行时间方面获得更可靠的结果。
下图(图 2)显示了实验中涉及的三种不同的管道。

图二。实验中优化的管道。示出了用于回归的三条流水线和用于分类任务的三条流水线。图中管道中的数据是自下而上传递的(图片由作者提供)
管道中的操作(模型)数量从两个(管道 A)到十个(管道 C)不等。模型名称解释:岭—岭回归、lasso — LASSO 回归、knnreg —回归任务的 K 近邻、svr —回归任务的支持向量机、rfr —回归任务的随机森林、dtreg —回归任务的决策树、knn —分类任务的 K 近邻、rf —分类任务的随机森林、dt —分类任务的决策树。
实验结果
如上所述,将根据两个标准进行比较:执行时间和验证样本的度量。根据验证样本的指标,我们还将引入一个额外的标准:结果越稳定(通过 30 次发射),方法就越可取。也就是说,我们不仅要看平均度量值,还要看方差。此外,我们还将检查哪种类型的管道考虑的方法工作得更好:简单的线性 A;相对简单但分支的 B 和复杂的多级 c。我们还将得出可用计算资源量(由迭代次数决定)是否影响该方法效率的结论。
重要信息。由于我们对 A、B 或 C 类管道的预测能力不感兴趣,而是对参数调整算法的效率感兴趣,因此我们将在下面讨论度量的增益(而不是绝对值)。对于对称绝对百分比误差(SMAPE ),增量将被计算为调整前的度量值和调整后的度量值之间的差值。对于分类,我们将在超参数调整之前从调整后的度量中减去。因此,“增量越大,算法越好”这句话对所有任务都适用。
免责声明:以下内容并非全面的科学研究,而是在常规开发过程中完成的。我们在原型开发期间使用了类似的基准测试。因此,如果你想在《Q1 日报》上就这样的主题写一篇科学文章,你将不得不做更多的实验:计算更多的指标,为每项任务获取 10-15 个数据集,形式化假设,进行统计测试,等等。
因此,回归任务的度量改进比较结果如图 3 所示。

图 3。通过不同方法调整超参数时 SMAPE 度量的改进,显示在三个回归数据集上。考虑了管道的三种变型(图片由作者提供)
在该图中,每个点云代表 30 次运行的样本,即,对于每种类型的管道(A、B、C)、对于每种方法(隔离、顺序、同时)、对于每个数据集(三次回归)、以及对于用于优化的分配迭代次数的两个选项(20 和 100),30 次运行。总共运行了 1620 次用于回归的调优模块,用于分类的次数相同。
数据集“pol”上的结果非常突出。事实上,在绝大多数情况下,超参数调整会导致延迟样本的预测更差。嗯,即使在超参数调整最终导致预测误差增加的情况下,选择比其他算法危害更小的算法仍然是更好的选择。如果我们观察更多“适当的”启动,我们可以看到,平均而言,同步调优会产生更一致的高结果:值的变化更小,云的位置高于竞争对手。
另一个观察结果是,实验使用不同程度的分支或复杂性的管道。图 3 显示,一般来说,管道越复杂,其调整的搜索空间就越大,可以获得的度量增益也就越大。
图 4 显示了分类数据集的结果。

图 4。通过不同方法调整超参数时 ROC AUC 度量的改进,显示在三个分类数据集上。考虑了管道的三种变型(图片由作者提供)
一般来说,分类的结果与回归的结果相同,在度量增益和方差方面胜出,即同时进行超参数调整。
现在让我们更仔细地看看一些情况,即对于表“Amazon_employee_access”(分类)和“cal_housing”(回归),每次运行 20 次迭代(图 5)。

图 5。在两个数据集上考虑的方法的度量改进的核密度估计(图片由作者提供)
从图中可以看出什么?—首先,可以注意到,在改进的情况下,分类指标按照隔离调优—顺序调优—同时调优的顺序逐渐“向右”滑动。这表明同步方法比顺序方法允许更好的超参数调整,顺序方法比隔离调整更可靠。还可以看出,只有第三种方法实现了度量的一致改进;对于其他算法,这是不能保证的。
现在,当解决回归问题时,考虑流水线 C 的分布:可以看出,只有在同时调谐的情况下,分布中的一个明显的高峰(模式)才会突出。在另外两种情况下,分布要么不是单峰的(孤立调谐),要么几乎沿着整个 x 轴伸展。换句话说,只有在同步调谐的情况下才有可能实现高密度。这表明,如果我们应用第三种方法,我们可能会获得与上次发射相同的高结果。
现在,使用“violin plot”中回归表的例子来检查方法的执行时间(图 6)。在这样的图中,分布的核密度估计被绘制在相对于每个项目的中心轴的边上。因此,这种“小提琴”的扩张位置显示了调式。

图 6。解决回归问题时,超参数调整模块在某些管道中的执行时间(图片由作者提供)
让我们从显而易见的开始:分配的迭代次数越多,算法运行的时间就越长。现在请注意,正如所料,隔离调优被证明是一种非常快速的算法。
但是,请注意,在顺序调优的情况下,有机会获得显著的改进。在给定迭代期间没有用新的超参数训练并且不依赖于调整模型(例如,位于它之前的管线中)的管线中的所有操作都不能被训练,而是简单地调用已经训练的模型的预测方法。事实上,预测因子没有改变,模型的超参数也没有改变。这意味着此类操作可以被缓存并仅用于生成预测。在同时调整超参数的情况下,不可能进行这种修改。
既然我们已经进入了决赛,我建议以带有指标的汇总表的形式结束(表 2)。

表二。指标相对于基线配置的增加百分比。显示了标准偏差值。未显示回归数据集“pol”的值。以粗体显示了每种情况下最佳方法的结果。
结论
在本文中,我们研究了如何在机器学习模型的集合中调整超参数。我们为我们确定的三种方法中的每一种创建了原型,以确定哪一种是最有前途的。然后我们在六个不同的数据集上进行实验。
结论是同步调优实现了更大的度量增益,同时允许以更稳定的方式实现这些增益。隔离方法是最快的(就平均执行时间而言)。平均而言,顺序优化比单独优化更有效,但不如同时优化有效。但是,使用操作缓存可以减少顺序优化的执行时间。
在我们的 FEDOT 框架中,我们最终决定实现并支持同步和顺序调优。默认的优化策略是同步策略。
有用的链接:
- FEDOT 框架的开源 AutoML 库
- 同实验科
- NSS 实验室网站,提供关于研究小组的信息
- 我们的新论文讨论了超参数优化的主题:用于设计复合机器学习流水线的自动进化方法
TensorFlow 和 PyTorch 的超调
原文:https://towardsdatascience.com/hypertuning-for-tensorflow-pytorch-a6e117e4655e

超参数调整就像用数据打碟一样——照片来源(unsplash.com/photos/ZfCVTJ30yoc)
深度学习库的根本问题是,它们是为单次内存运行而设计的,其唯一目的是最小化损失——而在现实中,调整一个架构需要多次运行,工作流需要持续,训练损失只是模型评估的开始。

红色非常受欢迎,尽管较少的图层类型功能较少-图片由作者提供
物以类聚。
一年前,我开始构建一个库,让 TensorFlow 和 PyTorch 的实验跟踪和参数调整更加容易。这些库共享相同的底层概念,因此包装它们很容易:
- fn_build — 为架构。
- fn_train — 定义回路。
- fn_lose —计算损耗。
- fn_optimize — 用于学习设置。
- fn_predict — 用于运行它。
很有趣!我用简单的 if 语句(例如 concave_convex:[True,False] )测试了完全不同的架构,甚至将 Torch 损失函数与 Keras 模型混合在一起。
根据我正在进行的分析类型(例如回归、二元分类和多标签分类),我开始注意到我正在使用相同的丢失-优化-预测组合。所以我能够为大多数组件设置可覆盖的默认值。
♻️类似于 Keras 如何抽象一个训练循环,我抽象了一个训练循环的循环,同时保持了工作流的可定制性。
以下是一个基本的多标签分类示例:
# Unique param combos will get passed into functions as `hp**`.
**hyperparameters** = {
"neuron_count": [9, 12]
, "batch_size": [3, 5]
, "epoch_count": [30, 60]
} **def fn_build***(features_shape, label_shape, **hp):*
model = Sequential()
model.add(Input(shape=features_shape))
model.add(Dense(units=hp['neuron_count'], activation='relu'))
model.add(Dense(units=label_shape[0], activation='softmax'))
return model **def fn_train***(
model, loser, optimizer,
samples_train, samples_evaluate, **hp
):*
model.compile(
loss = loser
, optimizer = optimizer
, metrics = ['accuracy']
)
model.fit(
samples_train["features"]
, samples_train["labels"]
, validation_data = (
samples_evaluate["features"]
, samples_evaluate["labels"]
)
, verbose = 0
, batch_size = hp['batch_size']
, epochs = hp['epoch_count']
, callbacks = [History()]
)
return model
这些组件用于组装培训工作的队列:
**queue = aiqc.Experiment.make**(
**# --- Analysis type ---**
*library* = "keras"
, *analysis_type* = "classification_multi"
**# --- Model functions ---**
, *fn_build* = fn_build
, *fn_train* = fn_train
, *fn_lose* = None *#auto CatCrossEnt.*
, *fn_optimize* = None *#auto Adamax <3.*
, *fn_predict* = None *#returns `preds, probs`.*
**# --- Training options ---**
, *repeat_count* = 2
, *hyperparameters* = hyperparameters
**# --- Data source ---**
, *splitset_id* = splitset.id #scroll down.
, *hide_test* = False
)**queue.run_jobs()**
#🔮 Training Models 🔮: 100%|███████████████████████| 16/16
然而,当到了用指标和图表评估每个模型的时候,我意识到一些关键问题被掩盖了。

AIQC 的内置可视化——作者图片

探索超维度特征空间以最小化损失——作者图片
深度学习,浅范围。
深度学习库做好一件事;高效地将损失降至最低。非常快—使用 20,000 列 650 个样本,每个时期 1 秒—非常快。
离计算机科学繁重的任务越远,这些库的帮助就越少。其他的事情——上游的数据准备和下游的模型评估都转移到了用户的肩上。核心深度学习库不是:
🤔 分析感知 —算法不知道您正在进行什么类型的分析,因此它不知道评估给定模型需要什么指标&图表。
💾 数据感知 —程序不知道你的数据集是如何构造的。这不仅适用于(a)需要评估的分割/折叠中的特征/标签等子集,还适用于(b)用于预处理/后处理的形状。
📂—模型、指标和任何预处理步骤只存在于内存中。保存和组织它们取决于从业者。
面向对象的方法。

AIQC 高级和低级原料药的要点-图片由作者提供。
算法只是希望你以正确的格式显示正确的数据。我厌倦了互联网上任意食谱中的这种“X_train,y_test”数学术语。就叫它们特性和标签吧!等等…就是这样!
基于所涉及的数据和分析的类型,有一些规则来管理这些对象应该如何相互交互。所有这些信息都需要持久化。这个标准使得机器学习成为对象关系模型(ORM)抽象的完美候选,也就是关系数据库的 API。
在构建低级 特征时,标签、编码器、分割、折叠等。—我意识到我每次都在用不同设置的相同对象。所以我将所有的摄取&预处理步骤组合成一个高级 流水线,并将所有的训练&评估对象组合成一个实验。
Iris 数据集的多标签分类问题的数据准备如下:
*import aiqc
# Creates or finds SQLite db for persisting workflow.
aiqc.setup()# Built-in example datasets.
from aiqc import datum
df = datum.to_pandas('iris.tsv') **splitset = aiqc.Pipeline.Tabular.make**(
**# --- Data source ---**
*df_or_path* = df
**# --- Label preprocessing ---**
, *label_column* = 'species'
, *label_encoder* = dict(
sklearn_preprocess=OneHotEncoder()
)
**# --- Feature preprocessing ---**
, *feature_cols_excluded* = 'species'
, *feature_encoders* = [
dict(
sklearn_preprocess = StandardScaler()
# Encode a list of `dtypes` or `columns`
, dtypes = ['float64']
)
] **# --- Stratification ---**
, *size_test* = 0.22
, *size_validation* = 0.12
, *fold_count* = None
, *bin_count* = None)*
AIQC 是面向对象 MLOps 的开源框架。
高级 API 允许从业者专注于他们的数据科学工作流程,而不是将脚本和拼凑的工具集粘在一起。它抽象出了使机器学习变得如此不可接近的数据争论。
https://github.com/aiqc/aiqc(别忘了⭐ )
假设检验和气候变化
原文:https://towardsdatascience.com/hypotesis-testing-and-climate-change-7ef750ce149e

美国国家海洋和大气管理局在 Unsplash 拍摄的照片
以气候变化为例学习假设检验
假设检验是机器学习中的一个基本主题。当然,如果你要去面试,这将是一个你需要准备的问题。但是为什么它如此重要呢?
为什么要假设检验?
假设您正在解决一个分类问题,并且假设您只对结果的准确性感兴趣。您首先非常快速地实现一个简单的模型 A ,它返回关于您的测试数据的某个精确度 acc(A) 。经过艰苦的工作和研究,你设法实现了第二个模型 B ,它比第一个复杂得多,并且具有精度 acc(B) 。
在项目的最后阶段,即模型选择阶段,您将比较两个精度,并注意到 acc(B) > acc(A)。这是否意味着 B 型比 A 型好?嗯,没那么容易……万一 B 只是运气好呢?您确定通过在数据集的不同分割(训练测试)上重新运行这两个模型,B 的结果总是比 A 好吗?
令人欣慰的是,统计学为我们提供了工具,能够客观地说一个模型是否比另一个模型表现得更好,而不管可能发生的偶然事件(如幸运的分裂),这就是为什么我们需要研究假设检验。
总体思路
假设您是一名研究人员,正在调查意大利罗马市的每日温度变化。日温度变化是一天中测得的最高温度和最低温度之间的差值。
科学界认为,平均每天的温度范围是 13。另一方面,你基于你的研究,认为这个平均数不再是 13 而是更大。
因此,假设零假设是目前被视为真的假设,即平均范围为 13,而你的替代假设是平均范围大于 13。

两个假设(作者图片)
但是现在你如何说服自己,说服别人你的假设是正确的,让你的新想法占上风?
检验统计量
请仔细注意,你的目标是拒绝零假设,如果你没有这样做,我们说你没有拒绝零假设。
嗯,最简单的方法就是去罗马测量几天的温度范围,然后计算平均值。你决定去测量未来 60 天的温度变化,所以你收集了一个数据样本。你对这 60 天的数据进行平均,结果= 13.7。你怎么想呢?也许零假设是正确的,我的研究是错误的。
然后你取另一个 60 天的样本,你得到= 16.3。现在你反而开始相信你的替代假设实际上是正确的,尽管你并不确定。
然而,在最后 60 天的样本中,你测得的是= 24.3。现在你非常确定你是对的,并且最终可以拒绝零假设。
但是这些考虑是非常直观和个人的。也许有人会说= 24.3 不足以否定零假设,也许只是车流量大,测得的温度更高的情况。然后,我们希望能够使用统计测试更客观地断言语句,并且能够以一定的置信度 C (通常是 C = 95% )拒绝或不拒绝零假设。
下表显示了在 60 天内对您的数据样本进行的测量:

样本数据(图片由作者提供)
我们这里测量的统计量****是数值的平均值,我们想了解它实际上是否不同于科学界接受的平均值:= 13。
在我们的例子中,我们可以使用一个 Z-test 来查看在我们的样本上计算出的平均值是否与 13 显著不同,或者它是否只是一个偶然事件。
现在,我们理所当然地认为,在这种情况下,z 测试是许多现有测试中最合适的测试,在接下来的文章中,我们将看到如何根据特定的情况选择合适的测试。计算 z 检验的公式如下:

z 测试(图片由作者提供)
这个测试将返回给我们一个数字( z ),它是在 60 天的努力工作中测量的各种数据的总结。
置信区间
现在我们的目标是用这个 z 值来看看我们是否能拒绝零假设。为此,我们将使用一个右尾检验,因为我们的替代假设是 > 13 ,如果是< 13,我们将使用一个左尾检验并最终 for!=13 a 双尾检验。
我们知道各种 z 值可能来自以零为中心的高斯分布,因此我们可以计算出具有某个 z 值的概率。这个概率对应于我们称之为 p 值的曲线下面积。
我们可以使用所有统计学书籍中提供的表格来计算分数的 p 值,例如本 one 。(根据您使用的是左检验、右检验还是双尾检验,请注意正确使用表格)

右尾测试(图片由作者提供)
我们希望我们的 z 值远离中心,这代表零假设。
请记住,我们希望拒绝某个置信水平 C 为 95%或显著性水平α = 1-C = 0.05 的零假设。
现在我们可以遵循一个简单的规则来拒绝或者不通过来拒绝零假设:

(失败)拒绝 H0(图片作者)
为了理解这个简单的规则,请看下图。

右尾测试(图片由作者提供)
我们拒绝显著性α = 0.05 (C = 95%)的零假设所需要的只是一个与图中α值相匹配的 z 值。我们的 z 值甚至比我们强加的更远离 H0,所以我们可以完全有把握地拒绝 H0,这就是为什么我们拒绝如果 p 值≤ α。
例如,如果我们的 p 值是 p 值= 0.16 ,我们可以以 C = 84%的置信度拒绝 H0。
让我们编码
你想要真实的气候数据吗?
你想研究真实的气候数据吗?我可以给你推荐一个有趣的网站,在那里你可以找到从地球观测的几个太空任务中收集的免费可用数据。
我目前在欧洲航天局工作,我专注于改进这个门户,在那里你可以找到一群工作人员(包括数据)。
https://earth.esa.int/eogateway
在这里你可以浏览来自哥白尼任务的数据。
https://spacedata.copernicus.eu/ https://scihub.copernicus.eu/
最后的想法
假设检验为做出有关感兴趣人群的数据决策提供了一个可靠的框架。它有助于研究人员成功地将数据从样本外推至更大的人群。
无论你是在研究解决气候变化问题的模型,还是预测下个月的股票价值,你总是需要评估和了解你的模型是否优于以前的模型或起始基线,为此你需要假设检验。在未来的文章中,我将更具体地阐述机器学习领域的各种统计测试,并解释如何在现实世界的应用中使用它们。
结束了
马赛洛·波利蒂
假设和潘德拉:生成用于测试的综合熊猫数据框架
用基于属性的测试创建干净和健壮的测试

作者图片
动机
假设您正在试图判断函数processing_fn是否正常工作。您使用 pytest 通过一个示例来测试该函数。
测试通过了,但是你知道一个例子是不够的。您需要使用更多的示例来测试该函数,以确保该函数可以正常处理任何数据。
为此,您可以使用 pytest parameterize,但是很难找出每个可能导致失败的示例。
即使你花时间去写所有的例子,你也要花很长时间去运行所有的测试。
如果有一种测试策略可以让您:
- 轻松编写测试
- 为测试生成良好的数据
- 快速检测伪造示例
- 制作简单明了的测试

作者图片
这就是假说和潘德拉派上用场的时候。
潘德拉是什么?
Pandera 是一个简单的 Python 库,用于验证熊猫数据帧。
要安装 Pandera,请键入:
pip install pandera
什么是假设?
Hypothesis 是一个灵活易用的基于属性的测试库。
基于实例的测试使用具体的实例和具体的预期输出。基于属性的测试将这些具体的例子归纳为基本的特征。
因此,基于属性的测试允许您编写更清晰的测试,并更好地指定代码的行为。

作者图片
要安装假设,请键入:
pip install hypothesis
本文将向您展示如何使用这两个工具来生成用于测试的合成熊猫数据帧。
Pandera —检查函数的输出
首先,我们将使用 Pandera 来测试当给定一个输入时,函数的输出是否满足某些约束。
在下面的代码中,我们:
- 使用
pandera.DataFrameSchema为输出指定一些约束,比如列的数据类型和值的范围。 - 使用
pandera.check_output装饰器来测试函数的输出是否满足约束。
因为运行这段代码时没有错误,所以输出是有效的。
假设—创建测试示例
接下来,我们将根据pandera.DataFrameSchema给出的约束,使用假设创建用于测试的数据。
具体来说,我们将补充:
schema.strategy(size=5)指定描述如何生成和简化数据的搜索策略@given在指定策略的大范围匹配数据上运行测试功能
使用 pytest 运行测试:
pytest test4.py
输出:
我们在不到 2 秒的时间里发现了一个伪造的例子!输出也很简单。例如,不要选择如下可能导致错误的示例:
val1 val2
0 1 2
1 2 1
2 3 0
3 4 0
4 5 1
假设选择了一个更简单易懂的例子:
val1 val2
0 0 0
1 0 0
2 0 0
3 0 0
4 0 0
这很酷,因为:
- 我们不需要列举任何具体的例子。
- 这些例子非常直观,足以让我们快速理解被测试函数的行为。
- 我们在短时间内找到了伪造的例子。
结论
恭喜你!您刚刚学习了如何使用 Pandera 和 Hypothesis 来生成用于测试的综合数据。我希望这篇文章能够为您提供为 Python 函数创建健壮和干净的测试所需的知识。
随意发挥,并在这里叉这篇文章的源代码:
https://github.com/khuyentran1401/Data-science/tree/master/data_science_tools/pandera_hypothesis
我喜欢写一些基本的数据科学概念,并尝试不同的数据科学工具。你可以通过 LinkedIn 和 Twitter 与我联系。
如果你想查看我写的所有文章的代码,请点击这里。在 Medium 上关注我,了解我的最新数据科学文章,例如:
使用 Python 进行假设检验:带实际示例的分步实践教程
假设就是主张,我们可以用统计数据来证明或反驳它们。在这一点上,假设检验构建了问题,因此我们可以使用统计证据来检验这些主张。所以我们可以检查这个声明是否有效。
在本文中,我想一步一步地展示用 Python 对几个问题进行假设检验。但在此之前,我先简单解释一下假设检验过程。如果你愿意,你可以直接提问。
1.定义假设
首先,我们应该了解我们在寻找哪个科学问题的答案,并且它应该以零假设(H₀) 和替代假设(H₁或 Hₐ).)的形式来表述请记住,H₀和 H₁一定是互斥的,H ₁ 不应该包含相等:
- H₀: μ=x, H₁: μ≠x
- H₀: μ≤x, H₁: μ > x
- H₀: μ≥x, H₁: μ < x
2.假设检验
为了决定是使用参数还是非参数版本的测试,我们应该检查下面列出的具体要求:
- 每个样本中的观测值都是独立同分布的(IID)。
- 每个样本中的观察值呈正态分布。
- 每个样本中的观察值具有相同的方差。
3.选择合适的测试
然后,我们选择要使用的适当测试。当选择合适的测试时,分析有多少组被比较以及数据是否配对是很重要的。为了确定数据是否匹配,有必要考虑数据是否是从相同的个体收集的。因此,您可以使用下表来决定合适的测试。

图片由作者提供。
4.决定和结论
在执行假设检验后,我们获得一个相关的p-值,它显示了检验的显著性。
如果p-值小于α(显著性水平),换句话说,有足够的证据证明 H₀不成立;你可以拒绝 H₀.否则,你就无法拒绝 H₀.请记住,拒绝 H₀就是认可 H₁.然而,未能拒绝 H₀并不意味着 H₀是有效的,也不意味着 H₁是错误的。

图片由作者提供。
现在我们准备开始代码部分。
可以访问https://github . com/ECE isik/EIP/blob/main/hypothesis _ testing _ examples . ipynb查看完整实现。
Q1。独立 t 检验

由于新冠肺炎的原因,一位大学教授在网上授课,而不是面对面授课。后来,他将录制的讲座上传到云端,供异步跟踪课程的学生(那些没有参加课程但后来观看了记录的学生)使用。但他认为,在上课时间上课并参与过程的学生更成功。因此,他在学期末记录了学生的平均成绩。数据如下。
同步=【94。,84.9,82.6,69.5,80.1,79.6,81.4,77.8,81.7,78.8,73.2,87.9,87.9,93.5,82.3,79.3,71.6,88.6,74.6,74.1,80.6]
异步=【77.1,71 ., 72.2, 74.8, 85.1, 67.6, 69.9, 75.3, 71.7, 65.7, 72.6, 71.5, 78.2]
通过使用 0.05 的显著性水平来评估无效假设和备选假设,进行假设检验以检查教授的信念是否具有统计显著性。在进行假设检验之前,检查相关的假设。对结果进行评论。
1.定义假说
因为分数是从不同的个体获得的,所以数据是不成对的。
h₀:μₛ≤μₐ
h₁:μₛ>μₐ
2.假设检验
H₀:数据呈正态分布。
H₁: 数据不是正态分布的。
假设α=0.05。如果p-值为> 0.05,可以说数据呈正态分布。
为了检查正态性,我使用了夏皮罗-维尔克的 W 检验,这通常是小样本的首选,但也有其他选择,如 Kolmogorov-Smirnov 和 D'Agostino 和 Pearson 的检验。请访问 https://docs.scipy.org/doc/scipy/reference/stats.html 了解更多信息。
p value:0.6556
Fail to reject null hypothesis >> The data is normally distributed
p value:0.0803
Fail to reject null hypothesis >> The data is normally distributed
H₀:样本的方差是相同的。
H₁: 样本的方差不同。
它检验总体方差相等的零假设(称为方差齐性或同方差齐性)。假设 Levene 检验的结果p-值小于显著性水平(通常为 0.05)。在这种情况下,基于从具有相等方差的总体中随机抽样,所获得的样本方差差异不太可能发生。
为了检查方差同质性,我更喜欢 Levene 的测试,但您也可以从这里检查 Bartlett 的测试:https://docs . scipy . org/doc/scipy/reference/generated/scipy . stats . Bartlett . html # scipy . stats . Bartlett
p value:0.8149
Fail to reject null hypothesis >> The variances of the samples the are same.
3.选择合适的测试
由于假设得到满足,我们可以对两组和不成对的数据进行参数版本的测试。
p value:0.00753598
since the hypothesis is one sided >> use p_value/2 >> p_value_one_sided:0.0038
Reject null hypothesis
4.决定和结论
在这个显著性水平上,有足够的证据可以得出这样的结论:同步跟随课程的学生的平均成绩高于异步跟随课程的学生。
Q2。方差分析

一位儿科医生希望了解配方奶粉的消费对婴儿平均每月体重增加(以 gr 为单位)的影响。为此,她从三个不同的小组收集数据。第一组是纯母乳喂养的儿童(只接受母乳),第二组是只接受配方奶喂养的儿童,最后一组是配方奶和母乳喂养的儿童。这些数据如下。
only _ 乳房=【794.1,716.9,993。, 724.7, 760.9, 908.2, 659.3 , 690.8, 768.7, 717.3 , 630.7, 729.5, 714.1, 810.3, 583.5, 679.9, 865.1]
only_formula =[ 898.8,881.2,940.2,966.2,957.5,1061.7,1046.2,980.4,895.6,919.7,1074.1,952.5,796.3,859.6,871.1,1047.5,919.1,1160.5,
两个=【976.4,656.4,861.2,706.8,718.5,717.1,759.8,894.6,867.6,805.6,765.4,800.3,789.9,875.3,740。, 799.4, 790.3, 795.2 , 823.6, 818.7, 926.8, 791.7, 948.3]
根据这些信息,进行假设检验,通过使用 0.05 的显著性水平来检查这三个组的平均月增益之间是否存在差异。如果存在显著差异,请执行进一步的分析,找出导致差异的原因。在进行假设检验之前,检查相关的假设。
1.定义假说
H₀: μ₁=μ₂=μ₃ 或样本的均值相同。
H₁: 他们中至少有一个是不同的。
2.假设检验
H₀:数据是正态分布的。H₁:的数据不是正态分布的。
H₀:样本的方差是相同的。
H₁: 样本的方差不同。
p value:0.4694
Fail to reject null hypothesis >> The data is normally distributed
p value:0.8879
Fail to reject null hypothesis >> The data is normally distributed
p value:0.7973
Fail to reject null hypothesis >> The data is normally distributed
p value:0.7673Fail to reject null hypothesis >> The variances of the samples are same.
3.选择合适的测试
由于假设得到满足,我们可以对超过 2 组和不成对的数据进行参数版本的测试。
p value:0.000000
Reject null hypothesis
4.决定和结论
在该显著性水平,可以得出结论,至少其中一组具有不同的平均月增重。 为了找出哪一组或哪几组造成了差异,我们需要进行如下的事后检验/成对比较。
注意:为了避免家庭价值膨胀,我使用了 Bonferroni 调整。你可以从这里看到你的另一个选择:https://sci kit-post hocs . readthedocs . io/en/latest/generated/sci kit _ post hocs . post hoc _ ttest/

在这一显著水平上,可以得出结论:
【仅乳房】不同于【仅配方】
【仅配方】不同于两者【仅乳房】****【两者】
【两者】不同于【仅配方】
Q3。曼恩·惠特尼大学

亚历克斯·科特利亚斯基在 Unsplash 上拍摄的照片
一位在科技公司工作的人力资源专员对不同团队的加班时间很感兴趣。为了调查软件开发团队和测试团队的加班时间是否存在差异,她在两个团队中随机选择了 17 名员工,并以一小时为单位记录了他们每周的平均加班时间。数据如下。
测试 _ 团队=【6.2,7.1,1.5,2,3,2,1.5,6.1,2.4,2.3,12.4,1.8,5.3,3.1,9.4,2.3,4.1】
开发者 _ 团队=【2.3,2.1,1.4,2.0,8.7,2.2,3.1,4.2,3.2
根据这些信息,使用 0.05 的显著性水平进行假设检验,以检查两个团队的过度工作时间之间是否存在差异。在进行假设检验之前,检查相关的假设。
1.定义假说
h₀:μ₁≤μ₂
t38】h₁:μ₁>μ₂
2.假设检验
H₀:数据是正态分布的。
H₁: 数据不是正态分布的。
H₀: 样本的方差相同。
H₁: 样本的方差不同。
p value:0.0046
Reject null hypothesis >> The data is not normally distributed
p value:0.0005
Reject null hypothesis >> The data is not normally distributed
p value:0.5410
Fail to reject null hypothesis >> The variances of the samples are same.
3.选择合适的测试
有两组,从不同的个体收集数据,所以不配对。但是,不满足正态性假设;因此,我们需要对不成对数据使用非参数版本的两组比较:Mann-Whitney U 检验。
4.决定和结论
p-value:0.8226
Fail to recejt null hypothesis
在这个显著性水平上, 可以说两个团队的平均过劳时间没有统计学上的显著差异。
Q4。克鲁斯卡尔-沃利斯

一家电子商务公司定期在 YouTube、Instagram 和脸书上为其活动做广告。然而,新经理很好奇这些平台吸引的客户数量之间是否有任何差异。因此,她开始使用 Adjust,一个可以让你发现你的用户来自哪里的应用。每个平台的 Adjust 报告的每日数字如下。
Youtube=【1913,1879,1939,2146,2040,2127,2122,2156,2036,1974,1956,2146,2151,1943,2125】
insta gram=【2305。, 2355., 2203., 2231., 2185., 2420., 2386., 2410., 2340., 2349., 2241., 2396., 2244., 2267., 2281.]
脸书=【2133。, 2522., 2124., 2551., 2293., 2367., 2460., 2311., 2178., 2113., 2048., 2443., 2265., 2095., 2528.]
根据这些信息,使用 0.05 的显著性水平进行假设检验,以检查这三个平台的平均客户获得量之间是否存在差异。如果存在显著差异,请执行进一步的分析,找出导致差异的原因。在进行假设检验之前,检查相关的假设。
1.定义假说
H₀: μ₁=μ₂=μ₃ 或样本的均值是相同的。
H₁: 他们中至少有一个是不同的。
2.假设检验
H₀:数据是正态分布的。
H₁: 数据不是正态分布的。
H₀:样本的方差是相同的。
H₁: 样本的方差不同。
p value:0.0285
Reject null hypothesis >> The data is not normally distributed
p value:0.4156
Fail to reject null hypothesis >> The data is normally distributed
p value:0.1716
Fail to reject null hypothesis >> The data is normally distributed
p value:0.0012
Reject null hypothesis >> The variances of the samples are different.
3.选择合适的测试
不满足正态性和方差齐性假设,因此我们需要对不成对的数据使用 ANOVA 的非参数版本(数据从不同来源收集)。
4.决定和结论
p value:0.000015
Reject null hypothesis
在这个显著性水平上,至少有一个平均客户获得数是不同的。
注:由于数据是非正态,所以采用非参数版本的后验概率检验。

来自 YouTube 的平均客户数量与其他公司不同(实际上比其他公司少)。
Q5。t 检验相关

大学健康中心在上学期诊断出十八名学生胆固醇过高。医护人员告诉这些患者高胆固醇的危害,并为他们制定了饮食计划。一个月后,患者来进行控制,并重新检查他们的胆固醇水平。测试患者的胆固醇水平是否有差异。
根据该信息,进行假设检验,以使用 0.05 的显著性水平检查饮食后患者的胆固醇水平是否降低。在进行假设检验之前,检查相关的假设。对结果进行评论
测试 _ 结果 _ 之前 _ 饮食 =[224,235,223,253,253,224,244,225,259,220,242,240,239,229,276,254,237,227]
测试 _ 结果 _ 之后 _ 饮食 =[198,195,213,190,2190
1.定义假说
H₀: μd > =0 或真均差等于或大于零。
H₁: μd < 0 或真均值差小于零。
2.假设检验
因变量必须是连续的(区间/比率)
观察值相互独立。
因变量应近似正态分布。
H₀:数据是正态分布的。
H₁: 数据不是正态分布的。
p value:0.1635
Fail to reject null hypothesis >> The data is normally distributed
p value:0.1003
Fail to reject null hypothesis >> The data is normally distributed
3.选择合适的测试
数据是成对的,因为数据是从相同的个体收集的,并且假设得到满足,那么我们可以使用相关 t 检验。
p value:0.000008 one tailed p value:0.000004
Reject null hypothesis
4.决定和结论
在这个显著性水平上,有足够的证据可以断定患者的平均胆固醇水平在节食后有所下降。
Q6。Wilcoxon 符号秩检验
GIF 来自 giphy.com
一位风险投资家希望投资一家提供数据压缩而不损失质量的初创公司,但有两个竞争对手:PiedPiper 和 EndFrame。起初,她认为端架的性能可以更好,但仍想在投资前进行测试。然后,她把同样的文件给各个公司压缩,记录他们的业绩得分。数据如下。
花衣魔笛=【4.57,4.55,5.47,4.67,5.41,5.55,5.53,5.63,3.86,3.97,5.44,3.93,5.31,5.17,4.39,4.28,5.25】
端架=【4.27,3.93,4.01,4.07, 4., 3.72, 4.16, 4.1 , 3.9 , 3.97, 4.08, 3.96, 3.96, 3.77, 4.09]
根据这些信息,使用 0.05 的显著性水平进行相关假设检验。在进行假设检验之前,检查相关的假设。对结果进行评论。
1.定义假说
因为性能分数是从相同的文件中获得的,所以数据是成对的。
H₀: μd > =0 或真实均值差等于或大于零。
H₁: μd < 0 或真均值差小于零。
2.假设检验
因变量必须是连续的(区间/比率)
观察值相互独立。
因变量应近似正态分布。
H₀: 数据正态分布。
H₁: 数据不是正态分布的。
p value:0.0304
Reject null hypothesis >> The data is not normally distributed
p value:0.9587
Fail to reject null hypothesis >> The data is normally distributed
3.选择合适的测试
不满足正态假设;因此,我们需要使用配对检验的非参数版本,即 Wilcoxon 符号秩检验。
4.决定和结论
p-value:0.000214 >> one_tailed_pval:0.000107
one sided pvalue:0.000107
Reject null hypothesis
在这个显著性水平上,有足够的证据得出结论,PiedPaper 的性能优于 EndFrame。
Q7。弗里德曼卡方检验

一位研究人员很好奇她开发的方法 C 和基线方法 A 和 B 在性能方面是否有区别。因此,她决定设计不同的实验,并记录每种方法达到的精度。下表显示了每种方法在测试集上达到的准确度。请注意,每种方法都使用了相同的训练集和测试集。

根据这些信息,进行假设检验,通过使用 0.05 的显著性水平来检查这些方法的性能之间是否存在差异。如果存在显著差异,请执行进一步的分析,找出导致差异的原因。在进行假设检验之前,检查相关的假设。对结果进行评论。
1.定义假说
H₀: μ₁=μ₂=μ₃ 或样本的均值相同。
H₁: 他们中至少有一个是不同的。
2.假设检验
H₀:数据是正态分布的。
H₁: 数据不是正态分布。
H₀: 样本的方差相同。
H₁: 样本的方差不同。
p value:0.3076
Fail to reject null hypothesis >> The data is normally distributed
p value:0.0515
Fail to reject null hypothesis >> The data is normally distributed
p value:0.0016
Reject null hypothesis >> The data is not normally distributed
p value:0.1953
Fail to reject null hypothesis >> The variances of the samples are same.
3.选择合适的测试
有三组,但是违反了正态性假设。因此,我们需要对配对数据使用非参数版本的方差分析,因为准确性得分是从相同的测试集获得的。
4.决定和结论
p value:0.0015
Reject null hypothesis
89.35 89.49 90.49
在这个显著性水平上,至少有一个方法有不同的表现。
注:由于数据呈非正态分布,因此使用非参数版本的事后检验。

方法 C 优于其他方法,获得了比其他方法更好的准确度分数。
Q8。适合度(奖金:)

照片由 janilson furtado 在 Unsplash 上拍摄
一家金融投资公司的分析师很好奇性别和风险偏好的关系。从数据库中随机抽取了 660 名顾客。样本中的客户根据他们的性别和风险偏好进行分类。结果在下表中给出。

测试该公司客户的风险偏好与他们的性别无关的假设。使用 α = 0.01 。
1.定义假说
H₀:性别和风险偏好是独立的。
H₁: 性别与风险偏好有依赖关系。
2.选择适当的测试和假设检查
这个问题应该用 chi2 检验。这种测试被称为拟合优度测试。这意味着如果观察数据非常接近预期数据。满足每个 Ei ≥ 5(至少 80%的电池)进行该试验的假设。
expected frequencies:
[[ 43.21 24.74 28.23 32.41 101.41]
[ 80.79 46.26 52.77 60.59 189.59]]
degrees of freedom: 4
test stat :7.0942
p value:0.1310
3.决定和结论
critical stat:13.2767
由于 p 值大于α=0.01(或计算统计值=7.14 小于临界统计值=13.28) →无法剔除 H₀.在这个显著性水平上,可以得出结论,性别和风险偏好是独立的。
可以访问https://github . com/ECE isik/EIP/blob/main/hypothesis _ testing _ examples . ipynb查看完整实现。
从数据科学的角度看 Hyrum 定律
原文:https://towardsdatascience.com/hyrums-law-from-a-data-science-perspective-db637268b83b

了解 SWE 的最佳实践
法律
“有了足够数量的 API 用户,
你在合同中承诺什么并不重要:
你系统的所有可观察到的行为
都将依赖于某个人。”
这种说法起源于一个注释被添加到某个 C++ 代码中,它实际上破坏了另一个实现者——贡献者的测试。这显然是意料之外的,显示了(先验中性)变化对某人使用您的代码的影响。
对于 DS 工作,经常与工程师一起工作(或者自己工作)并创建一个 API 来与模型的消费者一起创建某种程度的抽象。抽象对于驱使人们使用你的模型的输出是必不可少的。因此,人们通常希望隐藏幕后的复杂性。
Hyrum 向我们解释说,从理论上来说,一个接口应该为我们提供一个消费者和实现者之间的清晰界限。作为一名 DS,您不一定希望您的客户理解您是如何将您的输入数据分成多个批次以进行优化训练的。
然而,有趣的是,随着 API 使用的增加,实现者和消费者的体验会趋向于相同的价值。事实上,用户将开始真正探索所有的情况,包括甚至实现者可能都没有意识到的边缘情况。
最终,我认为这就是“隐式接口法则”所指的。Hyrum 指出,只要使用足够多,就不存在私有实现这种东西。我认为这一点非常正确。
Hyrum 还概述了理解消费者和实现者之间联系的一个有趣元素。性能是我们许多 DS 型号的关键。我们的一些 DS 模型离线运行。例如,洞察可以在一夜之间或一个月一次完成,以创建报告。然而,它们中有相当一部分在 API 这样的接口后面有在线行为。
在后一种情况下,用户对性能有一定的期望,也就是他们需要多少时间才能从您的模型中获得输出。因此,随着使用量的增长,实现者需要考虑到发生变化时的性能预期。事实上,你不可能在用户期望的 10 秒后返回一个输出。
按照这个逻辑,随着使用的增长,实现和接口将合二为一。因此,对实现的任何改变都将违背消费者的期望。
例如,如果您的 DS 模型变得非常受欢迎,您的客户将开始对它的来龙去脉有这样的理解,对它的任何改变都需要满足他们的期望。此外,请注意,他们会对您的模型有这样的理解,这将限制未来的变化。他们会根据自己的需要使用你的模型。这给它的实现和下一步的变化增加了很大的限制。这通常被称为依赖性。
希望,平均期望在某种程度上被很好地理解,对模型的改变将满足它们。但是人们因此需要预测和意识到用户对实现的影响。
如果使用足够多,就不会有私有实现这种东西。也就是说,如果一个接口有足够多的消费者,他们将共同依赖于实现的每个方面,不管是有意还是无意。这种效果用于约束对实现的更改,实现现在必须符合显式记录的接口,以及通过使用捕获的隐式接口。我们经常将这种现象称为“bug 对 bug 的兼容性” Hyrum 。
理解用户对你的模型的期望的一个简单的例子是你的模型返回一个排序的输出,比如说 A-B-C-D 。它随机地这样做,而不在后端进行实际的排序。但是,看到一两次,用户会认为它总是排序的,即当添加“E”时应该返回以下输出: A-B-C-D-E 。
现在,如果你的模型返回 E-A-B-C-D ,这肯定会打破你的模型输出的任何逻辑。这个在最后向我们展示了用户可能有定义你的模型的另一个版本的契约。自然,随着模型复杂性的增加,这很难预测。
此外,要意识到如果你做出改变,肯定会伤害到周围的人。我总是问自己,损失会不会很小。
可扩展性是机器学习中的一个常见问题,有一个完整的领域致力于此。但是,我发现 Hyrum 定律的有趣之处在于它是如何体现消费规模的概念的。它非常直观,但我们很多人都没有注意到。
如果你做到了,请考虑跟随我。
你的想法
我错过了什么吗?我想更多地了解这对我们这些数据科学家有什么影响。
资源
了解 Excel 强大的线性回归工具
另外,比较一下 R 和 Python 如何处理相同的数据

介绍
线性回归是最常见的回归分析类型,也是一种非常强大的工具。在较小的项目或面向业务的用例中,您可能会发现使用 Excel 的简单线性回归模型是您快速完成分析的完美工具。
回归分析帮助您检查两个或多个变量之间的关系。我们用y代表因变量,用X代表自变量。因变量X是一个固定的或输入到你的模型中的变量,y是你用模型预测的变量。
- 自变量也被称为预测变量或解释变量。
- 因变量也被称为响应变量。
对于简单的线性回归模型,利用普通最小二乘法 ( OLS )方法来拟合模型也是常见的。在 OLS 方法中,模型的准确性通过每个预测点的残差平方和来衡量。残差是数据集中的点和拟合线之间的正交距离。
今天,我们的例子将说明系统中的用户数量与我们的销货成本(COGS) 之间的简单关系。通过这种分析,我们不仅能够看到这两个变量的相关性有多强,还可以使用我们的系数来预测给定数量的用户的 COGS。
让我们看看我们的数据和散点图,以了解这两者之间的关系。就像他们说的,一张图片胜过千言万语。
USERS COGS
182,301 $4,761,393
232,824 $5,104,714
265,517 $5,023,121
307,827 $5,834,911
450,753 $5,599,829
484,245 $6,712,668
535,776 $7,083,847
594,604 $7,296,756
629,684 $7,602,863
659,109 $7,643,765
694,050 $7,739,618
874,305 $9,147,263

作者图片
在快速观察数据后,我们看到在我们的 COGS 和用户之间有一个正的(右上)关系。让我们再深入一点,看看我们如何用 R 、 Python 和 Excel 构建我们的模型。
R 的回归
让我们从 R 开始,这是一个在数据科学和分析社区中非常流行的统计计算平台。r 是开源的,图书馆的生态圈是广泛的!
我们将快速构建一个模型,绕过我们的X和y数据进入lm函数。我喜欢的关于 R 实现的事情之一是summary函数,它输出一个表格,其中包含解释结果所需的大部分内容。
library(ggplot2)
X <- c(182301, 232824, 265517, 307827, 450753, 484245,
535776, 594604, 629684, 659109, 694050, 874305)
y <- c(4761393, 5104714, 5023121, 5834911, 5599829,
6712668, 7083847, 7296756, 7602863, 7643765, 7739618, 9147263)
data <- data.frame(X, y)
# Output a text based summary of the regression model
model <- lm(y ~ X, data = data)
summary(model)
# Plot the results
ylab <- c(2.5, 5.0, 7.5, 10)
ggplot(data = data, mapping = aes(x = X, y = y)) +
geom_point() +
geom_smooth(method = "lm", se = TRUE, formula = y~x) +
theme_minimal() +
expand_limits(x = c(0,NA), y = c(0,NA)) +
scale_y_continuous(labels = paste0(ylab, "M"),
breaks = 10^6 * ylab) +
scale_x_continuous(labels = scales::comma)
输出:
Call:
lm(formula = y ~ X, data = data)
Residuals:
Min 1Q Median 3Q Max
-767781 -57647 86443 131854 361211
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 3.548e+06 2.240e+05 15.84 2.07e-08 ***
X 6.254e+00 4.203e-01 14.88 3.77e-08 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 296100 on 10 degrees of freedom
Multiple R-squared: 0.9568, Adjusted R-squared: 0.9525
F-statistic: 221.4 on 1 and 10 DF, p-value: 3.775e-08
在总结中,输出是一种快速识别系数的方法,这些系数具有显著的统计意义,其符号如下:
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
此外,ggplot2是一个强大的可视化库,允许我们轻松地渲染散点图和回归线,以便快速检查。

作者图片
Python 中的回归
如果你有兴趣在 Python 中产生类似的结果,最好的方法是使用statsmodels中的 OLS ( 普通最小二乘)模型。它有最接近基本 R lm包的输出,产生类似的汇总表。我们将从导入运行模型所需的包开始。
import matplotlib.pyplot as plt
import statsmodels.api as sm
接下来,让我们准备我们的数据。我们将从两个 Python 列表开始,分别用于我们的X和y数据。此外,我们需要在数据中添加一个常数来说明截距。
X = [182301, 232824, 265517, 307827, 450753, 484245,
535776, 594604, 629684, 659109, 694050, 874305]
y = [4761393, 5104714, 5023121, 5834911, 5599829, 6712668,
7083847, 7296756, 7602863, 7643765, 7739618, 9147263]
# Add an intercept
X_i = sm.add_constant(X)
X_i
输出:
array([[1.00000e+00, 1.82301e+05],
[1.00000e+00, 2.32824e+05],
[1.00000e+00, 2.65517e+05],
[1.00000e+00, 3.07827e+05],
[1.00000e+00, 4.50753e+05],
[1.00000e+00, 4.84245e+05],
[1.00000e+00, 5.35776e+05],
[1.00000e+00, 5.94604e+05],
[1.00000e+00, 6.29684e+05],
[1.00000e+00, 6.59109e+05],
[1.00000e+00, 6.94050e+05],
[1.00000e+00, 8.74305e+05]])
接下来,我们可以将模型拟合到我们的数据中,并打印出类似于 R 的摘要。注意,我们将sm.OLS用于普通的最小二乘法。
mod = sm.OLS(y, X_i)
results = mod.fit()
print(results.summary())

作者图片
pred_ols = results.get_prediction()
iv_l = pred_ols.summary_frame()["obs_ci_lower"]
iv_u = pred_ols.summary_frame()["obs_ci_upper"]
fig, ax = plt.subplots(figsize=(8, 6))
ax.plot(X, y, "*", label="Data", color="g")
ax.plot(X, results.fittedvalues, "b--.", label="OLS")
ax.plot(X, iv_u, "k--")
ax.plot(X, iv_l, "k--")
ax.legend(loc="best")

作者图片
Excel 中的回归!
终于!让我们使用 Excel 应用程序来执行同样的回归分析。Excel 的一个特点是,它在数值分析方面有着许多用户从未发现的惊人深度。让我们看看如何使用 Excel 执行相同的分析,但只需几分钟就能完成!
设置
首先导航到位于数据选项卡中的数据分析包。

作者图片
从这里,我们可以选择回归工具。

作者图片
和 Excel 中的大多数东西一样,我们用正确的行和列填充对话框,并设置一些附加选项。以下是一些最常见的设置,您应该选择这些设置来获得稳定的输出。

作者图片
最后,我们可以看到分析的结果。Excel 将创建一个包含结果的新工作表。它包含评估统计数据,如 R 平方和调整后的 R 平方。它还生成一个 ANOVA 表,生成诸如平方和(SS)均方误差 (MS)和 F 统计量的值。 F 统计量可以告诉我们模型是否具有统计显著性,通常是在值小于0.05时。
结果

作者图片
Excel 还提供了几个图用于目测,如残差图和直线拟合图。有关解释残差图的更多信息,请查看本文:如何使用残差图进行回归模型验证?

作者图片
预测新的价值
接下来,我们可能想要预测新的值。有几种方法可以做到这一点。首先是可以直接引用 Excel 中回归分析工具输出的单元格。第二个是自己计算斜率和截距,并在我们的回归公式中使用。您将使用两个公式,恰当地命名为=SLOPE和=INTERCEPT。在下面的公式中选择合适的X和y单元格。

作者图片
一旦你有了斜率和截距,你就可以把它们代入线性回归方程。因为这是一个简单的线性回归,我们可以把它想成一条线或Y = MX + B的方程,其中M是斜率和B是 Y 轴截距。我们在高中数学中学到的一些东西现在正在数据科学中产生效益!

作者图片
结论
我们介绍了线性回归,特别是包含两个变量的简单线性回归,以及评估模型准确性的普通最小二乘法。
- 首先,我们介绍了 R 如何使用基本函数
lm执行回归。 - 接下来,我们看看 Python 如何对
statsmodels包做同样的事情。 - 最后,我们看到了 Excel 的数据分析回归工具如何通过几个按钮执行相同的分析!
对于简单的线性回归模型,Excel 为执行分析提供了全面的工具。虽然 R 和 Python 可以执行类似的分析,但是您可以使用 Excel 获得相同的结果!
如果你喜欢阅读这样的故事,并想支持我成为一名作家,那就考虑成为一名灵媒吧。一个月 5 美元,让你可以无限制地访问成千上万篇文章。如果你使用 【我的链接】 注册,我会赚一小笔佣金,没有额外的费用。
我建立了一个脑机接口,用思想来玩太空入侵者
我将向您解释如何操作,包括 Python 中的代码示例

用我们的 BCI 系统和 g.tec Unicorn EEG 设备玩太空入侵者。图片作者。
想象一下,人们在脊髓损伤导致瘫痪后再次移动他们的手臂,用他们的思想控制机器人手臂。想象一下,人们中风后再次行走,用他们的思想指挥外骨骼。
有了脑机接口(BCI),这是可能的。
瘫痪的人不能移动他们身体的某些部分,但是大脑仍然可以通过运动想象(MI)想象移动这些部分。BCI 系统收集这些大脑信号数据,在机器学习(ML)的帮助下,可以预测一个人正在执行的精神任务。
这些脑机接口可以用来控制外部设备,如外骨骼和机械臂,使瘫痪的人恢复功能,并改善他们的康复[1]。
在过去的 6 个月里,我为 VU 的人工智能大师做了我的硕士项目。我学会了编写一个程序,让某人使用 MI 玩太空入侵者,使用比研究中常用的设备便宜得多的设备来捕捉大脑信号。能够使用独角兽玩太空入侵者游戏是建造更便宜的 BCI 的第一步,降低了最终被尽可能多的瘫痪者在康复期间使用的门槛。
在这篇文章中,我将告诉你我学到的主要概念,包括 Python 中的代码示例!
该职位的组织结构如下:
- 简而言之,脑-机接口的 6 个组成部分是:人、数据收集、预处理、特征提取、预测和输出。
- 人物:运动想象&如何设置你的实验
- 数据收集:使用哪种设备&如何将数据传输到 Python
- 预处理:陷波滤波器、公共平均参考和伪像检测
- 特征提取:频率滤波和空间滤波
- 预测:应用机器学习来获得你的预测
- 输出:使用预测输出作为输入来玩太空入侵者
脑机接口的 6 个组成部分

BCI 循环。图片由作者提供,灵感来自[2]。
人:当然,没有人使用这个系统,BCI 就不可能存在。这个人执行某种精神任务,如运动想象(MI),导致特定区域的大脑活动。MI 通常用于对健康人进行 BCI 实验,试图模拟瘫痪的人如何使用该系统。要区分的类别可能是左臂 MI 对右臂 MI 控制机械臂,腿 MI 对放松启动和停止外骨骼,或者在我的情况下,左臂 MI 对右臂 MI 对放松玩太空入侵者。
数据收集:当一个人在执行一项脑力任务时,我们需要收集他的大脑活动数据。在这篇文章中,我们主要关注脑电图。脑电图设备的电极被放置在你的头皮上,测量来自电极下区域脑细胞的电活动。

3 个电极的 EEG 信号示例。图片作者。
预处理:数据采集完成后,需要进行预处理。许多因素会影响 EEG 信号,并在信号中加入大量噪声。预处理的一般步骤包括去除电力线噪声(来自其它电子器件的噪声)、伪像检测和信号的重新参考。
特征提取:大脑活动以不同的幅度(信号的最大位移)和频率(每秒通过的波数)发生。不同的频段对应不同的精神状态,因为特定频率区间的大脑活动被发现比其他频率区间占主导地位。为了提取与您的任务相关的频带中的特征,需要应用频率过滤。接下来,应用空间滤波。由于 EEG 设备被放置在你的头皮上,因此远离你的大脑,并使用有限数量的电极来测量你的大脑活动,相关的大脑信号可以传播到几个 EEG 通道。空间滤波器组合来自几个通道的 EEG 信号的信息,试图通过收集来自不同通道的相关信息来恢复原始信号。
预测:在特征提取之后,我们得到预测。这就是机器学习模型发挥作用的地方,它试图将数据区分成类别(MI 的精神状态)。
输出:对于一个实时 BCI 系统来说,预测并不是最后一步!在在线 BCI 系统中,预测用于控制一些外部设备。值得注意的是,这种输出反过来会影响这个人的想法和目标,完成这个循环。
人物:运动想象&如何设置你的实验

为运动想象收集脑电图数据的典型设置。图片作者。
虽然未来的目标是在日常生活中使用脑机接口,但现在它们还不够好,影响性能的因素应该是有限的。想想分心。疲劳。噪音。
一个好的实验设计试图避免这种情况。那么,一个好的 BCI 实验是什么样的呢?
上图给出了一个实验中一个循环的概述。我们获取了右臂运动想象(右箭头)、左臂运动想象(左箭头)、腿部运动想象(下箭头)和放松(圆圈)的数据。每次试验,我们有 8 个循环。每个实验,我们有 10 个试验。
我们实施了多项措施来避免分心。有一点在上面的概述中看不到,但非常重要,那就是在这个人面前有一张空桌子,只有监视器显示这个实验。在每一个循环的开始,都会出现一个十字,意思是给人一个新任务即将到来的信号,让他们分散注意力集中在屏幕上。
执行 MI 可能很难。尤其是长时间这样做的时候。为了避免疲劳,我们将试验限制在 8 个循环(3 分钟),每个循环之间有 2 分钟的休息时间。
EEG 数据中的噪声可能由多种因素引起。在概览图中,您可以看到一个名为“ERP”的小模块。这是“事件相关电位”的缩写,是作为感官、认知或运动事件的直接结果,你大脑活动中特有的峰值数量[3]。在我们的实验中,ERP 可能是由于任务(认知和运动)的开始和线索(感觉)的消失而启动的。ERP 持续大约 1 秒钟。为了确保我们的数据中没有事件相关电位,我们跳过了运动想象任务的前 2 秒。
噪音的另一个原因是由于肌肉运动。想想眨眼睛,动眼睛,咬紧牙关,或者吞咽。因此,受试者被指示从提示出现的那一刻起直到任务结束尽可能少地眨眼,并在任务过程中避免下巴紧闭、突然的眼球运动和吞咽。
数据收集:使用哪种设备&如何将数据传输到 Python
市场上有各种各样的脑电图设备。直截了当地说,要寻找的最重要的特征是电极的位置!
为了说明这一点,让我谈谈我的两个运动想象项目:一个是去年的失败项目,另一个是我在硕士项目期间做的比较成功的项目。
去年夏天,我试图为运动想象预测建立我的第一个 BCI 项目。对于这个项目,我使用了缪斯 2 耳机。我一点也不知道,通过选择 Muse 2,我几乎不可能成功完成这项任务,甚至在我写任何代码之前!
负责运动控制的区域位于大脑中央部分的中间,在额叶中被称为运动皮层的区域。为了捕捉这个区域周围的大脑活动,需要在这个区域上方放置电极。10–20 国际系统是一个用于电极放置的系统,以确保不同研究之间的放置具有可比性,同时还能识别每个电极捕获的大脑层区域。
在下图中,我们看到了 Muse 2 与我今年使用的设备 g.tec Unicorn 的电极放置。可以清楚地看到,Muse 2 的电极没有覆盖运动皮层,而 Unicorn 在该区域有 3 个电极!这似乎更有希望。

Muse 2(左)和 g.tec Unicorn(右)的电极位置。红色突出显示的区域包含放置在运动皮层上方的电极。图片由作者提供,灵感来自[4]。
通过使用 PyLSL ,将数据流式传输到 Python 实际上非常容易,PyLSL 是实验室流式传输层(LSL)的 Python 接口。Unicorn 套件软件提供了一个扩展,Unicorn LSL 接口,您可以使用它从 Unicorn 获取数据并将其转发给 LSL。
将您的 Unicorn 连接到 Unicorn 套件:我们首先需要将 Unicorn 设备连接到 Unicorn 套件软件。关于这一点,g.tec 实际上提供了一些非常方便的 YouTube 教程。成功后,让我们进入第 2 步!
将 Unicorn 连接到 LSL 接口:在 Unicorn Suite 软件中,进入 DevTools > LSL 接口>打开。然后,启动 UnicornLSL 可执行文件,您的 Unicorn 设备应该显示为可用设备。然后,只需打开流,并按下开始。现在,你的独角兽正在向你的笔记本电脑传输数据。现在我们需要编写一些 Python 代码来收集这些数据!
写一个 Python 脚本:首先我们只需要安装 pylsl 和 pandas 包。然后,我们写下面的小 Python 脚本,就万事俱备了!
由于我们正在构建实时 BCI,我们还需要将数据分割成更小的部分;这些段就是我们的数据点,用作管道的输入。后来,当使用该系统进行实时预测时,我们向系统提供一个 2 秒的片段。
观察力敏锐的读者现在会注意到,我们的系统实际上不是实时的,而是有 2 秒的延迟。然而,实时听起来比几乎实时更酷。所以,请原谅我;)
补充一点,如果你不能自己收集数据,获得开源 EEG 数据的最佳在线资源是 MOABB:所有 BCI 基准之母。MOABB 是一个开放的科学项目,旨在建立一个应用于免费脑电图数据集的流行 BCI 算法的基准。代码可以在 Github 上找到,也可以在文档上找到。
预处理:陷波滤波器、公共平均参考和伪像检测
现在我们有了数据,我们可以应用预处理来清理我们的数据,以便进行下一步。这包括 3 个步骤:使用陷波滤波器的电力线噪声滤波、公共平均参考(CAR)和伪像检测。
脑电图检测房间里各种电子设备的噪音。因此,我们需要用陷波滤波器去除电力线的频率。在欧洲,这是在 50 赫兹,而在美国,这是在 60 赫兹。大多数 EEG 设备都有内置的模拟电力线噪声滤波器,如果没有,这就是如何在 Python 中以数字方式应用电力线噪声滤波器。
接下来,可以应用伪影检测。已经基于对片段的统计分析实现了自动伪影去除[5]。当试验的功率、标准偏差或振幅比所有片段的平均信号值高 2.5 倍时,该片段被认为包含伪影,并且这样的片段被拒绝。这种统计方法已在后来的研究中进行了调整,稍作修改,通过只分析当前段而不是所有段来处理实时实验[6]。当段的绝对幅度超过 125 μV 时,或者当段的峰度超过段的标准偏差四倍时,段将被丢弃。作者声称该方法是成功的,尽管没有提供该方法与其他伪影去除方法之间的比较。由于该方法的简单性,与更高级的算法相比,另一个好处是处理时间短,使其适合于实时处理。其他方法的处理时间可能需要更多时间,这可能成为实时处理的瓶颈。
最后,汽车可以应用[7]。汽车可以被描述为一个简单的空间噪声过滤器。CAR 的工作原理是计算每个时间点所有 EEG 通道的平均信号值,然后将这个值从每个 EEG 通道的值中减去。这里,目标是减少所有通道中存在的一般噪声,以便改善每个单独 EEG 电极下的大脑区域的通常微弱的特定信号分量。
对预处理步骤进行编码的过程如下。将我们的脑电图数据放在熊猫数据帧中(时间点,通道),代码将是:
特征提取:频率滤波&空间滤波
为了解释为什么我们应用频率和空间滤波进行特征提取,让我们首先解释在运动想象期间大脑中发生了什么。
运动想象通过所谓的事件相关同步(ERS)和事件相关去同步(ERD)引起大脑活动的变化。在某一频带内 EEG 信号功率的增加称为 ERS,EEG 信号功率的减少称为 ERD [8]。例如,右臂 MI 将导致运动皮层(即左运动皮层)中的对侧 ERD,并在 MI 结束后立即导致 ERS。对于左臂 MI,这适用于右运动皮层。
频带滤波给出了表示在特定时间窗口内给定频带的 EEG 数据的功率(能量)的特征。MI 期间信号功率的变化在 8 至 24 Hz 频带附近最为显著。然而,最显著的 MI 信号的频率因受试者而异。为了从所有受试者的 MI 中广泛捕获相关的 EEG 信号,研究人员通常为 MI-BCI 设计 4 到 40 Hz 之间的频谱滤波器。
Python 中的频率滤波可以按如下方式完成:
观察 2 秒钟的片段,我们可以清楚地看到信号的差异:

频率滤波前(左)和后(右)2 秒的 EEG 片段。图片作者。
为了揭示信号的位置,我们使用空间滤波。脑电图的空间分辨率
很差,潜在的大脑信号可以分散在几个脑电图通道周围。空间滤波器通常使用加权线性组合来组合来自几个通道的 EEG 信号的信息。因此,空间滤波可以通过收集分布在不同信道上的相关信息来恢复原始信号。两种常用的空间滤波方法是公共空间模式滤波器(CSP)和黎曼几何(RG) [9,10]。在这篇文章中,我们不会深入探讨这些方法,但是会提供代码,以便您可以应用它们。
用于空间过滤的代码将与机器学习模型的代码一起提供,因为 scikit-learn 和附加包使得将两者结合起来非常容易!
预测:应用机器学习来获得你的预测
在空间滤波之后,MI-BCI 管道中的下一步是使用机器学习(ML)对 MI 状态进行分类。
由于在应用 CSP 或 RG 后,数据已经被大量处理,因此通常应用传统的线性 ML 算法,如线性判别分析(LDA)、支持向量机(SVM)或更复杂的算法,如随机森林(RF)。
在编写代码之前,我们先来讨论一下数据格式。我们的数据本质上是一个 3D 数组,具有(通道、时间点)格式的多个分段。Scikit-learn 喜欢使用 Numpy 数组。为此,Numpy 函数栈可以派上用场。
np.stack :沿着一个新轴加入一系列数组。
因此,通过将我们的片段(每个片段是一个 np.array(通道,时间点))放入一个列表或数组中,我们可以使用 np.stack 来获得我们的 3D 数组。
要应用 CSP,我们需要安装 MNE 库。然后,我们从 mne.decoding 导入 CSP。
MNE-Python :开源 Python 包,用于探索、可视化和分析人类神经生理数据:脑磁图、脑电图、视觉诱发电位、ECoG、NIRS 等等。
关于黎曼几何,已经开发了一个名为 PyRiemann 的库。
PyRiemann :基于 scikit-learn API 的 Python 机器学习包。它通过对称正定(SPD)矩阵的黎曼几何为多元时间序列的处理和分类提供了一个高级接口。
然后,我们可以使用 scikit-learn 函数Pipeline和 scikit-learn 提供的 ML 模型将所有内容整齐地缝合在一起,如下所示:
如您所见,代码本质上只有 3 行:管道初始化、拟合和预测。那相当容易!
输出:使用预测输出作为输入来玩太空入侵者
我们做到了!我们现在有了一个关于执行哪种运动想象的预测。剩下的唯一事情就是将这个预测转化为太空入侵者游戏中的一步棋。我发现最简单的方法是写一个 Python 脚本,根据预测按下键盘上的某些键。
为此,我们运行两个 Python 脚本:一个运行太空入侵者游戏。我用 PyGame 做这个。另一个用于捕获数据,对于每两秒钟的片段,应用预处理、特征提取,用 ML 得到我们的预测,最后按下对应于预测的键盘按键。左臂运动想象会让玩家向左移动,右臂运动想象向右,放松会射出子弹!
结论
在这篇文章中,我们回顾了建造一个性能良好的 BCI 来玩太空入侵者的所有步骤。我们讨论了如何建立 BCI 实验,如何使用 g.tec Unicorn 收集数据,如何预处理这些数据,提取特征,并进行预测。最后,我们看了如何将所有这些与太空入侵者的游戏结合起来。
在这篇文章的最后,我想指出一个事实,即我们所讨论的 BCI 系统在开发时需要非常小心和遵守安全协议。虽然脑机接口对于运动障碍者通过外骨骼控制重新获得自主能力有很大的积极意义,但在特定情况下,系统故障和错误可能会危及这些系统用户的生命。例如,当一个人过马路时,基于 EEG 的外骨骼发生故障可能会导致事故,而外骨骼的意外移动可能会造成伤害。在日常生活中实施这种 BCI 系统也引起了数据收集的问题,因为数据将在日常活动的全过程中被收集,引起了隐私问题。

用我们的 BCI 系统和 g.tec Unicorn EEG 设备玩太空入侵者。图片作者。
关于这篇文章中讨论的每个步骤的更多细节,读者可以参考包含每个步骤的单独文章的出版物。
这里有各种代码片段,目的是让你一步一步地理解理论和代码。如果你想更深入地研究代码,我的项目的完整资源库可以在 GitHub 这里找到。在知识库中,还可以看到已经应用了带有迁移学习的深度学习。为了保证这篇文章篇幅有限,我就不深究这个话题了。然而,如果你对我如何应用深度迁移学习感兴趣,我很乐意向你推荐我的另一篇文章解释 BCIs 的卷积神经网络。
感谢您的阅读,祝您在脑机接口领域的旅程中好运!
参考
[1]刘静怡,穆罕默德阿卜杜勒巴尔,和约翰 H 池。采用基于脑机接口的步态方案进行长期训练可使截瘫患者获得部分神经功能恢复。神经外科,79(6):N13–N14,2016。
[2] Marcel Van Gerven,Jason Farquhar,Rebecca Schaefer,Rutger Vlek,耶鲁安 Geuze,Anton Nijholt,Nick Ramsey,Pim Haselager,Louis Vuurpijl,Stan Gielen,et al. 脑-机接口循环。神经工程杂志,6(4):041001,2009。
[3]史蒂文·J·勒克。事件相关电位技术简介。麻省理工学院出版社,2014。
[4] Piotr Szczuko。面向多媒体应用的基于粗糙集分析的脑电信号实部和虚部运动分类。多媒体工具与应用,76(24):25697-25711,2017。
[5] Eduardo López-Larraz、Fernando Trincado-Alonso、Vijaykumar Rajasekaran、苏拉娅·佩雷斯-Nombela、Antonio J . Del-Ama、Joan Aranda、Javier 明格斯、Angel Gil-阿古多和 Luis Montesano。带脑机接口的移动外骨骼控制,用于脊髓损伤步态康复。神经科学前沿,10:359,2016。
[6] Andreas Schwarz,Patrick Ofner,Joana Pereira,Andreea Ioana 斯布利亚,Gernot Rüller-Putz。从人体脑电图中解码自然的伸手抓握动作。神经工程杂志,15(1):016005,2017。
[7] Kip A Ludwig,Rachel M Miriani,Nicholas B Langhals,Michael D Joseph,David J Anderson 和 Daryl R Kipke。使用共同平均参考值改善来自微电极阵列的皮质神经元记录。神经生理学杂志,101(3):1679-1689,2009。
[8] Gert Pfurtscheller 和 FH Lopes Da Silva。事件相关脑电图/脑磁图同步化和去同步化:基本原理。临床神经生理学,110(11):1842-1857,1999。
[9]凯坑昂,钦,王传初,崔存泰,张海红.BCI 竞争 iv 数据集 2a 和 2b 上的滤波器组公共空间模式算法。神经科学前沿,6:39,2012。
[10] Alexandre Barachant、Stéphane Bonnet、Marco Congedo 和 Christian Jutten。利用黎曼几何进行多类脑-计算机接口分类。2012 年 IEEE 生物医学工程汇刊,59(4):920–928。
我用 Python 从数据中创造了音乐!
原文:https://towardsdatascience.com/i-created-music-from-data-using-python-adfc349f55f1
阅读了解我如何将比特币的价格波动数据转化为音乐。

马塞拉·拉斯科斯基在 Unsplash 上的照片
F 首先,下面是我在比特币数据发音上的音乐视频链接。喜欢的话可以继续看下去!
我们使用可视化来讲述数据。这是最常见的方法。条形图、折线图等就是一些例子。存在几种类型的数据可视化技术,其中大多数都有一个共同点。它们是基于图像的,信息被转换成视觉语境。
虽然不是很流行,但我们可以将声音用于同样的目的,即数据可视化。对此有一个不同的术语——数据语音化。在这里,我们用声音或音乐来呈现信息。
音乐元素
让我们来了解一下对音乐的一些基本理解,以及它的特别之处。我们把声音视为音乐或噪音。音乐可以被定义为令我们耳朵愉悦的有序而有规律的声波。噪音与此相反,刺耳无序。不可能将每种声音都归类为音乐或噪音,因为这部分有点主观,可以有不同的解释。
因此,为了使数据发音或音乐,我们需要从数据中产生有序的声波。以下是构成音乐的一些元素:
- 音高——(或频率)是每秒产生的振动次数。人类能够听到 20Hz 到 20,000 Hz 之间的声波。这是我们的听觉频谱。我们可以把这个频谱分成 3 个简单的部分:低音(20-250 赫兹),中音(250-4000 赫兹),高音(4000 赫兹以上)。
- 动态——(或振幅)是声波的高度,决定声音的响度。振幅较大的声音比较响亮,振幅较小的声音比较柔和。它是用分贝来衡量的。
- 节拍 —是每秒钟的节拍数(bpm)。bpm 较高的音乐比较快,bpm 较低的感觉比较慢。这决定了音乐应该有多快或多慢。
- 音色 —这只是音乐的音调或用来发出声音的乐器类型。比如长笛和吉他的音色就不一样。
通过将数据值映射到音乐的一个或多个元素,用数据制作音乐成为可能。例如,您可以选择分配乐器在低数据值时以低音调演奏,在高数据值时以高音调演奏。通过将数据值映射到音乐元素,我们可以创建不同的音乐变体。虽然有几种可能的方法来映射这些值,但只有少数组合听起来确实不错。正是这一点使得数据发音有点棘手。
将数据映射到音乐元素
步骤 1:下载数据
import music21 as m21
from midiutil import MIDIFile
import yfinance as yf
from datetime import datetime
from sklearn.preprocessing import MinMaxScalerdf = yf.download(tickers='BTC-USD', period = 'max', interval = '1d')
df['days'] = np.arange(len(df))
使用yfinance python 库可以轻松下载加密货币数据。下载完数据后,我将日期转换成一系列数字。
第二步:设置音乐时长
# time data (in beats)music_duration = 120 #seconds
tempo = 85 #bpm
scaler = MinMaxScaler((0,music_duration*(tempo/60)))
beats = scaler.fit_transform(df['days'].values.reshape(-1,1)).reshape(-1)
我选择音乐持续时间为 120 秒,速度为 85。现在,我缩放数据的总长度以适合 120 秒的持续时间(根据速度调整)。
改变速度值会极大地改变最终的音乐。您可以尝试不同的速度值。
第三步:操纵数值
# compress valuevalues = df.Close.values# map values optional
# scaler = MinMaxScaler((0,1))
# values = scaler.fit_transform(values.reshape(-1,1))
如果数字出现巨大波动,你可以选择转换这些值(在这种情况下是比特币每日收盘价),或者保持原样。
第四步:设置一个八度音域
# octave rangeoctave_range = m21.scale.MajorScale('c').getPitches('c3','b4',direction='ascending')octave_range_midi = [x.midi for x in octave_range]
这是我们选择音高的地方。我使用了从c3到b5音高的 c 大调音阶。为此,我们可以选择任何流行的音阶。这完全取决于你希望音乐听起来怎么样。要创建 midi 文件,您应该将音高记数法转换为 midi 音符编号。
步骤 5:将值映射到 midi 音符编号(音高)
# map values to midi note numbersscaler = MinMaxScaler((0,len(octave_range)-1))
pitch = scaler.fit_transform(values.reshape(-1,1)).round().reshape(-1)
pitch = [octave_range_midi[int(x)] for x in pitch]
我把比特币价格和 midi 音符号码对应起来。较低的价格对应于较低的音高,较高的价值对应于较高的音高。
步骤 6:将值映射到速度
# map values to velocityscaler = MinMaxScaler((30,127))
velocity = scaler.fit_transform(values.reshape(-1,1)).round().reshape(-1)
velocity = [int(x) for x in velocity]
力度是弹奏一个音符时敲击按键的力度。类似地,我将较低的值映射到较低的速度,反之亦然。速度的范围是从 0 到 127 (0 为最低,127 为最高)。因为速度为 0 时我们听不到任何声音,所以我选择了速度为 30 时的最小值。
步骤 7:保存到 midi
track = 0
time = 0
channel = 0
duration = 1 # In beats
tempo = 85 # In BPMMyMIDI = MIDIFile(1)
MyMIDI.addTempo(track,time,tempo)for i in range(len(df)):
MyMIDI.addNote(track, channel, pitch[i], beats[i], duration,
velocity[i])with open("bitcoin_c_major.mid", "wb") as output_file:
MyMIDI.writeFile(output_file)
现在我们有了一个可以导入到任何数字音频工作站(DAW)的 midi 文件。我用 Logic Pro 作为我的 DAW。
数字音频工作站

DAW(图片由作者提供)
上图是一个黎明的例子。您可以简单地导入 midi 文件,并选择任何乐器或合成器来播放音乐。为了给音乐添加更多的细节,我复制了同一个 midi 文件,并选择了不同音量的不同乐器。你可以发挥创造力来增加音乐的深度。我是 80 年代合成器乐器的粉丝,所以我在这个项目中使用了它们。
有一些 Github repos,我用来很好地理解数据发音的概念。你可以在这里找到链接和这里。
瞧啊。现在你有了一首从数据中生成的音乐。是不是很酷?我计划做一些像这样更有趣的项目,所以敬请关注。
PS:我是一名数据科学家,而不是一名音乐家,所以我可能过于简化了一些音乐概念。
感谢阅读,干杯!
**Want to Connect?**Reach me at [LinkedIn](https://www.linkedin.com/in/rohithteja/), [Twitter](https://twitter.com/rohithtejam), [GitHub](https://github.com/rohithteja) or just [Buy Me A Coffee](https://www.buymeacoffee.com/rohithteja)!
我不管你的数据有多大。
原文:https://towardsdatascience.com/i-dont-care-how-big-your-data-is-f487aa7ea74e
抛开影射不谈,“大”数据正变得越来越小,越来越快。数据领导者需要适应新的模式。

图片由 Elur /Shutterstock 提供。
在过去二十年的某个时候,我们数据的大小变得与我们的自我有着千丝万缕的联系。越大越好。
我们羡慕地看着 FAANG 公司谈论在他们的数据湖或数据仓库中优化数百 Pb。
我们想象在那个规模下进行工程会是什么样子。我们开始在会议上大吵大闹,就像举重运动员谈论他们的卧推,谈论我们的筹码数量,以此来表达我们对技术的掌握。
对于绝大多数组织来说,实际情况是规模并不重要。归根结底,构建适合您公司的堆栈(和收集数据)是最重要的,没有放之四海而皆准的解决方案。
正如仙妮娅·唐恩可能会说的,“好吧,所以你有一些 Pb。这并没有给我留下深刻印象。”
不再是“要么做大,要么回家”
当“大”在描述我们这个时代的主要技术趋势之一的标签中以“数据”开头时,这可能是一件有争议的事情。然而,大数据一直被定义为超出体积。对于那些已经忘记的人,还有另外四个 v:多样性、速度、价值和准确性。
数据量在数据工程师心中占据着至高无上的地位,因为在雪花/AWS/Databricks 时代之前,存储和处理大量数据的能力被视为商业价值的主要架构障碍。
旧的大数据范式认为你需要收集尽可能多的数据(这是新的石油!)并建立相应规模的架构。当数据科学家使用机器学习魔法从被认为是不相关的数据集中收集以前不可思议的相关性和商业见解时,所有这些数据都会格格不入。
数量和价值是一回事。毕竟,谁知道哪些数据会对机器学习黑匣子有价值呢?

旧的大数据范式涉及数据转储和机器学习魔法。图片由蒙特卡洛提供。
我还没有与拥有基于云的现代数据堆栈的数据领导者交谈过,他们认为缺少存储或计算是实现其使命的主要障碍。他们也没有告诉我他们的团队会做出什么惊人的事情,“如果他们能收集更多的数据就好了。”
如果有的话,膨胀的表和 TB 可能揭示了缺乏组织,增加数据事件的可能性,以及对整体性能的挑战。换句话说,数据团队可能会发现他们在以价值、准确性和速度为代价积累数据量。
这可能就是为什么 Gartner 预测 到 2025 年,70%的组织将把重点从大数据转移到小数据和宽数据。

图片由蒙特卡洛提供。
明确地说,我知道有一些组织正在解决与大量数据流相关的非常困难的问题。
但这些都是专门的使用案例,虽然对流数据的需求带来了新的大数据挑战,但今天大多数组织都享受着一个技术时刻,他们可以经济高效地访问足够的存储和计算来满足组织的需求,而无需担心。
以下是一些原因,说明为什么您应该鼓励您的团队从大(量)数据思维模式转变,让您的大数据变小(er)。
数据正在产品化
随着现代数据堆栈和概念(如数据网格)的出现,我们发现,在中央数据团队为业务利益相关方准备好临时快照交付或见解之前,非结构化和无组织的数据并不处于最佳状态。
更多的数据不会简单地转化为更多或更好的决策,事实上,它可能会产生相反的效果。为了实现数据驱动,企业中的各个领域需要访问与其工作流无缝匹配的有意义的近实时数据。
这导致了数据交付流程的转变,看起来非常像运送产品。需要收集需求;迭代的特性;启用自助服务,建立 SLA,并提供支持。
无论最终结果是每周报告、仪表板,还是嵌入面向客户的应用程序,数据产品都需要一定程度的完善和数据监管,这与无组织的无序蔓延是相对立的。
几乎每个数据团队都有一层数据专业人员(通常是分析工程师),他们的任务是将原始数据处理成业务可以解释的形式。您传输数据的能力实际上是无限的,但是您受到人类能力的限制,无法使数据具有可持续的意义。
通过这种方式,预先工作以更好地定义消费者需求和构建有用的自助式数据产品可以需要更少的(甚至只是数量减少的)数据。
当然,另一个制约因素是质量和信任。你可以拥有世界上最好的数据仓库,但如果数据不可信,就不会有消费者。
像数据可观察性这样的技术可以将数据监控扩大到一定规模,因此不需要在数量和质量之间进行权衡,但关键仍然是数据量本身不足以产生一个维护良好的高质量数据产品的一小部分影响。
机器学习不太需要数据
机器学习永远不会处理你的整个数据堆栈,以在随机表格的干草堆中找到洞察力的针。事实证明,就像数据消费者一样,机器学习模型也需要高质量的可靠数据(或许更是如此)。
数据科学家设计特定的模型,用于回答难题、预测决策结果或自动化流程。他们不仅需要找到数据,还需要了解数据是如何获得的。
作为产品和数据平台的负责人,查德·桑德森一再指出,数据蔓延会损害我们数据堆栈的可用性,并使这项工作变得非常困难。
与此同时,机器学习技术和方法正在改进到需要更少训练数据的地方(尽管拥有更多高质量的数据在准确性方面总是比拥有更少的数据更好)。
到 2024 年, Gartner 预测合成数据和迁移学习的使用将使机器学习所需的真实数据量减半。
收集很容易,记录和发现很难
许多数据团队在开发他们的数据操作时都采用类似的方法。在通过数据可观察性减少数据停机时间后,他们开始关注数据采用和民主化。
然而,民主化需要自助服务,这需要强大的数据发现,这需要元数据和文档。
《纽约时报》前数据副总裁谢恩·穆雷(Shane Murray)提供了一些有用的记分卡(scorecards)来衡量数据平台的影响。

该记分卡可以确定基础设施团队应该关注 ELT 工具的可扩展性和仓库文档。图片由蒙特卡洛提供。
“没有一个数据团队能够以创建数据的速度记录数据。当你是一个小团队时,你可以通过,但随着你的成长,这将导致问题,”谢恩说。
“记录需要深入了解您的数据资产是如何被使用的,以及如何为企业增加价值。这可能是一个艰苦的过程,就定义达成共识,所以你必须仔细考虑从哪里开始以及要走多远。
也就是说,为数据提供定义和上下文并使其更容易被发现的价值通常超过构建另一个数据集的价值。关注您最常用的报告表格中最重要的指标和维度是一个很好的起点。"
老实说,挑战的一部分在于,除了稀有的数据管理员之外,没有人喜欢文档。但这不应该降低数据领导者的优先级别(自动化程度越高越好)。
你有数据债务

Towfiqu barbhuiya 在 Unsplash 上的照片
技术债务是指一个简单的解决方案会在以后的某个时间点产生返工。除非定期分期付款,否则它通常会成倍增长,并会摧毁创新。例如,您可能在一个过时的平台上运行多个服务,而改造平台意味着改造依赖的服务。
已经提出了许多不同的数据债务概念,但与我产生共鸣的一个概念结合了数据沼泽的概念,其中太多组织不良的数据使得难以找到任何东西,以及过度设计的表,其中长 SQL 查询和一系列转换使得数据变得脆弱,难以放入上下文中。这造成了下游的可用性和质量问题。
为了避免数据债务,数据团队应该以更高的比率折旧数据资产。 我们针对 数百个数据仓库的研究表明,企业环境中每 15 个表就会发生一次数据事件。
虽然每个团队都受到时间的限制,但另一个恶化的因素是缺乏对沿袭的可见性,这通常使得团队无法鼓起勇气反对,因为害怕栈中某处的意外破坏。
数据可观察性有助于提高整个堆栈的数据质量,并提供自动化的沿袭,而数据发现工具可以帮助您渡过难关。但是技术应该用来补充和加速,而不是完全取代这些数据卫生最佳实践。
大不坏,但也不一定是好事
所有这些并不是说“大”数据没有价值。那将是矫枉过正。
我想说的是,这已经越来越不是衡量数据堆栈和数据团队复杂性的好方法了。
在下一次数据会议上,不要问“你的堆栈有多大?”尝试关注质量或用途而非数据收集的问题:
- 您支持多少种数据产品?他们支持多少月活跃用户?
- 您的数据停机时间有多长?
- 你的团队支持哪些商业关键的机器学习模型或自动化?
- 您如何处理数据资产的生命周期?

当改变范式时,总会有一些痛苦,但振作起来,你总是可以(过度)补偿,开着一辆真正的大车去参加下一次会议。
在 LinkedIn 上连接 巴尔 。
想更好地了解和增加你的数据平台的价值? 伸手到蒙特卡洛 。
我使用熊猫已经 3 年了——以下是我最常用的 8 个功能
实用指南

Pandas 是数据科学生态系统中使用最频繁的库之一。它有许多功能,可以帮助我们完成几乎所有的数据分析、操作和处理任务。
我已经使用熊猫大约 3 年了,我不记得我需要其他工具来解决任何问题。因此,我强烈推荐有抱负的数据科学家学习熊猫。
虽然熊猫有很多功能,但其中有一小部分是你最需要的。在这篇文章中,我将分享我最常用的 8 个功能。
当然,我们需要一个数据集来处理。我准备了一个销售数据集样本,你可以从我的 GitHub 页面下载。我们将使用名为“sample-sales-data.csv”的文件。
1.阅读 _csv
它用于通过从 csv 文件中读取数据来创建熊猫数据帧。DataFrame 是 Pandas 的二维数据结构,由标记的行和列组成。
使用 read_csv 函数非常简单。我们只需给它 csv 文件的路径,我们就完成了。然而,这个函数有几个参数提供额外的功能。
让我们首先通过只提供路径来使用它。
import pandas as pddf = pd.read_csv("Data/sample-sales-data.csv")df.head()

df(作者图片)
head 方法显示数据帧的前 5 行。
让我们看看几个使 read_csv 函数更有用的参数。
- 给定列名,parse_dates 参数将数据类型转换为 datetime64[ns],这是适用于日期的数据类型。
df = pd.read_csv("Data/sample-sales-data.csv",
parse_dates=["sales_date"])df.dtypes# output
store int64
product int64
sales_date datetime64[ns]
sales_qty int64
sales_rev float64
dtype: object
dtypes 方法返回每一列的数据类型。
- usecols 参数允许读取列的子集,这在有几列而我们只需要其中的一部分时特别有用。
df = pd.read_csv("Data/sample-sales-data.csv",
usecols=["store","product","sales_qty"])df.head()

df(作者图片)
- nrows 参数用于读取文件的前 n 行。当我们有一个非常大的数据集,并且只需要探索它的一小部分时,它就派上了用场。
df = pd.read_csv("Data/sample-sales-data.csv", nrows=100)df.shape# output
(100, 5)
shape 方法返回一个显示行数和列数的元组。
2.值计数
value_counts 函数对于检查分类列中值的分布非常有用。它返回列中的唯一值及其出现的次数。
这里有一个例子。
df["store"].value_counts()# output
3 29
4 22
2 19
5 16
6 8
1 6
Name: store, dtype: int64
商店列中有 6 个不同的商店号,最常见的是 3,有 29 个观察值(即行)。
我们可以使用 normalize 参数来检查百分比份额的分布。
df["store"].value_counts(normalize=True)# output
3 0.29
4 0.22
2 0.19
5 0.16
6 0.08
1 0.06
Name: store, dtype: float64
29%的行属于 3 号商店。
3.astype
用正确的数据类型存储数据至关重要,因为:
- 有些函数只适用于特定的数据类型
- 一些函数使用合适的数据类型会更有效
例如,在 Pandas 的 dt 访问器下有许多有用的方法,但是它们只能用于 datetime 数据类型。
astype 函数转换列的数据类型。这里有一个例子。
df["sales_date"] = df["sales_date"].astype("datetime64[ns]")df.dtypes# output
store int64
product int64
sales_date datetime64[ns]
sales_qty int64
sales_rev float64
dtype: object
我们还可以使用字典在一次操作中改变多个列的数据类型。
df = df.astype({"store":"category","sales_qty":"float"})df.dtypes# output
store category
product int64
sales_date datetime64[ns]
sales_qty float64
sales_rev float64
dtype: object
4.isna
我们经常需要处理丢失的值,这些值仅仅表示我们没有的数据。
让我们创建一个带有一些缺失值的样本数据帧。
import numpy as np
import pandas as pdcustomer = pd.DataFrame({
"id": [11001, 11102, 11005, 11107, 11010],
"name": ["John","Jane","Matt","Ashley","Emily"],
"salary": [75000, 72000, np.nan, 76000, np.nan],
"city": ["Houston","Dallas","San Antonio","Houston", np.nan],
"start_date": ["2020-10-01","2021-11-10","2019-05-20",
"2019-07-19", np.nan]
})customer

客户(图片由作者提供)
例如,我们不知道 id 为 11005 的客户的工资信息。对于最后一行的客户,我们只有姓名。
当我们有一个大的数据集时,我们不能直观地检查或计算缺失值。isna 函数检查每个单元格,并为缺失值返回 True。然后,我们可以使用 sum 函数来获得每一列或每一行中缺失值的数量。
customer.isna().sum()# output
id 0
name 0
salary 2
city 1
start_date 1
dtype: int64 customer.isna().sum(axis=1) # for rows# output
0 0
1 0
2 1
3 0
4 3
dtype: int64
5.德罗普纳
我们基本上有两个选项来处理缺失值:drop 或 fill。
选择哪个选项取决于缺失值的数量、数据集的大小和我们的任务。例如,我们可以删除最后一行,因为我们对这个客户了解不多。
dropna 函数用于删除缺少值的行或列。以下 3 个参数定义了该功能的运行方式:
- 轴:0 代表行,1 代表列
- 如何:any 用于删除带有任何缺失值的行或列,all 用于删除带有所有缺失值的行或列
- thresh:为一行或一列的非缺失值数量设置一个阈值,使其不被丢弃。
让我们删除任何没有至少 3 个非缺失值的行。
customer.dropna(axis=0, thresh=3, inplace=True)customer

客户(图片由作者提供)
最后一行已被删除,因为它只有 2 个非缺失值,小于我们的阈值 3。
inplace 参数用于保存更改。
6.菲尔娜
处理缺失值的另一个选择是用合适的值替换它们,这可以用 fillna 函数来完成。
例如,我们可以用 salary 列的平均值替换该列中缺少的值。
customer["salary"].fillna(customer["salary"].mean(), inplace=True)customer

客户(图片由作者提供)
fillna 函数提供了一种用上一个或下一个值填充缺失值的简单方法。当我们有顺序数据时,它特别有用。我们可以通过使用带有以下选项的方法参数来实现这一点:
- bfill:使用下一个值替换丢失的值
- ffill:使用以前的值替换丢失的值
7.分组依据
groupby 函数对于数据分析来说非常方便。它允许根据列中的不同值对行进行分组。然后,我们可以为每个组计算一个大范围的聚合。
例如,在我们的销售数据集中,我们可以计算每个商店的总销售量,如下所示:
df.groupby("store")["sales_qty"].sum()# output
store
1 140436
2 89710
3 71309
4 45068
5 69614
6 23598
Name: sales_qty, dtype: int64
我们还可以使用命名聚合,如下所示:
df.groupby("store").agg(
total_sales = ("sales_qty","sum")
)

(图片由作者提供)
为了使 groupby 函数更有用,我们还可以执行其他几种聚合。
如果你想了解更多,这里有一篇我以前写的关于 groupby 函数的文章:
</11-examples-to-master-pandas-groupby-function-86e0de574f38>
8.独一无二的
这两个功能非常相似,所以我想一起解释一下。
- unique 返回不同的值
- nunique 返回不同值的数量
df["store"].nunique()# output
6df["store"].unique()# output
array([1, 2, 3, 4, 5, 6], dtype=int64)
熊猫有更多的功能来简化和加快数据科学家和分析师的工作。我们只介绍了其中的 8 种,但我相信这些是你会用得最多的。
你可以成为 媒介会员 解锁我的全部写作权限,外加其余媒介。如果你已经是了,别忘了订阅https://sonery.medium.com/subscribe如果你想在我发表新文章时收到电子邮件。
*https://sonery.medium.com/membership *
感谢您的阅读。如果您有任何反馈,请告诉我。
我让一个人工智能阅读维特根斯坦,然后让它扮演哲学家
实践教程
我让一个人工智能阅读维特根斯坦,然后让它扮演哲学家
维特根斯坦 2022 是真理、诗歌和无意义的机械

图片属于公共领域或由作者提供
一百年前,一位自信得惊人的奥地利人出版了一本有影响力的独特的哲学著作。1922 年版的路德维希·维特斯坦根的 逻辑哲学研究 是双语的,新的英文译本的每一页都与德文原版相对。编者按带着歉意告诉我们原因:词汇提出【显而易见的困难】还有那个【奇特的文学性格】。
《逻辑推理》是一本雄心勃勃的书。它涉及语言、科学和思想本身的局限性。它声称它的七个主要命题及其 519 个按等级编号的子命题具有确定的真理。风格总是大胆而简洁,对细微差别没有太多耐心。在极少数情况下,另一位哲学家的工作被提及,这是为了告诉我们他们是如何明显地错了。
维特根斯坦穿越了形而上学、认识论、他的语言图像理论、哲学的本质、形式逻辑、科学、伦理、上帝、自由意志、死亡、神秘,以及——在诗意的结尾——沉默:“一个人不能说话的,他必须沉默”。
“真理是美的一种形式”—维特根斯坦 2022
就在维特根斯坦开始第一个命题之前——“世界是一切真实存在的东西”——他引入了一个简短的序言,以这样一句话开始:
这本书也许只有那些自己已经思考过书中所表达的思想——或者类似的思想——的人才能理解。
很酷,路德维希。
幸运的是,我们可以依靠伯特兰·罗素,他可能是二十世纪最有影响力的哲学家、逻辑学家和维特根斯坦解释者。罗素是他在剑桥的老师,我们在序言中也了解到这一点,并将其视为朋友。
罗素正在帮助我们完成《逻辑研究》的前言,他填补了维特根斯坦自己认为过于明显的一些空白。对于我们这些目前不在维特根斯坦头脑中的人来说,这个背景对理解这本书非常有用。
“哲学是关于生活的废话的理论”—维特根斯坦 2022
两个神奇的数字和一些新技术让 2022 年成为重温历史的绝佳时机。自第一份(正式)出版物和第一份英文译本问世以来,已经整整 100 年了。这个百年纪念恰好与作者去世 70 周年纪念日相吻合,导致大多数国家的版权保护到期。
进入公共领域的 Tractatus 中,我最喜欢的部分是玩弄文本、转换和扩展文本的能力。近年来,语言处理神经网络的能力呈指数级增长,这是由 Transformer 架构的引入和对大量文本进行训练的大型模型的产生推动的,例如 GPT-3 ,OpenAI 通过其 API 提供了这些模型。
“世界没有界限”—维特根斯坦 2022
因此,很自然地,我让 GPT-3 多读了几遍《T21 》,学习如何以相似的风格和相似的概念写作,然后让它产生一些适合后神秘数字 8 的全新命题。请允许我介绍: 维特根斯坦 2022 。
这是一个网站,它列出了所有原始的编号命题,并添加了第八个命题,就像我们的朋友路德维希会自信地做的那样。不认同你看到的?现在是 2022 年,只需按下 Shuffle 按钮,就能瞬间替换成不同的命题。类似于 1922 年的版本,我把这些命题的英文版和德文版并列展示。

在 wittgenstein.app 亲自尝试一下
GPT-3 发明的命题有些是诗意的,有些是安心的,有些是自我批判的,有些不区分大小写的,有些是假的,有些甚至显得是不可译的德语双关语。人工智能相当好地模仿了维特根斯坦和译者的风格,并且明确地吸取了《手稿》中的一些主要哲学主题。
虽然我们不应该在充满魔力的命题中寻找绝对真理🎱(事实上,我们应该关注早期维特根斯坦的作品——尽管声称是确定的),相反,我们可能会在其中发现一些艺术、诗歌或娱乐价值。继续引用本书序言中的话:
因此,它不是一本教科书。如果有一个人能理解地阅读它,并从中得到乐趣,那么它的目的就能达到。
我们也这么做吧。
“世界就是它看起来的样子”—维特根斯坦 2022
Be 在幕后,维特根斯坦 2022 正在使用 OpenAI 的微调 API,它允许你采用一个巨大的预训练 GPT-3 模型,并继续根据定制数据训练它。在这种情况下,数据由来自 Tractatus 的命题组成,每一个命题都有两种语言和它们的相关数字。这本书高度结构化的格式和双语的例子可能有利于学习[需要引用]。
我能找到的最好的文本版本来自古腾堡项目。他们有一个整洁的排版版本作为 LaTeX source,这允许我清晰地解析出各个命题并处理数学符号。我决定尽可能用普通的 Unicode 来呈现,只依赖 HTML 来强调、下标和上标。在有点残酷地颠倒维特根斯坦的图像理论中,我将表格复制为线性文本(使用括号和分号),同时为少数插图使用通用占位符。我也跳过了前言、序言和脚注。
我尝试了几种不同的方法来格式化和参数化 GPT 3 号的提示和完成。最后,最好的方法是只给出一个命题的编号,并让它同时产生两种语言。我将结果与子命题混合在一起,所以最初的版本保持简单,并且在顶层。网站上的样本来自一组不同的发动机,主要是居里和达芬奇,其中 top_p 、温度、频率 _ 惩罚和在场 _ 惩罚、参数的值各不相同。
该网站是基本的框架——没有 HTML、CSS 和 JavaScript,在谷歌字体的帮助下呈现出 1922 年的凸版印刷风格。 Shuffle 按钮异步加载一个随机命题(但是你的浏览器的后退按钮仍然有效)。这些都在应用引擎上提供,从 Firestore 数据库中提取预先生成的命题。
你可以在 GitHub 上找到所有的源代码,大部分的数据准备、训练和推理都在一个交互式的 Colab 中进行。它没有针对可重用性进行优化,但您可能会在其中找到一些灵感或有用的代码片段。
“没有未来的哲学,只有现在的哲学”—维特根斯坦 2022
那么,GPT-3 真的“理解”了“T8”的逻辑吗?有时候确实是这样。我不是一个哲学家(虽然我年轻的时候经常和一些人在一起),机器是否能够“理解”或“意识”的问题对我来说似乎从来没有什么建设性。有一次,我甚至有幸让约翰·塞尔试图说服我们几个谷歌工程师相信他著名的论点。它进行得不顺利。顺便提一下,我的同事布莱斯在他最近关于这个主题的文章中说得很有道理。
https://medium.com/@blaisea/do-large-language-models-understand-us-6f881d6d8e75
在写这个故事的时候,我用 OpenAI 的游乐场产生了一些标题创意。(可笑的是,GPT 3 号在这项任务上比我强多了。)其中一个建议似乎是在取笑这个问题:
如果人工智能不知道什么是正确的,它就不会错。
有个命题!

他在推特上@ 维特根斯坦 22
特别感谢前往 的卡特琳娜·罗曼诺娃 和 的尼马尔·帕特尔 对网站和故事草稿的反馈,前往 文森特·万霍克 对未来工作的构想,以及前往 埃利奥特·冈恩本
更新 : OpenAI 刚刚为 DALL E 2 打开了API。对于维特根斯坦 2022* ,这意味着我们为每个命题得到匹配的插图!*

偶尔还会为眼睛拍
我曾经训练一个人工智能押韵,GPT-J 花了很长时间
因为 Colab 很慢,所以我升级到了 Pro。每首打油诗花了我一毛钱。

“一个装在罐子里的发光大脑,通过电子方式连接到一台打字机上,上面有许多松散的文本页,数字艺术,“作者的 DALL-E 图像”
2022 年 3 月,我为我的深俳句文章探索用人工智能写一首特定格律的诗。为了那个项目,我训练了 GPT-J [1]语言模型来关注短语中音节的数量,以生成给定主题的短诗。我没有接受模特用韵脚写散文的能力。为了这个名为“深度打油诗”的项目,我花了上个月的时间研究和训练一个 GPT-J 模型来写打油诗,注意韵律和押韵。这里有一个关于足球主题的输出示例:
你是守门员,这是你的名字。你是这场游戏的最后一道防线。你是团队中最棒的。你才是应该尖叫的人。当其他 11 名队员瞄准时。
-深度打油诗
正如你所看到的,押韵非常简单,但是很扎实。计价器还可以,但不完美;第二行和最后一行似乎多了一两个音节。但总的来说,这首打油诗相当不错,甚至有点滑稽,因为它暗示对方的守门员可能会在你的网上射门。
在这篇文章中,我将介绍一些关于使用语言模型写押韵诗的局限性的背景知识,并讨论一些这方面的前期工作。我将给出深度打油诗系统的概述和组件的细节。最后,我将展示一些结果,并讨论可能的后续步骤。
请务必查看附录以获取更多结果。你也可以在这里使用 Google Colab 生成你自己的打油诗。

nonsenselit.org,爱德华·利尔为“一个从魁北克启航的裁缝”所作的插图,来源:公共领域
背景
利默里克形式的起源是未知的,但通常认为它来自爱尔兰的利默里克镇[2][3]。英国插画家兼诗人爱德华·利尔于 1821 年出版的《十五位绅士的轶事和冒险》是最早的打油诗集之一。这是书中的一个例子。
一个从魁北克出航的裁缝,
在一次暴风雨中,他冒险登上甲板,
但是海浪很大,
,
他翻了个跟头。
——爱德华·利尔
打油诗形式
正如 Maxey Brooke 在她的文章Limerick-gimer CK【3】中所描述的,“一首五行打油诗是一首无意义的诗,其中第 1、2、5 行是三英尺的押韵诗,第 3、4 行是两英尺的押韵诗。”其中术语“脚”表示重读音节。
这是经典的打油诗形式:
哒哒哒哒哒哒哒哒哒哒
哒哒哒哒哒哒哒哒哒哒哒
哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒哒
算上“das”,你可以看到传统五行打油诗的音节数是 8/8/5/5/8。如果你向上滚动并仔细查看生成的“足球”打油诗,你会发现脚是如何排列不正确的,但李尔王的“魁北克”打油诗有与经典形式完美匹配的脚。

wikipedia.org 艾萨克·阿西莫夫好色打油诗封面,来源:
肮脏的打油诗
历史上,打油诗的主题经常转向限制级的领域。例如,科幻作家艾萨克·阿西莫夫写了一系列充斥着“淫荡打油诗”的书这里有一个相对干净的例子。
一天晚上,一个卵子对一个精子说,“你是一个非常迷人的年轻的小家伙。来加入我吧,我亲爱的,让我们的细胞核相遇,九个月后我们都会足月的。
——艾萨克·阿西莫夫
这是作者维维安·霍兰德的另一篇文章,总结了为什么打油诗通常是肮脏的。
五行打油诗将笑从解剖学角度放入相当经济的空间。但是我见过的好的很少是干净的,而干净的很少是滑稽的。
——薇薇安·荷兰
接下来的部分将讨论先前使用人工智能系统创作打油诗的尝试。
先前的工作
杜克大学的五名研究人员在 2021 年构建了一个名为 LimGen 的生成五行打油诗的人工智能系统[6]。论文的标题是“曾经有一个非常糟糕的诗人,它是自动化的,但你不知道。”这个标题可能有点谦虚,但它似乎为结果设置了一个低门槛。
他们的系统基于 OpenAI 的 GPT-2 [7]模型,增加了学习押韵、重音和节拍的代码。这是他们论文中的一个例子,使用提示“money”生成
有一个叫托德的贪婪的人,他在一次诈骗中失去了所有的钱。
当他回去工作的时候,他被他的店员抢劫了,再也买不到鳕鱼了。
-利姆根
这是另一个使用提示“狡猾”的例子。
有一个魔术师名叫尼克,他用一个魔术骗过了全家人。当他回去躲藏时,被他的新娘发现,并被一只神奇的口红杀死
对我来说,这些打油诗确实看起来不怎么样。押韵很好,但是重音和节拍有点不对。散文也有点偏离主题,笑点也没什么意思。与其说他们有趣,不如说他们古怪。
这篇论文的作者承认他们的系统有积极和消极的方面。他们让专家比较人类和 LimGen 创作的打油诗,并根据五个标准从 1 到 5(越大越好)给它们打分。)

专家评委对打油诗的评价:人类对 LimGen ,来源: J. Wang 等
…尽管专家的判断证实了人类诗人优于林根,但它仍然表明,根据几个指标,林根的表现还不错:林根的语法不错,能用诗句很好地讲述一个故事。似乎语法和讲故事是最容易掌握的诗歌属性。…情感和理智更难学。但是真正区分人类诗人和林根的是诗人不断开玩笑的能力。
——王健友等。
接下来,我将讨论深度打油诗的主要组成部分,并进入实现细节。
系统组件
以下是深度打油诗系统的主要组成部分。

深石灰的成分,作者图解
我开始在zenodo.org【8】上收集超过 6 万首打油诗。它被称为“计算诗学打油诗数据集”,在知识共享署名许可下发布。
我为多任务学习准备了数据集,首先使用 KeyBERT [9]为每首打油诗提取一个或两个单词的主题。然后,我使用音素化器[10]将打油诗和主题转换成音素,以帮助系统学习韵律和韵律。我过滤掉了 55k 首打油诗,去掉了那些不押韵或者每行音节数不一的打油诗。
接下来,我使用主题、打油诗和每行的最后一个单词,也就是押韵集,为多任务学习创建了条目。训练集包括数据的文本和音素版本。我用谷歌 Colab Pro+ 对 GPT-J-6B 8 位模型[1]进行了五天的微调。
我设计的五行打油诗生成系统是交互式的。你首先输入一个主题,比如“足球”,它会产生多达 10 个押韵的集合,比如“比赛/名字/球队/尖叫/目标。”在选择了一个押韵集之后,系统将为打油诗的第一行生成几个候选词,每个都以单词“game”结尾你选择你最喜欢的一行,重复这个过程四次,直到五行打油诗完成。
组件和流程的详细信息将在以下章节中讨论。

“神经网络与橙电,系统细节与放大镜,数字艺术,”DALL-E 图片作者
系统详细信息
打油诗数据集
我使用 Almas Abdibaye 等人的计算诗学打油诗数据集的样本来训练这个系统。他们在知识共享署名开源许可下发布了数据集。它收集了 65,881 首质量不同的打油诗。这是数据集中的一个例子。
要让声波移动,要知道
需要一种介质,比如空气。真空中听不到声音
;他们发现它的导电性为零。(什么都没有。)
- DOLFCP #22519
这是另一个例子。
“让我去跳蚤市场,好吗?我从未见过这样的地方。”她接着犯了一个严重的新手错误,带着一大袋跳蚤回来。
- DOLFCP #51951
你可以看到押韵很好,节拍很好,两个例子都很幽默。此外,请注意各种标点符号,如引号和括号。我在训练数据中保留了标点符号,希望人工智能系统会使用这样的标记生成新的打油诗。
用于提取关键词的键盘
为了让新模型根据用户提供的主题生成新的打油诗,我提取了数据集中每个打油诗的关键字,并在训练数据中使用这些关键字。像以前的项目一样,我使用 KeyBert [9]来提取关键字。下面是 Python 代码。
from keybert import KeyBERT
kw_model = KeyBERT()limerick = """For a sound wave to move, be aware
That a medium’s needed, like air.
In a vacuum no sound
Can be heard; they have found
Its conduction is nil. (Nothing’s there.)"""keywords = kw_model.extract_keywords(limerick, keyphrase_ngram_range=(1, 2), stop_words=None)
print(keywords[0][0])
结果是这首打油诗的“声波”和上面第二个例子的“跳蚤市场”。
使用音素化器将文本转换为音素
为了让系统注意韵律和节奏,我使用文本(也称为字素)和音素来训练系统。我使用 Phonemizer 项目将打油诗转换为节日格式中的音素,这表示音节中断。源代码在这里。
from phonemizer import phonemize
from phonemizer.separator import Separatorlimerick = """For a sound wave to move, be aware
That a medium’s needed, like air.
In a vacuum no sound
Can be heard; they have found
Its conduction is nil. (Nothing’s there.)"""phonemes = phonemize(limerick, language='en-us',
backend='festival',separator=Separator(phone="-", word=' ',
syllable='|'), strip=True)
下面是字形和节日音素格式的打油诗示例。
要让声波移动,请注意
f-ao-r ax s-aw-n-d w-ey-v t-ax m-uw-v b-iy ax | w-eh-r需要一种媒介,比如空气。
DH-AE-t ax m-iy | d-iy | ax-m-z n-iy | d-ax-d l-ay-k eh-r在真空中没有声音
可闻;他们已经找到了
dk-AE-n b-iy hh-er-d DH-ey hh-AE-v f-aw-n-d它的传导性为零。(什么都没有。)
ih-t-s k-ax-n | d-ah-k | sh-ax-n ih-z n-ih-l n-ah | th-ax-ng-z DH-eh-r
首先,你可以看到在这首打油诗的语音形式中既没有标点符号也没有大写字母。您还可以看到破折号如何分隔音素,音节如何由横条分隔,空格如何分隔单词。注释音节模式将有助于语言模型遵循打油诗的节拍。
此外,注意语音形式如何帮助识别打油诗中的韵脚。这里是每一行的最后几个单词,拼写为字素和音素。
aw 是 / 空气 /声音/发现/there
ax | w-eh-r/eh-r/s-aw-n-d/f-aw-n-d/DH-eh-r
注意音节的最后部分在第 1、2 和 5 行的字素中的拼写是如何不同的。语音拼写完全匹配的地方。这是押韵的表示。
我使用打油诗的语音形式来筛选数据集,只包括遵循 AABBA 押韵方案并严格遵守音节 8/8/5/5/8 方案的条目。结果数据集有 55K 个条目,比 62K 少。

“神经网络,合成波”,DALL-E 图片作者
GPT-J 语言生成模型
GPT-J 是一个基于 OpenAI 商业 GPT-3 模型的开源大型语言模型[11]。它是用 Pile [12]来训练的,Pile 是一个 825 千兆字节的文本集合。
以下是《GPT j
我们的模型设计和超参数选择与 6.7B GPT-3 非常接近,但有一些不同。值得注意的是,
-该模型在来自具有 800GB 文本的堆数据集的 400B 标记上进行训练。
-为了简单起见,没有使用有效注意(线性、局部/滑动窗口等),因为在这种规模下,它不会显著提高吞吐量。
-每个注意力头的尺寸设定为 256,比同等尺寸的 GPT-3 大两倍。这显著提高了吞吐量,而性能下降最小。
—王贲和亚当·小松崎[1]
请注意,GPT-J 模型的表现类似于 OpenAI 的 GPT-3 居里模型,这是他们的大型达芬奇模型的一个下降。你可以在这里免费试用 GPT J。
偏见和局限
像大多数大型语言模型一样,GPT-J 从成堆的训练数据中学到了固有的偏见和局限性。GPT-J 模型的作者在他们的模型卡上写了这些。
GPT-J 是在 Pile 上接受训练的,Pile 是一个众所周知包含亵渎、淫荡和其他粗鲁语言的数据集。根据使用情况,GPT-J 可能会产生社会不接受的文本。…
与所有语言模型一样,很难预先预测 GPT J 将如何响应特定的提示,攻击性内容可能会在没有警告的情况下出现。我们建议在发布之前由人工管理或过滤输出,以审查不需要的内容并提高结果的质量。
好的,我注意到了。
使用 GPT J 生成打油诗
首先,我问 GPT-J 什么是打油诗,试了几次后,它反应不错。
什么是打油诗?
一首五行打油诗是一首英语诗歌,押韵方式是 AABBA。打油诗一般用第三人称写,讲一个故事,通常描绘一个幽默的情景。
好吧,它知道什么是打油诗。但是当我让它以“咖啡”为主题写一篇文章而不做任何微调时,我得到的是:
用五行写一首有趣的打油诗。
题目:咖啡
打油诗:
1。一个男人在咖啡馆喝咖啡。当咖啡师看到他时,他。问道,“那是什么?”
4。那人回答说:‘是一辆
5。“三种豆子的混合物”
这是一个有趣的故事开头,但它显然不是一首打油诗。我不得不微调这个模型,让它工作。

教室黑板多任务,DALL-E 图片作者
多任务学习
像我在我的深度俳句项目中使用的技术一样,我也使用多任务学习来教 GTP-J 如何创作打油诗。
相比之下,一种单一任务的方法是训练系统在给定主题的情况下生成五行打油诗,全部以字形的形式。问题是,系统将无法“看到”押韵和节拍,因为这些特征在字形中有些模糊。通过多任务学习,我能够同时教会系统以下四件事。
- 如何从字素转换到音素,然后再转换回来
- 给定主题,如何生成押韵集
- 给定一个主题和一组韵脚,如何用适当的形式写一首打油诗
- 给定一个题目,如何写一首语法好又有点幽默的打油诗
我为过滤后的数据集中的每首打油诗的训练数据构建了八行。我使用不同的字符来包含条目,以帮助系统知道哪个任务是哪个任务。此外,我使用一个四个字符的字符串来表示特定的转换,比如=T2L=表示主题到五行打油诗,而=G2P=表示字素到音素。这是八项任务。
1\. <topic_g =T2L= limerick_g>
2\. <topic_g =T2R= limerick_rhymes_g>
3\. <limerick_rhymes_g =R2L= limerick_g>
4\. (topic_p =T2L= limerick_p)
5\. (topic_p =T2R= limerick_rhymes_p)
6\. (limerick_rhymes_p R2L= limerick_p)
7\. [limerick_g =G2P= limerick_p]
8\. {limerick_p =P2G= limerick_g}
以下是为示例 limerick 列出的几个任务。
<sound wave =T2R= aware / air / sound / found / there>
(s-aw-n-d w-ey-v =T2R= ax|w-eh-r / eh-r / s-aw-n-d / f-aw-n-d / dh-eh-r)
在训练数据集中,我总共使用了 438,496 行。
训练 GPT J
一旦我得到了训练数据,我就按照这里的说明使用 8 位权重训练模型。下面是我使用的 Python 代码。
from transformers import Trainer, TrainingArguments, AutoModelForCausalLMtraining_args = TrainingArguments(
output_dir="/content/outputs",
overwrite_output_dir=True,
num_train_epochs=1,
evaluation_strategy="steps",
per_device_train_batch_size=4,
per_device_eval_batch_size=4,
eval_steps = 500,
warmup_steps = 500,
prediction_loss_only=True,
save_strategy="no",
weight_decay=0.01)trainer = Trainer(
model=gpt,
args=training_args,
train_dataset=lm_datasets['train'],
eval_dataset=lm_datasets['test'],
callbacks=[SaveCallback])
使用我的 Google Colab Pro+订阅花了五天时间来训练这个系统。当训练完成后,系统可以执行所有八项任务,但只需要两项来生成打油诗、主题到韵律和韵律到打油诗。学习其他六项任务有助于系统很好地执行前两项任务。
创作一首打油诗
有了经过训练的系统,我可以用它来交互式地创作一首给定主题的打油诗。首先,我根据一个主题写了一套押韵诗,然后我一行一行地做了首打油诗。
押韵集
为了创建押韵集,我要求它为主题“咖啡”生成一组五个押韵。它返回了以下结果。
topic: coffee
rhyme sets:
------------------------------------
1: drink / think / ice / nice / blink
2: pot / got / brew / you / hot
3: night / sight / tea / me / right
4: go / slow / drink / think / no
5: agree / tea / too / do / free
**6: today / okay / think / drink / away**
7: brazil / ill / do / too / pill
8: cup / sup / drink / think / up
注意,系统生成了十个押韵集,但是代码过滤掉了重复的,所以只显示了八个。
首行
我喜欢第六组韵脚,所以我把它传回到模型中,为打油诗生成一些初始的诗句。这是结果。
topic: coffee
rhyme-set: today / okay / think / drink / awayfirst lines:
---------------------------------------
1: "I don't drink coffee", said I today.
**2: Coffee’s a drink for today**
3: Coffee's just what you'll have today
4: The coffee shop's open today.
同样,系统会过滤掉任何重复的行或不以单词“today”结尾的行剩下的四个中,我喜欢第二个。我对其他四行重复了这个过程。我的选择用粗体表示。
预备之物
topic: coffee
rhyme-set: today / okay / think / drink / awayfirst_line: **Coffee's a drink for today**second lines:
----------------------------------------------
1: I'm not saying it's not okay
2: (It is caffeine, not tea). Okay,
3: Not just breakfast), but it's okay
4: And tomorrow, and all day, okay?
5: That I wouldn't be drinking okay?
6: Which I must have, I guess, okay?
**7: With a splash of fresh cream, it’s okay**
第三线
topic: coffee
rhyme-set: today / okay / think / drink / awayfirst_line: **Coffee's a drink for today**
second_line: **With a splash of fresh cream, it's "okay"**third lines:
----------------------------------
1: (Although many think
2: (Though I think it's not think
3: But the best I think
4: If the milk's not what you think
**5: If you do, as I think,**
6: To serve coffee, I think.
第四行
topic: coffee
rhyme-set: today / okay / think / drink / awayfirst_line : **Coffee's a drink for today**
second_line: **With a splash of fresh cream, it's "okay"**
third_line : **If you do, as I think,**fourth lines:
----------------------------------
1: Add some milk, and you'll drink
2: Add sugar and drink
3: But it's coffee you drink
4: **And you need a strong drink**
5: It's a milky drink,
6: That is, if you drink
第五行
topic: coffee
rhyme-set: today / okay / think / drink / awayfirst_line : **Coffee's a drink for today**
second_line : **With a splash of fresh cream, it's "okay"**
third_line : **If you do, as I think,**
fourth_line : **And you need a strong drink**fifth lines:
------------------------------------------
1: (Which is why I'm leaving it away).
2: Or you're not allowed to have milk away.
**3: The caffeine will blow you away.**
4: That will wake you up, coffee's away!
咻,选择真多!这是最后一首打油诗。
咖啡是今天的饮料
加一点鲜奶油,没关系
如果你这样做,正如我所想的,
你需要一杯烈酒
咖啡因会让你神魂颠倒。
-深沉的打油诗
结果相当不错。让一个人参与进来有助于确定一套好的押韵和台词。请务必在附录中查看更多生成的打油诗。
讨论和后续步骤
这个系统似乎运转得相当好。结果相当不错,尽管这很大程度上取决于人工输入。如果使用大型 GPT-3 达芬奇模型,该系统可能会完全自动运行,但这将花费数千美元。
改善结果的另一种方法是除了使用 Festival 形式之外,还使用音素的 eSpeak 形式。这将允许模型看到重读的音节,并更好地创建具有适当节奏的打油诗。
源代码和 Colabs
这个项目的所有源代码都可以在 GitHub 上获得。我在 CC BY-SA 许可下发布了源代码。

知识共享署名共享
感谢
我要感谢詹尼弗·林对这个项目的帮助。
参考
[1]J,B. Wang,A. Komatsuzaki,Mesh-Transformer-JAX:Model-Transformer 语言模型与 JAX 的并行实现 (2021)
[2] C. G .卢米斯,美国打油诗传统 (1963)
[3] M .布鲁克,利默里克-吉梅里克,《词途》:第 13 卷,第 1 期,第 10 条,(1980)
[4] E .李尔,《十五位绅士的轶事和冒险》(1821)
[5] I .阿西莫夫,《好色的打油诗》(1976)
[6]林根,王健友等,曾经有一个非常糟糕的诗人,这是自动化的,但你不知道它 (2021)
[7] GPT-2,a .拉德福德等人,语言模型是无监督的多任务学习者 (2019)
[8] A. Abdibayev,A. Riddell,Y. Igarashi 和 D. Rockmore,计算诗学打油诗数据集 (2021)
[9] M. Grootendorst, KeyBERT:使用 BERT 的最小关键词提取(2020)
[10] M. Bernard,Phonemizer:Python 中多种语言的文本到电话转录 (2016)
[11] T .布朗等人,GPT-3,语言模型是很少出手的学习者 (2020)
[12] L. Gao 等, The Pile:一个用于语言建模的 800GB 多样化文本数据集 (2020)
附录
以下打油诗是使用深度打油诗交互式生成的。
鱼在一家法国餐馆,我想要鱼。
有了沙拉,我很高兴地希望
有一张桌子可以用餐
并等待标志。因为我饿了,所以我会吃他们做的菜。马拉松
马拉松是一场漫长的比赛
,我想我能以一些优雅赢得比赛
如果我能跑到终点,
我知道我的朋友
会以我的速度为我加油。机器学习
人工智能系统可以学习。
有些人会利用这种学习来赚取
,而它只是一台机器
学习——所有,你可能指的是——
中央权力,世界关注。汤姆·布拉迪
汤姆·布拉迪,Buc 的四分卫的名字,
他是一个真正的艺术作品。遗憾的是
他永远也猜不到,
由于他们缺乏成功
他太老了,不能玩这个老游戏。
为了无限制地访问 Medium 上的所有文章,成为的会员,每月支付 5 美元。非会员每月只能看三个锁定的故事。
我运行了 80,000 次模拟来研究不同的 p 值调整
多重比较很难。哪种校正最能处理假阳性和假阴性?

由 Unsplash 上的 Edge2Edge 媒体拍摄
TL;博士:
- 在现实环境中,一次进行许多显著性测试是很常见的。然而,这意味着找到假阳性关系的可能性更大。有许多 p 值调整试图控制这一点,但它们往往过于保守,假阴性率非常高——尤其是在进行数百或数千次测试时。
- 我使用 80,000 次模拟来研究四种不同的调整程序在准确性、假阳性率和假阴性率方面的比较。这些模拟在比较次数、具有真实(即非空)效应的比较比例以及效应大小的平均显著性方面有所不同。
- 我发现,在反映社会科学设置中最常见的数据类型的情况下,调整后的 p 值比未调整的 p 值更准确。然而,在其他情况下,未经调整的值具有更高的准确性。
- 最好的方法是在体面的先验信息和理论的基础上进行测试。当这不可能时,该分析表明基于错误发现率调整 p 值是大多数现实社会科学案例的最佳策略。继续使用未经调整的 p 值应该基于对被测事物相关性的坚定信念。
在理想的世界中,所有的显著性检验都是为理论服务的。
然而,令几乎所有专业从事数据工作的人惊讶的是,我们并不是生活在一个理想的世界中。各种压力迫使许多从业者对同一数据集进行数十、数百甚至数千次显著性检验。这样做的一些原因比其他的更好,但是,独立于最好的动机:这种做法基本上打破了日常统计。p 值变小的保证——仅这种可能性就能刺激零差异出现的几率仅为 5%、1%或 0.1%——在你进行数百次、数千次或数万次博弈时是没有意义的。(总有一个相关的 XKCD )。正如我的微积分老师在向我们阐述极限的概念时所说的:
一个非常非常大的数除以一个大的数[或者,等价地,乘以一个小的比例]仍然是一个非常非常大的数。
最常见的方法是用一种程序来解释这种膨胀的假阳性率,这种程序伴随着膨胀大部分(如果不是全部)p 值,使显著性阈值更难达到[1,2]。我们进行的测试越多,新的门槛就越严格。但是,虽然这有助于确保人们不太可能在某样东西不在的时候断定它“在那里”,但这意味着他们不可避免地更有可能在它在的时候断定它不在“那里”。换句话说,我们优先考虑避免假阳性而不是假阴性[3]。虽然这种偏好在许多情况下是有意义的,但它不是(也不应该是)普遍偏好的。在某些情况下,在假阳性上浪费时间的成本很高——但是,在其他情况下,抑制潜在的洞察力比避免走进死胡同的成本更高。幸运的是,有替代的调整程序承诺不那么严格。不幸的是,(或者,也许,也幸运的是,这取决于你的观点),有几个重合的考虑因素在起作用,这意味着在何时使用哪种方法上根本缺乏严格的规则。
在我工作的地方,我们与大型跨国公司、新闻机构、政府机构和各种规模的非营利组织合作,收集和分析来自各种受众的意见数据。他们构建的调查种类多得令人难以置信;假设同一个解决方案能满足每个项目的所有需求是没有意义的。说某种方法绝对“更好”也是没有意义的。
“更好”总是与某个目标、某个基准相关,而每个目标或基准都涉及到权衡。我宁愿通过向我们的客户和合作伙伴提供每种方法的成本和收益来授权他们做出这些决定,而不是自以为知道什么对他们“最好”。
为此,本文对 80,000 个模拟进行了分析,根据 3 个主要考虑因素调查了不同 p 值调整策略的假阳性和假阴性率:
- 正在运行的测试的数量。
- 测试集中“真实”关系的数量。
- 我们对那些“真实的”关系有多确定。
(“真”是指来自不同于空值的分布;其中非零差的经典“替代”假设是正确的。在这篇文章中,我将使用“真实”,这既是为了语义简单,也是为了让外行读者也能理解——这两个概念经常互换使用。
我发现,在最有可能在社会科学数据中看到的背景下,p 值调整提高了我们准确分类效果是否“真实”的能力。然而,这主要来自于这样一个事实,即 un 调整后的 p 值可靠地夸大了假阳性率。在处理数据时,如果您预计测试集的低比例确实是显著的(比如 1%或 5%),这种夸大的假阳性率超过了调整后的 p 值具有非常高的假阴性率的强烈趋势(这种趋势只会随着测试数量的增加而加剧)。在处理数据时,如果您预计适度到更高比例的关系被测试为真正显著(例如,10%、20%或 40%),传统的、未调整的 p 值在分类准确性方面开始持续优于调整后的 p 值。
在深入研究结果之前,我想先了解一下模拟是如何设置的。这种讨论主要是概念性的;我把代码块留给那些有兴趣了解我如何在 R 语言中分析这些代码的人,这些代码块在我的个人博客上更具技术性。(也欢迎大家来参观这个项目的 GitHub 资源库)。
模拟设置
这个模拟的主要工具是谦逊 t 统计。由于在原始的模拟数据集之间进行比较只是用额外的(计算上昂贵的)步骤生成 t 统计量,所以我直接生成它们,然后将它们转换成 p 值。我假设空关系是正态分布的,平均 t 值为 0。这意味着大约 5%的真空值将大到足以被错误分类为“显著的”(即,触发假阳性)。你可能知道,这是传统的零假设显著性检验的要点。

图 1: 10,000 个模拟 t 值,其中所有值都来自零分布。图片作者。
为了模拟“真正的”显著差异,我生成了一个单独的 t 统计系列,其中第 20 个分位数位于传统的显著性临界值(1.96)的中心。这相当于 80%功率的测试;也就是说,由于偶然性,假阴性将发生 20%。我还假设这些 t 统计量遵循正态分布。

图 2: 10,000 个图,其中 90%为空,10%来自“真实”分布。注意较粗的尾部,因为更多的值在极端区域,比我们预期的要多。图片由作者提供。
标题中的 80,000 个模拟由 80 个条件组成,每个条件执行 1,000 次。每种方法都试图比较 5 种不同 p 值的假阳性和假阴性率: Bonferroni 调整的 p 值、 Holm 调整的 p 值、Benjamin 和 Hochberg 调整的 p 值、Benjamin 和 Yekutieli 调整的 p 值,以及未调整的 p 值。前两个针对家庭错误率(FWER)进行调整,旨在控制出现一个假阳性的可能性,而第三个和第四个调整基于假发现率(FDR),将受控数量的假阳性视为降低假阴性率的可接受代价[4]。这使我们能够相互比较两种调整的性能,以及与标准的、未调整的 p 值进行比较。
这 80 个条件由 3 个研究问题驱动:
- **假阳性/假阴性率将如何随着测试数量的增加而变化?我考虑了 4 种不同测试次数的情况:10、100、1000 和 10000 次。
- 随着“真实效应”比例的变化,假阳性/假阴性率将如何变化?我考虑了 5 种情况,其中真实效果占测试的不同预期百分比:1%、5%、10%、20%和 40%。
- **随着“真实效应”大小分布的变化,假阳性/假阴性率将如何变化?我研究了当真正分布有 4 个不同的标准差时会发生什么:0.1、0.5、1.0 和 2.0。这也转化为真实效应的不同平均 p 值:0.04、0.02、0.005 和 0.0003。这反过来又告诉我们,我们有多“确定”这些差异不等于零。实际上,这是由效果大小和估计噪声的某种组合决定的。
4 个大小条件 x 5 个比例条件 x 4 个分布条件 x 1,000 个重复,每个重复给我们 80,000 个模拟。
在这些成千上万的模拟中,我将关注 3 个主要的指标:分类准确率、假阳性率和假阴性率。所讨论的“分类”反映了使用 p 值阈值(通常为 0.05)来确定两个观察值之间的差异是否“真实”的常见频率主义实践——其中“真实”是指被比较的组实际上在感兴趣的度量上存在可测量的差异。虽然我(和许多许多其他人)不一定认为这是好的统计实践[5],但它在学术和商业研究领域都非常突出:在某些时候,你必须遇到他们所在的地方。
由于模拟确定了差异在这个意义上是否是“真实的”,所以这可以转化为一系列(非常大的)2×2 混淆矩阵,查看该值是否被生成为真,以及该过程是否确定其为真。(即(未)调整的 p 值是否低于 0.05)。

图 3:混淆矩阵示例。这是对 100 次比较进行 BH 调整的结果,其中 40%的比较实际上是显著的,真实分布的平均 p 值为 0.0003。
然后,我计算了分类准确度(正确识别的(非)重要关系的数量除以该模拟运行中的测试总数)、假阳性率(错误识别为重要的真正非重要关系的数量除以该运行中非重要关系的总数)和假阴性率(错误识别为非重要的真正肯定关系的数量除以该运行中重要关系的总数)。维基百科对这些措施进行了细分,但是,请注意,描述性的表格有点粗糙。
我应该注意的是,并不是我测试的所有值都特别现实——尤其是与其他值结合起来考虑的时候。很少有人做 10,000 次比较(或者,至少很少有人应该做那么多比较);更少的测试有 10,000 次,其中 40% 的比较是真正不同的,有很大一部分 t 值延伸到 5-6 sigma。如果我曾经看到有人声称拥有这样的数据,我会问他们是否也打算向我出售一座桥梁,或者他们是否来自经销商服务部,就我的汽车的延长保修问题打电话。但是我把它们包括进来是为了测试边界条件:看看这些因素在最极端的情况下如何影响我感兴趣的东西。
结果
分类准确度
下面一组图表显示了关于分类准确性的模拟结果。图表从左到右从最少(10)到最大(10,000)的比较次数进行排序,并从最低(1%)到最高(40%)的真实效果百分比进行排序。因此,更右边的图表显示了具有大量比较的模拟结果,而更靠近底部的图表显示了更大比例的所述比较实际上具有真实结果的结果。每个图表在其 Y 轴上按所查看的 p 值类型排序:未调整的 p 值、Bonferroni 调整的 p 值、Holm 调整的 p 值、Benjamini 和 Hochberg (BH)调整的 p 值以及 Benjamini 和 Yekutieli (BY)调整的 p 值。每个图表的 X 轴反映了分类的准确性,范围从 0 到 1。不同的形状反映了较高/较低的平均 p 值,线条代表了内部 95%模拟值的范围。圆形是平均 p 值 0.04,三角形是 0.02,正方形是 0.005,十字形是 0.0003。然后,在每个图表中,每个形状的位置反映了通过该方法获得的精度,形状本身表达了在真实关系中看到的平均 p 值。(点击放大图)。

图 4:精度上的 P 值调整性能。点反映平均值,线显示 95%模拟的分布。在小于 10%真值的情况下,BH 调整是最好的。在超过 10%真值的情况下,未经调整的 p 值是最好的。
如果看起来有很多事情发生,那就是有。类似于那些图片,你看得越多,你就发现越多,这里有大量的信息编码。这些图表最终揭示了 p 值类型及其分类准确性之间的高度上下文驱动关系。
- 对于具有 5%或更少真实效应的比较集,真实效应的平均 p 值对分类准确性没有特别大的影响。形状都是相互成一直线的;如果有什么突出的,那就是十字架——但这只在几个面板中有争议。在每种情况下,调整后的 p 值的精度都高于未调整的 p 值,尽管当比较次数较少时,精度等级会分散得多。当你达到 10000 点时,价差已经缩小到非常有限的范围。无论你采用哪种 p 值方法,这些组的平均分类排名都非常高:在所有条件下,所有方法的平均得分为 0.97,满分为 1.0。然而,尽管利润极其微薄,但 BH 的调整优于其他公司。
- 对于 10%的效应为真的对比组,故事变得更加复杂。在比较次数较少时,这些值仍然分散,随着比较次数的增加,这些值变得更加集中,但现在我们看到了十字形和其他形状之间的明显区别——至少在调整值的情况下是如此。最有趣的是,这个条件(真实关系的平均 p 值为 0.0003)是唯一一个可以可信地声称表现与未经调整的 p 值一样好或更好的条件——实际上这只是两个错误发现率调整(BH 和 BY)中的一个。
- 在 20%或 40%的影响为真的情况下,调整后的平均 p 值的影响变得更加相关。当平均真实 p 值为 0.04 或 0.02 时,分类准确度与未调整的 p 值显著不同(当 40%为真实效应时,差异显著)。当平均真实 p 值为 0.0003 时,这一点得到了很大的改善,但它没有接近除 BH 之外的所有校正方法的未校正 p 值的分类精度。(即便如此,也比未经调整的 p 值略差)。
对于大多数日常社会科学数据集,你很难让 20-40%的随机测试关系证明是真正重要的。我能想到的唯一例外是,如果这组比较已经通过至少不完整的理论考虑进行了筛选,或者如果所有可用的变量都已经过筛选。在大多数情况下,这些结果表明 p 值调整是最谨慎的,BH FDR 调整表现最好。但如果你有充分的理由认为你测试的比较中有相当多的事实上是显著不同的,那么使用调整后的 p 值通常比使用未调整的 p 值更差(尽管前者的表现随着平均真实 p 值的下降而改善)。
假阳性和假阴性率
虽然分类准确性很重要,但人们使用调整后的 p 值的主要原因是为了减少假阳性的数量,而人们经常拒绝调整的原因是因为假阴性的增加。那么这些策略是如何影响这些利率的呢?
下面这组图表研究了这个问题。和以前一样,图表从左到右按比较次数组织,从上到下按与真实关系的比较比例组织。图表上的点的形状标识了“真实”关系的平均显著性,X 轴编码了度量的分数,范围在 0-1 之间。不过,在这里,我们看到的是两个指标,而不是一个。Y 轴上列出的每个过程分为两种颜色:红色是该指标在该条件下的假阳性率,绿色是其假阴性率。(点击可缩放)。

图 5:假阳性(红色)和假阴性(绿色)率的 P 值调整。点反映平均值,线显示 95%模拟的分布。调整往往会导致较低的假阳性率,但通常会导致较高的假阴性率——尤其是当样本增加时。在这种情况下,未经调整的值更好,但平均“真实”p 值与阈值相差很大的比较集可能会更接近。
这里,各种面板再次揭示了一种高度上下文相关的关系。然而,有几个总体趋势是可以确定的。
- 关于假阳性率,调整程序普遍优于未调整的 p 值。按照设计,未经调整的 p 值将有 5%的假阳性率;调整后的测试倾向于低一个数量级或多一个数量级,其中 BY 调整在该组中具有最佳性能。
- 然而,关于假阴性率,调整程序普遍比未调整的 p 值差。总的来说,大量的测试倾向于提高调整后 p 值的假阴性率,而对未调整的 p 值没有影响。这是有意义的,因为调整后的度量标准的显著性阈值是随着您使用越来越多的测试而变得越来越严格的标准的比较次数的函数。相比之下,未调整的 p 值与测试的功效相关。对于与显著性临界值没有太大差别的真实关系,这意味着有效地将所有真正显著的关系错误分类为不显著。当平均真实 p 值变小时,所有调整程序的性能都有所提高,但性能最好的是那些通过 FDR(即 BH 和 by)而不是 FWER(即 Bonferroni 和 Holm)进行调整的程序,其中 BH 是性能最好的调整程序。
总而言之
对于社会科学数据中最常见的数据和比较类型,调整后的 p 值将提供更高的分类精度。这些方法中表现最好的往往是 BH 和 BY 调整。但是,除非您预期真正显著的比较具有比显著性阈值更小的平均 p 值(即 0.005 或更低),否则随着测试次数的增加,这些方法往往会出现越来越多的假阴性。
调整还是不调整?
那么,你应该选择调整后的还是未调整的 p 值呢?事实上,在数据科学和统计学领域,一切都要视情况而定。在真正有意义的比较比例在 1–5%之间的情况下,调整后的 p 值比未调整的 p 值具有更高的准确率。10%或更高,取决于你真实关系的平均强度。如果您有一堆值徘徊在您的显著性阈值附近,调整后的方法将导致几乎所有的值(如果不是全部的话)被错误分类为非显著性。当平均显著性值明显好于截止值时,这种趋势得到改善。这反映在不同情况下的假阳性和假阴性率上:调整程序的假阳性率极低,但假阴性率很高,除非大多数真值大大低于显著性阈值。当比较的数量不是过多时,调整程序做得最好,我们期望只有少量的比较是真正有意义的。在其他情况下,未经调整的 p 值可能更为谨慎。
这可能会鼓励一些读者认为未经调整的 p 值是正确的选择。从个人经验来看,如果有一条路是你被迫走的,你总是很容易说服自己你的数据符合某些标准。然而,犯错的代价相当高昂:假阳性的噪音比真阳性的信号要大得多。如果只有 5%的关系最终有真实的信号,模拟显示超过一半的标记为重要的关系实际上没有。如果真正重要的关系只占关系的 1%,你可以预期只有 15-20%的“重要”结果是正确的。最糟糕的是,在这两种情况下,你都不知道哪个是真实的,哪个是冒名顶替的。虽然在某些情况下,追求新创新失败的成本可能比走进死胡同更高,但很难证明在走向“真实”之前走进 4-5 个死胡同的成本是合理的,尤其是在无法保证“真实”有足够的杠杆来支付这些成本的情况下。同样,在某些情况下,接受 5 次失误是完全合理的(例如,天使投资者的相对成功率通常比这低),但这不是应该假设的事情。
如果有一些关系在 p 值调整后变得无关紧要,这让你感到惊讶,你总是可以用一个独立的样本单独重试这个关系:那种认为某个东西应该在那里的琐碎感觉可能是由某种理论预设或其他因素决定的。只有将你的期望建立在基础上,使它们变得清晰,并自己测试它们,研究才能得到改进。
也就是说,这项研究确实强烈表明,在某些情况下,即使进行多重比较,未经调整的 p 值也将是更谨慎的选择。这种情况包括当你测试大量的比较时,非常大的比例实际上反映了真实的关系。我必须强调,这样的数据很少。如何确定一个人的数据是否符合这条路线的标准超出了这个项目的范围(毕竟,当您控制关系的重要性时,很容易做出这个决定;在现实世界中,我们不直接拉动杠杆或输入真实值,这有点困难)。然而,我认为一个卓有成效的方法是从比较中的 p 值直方图开始。因为 p 值是均匀分布的,所以在小于 0.05 或 0.10 的值处看到大的峰值会让您感觉到您的数据实际上具有显著的关系。这个峰值相对于其他值的大小(以及峰值是否集中在更接近于 0.05 的值,比如说 0.001)可以帮助查明调整实际上是否是正确的策略。有多种方法可以估计 p 值直方图中“真零”的比例[6]。我目前正计划在未来几个月的某个时候对这个话题进行另一次调查,敬请关注!

图 6:p 值直方图,其中所有值均为空,正态分布(上图),10%的值来自平均 p 值为 0.005 的“真实”效应分布(下图)。后者比 0.05 的值集中得多。
总的来说,如果从这项模拟研究中有什么收获的话,那应该是:处理多重比较是很棘手的,而且取决于具体情况。应根据分析的目标和数据的性质来决定是否使用校正。在进行数十次、数百次或数千次比较时,简单地不调整 p 值并不是一个好主意——最好的解决方案也不是让所有东西都通过 p 值调整的“过滤器”。依我拙见,最好的解决办法是,尽可能地关注最重要的关系。只测试那些你真正感兴趣的和/或对你的用例有帮助的。权衡是做研究不可避免的一部分,但多重比较测试中的假阳性与假阴性问题大多来自于在没有设计的地方应用熟悉的测试范例。统计是神奇的,但不是字面上的神奇。如果我们把它的机制推到远远超出其预期的程度,当它崩溃时,我们不应感到惊讶。
Peter Licari 是一名专门研究美国政治行为的社会数据科学家。他在佛罗里达大学获得了美国政治和政治方法论的博士学位,目前是 Morning Consult 的商业数据科学总监。这里表达的观点是他自己的。也可以在 YouTube 和推特(@ PRLPoliSci)上找到他。剩下的一点点业余时间都花在了长跑、与妻子斯蒂芬妮(Stephanie)畅玩游戏和媒体、与女儿罗莎琳娜(Rosalina)玩耍、遛狗杜德(Dude)以及与他的猫亚洲(Asia)进行奇怪的、富有成效的单边对话上。**
[1]j . p .罗马诺、A. M .谢赫和 m .沃尔夫(未注明日期)。多重检测。**
[2]f . Bretz、t . hot horn 和 p . Westfall(2016 年)。使用 R 的多重比较。查普曼和霍尔/CRC。
[3]罗斯曼,K. J. (1990 年)。多重比较不需要调整。流行病学,43–46。
[4]诺布尔,W. S. (2009 年)。多重测试校正是如何工作的?。《自然生物技术》, 27 (12),1135–1137。
[5]盖尔曼(2013 年)。评论:P 值和统计实践。流行病学, 24 (1),69–72。http://www.jstor.org/stable/41739720
[6]姜,h .,,R. W. (2008 年).估计多重比较的真零假设的比例。癌症信息学, 6 ,25–32。
这是本分析的版本1 . 1 . 0。
我相信所有的作品都得益于读者的建设性反馈以及作者自己的重新审视和思考。但是并不是所有的作品都适合学术出版。为此,为了透明起见,我决定在我的非学术项目上线时,以一种其他人(希望)会接触到它的方式,对它们的要点进行可视化索引。小的修改(如语法或小的图像格式问题)会导致第三位数增加。由我自己的重访启发的大修改和由读者的建议启发的小修改导致第二位数的增加。由读者的建议或未来的反思和对项目的重新审视所驱动的主要修改会导致第一个数字的增加。在 6 个月没有更新之后,一个版本应该被认为是“最终版本”当前版本于 2012 年 4 月 17 日发布。本帖的 changelog 维护在 Github 上。
I-Scores:如何选择在数据集中填充 NAs 的最佳方法
应用 R-packageis cores中实施的插补分数选择最佳插补方法

图 1:显示螺旋上不同插补的原始论文插补分数图。
我们都在数据中遇到过它们:令人讨厌和担忧的缺失值,通常编码为 NA。事实上,在现代数据科学中,缺失值几乎是无法避免的。丢弃含有缺失信息的观测值会导致大量样本丢失,并经常导致数据分析中的偏差。因此,研究人员已经提出了一系列广泛的方法来在任何给定的设置中估算缺失值,例如通过链式方程进行多元估算( mice )或 missForest 等等。
问题是:选择一个好的插补方法
在给定的应用中,如何选择“最佳”插补方法?标准的方法是选择一些观测值,将它们的状态设置为缺失,用不同的方法对它们进行估算,并比较它们的预测精度。也就是说,简单地将估算值与被屏蔽的真实值进行比较。选择的精度度量通常是均方根误差(RMSE)或平均绝对误差(MAE)。对于分类变量,使用正确预测百分比(PCP)。为了选择一种插补方法,将选择总体误差值最低的方法。尽管这种方法很常见,但它有两个主要缺点:
- 人们不得不在某种程度上人为地屏蔽观察结果,这本身就带来了问题,例如选择多少观察结果和屏蔽哪些观察结果。
- 在 RMSE 精确度测量中,使用条件均值估算的方法更受欢迎;在 MAE 中,使用条件中位数估算的方法更受欢迎;在 PCP 中,使用条件模式估算的方法更受欢迎,而不是使用来自真实条件分布的样本。
第二点尤其严重:它可能导致选择“无意义的”估算方法。特别是,所选择的方法往往会人为地加强变量之间的关联。因此,应用于这样估算的数据集的统计估计和推断技术可能是无效的。
一个例子:螺旋的插补
我们用一个简单的例子来说明第二个问题。考虑上图:在最左边的两个图中,您可以看到原始数据集的灰色和黑色点位于一个嘈杂的螺旋上。灰点不包含任何 NA,黑点包含二维中的一个安娜。在最上面一行中,缺失值遵循一个随机缺失的(MAR),在最下面一行中,它们遵循一个完全随机缺失的(MCAR)机制。在这个帖子中,我们将介绍这些机制以及其他与缺失值相关的主题。**
接下来,您可以在两行中看到不同方法的 3 种不同插补:
i) mice.cart 用软件包 mice 进行插补,可从 CRAN 上获得,
ii) sample 从各个维度的观察值中获得每个条目的 NA 值,以及
iii) 黄土插补,通过使用局部回归方法估计完整病例的条件期望 E[X1|X2]和 E[X2|X1],并通过从 X1(如果 X2 缺失)或从 X2(如果缺失)进行预测来填充缺失值
从视觉上看,很容易发现最佳插补是方法鼠标推车的插补。当然,这种视觉排序在更高维度中是不可能的,需要另一种方法来选择最佳插补( I-Scores ,即将推出)。尽管如此,我们还是用这个例子来说明。

图 2:原始论文中的数字,根据三月(灰色)和 MCAR(黑色)的 3 次插补计算出负 RMSE。
如果我们在这 3 个插补上计算 RMSE(通过使用真实的基础值—在实践中不适用!)我们得到了图 2 中的排序,在图 2 中我们绘制了 RMSE 的负值:最好的方法是黄土,其次是样品、,最差的方法是 mice.cart 。这与我们从图 1 中得到的视觉印象明显矛盾。
解决方案:使用插补分数
与通过 RMSE 获得的排名相比,我们希望将 mice.cart 排名最高,因为它准确地再现了真实的底层联合数据分布。同样,黄土严重低估了数据中的变化,应该排在最末。为此,我们在论文插补分数 中开发了一个叫做“插补分数”(I-Scores)的框架。该分数避免了上述与绩效测量(如 RMSE)相关的缺陷:它不需要掩盖额外的观察值,并且通常支持从真实条件分布中取样的插补方法。此外,该方法适用于所有类型的数据(离散的和连续的),并且不需要完整的观测值。
我们在 CRAN 上提供 R-package Iscores 来实现这个分数。它可以很容易地应用于通过对具有 NAs ( X.NA )的数据集应用不同的插补方法获得的插补列表:

与图 2 所示的通过负 RMSE 获得的排名相反,我们观察到以下情况:

图 3:原始论文中的数字,Iscore 是根据三月(灰色)和 MCAR(黑色)的 3 次插补计算得出的。
因此,匹配图 1 中插补的视觉印象,最好的方法是 mice.cart ,其次是样本,最差的方法是黄土。
M 矿石包 Iscores 的使用细节可以在 我们的 github 资源库 中找到。
结论是:明智地选择
面对缺失值的数据集,有无数种插补方法可供选择。然而,关于如何在不同的插补方法中进行选择,几乎没有指导。I-Score 应该为这一选择提供有意义的帮助。
在这个意义上:感谢阅读和快乐得分!
I-Scores:对算法的直觉
原文:https://towardsdatascience.com/i-scores-intuition-about-the-algorithm-56717330077a
关于插补分数算法的一些细节,一种选择最佳插补的方法

根据一些读者的建议,这篇文章提供了关于我们的 I-Scores 算法的更多细节,该算法在早先的帖子中介绍过。
评估插补方法质量的一个常用评分标准是均方根误差(RMSE),它要求知道缺失值背后的真实值,编码为 NA..这在模拟环境中肯定是可行的,但在数据确实缺失的真实场景中却不可行。
与常用的 RMSE 相比,I-Score 看起来几乎不可思议:它不需要访问 NA 背后的真实数据,不需要屏蔽数据(人为地将观察值设置为 NA ),并且在没有完整病例(没有 NA 的观察值)时也有效。尽管如此,它避免了 RMSE 和其他绩效指标遇到的陷阱,即使它们被允许使用真实的基础数据。当然,付出的代价是方法复杂性的增加。尽管如此,尽管实现细节可能很快变得相当复杂,但主要原理非常简单:我们使用kull back-lei bler Divergence(KL-Divergence)将估算分布与数据集的完全观察部分的分布进行比较。如果插补是完美的(好的),则偏差应该为零(接近零),如果插补不好,则偏差应该很大。
让我们依次关注 I-Scores 算法的 3 个主要步骤。下图提供了对它们的直观总结:

作者图片
步骤 1:找到足够多的完整案例
在大多数合理规模的数据集中,很难找到完整的案例。原因很简单:假设数据集中的每个值都随机缺失(完全随机缺失,MCAR ),概率为 5%。不是很多,对吧?但是如果你在 20 个维度上有 500 个观察值,那么完整案例的预期数量只有(1–0.05)⁰*500,大约等于 179。
我们采用一个简单的方法来解决这个问题:随机投影。虽然在 20 维中可能没有太多的完整案例,但在较低维中观察到更多完整案例的机会更大。这就像将几盏聚光灯照射到数据空间的不同部分。当然,这种解决方案是有代价的,因为看投影会丢失信息。但是,应用足够多的投影通常可以解决这个问题。
步骤 2:KL-散度估计
出于我们的目的,我们需要两个密度 f,h 的负 KL 散度的以下表示,其中 f 是完全观察分布的密度,h 是插补分布的密度:

作者图片
获得一般分布的 KL 散度是困难的:我们需要估计观察分布和估算分布之间的密度比(根据上面的等式)。因此,我们使用一个技巧:通过使用分类器来估计完全观察的分布和估算的分布之间的密度比。更具体地说,我们
1)给完全观察的观察值分配标签 1;
2)给估算的观测值分配一个标签 0,
并且估计被标注为 1 的概率,我们姑且称这个估计量为 p(例如我们建议使用随机森林(RF))。这个估计的概率 p 通过 p/(1-p)提供了密度比 f/g 估计,基于此我们获得了 KL 散度的估计。
为什么会这样?想象 p 是完美的估计,即观察到 1 的真实条件概率。它由贝叶斯公式给出为

作者图片
这里我们假设观察到两个类的概率是 1/2。这实际上是在 I-Scores 算法中强制执行的。那么密度比可以写成:

作者图片
因此,很好地近似类别 1 的概率的估计 p 也应该导致密度比的良好估计。
这个巧妙的小技巧实际上已经被使用了很多。它已经在 2003 年被《随机森林》的发明者雇佣,并在甘文学中崭露头角。
第三步:形成一个简洁的单一 I-Score
然后,根据多个随机投影的平均值,从估计的 KL 偏差中构建插补的最终得分。选择分数的方向,使得较高的值表示较好的插补性能。如果有多个插补可供选择,可以对每个插补应用 I-Scores 算法,并选择得分最高的一个。因此,I-Scores 可以指导选择手头数据集的最佳插补。
结论
这篇文章希望能阐明 I-Scores 算法的内部工作原理。
总之,该算法采用数据的随机投影,并在这些投影上,将估算分布 h 与具有估计(负)KL 散度的完全观测分布 f 进行比较。因此,好的插补将得到高值,而坏的插补将得到低值。
在获得分数的过程中,我们使用了一个方便的技巧,即 KL 散度的估计可以从概率估计中获得,这在许多情况下都是有用的。
自然,我们遗漏了一些问题。最重要的一点是,在随机缺失(MAR)情况下,估算分布与完全观测分布 f 不必相同。这是因为在 MAR 下,数据的观察部分可以改变其分布。然而,这是一个有点微妙的话题,我们只是在这里提到,分数仍然有效,即使数据是 MAR。
免责声明和资源: 我们意识到我们遗漏了许多细节,但希望能对算法的直觉有所澄清。 M 矿石的详细算法可以在我们的 论文 中找到,关于 R-package Iscores 的使用指南,连同一个小的说明性例子,可以在我们的github 知识库 中找到。
使用弹性管道扩展 MLOps
通过重试策略提高 SageMaker 管道的弹性
昨天,我使用 SageMaker Pipelines 自动化了一个预测项目的工作流程。我启动了 3 个并发管道执行来在不同的时间范围内训练模型。想象预测一天,一周,一个月。
过了一会儿,2 次执行失败。这是一个简单的培训工作配额问题。

布莱恩·麦高恩在 Unsplash 上的照片
像 MLOps 项目中的所有事情一样,您的管道执行可能会由于各种因素而失败。事实上,建造一条永不失败的管道是不可能的。
在这篇文章中,我将向您展示如何使用重试策略使您的 SageMaker 管道对常见错误更有弹性。
先决条件
要浏览此示例,请确保您具备以下条件:
- 如果您对 SageMaker Pipelines 听起来很陌生,请访问介绍亚马逊 SageMaker Pipelines 。我们将以鲍鱼为例进行说明。
- 我们将在示例 MLOps 管道中使用重试策略。确保您熟悉 AWS 上的超时、重试和带抖动的回退。
每件事都会失败
默认情况下,使用 SageMaker 管道,当管道步骤报告错误时,会导致执行完全失败。
以下是在对具有某些实例类型的鲍鱼管道示例启动执行时可能会出现的错误示例。在这里,我的帐户在培训工作中使用ml.c5n.2xlarge的配额为 1:


作者图片:我的第一个管道执行成功了,而另外两个管道执行了resourcelimitexceed错误和 failed 。
当出现这种错误时,我们是否可以构建能够容忍并降低完全失败概率的管道?在 MLOps 项目中,是否有可能将失败视为一种自然现象?
通过重试策略提高管道弹性
在亚马逊,我们设计系统来降低和容忍失败的可能性。想象一下,即使房子着火了,系统也能继续运行。
你可以在彼得·德桑蒂斯和沃纳·沃格尔斯关于发明的会议中一窥端倪。每年我最喜欢的一些。

图片来自 re:Invent 。失败是必然的,随着时间的推移,一切终将失败——沃纳·威格尔
通常,在管道上应用重试策略是提高管道健壮性的一个好方法。 SageMaker 重试策略有助于在错误发生后自动重试管道步骤。它们可以很方便地应对局部和暂时故障,这些故障通常会在一段时间后自行纠正。
参见重试策略支持的异常类型了解 SageMaker 管道支持的异常的更多详细信息。
在管道中设置重试策略
在管道中添加重试策略很简单
在我们的鲍鱼示例中,我们将使用来自 SageMaker python SDK 的StepRetryPolicy和SageMakerJobStepRetryPolicy。他们将帮助我们减轻我们在培训步骤中看到的ResourceLimitExceeded问题。您可以在 SageMaker 文档中找到关于策略字段和模式的详细信息。
下面是我如何为我的渠道创建策略:
我将策略设置为间隔 30 秒,回退率为 2。这意味着 SageMaker 将在失败 30 秒后尝试启动训练步骤,1 分钟后重试,2 分钟后重试,依此类推。所有这些都有 240 分钟的到期时间,所以我不会以停滞的管道执行结束。
下面是我如何在培训步骤中应用这些策略:
您可以在这里找到示例管道,并根据您的用例随意调整策略。
现在是容易的部分!
您可以使用ml.c5n.2xlarge实例再次启动并发管道执行。



作者提供的图片:现在,其他执行并没有失败,而是等待并重试各自的训练步骤,直到成功。
结论
任何事情都会失败,您的 MLOps 管道也会失败。
在本文中,我们在鲍鱼管道示例中应用了重试策略,并使其对资源限制错误更具弹性。这是一个简单的技巧,您可以在项目中使用它来降低完全执行失败的可能性。
你也可以阅读 MLOps with MLFlow 和亚马逊 SageMaker Pipelines ,了解你可以用 SageMaker MLOps 项目做的其他很酷的事情。
我用人工智能画出了 10 个最美的英文单词
原文:https://towardsdatascience.com/i-used-ai-to-paint-the-10-most-beautiful-english-words-a687bfc33cff
你想看看他们长什么样吗?

在 Shutterstock 上由 Golubovy 拍摄的照片
我正在参与新兴的人工智能驱动的艺术场景。现在你也可以成为艺术家了。
我最近发现了人工智能生成的艺术世界。研究人员和艺术家正在利用人工智能来推动他们的创作。在生成神经网络的帮助下,他们可以创建与数据集中的图像完全不同的新图像。
起初,艺术家不能直接参与他们的创作。GANs 可以生成新图像,但不能接受指令。当 OpenAI 发布 CLIP 的权重时,这种情况发生了变化。 CLIP 是作为一个代表性的神经网络创建的,用于寻找文本描述和图像之间的对应关系。
研究人员利用它的能力来选择与给定图像最匹配的句子。瑞安·默多克第一个,凯瑟琳·克劳森第二个找到了将 CLIP 的技巧与甘的创造力结合起来的方法。CLIP 允许艺术家引导生成(创作)过程。他们可以在文本-图像表示的巨大潜在空间中“搜索”,以找到哪个图像最符合给定的文本提示。
默多克创作了大睡眠 colab 笔记本 (BigGAN+CLIP),克劳森随后创作了更受欢迎的 VQGAN+CLIP 笔记本。仅仅通过提示这些模型——就像我们对 GPT 3 号所做的那样——我们就能创造出触及想象力极限的图片,挑战我们对艺术的理解
用查理·斯内尔的话来说,这是一个“新兴的艺术场景”,他就这个主题写了一篇很棒的文章


提示音:比根+夹子上的[当风吹](http://When the wind blows)(鸣谢:[赖安·默多克](http://Ryan Murdock))和VQGAN+夹子上的一棵树枝变弱的树(鸣谢:凯瑟琳·克劳森)
重新定义艺术性
我搜索了测试这些“promptive”生成模型的方法,并找到了 Wombo 的 web 应用程序 Dream (没有透露他们使用的是哪种架构,但很可能是 CLIP +某种类型的 GAN)。它允许用户生成多达 20 种不同风格的无限图像。生成一幅图像大约需要 10-20 秒,与此同时,你可以看到人工智能如何将绘画从最初的模糊笔触发展到最终的抛光作品。
梦的意象有一种独特的美。与 DALL E 的作品相比,这些图像并没有描绘出定义明确、具体的物体或人物。相反,梦创造了一种具体的感觉,从未离开抽象美的安全。如果你仔细看这些图像,你会失去你认为你从远处看的物体的边界。

提示:村庄(风格玫瑰金)。如果你仔细看,一些以山景为背景的房子变成了一种抽象的混合形式,涂上了暖色调和柔和的颜色。作者在梦里创造的。
在被这个人工智能的绘画的独特性迷住之后,我有了一个想法:我要测试它在语言上的美感。我会让它画出最美的英语单词,而不是提示随机的句子。我搜索了前 10 名,但在意识到美丽不能被一致认可后,我决定把视觉和语言的美丽都考虑进去。(如果你想查其他我没画的漂亮字,这里有出处:语法上和 BuzzFeed 。)
我直接向模型提示了一些单词,没有修饰的提示——那些可以自我解释的。对于其他人,人工智能更难理解,我决定使用他们的定义。CLIP 在数据集中看到的单词越多,它就能更好地表示文本-图像对。像“pluviophile”这样的词很少见,所以 CLIP 还没有形成一个精确的视觉概念。我还根据最符合意思的样式来选择样式。
来自语言美的视觉美
我对 Dream 将单词与非常准确的视觉图像联系起来的能力印象深刻,尽管大多数单词在本质上非常抽象。调整提示和样式让我觉得自己有点像艺术家。我理解为什么现在这么多的数字艺术家使用这些工具——助手——来增强他们的艺术。
人工智能不会取代艺术家,但它肯定会重新定义我们对艺术家这个词的理解。
下面,我为每个单词展示了三幅画,每幅都有不同的风格。字义和形式都很美——这些画很好地诠释了它们。
迷恋:迷恋另一个人的状态



Limerence(风格:活力/神秘/黑暗幻想)。作者在梦里创造的。
摸头。身体锁在一起,跳着性感和裸体的舞蹈。两个人互相看着对方,周围是一个无关紧要的世界。梦捕捉到了理智和爱情之间的微妙区别。在所有三张图片中,一个人在另一个人之上,这可能反映了体验 limerence 的人和它的欲望对象之间的不平等关系。
机缘巧合:事件以有利的方式偶然发生



意外之喜(风格:活力/巴洛克/出处)。作者在梦里创造的。
梦在这里以颜色和形式的混合传达了意外之喜的内在积极性。这是非常有趣的出处风格的图片如何描绘福克,乐观的福龙在永无止境的故事。如果一个虚构的人物能体现出意外之喜,福克肯定是一个很好的候选人。
Petrichor:雨后宜人的泥土气息



彼得里奇(风格:黑暗幻想/通灵/出处)。作者在梦里创造的。
在写这篇文章之前,我从未听说过彼得里奇。然而,我非常清楚它所代表的感觉。中间的图片对我来说是这种感觉的视觉定义。很晚了,你正穿过附近的森林回家。一直在下雨。你可以看到最绿的叶子和尚未过滤到地面的水晶般的水滴。空气中弥漫着一股明显的香味。那是彼得里奇。
孤独:一种隔离或隔绝的状态



孤独(风格:巴洛克/合成波/黑暗幻想)。作者在梦里创造的。
孤独让我想到自己是无限宇宙中的一粒微尘。这是一种艰难但强大的感觉,有一种独特的美。梦在合成波绘画中很好地捕捉了这种感觉。一个人走在似乎是悬崖和大海的附近。她独自一人在深不可测的广阔世界中,但并不孤独:远处,群山、云彩和夕阳都在注视着。
极光:清晨的黎明



极光(风格:synth wave/wuthercuhler/黑暗幻想)。作者在梦里创造的。
Aurora 是其中一个不言自明的词。我们对这个词都有一个非常清晰的视觉表达——如果可能的话,会更漂亮。梦知道极光是什么,它在三幅画中表现出来。星星、夜晚和绿-蓝-紫的组合占据了主导地位。
田园诗般的:极其快乐、和平或如画的



田园风格(风格:奇幻艺术/金玫瑰/wuthercuhler)。作者在梦里创造的。
田园诗般的是一个有着明确含义的词,对我们每个人来说可以有非常不同的形式。在第一张照片中,Dream 描绘了一个宁静的村庄,周围是绿色的田野、远处的群山和一个清澈的湖泊。在第二张照片中,似乎是一艘船在落日的背景下扬帆远航。第三个,一个被鲜花和棕榈树环绕的小房子,可能是一个遥远的天堂。
欣快:极度快乐的感觉或状态



欣快感(风格:黑暗幻想/充满活力/出处)。作者在梦里创造的。
梦代表了强烈融合暖色的欣快感。即使在黑暗幻想风格中,这种风格通常使绘画充满模糊和低能的意象,欣快感战胜了黑暗,给了我们一个非常微妙的描绘,似乎是一个女人包裹在粉红色,紫色和橙色的柔软衣服中。
红杉:一种红杉树



红杉(风格:神秘/合成波/黑暗幻想)。作者在梦里创造的。
用劳伦·比奇的话说,红杉的魅力在于它是“一个七个字母的单词,有字母 Q 和所有五个元音。”作为这个列表中唯一一个具体的单词,Dream 更看重这个单词具体的视觉意义,而不是风格。每幅画仍然反映了风格的特质,但没有失去画面的主角:一个非常高的红色树干,树枝让人想起圣诞树。
缥缈的:极其精致、轻盈,不属于这个世界



缥缈(风格:黑暗幻想/神秘/通灵)。作者在梦里创造的。
我一直认为缥缈是半透明的,有灵性的。现在在这个世界上的东西,但即将离开,因为它不属于这里。Dream 通过透明的服装和柔和的颜色捕捉到了这一概念。画中没有人,更像是半人的幽灵,质地如此之薄,他们似乎在我们眼前消失了。
嗜雨者:雨的爱好者



Pluviophile(风格:黑暗幻想/通灵/充满活力)。作者在梦里创造的。
一个女人正坐在潮湿的地板上。一条粉红色的裙子遮住了她的身体,而整个城市在后面看着她。但是如果你仔细观察,图像中并没有真正的雨。梦把房子的墙壁和窗户变形为细长的形状,就像落下的水滴。尽管如此,我们还是能感觉到这个女人正在享受这场雨,让它充满这一刻。
最终想法
人工智能创造的艺术已经开始存在。正如编码和编写人工智能一样,人们可以利用这些模型的可能性来推动他们的创作。AI 艺术的特殊性在于,绘画本来就是无边无际的。有一些规则,但是一旦你掌握了这些规则,你就可以找到无限的方法去打破它们。
人工智能生成器提供了一波新的工具,将帮助艺术家揭示想象创作的未知形式——比如给最美丽的英语单词赋予视觉意义。
如果你已经读到这里,可以考虑订阅我的免费双周刊 【明天的思想】 !每两周一次关于人工智能和技术的新闻、研究和见解!
您也可以直接支持我的工作,使用我的推荐链接 这里 成为中级会员,获得无限权限!😃
我使用 ChatGPT 在 AWS 上创建了一个完整的 AI 应用程序
原文:https://towardsdatascience.com/i-used-chatgpt-to-create-an-entire-ai-application-on-aws-5b90e34c3d50
这种新的语言模型可能会成为你未来的编程搭档

由作者创建的图像-使用稳定扩散创建
这是怎么回事?
两天前,OpenAI 发布了一个新的语言模型,它是 GPT-3 的改进版本,可能会让我们一窥 GPT-4 在明年年初发布时的能力(据传闻)。使用 ChatGPT,可以与模型进行实际的对话,并返回到之前的对话中。
我想试试我是否可以作为一对程序员使用这个模型,我可以给一些指令,它为我生成代码。当然,我仍然会仔细检查那些代码片段,但至少我不用再从头开始写了。
所以在这篇博文中,我描述了如何使用 ChatGPT 从头开始创建一个简单的情感分析应用程序。该应用程序应该在 EC2 实例上运行,并利用拥抱脸模型中心最先进的自然语言处理模型。结果令人吃惊😮
完整的免责声明——我不得不多次尝试和修改提示,以获得我想要的结果。也就是说,通常只需要很小的改进就能得到想要的结果。我的提示和模型生成的代码可以在这个 GitHub repo 中找到。
为什么这很重要?
你在开玩笑,对吧?
我们开始吧
好了,我们开始吧!我们先来看看 ChatGPT 愿不愿意帮忙:

作者图片
好的,这是一个有希望的开始😊按照同伴的要求,让我们深入细节吧!
为 EC2 实例创建 CloudFormation 模板
我们希望在 EC2 实例上运行这个应用程序,但是我们不想通过 AWS 控制台来创建这个 EC2 实例。因此,ChatGPT 的第一个任务是创建一个 CloudFormation 模板来设置 EC2 实例:

作者图片
说明相当具体(例如,我必须自己查找 AMI ID),但我仍然很惊讶,它竟然提供了一个几乎完美的 CF 模板。请注意,ChatGPT 还在代码末尾添加了一些免责声明:

作者图片
ChatGPT 生成的完整代码:
注意,EC2 实例实际上没有收到我们在提示中指定的名称。但是好吧,让我们暂时接受这个事实。这个模板中的另一个“bug”是 AMI 上预装的 Pytorch 环境被称为“pytorch”而不是“pytorch_36”。让我们通过替换环境名来解决这个问题。
现在,我们实际上是如何操作的呢?我们来问问 ChatGPT:

作者图片
好了,运行这一行开始使用 CF 模板创建堆栈。几分钟后,我们看到 EC2 实例已经启动并运行。请注意,模板创建了一个安全组,EC2 实例使用该安全组,如下所示:

作者图片
让我们通过 SSH 进入 EC2 实例,看看我们需要的包是否已经安装:

作者图片
看起来一切都为 Streamlit 应用程序设置好了😊
简化应用程序
现在,我们需要一个运行在 Streamlit 上的应用程序来分析文本的情感。令我惊讶的是,这比我想象的还要简单:

作者图片

作者图片
再次强调,结尾的免责声明不错。
整个代码:
这实际上对我来说看起来不错,让我们试着不加修改地运行它。将这段代码复制并粘贴到 EC2 上名为“app.py”的文件中。但是我们如何再次运行 Streamlit 应用程序呢?让我们问问我们的“同事”:

作者图片
我们已经安装了 Streamlit,让我们继续运行“streamlit run app.py”:

作者图片
似乎一切都好!
测试应用程序
现在是关键时刻了。我们插入暴露的 URL streamlit,看看应用程序是否运行。

作者图片
哇,ChatGPT 刚刚用我们的指令构建了一个完整的文本情感应用程序🤯
结论
我很无语。这太有趣了,有无限的可能性。我会试着用这个模型做更多的实验,也想听听你用它做了什么。请在下面评论!
我用我的声音和 open ai GPT 3 互动
原文:https://towardsdatascience.com/i-used-my-voice-to-interact-with-openai-gpt-3-884b69dd3b0f
构建一个与 GPT 3 号对话的网络应用

大型语言模型(简称 LLMs),如 PaLM by Google , OpenAI 的 GPT-3 ,威震天-图灵 NLG by 微软-英伟达等。,在生成综合自然语言文本方面取得了显著的成绩。
目前,在这三种模型中,只有 OpenAI 的 GPT-3 可供公众使用,可使用 OpenAI 的 API 进行访问。因此,自发布以来,OpenAI 的 GPT-3 已经在 300 多个应用程序/产品中使用。(来源:此处)。
GPT-3 将文本输入作为提示,并通过一次预测一个标记来执行文本补全任务。GPT-3 的特别之处在于它的建造规模,拥有近 175 个参数。

GPT 3 生成自然语言文本的可视化。(来源:杰伊·阿拉玛)
虽然 GPT-3 背后的核心思想是响应“文本”提示,但集成语音输入应用程序最近也引起了社区的极大兴趣。
因此,在这篇博客中,我们将创建一个 Streamlit 应用程序,通过为它提供基于语音的输入来与 OpenAI GPT-3 进行交互。
文章的亮点如下:
我们开始吧!
应用程序工作流程
如上所述,GPT-3 模型期望文本提示作为输入。然而,如果我们从语音开始,我们首先需要将语音转换为文本,然后将转录的文本作为输入输入到 GPT-3 模型。
为了生成音频转录,我将使用 AssemblyAI 的语音到文本转录 API。
下图展示了该应用程序的高级工作流程:

应用程序的高级工作流(图片由作者提供)
首先,用户将提供语音输入,这将被记录。接下来,我们将把音频文件发送到 AssemblyAI 进行转录。一旦转录的文本准备好并从 AssemblyAI 的服务器中检索出来,我们将使用 OpenAI API 将其作为输入提供给 OpenAI GPT-3 模型。
先决条件
下面列出了创建可与 GPT-3 互动的语音应用程序的一些要求:
排名第一的安装简化版
首先,当我们使用 streamlit 创建这个应用程序时,我们应该使用以下命令安装 Streamlit 库:
#2 安装 OpenAI
接下来,要向 GPT-3 模型发送请求,我们应该安装 OpenAI API,如下所示:
#3 导入依赖关系
接下来,我们导入将在这个项目中使用的 python 库。
#4 获取 AssemblyAI API 令牌
为了利用 AssemblyAI 的转录服务,您应该从 AssemblyAI 网站获得一个 API 访问令牌。让我们为我们的 Streamlit 应用程序命名为assembly_auth_key。
#5 获取 OpenAI API 令牌
最后,要访问 GPT-3 模型并生成文本输出,您应该从 OpenAI 网站获得一个 API 访问令牌。在 OpenAI 中,这被声明为api_key属性,如下所示:
构建 Streamlit 应用程序
一旦我们满足了应用程序的所有先决条件,我们就可以继续构建应用程序了。
为此,我们将定义五个不同的函数。这些是:
**record_audio(file_name)**:顾名思义,这将允许用户向应用程序提供口头输入。该功能将收集音频并将其存储在本地的音频文件中,名为file_name。我已经引用了这个代码来将这个方法集成到应用程序中。**upload_to_assemblyai(file_name)**:该函数将获取音频文件,上传到 AssemblyAI 的服务器,并将文件的 URL 返回为upload_url。**transcribe(upload_url)**:一旦upload_url可用,我们将创建一个 POST 请求来转录音频文件。这将返回transcription_id,它将用于从 AssemblyAI 中获取转录结果。**get_transcription_result(transcription_id)**:为了检索转录的文本,我们将使用从transcribe()方法获得的transcription_id执行一个 GET 请求。该函数将返回转录的文本,我们将把它存储为一个prompt变量。**call_gpt3(prompt)**:最后,这个函数将传递来自用户的提示,并从 GPT-3 模型中检索输出。
方法 2:将音频文件上传到 AssemblyAI
一旦音频文件准备好并保存在本地,我们将把这个文件上传到 AssemblyAI 并获得它的 URL。
然而,在上传文件之前,我们应该声明 AssemblyAI 的头和转录端点。
在上面的代码块中:
upload_endpoint指定 AssemblyAI 的上传服务。- 上传文件后,我们将使用
transcription_endpoint转录音频文件。
upload_to_assemblyai()方法实现如下:
我们用upload_endpoint、headers和音频文件的路径向 AssemblyAI 发出 post 请求(file_path)。我们从收到的 JSON 响应中收集并返回upload_url。
方法 3:转录音频文件
接下来,我们将定义transcribe()方法。
与 upload_to_assemblyai()方法中的 POST 请求相反,这里我们调用了transcription_endpoint,因为目标是转录文件。
该方法为我们的 POST 请求返回transcription_id,我们可以用它来获取转录结果。
方法 4:获取转录结果
这个列表中的第四步是使用 GET 请求从 AssemblyAI 获取转录结果。
为了获取与我们的特定请求相对应的结果,我们应该在 GET 请求中提供从 AssemblyAI 接收到的惟一标识符(transcription_id)。get_transcription_result()方法实现如下:
转录运行时间将根据输入音频的持续时间而变化。因此,我们应该重复发出 GET 请求来检查请求的状态,并在状态变为completed或指示为error时获取结果。在这里,我们返回转录文本(prompt)。
方法 5:向 OpenAI GPT-3 发送提示
最后一个方法将使用 OpenAI API 将提示作为输入发送给 GPT-3 模型。
你可以在这里找到可用的 GPT-3 引擎列表。
集成主方法中的功能
作为我们的 Streamlit 应用程序的最后一步,我们将上面定义的函数集成到main()方法中。
执行应用程序
现在我们已经构建了整个应用程序,是时候运行它了。
打开一个新的终端会话,并导航到工作目录。在这里,执行以下命令:
streamlit run file-name.py
用你的应用文件名替换
file-name.py。

Streamlit 应用程序的界面(图片由作者提供)
演示演练
接下来,让我们快速浏览一下支持 Streamlit 语音的 GPT-3 应用程序。
正如我们在上面看到的,应用程序要求说出提示。在下面的演练中,我向 GPT-3 展示了以下提示:“想象一下地球之外是否存在生命。”

询问 GPT-3 关于地球外生命的存在(Gif 作者)
该应用程序记录音频并将其保存到本地文件中。接下来,它将文件发送到 AssemblyAI 进行转录。最后,转录的文本被发送到 GPT-3,其响应显示在应用程序上。
GPT 3 号对我们的提示的回应是:“这是一个很难回答的问题,因为没有具体的证据表明地球之外存在生命。然而,关于宇宙中生命可能存在的地方有许多可能的理论。”
结论
总之,在这篇文章中,我们使用 AssemblyAI API 和 Streamlit 构建了一个基于语音的交互工具,向 GPT-3 提出问题。
具体来说,我演示了如何获取语音输入,使用 AssemblyAI 将其转换为文本,然后作为提示发送给 GPT-3。
感谢阅读!
🧑💻成为数据科学专家!获取包含 450 多个熊猫、NumPy 和 SQL 问题的免费数据科学掌握工具包。
✉️ 注册我的电子邮件列表 不要错过另一篇关于数据科学指南、技巧和提示、机器学习、SQL、Python 等的文章。Medium 会将我的下一篇文章直接发送到你的收件箱。
我用排队论模拟了迪士尼新的虚拟线路系统
对新的虚拟队列和传统的快速通行系统进行逐分钟的人群模拟比较

图片由作者提供
简介:
2021 年 12 月 8 日,迪士尼乐园首次推出了一个新的虚拟排队系统,用预付费的移动版本取代了原来的纸质 fastpass。这项新服务被称为 Genie+,在公园里每人每天的费用是 15 美元。曾经是免费服务,所有客人都可以利用,现在是高级订阅。这种新方法能让那些付得起额外 15 美元的人过一个更美好的迪士尼日吗?迪士尼乐园是否正式将游客划分为迷你社会阶层?在这一期模拟迪斯尼乐园的节目中,我将尝试回答这些问题以及更多的问题。
构建模拟:
为了简化模拟,想象一个主题公园,游客进入 10 种不同的游乐设施中,从儿童游乐设施到惊险游乐设施。每个游乐设施与公园中心的距离相等,每个游乐设施的客流量相同,均为 1500 人/小时。
现在把所有 16800 个人想象成一个有着严格偏好的个体。这些人中的每一个都被随机赋予了这些品质:
- 类似于人群进出主题公园的预定到达和离开时间。这些时间分布如下所示,T = 480 分钟(上午 8 点)是公园开门的时间,T = 1320 分钟(晚上 10 点)是公园关门的时间。


图片由作者提供
- 使用与之前游乐设备分类相同的等级的游乐设备类型偏好。这种分布如下图所示,一般人享受 7 级,但大多数人享受 10 级。

图片由作者提供
- 停留时间,即乘客等待乘坐的最长时间。分布如下图所示,平均停顿时间为 45 分钟,95%的人停顿时间在 25 到 65 分钟之间。

图片由作者提供
- 不管这个人是否有快速通过的能力,1/3 的公园游客在 Genie+模拟中有这种能力。
当个人选择乘坐时,所有 10 次乘坐都根据当前等待时间、乘坐与个人刺激偏好的匹配程度以及他们是否已经乘坐过来评分。然后,这个人选择一个更好的乘车点,走向它。
当人们到达游乐设施入口时,他们要么排队,要么犹豫不决,离开去寻找另一个游乐设施。这个犹豫的时间对每个人来说都是不同的,就像在现实中一样。如果该人在时间上犹豫不决,并且具有快速通过能力,他们将获得快速通过/闪电通道返回时间,然后加入虚拟队列。当他们等待虚拟的返回时间时,他们也会选择一个新的旅程。这个决策的代码如下所示:
if crowd_data(j,11) > ride_data(crowd_data(j,6),5) % if person thinks wait time is goodwaittime_score = 1/(1+exp(0.05*((round(ride_data(k,1)*60/volume)) - 45))); % Waittime score is sigmoid-based, low wait times = 1, high wait times = 0thrill_score = 1/(1+exp(-0.25*(abs(ride_data(crowd_data(j,6),4) - crowd_data(j,3))-4))); % How similar the ride is to the person's interests, also sigmoid basedif crowd_data(j,crowd_data(j,6) + 11) == 0 % if the person has ridden the ride alreadyvariation_score = 1;elsevariation_score = 0;endcrowd_data(j,25) = 50*waittime_score + 33.33*thrill_score + 33.33*variation_score; % adds the score to the person's cellcrowd_data(j,5) = 3; % indicates waiting in linecrowd_data(j,7) = ride_data(crowd_data(j,6),5); % sets wait time to posted timeride_data(crowd_data(j,6),1) = ride_data(crowd_data(j,6),1) + 1; % increases ride posted wait timeride_data(crowd_data(j,6),5) = round(ride_data(crowd_data(j,6),1)*60/1500); % updates wait time
对于一天中的每一分钟(早上 8 点到晚上 10 点),每个人都有一个行动要做,不管是少排队一分钟,还是少步行一分钟。全天跟踪每个人和骑行的数据。这些数据点包括:
- 犹豫率——当天每个人在等待时间犹豫的次数。
- 使用的快速通道——当天每个人使用快速通道/闪电通道的次数。
- 骑行历史——每个人每次骑行的次数。
- 当前行动——一天中的每一分钟,这个人是在搭车、走路、排队还是在骑车。
- 总等待时间——每个人排队时间的总和。
- 总乘车次数——该人乘车次数的总和。
- 总“乐趣”分数——一个人上车后产生的值,基于他们等待的时间和他们享受该类型乘坐的程度。这些是每个人一天的总结。
- 等待时间-一天中每分钟每次乘坐的当前等待时间。
使用三种不同快速通过系统的这些数据点,我将能够比较每种类型对等待时间和人群情绪的影响。
结果:
我认为最好按照迪士尼乐园使用的顺序来展示每个系统的结果。这意味着从 1955 年到 1999 年的“无快速通道”系统将会先行。
模拟中没有人有快速通过的能力,他们要么选择排队,要么在另一次乘坐中试试运气。该模拟产生了以下结果:
- 平均每人乘坐次数:7.732 次
- 平均等候时间:60.97 分钟
- 平均“有趣”得分:528.68
从 1999 年到 2021 年,经典的纸张快速通过系统被使用。现在,模拟中的每个人都有快速通过的能力。该模拟产生了以下结果:
- 平均每人乘坐次数:8.90 次
- 平均等候时间:52.10 分钟
- 平均“有趣”得分:350.72
最后,从 2021 年开始,新的虚拟快速通行证系统将投入使用。现在,只有那些支付 15 美元的人拥有精灵+能力(大约 1/3 的人群)。该模拟产生了以下结果:
- 平均每人乘坐总次数:8.02 次
- 总平均等候时间:57.45 分钟
- 总平均“乐趣”得分:462.10
现在分为两类,购买了 Genie+的用户和没有购买的用户:
- 精灵+会员平均乘坐次数/人:10.24 次
- 精灵+会员平均等待时间:43.30 分钟
- 精灵+会员平均“有趣”得分:465.18
- 非-精灵+会员平均乘车次数/人:6.90 次
- 非-精灵+会员平均等待时间:68.10 分钟
- 非-精灵+会员平均“乐趣”得分:460.54
分析:
在所有三个类别中,为快速通行特权付费的人之间有明显的差异。与不付费的人相比,付费购买 Genie+的人多体验了 3.34 次乘车,少等了 33.8 分钟,平均多了 4.64 个乐趣点。
虽然这看起来像是一笔 15 美元的好交易,但与最初的纸质快速通行证相比,它实际上增加了每人的总平均等待时间(+5.35 分钟),减少了每人的平均乘车次数(-0.88 次)。然而,与最初的快速通过系统(350.72)相比,Genie+产生了更高的平均乐趣分数 462.10。为什么即使平均等待时间增加了,人们还是会玩得更开心?
这与闪电巷趣味评分分布的偏斜度有关。虽然平均乘坐次数和等待时间对 Genie+来说并不乐观,但有趣的分数分布远远偏向右侧,这意味着闪电车道允许那些为 Genie+付费的人获得大量有趣的分数。缺点是,与其他两个系统相比,拥有平均乐趣分数的人更少。

图片由作者提供
虽然这是闪电车道的一个优点,但它无法与 fastpass 之前的时代(1955 年至 1999 年)的有趣分数相比。考虑到除了在必要时排队等候之外没有其他选择,无快速通过模拟的有趣分数(528.68)令人难以置信。我想,当没有一条看不见的线在他们前面占座时,人们会更快乐。
下面的图代表每个人在一天中的每一分钟在公园里做什么(左=没有快速通行证,中=经典快速通行证,右=精灵+)。



图片由作者提供
这些显示了基于快速通行证系统类型的人群决策的差异。非快速通行系统在下午 5 点达到等待高峰,88%的人在排队,经典快速通行系统在下午 6:30 达到等待高峰,87%的人在排队,而 Genie+在下午 4:30 达到等待高峰,91%的人在排队。
这意味着当前的 Genie+系统使等待时间更快减少,公园在晚上 8:30 达到 50%的排队率(相比之下,非快速通行证系统的排队率为晚上 9:15)。
快速通道存在的主要原因之一是这些系统可以更均匀地分散整个公园的人群。通过找出一天中每分钟等待时间的变化,我可以看到所有三个系统的人群分布情况。下图显示了一天中所有 10 次乘坐等待时间的变化(左=无快速通过,中=经典快速通过,右=精灵+)。方差越小越好,这意味着所有的等待时间越相似。



图片由作者提供
非快速通过系统(左)的平均等待时间方差为 3.41,这意味着等待时间与当天的平均值相差 3.41 分钟。接近一天开始和结束时的变化峰值是由于个人在各自的时间到达和离开公园。当差异在一天当中达到最低点时,公园就达到了平衡状态。对于非快速通过系统,这个平衡期发生在上午 11 点—下午 4:30。
经典快速通过系统(中间)的平均等待时间方差为 3.44。它的平衡状态发生在上午 11 点—下午 5 点。
Genie+系统(右)的平均等待时间方差为 4.04。它的平衡状态发生在上午 11 点—下午 4:30,就像非快速通过系统一样。
在疏散人群方面,非快速通行证系统的效果最好,当天的平均方差最低(3.41)。然而,就最长的平衡周期而言,经典的快速通过系统在 6 小时的周期内效果最好。也许这个公园的人群在分散他们自己方面做得很好,不需要虚拟的队列来为他们做这件事。
结论:
回顾所提供的统计数据和图表,可以清楚地看到,传统的快速通过系统产生了最佳的等待时间(平均 52.10 分钟。分钟)和乘坐次数(平均 8.90 次。乘坐),这是真正的非快速通行证系统给了人群最好的迪士尼日(平均 528.68)。好玩点)。
迪士尼有没有正式把它的客人分成迷你社会阶层?如果我们看看那些为 Genie+付费的人和那些没有付费的人之间的差异,我们会发现额外的 3.34 次平均乘坐和 33.8 分钟的排队时间为双方提供了巨大的差距。如果有一些好处,如等待时间的总体减少,这可以被认为是可以接受的,但模拟表明,它使等待时间增加了 5 分钟,实际上增加了队列长度的变化。
那么,如果 Genie+没有公园收益,那么迪士尼为什么要在他们的公园中实施这一新系统呢?答案很简单——美元。对于这种规模的公园(10 个游乐设施,每天最多 16,800 名游客),公园收入每天将增加 84,000 美元。对于这个假想的公园来说,这意味着每年增加超过 3000 万美元的收入,这个公园的大小大约是迪斯尼乐园的 1/3。迪士尼乐园也是目前使用 Genie+的四个公园之一。难怪迪士尼在 2022 年第一季度实现了空前的营收和营业收入记录。
所以答案是肯定的,现在在迪斯尼乐园有两个截然不同的社会阶层。那些付得起每人每天 15 美元(一个 5 口之家 5 天假期 375 美元)的人,和那些付不起的人。这是不是意味着所有的快速传球都是不好的?不。经典的快速通行证系统并不掠夺家庭度假者,而是给那些更聪明地利用时间的人带来优势,而不管他们的预算或团队规模。
不幸的是,模拟显示迪士尼带着 Genie+走向了错误的方向。
如果你对这种类型的内容感兴趣,我建议你阅读我在迪士尼乐园模拟中的其他作品:
我在 2 年内写了 472 篇数据科学文章。以下是我的三点建议。
在许多方面,我已经成为一名更好的数据科学家

劳伦·曼克在 Unsplash 上的照片
2020 年 1 月 28 日,我写了第一篇文章。从那时起,我已经在媒体上发表了 472 篇文章,你现在正在阅读第 473 篇。
我最初的目标是通过展示我的知识和技能来支持求职过程。过了一段时间,结果是一场旁门左道。除了金钱上的回报,学习新的工具和概念是我继续写作的另一个动力。
如果你正在从事或计划从事数据科学方面的工作,请做好持续学习的准备。数据科学仍在不断发展,新的工具或概念也在不断推出。因此,有强烈的学习动机是值得的。
在这两年的写作之旅中,我学到了很多工具和话题。我坚信这让我在许多不同的方面成为了一名更好的数据科学家。
在这篇文章中,我想分享我在这段旅程中的 3 点收获。我认为,如果你对写数据科学或任何其他主题有热情,它们会对你有所帮助。
学得很好
你一定听过或读过好几遍,学习一件事的最好方法是教它。我自己也经历过。
我总是通过记笔记来学习,所以写作学习一直是我生活中的一部分。即使我看书是为了学东西,我也尽量用自己的话写自己理解的东西。
然而,为自己写作和为别人写作是截然不同的两件事。
当你为了向观众解释一个概念而写作时,它必须清晰简洁。句子应该按照逻辑顺序来建立一个结构。否则,读者可能会在文章中途迷失方向。
如果你给出太多的细节,你可能也会失去读者。此外,细节有时会增加理解主题的难度。另一方面,没有给出足够的细节会导致无法解释主题。
最后但同样重要的是,你需要非常小心,不要犯错误。当你在自己的笔记中犯了一个错误时,受影响的只有你一个人。然而,当你在发布的内容中犯了一个错误,你就会误导你的整个观众。
在你的写作中,要满足所有这些标准,只有一个办法:把它学得很好。
把它学得很好,这样你就把犯错误的几率降到最低,把理解题目的读者数量最大化。
这是我经历过的。当我继续写一个工具、一个概念或一个理论时,我意识到我学得很好,并开始掌握它。
一致性
两年内坚持不懈地频繁写作需要相当多的时间、精力和努力。
一开始似乎很难。我需要在晚上或周末抽出时间写作,因为我还有工作。
然而,在坚持写作一段时间后,它变成了一种习惯。我开始喜欢写作。看到越来越多的追随者,观点和阅读是一种祝福。
此外,它还帮助我培养了自律。
如果你打算在数据科学领域工作,请做好持续学习的准备,因为数据科学仍然是一个不断发展的领域。随着我自律能力的提高,我越来越容易坚持学习。因此,我在工作中表现得更好。
自律的好处很容易扩展到生活中的其他领域。例如,我在按时完成事情方面变得更好了。我过去常常拖延,但是知道我每天都需要抽出时间来写作,这激励我按时完成事情。我变成了一个更有系统的人。
网络
我敢肯定,大多数决定转行并从事数据科学工作的专业人士都面临着同样的挑战:很难找到你的第一份工作。
主要原因是没有先前的工作经验来证明你在这个领域的技能和知识。
建立一个从事数据科学工作的专业人员网络是克服这一挑战的有效途径。但是,不在域内,建立一个网络并不容易。
持续写作两年帮助我建立了一个读者群。我在 Medium 和 LinkedIn 上有很多粉丝。我的文章在社交媒体上被非常受欢迎的出版物分享,如《走向数据科学》。
当我回顾两年前的自己时,我看到了一个有抱负的数据科学家,他非常努力地想获得一次工作面试。现在招聘人员向我寻求工作机会。
我认为,在数据科学生态系统中拥有一群专业人士有助于我得到招聘人员和招聘经理的注意。
创建具有良好质量水平的一致内容有助于建立受众,这是建立网络的有效方式。
创建数据科学博客是我做过的最好的事情之一。它给我提供了很多好处和机会。它开始是为了支持我找工作,但后来变成了一种激情。
如果你在媒体或其他地方写作,让我们知道你的动机和回报。
坚持写作是一项艰巨的任务,我也很想听听你是如何坚持写作的。
你可以成为 媒介会员 解锁我的全部写作权限,外加其余媒介。如果你已经是了,别忘了订阅https://sonery.medium.com/subscribe如果你想在我发表新文章时收到电子邮件。
*https://sonery.medium.com/membership
感谢您的阅读。如果您有任何反馈,请告诉我。*
IBM 数据科学认证:课程 1 —什么是数据科学?
我最近总结了我完成 IBM 数据科学认证的经验(在这里阅读!收到了大量令人惊叹的反馈,有些人希望了解每门课程的更多信息。我决定将这 10 门课程逐一分解,并讨论这些课程中讨论的重要概念和定义。我的目标是提供课程之外的信息,这些信息有助于强化你对课程创建者设定的目标的了解。虽然这些出版物不一定会给你所有的答案,但它们将为完成数据科学专业化和你自己的职业生涯的进一步发展提供基础知识。
免责声明:我与 IBM 没有任何关系或财务激励。

课程 1:什么是数据科学?
IBM 数据认证课程 1 旨在向学生介绍什么是数据科学,以及与数据科学相关的一些重要概念。本课程不是编码密集型的,而是向用户提供定义和信息,以便他们能够了解数据科学家使用的工具。本课程从解释数据科学以及如何成为数据科学家开始,以数据科学的实际应用以及向客户展示数据的报告结构结束。
第一周
本周的学习目标旨在理解三个概念:
- 什么是数据科学?
- 通向数据科学职业的道路
- 数据科学的建议?
什么是数据科学?
数据科学有很多定义。
一个跨学科领域,使用科学方法、流程、算法和系统从嘈杂的结构化和非结构化数据中提取知识和见解,并在广泛的应用领域中应用来自数据的知识和可操作的见解
“数据科学是一个过程,而不是一个事件。这是使用数据来理解不同事物、理解世界的过程。”—“数据科学是揭示隐藏在数据背后的洞察力和趋势的艺术。当你把数据转化成故事的时候。所以用讲故事来产生洞察力。有了这些见解,你就可以为一家公司或一家机构做出战略选择。”
“数据科学是一种多学科方法,用于从当今组织收集和创建的大量且不断增长的数据中提取可行的见解。数据科学包括为分析和处理准备数据,执行高级数据分析,并呈现结果以揭示模式,使利益相关者能够得出明智的结论。”
虽然数据科学可以有许多含义,但主要的要点是数据科学包含许多不同的学科(实际上任何行业都可以利用数据科学工具)。比如说。我的背景是中文的经济学本科学位和运筹学硕士学位。
通向数据科学职业的道路
成为数据科学家有很多途径。无论您是自学还是参加数据科学课程,每个人都有机会利用数据科学为客户做出贡献。据 careerkarma 报道,以下行业目前为进入数据科学职业生涯提供了最大的支持:
- 金融服务
- 信息技术
- 卫生保健
- 零售
- 媒体/娱乐
数据科学家的建议
课程建议:
最优秀的数据科学家表现出的特征是那些好奇、问好问题、善于处理非结构化情况的人。
我的建议是:
虽然我还处于数据科学职业生涯的早期。我的建议是不断向前推进,学习、阅读和研究关于数据科学(人工智能、机器学习等)的一切。此外,我还不断尝试会见成功的数据科学家,询问他们建议我继续做些什么。最后,我不断联系企业,看看他们是否有任何需要数据科学方法的项目,以获得更多经验,并建立更广泛的项目组合。
第二周
第 2 周的学习目标旨在理解三个概念:
- 大数据及其特征(数量、速度、准确性、价值)
- 大数据工具(课程重点是 Hadoop)
- 成为数据科学家以及处理大数据所需的技能
大数据
定义(牛津):
可以通过计算分析来揭示模式、趋势和关联的极大数据集,尤其是与人类行为和互动相关的数据集。
来自 TechTarget 的以下定义有助于理解大数据的 V:
量:给定数据集内存在的数据量。
速度:数据生成和跨平台移动的速度。
准确性:数据集中存在的质量和准确性的数量(例如,是否有许多缺失的条目?).
价值:大数据能给一个组织带来的价值。
大数据工具
Apache Hadoop : —来自网站:
Apache Hadoop 软件库是一个框架,允许使用简单的编程模型在计算机集群上分布式处理大型数据集。它旨在从单个服务器扩展到数千台机器,每台机器都提供本地计算和存储。该库本身不是依靠硬件来提供高可用性,而是设计为检测和处理应用层的故障,因此在一个计算机集群上提供高可用性服务,每个计算机都可能容易出现故障。
MongoDB —来自网站:
借助基于领先的现代数据库构建的应用数据平台,让您的想法更快进入市场。支持交易、搜索、分析和移动用例,同时使用通用查询界面和开发人员喜欢的数据模型。
阿帕奇·卡珊德拉——来自网站:
Apache Cassandra 是一个开源的 NoSQL 分布式数据库,因其可伸缩性和高可用性而受到成千上万公司的信任,同时又不影响性能。商用硬件或云基础设施上的线性可扩展性和经验证的容错能力使其成为任务关键型数据的完美平台。
Cloudera —来自网站:
在整个企业中构建数据驱动的文化不再需要增加影响业务敏捷性的复杂性层。随着数据的不断增长和分发,企业必须让员工能够方便地访问做出正确决策所需的数据。必不可少。因为数据流向哪里,想法就跟到哪里。
OpenRefine —来自网站:
OpenRefine(以前的 Google Refine)是一个处理杂乱数据的强大工具:清理它;将它从一种格式转换成另一种格式;并用 web 服务和外部数据来扩展它。
OpenRefine 始终将您的数据保密在您自己的计算机上,直到您想要共享或协作。除非您希望,否则您的私人数据永远不会离开您的计算机。(它的工作原理是在你的电脑上运行一个小服务器,你用你的网络浏览器与它互动)
数据科学技能
课程讨论的技能:
- 如何进行数据挖掘?
正如 zipreporting 所概述的,数据科学流程有 7 个步骤:
- 数据清理 ~清理数据,使其可以在正在使用的程序中读取(填写缺失的条目等)。
- 数据集成 ~将不同类型的数据和数据集组合在一起。
- 为了数据质量而减少数据 ~通过丢弃不重要的特征和信息来减少数据集的大小。
- 数据转换 ~根据分析,数据可能需要转换(例如,在回归分析中对数据集使用了对数转换)。
- 数据挖掘~组织收集大数据并提取潜在模式的应用程序的实施。
- 模式评估 ~主题专家和数据科学家对数据集模式的讨论和见解收集。
- 表示数据挖掘中的知识 ~使用可视化、报告和演示来告知客户通过数据挖掘发现的信息。
- 如何使用机器学习。
有许多方法可以学习如何在数据集上使用和实现机器学习技术。我的建议是,在使用机器学习算法之前,先学习它们背后的数学知识。一旦你知道一个特定的算法是做什么的,我建议你选择一种编码语言并坚持使用它。例如,我用 Python 编写代码,一些主要的 Python 机器学习库有:
- TensorFlow ~适用于编程机器学习算法,这些算法将在给定组织的日常工作流程中发挥作用。
- py torch~对机器学习算法的动态编程有用。
- 用于实现分类、回归和聚类机器学习算法。
- Keras~一个对实现机器学习算法有用的 API,比如神经网络和随机森林。
- 自然语言工具包(NLTK) ~对自然语言处理(NLP)有用。
- 熊猫 ~用于创建数据框和操作数据集中的数据。
- OpenCv ~对计算机视觉项目有用。
- 如何使用深度学习。
学习如何实际理解和实现深度学习绝对不在本文讨论范围之内。深度学习的定义,由维基百科定义,是使用人工神经网络(或神经网络的变体)进行表征学习。这可以包括监督、半监督和非监督学习任务。主要思想是深度学习使用神经网络。
第三周
第 3 周的学习目标旨在理解三个概念:
- 数据科学的应用(本课程特别关注医疗保健)
- 公司如何开始数据科学之旅
- 消费者数据生成
数据科学的应用
数据科学在各个行业都有很多应用。课程中给出的例子包括
- 亚马逊、网飞、Spotify、谷歌~推荐系统
- Siri ~通过使用数据科学创造答案
- Fitbits,苹果手表,Andriod 手表~生物特征追踪和分析
消费者生成的数据
课程中对这一目标的研究是为了让学生理解,公司用于训练机器学习算法的大量数据是由消费者产生的。消费者生成的数据包括:
- 搜索历史
- 订单历史
- 屏幕上的时间
- 广告点击
- 来自可穿戴设备的数据
报表结构
第 3 周还提供了数据科学报告的布局。
- 封面 ~标题、作者姓名、作者所属机构、作者联系人、出版商、出版日期
- 目录 ~章节页码、章节页码、表格列表、图表列表
- 摘要 ~提供所实施技术的摘要以及研究的主要结论
4.方法论部分 ~为读者提供数据集组织、过程和使用的研究方法。
5.结果部分 ~主要发现
6.讨论部分 ~说说结果和它们的意义。
7.结论部分 ~重申分析的重要发现,回答之前陈述的任何问题,并为未来的研究提供建议。
8.附录 ~补充信息
虽然报告的结构会因项目而异,但在为您的研究创建最终文档时,这是一个很好的通用指南!
结论
作为 IBM 数据科学认证十门课程中的第一门,这门课程在为用户提供背景信息方面做得非常好。了解数据科学的背景很重要,这样用户就知道为什么他们可能会在课程的后面实现某种机器学习算法。我强烈推荐任何希望对什么是数据科学有一个总体了解,并了解哪些公司在日常运营中实施数据科学的人学习本课程。
如果你喜欢今天的阅读,请关注我,并告诉我你是否还有其他想让我探讨的话题!另外,在LinkedIn上加我,或者随时联系!感谢阅读!
IBM 数据科学认证评论
原文:https://towardsdatascience.com/ibm-data-science-certification-review-970c2d85a039
Coursera 上的 IBM 数据科学认证(专业化)课程为那些希望扩展其分析技能的人提供了一个很好的数据科学介绍。本课程介绍了数据科学中的各种主题,包括如何正确显示和可视化数据,以用 Python 构建高性能的机器学习模型。这个专业不仅对以前从未见过这些主题的初学者来说很棒,而且对希望验证和建立他们当前技能的更高级用户来说也很棒。
免责声明:我与 IBM 没有任何从属关系或财务激励。

概观
许多人虽然在分析技能方面很有能力,但希望扩大他们对各种数据科学技术的理解。虽然完成数据科学硕士或博士学位是一项令人钦佩的事业,但申请并完成数据科学认证是另一个出色的选择,它不会迫使你在磨练数据科学技能的同时倾家荡产。
在完成运筹学理学硕士(MS)的同时,我决定参加 Coursera 上提供的 IBM 数据科学认证课程,以便在我的教育任务中使用这些技能。该课程预计需要大约 10 个月的时间,而我能够在大约 5 个月内完成(我承认我以前上过数据科学课程,并接受过关于每门课程中讨论的数学概念的更深入的教育)。我从这门课中学到的两点。
- 对于初学者来说,这是一个很好的入门课程,尤其是对于那些害怕 Python 编码并远离它的人。
- 对于有经验的人来说,这是一门很好的课程,但是,对于更高级的用户来说,有些课程可能感觉太容易了,而且课程中的编码练习可能不足以提高更高级的数据科学家的编码能力。
总分: 7.8/10
主要优点:课程的集合非常有助于以可控的速度缓慢进步和微调学生的技能。最初的几门课程主要解释数据科学领域及其内容,而后面的课程更具技术性,涉及学生使用 Python 编码语言应用各种数据科学技术。
许多编码实践仅仅给出了答案和代码。虽然从技术上讲,这不是一个编码密集型专业,但我相信,如果将认证时间延长到一年,增加一门关于编码的课程,并在编码模块中提供更少的信息,这样学生就必须实际学习并找出每种技术的正确代码,会更有好处。那些在数据科学方面更先进的人可能希望有一个更密集的编码课程,迫使他们从零开始,并教授更高级的编码方法。
课程 1:什么是数据科学?
在本课程的第 1 周,学生将了解数据科学包含的内容以及数据科学家的方法和实践。在第 2 周,学生将学习数据科学的一些主要领域,如大数据、数据挖掘、深度学习和机器学习。本课程的最后一周向学生讲授商业中的数据科学。这门课程最大的优点是它很好地给出了数据科学的总体情况。最大的缺点(如果有的话)是,如果学生已经具备中等的技术背景和对所提到的数据科学技术的理解,他们可能会发现本课程没有必要,而且由于信息冗余而令人厌烦。
课程 2:数据科学工具
本课程从概述计算机程序员(在数据科学中)常用的不同编码语言开始。这些语言包括 Python、R 和 SQL(虽然有些人在技术上不认为 SQL 是一种语言,但本课程介绍了 API)。在向学生介绍了不同的语言之后,该课程将教授他们不同的云计算服务、应用编程接口(API)、数据集和机器学习模型的类型。在课程的第 2 周,学生将接触到各种开源材料,包括 Jupyter Notebook、JupyterLab、RStudio 和 GitHub。在第 3 周,该课程帮助学生浏览 IBM 为数据科学提供的工具。课程的大部分内容涉及在 IBM 的在线编码平台沃森工作室中使用 Jupyter 笔记本。最后,在第 4 周,学生将学习如何在 Watson Studio 中实现和查看 Jupyter 笔记本。
课程 3:数据科学方法
在本课程中,学生首先学习如何发现(或给出)一个问题,并寻找解决该问题的要求。在理解了问题的要求之后,学生就被引入了收集的概念:为解决问题而收集信息。一旦收集完成,该课程进一步教学生如何评估和验证模型的有效性,这是为利益相关者创建产品时的一个重要概念。最后,本课程概述了部署模型和接收模型反馈的方法,无论是来自利益相关者还是内置的模型评估功能。
data_falcon9**.**loc[:,'FlightNumber'] **=** list(range(1, data_falcon9**.**shape[0]**+**1))
data_falcon9
上面的代码是本课程的一个片段,其中要求学生对之前在数据框中收集的 Falcon9 发射进行排序。
课程 4:用于数据科学、人工智能和开发的 Python
本课程首先向学生介绍 Python 编程的基础。其中一些基础知识包括通过转换或转换字符串、浮点和整数等数据类型来理解 Python 中的类型,通过应用数学运算来解释变量和求解表达式,以及在 JupyterLab 中构建程序。在第二周,学生将学习列表和元组,字典和集合。Python 编程的基础将在第 3 周介绍。这些基础包括条件和分支、循环、函数、异常处理以及对象和类。第 4 周展示了使用 open 读写文件,以及使用 Python 库 Pandas 和 Numpy。最后,在第 5 周,除了 REST APIs、web 抓取技术(即使用 BeautifulSoup),并处理 HTML 文件。
课程 5:面向数据科学的 Python 项目
在课程的这一点上,学生将已经(主要)了解了 python 编码语言,并期望完成一个包含数据科学的 Python 项目。虽然这听起来有点吓人,但我之前提到过,课堂上已经提供了很多代码,学生只需要填写空白。虽然这有助于完成课程,但努力从头学习代码比获得代码更有利于学习(本书更深入地讨论了学习概念)。在本课程中,你需要制作一个简单的仪表板。仪表板在可视化数据集(或多个数据集)的不同参数如何交互方面非常有用。对于学生来说,学习如何制作一个 Plotly 仪表板是很重要的,因为当他们被雇佣时,利益相关者会要求他们提供许多可交付成果。 Plotly 是一个伟大的 Python 工具,对于数据科学家来说是一个非常有用的 API。

图片:仪表板中的示例图表(图片来自作者)
课程 6:使用 Python 的数据科学的数据库和 SQL
对我来说,这个课程是最有益的,因为我以前没有使用 SQL 的经验。课程 6 通过 Jupyter 笔记本教你使用 SQL 平台。学生将学习一些常见的命令,例如查询所有的列名,根据特定的标准查找特定的实例。课程的第一周向学生介绍 SQL,并让学生对数据库有更深入的了解。对于 SQL 来说,数据库是关系型的,这意味着不同的数据库有对应于其他数据帧中某些特性的条目。第 2 周揭示了关系数据库,如何创建关系数据库,并提供了使用关系数据库的练习。第 3 周将教学生如何使用字符串和范围集来解析和组合数据库中的不同数据点。最后,在第 4 周,学生将在 Jupyter 笔记本上磨练他们的技能,并使用 python 完成一个小型 SQL 练习。
**%sql** select landing__outcome from SPACEXTBL;
上面是用于在数据库中显示 SpaceX 发射的所有着陆结果的代码示例。
课程 7:用 Python 实现数据可视化
认证的课程 7 非常重要,因为虽然数据科学家必须能够进行适当的分析,但无法将分析直观地呈现给利益相关者可能最终会破坏项目的成功。第一周介绍数据可视化,包括如何创建简单的图(散点图和线图)。本课程中使用的两个主要 Python 库是 Pandas 和 Matplotlib 。在第 2 周,重点转向创建面积图、直方图、条形图和箱线图。我必须创建的一个图是一个线形图,用于检查一段时间内成功发布的趋势

图片:成功发布的趋势示例(图片来自作者)
此外,还提供了更多练习来理解和创建散点图和气泡图。在第三周,随着华夫饼图表和单词云的引入,课程开始变得更加专业。本周介绍另一个高级 Python 可视化库 seaborn 。在这一周中,学生不仅将学习如何绘制回归图,还将介绍叶子图。对于任何使用地理空间数据并希望绘制地理空间数据的人来说,leav map plot 是一种非常好的方法。

图片:树叶地图示例(图片来自作者)
第 4 周是前 3 周的高潮,让学生使用 Plotly Dash 创建一个带有视觉效果的仪表板。每当你为利益相关者做项目时,我推荐你为这个课程做书签作为参考,因为它为决策者创建最终项目提供了很好的指导。
课程 8:用 Python 进行机器学习
课程 8 为那些想了解更多关于用 Python 实现机器学习(ML)模型的人提供了很好的介绍。第一周介绍了机器学习的基础知识,包括监督学习和非监督学习的区别,以及一些 ML 通用算法。在第二周,学生学习如何进行和实施回归分析。本周探索的不同类型的回归模型包括线性、非线性、简单、多重和多项式回归。第三周介绍不同类型的机器学习分类模型,包括 KNN、决策树、逻辑回归和支持向量机。在最后一周,向学生介绍聚类算法。学生将探索的三种算法是基于分区的、分层的和基于密度的聚类。对于课程 9 中的每个模型,学生还将学习模型评估。例如,他们必须找到每个模型的混淆矩阵,以确定模型的适当性(如下例)。

图片:逻辑回归模型混淆矩阵的例子(图片来自作者)
课程 9:应用数据科学顶点
认证的最后一门课程是我最喜欢的课程之一,因为我可以将我在过去 8 门课程中学到的所有技能用于实际应用。在本课程中,学生将获得 SpaceX 发射的数据集。有各种各样的特征与数据集相关联,其中发射失败和成功是项目中主要的突出特征。学生将使用 SQL 进行探索性分析,查看数据集的不同属性,并收集关于 SpaceX 发射城市的不同统计数据。在找到与发射场相关的成功和失败之后,学生们将使用 python 创建一个 follow 地图,以查看发射场的地理位置以及它们附近的物理特征。一旦学生了解了发射场的位置及其成功率,他们将使用机器学习来创建不同的预测模型(KNN、SVM、逻辑回归、随机森林)。最后,学生们将为他们的发现创建一个 Plotly 仪表板,SpaceX 的工作人员可以使用它来了解每个单独发射及其相应站点的相互作用。创建控制面板后,学生有机会为利益相关者创建完整的演示文稿,这是一堂宝贵的课,尤其是对于那些在受雇为数据科学家时必须演示和辩护其工作的人来说。
结论
无论您计划从另一个劳动部门进入数据科学领域,还是只想学习新技能以进行更深入的分析,我都强烈建议您完成 IBM 数据科学技能发展专业化认证。对于初学者来说,专业化提供了各种数据科学技术的概述,并慢慢向更具技术性的分析方法前进。对于更高级的用户来说,该课程可以帮助他们重新建立任何已经生疏的技能,同时也可以让他们了解一些在学习该课程之前可能不知道的技术。报名参加该课程绝对让我受益匪浅,我觉得该课程提高了我的数据科学技能!
如果你喜欢今天的阅读,请关注我,并告诉我你是否还有其他想让我探讨的话题!另外,在LinkedIn上加我,或者随时联系!感谢阅读!
等幂写入 Delta Lake 表
原文:https://towardsdatascience.com/idempotent-writes-to-delta-lake-tables-96f49addd4aa
使用开源三角洲湖的演练

https://unsplash.com/photos/JI0KxozvOtQ
介绍
根据维基百科:
幂等性是数学和计算机科学中某些运算的属性,由此它们可以被多次应用而不改变最初应用之后的结果。
一些非技术性的例子是电梯呼叫按钮和人行横道按钮。在许多情况下,拥有一个幂等的软件 API 是一个关键的必备特性。其中一种情况是 Spark 结构化流。默认情况下,结构化流使用检查点和预写日志来保证一次性容错。当使用foreachBatch API 时,这并不适用。
这个 API 非常强大,通常需要在流数据上应用 Spark SQL 批处理 API,或者将输出写入多个目的地,或者调用像MERGE INTO这样的 API 来更新下游增量表。这种方法的主要问题是,如果流在foreachBatch中失败或中断,目标表中就有可能出现重复。在本帖中,我们将会看到这种情况(并不罕见)会产生什么影响,以及如何处理它。
幸运的是,Delta Lake已经完全开源,因此很容易理解像 delta lake 幂等表写这样的某个特性是如何实现的,以及它的限制是什么。
环境设置
这应该很容易,我们需要的只是 delta lake 的最新预览版和 python 3.7+虚拟环境。
- 您可以使用 Mac/Linux/Windows,但是在 Windows 上最好使用 WSL2。这里会有 Spark,你不想浪费时间在 wintuils 和其他步骤上让 Spark 在 Windows 上运行。
- 使用您喜欢的环境管理工具创建一个新的 Python 环境并激活它。
- 运行
pip install delta-spark==2.0.0rc1。 - 运行
pyspark --version确认您的安装,它应该显示 3.2.1 ,因为它与delta-spark捆绑在一起。
快乐的场景
我们都喜欢快乐的场景!至少看到代码按预期工作并产生一些结果感觉很好。让我们构建一些基本的 Spark 结构化流设置。源将是一个包含 10 次提交的增量表,其中每次提交都是一个文件。目的地是另一个增量表,但写入将使用foreachBatch API 完成,而不是作为传统的增量流接收器。
复制下面要点的内容,保存为producer.py。
生产者脚本执行以下操作:
- 从一些导入开始,准备一个文件夹来保存增量表和流检查点。
- 创建一个火花会议与三角洲湖依赖连线给我们。
- 创建一些虚拟数据帧,并将其附加到名为 source 的增量表位置。这个过程重复 10 次,每次追加(提交)有 10 条记录,将保存在一个文件中,因此称为
repartition(1)。之所以这样设计,是因为流应用程序将被配置为每个微批处理提取一个数据文件(一对一映射到提交),只是为了让事情更容易理解。总之,我们的源表有 100 条记录,分成 10 个文件。 - 脚本的最后一部分回显了一些关于写入数据的指标和细节,以确认它处于下一步的良好状态。
在上一步的同一个 Python 环境中,运行python ./producer.py,您应该会得到如下结果。

作者图片
现在,创建另一个名为consumer.py的文件,并用下面的要点填充它。
这个文件对制作人来说是硬币的另一面:
- 它从经典导入开始,并创建一个 Spark 会话。
- 然后,它定义了
foreachBatchAPI 回调函数,该函数简单地打印批处理 Id,回显微批处理的内容,最后将其附加到目标增量表中。这是可以使用的基本逻辑。打印批次 Id 和微批次仅用于说明目的。
提示: 你在微批上做的任何火花动作都会触发整个血统。因此,如果要在函数中多次使用微批处理,缓存它可能会很有用。
- 脚本中的下一部分是流查询:
-从使用 producer 脚本准备的增量表中读取数据流。
-通过使用maxFilesPerTrigger
从每个微批次的源中提取一个单个拼花数据文件-设置检查点位置并连接foreachBatch
的处理程序-触发持续时间为 2 秒的流并运行它最多 30 秒,然后继续下一步。如果我们使用一个与 Spark 3.3 兼容的 delta lake(至少对于 OSS delta 来说现在不是这种情况),我们可以使用流式触发availableNow。这将使我们不必指定超时,并且它仍然会考虑其他选项,如maxFilesPerTrigger。这里的假设是,在大多数机器上,所有的源表文件将在不到 30 秒的时间内得到处理。 - 最后,打印目标表中的记录数,猜猜会发生什么?那应该能印 100,除非什么东西真的坏了。
现在运行python ./consumer.py,让我们看看这是否真的是一个快乐的场景😅。预期的结果将如下所示。

作者图片
正如您所看到的,每个微批次的 Id 和内容都被打印出来,目标表的最终记录计数为 100,证明一切都按预期运行。

“不那么”快乐的场景!
好了,是时候让事情崩溃了。要重新开始,您可以删除检查点和目标表的文件夹,但更简单的方法是再次运行生成器,这将清理所有相关的文件夹,包括检查点和目标表。
这次我们将模拟写完微批号 5 后直接发生错误的事情。将消费者文件中的batch_function代码修改如下:
def batch_function(df, batch_id): print(f"Processing micro-batch {batch_id}") df.show() df.write.mode("append").format("delta").save(target) if batch_id == 5: raise RuntimeError("Simulated exception!")
在写入批次 Id 5 的数据后,最后两行引发一个伪异常。这将导致流崩溃,无法写入检查点文件。实际上,这模拟了未记录或跟踪的对目标系统的影响。现在让我们试一试。
python ./consumer.py

作者图片
不出所料,程序崩溃了,但我们想确认数据是否已写入目标。您可以运行一个独立的 PySpark shell 会话来验证这一点。
pyspark --packages io.delta:delta-core_2.12:2.0.0rc1 --conf "spark.sql.extensions=io.delta.sql.DeltaSparkSessionExtension" --conf "spark.sql.catalog.spark_catalog=org.apache.spark.sql.delta.catalog.DeltaCatalog"

记录的数量是 60,这是正确的,因为我们从批次 Id 0 开始,批次 Id 5 的内容在目标表中,所以发生了写操作。
现在想象一下,如果我们移除模拟异常并重新运行消费者应用程序,会发生什么!
流检查点文件夹没有任何关于批次 Id 5 的持久化数据,因此流将在批次 Id 5 处恢复,这显然会在目标增量表中产生重复数据。
因此,删除模拟的异常行并重新运行消费者。流应该从批处理 Id 5 开始,目标表中的记录数将是 110 而不是 100,这表明数据损坏(在我们的例子中是重复)。

作者图片
幂等写拯救!
因为这是一个非常常见的用例,因为流重启是不可避免的,所以 delta lake 团队想出了一个解决方案,在从流写入 delta 表时,这个解决方案非常有效。请记住这一点,因为它不是一个通用的解决方案,所以例如,当写入普通的拼花文件时,使用它是没有意义的。
无论如何,因为 delta lake 最近的开源,所有这些先进的 delta lake 功能现在都可以在 GitHub 上获得;我们可以检查源代码,甚至在尝试之前就理解它是如何工作的。
核心解决方案围绕两个选项,这两个选项可用于流式查询。第一个是txnAppId,它保存了应用程序写入目标增量表的 Id。这是必要的,因为增量表可以有多个并行写入的流,并且每个流都可以是独立的不同应用程序。另一个选项txnVersion是一个序列号,表示正在写入的数据的应用版本。在foreachBatch的情况下,这通常是批次 Id。这两个选项的伪代码如下:
txnVersion = options.get("txnVersion")
txnAppId = options.get("txnAppId")
接下来,如果接收到的版本以前在增量表中出现过(对于同一个 appId),writer 类将简单地跳过写入。增量表事务日志不需要存储所有的历史版本,只需要每个应用 Id 的最新版本就足够了。我稍后会谈到这一点。那个逻辑的伪代码是这样的。
def hasTransactionBeenExecutedBefore(table, txnVersion, txnAppId) latestSeenVersion = table.getLatestSeenVersionForApp(txnAppId) if (latestSeenVersion >= txnVersion) return true
else
return false
现在让我们重新开始:
- 运行生产者
- 更新消费者文件中的
df.write功能,使.write后有.option("txnAppId", "idempotent_app").option("txnVersion", batch_id)。 - 运行带有模拟异常的使用者。
该程序应该如预期的那样失败,并且微批次 5 将被写入目标增量表。现在可以检查的是目标表的增量日志文件。
在您喜欢的文本编辑器中打开任何一个目标增量表提交文件,如/tmp/idempotent-writes/target/_delta_log/00000000000000000003.json。

第一行是一个名为事务标识符的 delta lake 协议动作。正如你所看到的,它包含了我们作为一个选项发送的固定申请 Id 和一个交易版本,即批次 Id。Delta lake 在后台收集所有这些 json 文件(有时还有另一个名为 checkpoint 的 parquet 文件,它在每 10 次提交后被写入)来构成 Delta 表状态的快照视图。在我们的例子中,状态具有由每个应用程序 Id 写入的最新版本,在我们的例子中是单个应用程序。因此,在下一次写入时;delta lake 可以确定它之前是否看到过相同的写尝试,并基于此可以继续或跳过它。
理论够了!
移除模拟异常,并再次运行使用者。
嘣!目标表有 100 条与源表相同的记录,您甚至可以检查目标表,确保没有重复的记录(id & group 列可以充当复合 PK)

这个概念简单又酷,可能在非流式场景中也很有用。
交易标识符操作将始终记录在增量日志 JSON 或 parquet 文件中。正如我提到的,delta-lake 不需要提取完整的版本列表来实现这个逻辑。只有每个应用程序 Id 的最新条目就足够了。例如,在目标表上执行 100 个微批处理后,txn操作如何出现在增量日志中。

总结和警告
当使用 DataFrameWriter 从foreachBatch API 写入 delta lake 表时,这个特性是非常必要的。没有它,重复可能会发生,他们可能很难发现,因为他们只会悄悄地发生。
记得清理你的测试数据。
rm -r /tmp/idempotent-writes
警告是:
- 此功能仅在 DataFrameWriter API 中可用。它不能用来(a̵s̵ ̵o̵f̵ ̵n̵o̵w̵见下面的更新)运行类似
MERGE INTO的命令。类似地,如果写给 parquet 或 JDBC,你需要自己解决幂等问题。 - 从文档中复制一个警告:
如果删除流式检查点并用新的检查点重新开始查询,则必须提供不同的
appId;否则,来自重新启动的查询的写入将被忽略,因为它将包含相同的txnAppId,并且批处理 ID 将从 0 开始。
当您尝试重新开始时,上述情况适用,但是如果重新创建目标增量表,您选择只删除现有记录。该表在数据方面将是空的,但事务日志仍将具有先前流运行的痕迹,因此将跳过之前看到的所有批处理 id。
在上述情况下,如果您将日志记录级别更改为 INFO,您将看到如下消息,指示微批处理被跳过。
22/07/03 21:36:51 INFO WriteIntoDelta: Transaction write of version 0 for application id idempotent_app has already been committed in Delta table id d5e0a924-683a-45a0-a299-718b89409b6a. Skipping this write.
这并不是一件坏事,特别是当它发生在恢复失败的流应用的正常情况下,使得最后提交的微批处理不会被重新写入目标增量表时。
希望您现在对如何使用增量表中的幂等写来使您的流工作负载对瞬时故障具有鲁棒性有所了解。
【2023 年 1 月更新:
Deltalake 版本 2.0.2 扩展了对所有 DML 操作的支持。奇怪的是,在 2.2.0 中没有找到同样的支持。看看这个要点就一窥端倪。OSS deltlake 2.0.2 提供该功能。我已经在 Databricks DBR 12.0 上试过了,效果很好,甚至可以在之前 DBR 的 Databricks 上使用。
快乐流媒体!
确定 Power BI 中的数据加载瓶颈
原文:https://towardsdatascience.com/identify-data-loading-bottlenecks-in-power-bi-4a436355558c
数据刷新过程缓慢?查看本文,了解数据刷新工作流中最常见的瓶颈!

作者图片
性能、性能、性能…我们一直努力使我们的(Power) BI 解决方案更加高效!我已经写了很多关于如何在多种场景下提高性能的文章——例如,通过减少整体数据模型大小,或者通过利用查询折叠特性,或者通过优化报告中使用的可视化。
还有,不要误会,当然知道如何修复问题超级重要!然而,最具挑战性的部分往往是找出问题所在!
免责声明: 本文将涵盖 识别 数据加载瓶颈的主题,而另一篇文章将涵盖性能改进实现技巧和技术
与表现不佳的报告前端(暴露给用户的区域)不同,如果报告呈现缓慢,你可以打赌有人会抱怨,在数据加载和数据刷新方面,你不能依赖最终用户警告你。简单地说,他们不知道幕后发生了什么,可能的情况是,数据刷新过程需要几个小时,而报告的工作速度超级快。
了解数据刷新过程
在我们解释如何识别瓶颈之前,我们先来看看 Power BI 中的数据刷新流程:

作者图片
请务必记住,此工作流仅适用于导入模式,因为与 DirectQuery 一样,中间没有 PowerQuery 层,没有数据加载到为 Power BI 报告提供服务的 Analysis Services 实例中,因为 Power BI 生成的查询将直接针对数据源。
启动数据加载流程后,让我们简要解释一下工作流程的所有阶段:
- 存储 Power BI 模型数据的 Analysis Services 实例将运行一个查询,或者在大多数实际情况下,运行多个查询来检索数据。Power Query 是物理存储在 Analysis Services 实例中的数据和物理存储在 Power BI 外部的数据(即 SQL 数据库、Excel 文件、Azure 数据湖、Azure Synapse 等)之间的逻辑层。)
- Power Query 会将查询转发给 Power BI 之外的各种数据源
- 各种数据源会将数据发送回 Power Query
- Power Query 将对数据应用必要的转换(如果有的话),并将最终数据推回 Analysis Services 数据库
正如你可能已经注意到的,在上面的插图中,有三个不同的“灰色地带”。这三个区域中的每一个都可能是性能问题的根源。那没多大帮助,对吗?
那么,让我们看看如何确定数据刷新过程缓慢背后的主要原因。
我听到了,我听到了:我们可以使用 Power BI 服务中的刷新历史来确定数据刷新是否有问题。的确如此,但它只显示了总刷新时间,没有提供更细粒度的视图。这可能足以敲响警钟并吸引您的注意力,但它无助于关注数据刷新过程的哪一部分。
Power BI Premium 通过 Power BI Premium 容量指标应用程序提供更精细的概述:

作者图片
电源查询性能故障排除
如果您比较 Power BI Desktop 和 Power BI Service,您可能会发现,对于相同的数据,数据刷新过程有时会以非常不同的速度运行。根据超级查询查询运行的位置,性能可能会有很大差异。
如果您使用 Power BI Desktop 来开发报告,Power Query 查询将在您的计算机上本地运行。如果您只连接到云资源,那么 Power Query 查询将在 Power BI 服务中运行。
最后,从故障排除的角度来看这很重要, 如果您连接到本地数据源,您的 Power Query 查询将在托管 Power BI 数据网关的机器上运行!为什么这很重要?嗯,因为它在数据刷新过程中引入了另一个潜在的瓶颈:安装网关的机器。
记住所有这些,超级查询性能可能取决于查询本身的复杂性和效率,但也取决于运行这些查询的机器的硬件规格(CPU 和 RAM)。
优秀的旧 SQL Server 事件探查器可助您一臂之力
由于 Power BI 将数据存储在 Analysis Services 表格数据库的实例中,因此我们可以利用一个很好的旧 SQL Server Profiler 来获取有关数据刷新过程中由 Analysis Services 启动的不同事件的最细粒度的信息。
由于我现在在本地机器上测试性能,一旦我获取了本地主机端口信息,我就可以使用 SQL Server Profiler 来收集数据刷新背后的各种指标。我将选择命令开始、命令结束、查询开始和查询结束事件:

作者图片
当我在 Power BI 桌面上点击“刷新”时,Profiler 将捕获一系列在后台发生的不同事件。一旦数据刷新结束,您应该能够看到如下内容:

作者图片
这里显示了许多有用的信息,比如查看运行时间最长的对象、以毫秒为单位的数据刷新总持续时间(在我们的例子中,大约花费了 76 秒)、CPU 花费的总时间,以及在后台生成的 XMLA 查询。
SQL Server Profiler 是对数据刷新过程进行故障排除的最可靠的方法,直到最近,它还是我检查数据刷新期间是否存在任何瓶颈的首选方法。
你可能想知道——为什么直到最近。有没有更好或者更方便的方法来达到同样的目的?
借助 Phil Seamark 的数据刷新流程可视化提升专业水平
在排除数据刷新过程的故障时,这是专业级别。我非常喜欢它,在我的 Power BI 性能调优研讨会上,我教人们如何使用它。它功能强大、健壮、超级直观,我可以向您保证,它将极大地简化故障排除工作流程。
这是怎么回事?这种方法的核心是 SQL Server Profiler,但这次我们将把 SQL Server Profiler 日志保存到一个跟踪 XML 文件中,然后使用 Power BI 可视化整个过程!
以下是 Phil Seamark 博客上原始帖子的链接,在这里您可以找到分步指南和 PBIX 文件,您可以将它们用作自己的故障诊断任务的模板:

作者图片
在事件选择下,选择作业图表和进度报告结束:

作者图片
一旦数据刷新过程完成,并且您将 XML 跟踪日志数据导入到 Power BI 中,您应该能够看到如下内容:

按作者分类的图片:根据您的表格/查询,结果可能会有所不同
这太棒了。我可以立即看到一堆有用的信息,帮助我快速了解潜在的瓶颈在哪里。这里有一点很重要:这个 pbix 文件不是一根魔杖,它会自动为你提供一个解决数据刷新缓慢的方法。它将为您提供所有相关信息,但接下来要由您来寻找优化刷新性能的方法(如果有的话)。
例如,您可能会看到表 X 占用了总数据刷新过程时间的 90%以上。但是,可能发生的情况是,表 X 非常大,只需要更多的时间来刷新。所以,不一定说明那张桌子有问题。
执行 SQL 表示从发出查询到从数据源返回第一批行的时间,而 Process 显示 Analysis Services 处理数据和执行各种任务所需的时间,如数据压缩。这两个指标之间的差异可能会给你一些启发——如果蓝色条很长,你可能想尝试优化数据源的性能(下一篇文章中有更多关于优化技巧和技术的内容)。
另一方面,长的黄色条可能是由安装本地网关的机器的物理限制,或者在本地网关机器上执行的复杂电源查询转换造成的。
结论
识别潜在瓶颈是数据刷新流程优化中最具挑战性的任务之一。在您可以卷起袖子投入到应用性能优化工具箱中的一种(或多种)技术之前,理解工作流的各个领域和“关键”点是至关重要的。
在本文中,您了解了对执行缓慢的数据刷新过程进行故障排除的不同方法。请继续关注,在下一篇文章中,我们将对此展开讨论,我将与您分享多种优化技巧和诀窍,让您的数据刷新过程更顺畅、更快速。
如果你有兴趣了解这个话题,我强烈建议你观看这个精彩的克里斯·韦伯在 SQL Bits 的演讲。
感谢阅读!
神经网络训练的良好实践:识别、保存和记录最佳模型
提示和技巧
神经网络训练的良好实践:识别、保存和记录最佳模型
介绍训练神经网络时最佳模型保存的良好实践,以及使用 fastai 和权重&偏差的实际实现
本文原载于我的 个人博客
在这篇文章中,我们将:
- 介绍最佳模式的概念
- 讨论如何在培训期间识别、保存和记录最佳模型
- 探索如何利用 fastai 和 Weights & Biases(几乎)自动且毫不费力地完成所有这些工作
什么是“最佳模式”,我为什么要关心?
模型训练可以被看作是模型的后续版本的生成——在每一批之后,模型权重被调整,并且作为结果,模型的新版本被创建。每个新版本都将具有不同的性能水平(根据验证集进行评估)。
如果一切顺利,训练和验证损失将随着训练时期的数量而减少。然而,模型的最佳执行版本(这里缩写为最佳模型)很少是在训练过程结束时获得的版本。

过度拟合训练曲线的一个典型例子(图片由作者提供)
举一个典型的过度拟合案例——首先,随着训练的进行,训练和验证损失都在减少。在某一点上,验证损失可能开始增加,即使训练损失继续减少;从这一点开始,在训练过程中产生的后续模型版本会过度拟合训练数据。这些模型版本不太可能很好地推广到看不见的数据。在这种情况下,最佳模型将是在验证损失开始偏离时获得的模型。
过度拟合是一个方便的例子,但是类似的观察也适用于模型训练的其他动态,例如局部最大值或最小值的存在。
在训练期间识别并保存最佳模型
解决该问题的简单方法是在训练期间的每个时期之后保存我们的模型,并基于训练曲线回顾性地选择最佳版本。这种方法有几个明显的缺点:
- 存储空间:大型模型在保存时往往会占用大量的存储空间,文件大小会达到数百 MB 到 GBs。用这个乘以历元数,你最终会得到一个相当大的存储空间,专门用来保存一个模型的所有版本。这很快就会成为问题,尤其是在远程训练模型时。
- 计算影响:在每个历元之后保存一个模型将会影响整个训练时间——一些模型的序列化/导出可能计算量大且速度慢。

天真的方法是持久化所有模型版本,而最好的方法是只持久化最近表现最好的模型(图片由作者提供)
作为这种在训练期间保存所有模型版本的强力方法的替代,可以更有选择性。从上面我们知道,我们的最佳模型很可能与低验证损失相关联。因此,我们可以为选择我们的最佳模型制定一个标准:它必须比前一个候选模型具有更低的验证损失。作为伪代码:
if current validation loss lower than candidate validation loss: save model to disk overwriting previous candidate
set candidate validation loss to current validation loss
这种方法的主要优点是:a)只有当验证损失比之前的最佳候选模型有所改善时,才导出新的模型;b)在任何给定时间,我们只有一个模型版本保存到存储中。因此,我们成功地解决了简单方法的两个缺点。
也许更重要的是,仅保存最佳模型还通过要求在培训开始之前决定性能评估方法来鼓励良好的实践,并且它消除了在单独的测试数据集上追溯评估模型的其他版本的诱惑。
关于验证损失、替代指标和模型文档的注释
到目前为止,我们使用验证损失作为我们的目标度量,以在训练期间识别最佳模型。你可能会问,为什么会有验证损失?事实上,它几乎总是在训练期间计算,这使得它成为一个方便的例子来说明本文中讨论的概念。
然而,验证损失可能与您的特定用例或领域不相关,可以使用任何其他度量来代替。对于分类任务,准确性可能是一个不错的选择。同样,您可以选择目标指标,以确保最佳模型也能很好地推广到看不见的数据,例如在处理严重不平衡的数据集时使用 Matthews 相关系数。
无论您决定使用什么样的目标度量,记录模型的这个特定版本的其他方面也是很重要的。通常,这将包括培训期间跟踪的所有绩效指标。将这些信息与实际的模型工件保存在一起在以后会非常有用,例如,对从超参数搜索中获得的模型进行排序,或者在产品中部署时执行集成测试(在以后的文章中会有更多关于这方面的内容!).
使用 fastai 轻松保存训练期间的最佳模型
最佳模型保存的实现需要改变训练循环,以便监控目标度量并在检测到改进时触发模型保存。许多现代框架都内置了这种能力。这里,我们将重点关注 fastai 实现,但是类似的功能也可能适用于您选择的库。您仍然可以了解如何在实践中实现这一点。
如果你不知道 fastai 是什么,它的官方描述是 :
fastai 使用现代最佳实践简化了快速准确神经网络的训练
可以使用在训练的特定阶段调用的回调方法来修改和扩展 fastai 训练循环,例如在一个时期完成之后,或者在训练结束时。方便的是, SaveModelCallback 恰好(几乎)做了我们需要的事情。
使用回调再简单不过了:python
learner.fit_one_cycle(... ,cbs=[..., SaveModelCallback(monitor='valid_loss')])
其中learner是标准 fastai 学习对象。默认情况下,回调将跟踪验证损失,以确定何时保存新的最佳模型。使用monitor参数将其设置为由您的learner对象跟踪的任何其他指标。在训练期间的每个时期之后,将目标度量的当前值与先前的最佳值进行比较——如果是改进,则将模型保存在models目录中(并覆盖先前的最佳候选,如果存在的话)。
在幕后,回调试图判断改进是较小的值(如果目标度量包含loss或error)还是较大的值(其他的)。使用comp参数可以覆盖这种行为。该模型使用 fastai 的[save_model](https://docs.fast.ai/learner.html#Learner.save)函数持久化,该函数是 Pytorch 本机[torch.save](https://pytorch.org/docs/stable/generated/torch.save.html)的包装器。
内置回调不是我们所需要的的原因是,它只会记录用于识别最佳模型的目标度量,而不会记录其他任何东西。它不会记录其他指标(例如准确性,如果最佳模型是基于验证损失确定的)。这可能没问题,但是考虑到我们的最佳模型可能会在某个地方被用作产品的一部分,尽可能多地描述它的特征是一个好主意。我组装了一个定制版本的SaveModelCallback,它将记录 fastai 在培训期间跟踪的所有指标。的代码可以在这里找到。
这种定制版本的回调可以作为替代。它真正做的只是在内部跟踪与最佳模型相关联的度量字典(last_saved_metadata)。如何利用这一点?一切将在下一节揭晓!
自动记录带有权重和偏差的最佳模型
在本地保存最佳模型是一个好的开始,但是如果你远程工作,或者进行大量的实验,它会很快变得难以操作。那么如何跟踪所创建的模型,以及它们相关的度量标准呢?这就是权重&偏差的来源。B 是其中一种工具,它让你想知道没有它们你怎么能正常工作。虽然官方将其描述为“开发者至上的 MLOps 平台”,但我更愿意称之为 MLOps 的瑞士军刀。
W&B 对于跟踪和比较实验非常有用。然而,出于本文的目的,我们主要对它几乎通用的版本控制能力感兴趣。在 W&B 生态系统中,工件是可以被版本化的组件,可能连同它们的血统一起。模型可以被版本化为工件。
方便的是,fastai 有一个内置的回调函数来与 W&B 集成,它被恰当地命名为[WandbCallback](https://docs.fast.ai/callback.wandb.html#WandbCallback)。要使用它,需要初始化一个 W & B 运行,并向学习者对象添加回调,如下所示:
# Import W&B packageimport wandb# Initialize W&B run (can potentially set project name, run name, etc...)wandb.init()# Add Callback to learner to track training metrics and log best modelslearn = learner(..., cbs=WandbCallback())
回电的主要目的是将有关培训过程的有用遥测记录到您的 W&B 帐户,包括环境信息和指标。当它与SaveModelCallback结合使用时,神奇的事情发生了——在训练过程结束时,表现最好的模型将自动记录为 W & B 运行的工件。
默认的WandbCallback有一个主要问题:与模型相关的元数据是在运行结束时记录的,而不是在保存最佳模型的时候。换句话说,元数据与保存的模型根本不对应,并且可能是误导性的(例如,当跟踪的度量由于过度拟合而在训练接近结束时偏离)。
这就是上一节讨论的定制SaveModelCallback的用武之地。它将保存将模型与其实际元数据相关联所需的所有信息。为了利用这一点,还需要使用一个定制版本的WandbCallback,可以在这里找到。
这里突出显示了在自定义回调中所做的更改:
def after_fit(self): if self.log_model: if self.save_model.last_saved_path is None: print('WandbCallback could not retrieve a model to upload') else: log_model(self.save_model.last_saved_path, metadata=self.save_model.last_saved_metadata) for metadata_key in self.save_model.last_saved_metadata: wandb.run.summary[f'best_{metadata_key}'] = self.save_model.last_saved_metadata[metadata_key]
因此,将自动发生以下情况:
- 记录到 W&B 运行中的模型与包含正确度量值的元数据相关联
- 将最佳模型的所有指标值添加到运行总结中,前缀为
best_。这允许根据各自最佳模型的性能对运行进行分类和比较

记录权重和偏差的最佳模型。(左)包括关键指标的模型元数据;(右)W&B 项目中的模型按与其各自的最佳模型相关的***best_matthews_corrcoef***元数据排序(图片由作者提供)
包扎
那么,我们从这篇文章中学到了什么?
- 只有在培训期间保存最佳模式才是有效的,并鼓励良好的实践
- 元数据,包括与最佳模型相关的关键指标,几乎与模型工件本身一样重要
- 使用 fastai 和 Weights & Biases,可以自动保存和记录最佳模型。描述了两个定制回调函数来使这个过程更好( SaveModelCallback 和 WandbCallback )。
使用 SQL 识别 bot 流量
原文:https://towardsdatascience.com/identifying-bot-traffic-with-sql-3327c893bdb2

菲利普·格利克曼的照片
使用 SQL 识别 bot 流量
Web 数据通常包括某种类型的 bot 流量。无论是自动化测试用户还是不需要的 web 抓取者,在使用数据进行报告或分析之前,您都需要将他们从数据中删除。
这个 SQL 查询是系统地识别非人类浏览行为的一种快速而简单的方法,因此您可以花更少的时间清理数据,而花更多的时间做有趣的事情。
你需要什么数据
您将需要一个数据集来表示用户在一段时间内的每个页面视图(或类似的浏览行为,如点击)。这个查询是使用针对亚马逊红移仓库和来自 T2 段的基本页面视图数据的语法编写的,但是您可以很容易地将它应用于任何页面视图数据源和仓库组合。
SQL 查询
SELECT
v.anonymous_id
FROM (
SELECT
p.message_id
, p.anonymous_id
, p.timestamp
, LAG(p.timestamp) over
(PARTITION BY p.anonymous_id ORDER BY p.timestamp ASC)
as last_pageview_ts
FROM website.pages p
) vWHERE date_diff('minute', v.last_pageview_ts, v.timestamp) < 30
and v.last_pageview_ts is not nullGROUP BY 1HAVING
avg(date_diff('ms', v.last_pageview_ts, v.timestamp)) <= 1000
and COUNT(1) > 10
它是如何工作的
在一个高层次上,这个查询使用页面视图数据来查找以非常快的速度查看了许多页面(> 10 个页面)的访问者(< 1 second between views on average). Viewing that many pages so quickly is a strong indication that the viewer is not human and should be removed from the data before analysis.
We defined these thresholds using our common sense about what was and wasn’t 正常的用户行为)。如果你想客观一点,我在最后提供了一些提示,告诉你如何使用你的数据来设定阈值。
我们来分解一下
步骤 1 —计算每次页面浏览之间的时间
LAG(p.timestamp)
over (PARTITION BY p.anonymous_id
ORDER BY p.timestamp ASC) as last_pageview_ts
步骤 2 —排除来自不同会话的任何连续页面视图
WHERE date_diff('minute', v.last_pageview_ts, v.timestamp) < 30
最终,我们将需要计算每次页面浏览之间的平均时间,如果我们包括会话之间的时间,我们将严重扭曲我们的平均值。通过删除这些记录,我们可以确保我们只计算同一个会话中页面浏览之间的时间。
步骤 3 —计算每个用户每次页面查看之间的平均时间,并仅过滤查看之间的平均时间为< 1 秒(1000 毫秒)的用户
avg(date_diff('ms', v.last_pageview_ts, v.timestamp)) <1000
第 4 步 —排除页面浏览量少于 10 次的人
COUNT(1) > 10
该查询将返回一个查看了 10 个以上页面的用户列表,平均每个页面查看时间不到 1 秒。最好抽查这些用户及其页面查看行为,以确保所选的 anonymous_id 看起来像机器人。
提示:调整平均浏览量和总浏览量的过滤器,使算法更具限制性。
一旦你调整了这些参数,你的机器人用户列表就准备好了!
就是这样!
最后一步是从所有建模的表中删除它们。讲述人可以很容易地排除用户的动态列表,所以你不必担心他们偷偷进入你的数据集。了解更多
好处:使用数据选择好的阈值
在这一节,我将分享一些使用现有行为数据选择一个好的阈值的技巧。这些方法是不受监督的,这意味着我们没有一个带标签的数据集来告诉我们哪个用户实际上是机器人,哪个不是,所以我们不会建立一些欺诈检测模型。这些只是简单的试探法,你可以用来选择一个更明智的阈值。
目标是确定与你网站上的其他用户相比,什么应该被认为是异常的行为。行为会因您的站点而异,因此在选择阈值之前,我们将查看这些指标的分布,以确定哪些是正常的,哪些是不正常的。
页面查看之间的时间阈值
下面的 SQL 使用 Redshift 中的 PERCENTILE_CONT() 函数来计算每个页面查看之间的时间的各个百分点。这将给我们一个分布的感觉,而不必做更深入的分析。
with pvs as (
SELECT
p.message_id
, p.anonymous_id
, p.timestamp
, LAG(p.timestamp) over
(PARTITION BY p.anonymous_id ORDER BY p.timestamp ASC)
as last_pageview_ts
FROM website.pages p
),time_between as (SELECT
v.anonymous_id
, date_diff('ms', v.last_pageview_ts, v.timestamp) ms_btwn_pvsFROM pvs v
WHERE date_diff('minute', v.last_pageview_ts, v.timestamp) < 30
and v.last_pageview_ts is not null)SELECT
PERCENTILE_CONT(0.990) within group (ORDER BY ms_btwn_pvs desc) over () as ptile_99
, PERCENTILE_CONT(0.950) within group (ORDER BY ms_btwn_pvs desc) over () as ptile_95
, PERCENTILE_CONT(0.90) within group (ORDER BY ms_btwn_pvs desc) over () as ptile_90
, PERCENTILE_CONT(0.750) within group (ORDER BY ms_btwn_pvs desc) over () as ptile_75
, PERCENTILE_CONT(0.500) within group (ORDER BY ms_btwn_pvs desc) over () as ptile_50
, PERCENTILE_CONT(0.250) within group (ORDER BY ms_btwn_pvs desc) over () as ptile_25
FROM time_between
LIMIT 1
查询输出(毫秒)

该查询将输出页面视图之间时间的各种百分比。例如,PTILE_75 是页面查看之间时间的第 75 个百分位数,这意味着 25% (100% — 75%)的页面查看发生在它之前的页面查看的 4,618 毫秒或更短时间内。作为参考,4,618 ms 相当于 4.618 秒。这符合我们对网站典型页面浏览行为的预期。
您还可以可视化分布(如曲线图)以获得更好的感觉,但是 SQL 输出应该足以确定一个好的阈值。

作者图片
选择阈值
一旦您对分布有了更好的理解,您就可以为页面浏览之间的时间选择一个明智的阈值。对于非常保守的阈值,选择 PTILE_99(第 99 个百分位数表示两次页面查看之间的最短时间)或第 95 或 90 个百分位数表示更宽松的阈值。为了简单起见,我选择 1000 毫秒作为我的阈值,因为根据我的数据,这是一个介于 99%和 95%之间的很好的数字。
要更新查询,只需将 bot 查询中带有 1,000 的行更改为您的新阈值:
avg(date_diff('ms', v.last_pageview_ts, v.timestamp)) < 1000
总页面浏览量阈值
我们将使用相同的方法来选择总页面浏览量的阈值。首先,使用下面的 SQL,我们将计算每个用户的总页面浏览量,并计算百分位数以更好地了解分布情况。
with pvs as (
SELECT
p.message_id
, p.anonymous_id
, p.timestamp
, LAG(p.timestamp) over
(PARTITION BY p.anonymous_id ORDER BY p.timestamp ASC)
as last_pageview_ts
FROM website.pages p
),total_views as (SELECT
v.anonymous_id
, count(1) total_pvsFROM pvs v
WHERE date_diff('minute', v.last_pageview_ts, v.timestamp) < 30
and v.last_pageview_ts is not null
GROUP BY 1)select
PERCENTILE_CONT(0.990) within group (ORDER BY total_pvs ASC) over () as ptile_99
, PERCENTILE_CONT(0.950) within group (ORDER BY total_pvs ASC) over () as ptile_95
, PERCENTILE_CONT(0.90) within group (ORDER BY total_pvs ASC) over () as ptile_90
, PERCENTILE_CONT(0.750) within group (ORDER BY total_pvs ASC) over () as ptile_75
, PERCENTILE_CONT(0.500) within group (ORDER BY total_pvs ASC) over () as ptile_50
, PERCENTILE_CONT(0.250) within group (ORDER BY total_pvs ASC) over () as ptile_25
from total_views
limit 1
查询输出

选择阈值
就像以前一样,我们有百分位数来更好地了解总页面浏览量的分布。我们将为 bot 查询选择一个合适的阈值。我选择这个阈值稍微宽松一点(10),因为我预计大多数机器人是通过页面浏览之间的时间而不是他们在网站上浏览的总页面来检测的。
一旦您确定了想要使用的阈值,只需将 bot 查询中的这一行替换为您的阈值 10 。
and COUNT(1) > 10
仅此而已。您已经分析了站点上用户的典型行为,并可以相应地调整阈值。
让我们找到那些机器人!!

基于机器学习的守门员组合风格识别
用于足球分析的聚类算法

Á·阿尔瓦罗·门多萨在 Unsplash 上拍摄的照片
介绍
准备阶段是一个球队控球并试图向对手射门得分的阶段。
守门员是防守队员的最后一人,但也是组织进攻的第一人。
了解守门员的构建方案是了解球队踢球风格的最重要的信息之一。
机器学习算法可以在分离守门员制定的不同构建方案中发挥作用。
在本文中,我将向您展示如何使用聚类算法将守门员的传球分成几个聚类。事不宜迟,我们开始吧!
履行
数据源
对于数据源,我们将使用来自 StatsBomb 的开放数据(经许可)。我们将从 2020 年欧锦赛开始分析守门员的组合模式。你可以在这里 通过 GitHub 库 阅读关于数据集的细节。
行动(或活动、袭击)计划
为了生成守门员的累积传球集群,我们必须在可视化之前准备好数据。这些步骤是:
- 加载数据,
- 准备数据,
- 使用 K-Means 算法对数据进行聚类,
- 想象每个守门员的集群。
加载数据
因为有来自不同比赛的大量数据,我们需要检索 2020 年欧锦赛的比赛、赛季和比赛标识符。
为了检索比赛和赛季 id,我们可以查看 competition.json 文件。下面是实现这一点的代码:
从上面可以看到,2020 欧洲杯对应的比赛和赛季 id 分别是 55 和 43。接下来,我们将使用这些 id 检索匹配 id。下面是实现这一点的代码:
获得完整的匹配 id 后,下一步是将数据组合成一个数据帧。下面是实现这一点的代码:
准备数据
在检索完整的匹配数据之后,我们需要准备数据,以便它可以用于建模过程。首先,我们将过滤包含守门员信息的数据。下面是实现这一点的代码:
然后,我们过滤描述守门员传球的数据。下面是实现这一点的代码:
接下来,我们选择列来简化建模和分析过程。这些列是球员和球队的名字,传球位置的起点和终点,传球的距离,以及传球的角度。下面是实现这一点的代码:
因为位置信息使用列表格式,所以我们需要提取这些值,并将它们放在单独的列中。下面是实现这一点的代码:
聚集数据
我们将使用一种称为 K-Means 算法的聚类算法来将过程分成几个组。
简而言之,K-Means 会初始化几个质心,每个数据都属于一个基于最近距离的质心。重复该过程,直到算法捕捉到最佳误差值。
但是在应用该算法之前,我们需要为每次传递提取位置、距离和角度等列。下面是实现这一点的代码:
然后,我们需要选择最佳的聚类数。我们可以用肘法来确定最佳聚类数。下面是查找号码的代码:
为了推断结果,我们需要查看一个聚类点,该点形成一条肘形线。还有,我们需要看看在肘形线之后误差是否没有显著减少。
正如您从上面所看到的,值 6 是集群的最佳数量。在该值之后,误差不会显著减小。因此,我们将使用它来分隔每个守门员的传球。
让我们通过使用最佳数量的集群并将标签放入数据帧来再次进行建模过程。下面是实现这一点的代码:
想象结果
现在我们有了集群的标签。最后一步是可视化这些集群,并为每个守门员划分它们。
为了使见解更有意义,我们只根据通过次数取前 3 个聚类。
我们将使用 mplsoccer 库来可视化图表。下面是实现这一点的代码:
我们能从这个可视化中推断出什么?我们可以看到每支球队都有不同的风格。
我们可以看到像蒂博·库尔图瓦、吉安路易吉·多纳鲁马和曼努埃尔·诺伊尔这样的守门员把球传给他们的后卫或中场。
马尔滕·斯特克伦博格、乌古坎·恰基尔和罗宾·奥尔森演奏了不同的风格。他们试图将球传给对方半场。
有了这些信息,球队将知道在哪个位置进行预测,这样球员就可以阻止对手控球。
结束语
干得好!您已经学习了如何使用聚类算法来生成守门员的集结模式。我希望您可以将该概念应用于不同的数据或应用程序。
如果你对我的文章感兴趣,你可以在 Medium 上关注我,看更多这样的文章。谢谢你看我的文章!
识别社交媒体上的影响者:使用 Python 的社交网络分析指南
使用诸如入度、出度、模块性和中介中心性等指标来识别在线网络中的关键参与者

图片由皮克斯拜的 Gerd Altmann 提供
社交媒体平台已经成为交流和信息传播的重要渠道,这使得了解信息如何在这些网络中传播变得越来越重要。一种方法是通过使用社会网络分析(SNA),它提供了一系列工具和技术来分析和理解个人或组织网络中的关系和模式。
在本文中,我们将提供四个关键指标的概述,这些指标可用于识别社交媒体上的影响者:程度、程度、模块性和中间中心性。我们将使用 Python 编程语言以及 networkx 和 community 包来计算和可视化样本社交网络数据集的这些指标。
我们将介绍以下内容:
社交网络分析(SNA)解释
识别影响者:来自 Recuero 等人(2019)和 Kim and valente(2021)
理解和计算SNA
∘可见性(入度)
∘ 参与度(出度)
∘ 关于主题的立场(模块化
社会网络分析(SNA)解释
社会网络分析(SNA)是一个研究领域,涉及使用统计和数学技术来分析和理解个人或组织网络中的关系和模式。它通常用于识别关键人物和理解社会、职业和交流网络的动态。
在 SNA 中,网络通常表示为图形,节点表示个体参与者(例如,人、组织),边表示它们之间的关系(例如,友谊、协作)。可以使用各种不同的度量来分析这些网络,包括中心性的度量(例如,程度、中间性)、社区结构的度量(例如,模块性)以及网络演化的度量(例如,优先连接)。
SNA 的应用范围很广,包括研究信息和思想的传播,识别关键参与者和影响者,了解社交网络的结构和动态,预测新关系的形成。它被用于社会学、心理学、人类学、通信和计算机科学等领域,也已被应用于商业、政治和公共卫生领域的网络分析。
识别社交媒体上的影响者:来自 Recuero 等人(2019 年)和 Kim 和 Valente (2021 年)的见解
在他们的研究中, Recuero,扎戈和 Soares (2019)使用社交网络分析和社会资本来确定 Twitter 上极化政治对话的四种影响者。他们使用了几个指标来识别这些影响因素,包括模块化、内向度和外向度。

图四。网络图—2018 年 1 月 22 日卢拉受审。来自雷库埃罗、扎戈和苏亚雷斯 (2019)
模块性衡量网络划分为不同组或社区的强度(如图所示)。Indegree 衡量用户收到的提及和转发次数,高 in degree 表示高可见性。Out-degree 衡量某个人在给定网络中转发或提及的用户数量,表示参与度。
根据这些指标,研究中确定的四种影响因素是:
- 意见领袖:这些用户有明确的政治立场和较高的参与度,表明他们在形成公众意见方面有影响力,并经常被他人提及或转发。
- 活动家:这些用户也有明确的政治立场和很高的出镜率,表明他们是某个特定事业或问题的倡导者,经常转发或提及其他用户。
- 信息影响者:这些用户没有明确的政治立场,但有很高的认同度,通常表明他们是新闻渠道或其他可靠信息的来源。
- 新闻剪辑师:这些用户也没有明确的政治立场,但有很高的外向度,表明他们积极分享新闻文章和其他来自知名来源的信息。

金,我,&瓦伦提,T. W. (2021)。推特上的新冠肺炎健康传播网络:识别来源、传播者和经纪人。连接, 40 (1),129–142。
Kim 和 Valente (2021)对 Twitter 上的新冠肺炎健康传播网络进行了研究,并根据他们在网络中的角色确定了三种类型的用户。这些用户是:
- 信息源(P):这些用户有很高的投入度,表明他们经常被其他用户提及或转发。他们被视为有关新冠肺炎的信息来源,并在塑造围绕疫情的公共话语方面发挥了关键作用。
- 信息传播者(Q):这些用户有很高的外向度,表明他们在分享信息和与其他用户互动方面很活跃。他们在传播关于新冠肺炎的信息和塑造疫情周围的公共话语方面发挥了关键作用。
- 信息经纪人(G):这些用户具有很高的中间性,表明他们在连接网络内不同群体方面发挥了核心作用。他们被视为促进不同群体之间信息流动的中间人,并在塑造新冠肺炎公共话语方面发挥了关键作用。
总的来说,这些发现表明,社交网络分析可以成为一种有用的工具,用于识别关键人物和理解 Twitter 等社交媒体平台上健康传播网络的动态。
使用 Python 理解和计算社交网络分析的关键指标:入度、出度、模块性和介数中心性
对于市场营销、公共关系和政治活动等各种应用来说,识别网络中的影响者可能是一项有价值的任务。在本文中,我们将探讨如何使用四种不同的指标来识别社交网络中的影响者:可见性(入度)、参与度(出度)、主题位置(模块化)和网络位置(中间中心性)。在接下来的章节中,我们将提供每个指标的概述,并讨论如何使用 Python 来计算它。
先决条件
在我们开始之前,您需要在 Python 环境中安装以下包:
networkx:用 Python 处理图形和网络的包community:用于检测网络中社区的包matplotlib:创建情节和可视化的软件包
您可以使用pip安装这些软件包:
pip install networkx community matplotlib
数据
对于本教程,我们将使用由networkx包(BSD-3)提供的一个样本社交网络数据集。这个数据集代表了一组个体之间的友谊的社会网络。图中的节点代表个体,边代表他们之间的友谊。
要加载数据集,我们可以使用以下代码:
import networkx as nx
# Load the sample social network dataset
G = nx.karate_club_graph()
能见度(以度为单位)
识别社交网络中的影响者的一种方法是看个人的可见性或他们拥有的联系数量。在社交网络中,这通常被称为节点的入度。
要计算图中每个节点的入度,我们可以使用以下代码:
in_degree = dict(G.in_degree())
print(in_degree)
这将打印出一个字典,其中节点作为键,入度作为值。例如,输出可能如下所示:
{0: 16, 1: 9, 2: 10, 3: 6, 4: 3, 5: 4, 6: 4, 7: 4, 8: 5, 9: 2, 10: 3, 11: 1, 12: 2, 13: 5, 14: 2, 15: 2, 16: 2, 17: 2, 18: 2, 19: 3, 20: 2, 21: 2, 22: 2, 23: 5, 24: 3, 25: 3, 26: 2, 27: 4, 28: 3, 29: 4, 30: 4, 31: 6, 32: 12, 33: 17}
然后,我们可以使用这些信息来识别具有最高学位的个人,他们将被认为是网络中最可见或最有影响力的人。
参与(非学位)
识别社交网络中的影响者的另一种方法是查看个人的参与情况或他们与其他个人建立的联系数量。在社交网络中,这通常被称为节点的出度。
要计算图中每个节点的出度,我们可以使用以下代码:
out_degree = dict(G.out_degree())
print(out_degree)
这将打印出一个字典,其中节点作为键,出度作为值。例如,输出可能如下所示:
{0: 9, 1: 10, 2: 5, 3: 4, 4: 4, 5: 3, 6: 3, 7: 2, 8: 2, 9: 2, 10: 3, 11: 1, 12: 2, 13: 5, 14: 2, 15: 2, 16: 2, 17: 2, 18: 2, 19: 3, 20: 2, 21: 2, 22: 2, 23: 5, 24: 3, 25: 3, 26: 2, 27: 4, 28: 3, 29: 4, 30: 4, 31: 6, 32: 17, 33: 12}
类似于入度,我们可以使用出度来确定在网络中建立了大量联系或高度参与的个人。
主题定位(模块化)
识别社交网络中的影响者的另一种方法是查看个人在特定主题或社区中的位置。在社交网络中,这可以用模块化来衡量,模块化是一种衡量网络由密集连接的社区组成的程度的方法。

按作者
有争议的话题可能更吸引人,因为它们往往会引发人们强烈的情绪和观点。当人们遇到挑战他们的信仰或价值观的信息或想法时,他们可能会感到有动力参与其中并表达自己的观点。这可能会导致社交媒体平台上的活动和参与度增加,因为人们会评论、分享和喜欢与争议话题相关的内容。
此外,有争议的话题可以产生紧迫感或重要性,因为人们可能会觉得这个话题对自己或他人有直接的相关性或后果。这也有助于增加接触和参与。
值得注意的是,有争议的话题也会产生负面或有害的影响,如两极分化、错误信息和不宽容。小心处理有争议的话题,并考虑接触这些内容的潜在后果,这一点很重要。
为了计算图的模块性,我们可以使用community包中的community.best_partition函数:
import community
# Calculate the modularity of the graph
partition = community.best_partition(G)
modularity = community.modularity(partition, G)
print(modularity)
这将打印出图的模块性,它将是一个介于 0 和 1 之间的值。模块性值越高,表明网络对社区的划分越强。
然后,我们可以使用划分字典来识别在他们各自的社区中高度关联的个人,因此,这些个人可能在那些社区中有影响力。
在网络上的位置(中间中心性)
最后,我们可以通过观察社交网络中的影响者在整个网络中的位置来识别他们。在社交网络中,这可以使用介数中心性来测量,介数中心性是对网络中节点作为其他节点之间的桥梁的次数的测量。
或者换句话说,介数中心性是一个节点在网络信息流中充当桥梁或中介的次数的度量。它反映了一个节点在连接其他节点和促进它们之间信息流动的能力方面的重要性。
想象一下,一群人试图在不透露信息来源的情况下互相分享信息。他们可以这样做的一个方法是通过使用经纪人或中间人,他们作为中间人,让人们互相传递信息。这个代理将具有很高的中间中心性,因为他们负责连接网络中的许多人,并促进他们之间的信息流动。
另一方面,一个只与少数其他人直接交流的人具有较低的中间中心性,因为他们不是网络中许多其他连接的中介。
一般来说,具有高介数中心性的节点在网络中起中心作用,并且通常被认为在信息流方面有影响或重要。因此,它们对于在网络中传播信息或思想是有价值的。
为了计算图中每个节点的介数中心性,我们可以使用networkx包中的nx.betweenness_centrality函数:
betweenness_centrality = nx.betweenness_centrality(G)
print(betweenness_centrality)
这将打印出一个字典,其中节点作为键,介数中心性作为值。例如,输出可能如下所示:
{0: 0.4376352813852815,
1: 0.053936688311688304,
2: 0.14365680615680618,
3: 0.011909271284271283,
4: 0.0006313131313131313,
...
然后,我们可以使用这些信息来识别具有高介数中心性的个人,这表明他们在整个网络中具有良好的联系和潜在的影响力。
摘要
在本教程中,我们演示了如何使用 Python 和各种包来基于四个不同的指标识别社交网络中的影响者:可见性(入度)、参与度(出度)、主题位置(模块化)和网络位置(中间中心性)。通过计算和分析这些指标,我们可以识别网络中可能在各种环境中具有影响力或重要性的关键人物。
通过使用这些指标来识别影响者,组织和个人可以更深入地了解在线网络的动态,并可以制定更有效的沟通和参与策略。Recuero、扎戈和 Soares (2019)使用这些指标确定的影响者的例子包括意见领袖(具有明确政治立场和高外向度的用户)、活动家(具有明确政治立场和高外向度的用户)、信息影响者(没有明确政治立场和高内向度的用户)和新闻剪辑师(没有明确政治立场和高外向度的用户)。
除了这些影响者之外,Kim 和 Valente (2021)在他们对 Twitter 上的新冠肺炎健康传播网络的研究中确定了另外三种类型的用户:信息源(P)(具有高进入度的用户)、信息传播者(Q)(具有高外出度的用户)和信息经纪人(G)(具有高中间中心度的用户)。这些用户在关于新冠肺炎的信息传播中扮演了特定的角色,并且可以使用上述相同的度量来识别。
通过了解这些不同类型的影响者所扮演的角色,组织和个人可以更有效地进行沟通,并与他们网络中的关键人物进行互动。无论您是在寻找意见领袖、活动家、信息影响者、新闻剪辑师、信息源、信息传播者还是信息经纪人,社交网络分析都可以提供对在线网络动态的宝贵见解,并帮助制定有效沟通和参与的策略。

按作者
参考文献:
- 金,我,&瓦伦提,T. W. (2021)。推特上的新冠肺炎健康传播网络:识别来源、传播者和经纪人。连接,40(1),129–142。
- ·雷库罗,r .,扎戈,g .&苏亚雷斯,F. (2019)。使用社会网络分析和社会资本来识别用户在 Twitter 上两极分化的政治对话中的角色。社交媒体+社会,5(2),2056305119848745。
基于机器学习的在线考试抄袭检测
原文:https://towardsdatascience.com/identifying-plagiarism-during-online-exams-78047895139a
在 Python 中识别推文情感
原文:https://towardsdatascience.com/identifying-tweet-sentiment-in-python-7c37162c186b
如何使用 Tweepy 和 Textblob 识别推文情感

这篇文章的目的是为读者提供关于 tweepy 和 textblob 库的入门指南。本文将涵盖下面目录中列出的下列主题。
目录 - Twitter API 凭证
-连接 Twitter API
-抓取 Twitter 数据
-清理 Tweet 数据
-情感分析
-结束语
-资源
Twitter API 凭证
获得连接到 twitter API 所需的凭证相当简单,首先要创建一个 twitter 帐户。请注意,如果你是学生,你有更高的机会被 Twitter 接受,如果你不是学生,并且想出于非教育/研究目的这样做(例如,如果它将被应用到公司的产品中),那么 Twitter 的准则对你的要求和细节就要高得多。
要开始这个过程,你需要创建一个 twitter 开发者账户,这可以在这里完成。接下来,你需要向 Twitter 提供一些细节,说明你为什么想要访问开发者账户,理由可能是进行研究、建立网络、探索 API 等等。这一流程非常广泛,因为 Twitter 要求提供许多至少 100 个字符的详细信息,然后 Twitter 开发人员会审查您提交的产品&应用程序的 API 访问权限,您应该会看到关于该流程的接受/拒绝通知。
最后,一旦你的申请被批准,你就可以前往 twitter 开发者平台继续你的应用程序。转到Keys and tokens选项卡,生成必要的 API 键来连接 twitter API。

在生成您的 API 密钥和令牌时,它应该类似于下图。(图片由作者提供)
如果您仍然有问题,Twitter 已经在他们的文档中写了一份关于如何连接 API 的详细指南:
https://developer.twitter.com/en/docs/twitter-api/getting-started/getting-access-to-the-twitter-api
连接到 Twitter API
在连接到 twitter API 之前,您必须首先满足必要的安装要求。本文中概述的代码是在以下库和版本上编写的:
Python=3.8.8
pandas=1.2.4
numpy=1.20.1
textblob=0.17.1
tweepy=4.4.0
matplotlib=3.3.4
python-dotenv=0.19.2
一旦满足了必要的安装要求,就可以创建一个名为.env的环境文件来存储上面从 twitter 仪表板生成的密钥。您可以使用 dotenv 和 os 模块来引用此环境文件。这将允许您的登录凭据保持匿名。环境文件可以具有以下格式:
twitter_api_key=''
twitter_secret=''
twitter_bearer=''
twitter_access_token=''
twitter_access_token_secret=''
请注意,当将您的工作推送到 Github 帐户时,您可能希望将.env添加到.gitignore文件中,这样环境文件就不会被推送到 git。
现在,通过下面的代码片段可以很容易地连接到 API。
抓取 Twitter 数据
现在已经建立了到 twitter API 的连接,让我们对 API 进行一些调用来获取一些 tweet 数据。出于本教程的目的,我们将从 GTA 的警察账户中收集推文,目的是识别与推文相关的情绪。我在下面概述了创建名为 data 的目录并存储与列表中传递的 twitter 句柄相关的最新 tweet 数据的各种函数。这些功能相互配合,以.csv文件的形式获取和存储数据。注意函数get_all_tweets是从亚诺夫斯基重新转换而来的。由于我们使用 twitter API 的免费层,我们可以从用户那里获取的 tweet 数量有限制,yanofsky 发现了一种从超过限制的用户那里获取 tweet 的好方法。基本上,如果多伦多警察局在其整个 twitter 职业生涯中有 10,000 条推文,twitter API 将在获取 100 条推文后达到上限(这 100 条推文将是用户的最新 100 条推文)。Yanofsky 的解决方案现在允许我们从该用户那里获取近 3000 条推文。
上面代码的结果应该会产生一个 tweets_df,包含以下几列:
['author_name', 'tweet_id', 'tweet_created_at', 'content', 'hashtags', 'location', 'author_created_at', 'author_url', 'author_screen_name', 'tweet_favourite_count', 'tweet_favourited', 'retweet_count', 'retweeted', 'author_followers_count', 'author_friends_count']
清理推文数据
在进行情感分析之前,最好对推文进行一些初步清理。像删除停用词和标点符号这样简单的事情会对情感分析的结果产生很大的影响。下面的函数就是这样做的。
情感分析
textblob 库有一个不错的内置函数,可以对推文进行情感分析。由此产生的情绪是指对推文的情绪或态度。它产生的分数上限为 1,下限为-1。更接近-1 的分数可以视为一条negative推文,同样,更接近 1 的分数可以视为一条positive推文。等于 0 的分数通常是neutral(即既非正也非负)。
这导致了以下 GTA 中警察部门发布的正面、负面和中性推文的分布。

图片由作者提供
令人惊讶的是,积极的推文比消极的多,然而大多数是中性的。查看与每个标签相关的推文样本,我们可以尝试看看这个情感评分表现如何。
正面推文的随机样本:
tweets_df[tweets_df['tweet_sentiment'] == 'Positive'].content.sample(3).valuesSmartest idea we've heard all night! Way to be a responsible driver! ^MRT [https://t.co/htkoL5EpKJ](https://t.co/htkoL5EpKJ)"Hanukkah Sameach! May this festival of light and joy bring blessings of health, happiness and prosperity. [https://t.co/0bq0dKNFc3](https://t.co/0bq0dKNFc3)'RT @CACP_ACCP: On this National Day of Remembrance & Action on Violence Against Women, we are proud to launch the Canadian Framework for Co…'
负面推文的随机样本:
tweets_df[tweets_df['tweet_sentiment'] == 'Negative'].content.sample(3).valuesCOLLISION:\nFGX/Parklawn, single vehicle involved, E/B express lanes approaching Lakeshore exit, 2 lanes closed.… [https://t.co/Pz5hBIC0H9](https://t.co/Pz5hBIC0H9)@Toronto_Fire @TorontoMedics FIRE: (UPDATE)\nDunn Ave & Queen St W\n- roads have re-opened\n#GO1455141\n^alBollywood superstar's son walks out from jail in drugs case\nhttps://t.co/Yf9ytlkNiF [https://t.co/vsdkYzQnad](https://t.co/vsdkYzQnad)
中立推特的随机样本:
tweets_df[tweets_df['tweet_sentiment'] == 'Neutral'].content.sample(3).valuesRemember to follow our @DRPSCommunity account for helpful safety tips during Crime Prevention Week. #CPWeek2020 [https://t.co/vGO8saST4r](https://t.co/vGO8saST4r)The #BarriePolice is reminding the public that you DO NOT have to pay money to receive a lottery prize. If you get… [https://t.co/G5H2dsc468](https://t.co/G5H2dsc468)Road closure on Dundas St East in Whitby between Hopkins and Craydon for Police Investigation.
结束语
总的来说,本文展示了如何通过开发者门户访问 twitter API、连接到它、获取与用户句柄相关的 tweet、执行简单的文本清理以及识别与 tweet 相关的情感。基于正面、负面和中性推文的随机小样本,模型的表现似乎不会太差。很明显,这个样本太小了,无法对情感分析的性能做出任何明确的陈述,然而对于第一遍来说,它似乎并不太糟糕。
这个过程可以被复制,用于分析来自 twitter 或其他来源的任何其他主题。这篇文章关注的是从警察局发来的推文,但是从健康组织、政治家、你的朋友等发来的推文也有同样的结果。只需对代码做最少的改动。将此作为你自己探索的起点!
资源
- https://gist.github.com/yanofsky/5436496
- https://text blob . readthedocs . io/en/dev/quick start . html #情绪分析
- https://developer.twitter.com/en/portal/dashboard
如果你喜欢这篇文章,我写了更多你可能也会喜欢的文章
[## 贝叶斯 A/B 测试解释
towardsdatascience.com](/bayesian-a-b-testing-explained-344a6df88c1a)
浓缩咖啡的 iDroprep 篡改
原文:https://towardsdatascience.com/idroprep-tamper-for-espresso-2b3640d9d3ec
咖啡数据科学
数据来自泰门·考夫曼
iDroprep 夯锤是一种新型夯锤,它使用凝胶状垫来改变夯锤冲击地面的方式。 Timon Kaufmann 制作了一个视频并收集了一些关于篡改性能的数据(如果你不会说德语,请打开翻译成英语的自动隐藏字幕)。他以表格的形式展示了数据,所以我提取了数据并用图表显示出来,以获得不同的外观。

所有图片由作者提供
以下是原始数据:

他收集了提取率(EY ),用来衡量提取了多少咖啡。这是目前确定方法效率的最佳指标。
他为每个篡改做了两个变种:
- 最佳冰球准备
- 基本圆盘准备
以下是每个变体的三个镜头:

这给出了一个很好的故事,但在更一般的情况下,我们可以针对每个篡改绘制所有数据。我这样做了,并对样本进行了分类。性能上的差距非常明显,但是数据量对于统计测试来说是一个很差的样本量。

此外,我绘制了拍摄时间,但拍摄时间非常接近,所以没有太多启示。

我总是希望新技术有相关的数据来显示性能。这是一个小样本数据,但小数据总比没有数据好。我很有兴趣看看还有什么其他数据来为这个夯锤和冰球准备。很可能不同的圆盘准备会更好地最大限度地利用 iDroprep 夯锤。
如果你愿意,可以在推特、 YouTube 和 Instagram 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。也可以在中关注我,在订阅。
我的进一步阅读:
如果我要雇用一名数据科学家,我会问这两个问题
它们会引发一场揭示许多重要事情的对话

文森特·范·扎林格在 Unsplash 上拍摄的照片
我目前没有能力聘请数据科学家,但我希望有一天会。话虽如此,我已经好几次站在面试台的另一边了。
在数据科学家的访谈中,我回答了各种主题的问题,从 SQL 到 Python,从机器学习到贝叶斯定理。有些真的很有挑战性,但有些对我来说似乎没什么用。
现在我是一名数据科学家,我有时会想,如果我要雇用一名数据科学家,我会提出什么要求。
我这样做的主要目标不是找到最好的问题来提问,也不是在候选人中找到最好的数据科学家。相反,我的目标是成为一名更好的数据科学家。这样的思考过程帮助我提高技能,激励我学习新东西。由于数据科学仍在发展,持续学习至关重要。
先说第一个问题。
问题 1 :你被指派创建一个模型来解决一个监督学习问题,要么是回归,要么是分类。你会选择哪种算法,为什么?
你首先想到的大概是,这取决于很多东西。你完全正确。因此,我们并不试图找到一个单一的算法作为答案。
这个问题的目的是开始一个关于机器学习算法的讨论。但是,在讨论过程中的某个时候,我想让候选人解释以下内容:
- 预测准确性和模型可解释性之间存在权衡。
我们来详细阐述一下这个。一般来说,随着算法灵活性的增加,它往往会给出更准确的结果。例如,梯度推进决策树比线性回归灵活得多,并且在准确性和性能方面优于线性回归。
然而,我们用可解释性的代价换来了 GBDT 极其出色的表现。这意味着我们对决策是如何做出的了解有限。该算法可以计算特征重要性值,但我们对哪些特征起关键作用有模糊的理解。
另一方面,线性回归具有非常有限的灵活性,但提供了高度的可解释性。通过线性回归模型,我们可以全面了解每个特征对预测的影响。
所以算法的选择取决于我们想要达到的目的。如果我们不关心可解释性,只想获得好的结果,我们可以使用灵活的算法。股票价格预测就是一个例子。我们通常只对获得高精度感兴趣。
当我们在一项任务中工作时,重点是“为什么”要做出预测,那么我们应该选择可解释的模型。
机器学习不仅用于推荐系统等低风险环境,还用于癌症预测、药物测试等关键任务。在这些情况下,我们肯定想知道为什么一个决定是错误的。
这个问题本质上和可解释的机器学习或者可解释的 AI 有关。如果你想了解更多关于可解释机器学习的知识,这里有一本由 Cristoph Molnar 写的很棒的书。
https://christophm.github.io/interpretable-ml-book/
问题 2:什么是机器学习中的偏差和方差?
这也是一个开放式问题,目标是看候选人是否知道偏差、方差、它们对机器学习模型的意义以及它们之间的权衡。
方差是模型对训练数据的敏感程度的度量。由于训练集中的微小变化,具有高方差的模型的预测可能会发生显著变化。然而,这不是所期望的。我们希望我们的预测在不同的训练集之间不会有太大的差异。因此,我们尽量避免高方差的模型。
偏差是指用一个非常简单的模型来近似一个复杂的问题。例如,使用线性回归模型,非线性关系可能导致高偏差。我们无法通过使用高偏差的模型来获得对目标变量的良好估计。因此,我们也尽量避免具有高偏差的模型。
但是,我们如何才能实现低偏差和低方差的模型呢?
预测模型的误差基本上是预测值和实际值之间的差异。这些误差由可约误差和不可约误差两个主要部分组成。
我们可以通过关注可减少的误差部分来改进模型,误差部分可以表示为预测的方差和方差。方差和方差都是非负值,因此,在最佳情况下,我们的目标是低偏差和低方差的模型。然而,这通常是一项非常具有挑战性的任务,并且在它们之间有一个权衡。
一般来说,随着方法灵活性的增加,方差趋于增加,偏差趋于减少。例如,基于 GBDT 的算法,如 XGBoost 和 LightGBM,可能具有非常低的偏差,但具有高的方差。
随着灵活性的增加,偏差下降的速度往往会快于方差增加到某一点的速度。在此之后,如果我们继续增加灵活性,我们不会在偏差方面取得很大成就,但方差会显著增加。
创建一个健壮而准确的模型的关键是找到最佳点。
还会有特征和目标变量之间的关系是线性的情况。在这些情况下,线性模型将没有偏见,因此它们优于更先进和复杂的模型。
这些是我肯定会问数据科学家候选人的问题。他们引导对话,揭示候选人对机器学习和统计学中许多重要概念的知识和理解。
我强烈建议思考你自己的问题,因为这既是一个很好的思维锻炼,也是一个学习的机会。
你可以成为 媒介会员 解锁我的全部写作权限,外加其余媒介。如果你已经是了,别忘了订阅https://sonery.medium.com/subscribe如果你想在我发表新文章时收到电子邮件。
*https://sonery.medium.com/membership
感谢您的阅读。如果您有任何反馈,请告诉我。*
如果您正在使用 Python 和 Google 云平台,这将简化您的生活(第 1 部分)
使用 Artifact Registry 管理您的私有包,并将它们导入您的云功能和云运行服务

Max van den Oetelaar 在 Unsplash 上拍摄的照片
如果您在专业环境中使用 python,我可以告诉您,您已经在寻找一种在私有存储库中部署 Python 包的方法。好吧,让我来介绍一下工件注册,这是谷歌云平台的工件管理服务,可能正是你所需要的。
工件注册解决的 3 个问题
假设您有一个 python 类(例如一个日志记录类),它被一个云函数和一个云运行服务使用。
实施 DRY 原则:如果没有管理 python 包的解决方案,您最终将部署 python 类和云函数。同样,您需要部署与云运行服务打包在一起的 python 类,复制相同的代码片段。工件注册库使您能够通过将 python 类部署到一个库中,并从云功能和云运行服务中提取该库中的内容,来执行非常重要的 DRY(不要重复自己)原则。当您需要修改或修复 python 类中的任何 bug 时,这为您提供了一个单一的地方。
部署安全版本 : 每次对 python 类进行更改,都存在破坏云功能和/或云运行服务的风险。我们称之为回归。虽然有可能用非回归测试来降低破坏东西的风险,但是这些测试通常是不够的。此外,您会希望用 python 类的工作版本来精确定位每个版本。精彩!Python 工件注册库允许您这样做。
管理对包的访问 : 如果你不在乎隐私,也就是说,如果你不介意你的 python 包被世界上任何人看到和使用,我鼓励你把它们放在公共 python 库。但是如果您需要控制谁查看您的包,正如在专业环境中经常出现的情况,工件注册库是一个很好的工具,因为它使您能够只与选定的人共享您的库。
说够了!让我们构建一个 python 工件注册表存储库,在里面部署一些东西,并尝试从云函数中提取存储库。
如果你需要的是将私有 python 包安装到 Cloud Composer DAG 中,请勾选这个。
为您的包创建一个存储库
假设您可以访问 GCP 项目和云外壳,为 python 包创建一个存储库是很简单的。
是您要给 python 库 起的名字- <您的存储库位置>是存储库的位置。类似于“美国-中部 1”或“欧洲-西部 1”的内容
是描述存储库用途或效用的文本
部署简单的包
现在我们的存储库已经创建好了,让我们在其中部署一个玩具 python 包。我们将使用一个包含计算两点间哈弗线距离的函数的库。图书馆可以在这里找到。这是您将库部署到先前创建的工件注册库的方式:
在克隆了样例包存储库之后,我们构建了一个轮子,并使用 python 库‘twine’将轮子上传到工件注册库。
注意我们是如何使用【g cloud auth】对 gcp 账户进行认证的。该过程还在本地保存认证凭证,然后由【twine】在上传到工件注册表时使用。
部署一个从私有 Python 包中提取的简单云函数
现在让我们使用我们的库来计算一个云函数中的哈弗线距离。首先,克隆包含该函数的存储库。其次,激活需要的 API。第三,使用【g cloud】命令部署云功能。
虽然这是一个安全漏洞,但为了简单起见,我们使用标志 - allow-unauthenticated 来简化函数调用。在真实的场景中,您会希望限制对您的函数的访问,以便只有具有适当权限的客户端才有机会调用它。
现在是测试的时候了。我们将调用这个函数,看看它是否能像预期的那样工作。
基本上,cloud 函数从工件注册中心导入示例包(我们之前部署的包),并使用它来计算两点之间的哈弗线距离。它还将结果记录到云日志中。
好,那么(1,2)和(3,4)之间的哈弗线距离是多少。为了找到答案,我们调用了云函数。
成功执行 curl 命令后,通过查看函数日志,您应该能够看到哈弗线距离。

这里发生了一件神奇的事情
至此,我们已经成功地将一个 python 包部署到工件注册中心,并且我们能够使用一个 requirements.txt 文件从云函数中提取这个包。requirements.txt 包含两件事:
- python 工件注册库的 URL:https://
-python . pkg . dev/ / /simple/ - python 包的名称及其版本: mypythonlib==0.2.0
当我们使用【g Cloud】命令部署该功能时,会触发云构建服务,并且会向工件注册表进行认证以提取包。它使用我们启用云构建 API 时自动创建的服务帐户来实现这一点。
幸运的是,云构建服务帐户—project_number939016278554@cloudbuild.gserviceaccount.com拥有从同一个项目中的任何工件注册库提取所需的权限。然而,如果注册中心位于不同的项目中(例如,在一个共享项目中,这是一种常见的设计),如果我们不明确地给予云构建服务帐户在持有注册中心的项目中的 工件注册中心阅读器 角色,云功能将不能从注册中心中拉出。
结束注释
使用托管在云运行服务的工件注册表中的私有 python 包的工作方式非常相似。只需在 requirements.txt 文件中为 extra-index-url 选项设置正确的值,如果需要的话,给云构建服务帐户工件注册表阅读器角色,就可以了。
非常感谢你的时间。请在这里找到示例 python 包的代码,在这里找到 google cloud 函数的代码。
直到下一次写作,拜拜。
如果您正在使用 Python 和 Google 云平台,这将简化您的生活(第 2 部分)
使用工件注册表管理您的私有包,并将其导入 Cloud Composer DAGs

Max van den Oetelaar 在 Unsplash 上拍摄的照片
如果您在专业环境中使用 python,我可以告诉您,您已经在寻找一种在私有存储库中部署 Python 包的方法。好吧,让我来介绍一下工件注册,这是谷歌云平台的工件管理服务,可能正是你所需要的。
工件注册解决的 3 个问题
假设你有一个 python 类(比如一个日志类),它被一个气流有向无环图 (DAG)和一个云函数使用。
实施 DRY 原则:如果没有一个管理 python 包的解决方案,您最终会将 python 类与 Airflow DAG 一起部署,也就是说,您必须将 python 类复制到 DAG 文件夹中。
同样,您需要部署与云函数打包在一起的 python 类,复制相同的代码片段。工件注册库使您能够通过将 python 类部署到一个库中,并从该库中从 Airflow DAG 和 Cloud 函数中提取,来执行非常重要的 DRY(不要重复自己)原则。当您需要修改或修复 python 类中的任何 bug 时,这为您提供了一个单一的地方。
部署安全版本 : 每次您对 python 类进行更改时,都存在破坏气流 DAG 和/或云函数的风险。我们称之为回归。虽然有可能用非回归测试来降低破坏东西的风险,但是这些测试通常是不够的。此外,您会希望用 python 类的工作版本来精确定位每个版本。精彩!Python 工件注册库允许您这样做。
管理对包的访问 : 如果你不在乎隐私,也就是说,如果你不介意你的 python 包被世界上任何人看到和使用,我鼓励你把它们放在公共 python 库。但是如果您需要控制谁查看您的包,正如在专业环境中经常出现的情况,工件注册库是一个很好的工具,因为它使您能够只与选定的人共享您的库。
说够了!让我们构建一个 python 工件注册库,在里面部署一些东西,并尝试从一个 Airflow DAG 中拉出这个库。
如果你需要的是将私有 python 包安装到云函数或者云运行,敬请参考这篇文章。
为您的包创建一个存储库
假设您可以访问 GCP 项目和云外壳,为 python 包创建一个存储库是很简单的。
作者图片
是您想要给 python 库 起的名字- <您的存储库位置>是存储库的位置。类似于“美国-中部 1”或“欧洲-西部 1”的内容
是描述存储库 的用法或效用的文本
部署简单的 Python 包
现在我们的存储库已经创建好了,让我们在其中部署一个玩具 python 包。我们将使用一个包含计算两点间哈弗线距离的函数的库。图书馆可以在这里找到。这是您将库部署到先前创建的工件注册库的方式:
作者图片
在克隆了样例包存储库之后,我们构建了一个轮子,并使用 python 库‘twine’将轮子上传到工件注册库。
注意我们是如何使用【g cloud auth】对 gcp 账户进行认证的。该过程还在本地保存认证凭证,然后由【twine】在上传到工件注册表时使用。
部署一个简单的气流 DAG,它来自私有 Python 包
在 GCP 构建 DAG 的最快方法是创建一个 Cloud Composer 环境。这是一个漫长的过程(创建环境需要 20 分钟左右),涉及许多操作,包括在Google Kubernetes Engine(GKE)集群中启动大量资源,以及部署 Cloud SQL Postgres 实例。以下命令实际上创建了一个服务帐户,并在 GKE 集群上安装了 Airflow。
作者图片
由于 Airflow 现在已经启动并运行,我们可以继续安装私有的 python 包了,这个包已经在工件注册表中进行了推送。为此,我们需要做两件事。
- 向 Cloud Composer 提供 python 存储库 url
我们通过运行 gcloud 工件打印-设置 命令来获取这些信息。
作者图片
该命令的输出应该如下所示:

作者图片
复制名为 pip.conf 的文件中的最后两行,并将该文件上传到文件夹 config/pip 中的 Cloud Composer bucket 中。
作者图片
- 安装私有 python 包
作者图片
我们使用update-pypi-package选项,并提供要安装的软件包的名称和版本。
几分钟后…
不起作用……
您应该得到一条错误消息,指出安装 pypi 包失败了。
嗯,让我们来揭开这到底是怎么回事。当我们运行带有update-pypi-package选项的g cloud composer environments update命令时,会触发一个 Cloud Build 实例,并尝试构建一个安装了 python 包的自定义 Cloud Composer 映像。在发出更新命令后不到 5 分钟,这个构建就会出现在构建历史中(在云控制台中)。
构建包括 11 个步骤(至少在本文使用的 Composer 版本中是这样的——Composer-1 . 17 . 8-air flow-2 . 1 . 4 ),它在第 7 步失败,在这一步它试图安装私有 python 包。

作者图片
通读错误堆栈指出了问题的根本原因。实际上,构建很难验证工件注册。嗯,这不是我所期望的,因为构建服务帐户拥有读取工件注册表的权限。
为云构建提供身份认证密钥
在一些测试和学习迭代以及通读工件注册表文档之后,我发现了一个解决方案,它包括在我们之前创建的 pip.conf 文件中包含一个服务帐户密钥。这不符合安全最佳实践,显然看起来像是一种变通方法。然而,在撰写本文时,这是让 Cloud Composer 从任何工件注册库安装包的唯一方法。
要做到这一点,服务帐户应该拥有从工件注册中心读取的权限。请遵循以下 7 个步骤:
- 创建一个服务帐户(或者使用一个现有的帐户),并赋予它工件注册阅读器角色
- 使用服务帐户创建一个 json 服务帐户密钥
- 用 gcloud 工件打印-设置 命令生成私有 python 库 url。这一次,使用 json-key 选项并提供服务帐户密钥的路径
- 验证新生成的 extra-index-url 是否嵌入了服务帐户 json 密钥,即 url 应该看起来像
***https://_json_key_base64:<KEY>@<LOCATION>-python.pkg.dev/<PROJECT>/<REPOSITORY>/simple/***,其中<密钥>是嵌入的服务帐户密钥 - 用新生成的 url ( - extra-index-url)替换 pip 文件的内容
- 将修改后的 pip.conf 复制到 Cloud Composer bucket 中
- 运行软件包安装命令
作者图片
几分钟后… 它开始工作了
最后要做的事情是通过将 dag.py 文件复制到 Cloud Composer bucket 中来部署 DAG。
作者图片
DAG 只包含一个任务,除了打印(1,2)和(3,4)之间的哈弗线距离之外什么也不做。距离的计算是通过从工件注册中心导入私有 python 包— mypythonlib 来完成的。
作者图片
结束注释
在 pip 配置文件中嵌入服务帐户密钥看起来更像是补偿 Cloud Composer 实现缺陷的一种解决方法。Cloud Composer 团队有一个未解决的问题,即让工件注册中心的认证更直接地依赖于 IAM 角色。
非常感谢你的时间。请在这里找到示例 python 包的代码,在这里找到气流 DAG 的代码。
直到下一次写作,拜拜。
如果你懂 Python,你可以成为一名学习 r 的双语数据科学家。
用代码解释数据科学的核心编程概念

图片来自 Shutterstock,授权给 Frank Andrade
如果你对学习数据科学感兴趣,你应该学习 R。R 是一种擅长统计分析和可视化的编程语言。
但这还不是全部!使用 R,我们可以进行数据分析,应用机器学习算法,并完成许多其他数据科学任务,这些任务我们会使用 Python 等其他数据科学编程语言来完成。
与其在 Python 和 R 之间选择数据科学,为什么不两全其美,成为双语数据科学家呢?到最后,像 R 这样的新编程语言会为你打开不同的工作机会(即使你已经懂 Python)。
在本指南中,我们将学习每个数据科学家都应该知道的一些 R 核心编程概念。您可以将本指南视为您学习 R for data science 的第一步!
注意:在本文的最后,你会发现一个 PDF 版本的 R 备忘单(下面目录中的第 8 节)
**Table of Contents** 1\. [R Variables](#195c)
2\. [R Data Types](#1a9a)
3\. [R Vectors](#3424)
4\. [R If Statement](#7968)
5\. [R While Loop](#37ce)
6\. [R For Loop](#d795)
7\. [R Functions](#475c)
8\. [R for Data Science Cheat Sheet](#4095) (Free PDF)
r 变量
在 R 中,我们使用变量来存储数值,比如数字和字符。为了给变量赋值,我们在 r 中使用了<-。
让我们创建第一条消息“我喜欢 R ”,并将其存储在一个名为message1的变量中。
message1 <- "I like"
现在我们可以在 R 中创建任意多的变量,甚至可以对它们应用函数。例如,我们可以使用paste函数连接 R 中的两条消息。
让我们创建一个值为“R programming”的变量message2,并使用paste函数将其连接到message1。
> message1 <- "I like"
> message2 <- "R programming"
> **paste**(message1, message2)
[1] "I like R programming"
我们甚至可以将输出分配给一个新变量message3。
message3 <- **paste**(message1, message2)
r 数据类型
在 R 中,我们使用变量来存储不同类型的值,比如数字、字符等等。R 中的每个变量都有一个数据类型。以下是 R 编程语言中最常见的数据类型。
- 整数:没有小数点的实数值。后缀
L用于指定整数数据。 - 数字:所有实数的集合
- 复数:r 中的纯虚数。后缀
i用于指定虚部。 - Character:它用于指定变量中的字符或字符串值(一系列字符)。单引号
''或双引号""用来表示字符串。 - 逻辑:也称为布尔数据类型,可以有
TRUE或FALSE值。
下面是 r 中每种数据类型的表示。
x <- 1L *# Integer*
y <- 1.5 *# Numeric*
z <- 2i *# Complex*
message <- "Hi!" *# character*
is_cold <- TRUE *# logical*
为了检查变量的数据类型,我们在 r 中使用了class。
> message <- "Hi!"
> **class**(message)
[1] "character"
我们可以看到,message的数据类型是字符。
r 向量
在 R 中,向量是共享相同数据类型的元素序列。实际上,您可以在一个 vector 中包含不同数据类型的元素,但最终,它们将被转换为相同的数据类型。
为了创建一个矢量,我们使用了c()函数。让我们创建一个名为 countries 的向量,并获取它的数据类型。
> countries <- c(‘United States’, ‘India’, ‘China’, ‘Brazil’)
> class(countries)
[1] "character"
我们刚刚创建了一个字符向量,因为向量的所有元素都是字符串。
向量名称
我们还可以给当前的countries 向量的每个元素添加向量名。让我们创建一个新的population向量,然后添加countries作为它的向量名。
> population <- c(329.5, 1393.4, 1451.5, 212.6)
> names(population) <- countries> population
United States India China Brazil
329.5 1393.4 1451.5 212.6
向量索引
我们可以通过索引获得向量中的特定元素。向量中的每一项都有一个索引(也称为位置)。与其他编程语言不同,R 中的索引从 1 开始。
在 R 中,通过索引访问一个元素,我们使用方括号[]。让我们看几个使用我们之前创建的countries向量的例子。
> countries[1]
[1] "United States"> countries[4]
[1] "Brazil"
我们还可以使用方括号中的c()函数索引多个元素,甚至可以使用向量名来代替索引位置。
> countries[c(1,4)]
[1] "United States" "Brazil"> population[c("United States", "China")]
United States China
329.5 1451.5
矢量切片
向量切片意味着访问向量的一部分。切片是元素的子集。符号如下:
vector[start:stop]
其中“start”表示第一个元素的索引,stop 表示要停止的元素(包括在切片中)。
让我们看一些例子:
> population[1:3]
United States India China
329.5 1393.4 1451.5
太好了!我们已经获得了索引 1 和 3 之间的元素。
过滤
我们可以通过比较过滤掉向量中的一些元素。使用的语法类似于索引,但是我们没有在方括号[]中键入索引,而是使用逻辑运算符进行比较。
让我们过滤掉人口少于 3 亿的国家。
> population[population>300]
United States India China
329.5 1393.4 1451.5
正如我们所看到的,拥有 2.12 亿人口的国家巴西被从我们的向量中过滤掉了。
R 中的 If 语句
就像在任何其他编程语言中一样,if语句在 r 中非常常见。我们用它来决定一个(或多个)语句是否将被执行。
下面是 R 中if语句的语法:
**if** (condition1) {
statement1
} **else if** (condition2) {
statement2
} **else** {
statement3
}
让我们通过一个例子来看看这是如何工作的。下面的代码将基于height变量输出一条消息。
height <- 1.9**if** (height > 1.8){
**print**("You're tall")
} **else** **if**(height > 1.7){
**print**("You're average")
} **else** {
**print**("You're short")
}
代码的意思是“如果你的身高超过 1.8 米,你就是高的;如果身高在 1.7 到 1.8 之间,你就是一般;如果身高在 1.7 或以下,你就是矮的”
在 R 中循环时
只要满足指定的条件,循环就允许我们重复特定的代码块。
下面是 R 中while循环的语法:
**while** (condition) {
<code>
}
让我们通过下面的例子来看看它是如何工作的。
x <- 5**while**(x<10){
**print**(x)
x <- x+1
}
如果我们运行上面的代码,输出将如下:
[1] 5
[1] 6
[1] 7
[1] 8
[1] 9
我们得到的值一直到数字 9,因为在那之后,条件 x <10 was unsatisfied, so the loop broke.
We can also use the 【 keyword to break out of a loop. To do so, we usually combine it with the 【 statement. Let’s see an example.
x <- 5**while**(x<10){
**print**(x)
x <- x+1
**if** (x==8){
**print**("x is equal to 8\. Break loop!")
**break**
}
}
The output will be the following:
[1] 5
[1] 6
[1] 7
[1] "x is equal to 8\. Break loop!"
In this case, the 【 loop broke when x reached the value of 8 because of the new condition we created.
For Loop in R
One of the most common loops in any programming language is the 【 loop. The for loop allows us to iterate over items of a sequence (like our vectors in R) and perform an action on each item.
Here’s the syntax of the 【 loop in R:
**for** (val in sequence)
{
<code>
}
Let’s loop through the 【 vector that we created before and print each item.
> countries <- **c**('United States', 'India', 'China', 'Brazil')> **for** (i **in** countries){
+ **print**(i)
+ }[1] "United States"
[1] "India"
[1] "China"
[1] "Brazil"
We can also use the for loop and if statement together to perform an action to only certain elements.
As an example, let’s loop through the 【 vector but now only print the element “India”
> **for** (i **in** countries){
+ **if** (i=="India"){
+ **print**(i)
+ }
+ }[1] "India"
R Functions
R has different built-in functions that help us perform a specific action. So far we’ve seen the 【 and 【 function, but there are many others. To name a few:
**sum**() -> returns sum
**rnorm**() -> draw random samples from a distribution
**sqrt**() -> returns the square root of an input
**tolower**() -> converts the string into lower case
But that’s not all! We can also create our own function in R. We have to follow the syntax below.
function_name <- **function**(<params>){
<code>
**return**(<output>)
}
Let’s create a function that converts the temperature from Fahrenheit to Celsius.
fahrenheit_to_celsius <- **function**(temp_f){
temp_c <- (temp_f - 32) * 5 / 9
**return**(temp_c)
}
Now if we call the function 【 we will convert the temperature from Fahrenheit to Celsius.
> fahrenheit_to_celsius(100)
[1] 37.77778
Learning Data Science with R? 通过加入我的 20k+人的电子邮件列表来获得我的免费 R for Data Science 备忘单。
如果你喜欢阅读这样的故事,并想支持我成为一名作家,可以考虑报名成为一名媒体成员。每月 5 美元,让您可以无限制地访问数以千计的 Python 指南和数据科学文章。如果你用我的链接注册,我会赚一小笔佣金,不需要你额外付费。
https://frank-andrade.medium.com/membership
如果您处理文本数据,请学习这些 Pandas 方法
原文:https://towardsdatascience.com/if-you-work-with-textual-data-learn-these-pandas-methods-3f224122ebaf
文本数据比数字携带更多的信息

乌列尔·索伯兰斯在 Unsplash 上拍摄的照片
尽管我喜欢与数字打交道,但文本数据仍然存在于我的工作流程中。我没有测量或分析它,但我感觉我拥有的文本数据量超过了数字数据量。
文本数据和数字数据之间最显著的区别是它们需要的清理和预处理的数量。
数字通常采用可以直接用于分析或建模的格式。如果没有,他们需要一些小的接触来准备好。
另一方面,文本数据可能有多种格式。此外,同一属性的不同观察结果可能不会以标准格式表示。
在 Python 中,文本数据被表示为字符串。字符串是一系列 unicode 字符。与其他一些编程语言不同,Python 没有字符数据类型,所以单个字符是长度为 1 的字符串。
谢天谢地,Pandas 简化并加速了文本数据的处理。在这篇文章中,我们将回顾熊猫用于这一目的的方法。
让我们首先创建一个填充了模拟文本数据的样本数据帧。
import pandas as pddf = pd.DataFrame({
"first_name": ["John","jane","emily","Matt","Alex"],
"last_name": ["Doe","doe","uth","Dan","mir"],
"group": ["A-1B","B-1B","A-1C","A-1B","C-1C"],
"salary": ["$75000","$72000","£45000","$77000","£58,000"]
})df

df(作者图片)
字符串数据类型
默认情况下,字符串以“对象”数据类型存储。这可能会导致一些缺点,因为非字符串数据也可以用这种数据类型存储。因此,在 Pandas 版本中,引入了一种新的字符串数据类型:“string type”。
到目前为止,使用“StringDtype”是可选的,但将来可能会要求这样做。
Pandas 字符串方法可以通过 str 访问器来访问。
裂开
一个字符串可能包含多条信息。例如,典型的地址显示街道、城市和州的信息。在我们的模拟数据帧中,group 列由两部分组成,并用连字符连接。如果我们想在两个单独的列中表示组,我们可以拆分它。
df["group"].str.split("-")**# output**
0 [A, 1B]
1 [B, 1B]
2 [A, 1C]
3 [A, 1B]
4 [C, 1C]
Name: group, dtype: object
输出值是包含拆分后创建的每个项目的列表。我们可以使用 expand 参数创建单独的列。
df["group"].str.split("-", expand=True)**# output** **0 1** 0 A 1B
1 B 1B
2 A 1C
3 A 1B
4 C 1C
Name: group, dtype: object
现在输出是一个 DataFrame,所以我们可以用它来创建新的列。
df["group1"] = df["group"].str.split("-", expand=True)[0]
df["group2"] = df["group"].str.split("-", expand=True)[1]df

df(作者图片)
结合
就像我们拆分字符串一样,我们有时也需要组合它们。让我们创建一个包含名字和姓氏的姓名列。我们有两个选择:
- Cat 方法
- +运算符
**# method 1**
df["first_name"].str.cat(df["last_name"], sep=" ")**# method 2**
df["first_name"] + " " + df["last_name"]**# output of both 1 and 2**
0 John Doe
1 jane doe
2 emily uth
3 Matt Dan
4 Alex mir
dtype: object
这里需要注意的一点是,这些列中的字符是大小写混合的。Python 认为“doe”和“Doe”不是一回事。因此,在组合它们之前,我们需要将它们全部变成小写或大写。另一种选择是将它们大写,这意味着只有第一个字母是大写的。
**# lowercase**
df["first_name"].str.lower() + " " + df["last_name"].str.lower()**# output**
0 john doe
1 jane doe
2 emily uth
3 matt dan
4 alex mir
dtype: object----------------------------------------------------------------
**# capitalize** df["first_name"].str.capitalize() + " " + df["last_name"].str.capitalize()**# output**
0 John Doe
1 Jane Doe
2 Emily Uth
3 Matt Dan
4 Alex Mir
dtype: object
我们可以使用上面的输出来创建一个新的列。我希望 name 列是第一列,所以我将使用 insert 函数,该函数允许在特定位置创建一列。
name = df["first_name"].str.lower() + " " + df["last_name"].str.lower()df.insert(0, "name", name)df

df(作者图片)
索引
我们有时需要从字符串中提取数字数据。薪水栏是这种情况的典型例子。我们需要的是去掉货币符号和逗号。
我们已经提到字符串是字符序列,所以我们可以使用索引来访问字符。由于货币符号是第一个字符,我们可以通过选择从第二个字符开始的字符来删除它们。
df["salary"].str[1:]**# output**
0 75000
1 72000
2 45000
3 77000
4 58,000
Name: salary, dtype: object
在其中一个值中,逗号被用作千位分隔符。我们可以使用 replace 方法删除它。
df["salary"].str[1:].str.replace(",","")**# output**
0 75000
1 72000
2 45000
3 77000
4 58000
Name: salary, dtype: object
逗号已被空字符串替换,这相当于删除了它。我们在上面的例子中看到的另一个很酷的事情是,多个字符串操作可以在一个步骤中完成。
如果我们想使用这些清理后的薪水值来创建一个数字列,我们还需要更改它的数据类型。让我们一步到位。
df["salary_numeric"] = df["salary"].str[1:].str.replace(",","").astype("int")df.dtypes**# output**
name object
first_name object
last_name object
group object
salary object
group1 object
group2 object
salary_numeric int64
dtype: object
编码分类值
我们已经清理了字符串列,但如果我们要在机器学习模型中使用它们,这可能还不够。一些算法不接受字符串值,所以我们需要通过标签编码或一键编码将它们转换成数值。
标签编码只是用数字替换字符串。让我们对“group1”列执行标签编码。我们可以手动用数字替换数值,但这是一项繁琐的工作。此外,不同值的数量很大,因此这种方法肯定不实用。
更好的选择是将该列的数据类型更改为 category,然后使用类别代码。
df["group1"] = df["group1"].astype("category")df["group1_numeric"] = df['group1'].cat.codesdf[["group1", "group1_numeric"]]

(图片由作者提供)
每个类别由一个数字代替。然而,除非在类别之间存在层次,否则标签编码不适用于某些算法。在上面的例子中,类别 C 可能被赋予更高的重要性。
在这种情况下,我们应该进行一次性编码,这意味着为每个不同的值创建一个新列。get_dummies 函数可用于对一列或多列进行一次性编码。
pd.get_dummies(df["group1"])

一键编码(图片由作者提供)
“group1”列中的值是 A,然后 A 列中的值变成 1,依此类推。
你可以成为 媒介会员 解锁我的全部写作权限,外加其余媒介。如果你已经是了,别忘了订阅https://sonery.medium.com/subscribe如果你想在我发表新文章时收到电子邮件。
*https://sonery.medium.com/membership
感谢您的阅读。如果您有任何反馈,请告诉我。*
如果你的模型表现不佳,建立更好的数据集
如果你的数据很差,这不是你的模型的错

Johannes Plenio 在 Unsplash 上拍摄的照片
处理数据可能很难。你可能在你的模型或分析上花费数小时,却没有得到任何合理的结果。在这种情况下,很容易将性能问题归咎于错误的方法选择。毕竟,有这么多的算法,必须存在一个候选人,将解决问题,对不对?
然而,更多的时候,潜在的问题是数据本身。事实上,只要你有一个好的数据集,你可以用非常简单的模型走得很远。因此,在本文中,我们将探索四种方法来改善后者。
首先,我们将看看“只是”增加可用数据大小的并不令人惊讶的方法。虽然这确实是一个显而易见的解决方案,但我们将探讨一些有趣的注意事项。其次,我们将考虑提高数据集质量的方法,即如何构建更好的数据集。

如何构建更好的数据集——简单概述(图片由作者提供)
更广泛的数据——维度的祝福
有没有一种方法可以极大地改善数据集,以至于一个简单的 if-else 规则会胜过一个复杂的深度学习模型?答案是肯定的。考虑下面的一维二元分类问题:

玩具分类问题——你能找到区分蓝色(0 类)和红色圆点(1 类)的规则吗?(图片由作者提供)
问问你自己,你手头的最佳模型在这里是否能表现得相当好。不幸的是,条件类分布似乎是完全随机的。即使有最先进的模型和高端硬件,您也无法构建一个合理的预测解决方案。
如果我告诉你我没有使用任何随机值就创建了数据集,会怎么样?以下是解决方案:

考虑第二个变量突然使问题变得微不足道(图片由作者提供)
遗漏一个关键的第二变量将一个简单的预测问题变成了一个难题。那么,这个微不足道的例子有什么实际后果呢?
虽然你目前的功能看起来不错,但你可能还缺少其他不太明显但却至关重要的功能。例如,如果没有季节性和周末特征,预测产品销售可能会很困难。此外,正如这个玩具示例所示,两个或更多的特征甚至可能只在相互作用时具有预测性。
反过来,添加越来越多的功能是否总是一个好主意?当然不是。你可能知道,预测建模的一个重要部分是变量选择。仅仅为了包含更多的候选特性,肯定会使这一步变得更加繁琐。
如果这个额外的功能看起来很有前景,它可能会带来完全的不同。总是质疑你手中的数据是否足以解决你的问题。
为什么维度越多越好?一些理论
作为一个简单的例子,考虑三个变量,X,Y,Z,其中Z是目标变量。同样,让所有三个变量遵循一个多元高斯分布。对于均值向量和协方差矩阵,我们有:

(图片由作者提供)
将定律应用于条件高斯方差两次,我们得到:

(图片由作者提供)
这意味着在两种情况下,使用更多的解释变量可以减少预测的不确定性:
- 相关性:所有解释变量都与目标相关
- 无冗余:解释变量之间相关性不高
此外,正如这些讲座幻灯片显示的线性回归,你需要注意维数灾难。模型复杂性的显著增加需要更多的数据点或更强的正则化。否则,你可能会得到一个比以前更差的模型。
我应该在哪里期待丢失的列?
- 不完整的信息无处不在:如果你想得够久,你几乎总能在你的数据中找到信息缺口。不幸的是,收集更多的数据并不总是微不足道的,而且往往是不可能的。试着在太少的信息和太多的努力或成本之间找到一个平衡点。
- 图像数据:这里,相当于未观察列的是未观察像素。更高分辨率的图像可能是答案。但是,要注意维数灾难。
如何获得更多维度,以及如何获得正确的维度:
- 与领域专家密切合作,或者自己成为一名专家:主题专家通常能够准确指出建模给定问题所需的信息。
- 在替代数据方面要有创造性:在替代数据集的创造性使用方面,华尔街可以成为一个激励人心的例子。例如,众所周知,一些对冲基金使用停车场卫星数据来预测零售公司的季度销售数字。
- 增加粒度:正如图片示例中提到的,使用更细粒度的数据可以为您的模型添加重要信息。考虑 BERT 和大多数其他现代 NLP 算法,它们通常以单词片段而不是完整的单词作为输入。
更长的数据——如果你不能把这些点连接起来,你的模型又怎么可能呢?
任何处理数据的人都知道,数据点多总比少好。在大多数情况下,额外的数据存储是廉价的。因此,您应该能够从模型中排除数据,而不是一开始就没有数据。

哪个函数最能描述数据?只有两个数据点,很难说——即使是对最先进的人工智能来说。(图片由作者提供)

有了 10 个数据点,事情看起来清楚多了。在高维度中,你需要更多的观察来获得相似的效果。(图片由作者提供)
让我们看看一些理论上的考虑:
为什么数据越多越好—从均方误差的角度
考虑现代机器学习的核心概念,经验风险最小化。我们在实际目标和预测目标之间有一个损失函数:
常见的选择是平方损失

(图片由作者提供)
理想情况下,我们希望选择一个最佳候选模型,使数据生成分布的预期损失(也称为风险)最小化:

(图片由作者提供)
由于产生数据的分布通常是未知的,我们需要通过经验风险来估计实际风险:

(图片由作者提供)
利用之前的平方损失,我们获得了流行的均方误差目标:

(图片由作者提供)
在一般情况下,经验风险估计量具有以下统计特性:

(图片由作者提供)
简单地说,经验风险估计值是
- 无偏 —平均而言,针对经验风险的优化相当于针对真实风险的优化
- 一致 —随着样本量的增加,经验风险和真实风险之间出现较大偏差的可能性降低
作为一个警告,大样本量只能保证你能更好地找到真正的风险最优模型。如果你的搜索算法很差,你可能仍然会比样本少但搜索策略好的结果更差。深度学习中的多重局部最优问题就是其中的一个例子。
此外,从理论上讲,如果真实风险的均值或方差不存在,任何基于经验风险的优化都是有缺陷的。如果你的数据是重尾,这就可能发生。Nassim Taleb 在这个视频中对这个问题有一些有趣的看法。
您可能会丢失模型的一些观察值
- 敏感数据:存在法律或其他政策不允许访问完整数据集的情况。联合学习可能是这种情况下的解决方案。
- 选择退出或选择加入政策:如果你的用户不想让他们的数据被收集,你除了接受他们的决定之外,什么也做不了。在这种情况下,你必须接受更少的数据,并充分利用这些数据。
- 数据丢失或删除:理想情况下,意外的数据丢失永远不会发生。因为我们并不是生活在一个完美的世界里,所以总是考虑这种最坏的情况。
如何获得更多的观测值或处理太少的观测值
- 提高数据采集的采样率:如果可能的话,尝试提高数据采集的频率——例如,如果您正在处理传感器数据。您可以随时切换到较低的采样速率,但不能反过来。
- 降低数据的维度:通常,你的模型越复杂,你需要的数据就越多。如果您必须使用较少的数据点,减少数据的维数可以提高模型的准确性。
- 使用模型正则化和先验知识:正则化通常被教授,它比仅仅使用 L1/L2 规范要深入得多。例如,贝叶斯机器学习是一个通过先验知识进行正则化的数学上合理的框架。这远远超出了标准的正则化技术。
噪音较小的数据—给我一个信号
谈到噪声,我们需要区分两种类型:
- 预测噪声:一个更好的术语应该是“随机性”。虽然您可能观察到目标变量没有失真,但您无法确定地预测它。包括更多的预测特征可以改善这种类型的噪声。
- 扰动噪声:又称测量误差。这就是我们想在本节讨论的噪声类型。我们没有观察到变量本身,而是观察到它的一些扭曲的版本。例如,考虑在地震中收集人体运动数据。
正如您可能想象的那样,应该避免有噪声的数据,或者将噪声降至最低。下面是一个简单的线性回归示例,说明在噪声环境下预测质量会发生什么变化。
我们从以下无噪声数据生成模型开始:

(图片由作者提供)
代替原始目标和特征,我们观察其噪声版本:

(图片由作者提供)
现在,我们设想两种情况:
- 真正的随机(零均值)噪声:误差被平均“抵消”。当你用随机摇晃的相机拍照时,这种情况可能会发生。
- 系统(非零均值)噪声:你的观察结果平均来说是失真的。相机镜头上的污点可能会导致图像数据出现这种情况。
让我们比较随机和系统测量误差对回归示例的影响:

对于零均值高斯噪声,预测变得不太准确(图片由作者提供)

如果测量误差是系统性的,预测变得更糟甚至更快(图片由作者提供)
在非零均值噪声情况下,模型失真比零均值情况下严重得多。对于真实世界的数据,后果可能或多或少有些严重。无论哪种方式,噪音的影响肯定没有在实验室条件下那么容易分析。
对带有高斯噪声的高斯数据的深入观察
让我们考虑另一个简单的双变量正态例子,其均值和协方差如下:

(图片由作者提供)
如果使用线性回归,对于任意多个数据点,我们得到以下参数:

(图片由作者提供)
现在,我们用独立的高斯噪声污染这两个变量:

(图片由作者提供)
这导致了一个扭曲的双变量高斯分布:

(图片由作者提供)
这允许我们在以下条件下导出回归参数

(图片由作者提供)
这些公式意味着什么
- 目标中的零均值噪声:如果只有目标被零均值噪声破坏,如果样本量足够大,您的参数仍然是正确的。随着噪声方差的增加,您可能需要更大的样本量。
- 输入中的零均值噪声:在这种情况下,输入特征的预测能力相对于噪声量有所减弱。因此,根据严重程度,降噪可以将以前无用的特征变成高度可预测的特征。
- 非零平均噪声:您的参数估计值和预测值会有偏差。你应该不惜一切代价避免这种系统性的测量误差。
当然,现实世界中的噪声通常要复杂得多。噪声可能随时间变化,或者只在一个方向上污染你的变量。上面的例子应该给你一个大概的概念,为什么限制测量误差的影响很重要。
哪里可以期待嘈杂的数据?
- 传感器和图像数据:受污染的传感器或相机镜头很容易给你的数据带来不必要的噪音。此外,噪音可能暗示你的数据收集设备的技术能力或杂质。如果你目前的测量误差水平无法忍受,你可能需要一个更好的。
- 文本数据:社交媒体数据可能特别嘈杂——用户在 Twitter 上的提及或文本中的 URL 很容易使 NLP 工具产生偏见。
如何从两个角度降低噪音
- 事前去噪:理想情况下,你应该在测量误差进入你的数据集之前阻止它。对于计算机视觉问题,这可能就像保持相机镜头清洁一样简单。
- 事后去噪:如果无法避免测量误差的发生,就需要借助无数的方法对数据去噪。快速谷歌搜索应该是一个很好的起点。
更好的数据采样——忠实于数据生成过程
现在我们来看潜在数据集改进的最微妙的形式。如果上述问题是显而易见的,或者至少是可以预见的,那么这里的情况可能就不是这样了。错误采样的数据可能看起来很好,但仍然会导致错误的模型或错误的结论。
在理想情况下,我们可以从底层数据生成分布中抽取真正随机的样本。然而,在现实中,完美的随机抽样几乎是不可能的。这最迟会发生在你根据过去的数据预测未来的数据的时候。
在这种情况下,数据生成分布可以任意延伸到未来。然而,由于你不能收集未来的数据,你的模型将偏向于过去。一个今天能很好地预测购买者偏好的模型可能很难应对未来消费者行为的变化。
这就是臭名昭著的分布式或域转移问题。无论你的模型此刻有多优秀,你都可能看到它们的性能在任何时候消失。幸运的是,这也是一个众所周知的问题,有许多方法可以在一定程度上缓解这个问题。
不过,请记住,畴变并不是采样偏差的唯一例子。分布可能非常稳定,但是您的采样过程本身仍然可能有缺陷。
具有扭曲抽样分布的经验风险最小化
再次考虑经验风险估计的统计特性:

(图片由作者提供)
这些公式中一个至关重要的细节是p(x,y)。除非你的样本来自真实的数据生成过程,否则你的风险估计是有缺陷的。如果我们的抽样分布不同,比如说q(x,y),就没有办法保证我们正在为正确的风险进行优化。这可以通过一个简单的实验来说明:
无定义域偏移的有偏采样过程的一个具体例子
假设你的花园里有一台摄像机,你想对在摄像机里玩耍的动物进行分类。因此,你的目标是建立和训练一些卷积神经网络分类器。
假设有四种可能的动物,猫,狗,兔马。为了简单起见,我们还假设每一个都同样有可能出现在你的花园里:

可能在你的花园里玩耍的四种动物的分布。每种动物发生的可能性都是一样的。(图片由作者提供;参见末尾的附加资源)
作为一个真正的爱好者,你在接下来的几天里为各自的动物拍了很多照片。然而,由于你主要关注的是猫,所以收集到的猫图片的数量要大得多。示例中图片的分布可能如下所示:

基于你偏猫摄影技巧的四种动物分布图(图片由作者提供;参见末尾的其他来源)
现在,我们在花园中动物的分布和样本中动物图像的分布之间有了分歧。由于有偏差的采样过程,猫出现在训练集中的机会要大得多。样本不是完全随机抽取的:

问题的大“图景”——您的训练集和实际评估集的分布不匹配。(图片由作者提供;参见末尾的附加资源)
为了进一步简化,假设你只有两个候选的计算机视觉模型。当然,在现实中,你通常有无限多的候选模型。例如,对于神经网络,每个可能的参数配置都是单独的候选。最佳模型的搜索算法通常是梯度下降法。
无论如何,假设我们的两个候选人具有以下属性:

候选模型 1 正确分类所有图像,除了猫图像(图片由作者;末尾的附加源)

候选模型 2 错误地分类了来自狗和马的所有图像(图片由作者提供;文末附加来源)
有偏采样过程的影响
为了从两个模型中选择最佳模型,您应用经验风险最小化。假设您想将的零一损失降至最低:

(图片由作者提供)
现在,我们可以计算给定自然分布的候选模型 1 的预期经验风险:

(图片由作者提供)
如果我们对候选人和分布都这样做,我们会得到以下结果:

(图片由作者提供)
显然,一旦我们想在“生产”中使用我们的模型,候选 1 是更好的。然而,由于我们有偏见的抽样过程,次等候选模型 2 显得更有吸引力。根据上述公式,风险估计值的方差随着样本的增加而减小。因此,更多的数据点实际上会增加选择错误模型的机会。
实际上,事情当然要复杂得多。即使有轻微偏差的数据,你仍然可能得到一个足够强大的模型。另一方面,你永远无法保证你的模型不会随着时间的推移而受到分布变化的影响。这使得抽样偏差问题成为一个永久的主题,你应该永远记住。
你可能会遇到有偏差的采样
- 数据是随着时间的推移而获得的:同样,分布发生了变化。几乎每个机器学习数据集都是如此。只要分布变化不是太不稳定,你通常可以用一种合理的方式来处理这个问题。
- 数据被系统地更改或删除:当用户通过 GDPR 或相关表格选择退出时,可能会出现这种情况。如果这个过程不是独立于用户特征的,那么在你的采样过程中会有一些偏差。
如何减轻或减少有偏采样的影响
- 尽可能密切地监控您的模型:如果您正在遵循 MLOps 最佳实践,您应该已经熟悉这一点。
- 在您的模型中包含偏差变量:例如,将时间戳作为一个单独的变量来考虑可能会缓解域随时间推移而移动的问题。只要分配变化的模式本身保持不变,这可能是一个可行的解决方案。然而,请记住,仍然不能保证恒定畴变的假设。
- 监控并优化抽样程序:如果你能控制抽样过程,确保它尽可能接近理想的随机抽样。
- 考虑在线学习:为了快速适应变化的分布,你应该尽可能频繁地更新你的模型。事实上,网上学习是最快的方法。如果您能够负担实时更新模型的额外工作,您应该尝试一下这个想法。
- 考虑重新加权或重新采样:如果以上都不可行,你还可以尝试逆概率加权等方法。
结论
如果您一直读到这里,您可能已经意识到,当涉及到数据时,总是有改进的空间。虽然你不能整天优化单个数据集,但它们的质量对于有效的数据科学和机器学习来说是必不可少的。因此,如果你的模型看起来没有改进,考虑仔细看看输入。
图像来源
- 马——照片由海伦娜·洛佩斯在 Unsplash 上拍摄
- 狗——照片由marli ese stree land在 Unsplash 上拍摄
- 猫——Raoul Droog 在 Unsplash 上拍摄的照片
- 兔子——照片由加里·本迪格在 Unsplash 拍摄
参考
【1】格罗夫斯、罗伯特·m 等著《调查方法论》。约翰·威利的儿子们,2011 年。
【2】莫赫利、梅赫亚尔、阿夫申·罗斯塔米扎德、阿米特·塔尔瓦尔卡尔。机器学习基础。麻省理工学院出版社,2018。
Wooldridge,Jeffrey M .计量经济学导论:现代方法。Cengage learning,2015。
原载于 2022 年 3 月 31 日 https://sarem-seitz.comhttps://sarem-seitz.com/blog/if-your-models-are-underperforming-build-better-datasets/。****
我是一名自学成才的数据科学家。以下是我对新人的 3 点建议
让你的学习之旅更加高效。

凯利·西克玛在 Unsplash 上的照片
我的数据科学之旅始于 2019 年。那些在 Medium 上关注我的人会知道,我喜欢分享我学习数据科学的经验。我写我犯的错误,我面临的挑战,我经常使用的工具,等等。
在本文中,我想向那些计划成为数据科学家或刚刚开始学习数据科学的人分享 3 条建议。这些都是基于我自己的经验和我在数据科学生态系统中观察到的。
事不宜迟,我们开始吧。
1.敏捷
越来越多的企业投资数据科学,目的是将数据转化为价值。这种价值的形式取决于企业和行业。
- 零售商使用数据科学,通过创建准确而强大的机器学习模型来更有效地管理库存。
- 工厂收集和分析大量传感器数据,用于预测性维护。
- 我看到一些餐馆实施图像处理系统来检测哪些物品被放入垃圾桶。这让他们可以更好地管理他们应该煮多少。
这样的例子不胜枚举。各行各业都有大量的数据科学应用和产品。
在这个不断扩展的数据科学世界中,工具和技术也在不断发展和扩展。为了保持竞争力,数据科学家应该保持敏捷,轻松适应变化。
一旦你习惯了使用一个特定的工具,学习一个新的工具似乎是浪费时间。然而,新工具可能会提供更好的性能。尽可能地跟随技术和研究的进步,不要犹豫尝试新的工具。
当然,这并不意味着你应该学会如何使用现有的一切。这是不可能或不可行的。随着你在这个领域获得更多的经验,你会意识到什么是有前途的,有值得发现的潜力。不过最基本的要求是你需要为改变做好准备。
2.证书很重要,但不算数
学习数据科学的资源数量和种类是巨大的。可以看书,看教程,上网络课程等等。
此外,还有证书的现实。您可以找到几乎任何数据科学主题的证书。有些涵盖更广泛的范围,而有些侧重于特定的任务,如熊猫的数据清理。
如果你要走一条自学之路,证书首先会派上用场。我开始收集了一些。证书的两个重要优势是:
- 它们比传统的学习方法如硕士学位要便宜得多。
- 他们通常是有组织的,结构良好,所以你可以很快熟悉这个领域。
虽然我同意证书的好处,但我建议不要过于关注证书。拥有 20 个证书不会对招聘经理或招聘人员产生重大影响。我觉得他们不会去翻一张 20 个证书的单子。
同样,你从证书中学到的东西也是有限的。大部分都需要看教程,解决简单的习题。看教程就能理解一个话题。然而,为了真正学会它,你需要亲身体验和积极参与。
3.做一个模仿整个工作流程的项目
从外部来看,数据科学家的工作似乎是分析数据以提取见解并创建模型。至少我是这么想的。
现在我加入了,我对数据科学家工作的想法非常不同。当然,从数据中提取见解或创建模型是其中重要的一部分。然而,在大多数情况下,对数据科学家的期望不止于此。
它大量涉及数据工程师的工作。例如,作为一名数据科学家,您可能需要参与 ETL 过程。根据公司的不同,你可能需要处理一些软件工程任务。
我认为最具挑战性的部分是在生产和规模上进行机器学习。
假设您被分配创建一个用于销售预测的机器学习模型。学习数据科学时,我们通常在 Jupyter 笔记本上工作。然而在现实生活中,您的模型需要在生产中部署。这可能是你的责任,或者你需要参与其中。
无论如何,我建议让自己熟悉生产中用于机器学习的工具。顺便说一下,它不一定是机器学习。相反,它可以从几个不同的来源收集数据,清理和组合它们,并执行一些分析。共同的部分是需要在生产中完成。
对这些任务最有帮助的是做一个涉及生产中典型数据科学工作流程步骤的项目。
以下是一个项目的建议:
- 收集存储在云中的数据(例如 S3 桶)
- 运行清理和预处理数据的脚本
- 创建一个机器学习模型,并用预处理后的数据进行训练
- 做预测
- 将预测写到云上
整个过程可以在 EC2 服务器上运行,并使用 Airflow 进行编排。通过成功完成本项目,您将在以下领域获得实践经验:
- 云计算
- 数据清理和预处理
- 机器学习
- 数据管道和工作流的编排
我很确定这个项目比完成很多证书更有帮助。
你可以成为 媒介会员 解锁我的全部写作权限,外加其余媒介。如果你已经是了,别忘了订阅https://sonery.medium.com/subscribe如果你想在我发表新文章时收到电子邮件。
*https://sonery.medium.com/membership
感谢您的阅读。如果您有任何反馈,请告诉我。*
用 OpenCV 检测运动——适合初学者的图像分析
如何用 OpenCV 检测和分析运动物体

我们探测到了移动!(图片由作者提供)
运动检测有许多目的。一旦在野生动物摄像机或安全摄像机上看到运动,您就可以使用它开始记录,例如,另一个应用是提高性能。我们不需要分析整个图像,只需要处理移动的小部分。比如只识别上图中行驶汽车的颜色。
在本文中,我们将创建一个完全工作的运动检测器,可用于上述所有用例。在这个过程中,我们会学到很多关于用 OpenCV 处理图像的知识。在本文结束时,您将拥有一个完全可操作的运动检测器和更多关于图像处理的知识。我们来编码吧!
系列
本文是关于 OpenCV 图像处理系列的一部分。查看其他文章:
- 读取图像、视频、您的屏幕和网络摄像头
- 检测和模糊人脸
- 用模板匹配破坏猎鸭:在图像中寻找图像
- 创建运动检测器(📍你在这里!)
- 检测没有 AI 的形状(在建;即将推出)
- 从图像中检测和读取文本(正在建设中;即将推出)
设置
我们的客户要求我们开发一些分析城市交通的软件。他们想知道在某一天有多少自行车、汽车、公共汽车和行人访问某一特定位置。我们安装了一个摄像头来拍摄这些地点。
数据科学团队有一些非常奇特的模型来确定特定图像是自行车、公共汽车还是人的汽车,但是他们不能在整个图像上运行这个模型。我们的任务是对图像进行预处理。我们需要一些小片段来运行识别模型。
我们实际上如何检测运动?我们将把我的网络摄像头中的视频流的每一帧与前一帧进行比较,并检测所有发生变化的点。最终结果看起来会像这样:

我们的最终结果
现在,数据科学团队可以在每个绿色矩形上运行他们的模型,而不是整个屏幕。更小,因此,更快。我们来编码吧!
属国
我们将把 YouTube 上我的美丽城市格罗宁根的实时网络摄像头放在屏幕上并录制下来,而不是看一个真实的网络摄像头。如果你想使用另一个来源,那么看看这篇文章https://mikehuls.medium.com/image-analysis-for-beginners-how-to-read-images-video-webcam-and-screen-3778e26760e2来学习如何阅读它。
在我们的案例中,我们需要以下进口:
pip install opencv-python pillow
创建运动检测器
让我们编写一个运动检测器!我们会一部分一部分的来。下面将提供完整的代码。
第一步。阅读和准备我们的框架
首先,我们将实际读取图像,并将其从 OpenCV 的默认 BGR 转换为 RGB:
如果你读过前一篇文章,这并不新鲜;我们加载我们的图像,将它的颜色转换成 RGB(这样我们可以在以后显示它)。
接下来的一点更有趣。我们将图像转换为灰色,并通过模糊图像来平滑图像。转换为灰色会将所有 RGB 像素转换为 0 到 255 之间的值,其中 0 表示黑色,255 表示白色。这比处理三个值(R、G 和 B)要快得多。这是结果:

我们的原始图像和经过处理的图像(作者提供的 gif)
第二步。确定运动(与前一帧相比的变化)
在这一部分,我们将进行实际的运动检测。我们将通过检查像素值来比较前一帧和当前帧。请记住,因为我们已经将图像转换为灰色,所以所有像素都由 0 到 255 之间的单一值表示。
在下面的代码中,如果没有帧,我们设置一个前一帧。然后我们用absdiff 的方法计算这个差值。之后,我们更新之前的框架。接下来,我们淡化了一点图像。膨胀填补了空洞,连接了区域;它通过增加尺寸和亮度使微小的差异变得更加清晰。
我们对稍微亮一点或暗一点的像素不感兴趣;我们使用cv2.threshold函数将每个像素转换为 0(白色)或 1(黑色)。这个的门槛是 20;如果当前帧和前一帧之间的阴影差大于 20,我们就把那个像素变成白色,否则我们就把它变成黑色。看起来是这样的:

跟踪变化的像素(作者提供的 gif)
请注意,白点比实际移动的物体要大得多。这是淡化形象的结果。在下一部分定义轮廓时,较大的区域将有助于我们。
第四步。寻找区域和轮廓
我们希望找到自上一帧以来发生变化的区域,而不是每个像素。为了做到这一点,我们首先需要找到一个区域。这就是cv.findContours所做的;它从上面部分的每个白点中检索轮廓或外部界限。在下面的代码中,我们找到并绘制了所有的轮廓。
两行!还不错。看看下面的结果。请注意,我们甚至在追踪鸟类!

在移动的物体上画出我们的轮廓(作者图片)
虽然这看起来很漂亮,但我们向数据科学团队承诺了图像,所以让我们找到这些区域的坐标,然后就到此为止:
这将再次找到我们的轮廓。然后,我们将循环遍历,丢弃任何太小的区域,并从这些区域中检索坐标。这些是我们必须发送给数据科学团队的坐标。现在,我们只是用cv2.boundingRect在它周围放一个绿色的矩形。
第五步。最后一步
在上一步中,我们已经在 RGB 框架上绘制了图像,因此在最后一步中,我们只展示结果:
cv2.imshow('Motion detector', img_rgb)
if (cv2.waitKey(30) == 27):
break
在下面的 gif 中,你可以看到我们分析的最终结果。查看 YouTube 上的https://www.youtube.com/watch?v=q2Urd1GNGMM和 这段 视频,了解更多演示。所有代码都可以在这里获得。****

最终结果(作者 gif)
下面是一个快速的并排,我们比较我们的输入帧,处理帧和输出。的确非常漂亮!

所有步骤并排(作者 gif)
结论
在 OpenCV 系列的这一部分中,我们研究了运动检测器的工作原理。在这个过程中,我们处理图像;模糊,稀释,寻找帧之间的差异。此外,我们进行了一些非常方便的分析;寻找轮廓和边界框。最后,我们学习了在图像上绘制矩形和轮廓。
别忘了查看本系列的其他文章!
如果你有建议/澄清,请评论,以便我可以改进这篇文章。同时,看看我的其他关于各种编程相关主题的文章,比如:
- Python 为什么慢,如何加速
- Python 中的高级多任务处理:应用线程池和进程池并进行基准测试
- 编写自己的 C 扩展来加速 Python x100
- cyt hon 入门:如何用 Python 执行>每秒 17 亿次计算
- 用 FastAPI 用 5 行代码创建一个快速自动记录、可维护且易于使用的 Python API
- 创建并发布你自己的 Python 包
- 创建你的定制私有 Python 包,你可以从你的 Git 库 PIP 安装
- 完全初学者的虚拟环境——什么是虚拟环境以及如何创建虚拟环境(+示例)
- 通过简单升级,显著提高数据库插入速度
编码快乐!
—迈克
页(page 的缩写)学生:比如我正在做的事情?跟我来!
用 OpenCV 破坏猎鸭——初学者的图像分析
编写代码,将击败每一个鸭子狩猎高分

让我们让这只狗工作吧!(图片由作者提供)
猎鸭辛苦;让我们写一些代码,甚至不需要触摸我们的鼠标就可以获得高分!我们将应用一些 OpenCV 技术来分析我们的屏幕,搜索鸭子并通过点击它们来拍摄它们。它每秒钟会这样做很多次,所以不要指望再看到那只狗嘲笑你了!
在这篇文章的结尾,你将能够在不接触鼠标的情况下击败你自己的游戏的高分。点击查看最终结果。

怪物杀死:我们每帧拍摄多只鸟!(图片由作者提供)
系列
本文是关于 OpenCV 图像处理系列的一部分。查看其他文章:
- 读取图像、视频、屏幕和网络摄像头
- 检测和模糊人脸
- 用模板匹配破坏猎鸭(📍你在这里!)
- 创建运动检测器
- 检测没有 AI 的形状(在建;即将推出)
- 从图像中检测和读取文本(正在建设中;即将推出)
猎鸟
很明显我们不是在玩原来的游戏。那需要用 NES Zapper 来演奏。我们将会玩一个在线版本,在这个版本中我们只需要点击鸟就可以了。我们的目标如下:
- 记录我们的屏幕,以便我们可以分析帧
- 预处理帧并找到一只鸟
- 如果发现一只鸟:移动鼠标到那个位置并点击
首先,我们安装我们的依赖项,这很简单:
pip install opencv-python pillow
我们的进口看起来像这样:
import math
import cv2
import numpy as np
from PIL import ImageGrab
1.记录我们的屏幕
在这方面,我们将从我们的功能,可以找到并拍摄鸟类(完整的代码可在底部)。
在前几行中,我们定义了几个常量。在第 4–7 行,我们指定了我们想要记录的屏幕部分。我们将这个边界框传递给第 19 行的 ImageGrab 方法;这将在这些坐标上“截屏”我们的屏幕。请注意,这是一个 BGR 格式的图像,而不是 RGB。更多在 这篇文章 讲的是为什么,我们只要记住最后要换算就行了。
接下来,我们定义模板,如下图所示:

鸭子的眼睛;我们代码的模板(图片由作者提供)
这是鸟眼大部分时间的样子(除非是水平飞行,我们后面会看到)。最重要的是,鸟的眼睛看起来不一样,当他们死了,所以我们不要浪费弹药射击已经被击中的鸟。我们还提取模板的高度和宽度以备后用。
在第 13 行,我们开始记录我们的屏幕。我们不需要每一帧都搜索鸟(每秒超过 30 次,取决于你的硬件)。因此,我们每隔 5 帧才执行一次代码(第 15-17 行),这对那些讨厌的鸟来说已经足够快了。
https://medium.com/geekculture/applying-python-multiprocessing-in-2-lines-of-code-3ced521bac8f
2.预处理和模板匹配
预处理部分非常小;我们只是转换成灰色,使执行速度更快一点。
在第 5 行,我们用cv2.matchTemplate方法执行实际的模板匹配。这将返回一组可能的候选项。对于下一行,我们只保留那些准确率超过 70%的候选项。接下来,我们循环遍历所有的鸟,提取坐标并使用这些坐标在上面画一个圆和一个标记
如果还有任何候选人,我们将使用第 8 行中的坐标。在这一部分,我们将在找到的模板上画一个十字标记和一个圆圈,在鸟上放一个十字准线。
剩下的代码包括将 BGR 转换为 RGB,这样我们就可以看到原始的颜色,并确保我们可以通过按 escape 退出。让我们来看看结果:

我们已经成功地找到了我们的目标,让我们锁定和加载(Gif 作者)
正如你在上面的 GIF 中看到的,模板匹配函数正在做它应该做的事情:识别鸟类并在它们身上做标记。在接下来的部分,我们将拍摄鸟类。
3.射鸟
在前一部分,我们已经找到了鸟和它们的 x-y 位置。在这一部分,我们只需将鼠标移动到该位置并单击。
首先,在第 3 行,我们将坐标从框架坐标转换到屏幕坐标。我们希望将鼠标移动到屏幕上的某个位置,而鸟的坐标是相对于框架的(因为我们使用了边界框,所以它是屏幕上较小的一部分)。这就是我们必须将边界框坐标添加到鸟坐标来获得屏幕坐标的原因。
从第 6 行开始,我们检查坐标是否太靠近我们之前点击的位置(在同一帧中)。这样做是因为有时模板匹配算法会在左侧仅一个像素处找到另一个匹配;我们不想两次击中同一只鸟而浪费弹药。为此,我们使用我们在顶部定义的just_shot_coords列表。
最后,我们设置光标位置并点击 ctypes 库。
最终结果
我们去打猎吧!在下面的 gif 中,你可以看到我们工作算法的一个小亮点。我已经把整个 2 分钟的游戏放在 YouTube 上了。检查一下 这里 (原谅可怕的游戏声音)或者 这里 我调整了一些参数以拍得更快。

结论
在本系列的这一部分中,我们已经了解了模板匹配;一个非常方便的功能,可以让你在图片中搜索图片。一路上,我们学到了很多关于使用边框和抓取屏幕,颜色转换和限制执行“每 x 帧一次”的知识。
别忘了查看本系列的其他文章!
如果你有建议/澄清,请评论,以便我可以改进这篇文章。同时,请查看我的 关于各种编程相关主题的其他文章 ,例如:
- 绝对初学者如何用 Python 做数据库连接
- Python 中的高级多任务处理:应用线程池和进程池并进行基准测试
- 编写自己的 C 扩展来加速 Python x100
- cyt hon 入门:如何用 Python 执行>每秒 17 亿次计算
- 用 FastAPI 用 5 行代码创建一个快速自动归档、可维护且易于使用的 Python API
- 创建并发布你自己的 Python 包
- 创建您的定制私有 Python 包,您可以从您的 Git 库 PIP 安装该包
- 面向绝对初学者的虚拟环境——什么是虚拟环境以及如何创建虚拟环境(+示例)
- 通过简单的升级大大提高您的数据库插入速度
编码快乐!
—迈克
页(page 的缩写)学生:比如我正在做的事情?跟着我!
https://mikehuls.medium.com/membership
如何在 OpenCV 中读取图像、视频、网络摄像头和屏幕——初学者图像分析
如何读取 OpenCV 要处理的图像的分步指南

计算机视觉!(图片由 Wolfgang Hasselmann 在 unsplash 上拍摄)
OpenCV 是从图像中获取信息的一个很好的工具。在这个系列中,我们将学习如何处理和分析图像,以便我们可以检测运动、模式、模板甚至阅读文本。然而,在这之前,我们需要图像来分析,这也是本文的主题。
我们将为 OpenCV 检查 4 种获取图像的方法,并一步一步地研究它们。最终,您将能够:
- 加载图像
- 加载视频文件
- 阅读您的网络摄像头
- 阅读你的屏幕
- 调整颜色
我们将在本系列的下一部分中使用这些知识来创建一些漂亮的分析。
系列
本文是关于 OpenCV 图像处理系列的一部分。查看其他文章:
- 阅读图像、视频、您的屏幕和网络摄像头(📍你在这里!)
- 检测和模糊人脸
- 用模板匹配破坏猎鸭:在图像中寻找图像
- 创建运动检测器
- 检测没有 AI 的形状(在建;即将推出)
- 从图像中检测和读取文本(正在建设中;即将推出)
设置
在这一部分中,我们将安装我们的依赖项,并启动和运行 OpenCV。目标是有一个我们可以使用的图像。我们只需要安装两个依赖项:OpenCV 和 PIL (Python 图像库)。将它们安装在:
pip install opencv-python pillow

读取图像
我们将从简单地从文件中读取图像开始。接下来我们开始“拍摄”我们的屏幕。我们可以用它来分析我们在屏幕上看到的图像(我们稍后会谈到这一点)。最后,我们将开始从网络摄像头和视频文件中读取图像。
1.加载文件
首先,我们在第 1 行导入 OpenCV(cv2 是 OpenCV 在 Python 实现中的模块名)。
import cv2img = cv2.imread(filename="c:/path/to/image.png")
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
在第 3 行中,我们加载了一个图像,在下一行中,我们告诉 cv2 向我们显示它。第 5 行告诉 cv2 等待按钮按下(任何按钮)。如果没有这一行,图像将在 cv2 显示后立即关闭。最后一行用于清理所有打开的窗口。这是结果:

这幅美丽的风景图片的 BGR 版本(图片由 Hendrik Conrlissen 在 Unsplash 上拍摄)
颜色有点不对,不是吗?那条河和天空不应该是美好的蓝色吗?原因是 OpenCV 使用 BGR 而不是 RGB(这在当时的相机制造商和软件中很流行)。这将我们带到我们的第一个图像操作:从 BGR 转换到 RGB:
img = cv2.imread(filename="c:/path/to/image.png")
img = cv2.cvtColor(src=img, code=cv2.COLOR_BGR2RGB)
cv2.imshow('image', img)
cv2.waitKey(0)
通过添加一行,我们转换了图像,这样我们就得到它应该有的样子:

好多了(图片由 Hendrik Conrlissen 在 Unsplash 上拍摄)
2.拍摄您的屏幕
想为一个游戏创建一个 AI 或者分析视频数据?然后,你可能想阅读你的屏幕,以分析图像。这非常类似于加载文件:
import cv2
import numpy as np
from PIL import ImageGrabwhile (True):
screen = np.array(ImageGrab.grab())
screen = cv2.cvtColor(src=screen, code=cv2.COLOR_BGR2RGB)
cv2.imshow('my_screen', screen)
# press escape to exit
if (cv2.waitKey(30) == 27):
break
cv2.destroyAllWindows()
在第 6 行我们使用了 PIL 的 ImageGrab.grab()方法;这将返回我们的屏幕。我们必须将它转换成一个 Numpy 数组,这样 OpenCV 才能使用它。然后,在接下来的几行中,我们做和以前一样的事情:转换成 RGB 并告诉 OpenCV 向我们显示图像。然而,在第 9 行和第 10 行,当我们按下 escape 键时,我们告诉 OpenCV 中断循环;这就是waitKey(30) == 27的意思。最后一行又是大扫除。
3.读取网络摄像头
让我们加快速度;代码与上一部分非常相似。
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
frame = cv2.cvtColor(src=frame, code=cv2.COLOR_BGR2RGB)
cv2.imshow('webcam', frame)# press escape to exit
if (cv2.waitKey(30) == 27):
break
cap.release()
cv2.destroyAllWindows()
在第 1 行中,我们使用 OpenCV 方法来捕获我们的网络摄像头。然后,我们使用第 3 行中的read()方法来接收两个变量:ret,如果我们可以读取帧,则为真;以及frame。接下来,我们再次转换我们的颜色(这一次是灰色),显示帧并等待我们的退出键进行清理。
4.读取视频文件
读取一个视频文件与读取你的网络摄像头是一样的,唯一的区别是我们必须传递文件的位置:
cap = vc2.VideoCapture('c:/path/to/my/file.mp4
结论
在本系列的第一部分中,我们已经处理了将图像导入 OpenCV 的绝对基础。在接下来的部分,我们将做一些漂亮的预处理和分析。
不要忘记查看本系列的其他文章!
如果你有建议/澄清,请评论,以便我可以改进这篇文章。同时,看看我的其他关于各种编程相关主题的文章,比如:
- Python 为什么慢,如何加速
- Python 中的高级多任务处理:应用线程池和进程池并进行基准测试
- 编写自己的 C 扩展来加速 Python x100
- 【Cython 入门:如何在 Python 中执行>每秒 17 亿次计算
- 用 FastAPI 用 5 行代码创建一个快速自动归档、可维护且易于使用的 Python API
- 创建并发布你自己的 Python 包
- 创建您的定制私有 Python 包,您可以从您的 Git 库 PIP 安装该包
- 面向绝对初学者的虚拟环境——什么是虚拟环境以及如何创建虚拟环境(+示例)
- 通过简单升级,显著提高数据库插入速度
编码快乐!
—迈克
页(page 的缩写)学生:比如我正在做的事情?跟着我!
https://mikehuls.medium.com/membership
面向所有人的图像分析:图像分类
原文:https://towardsdatascience.com/image-analytics-for-everyone-image-classification-a6e542fd07f2
用分类编辑照片集
在过去的几年里,我拍了很多照片,但是没有时间编辑这些照片。我想根据类别将它们分类到子文件夹中。机器学习算法和图像嵌入能解决我的问题吗?
本文将展示一种识别照片类别和对子文件夹中的图像进行排序的方法。我们将使用名为分类的监督方法。

作者图片
什么是监督方法?
监督方法使用一个模型,该模型根据为特定输出标记的输入数据进行训练。该模型被训练以检测输入数据和输出标签之间的关系,从而能够预测以前未见过的数据的标签。
两种最著名的监督方法是分类和回归。在本文中,我们将了解分类。
什么是分类?
分类是预测每个数据点的类别(也称为类别)的过程。分类模型是从输入变量映射到一个可用类的映射函数。分类模型通常在一组已经标记的数据点上训练。
例如,在我们的例子中,分类模型预测测试数据集中每个图像的类别。我们将在已经标记的图像上训练我们的模型——训练数据集。
在下面的例子中,我们使用了最常见的分类模型之一,即逻辑回归,您不需要详细了解它。不过,如果你很好奇,你可以在这篇文章中了解更多关于的信息。
示例:用橙色进行图像分类
如前所述,我想对图像进行分类,将它们放入电脑的子文件夹中。为此,我将训练一个分类模型,用类别标记图像。
为了训练模型,我需要一组已经标记的图像。我们可以通过为每个类别选择几个例子并将其分类到子文件夹中来准备它们。好消息是,我们只需要制作一次训练集,以后可以重用它。
我用 5 个子文件夹中的图片准备了一个训练数据集。文件夹被命名为:城市,食物,山脉,日落和冬天。文件夹的名称定义了类别。一旦我在这些图像上训练模型,它将总是将图像分类到那些类别中。你可以从这里下载我的训练数据集。
在演示中,我将使用 Orange——我最喜欢的数据分析工具。在我之前的文章中阅读更多关于橙色的内容。
模型训练和评估
我将首先测试模型的准确性和可靠性。

导入图像会从电脑上的文件夹中载入照片。图像查看器显示具有所选属性的图像——在我的例子中是类别。(图片由作者提供)
导入图片小工具从训练数据集中加载图片(下载训练数据)。由于图像被分类在多个文件夹中,小工具将文件夹名称作为一个类别分配给图像,我们可以在图片下方的图像查看器中看到。

图像嵌入将向量表示分配给图像,并且测试和评分小部件在训练数据(作者的图像)上测试逻辑回归模型
我用 Inception v3 嵌入器在图像嵌入小部件中嵌入图像,将图像转换成适合分类的向量表示。在我之前的文章中阅读更多关于图像嵌入的内容。
我在测试和评分小部件中执行交叉验证,以查看模型在训练图像上的准确性(类别已知)。交叉验证建立多个模型,使得每个训练图像被用作一次测试图像来测量准确度。我用的模型是逻辑回归。
我对这个模型感到满意,它的 AUC 为 0.993,分类精度为 0.929,这意味着该模型对 92%的图像进行了正确分类。注意,数据集还包括一些可分为多个类别的边界图像。例如,我有一些日落的照片,也可以是自然的照片,这意味着一些错误的分类可能实际上并不是错误。
用逻辑回归进行类别预测
我测试了逻辑回归,发现它在前面部分足够准确。现在,我可以训练我将用来标记未标记的训练图像的模型。

我在上面分支中的标记训练数据上训练逻辑回归模型,并使用该模型对看不见的和未标记的测试数据进行预测(图片由作者提供)
我在顶部分支中标记的训练图像上训练逻辑回归模型(与前一部分中使用的数据相同)。你可以看到我直接将图像嵌入连接到逻辑回归小部件。这样,模型就可以根据提供的数据进行训练。
在底部的分支中,我加载了没有我想要分类的已知类别的测试图像,并用 Inception-v3 嵌入器将它们嵌入,与训练图像一样。我使用的图片你可以从这里下载。预测小工具根据之前训练的逻辑回归模型给图像分配标签。
我使用图像浏览器来检查分配给图像的类别。他们似乎是有意义的,正确的。
现在我们已经对图像进行了分类,我们使用保存图像小部件将它们保存到文件夹结构中,这样每个文件夹就是一个类别。

保存一个模型并将图片分类到电脑中(图片由作者提供)
由于预测小部件将预测(名为逻辑回归的列)作为元数据放置到数据表中,因此我们需要首先使用选择列小部件将属性设置为目标——保存图像小部件使用目标列作为子文件夹名称。保存图像小工具将根据预测的类别将图像分类到子文件夹中。
我们还将保存模型小部件连接到逻辑回归。通过这种方式,模型被保存起来以备后用,并且可以被重用。
结论
我们展示了一种可以在各种任务中使用的监督分析图像的方法。图像分类需要额外的工作来准备训练数据集,这导致了识别类别的自动管道,并且可以在未来多次使用。
要了解更多关于 Orange 中的图像分析,观看教程视频或阅读 Orange 网站上的博客:图像分析:聚类,莫奈和马奈的聚类,以及交通标志中的离群值。
如果你还没有阅读,请不要忘记阅读我的人人图像分析系列的第一篇博客。
文学
- demar,Janez,等,《橙色:Python 中的数据挖掘工具箱》。机器学习研究杂志,2013,14.1:2349–2353。
- Godec,Primo 等人通过深度模型和小规模机器学习的集成,通过可视化编程实现了图像分析的民主化。自然通讯,2019,10.1:1–7。
- 逻辑回归。纽约:斯普林格出版社,2002 年。
- 西蒙扬,卡伦;安德鲁·塞斯曼。用于大规模图像识别的非常深的卷积网络。 arXiv 预印本 arXiv:1409.1556 ,2014 年。
- 重新思考计算机视觉的盗梦空间架构。发表于:IEEE 计算机视觉和模式识别会议论文集。2016.第 2818–2826 页。
我的其他文章
面向所有人的图像分析:用 Orange 嵌入图像
什么是图像嵌入,如何在不编码的情况下用机器学习分析图像?
本文着眼于图像嵌入(图像在向量空间中的变换),并描述如何通过将图像嵌入向量空间来为分析做准备。我们在简单的分析中展示了嵌入的有用性,我们将尝试对相似的照片进行分组。分析是在 Orange 中执行的,这是一个任何人都可以使用的简单而免费的数据分析工具包。

相似图像靠得更近的网格是基于图像嵌入计算的(作者提供的图像)
什么是图像嵌入?
大多数用于分类、回归或聚类的机器学习算法不能直接在图像上工作。为了进行分析,我们需要将它们转换成更合适的表示形式——称为嵌入的数字向量。图像嵌入是图像的矢量表示,其中具有相似动机的图像具有相似的矢量轮廓。

嵌入器将每个图像转换成一个数字向量。绿色向量属于绿色图像;橙色向量属于橙色图像,蓝色属于蓝色,红色属于红色。(图片由作者提供)
一般来说,嵌入是人类无法理解的,因为它们是由计算机推断的数字向量(而且,推断数字的网络是从标记的图像中自动学习的)。每个数字代表图像的一个属性。举例来说,我们可以说,第一个数字告诉如何可能的房子在图像中,第二个可能说,图像包含云,最后一个给出关于太阳的存在的信息。
我们将使用预训练的神经网络,如 Inception v3 、 VGG-16 或 VGG-19 来获得嵌入。我们将图像馈入神经网络,并将神经网络倒数第二层的输出作为嵌入。
柑橘
在本文的演示中,我将使用我最喜欢的数据分析工具——Orange。Orange 是最著名的开源工具之一,用于交互式数据分析、可视化和机器学习。你可以从的网站下载它,并通过几个简单的步骤进行安装。Orange 由放置小部件并连接它们以创建工作流的画布组成。每个小部件代表数据分析中的一个步骤。
在本文中,我们使用 Orange 的图像分析插件。为了说明 Orange 是如何工作的,下面是一个简单的工作流,它包含导入图像小部件和图像查看器,前者加载图像,后者显示加载的图像。在本文的后面,我们将展示嵌入图像和执行简单分析的工作流程。

橙色的简单工作流程(在顶部)从计算机加载图像并显示给用户。工作流程下方是图像查看器小部件的窗口(按作者分类的图像)
橙色图像嵌入
图像嵌入小部件是 Orange 中图像分析的核心小部件。该小部件获得带有图像的表格,并将它们发送到服务器,以机器学习算法可以理解的格式嵌入。服务器通过预先训练的深度神经网络推送图像,并将数字向量返回给小部件。

在图像嵌入小部件中,用户可以选择他们想要使用的神经网络来将图像嵌入到向量空间中(由 Autor 生成的图像)
这个小部件提供了几个针对各种特定任务训练的深度神经网络。 Inception v3 、 VGG16、VGG19 和 SqueezeNet 经过训练,将图像按照图像上的物体进行分类。画家神经网络被训练预测画作作者, DeepLoc 预测酵母细胞结构, OpenFace 进行人脸识别。我们忽略网络的分类层,而是考虑倒数第二层,并将其用于图像的矢量表示。
示例分析—识别相似图像
图像嵌入可以用来识别相似的图像。对于这个例子,我从我的照片集中收集了 70 张图片。
我用电脑文件夹中的导入图像小工具加载了它们。“导入图像”小部件将图像发送到“图像嵌入”小部件,后者为每张图像计算一个数字向量(称为嵌入)。我使用 Inception v3 神经网络,因为它通常在日常生活图像中表现最好。
我们先来看看嵌入的图像是什么样子的?

工作流使用“导入图像”小部件加载图像,使用图像嵌入将图像嵌入到矢量空间中,并在数据表的表格形状中显示嵌入内容(按作者显示图像)
在上面的数据表中,您可以看到每个图像(由一行描述)由 2048 个数字(表中白色部分的列)表示,从 n0 到 n2047,由图像嵌入小部件生成。他们用一种人类难以理解的方式描述图像。如前所述,一般来说,我们可以说每一列代表图像的一个属性。
嵌入图像后,我们可以将它们放在网格上,这样相似的图像在图像网格小部件中就可以靠得很近。它接受嵌入并使用 t-SNE 方法在二维画布上分发图片。照片根据它们的相似性放在画布上。

产生图像网格的工作流程,其中相似的图像靠在一起(按作者排列的图像)
我们可以观察到冬天的图像在右边,日落的图像在右下方。如果我们向中心移动,我们可以看到徒步旅行/自然的图像。城市的图片在左边,食物在左上方。
结论
本文解释了图像嵌入以及如何在一个简单的分析中使用它们。这篇文章之后还会有两篇文章。一个将涵盖图像的无监督学习的主题,另一个将讨论图像的监督学习。
文学
- demar,Janez,等,《橙色:Python 中的数据挖掘工具箱》。机器学习研究杂志,2013,14.1:2349–2353。
- Godec,Primo 等人通过集成深度模型和小规模机器学习,通过可视化编程实现了图像分析的民主化。自然通讯,2019,10.1:1–7。
- 西蒙扬,卡伦;安德鲁·塞斯曼。用于大规模图像识别的非常深的卷积网络。 arXiv 预印本 arXiv:1409.1556 ,2014。
- 重新思考计算机视觉的盗梦空间架构。发表于:IEEE 计算机视觉和模式识别会议论文集。2016.第 2818–2826 页。
我的其他文章
张量流中的图像增强
原文:https://towardsdatascience.com/image-augmentations-in-tensorflow-62967f59239d
随机旋转图像很简单;以下是如何做的
本着前一篇文章的精神,其中的主题是增加音频数据,今天,我们将重点放在增加图像数据。在这个领域中,研究人员和从业者都可以访问许多数据转换:从随机调整对比度到合并样本开始,可用的扩充往往覆盖所有用例。

由于 TensorFlow 的数据 API,一旦练习几次,添加这样的转换就非常方便和简单。通常,我们首先创建一个 Dataset 对象,实现或搜索增强的代码,然后通过转换操作输入每个样本。其中许多已经实施;像相册和 tensorflow-addons 这样的库是开始的好地方。
因此,让我们开始实施我们的图像增强管道没有进一步的麻烦。
设置和数据集下载
通常,我们从所有必要的导入开始。如果您没有安装其中一个软件包,请在命令行中键入pip install
第一个附加库 tensorflow-datasets ,直接用于下载花的图像数据集。这个数据集是在许可的 Creative Commons 2.0 许可证下许可的,这使得它成为各种任务的理想候选。
顾名思义,数据集是花朵图像的集合。每张图片都属于一类:郁金香、向日葵、玫瑰、蒲公英和雏菊。以下是数据集中的一个示例:

来自 TensorFlow flowers 数据集的样本。
数据集分析
在我们对图像进行任何增强之前,我们必须检查数据集;样本没有被标准化为具有固定的高度或宽度。我们使用以下代码,该代码检查数据集,然后绘制图像大小的分布:
分析完训练数据集后,我们可以绘制分布图,生成以下两幅图像:

张量流花卉数据集中图像高度的分布。图片由作者提供。

张量流花卉数据集中图像宽度的分布。作者图片
在左边,我们看到高度分布;右边是宽度分布。我们可以从这些样本中推断出合理的尺寸来重塑所有的图像。在本例中,它的高度为 350,宽度为 500。注意:这个相对浅显的分析足以满足这篇博文的目的;我们将在不同的背景下进行更深入的研究。
有了推导出的形状信息,我们可以将所有的图像归入一个共同的形状:
既然所有样本都有相同的大小,我们就可以实施扩增步骤了。在本文中,我们将保持少量的转换。首先,我们批处理数据集,使任何支持的操作运行得更快。这个概念被称为矢量化,帮助我们将相同的转换应用于一批样本,而不是逐个扩充数据:
图像变亮和翻转
我们的第一个增强步骤包括随机改变图像的亮度水平,水平或垂直翻转。然后,通过添加种子,我们使转换可重复:
让我们看一个随机样本:

应用于 TensorFlow 花卉数据集样本的图像增强示例。
图像旋转
我们增强管道的第二步包括旋转图像。为此,我们使用前面提到的 tensorflow-addons 库。在图像旋转中,这个库提供了许多还不是标准 TensorFlow 的一部分的特性。我们在-45°和+45°之间的区间内随机选取旋转角度:
我们的第一个样本现在已经稍微旋转了一下;如您所见,外部值用零填充:

展示 TensorFlow flower 数据集上图像旋转的示例图像。
组合增强步骤
现在,让我们将前面的步骤包装成一个函数,合并所有的转换操作。请注意,只有训练数据集得到了扩充;验证和测试集保持不变:
扩增管道到此为止。但是,单靠管道不会帮你获得更好的结果;一个模型也必须接受扩充数据的训练。这就是我们现在要做的。
模特培训
为简单起见,我们将使用剩余网络。特别是,我们的选择是一个 ResNet50 模型,它已经在流行的 ImageNet 数据集上进行了训练。因为它已经看过海量的图像数据,已经有了图像的概念。这是我们通过加载预先训练的权重来利用的事实:我们跳过了“学习物体的基础知识(如边缘、形状、颜色)”部分,可以更快地处理手头的问题。
尽管不平衡并不严重,但我们的数据集仍然不平衡;这些标签并不是平均分布在各个类别中的。因此,简单的准确性度量没有太大的意义。但是 F1 的分数会。Kenneth Leung 写了一篇很好的介绍所有不同 F1 分数变体的文章。
与 Adam 优化器(与默认设置一起使用)一起,我们将实例化我们的指标和损失对象,然后编译模型:
最后,我们可以通过一个简单的调用来训练模型:
根据您计算机的能力,培训会很快进行。例如,在谷歌合作实验室,一个纪元需要大约两分钟。
这个帖子到此为止。你可以在 GitHub 上找到所有附带代码;请随意做进一步的修改。
摘要
在这篇文章中,我们用 TensorFlow 实现了一个图像增强管道。我们从加载一个花数据集开始,然后向训练数据添加了四个随机修改:亮度调整、垂直翻转、水平翻转和旋转。最后,我们使用预先训练的残差网络,并在数据集上对其进行训练。
如果你想更进一步,你可以添加额外的增强,如模糊或对比度调整。或者你可以将它们添加到你的定制数据集中(理想情况下已经是TF record 格式)。
基于卷积神经网络的图像分类
原文:https://towardsdatascience.com/image-classification-with-convolutional-neural-networks-12a7b4fb4c91
用于图像分类的卷积和卷积神经网络综合指南,从 Python 和 TensorFlow 的实现到优化和转移学习技术
在我之前的文章中,我们学习了神经网络的基础知识
是时候了解卷积神经网络及其在图像分类中的用途了。

作者图片
什么是卷积?
卷积运算是用具有恒定大小的“窗口”移动图像,并将图像像素与卷积窗口相乘以获得输出图像的过程。让我们看看下面的例子:

作者图片
我们看到一个 9×9 的图像和一个 3×3 的卷积滤波器,其恒定权重为 3 0 3 2 0 2 1 0 1,以及卷积运算的计算。尝试使用如下图所示的滤镜浏览图像,并更好地理解如何通过卷积计算输出图像的这些像素。

作者图片
- 、滤镜、内核、遮罩是提及“卷积滤镜”的不同方式,我们也将在整篇文章中使用这些术语。
填充
填充是在我们的输入图像边界上添加额外像素的过程,主要是为了保持输出图像的大小与输入图像相同。最常见的填充技术是加零(称为 零填充 )。
跨步
步幅是我们用卷积窗遍历图像时每次迭代的步长。在下面的例子中,我们实际上看到步长是 1,所以我们将窗口移动了 1。现在让我们看另一个例子,以便更好地理解水平步幅= 1,垂直步幅= 2:

作者图片
- 因此,填充尺寸、步幅尺寸、滤波器尺寸和输入尺寸影响输出图像尺寸,并且根据这些不同参数的输出图像尺寸的公式如下:

作者图片
到目前为止,我们只研究了应用于输入图像的 1 个卷积运算,现在让我们来看看什么是卷积神经网络以及我们如何训练它们。
卷积神经网络(CNN)
如果我们将层作为卷积窗口,其中窗口中的每个像素实际上是一个权重(而不是我们在之前的文章中学习的完全连接的神经网络),它是一个卷积神经网络,我们的目标是训练模型以在最后以最小的成本更新这些权重。因此,与前面的例子相反,我们的卷积滤波器中没有任何常量值,但是这些是我们的权重,我们应该让模型为它们找到最佳值。****
换句话说,与 图像处理 相反,在图像处理中,我们将这些卷积运算用于特定的滤波器(在卷积滤波器中具有特殊的和已知的权重),在卷积神经网络的卷积层中,我们的窗口具有一些随机的、不重要的权重,并且我们在模型训练步骤中更新它们,以获得使我们的损失最小化的最佳权重。(因此它们不是图像处理术语中的常数
所以一个简单的 CNN 无非是如下一些卷积运算的序列:

作者图片
用 CNN 进行图像分类
但是如何利用 CNN 实现图像分类呢?
我们在之前的中看到了训练过程如何更新回归或分类模型权重。图像分类的唯一区别是,现在我们处理的是图像,而不是结构化数据,如房价、房间号等。
每个卷积运算都参与提取图像特征,如耳朵、脚、狗嘴等。这个特征提取步骤随着更深的卷积层而变得更深,而在第一层,我们只获得图像的一些边缘。****
要了解更多关于应用边缘检测的不同卷积滤波器的信息,请参考本文 :
****
所以卷积层承担了提取重要特征的责任,最后要有一个完整的图像分类模型,我们只需要一些全连通的输出节点就可以根据它们的权重决定图像的正确类别!
让我们假设我们有一个狗和猫类的图像分类问题。在这种情况下,在训练结束时,一些输出节点将表示狗类要素和一些猫类要素。如果通过这些卷积层的输入图像在我们的激活函数结束时给予代表节点的狗类更高的输入,则这些节点的总和将给予狗类输出节点更高的结果。否则,它就是一个 cat 类输出节点。让我们想象一下这个过程:

作者图片
我们看到拼图的所有碎片都聚集在一起 CNN +全连接神经网络创建了一个图像分类模型!
在介绍用于图像分类的常见 CNN 架构之前,让我们先来看一些更复杂、更真实的 CNN 示例:
- 当我们谈到 CNN 层的时候,我们不是说 1 层只有 1 个卷积核;实际上,多个卷积核创建一个卷积层。因此,我们将所有这些卷积滤波器一个接一个地应用于输入图像,然后进入下一个卷积层。1 卷积层中卷积核的个数决定了 3。我们图层的尺寸就像一个图像的“通道尺寸”。

作者图片
- 我们学习了如何在应用卷积运算后计算输出图像大小,现在您应该知道卷积层的通道大小直接是输出通道大小,因为我们应用 1 个卷积运算来获得 1 个输出图像。因此,如果 CNN 层内部有 5 个卷积核,我们在这一层的末尾获得 5 个输出核。
- 如果我们处理 RGB 图像,我们有 3 个通道,而灰度图像只有 1 个通道。在这种情况下,我们将 1 个卷积核应用 3 次,每个通道 1 乘 1,以获得输出通道。因此,输出图像的通道大小不会改变,但卷积层的参数总数会改变。1 个 CNN 层的参数总数计算如下:

作者图片
因此,对于前面的例子,我们可以说 n=64 m=64 l=1 k=32。
联营
这是在图像分类 CNN 架构中使用的另一个重要术语。这是一种用来减少 CNN 模型参数的方法。我相信你已经发现(使用上面提到的公式),我们讨论的大量参数是如何与具有超过 5-10 个卷积滤波器的几个 CNN 层相关的。因此,一次一次地减少参数的数量,并从特征图中仅选择最重要的特征(输出图来自每个卷积层)。正如我们之前所说,更深的卷积层具有更具体的特征)是重要的。共有两种常见的池类型:
- 最大池化
基本思想是再次使用一个窗口,此时没有任何权重,并且在遍历要素地图时,选择最大像素值作为输出。

作者图片
我们使用相同的公式来计算我前面提到的卷积运算的最大池的输出映射大小。重要的一点是,因为目标是减少参数大小,所以给定填充大小= 0,步长大小=池内核的大小是一个合理的和常见的方法,就像我们在本例中所做的那样。
- 平均统筹
我们不是从最大像素计算输出,而是从停留在池内核中的像素的平均值计算输出。

作者图片
常见的卷积神经网络架构
ImageNet 大规模视觉识别挑战赛( ILSVRC )多年来一直是一项非常受欢迎的比赛。我们将考察不同年份的一些获胜者。这些是用于图像分类任务的最常见的架构,因为它们在竞赛年与其他模型相比具有更高的性能。
ImageNet 是一个数据集,具有针对 1000 个类别的 1,281,167 幅训练图像、 50,000 幅验证图像和 100,000 幅测试图像。
验证数据集:除了用于任何模型的训练步骤的训练数据集和在训练步骤完成后用于测试模型以计算模型精度-性能的测试图像之外,它是模型以前没有见过的数据,即在训练阶段不参与反向传播-权重更新阶段,而是用于测试以便真实地跟踪训练阶段的进展。
- AlexNet (2012 年)
该模型由 8 层组成,具有5-卷积和 3 全连接。
针对 RGB 输入图像实施(3 通道)
包含 6000 万个参数
ImageNet 测试数据集的最终误差为 15.3%
ReLU 激活功能首次用于该型号。除了最后一个全连接层具有 Softmax 激活功能外,ReLu 用作整个模型的激活功能
使用比率为 0.5 的脱扣机构。
动量随机梯度下降用于动量= 0.9,批量= 128
使用标准偏差= 0.01,用零均值高斯分布初始化权重
偏差初始化为常数值 1。
学习率被初始化为 0.01,并且“权重衰减正则化被应用为 0.0005

作者图片
在上图中,我们看到了 AlexNet 架构,输出特征映射了每一步的大小,当然,我们有 1000 个完全连接的层节点,因为 ImageNet 数据集有 1000 个类。
对于这种架构,我们还会遇到一些我之前没有提到的术语:
带动量的梯度下降:这是对梯度下降计算的优化,我们将之前梯度下降的导数添加到我们的梯度下降计算中。对于这种架构,我们将这一额外部分乘以动量超参数 0.9。
高斯分布的权重初始化:在开始训练模型之前,有不同的方法来初始化我们的权重。例如,将所有权重设为 0 是一种方法,但却是一种糟糕的方法!取而代之的是,根据高斯分布初始化所有权重是一种常用的方法。我们只需要选择分布的平均值和标准偏差,我们的权重将在这个分布范围内。
权重衰减优化:在本文中使用 SGD(随机梯度下降)的情况下,这与 L2 正则化是一样的!
- VGG16 (2014 年)
VGG 架构是一个 16 层模型,具有 13 个卷积和 3 个全连接。
它有 1.38 亿个参数
在测试数据集上有 7.5%的误差
与 AlexNet 相反,每个卷积层中的所有内核使用相同的大小。那是 3x3 内核,步幅= 1,填充= 1。对于跨度= 2 的 2x2 内核的最大池
类似于 AlexNet ,ReLu 用于隐藏层,Softmax 用于输出层。动量为 0.9 的 SGD,衰减参数为 0.00005 的权重衰减正则化,初始学习率为 0.01,使用高斯分布的权重初始化。
作为 AlexNet 的一个小区别,VGG16 架构使用 batch size = 256,初始化 bias,不是 1 而是 0,输入图像大小为 224x224x3。
有一个更新的版本叫做 VGG19,总共有 19 层。
- ResNet (2015 年)
该架构的名称来自残差块,其中残差块是“相同输入”和“卷积和激活函数后的输出”的组合。

作者图片
ResNet 的不同版本具有不同的层数,如 Resnet18、Resnet34、Resnet50、Resnet101 和 Resnet152。在下图中,我们看到左侧是一个“正常”的 18 层架构,右侧是残余块版本。红线表示相同的块和主块具有相同的尺寸,因此它们可以直接组合,蓝线表示尺寸不同,因此相同的层应该添加零填充,或者在使用兼容的填充(步长大小)用 1x1 卷积内核调整大小之后添加。

作者图片
ResNet152 对于测试数据集实现了%3.57 的误差,并且它具有 58M 的参数。当我们将参数大小和小误差与以前的架构进行比较时,这已经很不错了,对吗?****
除了可训练参数的数量,每秒 FLOPS ( **浮点运算)是一个重要因素。让我们比较一下到目前为止我们检查过的模型:******

作者图片
如果你想考察更多类型的卷积神经网络,我建议你搜索 Inception 、 SeNet (2017 年 ILSVRC 获奖者)、以及 MobileNet 。
现在该是我们用 VGG16 配合 Python 和 Tensorflow 应用图像分类的时候了!
VGG 建筑从无到有
这是用 Python 和 Tensorflow 实现的 VGG16。让我们稍微检查一下代码
- 首先,我们检查我们的 TensorFlow-Cuda-Cudnn 安装是否正常,以及 TensorFlow 是否能找到我们的 GPU 。因为如果不是这样,这意味着有一个包冲突或一个我们应该解决的错误,因为用 CPU 进行模型训练太慢了,几乎不可能。
- 然后,我们使用用于卷积层的 Conv2D 函数、用于最大池层的 MaxPool2D 函数、 Flatten 函数来创建 VGG16 模型,以使 CNN 输出能够传递到全连接层的平坦输入、 Dense 函数用于全连接层、 Dropout 函数用于在最后的全连接层之间添加 Dropout 优化。你可以看到,我们应该添加权重初始化,偏差初始化,l2 正则化 1 乘 1 到层,同时使用相关的层函数。注意,正态分布是高斯分布的同义词,所以当看到 weight _ initializer = TF . keras . initializer . randomnormal(mean = 0.0,stddev=0.01,seed=None)时,不要让您的思维混乱
- 我将用 2 个类来测试这个模型,而不是用 1000 个类来处理非常庞大的 ImageNet 数据集,因此我将输出层从 1000 个节点更改为 2 个节点
- 使用 ImageGenerator 函数和 flow_from_directory() ,我们将数据集准备为能够与 TensorFlow 模型一起工作的向量。我们在这里也给出了批量大小。(由于我的计算机内存不足,我可以给出批量大小 16,而不是论文中提到的 256。) shuffle 参数意味着数据集将在每个历元之前被打乱,以使批次不那么恒定
- model.compile() 函数是训练我们的模型之前的最后一部分。我们确定使用哪个优化器(带动量的 SGD),哪个损失函数(二进制交叉熵,因为在我们的例子中我们有 2 个类),以及使用哪个度量来计算训练期间的性能(二进制准确度)。
- 我使用 model.fit() 函数来训练我们的模型。我添加了一些选项,比如“提前停止”和“模型检查点”回调。如果验证损失在 10 个时期内没有增加,则第一个停止训练,并且如果验证损失比前一个时期好,则模型检查点不仅在结束时而且在训练期间保存我们的模型。
- CustomLearningRateScheduler是我手动实现的学习率调度器,用于应用“如果验证精度停止提高,我们通过除以 10 来更新学习率”
- 在培训结束时,我将验证和培训数据集的准确性和损失图表可视化。
- 最后一部分是用测试数据集测试模型。我们检查是否有名为“trained_model.h5”的已训练模型,如果没有,我们训练一个模型,如果有,我们使用这个模型来测试性能
我想解释一下我在解释我的代码时使用的一些附加术语:
- **验证—训练准确性:验证准确性是一个衡量我们的模型在验证数据集上表现如何的指标。它只是检查在验证数据集中有多少图像预测为真。我们也对训练数据集进行同样的检查。但这只是针对我们的监控模型,只有列车损失用于梯度下降计算
- 迭代—历元 : 1 次迭代是对一批中的所有图像进行建模的过程。历元是指为我们训练数据集中的所有图像提供模型的过程。例如,如果我们有 100 个图像,并且我们的批量大小= 10,则 1 个时期将有 10 次迭代。
- **拉平:这只不过是重塑我们的 CNN 输出,使其具有 1D 输入,这是从卷积层传递到全连接层的唯一方式。
现在让我们检查结果

作者图片
即使我们通过监控验证精度来改变学习率,结果也不好,验证精度也没有提高。但是为什么它不像 VGG16 论文中那样直接工作呢?
- 在原始论文中,我们讨论了 374 个时期的 1000 个输出类和 1500000 个图像。不幸的是,我使用这个数据集并为 1000 个类训练一个模型需要几天时间,所以正如我之前所说,我为 2 个类使用了一个数据集,并将输出层更改为具有 2 个输出节点。
- 模型架构和数据集都需要不同的优化,因此一个运行良好的模型可能不适用于另一个数据集。
- 微调超参数以改进您的模型与了解如何首先构建模型一样重要。您可能已经注意到我们需要微调的超参数数量
- 权重和偏差初始化
- 损失函数选择
- 初始学习率选择
- 优化器选择(梯度下降法)
- 是否使用辍学
- 数据扩充(如果您没有足够的图像,或者图像过于相似,您希望获得相同图像的不同版本)
诸如此类…
但是在你对如何优化如此多的变量感到悲观之前,我想提两个要点。
- 转移学习
这是一个非常重要的方法,我们使用一个预训练模型用我们自己的数据集来训练它。在我们的例子中,我们不会只使用 VGG16 架构,而是使用已经用 VGG16 架构和 ImageNet 数据集训练过的模型,我们将使用比 ImageNet 小得多的数据集重新训练它。这种方法为我们提供了并非未知的开始——一些随机的权重但是一些已经意味着一些特征。我们的数据集可能包含不同的对象,但不同对象之间有基本的共同特征,如边缘、圆形,为什么不使用它们而不是从头开始呢?我们将看到这种方法如何减少耗时并提高我们的精度性能。
2.当我查看我们的输出时,我看到问题是不是因为模型停止学习,而是因为它没有开始学习!精度从 0.5 开始,永远不变!那个特定的结果给了我们一个非常明确的信息:我们应该降低学习率让模型开始学习。请记住,学习率是学习的步长,我们的权重会受到每次迭代的梯度下降计算的影响。如果这个比率太大,步长太大,以至于我们不能控制权重更新,所以模型不能学习任何东西,它只会变得混乱。
以下代码仅包括迁移学习的更改,其中我采用了具有预训练权重 Tensorflow 内置模型和较低学习率(0.001)的模型
让我们来看看结果:

作者图片

作者图片
性能有了很大的提高,对吗?!我们看到,验证精度不再停滞不前,并增加到 0.97,而训练精度达到 1.00。测试准确度的最终检查:

作者图片
一切似乎都很好!
我想和你们分享我做的一些实验和它们的结果:

作者图片
现在,我们可以做如下分析:
- 如果我们的学习速度不一致,迁移学习本身仍然是不够的。
- 如果我们将 base _ model _ trainable 变量设置为 False,这意味着我们不训练我们采用的模型,我们只训练我们添加的最后一个完全连接的层。(VGG 有 1000 个输出,所以我使用 include_top = False 上传了没有最后密集层的模型,我在这个预训练模型的末尾添加了一个密集层,如代码中所示)如果我们不更新这些权重,我们会得到比 base _ model _ trainable = True 选项稍差的结果。从 4 点停止训练开始就更快了。虽然是时代。
- 只是为了看看,我改变了 vgg_from_scratch 实现的学习速率,但是验证 acc 仍然停留在 0.5,所以仅仅学习速率优化本身也是不够的。(至少如果你没有几天的时间在微调之后从头训练一个模型。)
你可以通过这个 链接 到达我使用的数据集,请不要忘记把它和你的代码放在同一个文件夹里,如下所示。当然,h 和日志文件将在培训完成后提供!🌼):
我用 Python=3.7,Tensorflow=2.2.0,Cuda=10.1,Cudnn=7.6.5 的 Anaconda,用 GPU 来训练和测试我的模型。如果您不熟悉这些术语,这里有一个快速教程:
- 从https://www.anaconda.com/products/individual下载 Anaconda (请为您的电脑选择合适的操作系统)
- 打开 Anaconda 终端,使用conda create-n im _ class python = 3.7 Anaconda命令创建一个环境。环境术语可能是 Anaconda 最重要的属性,它允许您为不同的项目单独工作。您可以将任何包添加到您的环境中,如果您需要另一个包的另一个版本,您可以简单地创建另一个环境以避免您的另一个项目崩溃,等等。
- 使用 conda activate im_class 命令进入到您的环境中添加更多的包(如果您忘记了这一步,您基本上不会在您的环境中而是在所有的 anaconda 空间中进行更改)
- 使用 pip 安装具有 GPU 能力的 tensorflow-gpu==2.2.0
- 使用conda install CUDA toolkit = 10.1命令安装 CUDA 和 Cudnn
现在您已经准备好测试上面的代码了!
请注意,包版本之间的冲突是一个很大的问题,请只使用这些命令和上面提到的版本。这将帮助您在构建环境时花费更少的时间。
恭喜你。您已经完成了卷积神经网络图像分类教程。您可以尝试从头开始构建任何模型(甚至可能是您自己的模型👀),对其进行微调,针对不同架构应用迁移学习等等。现在轮到你自己动手做一些实验了!
下一篇文章再见,我们将讨论✋ ✋物体识别的神经网络架构
**** ****
AlexNet 论文:https://arxiv.org/ftp/arxiv/papers/1803/1803.01164.pdf
****VGG16 论文:【https://arxiv.org/pdf/1409.1556.pdf ****
****ResNet 论文:【https://arxiv.org/pdf/1512.03385.pdf ****
ImageNet 官网,数据集和挑战:https://www.image-net.org/download.php






浙公网安备 33010602011771号