admin管理员组

文章数量:1645532

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

交叉验证

原文:https://towardsdatascience/cross-validation-705644663568

它是什么,为什么使用它?

照片由耶鲁安穴獭在 Unsplash

回归和分类机器学习模型旨在从数据中包含的变量预测值或类。每个模型都有自己的算法来尝试识别数据中包含的模式,从而做出准确的预测。

模型除了要准确,还必须是通才,能够解释以前从未见过的数据,并得出适当的结果。评估模型泛化能力的一种方法是应用交叉验证。

但是什么是交叉验证呢?

由市政府在 Unsplash 拍摄的照片

交叉验证是一种用于获得模型整体性能评估的技术。有几种交叉验证技术,但它们基本上都是将数据分成训练和测试子集。

顾名思义,训练子集将在训练过程中用于计算模型的超参数。为了计算模型的泛化能力,在训练阶段之后,使用测试模型。

使用来自测试数据集的真实标签和由经过训练的模型对测试数据做出的预测来计算模型的性能度量,例如准确度(分类)和均方根绝对误差(回归)。

交叉验证技术有很多种,在这篇文章中我将讨论其中的三种:保持K-Fold留一法。

维持交叉验证

咖啡极客在 Unsplash 上的照片

可能最著名的交叉验证技术是拒绝验证。该技术包括将整个数据集分成两组,没有重叠:训练集和测试集。根据项目的不同,这种分离可以是混排数据,也可以是保持数据的排序。

在项目和研究中通常会看到 70/30 的分割,其中 70%的数据用于训练模型,其余 30%用于测试和评估模型。但是,这个比率不是一个规则,它可能会根据项目的特殊性而变化。

应用于数据集的维持交叉验证示例-按作者分类的图像

在 Python 中,使用 scikit-learn 库中的train_test_split 函数可以轻松完成维持交叉验证。

使用乳腺癌数据集和 70/30 分割,我们得到:

k 倍交叉验证

照片由安迪·霍尔在 Unsplash 拍摄

在将数据分成训练集和测试集之前,K-Fold 交叉验证将整个数据分成 K 个大小近似的独立子集。只有这样,每个子集才会被分成训练集和测试集。

每个子集用于训练和测试模型。在实践中,这种技术产生 K 个不同的模型和 K 个不同的结果。K 倍交叉验证的最终结果是每个子集的单个指标的平均值。

应用于数据集的三重交叉验证示例-按作者分类的图像

值得注意的是,由于 K 倍将原始数据划分为更小的子集,因此必须考虑数据集的大小和 K 个子集。如果数据集很小或者 K 的数量太大,那么产生的子集可能会变得非常小。

这可能导致只有少量数据用于训练模型,从而导致性能不佳,因为算法由于缺乏信息而无法理解和学习数据中的模式。

Python 也有一种简单的方法来执行 K-Fold 分割,即使用来自 scikit-learn 库的Kfold

使用与之前相同的数据集,K = 3,我们得到:

基本上,维持交叉验证与一折交叉验证相同。

留一交叉验证

威尔·梅尔斯在 Unsplash 拍摄的照片

留一交叉验证包括创建多个训练和测试集,其中测试集仅包含原始数据的一个样本,而训练集包含原始数据的所有其他样本。对原始数据集中的所有样本重复此过程。

这种类型的验证通常非常耗时,因为如果使用的数据包含 n 个样本,算法将不得不训练(使用 n-1 个样本)并评估模型 n 次。

从积极的一面来看,这种技术,在本文所见的所有技术中,是模型中用于训练的样本量最大的一种,这可能会产生更好的模型。此外,不需要打乱数据,因为所有可能的训练/测试集的组合都将被生成。

应用于数据集的留一交叉验证示例-按作者分类的图像

使用LeaveOneOut在 scikit-learn 库也可以进行留一交叉验证

使用乳腺癌数据集,我们有:

与维持类似,留一交叉验证也是一种特殊类型的 K-Fold,其中 K 的值等于数据集的样本数。

性能比较

Sabri Tuzcu 在 Unsplash 上拍摄的照片

为了显示每种交叉验证的性能差异,这三种技术将与简单的决策树分类器一起使用,以预测乳腺癌数据集中的患者是良性(1 类)还是恶性(0 类)肿瘤。为了进行比较,将使用 70/30 分割、3 折和留一的维持。

使用的代码可以在我的 github 页面找到:https://github . com/alerlemos/medium/blob/main/cross _ validation/cross _ validation _ examples . ipynb

获得的结果如下表所示:

每种交叉验证技术获得的结果

正如预期的那样,与其他两种技术相比,留一法的运行时间要长得多,尽管它使用了更多的数据来训练模型,但总体而言它并不是最佳性能。

解决这个特定问题的最佳技术是维持交叉验证,其中 70%的数据用于培训,30%用于测试模型。

马库斯·斯皮斯克在 Unsplash 上的照片

感谢您的阅读,希望对您有所帮助。

欢迎任何意见和建议。

请随时通过我的 Linkedin 联系我,并查看我的 GitHub。

领英

Github

交叉验证和网格搜索

原文:https://towardsdatascience/cross-validation-and-grid-search-efa64b127c1b

在随机森林模型上使用 sklearn 的 GridSearchCV

图片由 Annie Spratt 通过 Unsplash 提供

为机器学习问题找到最佳的调整参数通常是非常困难的。我们可能会遇到过度拟合,,这意味着我们的机器学习模型在我们的训练数据集上训练得过于具体,当应用于我们的测试/维持数据集时,会导致更高水平的错误。或者,我们可能会遇到欠拟合,,这意味着我们的模型没有针对我们的训练数据集进行足够具体的训练。当应用于测试/维持数据集时,这也会导致更高水平的误差。

在为模型定型和测试执行常规定型/验证/测试拆分时,模型会对随机选择的特定数据部分进行定型,对单独的数据集进行验证,最后对维持数据集进行测试。在实践中,这可能会导致一些问题,尤其是当数据集的大小相对较小时,因为您可能会删除一部分对训练最佳模型至关重要的观察值。将一定比例的数据排除在训练阶段之外,即使其 15–25%仍然包含大量信息,否则这些信息将有助于我们的模型更有效地训练。

我们的问题有了一个解决方案——交叉验证。交叉验证的工作方式是将我们的数据集分成随机的组,选出一组作为测试,然后在其余的组上训练模型。对作为测试组的每个组重复这一过程,然后将模型的平均值用于结果模型。

最常见的交叉验证类型之一是 k-fold 交叉验证,其中“k”是数据集中的折叠数。使用 k =5 是常见的第一步,下面的例子很容易说明这一原理:

作者图片

这里我们看到模型的五次迭代,每次迭代都将不同的褶皱作为测试集,并在其他四个褶皱上进行训练。所有五次迭代完成后,将结果迭代平均在一起,创建最终的交叉验证模型。

虽然交叉验证可以极大地有益于模型开发,但是在进行交叉验证时,也应该考虑一个重要的缺点。因为模型的每次迭代(最多 k 次)都需要运行完整的模型,所以随着数据集变大以及“k”值的增加,计算成本会变得很高。例如,在具有 100 万个观察值的数据集上运行 k = 10 的交叉验证模型需要运行 10 个单独的模型,每个模型都使用所有 100 万个观察值。对于小型数据集来说,这并不是一个问题,因为计算时间可能只有几分钟,但是当处理大型数据集,其规模可能达到数 Gb 或 Tb 时,所需的时间将会显著增加。

在本文的剩余部分,我们将在我之前的文章中创建的随机森林模型上实现交叉验证。此外,我们将实现所谓的网格搜索,它允许我们在超参数网格上运行模型,以确定最佳结果。

**数据:**该数据集提供了乘客的信息,如年龄、机票等级、性别,以及乘客是否幸存的二元变量。这些数据也可以用于 Kaggle Titanic ML 比赛,所以本着保持比赛公平的精神,我不会展示我进行 EDA &数据争论的所有步骤,或者直接发布代码。我将构建我在上面提到的文章中开发的先前的模型。

提醒一下,使用的基本随机森林训练模型如下所示:

# Train/Test split
x_train, x_test, y_train, y_test = train_test_split(X, y, test_size = .25, random_state = 18)# Model training
clf = RandomForestClassifier(n_estimators = 500, max_depth = 4, max_features = 3, bootstrap = True, random_state = 18).fit(x_train, y_train)

我们取得的成果是:

作者图片

对于本文,我们将保留这个训练/测试分割部分,以保持模型之间的维持测试数据一致,但是我们将使用交叉验证和网格搜索对训练数据进行参数调整,以查看我们的结果输出与使用上面的基本模型得到的输出有何不同。

GridSearchCV:
我们将在本文中使用的模块是 sklearn 的 GridSearchCV,它将允许我们传递我们的特定估计量、我们的参数网格和我们选择的交叉验证折叠数。此方法的文档可以在这里找到。下面重点介绍了一些主要参数:

  • 估计器 —此参数允许您选择要运行的特定模型,在我们的示例中为随机森林分类。
  • param_grid —该参数允许您传递正在搜索的参数网格。这个网格必须格式化为一个字典,其中的键对应于特定估计器的参数名,值对应于要为特定参数传递的值列表。
  • cv — 该参数允许您更改交叉验证的折叠次数。

模型训练: 我们将首先为随机森林分类模型创建一个参数值网格。网格中的第一个参数是 n_estimators,它选择随机森林模型中使用的树木数量,这里我们选择 200、300、400 或 500 的值。接下来,我们选择 max_feature 参数的值,它限制了每棵树考虑的特征数量。我们将该参数设置为“sqrt”或“log2”,它将采用数据集中估计量的平方根或以 2 为底的对数的形式。第三个参数是 max_depth,它将随机森林模型中每棵树的最大深度设置为 4、5、6、7 或 8。最后,标准参数将通过“基尼”或“熵”进行搜索,以找到理想的标准。该网格如下所示:

grid = { 
    'n_estimators': [200,300,400,500],
    'max_features': ['sqrt', 'log2'],
    'max_depth' : [4,5,6,7,8],
    'criterion' :['gini', 'entropy'],
    'random_state' : [18]
}

创建网格后,我们可以运行 GridSearchCV 模型,将 RandomForestClassifier()传递给我们的估计器参数,将我们的网格传递给 param_grid 参数,并将交叉验证折叠值设为 5。

rf_cv = GridSearchCV(estimator=RandomForestClassifier(), param_grid=grid, cv= 5)
rf_cv.fit(x_train, y_train)

我们现在可以使用了”。best_params_ "方法来为我们的模型输出最佳参数。

rf_cv.best_params_

作者图片

现在我们有了最佳的参数列表,我们可以使用这些参数运行基本的 RandomForestClassifier 模型,并与使用没有网格搜索的原始训练/测试分割获得的结果进行比较来测试我们的结果。

rf2 = RandomForestClassifier(n_estimators = 200, max_depth = 7, max_features = 'sqrt',random_state = 18, criterion = 'gini').fit(x_train, y_train)

作者图片

我们的更优模型的结果优于我们的初始模型,准确性得分为 0.883,而先前为 0.861,F1 得分为 0.835,而先前为 0.803。

合并 GridSearchCV 的一个缺点是运行时。如前所述,交叉验证和网格调整会导致较长的训练时间,因为模型必须经过多次迭代。整个 GridSearchCV 模型运行大约需要 4 分钟,这看起来不多,但是考虑到我们在这个数据集中只有大约 1k 个观察值。你认为进行 10 万次观察或者数百万次观察需要多长时间?

结论: 通过使用交叉验证和网格搜索,当与我们的原始训练/测试分割相比时,我们能够以最小的调整获得更有意义的结果。交叉验证是一种非常重要的方法,用于通过对训练数据集的所有部分进行训练和测试来创建更好的拟合模型。

感谢您花时间阅读这篇文章!我希望你喜欢阅读,并了解了更多关于如何将交叉验证和网格搜索应用到你的机器学习模型中。如果你喜欢你所读的,请关注我的个人资料,成为第一批看到未来文章的人!

交叉表或数据透视表(在 Pandas 中)决定何时使用哪个

原文:https://towardsdatascience/crosstab-or-pivot-table-in-pandas-deciding-when-to-use-which-a8ee3a9adfd0

交叉制表和数据透视表之间的选择可能会令人困惑

概观

熊猫的许多特征可以产生类似的结果。对于初学者和有经验的用户来说,这种明显的模糊性可能会令人困惑和沮丧。本文对pd.crosstabpd.pivot_table进行了比较,以帮助您了解两者的不同表现。

在这个演示中,我将引用一个个人数据源:我在 LinkedIn 上的发帖活动。这篇相关文章展示了如何自己查找和访问这些数据。

数据

如果您是来寻求热图建议的:请浏览文章末尾的代码摘录,它可以生成如下所示的最佳热图。

以下是我的shares.csv文件中的前五条记录,你可以从 LinkedIn 下载。

图片鸣谢:作者资料节选。显示了我在 LinkedIn 上的前五篇文章,在文章中有进一步的描述。

我在 2010 年(或者更早)开始了在 LinkedIn 上的生活。我的第一篇文章是“工作时吹口哨”。让它发生!”我不知道我在想什么。另外,显然我对 Audible 有着长久的兴趣。

根据这些信息,我将准备一个新的数据框,让我按年、月、日、周和小时来统计帖子。

# Prepare An Empty Year Of Data To Avoid Index Errors
next_year = int(str(date.today())[:4]) + 1
next_year = pd.date_range(start=f'1/1/{next_year}', 
                          end=f'12/31/{next_year}')
next_year = pd.DataFrame({'Count':[0] * len(next_year)}, 
                         index=next_year)# Extract The Columns Of Interest From Shares DataFrame
shares_viz = pd.DataFrame(shares.index)
shares_viz['Count'] = 1
shares_viz = shares_viz.set_index('Date')# Concatenate Shares With The Empty Year
shares_viz = pd.concat([shares_viz, next_year])shares_viz['Year'] = pd.DatetimeIndex(shares_viz.index).year
shares_viz['Month'] = pd.DatetimeIndex(shares_viz.index).month
shares_viz['DOfMonth'] = pd.DatetimeIndex(shares_viz.index).day
shares_viz['DOfWeek'] = pd.DatetimeIndex(shares_viz.index).dayofweek
svshares_viz['HourOfDay'] = pd.DatetimeIndex(shares_viz.index).hour

完成后,新数据将类似于:

图片鸣谢:作者资料节选。显示我的前五条记录,在文章中有进一步的描述。

熊猫杂交列表

根据文档pd.crosstab()将“计算两个(或更多)因素的简单交叉列表”以上述数据为例,我们可以使用pd.crosstab(shares_viz['Year'], shares_viz['Month'])快速总结历年来每月的发布模式:

图片鸣谢:作者资料节选。显示文章中描述的交叉列表。

要解释上面的(摘录)输出,请查看标记为 2020 的行。2020 年 1 月,我在 LinkedIn 上发了 39 次帖子。然而在 2020 年 6 月的晚些时候,我根本没有发帖。

熊猫旋转餐桌

数据透视表产生类似的结果,但不完全相同。pd.pivot_table()的文档解释说它将“创建一个电子表格风格的数据透视表作为数据框架”使用pd.pivot_table(shares_viz, values='Count', index='Year', columns='Month' aggfunc='sum'),我们看到以下版本:

图片鸣谢:作者资料节选。显示文章中描述的数据透视表。

结果相似。例如,在 2020 年,我们再次看到 2020 年 1 月有 39 个帖子,2020 年 6 月没有。

您还可以在两个表格中看到,2022 年 5 月或 5 月之后没有帖子,本文的数据贯穿于 2022 年 4 月。

差异

注意pd.crosstab()如何返回没有计数的0,而pd.pivot_table()如何返回没有计数的NaN

一些调整可以使输出更加匹配。例如,通过添加fillna(0)pd.pivot_table()的简单修改将会用0替换那些NaN值。

为什么这很重要

这在很多情况下都很重要。例如,考虑数据可视化。pd.crosstab()pd.pivot_table()的一个常见用例是将输出传递给sns.heatmap()

一个常见的热图用例是将月份放在行中,将月份中的日期放在列中。这样的热图概括了一年中的日常活动。在下图中,每个单元格(或方块)计算该行当月相应日期(列)的帖子数。

交叉列表热图

作者的形象化。显示文章中描述的热图。

在上面的许多问题中,这里是其中的一个:注意 2 月 29 日、30 日和 31 日有零,4 月、6 月、9 月和 11 月 31 日也有零。那些日子是不存在的。难道我们不应该把它们渲染成某种形式的空的吗?

数据透视表热图

作者的形象化。显示文章中描述的热图。

首先注意不存在的日子与此输出有何不同。在这里,数据透视表输出似乎以一种更有助于数据可视化的方式执行。

数据透视表优于交叉制表还有另一个重要原因(在这种特定情况下)。为了避免数据可视化和分析过程中的索引错误,上面的代码添加了一个“空”年份。上图交叉列表中,空的一年错误地将“计数”增加了一。

结论

在数据分析中,通常有许多方法来完成相同的任务。Pandas 是众多工具中的一个,它提供了多种方法和技术,可以产生相似但不完全相同的结果。

本文展示了pd.crosstab()pd.pivot_table()如何执行相似的功能——有时会产生相似的输出。输出结果可能非常相似,如果不仔细检查,分析师可能会无意中误报结果。

在上面给出的用例中,pd.pivot_table()是产生期望结果的最好和最快的选项。pd.crosstab()也是一种选择吗?是的,pd.crosstab()也是一个选择——但这需要额外的工作来确保输出完全符合预期。

这个故事的寓意是:一遍又一遍地检查你的代码和结果。

感谢阅读

你准备好了解更多关于数据科学职业的信息了吗?我进行一对一的职业辅导,并有一份每周电子邮件列表,帮助专业求职者获取数据。联系我了解更多信息。

感谢阅读。把你的想法和主意发给我。你可以写信只是为了说声嗨。如果你真的需要告诉我是怎么错的,我期待着尽快和你聊天。推特:@ adamrossnelsonLinkedIn:亚当罗斯尼尔森。

热图代码

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from datetime import dateshares = pd.read_csv('shares.csv').set_index(['Date']).sort_index()# Prepare An Empty Year Of Data To Avoid Index Errors
next_year = int(str(date.today())[:4]) + 1
next_year = pd.date_range(start=f'1/1/{next_year}', 
                          end=f'12/31/{next_year}')
next_year = pd.DataFrame({'Count':[0] * len(next_year)}, 
                         index=next_year)# Extract The Columns Of Interest From Shares DataFrame
shares_viz = pd.DataFrame(shares.index)
shares_viz['Count'] = 1
shares_viz = shares_viz.set_index('Date')# Concatenate Shares With The Empty Year
shares_viz = pd.concat([shares_viz, next_year])shares_viz['Year'] = pd.DatetimeIndex(shares_viz.index).year
shares_viz['Month'] = pd.DatetimeIndex(shares_viz.index).month
shares_viz['DOfMonth'] = pd.DatetimeIndex(shares_viz.index).day
shares_viz['DOfWeek'] = pd.DatetimeIndex(shares_viz.index).dayofweek
shares_viz['HourOfDay'] = pd.DatetimeIndex(shares_viz.index).hour# Month & Day Posting Patterns
plt.figure(figsize = (12,4))
sns.heatmap(pd.pivot_table(shares_viz, 
                           values='Count', 
                           index='Month', 
                           columns='DayOfMonth',
                           aggfunc='sum'),
            cmap=my_blues,
            annot=True)
plt.suptitle(f'Daily Monthly Activity Since {shares_viz["Year"].min()}', 
             horizontalalignment='right')
plt.title(f'{total_posts} Posts Over {shares_viz["Year"].max() - shares_viz["Year"].min()} Years',
          horizontalalignment='right')

SQL 中的 cte 是什么

原文:https://towardsdatascience/cte-sql-945e4b461de3

了解 SQL 中的公用表表达式(CTE)

由 Sunder Muthukumaran 在 Unsplash 拍摄的照片

编写清晰、易读和高效的 SQL 查询是团队中任何工程或分析过程的一个重要方面。这种查询可以有效地维护,并且在适当的时候可以很好地扩展。

为了实现这一点,开发人员和分析人员都可以轻松采用的一个 SQL 结构是通用表表达式(CTE)。

常用表表达式

公用表表达式(CTE)是一种构造,用于临时存储指定查询的结果集,以便后续查询可以引用它。CTE 的结果不是持久存储在磁盘上,而是其生命周期持续到引用它的查询的执行。

用户可以利用 cte,将复杂的查询分解成更容易维护和阅读的子查询。此外,公共表表达式可以在一个查询中被多次引用,这意味着您不必重复。假定 cte 是命名的,这也意味着用户可以让读者清楚地知道一个特定的表达式应该返回什么结果。

构造公共表表达式

每个 CTE 都可以使用WITH <cte-name> AS子句来构造

WITH sessions_per_user_per_month AS (
    SELECT
      user_id,
      COUNT(*) AS no_of_sessions,
      EXTRACT (MONTH FROM session_datetime) AS session_month,
      EXTRACT (YEAR FROM session_datetime) AS session_year
    FROM user_sessions
    GROUP BY user_id
)

在一个查询中可以指定多个 cte,每个 cte 之间用逗号分隔。cte 也可以引用其他 cte:

WITH sessions_per_user_per_month AS (
    SELECT
      user_id,
      COUNT(*) AS no_of_sessions,
      EXTRACT (MONTH FROM session_datetime) AS session_month,
      EXTRACT (YEAR FROM session_datetime) AS session_year
    FROM user_sessions
    GROUP BY user_id
),
running_sessions_per_user_per_month AS (
    SELECT
      user_id, 
      SUM(no_of_sessions) OVER (
        PARTITION BY 
          user_id, 
          session_month, 
          session_year
      ) AS running_sessions
    FROM sessions_per_user_per_month
)

然后,后续查询可以像任何表或视图一样引用 cte:

WITH sessions_per_user_per_month AS (
    SELECT
      user_id,
      COUNT(*) AS no_of_sessions,
      EXTRACT (MONTH FROM session_datetime) AS session_month,
      EXTRACT (YEAR FROM session_datetime) AS session_year
    FROM user_sessions
    GROUP BY user_id
),
running_sessions_per_user_per_month AS (
    SELECT
      user_id, 
      SUM(no_of_sessions) OVER (
        PARTITION BY 
          user_id, 
          session_month, 
          session_year
      ) AS running_sessions
    FROM sessions_per_user_per_month
)

SELECT 
  u.username,
  u.email
  u.country,
  s.running_sessions
FROM users u
LEFT JOIN sessions_per_user_per_month s
  ON u.user_id = s.user_id
WHERE country = 'US';

cte 与子查询

通常,使用子查询可以获得相同的结果。顾名思义,子查询是在另一个查询中定义的查询(也称为嵌套查询)。

有一种误解,认为 cte 往往比子查询执行得更好,但这不是真的。实际上, CTE 是一个语法糖,这意味着在后台,子查询仍然会被执行,但是在决定是否要编写一个公共表表达式或子查询时,您需要记住一些事情。

cte 比嵌套查询更具可读性。您需要做的不是在一个查询中包含两个或多个查询,而是定义一个 CTE 并在后续查询中引用它的名称。

这意味着cte 还可以被后续查询多次重用和引用。对于子查询,您必须一遍又一遍地重写相同的查询。

此外, CTE 可以是递归的,这意味着它可以引用自身。递归 cte 的语法与用于指定非递归 cte 的语法有些不同。您将需要使用WITH RECURSIVE来指定它,并使用UNION ALL来组合递归调用和基础用例(也称为锚)的结果:

-- Syntax used for recursive CTEs
WITH RECURSIVE <cte-name> AS (
  <anchor case>
  UNION ALL
  <recursive case>
)

我现在不打算谈论更多关于递归 cte 的细节,但是我计划在接下来的几天里专门为此写一篇文章,所以一定要订阅下面的内容,并且在它发布的时候得到通知!

最后的想法

公共表表达式提供了一种简单而强大的方式来编写干净、可读和可维护的 SQL 查询。用户可以利用这种结构来增强跨查询的可重用性,在某些情况下甚至可以提高性能,因为 CTE(临时结果集)可以被多次引用。

只要有可能,cte 应该优先于嵌套连接,因为后者会使您的代码混乱,如果需要多次,会使您的代码可读性更差。此外,cte 还可以是递归的,如果需要的话,这是一大优势。

尽管存在子查询可以提供比 cte 更大灵活性的用例,但是本文并不打算让您相信子查询是完全无用的!例如,因为 cte 必须在SELECT子句之前指定,这意味着它们不能在WHERE子句中使用。

成为会员 阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。

https://gmyrianthous.medium/membership

相关文章你可能也喜欢

三次样条:终极回归模型

原文:https://towardsdatascience/cubic-splines-the-ultimate-regression-model-bd51a9cf396d

为什么三次样条是最好的回归模型。

三次样条简介—作者

介绍

在本文中,我将介绍三次样条,并展示它们如何比高度线性回归模型更稳健。首先,我将遍历三次样条背后的数学,然后我将用 Python 展示这个模型,最后,我将解释龙格现象。

本文中使用的 python 库叫做 Regressio 。这是作者为单变量回归、插值和平滑创建的开源 python 库。

首先,三次样条是一种分段插值模型,它将三次多项式拟合到分段函数中的每一段。在两个多项式相交的每一点,一阶和二阶导数相等。这有助于形成平滑的拟合线。

分段函数示例—作者

例如,在上图中,我们可以看到三次多项式是如何被分割的。在这个图像中,有 3 块,中间的两个蓝色点是多项式相交的地方。我们可以看到函数在这两点周围是光滑的,并且整个函数是连续的。让我们看看拟合三次样条背后的数学原理。

数学

注:所有数学函数均由本文作者创建。

假设我们有三个数据点(2,3)、(3,2)和(4,4)。计算三次样条时,我们必须使用至少 2 个最多 n-1 个分段函数。这些分段函数中的每一个都是三次回归模型。

由于我们有 3 个数据点,我们将需要 2 个分段函数。我们将这些表示为 f1(x)和 f2(x)。

在上面的每个方程中,我们有 4 个未知变量(a,b,c,d)。我们需要建立一个方程组来计算每个方程中的未知数,所以我们总共有 8 个未知数。首先,我们知道对于第一点和第二点,这些点必须落在第一个函数上。

接下来,我们知道第二点和第三点一定落在第二个函数上。

我们还知道,两个函数相交时,它们的一阶导数必须相等。

然后,我们知道相交样条的二阶导数必须相等。

最后,我们希望每个端点的二阶导数为 0。这就形成了自然的三次样条曲线。

得到的 8 个等式如下。

然后我们可以插入三个数据点(2,3),(3,2),(4,4)。

上述方程可以用矩阵形式表示,并用线性代数求解。这些方程用大小为 4 x (n - 1)的矩阵表示。在这个例子中,矩阵将是 8×8。

然后我们可以把这些值代入我们的两个方程,我们就有了分段函数!

我们理解了算法背后的数学原理,这很好,但是我们不应该每次都必须手动计算权重。在下面的单元格中,我展示了如何在 Numpy 中求解方程。

Numpy 输出—按作者

回归图书馆

我们可以做一个比手工输入更好的。我们可以使用一个名为 Regressio 的轻量级 python 库。该库具有用于回归、插值和平滑的单变量模型。在下面的单元格中,我们正在安装库并生成 200 个数据点的随机样本。

随机数据样本—按作者

然后,我们可以简单地导入三次样条模型,并对数据进行拟合。

回归三次样条模型—作者

这是一个非常惊人的模型,因为我们可以使用低次多项式的组合来拟合高度可变的关系。使用 Regressio 可以很容易地试验不同的数据集和分段大小,我鼓励你使用这个库进行试验。现在让我们看看为什么这些模型比线性回归更好。

龙格现象

拟合样条模型正是 Carl David Tolmé Runge 在 1901 年所做的事情,他发现三次样条等多项式插值方法的效果优于高次线性回归模型。这是由于线性回归模型中区间边缘的大振荡。这在下面的这张图片中很好地显现出来。

作者:龙格现象

在此图像中,橙色线表示三次样条插值法,蓝色线表示线性回归模型。我们可以看到这两个模型的振荡非常不同。

对于线性回归模型,如果给我们一个位于训练数据边界之外的数据点,那么我们将得到一个异常预测。这是因为训练范围之外的模型导数的幅度非常大。这不是样条模型的情况,也是它更健壮的原因。

最后的想法

希望本文向您展示了三次样条如何比高度线性回归模型更稳健。在回归入门课程中,经常会跳过三次样条,但这是不应该的。在我看来,它们是线性回归模型的优秀解决方案,因为它们减轻了龙格现象。

我鼓励你尝试更多三次样条的例子,并看看回归库中的其他模型。作为这个包的作者,我使代码库对于那些试图理解每个模型如何工作的人来说非常可读。该库仍在生产中,并经常发生变化,请随意给它一颗星以跟踪它的变化,或者自己做出贡献!

如果你想要这篇文章的笔记本代码,你可以在这里找到。

参考

  • 约翰·c(2022 年 4 月 11 日)。等间距节点插值的龙格现象。约翰 d .库克|应用数学咨询。检索于 2022 年 7 月 26 日,发自 https://www.johndcook/blog/2017/11/18/runge-phenomena/
  • Python 编程和数值方法:工程师指南和… 加州大学伯克利分校。(2021).检索于 2022 年 7 月 26 日,来自https://python numerical methods . Berkeley . edu/notebooks/index . html
  • 维基媒体基金会。(2022 年 6 月 9 日)。龙格现象。维基百科。检索于 2022 年 7 月 26 日,来自https://en.wikipedia/wiki/Runge%27s_phenomenon

CUDA by Numba 示例

原文:https://towardsdatascience/cuda-by-numba-examples-1-4-e0d06651612f

阅读本系列文章,从头开始学习使用 Python 进行 CUDA 编程

第 1 部分,共 4 部分:开始并行之旅

介绍

顾名思义,GPU(图形处理单元)最初是为计算机图形而开发的。从那以后,它们在几乎每个需要高计算吞吐量的领域都变得无处不在。这一进步是由 GPGPU(通用 GPU)接口的开发实现的,它允许我们为通用计算编程 GPU。这些接口中最常见的是 CUDA ,其次是 OpenCL 以及最近的 HIP 。

图 1.0。运行稳定扩散带parallel lines futuristic space。学分:在 CreativeML Open RAIL-M 许可下拥有作品。

Python 中的 CUDA

CUDA 最初是为了与 C 兼容而设计的,后来的版本将其扩展到了 C++和 Fortran。在 Python 生态系统中,使用 CUDA 的方法之一是通过 Numba ,这是一个针对 Python 的实时(JIT)编译器,可以针对 GPU(它也针对 CPU,但这不在我们的范围之内)。使用 Numba,可以直接用 Python(的子集)编写内核,Numba 将动态编译代码并运行它。虽然它没有实现完整的 CUDA API,但与 CPU 相比,它支持的功能通常足以获得令人印象深刻的加速(有关所有缺失的功能,请参见Numba 文档)。

然而,Numba 并不是唯一的选择。CuPy 提供了依赖 CUDA 的高级功能、集成 C 语言内核的低级 CUDA 支持和可 JIT 的 Python 功能(类似于 Numba)。 PyCUDA 提供了更细粒度的 CUDA API 控制。最近,Nvidia 发布了官方的 CUDA Python ,这必将丰富生态系统。所有这些项目都可以互相传递设备阵列,您不会被限制只能使用一个。

在这个系列中

本系列的目标是通过用 Numba CUDA 编写的例子提供一个通用 CUDA 模式的学习平台。本系列不是 CUDA 或 Numba 的综合指南。读者可以参考他们各自的文档。本教程的结构灵感来自 Jason Sanders 和 Edward Kandrot 所著的《CUDA by Example:a Introduction to General-Purpose GPU Programming》一书。如果您最终不再使用 Python,而是想用 C 语言编写代码,这是一个极好的资源。该系列还有三个部分:第二部分、第三部分和第四部分。

在本教程中

我们将学习如何运行我们的第一个 Numba CUDA 内核。我们还将学习如何有效地使用 CUDA 来处理令人尴尬的并行任务,即彼此完全独立的任务。最后,我们将学习如何从 CPU 对内核运行时进行计时。

点击此处获取 Google Colab 中的代码。

GPU 并行编程简介

与 CPU 相比,GPU 的最大优势在于它们能够并行执行相同的指令。单个 CPU 内核将一个接一个地串行运行指令。在一个 CPU 上实现并行化需要同时使用其多个内核(物理内核或虚拟内核)。一台标准的现代计算机有 4-8 个内核。另一方面,现代 GPU 拥有数百个甚至数千个计算核心。这两者之间的比较见图 1。GPU 核心通常较慢,只能执行简单的指令,但它们的数量通常会成倍地弥补这些缺点。需要注意的是,为了让 GPU 拥有 CPU 的优势,它们运行的算法必须是可并行的。

我认为 钻研 GPU 编程主要有四个方面。第一个我已经提到了:理解如何思考和设计本质上并行的算法。这可能很难做到,因为有些算法是串行设计的,还因为同一算法可能有多种并行方式。

第二个方面是学习如何将位于主机上的结构(如向量和图像)映射到 GPU 构造(如线程和块)上。循环模式和辅助函数可以帮助我们做到这一点,但最终,实验对于充分利用 GPU 是非常重要的。

第三是理解驱动 GPU 编程的异步执行模型。不仅 GPU 和 CPU 彼此独立地执行指令,GPU 还有,允许多个处理流在同一个 GPU 中运行。在设计最佳处理流程时,这种异步性非常重要。

第四个也是最后一个方面是抽象概念和具体代码之间的关系:这是通过学习 API 及其细微差别来实现的。

当你阅读第一章时,试着在下面的例子中识别这些概念!

图 1.1。简化的 CPU 架构(左)和 GPU 架构(右)。算术发生在 ALU(算术逻辑单元)、DRAM 数据、高速缓存中,高速缓存甚至保存可以更快访问的数据,但通常容量较小。控制单元执行指令。信用:维基百科。

入门指南

我们将从设置我们的环境开始:高于 0.55 的 Numba 版本和支持的 GPU。

Numba CUDA 的主要工具是cuda.jit装饰器。它用于定义将在 GPU 中运行的函数。

我们首先定义一个简单的函数,它接受两个数字,并将它们存储在第三个参数的第一个元素上。我们的第一个教训是内核(启动线程的 GPU 函数)不能返回值。我们通过传递输入和输出来解决这个问题。这是 C 中常见的模式,但在 Python 中并不常见。

您可能已经注意到,在我们调用内核之前,我们需要在设备上分配一个数组。此外,如果我们想显示返回值,我们需要将它复制回 CPU。您可能会问自己,为什么我们选择分配一个float32(单精度浮点型)。这是因为,虽然大多数现代 GPU 都支持双精度算法,但双精度算法比单精度算法耗时 4 倍或更长。所以最好习惯用np.float32``npplex64而不是float/np.float64``complex/npplex128

尽管内核定义看起来类似于 CPU 函数,但内核调用略有不同。特别是,它在参数前有方括号:

add_scalars[1, 1](2.0, 7.0, dev_c)

这些方括号分别表示网格中的数量,以及块中线程的数量。随着我们学习使用 CUDA 进行并行化,让我们更深入地讨论一下这些意味着什么。

使用 CUDA 实现并行化

CUDA 网格的剖析

当一个内核启动时,它有一个网格与之相关联。一个网格由组成;一个滑块由螺纹组成。图 2 显示了一个一维 CUDA 网格。图中的网格有 4 个块。网格中的块数保存在一个特殊的变量中,这个变量可以在内核中被访问,称为gridDim.x.x是指网格的第一维度(本例中唯一的一个)。二维网格也有.y和三维网格,.z变量。截至 2022 年,没有 4 维或更高的网格。同样在内核内部,您可以通过使用blockIdx.x找出哪个块正在被执行,在本例中它将从 0 运行到 3。

每个程序块都有一定数量的线程,保存在变量blockDim.x中。线程索引保存在变量threadIdx.x中,在本例中从 0 到 7 运行。

重要的是,不同块中的线程被调度为不同的运行方式,可以访问不同的内存区域,并且在其他方面也有所不同(参见CUDA Refresher:The CUDA Programming Model进行简要讨论)。现在,我们将跳过这些细节。

图 1.2。一维 CUDA 网格。图片作者。

当我们在第一个例子中用参数[1, 1]启动内核时,我们告诉 CUDA 用一个线程运行一个块。用几个线程传递几个块,会多次运行内核。操纵threadIdx.xblockIdx.x将允许我们唯一地识别每个线程。

让我们尝试对两个数组求和,而不是对两个数字求和。假设每个数组有 20 个元素。如上图所示,我们可以启动一个每个块有 8 个线程的内核。如果我们希望每个线程只处理一个数组元素,那么我们至少需要 4 个块。启动 4 个块,每个块 8 个线程,然后我们的网格将启动 32 个线程。

现在我们需要弄清楚如何将线程索引映射到数组索引。threadIdx.x从 0 运行到 7,所以他们自己不能索引我们的数组。此外,不同的区块有相同的threadIdx.x。另一方面,他们有不同的blockIdx.x。为了获得每个线程的唯一索引,我们可以组合这些变量:

i = threadIdx.x + blockDim.x * blockIdx.x

对于第一个块,blockIdx.x = 0i将从 0 运行到 7。对于第二块,blockIdx.x = 1。从blockDim.x = 8开始,i将从 8 运行到 15。同样,对于blockIdx.x = 2i将从 16 运行到 23。在第四个也是最后一个程序块中,i将从 24 运行到 31。见下表 1。

我们解决了一个问题:如何将每个线程映射到数组中的每个元素…但现在我们有一个问题,一些线程会溢出数组,因为数组有 20 个元素,而i上升到 32-1。解决方案很简单:对于那些线程,不要做任何事情!

让我们看看代码。

在 Numba 的新版本中,我们得到一个警告,指出我们用主机数组调用了内核。理想情况下,我们希望避免在主机和设备之间移动数据,因为这非常慢。我们应该在所有参数中使用设备数组来调用内核。我们可以通过预先将阵列从主机移动到设备来做到这一点:

dev_a = cuda.to_device(a)dev_b = cuda.to_device(b)

此外,每个线程的唯一索引的计算会很快过时。令人欣慰的是,Numba 提供了非常简单的包装器cuda.grid,它是用网格维度作为唯一参数来调用的。新内核将如下所示:

当我们改变数组的大小时会发生什么?一种简单的方法是简单地改变网格参数(块的数量和每个块的线程数量),以便启动至少与数组中的元素一样多的线程。

设定这些参数需要一些科学和艺术。对于“科学”,我们会说(a)它们应该是 2 的倍数,通常在 32 到 1024 之间,以及(b)它们应该被选择为最大化占用率(有多少线程同时处于活动状态)。Nvidia 提供了一个电子表格可以帮助计算这些。对于“艺术”来说,没有什么可以预测内核的行为,所以如果你真的想优化这些参数,你需要用典型的输入来分析你的代码。实际上,现代 GPU 的“合理”线程数是 256。

在讨论矢量求和之前,我们需要讨论一下硬件限制。GPU 不能运行任意数量的线程和块。通常每个块不能有超过 1024 个线程,一个网格不能有超过 2 个⁶1 = 65535 块。这并不是说您可以启动 1024 × 65535 个线程…除了其他考虑因素之外,根据寄存器占用的内存大小,可以启动的线程数量是有限制的。此外,必须警惕试图同时处理不适合 GPU RAM 的大型数组。在这些情况下,可以使用单个 GPU 或多个 GPU 来分段处理数组。

INFO: 在 Python 中,硬件限制可以通过 Nvidia 的 *cuda-python* 库通过 函数 [*cuDeviceGetAttribute*](https://nvidia.github.io/cuda-python/module/cuda.html#cuda.cuda.cuDeviceGetAttribute) 在他们的文档 中获得。有关示例,请参见本节末尾的附录。

网格步长循环

如果每个网格的块数超过了硬件限制,但数组适合内存,我们可以使用一个线程来处理几个元素,而不是每个数组元素使用一个线程。我们将通过使用一种叫做网格步长循环的技术来实现。除了克服硬件限制之外,grid-stride 循环内核还受益于线程重用,这是通过最小化线程创建/销毁开销实现的。马克·哈里斯的博客文章 CUDA Pro 提示:用网格步长循环编写灵活的内核 详细介绍了网格步长循环的一些好处。

这项技术背后的思想是在 CUDA 内核中添加一个循环来处理多个输入元素。顾名思义,这个循环的步距等于一个网格中的线程数。这样,如果网格中的线程总数(threads_per_grid = blockDim.x * gridDim.x)小于数组元素的数量,那么一旦内核处理完索引cuda.grid(1),它将处理索引cuda.grid(1) + threads_per_grid等等,直到所有的数组元素都被处理完。事不宜迟,我们来看看代码。

这段代码与上面的非常相似,不同的是我们在cuda.grid(1)开始*,但是执行更多的样本,每threads_per_grid一个,直到我们到达数组的末尾。*

现在,哪一个内核更快?

定时 CUDA 内核

GPU 编程都是关于速度的。因此,准确测量代码执行是非常重要的。

CUDA 内核是由主机(CPU)启动的设备功能,当然它们是在 GPU 上执行的。GPU 和 CPU 不通信,除非我们告诉他们。因此,当 GPU 内核启动时,CPU 将简单地继续运行指令,无论它们是启动更多的内核还是执行其他 CPU 功能。如果我们在内核启动前后发出一个time.time()调用,我们将只计算内核启动所花的时间,而不是运行*。*

我们可以用来确保 GPU 已经“跟上”的一个函数是cuda.synchronize()。调用此函数将停止主机执行任何其他代码,直到 GPU 完成执行其中已启动的每个内核。

为了给内核执行计时,我们可以简单地计算内核运行和同步的时间。对此有两点需要注意。首先,我们需要使用time.perf_counter()time.perf_counter_ns()而不是time.time()time.time()不计算主机休眠等待 GPU 完成执行的时间。第二个警告是,来自主机的定时代码并不理想,因为存在与此相关的开销。稍后,我们将解释如何使用 CUDA 事件来为设备中的内核计时。马克·哈里斯有另一篇关于这个主题的优秀博文,题为 如何在 CUDA C/C++ 中实现性能指标。

在使用 Numba 的时候,有一个细节是我们必须注意的。Numba 是一个实时编译器,这意味着函数只有在被调用时才会被编译。因此,对函数的第一次调用计时也将对编译步骤计时,编译步骤通常要慢得多。我们必须记住,总是首先通过启动内核来编译代码,然后同步它,以确保没有任何东西留在 GPU 中运行。这确保了下一个内核无需编译就能立即运行。还要注意数组的dtype应该是相同的,因为 Numba 为参数dtypes的每个组合编译一个唯一的函数。

对于简单的内核,我们还可以测量算法的吞吐量,即每秒钟浮点运算的次数。它通常以 GFLOP/s(每秒千兆次浮点运算)来度量。我们的加法运算只包含一个翻牌:加法。因此,吞吐量由下式给出:

2D 的例子

为了结束本教程,让我们制作一个 2D 内核来对图像应用对数校正。

给定值在 0 和 1 之间的图像 I(x,y ),对数校正图像由下式给出

Iᵪ(x,y) = γ log₂ (1 + I(x,y))

首先让我们获取一些数据!

图 1.3。原始“月球”数据集。图片作者。

如你所见,数据在低端已经饱和。0.6 以上的数值几乎没有。

让我们来写内核。

让我们记下这两个for循环。请注意,第一个for循环从iy开始,第二个最里面的循环从ix开始。我们可以很容易地选择i0ix开始,而i1iy开始,这样会感觉更自然。那么我们为什么选择这个顺序呢?事实证明,第一种选择的内存访问模式效率更高。由于第一个网格索引是最快的一个,所以我们想让它匹配我们最快的维度:最后一个。

如果你不想相信我的话(你不应该相信!)您现在已经了解了如何对内核执行进行计时,您可以尝试这两个版本。对于像这里使用的这种小数组,这种差异可以忽略不计,但是对于更大的数组(比如 10,000 乘 10,000),我测得的加速大约是 10%。不是很令人印象深刻,但是如果我可以通过一次变量交换给你 10%的提高,谁会不接受呢?

就是这样!我们现在可以在校正后的图像中看到更多细节。

作为一个练习,尝试用不同的网格来计时不同的启动,以找到适合您的机器的最佳网格大小。

图 1.4。原始(左)和对数校正(右)“月球”数据集。图片作者。

结论

在本教程中,你学习了 Numba CUDA 的基础知识。您学习了如何创建简单的 CUDA 内核,并将内存转移到 GPU 来使用它们。您还学习了如何使用一种叫做网格步长循环的技术迭代 1D 和 2D 数组。

附录:使用 Nvidia 的 cuda-python 探测设备属性

为了对 GPU 的确切属性进行精细控制,您可以依赖 Nvidia 提供的底层官方 CUDA Python 包。

CUDA by Numba 示例

原文:https://towardsdatascience/cuda-by-numba-examples-215c0d285088

阅读本系列文章,从头开始学习使用 Python 进行 CUDA 编程

第 2 部分,共 4 部分:穿针引线

介绍

在本系列的第一期中,我们讨论了如何使用 GPU 运行令人尴尬的并行算法。令人尴尬的并行任务是那些任务彼此完全独立的任务,例如对两个数组求和或应用任何元素级函数。

图 2.0。用“穿针赛博朋克”运行稳定扩散。学分:在 CreativeML Open RAIL-M 许可下拥有作品。

在本教程中

许多任务虽然不是令人尴尬的并行,但仍然可以从并行化中受益。在本期的 CUDA by Numba Examples 中,我们将介绍一些允许线程在计算中协作的常用技术。

点击这里在 Google colab 中抓取代码。

本教程后面还有两个部分:第三部分和第四部分。

入门指南

导入和加载库,确保你有一个 GPU。

线程协作

简单并行归约算法

我们将从一个非常简单的问题开始这一部分:对一个数组的所有元素求和。串行地,这个算法非常简单。不借助 NumPy,我们可以这样实现它:

我知道,这看起来不太像蟒蛇。但是它强调了s正在跟踪数组中的所有元素。如果s依赖于数组的每一个元素,我们如何将这个算法并行化?首先,我们需要重写算法以允许一些并行化。如果有我们不能并行化的部分,我们应该允许线程相互通信。

然而,到目前为止,我们还没有学会如何让线程相互通信…事实上,我们以前说过,不同块中的线程是不通信的。我们可以考虑只启动一个块,但是请记住,在大多数 GPU 中,块只能有 1024 个线程!

我们如何克服这一点?那么,如果我们把数组分成 1024 个(或者适当数量的threads_per_block)的块,然后分别对每个块求和,会怎么样呢?最后,我们可以把每个数据块的结果相加。图 2.1 显示了一个非常简单的两块分割的例子。

图 2.1。对数组元素求和的“分治”方法。图片作者。

我们如何在 GPU 上做到这一点?首先,我们需要将数组分成几个块。每个块都对应于一个块,有固定数量的线程。在每个块中,每个线程可以对多个数组元素求和(grid-stride 循环)。然后,我们必须在整个块中计算每个线程的值。该位需要线程进行通信。我们将在下一个例子中讨论如何做到这一点。

由于我们是在块上并行化,内核的输出应该作为一个块来确定大小。为了完成缩减,我们将它复制到 CPU 并在那里完成工作。

警告 :共享数组必须

  • 要“小”。确切的大小取决于 GPU 的计算能力,通常在 48 KB 和 163 KB 之间。参见本表 项“每个线程块的最大共享内存量”。
  • 在编译时有一个已知的大小(这就是为什么我们调整共享数组 *threads_per_block* 而不是 *blockDim.x* )。诚然,我们总是可以定义一个 工厂函数 来共享任意大小的数组…但是要注意这些内核的编译时间。
  • *dtype* 由 Numba 类型指定,而不是 Numpy 类型(不要问我为什么!).

我在 Google Colab 上运行了这个,我们得到了 10 倍的加速。相当不错!

一种更好的并行归约算法

你现在可能想知道为什么我们把一切都命名为“幼稚”。这意味着有一些非幼稚的方式来做同样的功能。事实上,有很多技巧可以加速这类代码(参见 CUDA 演示中的 优化并行化缩减作为基准)。

在我们展示更好的方法之前,让我们回忆一下内核的最后一点:

我们几乎并行化了所有的事情,但是在内核的最后,我们让一个线程负责对共享数组s_block的所有threads_per_block元素求和。为什么我们不把这个求和也并行化呢?

听起来不错,怎么样?图 2.2 显示了如何在threads_per_block尺寸为 16 的情况下实现这一点。我们从 8 个线程开始工作,第一个线程将对s_block[0]s_block[8]中的值求和。第二个是s_block[1]s_block[9]中的值,直到最后一个线程将值s_block[7]s_block[15]相加。

在下一步中,只有前 4 个线程需要工作。第一个线程将对s_block[0]s_block[4]求和;第二,s_block[1]s_block[5];第三,s_block[2]s_block[6];第四个也是最后一个,s_block[3]s_block[7]

第三步,我们现在只需要 2 个线程来处理s_block的前 4 个元素。第四步也是最后一步将使用一个线程对 2 个元素求和。

由于工作在线程之间进行了划分,因此它被并行化了。当然,这不是每个线程的平均划分,但这是一种改进。在计算上,这个算法是 O(log2( threads_per_block)),而第一个算法是 O( threads_per_block)。在我们的例子中,简单算法有 1024 次运算,而改进算法只有 10 次!

还有最后一个细节。在每一步,我们都需要确保所有线程都已经写入共享数组。所以我们要调用cuda.syncthreads()

图 2.2。通过顺序寻址进行归约。鸣谢:马克·哈里斯, 优化 CUDA 中的并行还原

在我的机器上,这比简单的方法快 25%。

警告 :您可能想将 *syncthreads* 移动到 *if* 块内,因为在每一步之后,超出当前线程数一半的内核将不会被使用。然而,这样做将使名为 *syncthreads* 的 CUDA 线程停止并等待所有其他线程,而所有其他线程将继续运行。因此,停止的线程将永远等待永不停止的线程进行同步。要点是:如果同步线程,确保在所有线程 中调用 ***cuda.syncthreads()***

i = cuda.blockDim.x // 2
while (i > 0):
    if (tid < i):
        s_block[tid] += s_block[tid + i]
        cuda.syncthreads() # don't put it here
    cuda.syncthreads()  # instead of here
    i //= 2

数字减少

因为上面的归约算法很重要,Numba 提供了一个方便的cuda.reduce装饰器,将二进制函数转换成归约。上述长而复杂的算法可以替换为:

就个人而言,我发现手写的归约通常要快得多(至少快 2 倍),但是 Numba 递归非常容易使用。也就是说,我鼓励阅读 Numba 源代码中的 reduction 代码。

还需要注意的是,默认情况下,缩减拷贝到主机,这将强制同步。为了避免这种情况,您可以使用设备数组作为输出来调用缩减:

2D 还原示例

并行归约技术很棒,但如何将其扩展到更高维度并不明显。虽然我们总是可以用一个未展开的数组(array2d.ravel())来调用 Numba 归约,但是理解我们如何手动归约多维数组是很重要的。

在这个例子中,我们将结合我们对 2D 核的了解和对 1D 约简的了解来计算 2D 约简。

设备功能

到目前为止,我们只讨论了内核,这是启动线程的特殊 GPU 函数。内核通常依赖于较小的函数,这些函数在 GPU 中定义,并且只能访问 GPU 数组。这些被称为设备功能。与内核不同,它们可以返回值。

在本部分教程的最后,我们将展示一个跨不同内核使用设备函数的例子。该示例还将强调在使用共享数组时同步线程的重要性。

INFO*:CUDA 新版本中,内核可以启动其他内核。这叫做动态并行,Numba CUDA 还不支持。*

2D 共享阵列示例

在本例中,我们将在一个固定大小的数组中创建一个波纹图案。我们首先需要声明我们将使用的线程数量,因为这是共享数组所要求的。

图 2.3。左:来自同步(正确)内核的结果。右图:来自不同步(不正确)内核的结果。学分:自己的工作。受 Sanders 和 Kandrot 的 CUDA 示例中图 5.5 和 5.6 的启发。

结论

在本教程中,你学习了如何开发需要一个归约模式来处理 1D 和 2D 数组的内核。在此过程中,我们学习了如何利用共享阵列和设备功能。

CUDA by Numba 示例

原文:https://towardsdatascience/cuda-by-numba-examples-7652412af1ee

阅读本系列的第 3 部分,了解 Python 的 CUDA 编程中的流和事件

第 3 部分,共 4 部分:流和事件

介绍

在本系列的前两部分中(第一部分在这里,以及第二部分在这里,我们学习了如何用 GPU 编程来执行简单的任务,比如令人尴尬的并行任务、使用共享内存的缩减以及设备功能。我们还了解了如何对主机的函数计时——以及为什么这可能不是对代码计时的最佳方式。

图 3.0。运行稳定扩散与“湾流多彩空间平静”。学分:在 CreativeML Open RAIL-M 许可下拥有作品。

在本教程中

为了提高我们的计时能力,我们将介绍 CUDA 事件以及如何使用它们。但是在我们深入研究之前,我们将讨论 CUDA 流以及为什么它们很重要。

点击这里在 Google colab 中抓取代码。

本教程后面还有一个部分:第四部分。

入门指南

导入和加载库,确保你有一个 GPU。

当我们从主机启动内核时,它的执行会在 GPU 中排队,只要 GPU 完成了之前启动的所有任务,就会执行这个任务。

用户在设备中启动的许多任务可能依赖于之前的任务,“将它们放在同一个队列中”是有意义的。例如,如果您正在将数据异步复制到 GPU,以便用某个内核处理它,则该副本必须在内核运行之前完成。

但是,如果有两个相互独立的内核,将它们放在同一个队列中有意义吗?大概不会!对于这些情况,CUDA 有个流。您可以将流视为独立的队列,它们彼此独立运行。它们也可以并发运行,即同时运行。当运行许多独立的任务时,这可以大大加快总运行时间。

图 3.1。使用不同的流可以允许并发执行,从而提高运行时间。演职员表:张等 2021 (CC BY 4.0)。

Numba CUDA 中的流语义

我们将把到目前为止学到的两个任务进行排队,以创建一个规范化管道。给定一个(主机)数组a,我们将用它的规范化版本覆盖它:

a ← a / ∑a[i]

为此,我们将使用三个内核。第一个内核partial_reduce将是我们对第 2 部分的部分缩减。它将返回一个threads_per_block大小的数组,我们将把它传递给另一个内核single_thread_sum,后者将进一步把它简化为一个单独的数组(大小为 1)。这个内核将在一个单独的块上用一个单独的线程运行。最后,我们将使用divide_by就地除出原始数组,但我们之前计算的总和。所有这些操作都将在 GPU 中进行,并且应该一个接一个地运行。

当内核调用和其他操作没有流时,它们在默认流中运行。默认流是一个特殊的流,其行为取决于运行的是传统流还是每线程流。对我们来说,如果您想要实现并发,您应该在非默认流中运行任务,这样说就足够了。让我们来看看如何为内核启动、阵列拷贝和阵列创建拷贝等操作实现这一点。

在我们真正谈论流之前,我们需要谈论房间里的大象:cuda.pinned。这个上下文管理器创建了一种特殊类型的内存,称为页面锁定固定内存,CUDA 在将内存从主机转移到设备时会从中受益。

驻留在主机 RAM 中的内存可以随时被 分页 ,即操作系统可以偷偷将对象从 RAM 移动到硬盘。这样做是为了将不常用的对象移到较慢的内存位置,从而将快速 RAM 内存留给更急需的对象。对我们来说重要的是,CUDA 不允许从可分页对象到 GPU 的异步传输。这样做是为了防止持续不断的非常慢的传输:磁盘(分页)→ RAM → GPU。

为了异步传输数据,我们必须确保数据总是在 RAM 中,通过某种方式防止操作系统偷偷把它藏在磁盘的某个地方。这就是内存锁定发挥作用的地方,它创建了一个上下文,在这个上下文中,参数将被“页面锁定”,也就是说,被强制放在 RAM 中。参见图 3.2。

图 3.2。可分页内存与固定(页面锁定)内存。演职员表:里兹维等人 2017 (CC BY 4.0)。

从那时起,代码就非常简单了。创建了一个流,之后它将被传递给我们想要在该流上操作的每个 CUDA 函数。重要的是,Numba CUDA 内核配置(方括号)要求流位于第三个参数中,在块维度大小之后。

警告: 一般来说,将流传递给 Numba CUDA API 函数并不会改变它的行为,只会改变它运行所在的流。一个例外是从设备到主机的拷贝。当调用 *device_array.copy_to_host()* (无参数)时,复制同步发生。调用 *device_array.copy_to_host(stream=stream)* (带流)时,如果 *device_array* 没有被钉住,复制会同步发生。仅当 ***device_array*** 被钉住并且流被传递 时,复制才会 异步发生。

***INFO:***Numba 提供了一个有用的上下文管理器,可以将所有操作放入其上下文中;退出上下文时,操作将被同步,包括内存传输。例 3.1 也可以写成:

with cuda.pinned(a):
    stream = cuda.stream()
    with stream.auto_synchronize():
        dev_a = cuda.to_device(a, stream=stream)
        dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)
        dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream)
        partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)
        single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum)
        divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum)
        dev_a.copy_to_host(a, stream=stream)

将独立内核与流解耦

假设我们想要规格化不是一个数组,而是多个数组。对单独数组进行规范化的操作是完全相互独立的。因此,GPU 等到一个规范化结束后再开始下一个规范化是没有意义的。因此,我们应该将这些任务分成不同的流程。

让我们看一个规范化 10 个数组的例子——每个数组都使用自己的流。

现在让我们来比较一下单流。

但是哪个更快呢?当运行这些例子时,当使用多个流时,我没有获得一致的总时间改进。这可能有很多原因。例如,对于并发运行的流,本地内存中必须有足够的空间。另外,我们是从 CPU 计时的。虽然很难知道本地内存中是否有足够的空间,但从 GPU 进行计时相对容易。让我们学习如何!

***INFO:***Nvidia 提供了几个调试 CUDA 的工具,包括调试 CUDA 流。查看 Nsight 系统 了解更多信息。

事件

来自 CPU 的计时代码的一个问题是,它将包括比 GPU 更多的操作。

谢天谢地,有可能用 CUDA 直接从 GPU 计时事件。事件只是一个时间寄存器,记录了 GPU 中发生的事情。在某种程度上,它类似于time.timetime.perf_counter,与它们不同的是,我们需要处理这样一个事实,即当我们从 CPU 编程时,我们想要从 GPU 计时事件。

因此,除了创建时间戳(“记录”事件),我们还需要确保事件在访问其值之前与 CPU 同步。让我们来看一个简单的例子。

为内核执行计时的事件

计时 GPU 操作的一个有用方法是使用上下文管理器:

计时流的事件

在本系列文章的最后,我们将使用 streams 来更好、更准确地了解我们的示例是否受益于 streams。

结论

CUDA 关注的是性能。在本教程中,您学习了如何使用事件来精确测量内核的执行时间,这种方法可以用来分析您的代码。您还了解了以及如何使用它们让您的 GPU 保持忙碌,以及固定映射数组,以及它们如何改善内存访问。

Numba 的 CUDA 示例:原子和互斥

原文:https://towardsdatascience/cuda-by-numba-examples-c583474124b0

本系列的第 4 部分总结了使用 Python 从头开始学习 CUDA 编程的旅程

介绍

在本系列的前三期中(第 1 部分这里、第 2 部分这里和第 3 部分),我们已经了解了 CUDA 开发的大部分基础知识,例如启动内核来执行令人尴尬的并行任务、利用共享内存来执行快速缩减、将可重用逻辑封装为设备功能,以及如何使用事件和流来组织和控制内核执行。

图 3.0。运行稳定扩散以“大历风格的原子心妈妈专辑封面”。学分:在 CreativeML Open RAIL-M 许可下拥有作品。

在本教程中

在本系列的最后一部分中,我们将讨论原子指令,它将允许我们从多个线程安全地操作同一个内存。我们还将学习如何利用这些操作来创建一个 互斥体 ,这是一种允许我们“锁定”某个资源的编码模式,因此它一次只能被一个线程使用。

点击此处获取 Google colab 中的代码。

入门指南

导入和加载库,确保你有一个 GPU。

原子学

GPU 编程完全基于尽可能并行化相同的指令。对于许多“令人尴尬的并行”任务,线程不需要协作或使用其他线程使用的资源。其他模式,比如 reductions,通过算法的设计来确保相同的资源只被线程的子集使用。在这些情况下,我们通过使用syncthreads来确保所有其他线程保持最新。

在某些情况下,许多线程必须读写同一个数组。当试图同时执行读取或写入时,这可能会导致问题。假设我们有一个将单个值递增 1 的内核。

当我们用一个线程块启动这个内核时,我们将在输入数组中获得一个值 1。

现在,当我们启动 10 个 16 线程的块时会发生什么?我们将 10 × 16 × 1 的总数加到同一个存储元素上,因此我们应该希望得到存储在dev_val中的值 160。对吗?

事实上,我们不太可能在dev_val达到 160。为什么?因为线程是同时读写同一个内存变量的!

下面是当四个线程试图从同一个全局内存中读取和写入时可能发生的情况的示意图。线程 1–3 在不同的时间(分别为 t=0、2、2)从全局寄存器读取相同的值 0。它们都递增 1,并在 t=4、7 和 8 时写回全局内存。在 t=5 时,线程 4 的启动比其他线程稍晚一些。此时,线程 1 已经写入全局内存,因此线程 4 读取 1 的值。它最终会在 t=12 时将全局变量改写为 2。

图 4.1。几个线程试图从同一个全局内存中读写可能会导致竞争情况。学分:自己的工作。

图 4.2。当线程对内容进行操作时,资源被锁定读/写,我们确保每个线程在读取时获得更新的值,并且它的写入被其他线程看到。原子操作通常较慢。学分:自己的作品。

如果我们想得到我们最初期望的结果(如图 4.2 所示),我们应该用非原子加法运算代替原子运算。原子操作将确保一次由一个单独的线程完成对任何内存的读/写。让我们多谈谈他们。

原子加法:计算直方图

为了更好地理解在哪里以及如何使用原子,我们将使用直方图计算。假设一个人想计算字母表中的每个字母在某个文本中有多少个。实现这一点的简单算法是创建 26 个“桶”,每个桶对应于英语字母表中的一个字母。然后,我们将遍历文本中的字母,每当我们遇到“a”时,我们将第一个桶递增 1,每当我们遇到“b”时,我们将第二个桶递增 1,以此类推。

在标准 Python 中,这些“桶”可以是字典,每个都将一个字母链接到一个数字。由于我们喜欢在数组上操作 GPU 编程,我们就用数组来代替。我们将存储所有 128 个 ASCII 字符,而不是存储 26 个字母。

在此之前,我们需要将字符串转换为“数字”数组。在这种情况下,将 UTF-8 字符串转换为uint8数据类型是有意义的。

请注意,小写和大写字母有不同的代码。因此,我们将使用几个实用函数来只选择小写字母或大写字母。

此外,Numpy 已经提供了一个直方图函数,我们将使用它来验证我们的结果并比较运行时间。

“Numba 示例”的 CUDA 直方图。学分:自己的工作。

让我们编写自己的 CPU 版本的函数来理解其中的机制。

由于每个 ASCII 字符都映射到 128 元素数组中的一个 bin,所以我们需要做的就是找到它的 bin 并递增 1,只要该 bin 在 0 和 127(包括)之间。

我们已经准备好了我们的第一个 GPU 版本。

酷!所以至少我们的功能在起作用。内核非常简单,与串行版本具有相同的结构。它从标准的 1D 网格步长循环结构开始,与串行版本不同,它使用原子加法。Numba 中的原子 add 有三个参数:将被递增的数组(histo)、将看到 incremenet 的数组位置(arr[iarr],它相当于串行版本中的char),最后是histo[arr[iarr]]将被递增的值(即本例中的 1)。

现在,让我们加大赌注,将其应用于更大的数据集。

我们将处理大约 570 万个字符。让我们运行并记录到目前为止的三个版本。

学分:自己的工作。

以我们的 GPU 版本为基准,我们看到 NumPy 版本至少慢 40 倍,而我们的 CPU 版本慢数千倍。我们可以在几毫秒内处理这个 570 万字符的数据集,而我们简单的 CPU 解决方案需要 10 秒以上。这意味着我们有可能在几秒钟内处理 200 亿个字符的数据集(如果我们有一个超过 20 Gb RAM 的 GPU),而在我们最慢的版本中,这将需要一个多小时。所以我们已经做得很好了!

我们能改进它吗?好了,让我们再来看看这个内核的内存访问模式。

...
for iarr in range(i, arr.size, threads_per_grid):
    if arr[iarr] < 128:
        cuda.atomic.add(histo, arr[iarr], 1)

histo是一个位于 GPU 全局内存中的 128 元素数组。在任一点启动的每个线程都试图访问该数组的某个元素(即元素arr[iarr])。因此,在任何时候,我们都有大约threads_per_block * blocks_per_grid = 128 × 32 × 80 = 327,680 个线程试图访问 128 个元素。因此,我们平均有大约 32 × 80 = 2,560 个线程竞争同一个全局内存地址。

为了减轻这种情况,我们在共享存储器阵列中计算局部直方图。这是因为

  1. 共享阵列位于芯片上,因此读/写速度更快
  2. 共享数组对于每个线程块来说都是本地的,因此较少的线程可以访问并因此竞争它的资源。

INFO: 我们的计算假设字符是均匀分布的。要小心,像自然数据集这样的假设可能不符合它们。例如,自然语言文本中的大多数字符都是小写字母,而不是平均 2560 个线程竞争,我们将有 128×32×80÷26≈12603 个线程竞争,这就有更多的问题了!

之前我们有 2,560 个线程争用同一个内存,现在我们有 2,560 ÷ 128 = 20 个线程。在核的末尾,我们需要对所有的局部结果求和。由于有 32 × 80 = 2,560 个块,这意味着有 2,560 个块在争用全局内存。然而,我们确保每个线程只做一次,而以前我们必须这样做,直到我们用尽了输入数组的所有元素。

让我们看看这个新版本与以前的版本相比如何!

学分:自己的工作。

因此,这比原始版本提高了约 3 倍!

我们将块的数量设置为 32×SMs 数量的倍数,就像上一个教程中建议的那样。但是哪个倍数呢?让我们计时吧!

学分:自己的工作。

两件事:首先,我们需要两个轴来显示数据,因为原始版本(蓝色)要慢得多。第二,竖线显示对于某个功能有多少条短信是最佳的。最后,尽管简单版本不会因为添加了更多的块而变得更差,但共享版本却不是这样。要理解这是为什么,请记住共享数组版本有两个部分

  • 第一部分很少有线程竞争相同的(快速)内存(共享数组部分)。
  • 第二部分是许多线程竞争同一个(慢)内存(最后的原子加法)。

随着更多块的添加,在简单版本中,它很快就遇到了瓶颈,而在共享阵列版本中,竞争在第一部分保持不变,但在第二部分增加了。另一方面,太少的块不会产生足够的并行化(对于任一版本)。上图找到了这两个极端之间的“最佳点”。

用互斥锁锁定资源

在前面的例子中,我们使用了带有整数值的原子加法操作来锁定某些资源,并确保一次只有一个线程控制它们。加法不是唯一的原子操作,它不需要应用于整数值。Numba CUDA 支持对整数和浮点数的各种原子操作。但是曾几何时(CUDA compute 1.x),浮点原子是不存在的。因此,如果我们想用原子为浮点写一个归约,我们就需要另一个结构。

虽然现在原子确实支持浮点,但允许我们应用任意原子操作的“互斥”代码模式在某些情况下仍然有用。

互斥,也就是互斥,是一种向试图访问它的其他线程发出某种资源可用或不可用的信号的方式。互斥体可以用一个变量来创建,这个变量有两个值:

  • 0 : 🟢绿灯,继续使用某个内存/资源
  • 1 :🔴红灯,停止,不要试图使用/访问某个内存/资源

要锁定内存,应该向互斥体写入 1,要解锁,应该写入 0。但是需要注意的是,如果有人(自动地)写互斥体,其他线程可能正在访问该资源,至少会产生错误的值,甚至更糟,会产生死锁。另一个问题是互斥体只有在之前没有被锁定的情况下才能被锁定。因此,在写入 1(锁定)之前,我们需要读取互斥体并确保它是 0(未锁定)。CUDA 提供了一种特殊的操作来以原子方式完成这两件事:atomicCAS。在 Numba CUDA 中,它的名称更加明确:

cuda.atomicpare_and_swap(array, old, val)

如果array[0]处的当前值等于old(这是“比较”部分),该函数只会自动将val分配给array[0](这是“交换”部分);否则它现在将交换。此外,它自动返回array[0]的当前值。为了锁定一个互斥体,我们可以用

cuda.atomicpare_and_swap(mutex, 0, 1)

由此我们将仅在锁被解锁(0)时分配锁(1)。上面这行代码的一个问题是,如果线程到达它并读取 1 (locked ),它就会继续执行,这可能不是我们想要的。理想情况下,我们希望线程停止前进,直到我们可以锁定互斥体。因此,我们采取以下措施:

while cuda.atomicpare_and_swap(mutex, 0, 1) != 0:
    pass

在这种情况下,线程将一直存在,直到它可以正确地锁定线程。假设线程到达了一个先前锁定的互斥体。它的当前值是 1。所以我们首先注意到从curr = 1 != old = 0开始compare_and_swap而不是能够锁定它。它也不会退出while,因为当前值 1 不同于 0(while条件)。它将保持在这个循环中,直到最终能够读取当前值为 0 的未锁定互斥体。在这种情况下,从curr = 0 == old = 0开始,它也能够将 1 分配给互斥体。

要解锁,我们只需要自动给互斥体赋值一个 0。我们将使用

cuda.atomic.exch(array, idx, val)

它简单地自动赋值array[idx] = val,返回旧值array[idx](自动加载)。因为我们不会使用这个函数的返回值,在这种情况下,你可以把它看作一个原子赋值(也就是说,鉴于atomic_add(array, idx, val)array[idx] += val的赋值就像exch(array, idx, val)array[idx] = val的赋值)。

现在我们有了锁定和解锁机制,让我们重试原子的“添加一个”,但是使用互斥体代替。

上面的代码相当简单,我们有一个内核,它锁定线程的执行,直到它们自己可以得到一个解锁的互斥体。此时,他们将更新x[0]的值并解锁互斥体。在任何时候x[0]都不会被一个以上的线程读取或写入,这实现了原子性!

上面的代码中只有一个细节我们没有涉及,那就是cuda.threadfence()的使用。这个例子并不要求这样,但是要确保锁定和解锁机制的正确性。我们很快就会知道为什么了!

互斥点积

在本系列的第 2 部分中,我们学习了如何在 GPU 中应用缩减。我们用它们来计算一个数组的和。我们代码的一个不优雅的方面是我们把一些求和留给了 CPU。我们当时缺乏的是应用原子操作的能力。

我们将这个例子重新解释为点积,但是这一次将求和进行到底。这意味着我们不会返回“部分”点积,而是通过使用互斥体在 GPU 中使用原子求和。让我们首先将 reduce 重新解释为一个点积:

一切都检查过了!

在我们结束之前,我答应我们会再来一次。来自 CUDA“圣经”( B.5 .内存栅栏函数 ): __threadfence() 确保在调用 __threadfence() 之后,调用线程对所有内存的写操作不会被设备中的任何线程观察到,因为在调用 __threadfence() 之前,调用线程对所有内存的任何写操作都会发生。

如果我们在解锁互斥锁之前忽略线程防护,即使使用原子操作,我们也可能会读取陈旧的信息*,因为内存可能还没有被其他线程写入。同样,在解锁之前,我们必须确保更新内存引用。所有这些都不是显而易见的,而且是在许多年前在阿尔格拉夫等人 2015 首次提出的。最终,这个补丁在 CUDA 的勘误表中发布了,这个例子启发了这一系列的教程。*

结论

在本系列的最后一个教程中,您学习了如何使用原子操作,这是协调线程的一个基本要素。您还学习了互斥模式,它利用原子来创建自定义区域,一次只有一个线程可以访问这些区域。

收场白

在本系列的四个部分中,我们已经介绍了足够多的内容,可以让您在各种常见的情况下使用 Numba CUDA。这些教程并不详尽,它们旨在介绍并激发读者对 CUDA 编程的兴趣。

我们还没有涉及的一些主题有:动态并行性(让内核启动内核)、复杂同步(例如 warp-level、协作组)、复杂内存防护(我们在上面提到过)、多 GPU、纹理和许多其他主题。其中一些目前还不被 Numba CUDA 支持(从 0.56 版本开始),其中一些对于入门教程来说被认为是太高级的技术。

为了进一步提高你的 CUDA 技能,强烈推荐 CUDA C++编程指南,以及 Nvidia 博客文章。

在 Python 生态系统中,重要的是要强调 Numba 之外的许多解决方案可以提升 GPU。而且它们大多是互操作的,所以不需要只选择一个。 PyCUDA 、 CUDA Python 、 RAPIDS 、 PyOptix 、 CuPy 和 PyTorch 是正在积极开发中的库的例子。

用不同的水冲浓缩咖啡

原文:https://towardsdatascience/cupping-different-water-for-espresso-ec552f7640fa

咖啡数据科学

第三波水品鉴

第三波水给我发了几包水测试,我就先从咖啡品鉴开始。我想知道不同的浓度是否会改变味道。我通常需要在食物中加入更多的盐,所以我想也许更浓的水溶液会更有吸引力。然而,我没有注意到和拔火罐有什么不同。

我从一种我很熟悉的咖啡开始,我设置了四种浓度。我用了 2 倍、1.5 倍、1 倍和 0.75 倍包装的浓缩水。我主要是按照 SCA 拔火罐配 8.25g 咖啡和 150ml 热水。

从碗左到右:2x,1.5x,1x,0.75x .所有图片由作者提供。

然后,我用微波炉将水烧开,这样有助于减少不同咖啡浸泡时间的差异。

然后我倒了水。

当咖啡冷却时,我没有看到任何视觉差异。

当我剥掉外壳后,我没有闻到或看到任何不同。

当咖啡变凉时,我尝了很多次,我找不到质地、甜味、酸味或苦味的区别。我没有接受过拔火罐的训练,有经验的品尝者可能会发现不同之处。

我希望这次品尝会在更大的浓缩咖啡实验之前给我一个路标,但这个实验仍然给出了品尝水浓度差异的难度的有趣信息。

如果你愿意,可以在推特、 YouTube 和 Instagram 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。也可以在中关注我,在订阅。

我的进一步阅读:

我未来的书

我的链接

浓缩咖啡系列文章

工作和学校故事集

如何在 Python 中获取当前时间

原文:https://towardsdatascience/current-time-python-4417c0f3bc4f

使用 Python 以编程方式计算当前日期和时间

照片由 Djim Loic 在 Unsplash 上拍摄

如果你已经编程了一段时间,我敢肯定你遇到过必须处理日期(时间)和时间戳的用例。

当使用这样的结构时,确保使用时区敏感的对象是非常重要的。在生产环境中,为了完成某项任务,全球范围内许多不同的服务相互连接是很常见的。这意味着我们——作为程序员——需要确保避免时区无关的构造,这些构造可能会引入不必要的错误。

在接下来的几节中,我们将介绍几种不同的方法,您可以遵循这些方法以编程方式在 Python 中计算当前时间。

更具体地说,我们将演示如何使用datetimetime模块推断当前日期和/或时间以及时间戳。此外,我们将讨论如何将日期时间作为字符串处理,以及如何考虑时区。

如何用 Python 计算当前时间

现在为了以编程方式推断 Python 中的当前日期时间,我们可以使用如下概述的datetime模块:

>>> from datetime import datetime
>>>
>>> now = datetime.now()
>>> now
datetime.datetime(2022, 9, 30, 16, 34, 24, 88687)

上面的表达式将返回一个类型为datetime.datetime的对象。如果您想以更直观、更易读的格式打印日期时间,您需要做的就是将它转换为str(例如str(now))或者调用strftime()来指定您希望新字符串对象具有的确切字符串格式。

例如,假设我们希望只保留日期时间中的日期部分,而放弃时间信息。以下内容应该可以解决问题:

>>> now_dt = now.strftime('%d-%m-%Y')
>>> now_dt
'30-09-2022'

类似地,如果您想只保留 datetime 对象的时间部分,您可以使用time()方法:

>>> from datetime import datetime
>>> now_time = datetime.now().time()
>>> now_time
datetime.time(16, 43, 12, 192517)

同样,我们可以将上面的 datetime 对象格式化为一个字符串。如果我们想要丢弃毫秒记录部分,只保留小时、分钟和秒,那么下面的表达式就可以满足我们的要求:

>>> now_time.strftime('%H:%M:%S')
'16:43:12'

另一个可以帮助我们处理时间的有用模块是内置的time。根据操作系统和主机的日期时间配置,ctime方法将返回当前时间的字符串。

>>> import time
>>> time.ctime()
'Fri Sep 30 16:48:22 2022'

引入支持时区的日期时间对象

现在,我们在上一节中展示的问题是,我们创建的 datetime 对象是时区无关的。例如,我住在伦敦—这意味着如果我和另外一个住在美国或印度的人在同一时间点运行我们之前演示的相同命令,我们最终都会得到不同的结果,因为上面的所有表达式都将根据主机的时区来计算当前时间(这显然会因地点而异)。

通用协调时间(UTC)是一个全球标准,也被程序员群体所采用。UTC(几乎)等同于 GMT,它不会因为夏令时等而改变。用 UTC 交流与日期时间相关的需求是很常见的,因为它是通用时区,采用 UTC 可以帮助人们更容易地交流日期时间和日程安排。其他常见的编程结构(如时间戳/unix 纪元时间)也使用 UTC 进行计算。

回到前面的例子,让我们试着推断构造的 datetime 对象的时区。请再次注意,我的工作地点在伦敦,在撰写本文时,我们正处于英国夏令时(BST):

>>> from datetime import datetime
>>> now = datetime.now()
>>> tz = now.astimezone().tzinfo
>>> tz
datetime.timezone(datetime.timedelta(seconds=3600), 'BST')

现在,如果你在不同的时区运行上面的命令,你会得到不同的nowtz的值——这正是问题所在。

相反,我们可以用 UTC 时区计算当前的日期时间,这样我们所有人都会得到相同的计算结果。假设我住在伦敦(目前是英国夏令时),我们预计当前的 UTC 日期时间将比我当地的 BST 时区晚一个小时:

>>> from datetime import datetime
>>> now = datetime.utcnow()
>>> now
datetime.datetime(2022, 9, 30, 16, 22, 22, 386588)

注意,也可以通过调用replace()方法并提供datetime.timezone模块中可用的时区选项之一来更改日期时间对象的时区信息。

例如,让我们创建一个当前日期时间的日期时间对象(BST 格式):

>>> from datetime import datetime, timezone
>>> now = datetime.now()
>>> now
datetime.datetime(2022, 9, 30, 17, 26, 15, 891393)
>>> now_utc =  now.replace(tzinfo=timezone.utc)
datetime.datetime(2022, 9, 30, 17, 26, 15, 891393, tzinfo=datetime.timezone.utc)

最后的想法

在今天的文章中,我们展示了用 Python 计算日期时间和时间戳的几种不同方法。这可以通过使用两个内置模块中的一个来实现,即datetimetime

此外,我们讨论了使用时区感知构造的重要性。在生产环境中运行的现代系统通常涉及全球托管的许多不同的服务。

这意味着托管在不同国家的服务将位于不同的时区,因此我们需要以一致和准确的方式处理这种不规则性。

这就是我们通常想要使用时区感知对象的原因。此外,在编程环境中,坚持 UTC 时区和 unix 时间戳是很常见的。

在我即将发表的一篇文章中,我们将讨论更多关于 UTC 和 Unix 纪元时间的内容,所以请保持关注:)

成为会员 阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。

https://gmyrianthous.medium/membership

相关文章你可能也喜欢

用非常简单的代码为你的网站构建定制的基于 GPT 3 的聊天机器人

原文:https://towardsdatascience/custom-informed-gpt-3-models-for-your-website-with-very-simple-code-47134b25620b

当你建立一个基于 GPT 3 的在线聊天机器人时,学习 GPT 3、PHP 和 JavaScript,它专门针对你教授的给定主题

内容

  1. 简介
  2. GPT 3 号搭载的惊艳聊天机器人
  3. 用简单的 PHP 和 JavaScript 将 GPT-3 整合到你的网站中
  4. 以 OpenAI 提供的形式教授你的模型它不知道的事实数据
  5. 一个完整的网络应用程序,用户可以使用自己的 API 密钥与你定制的 GPT 3 聊天机器人进行交互

1.介绍

由 OpenAI 开发的 GPT-3 是一个专门从事语言处理的机器学习模型。给定一个输入文本,GPT-3 输出新的文本。根据输入和使用的 GPT-3 的确切风格,输出文本将符合一项任务,例如回答输入中提出的问题,或用附加数据完成输入,或将输入从一种语言翻译成另一种语言,或总结输入,或推断情感,甚至更疯狂的事情,例如根据输入中给出的指示编写一段计算机代码。在许多其他应用中!

经过数十亿参数和大量文本语料库的训练,GPT-3 是最大的可用模型之一。但是在所有的语言处理模型中,我发现 GPT-3 特别有吸引力,因为它有以下特点:

  • 它在线运行,所以我不需要下载任何东西到我想使用它的电脑或服务器上。我可以通过在源代码中包含对 GPT-3 API 的调用来使用它。
  • 以上包括编写调用 GPT-3 API 的 web 代码的可能性,因此您可以在您的 web 页面中包含该工具的强大功能。我们将在这里看到如何用简单的 PHP 和 JavaScript 实现这一点。
  • 尽管没有掌握可执行程序(因为它都在线运行),GPT-3 是非常可定制的。这允许你编写聊天机器人,它们的行为方式你可以调整,甚至更有趣,它们“知道”你教它们的特定主题。事实上,我们将在这里看到两种可能的方法之一来训练您的 GPT-3 模型来实现您的目标。
  • GPT-3 非常容易使用,例如在 Python 中或者在 JavaScript 和 PHP 中。

我最近测试了 GPT 3 号在协助科学教育和研究方面的能力和潜力,它充当了一个随时可用的机器人,可以回答学生或研究人员的问题。这些测试的一部分包括教 GPT-3 一些数据,然后人们可以就这些数据提问。

结果令人印象深刻,尽管有许多限制,主要是因为该模型当然并不真正理解它所读和写的内容…它只是一个统计模型,综合了语法上正确但事实上可能准确也可能不准确的文本。您可以在最近的文章中了解更多关于我所做的这些测试的信息:

重要的是,正如我在一些例子中展示的,特别是在这个例子中,通过正确设置 GPT-3 的参数,并用特别的内容训练它,它被证明非常“聪明”,特别是对于使用关于事实主题的自然语言的信息检索。我们将在这里利用这一点来制作一个网页,它知道你教 GPT-3 的一个特定主题。

通过正确设置 GPT-3 的参数,并用特别的内容对其进行训练,它被证明非常“聪明”,特别是对于使用自然语言对事实主题进行信息检索。我们将在这里利用这一点来制作一个网页,它知道你教 GPT-3 的一个特定主题。

2.GPT-3 允许的惊人聊天机器人

在本文中,我们将看到如何构建一个简单的聊天机器人,它知道你作为提示提供的特定主题。用户可以通过网页自然地与机器人聊天,问任何他们想要的。当问题涉及您告知聊天机器人的主题时,聊天机器人将根据该内容进行回复。最棒的是,你不需要以非常结构化的方式提问,因为你需要一个普通的问答匹配聊天机器人来理解你。相反,您可以用自然的方式检索信息。此外,这种反应取决于语境;例如,你可以谈论一个人的名字,然后用相应的冠词指代他或她。

举个例子,让我们比较一下普通的 GPT-3 聊天机器人在被问及我时的输出和 GPT-3 聊天机器人在被告知关于我和我的一些项目的简短简历时的输出。前者会编造一些东西,或者在最好的情况下不提供任何答案,而后者的答案会更准确,至少当你问它关于我提供的信息时。

让我先给你看这段与定制训练的 GPT-3 聊天机器人的简短对话。我用一种被 OpenAI 人称为“少量学习”的方法实现了这一点;它本质上是在提示问题(要发送给 GPT-3 API)之前加上一个包含相关信息的文本块。这不是很有效,因为它限制了您可以传递的信息量,还因为每次发送提示都会消耗很多令牌。但是极其简单实用。

你看到我的问题用粗体字;剩下的是聊天机器人的回答,我写了一些评论:

参观者:谁是卢西亚诺·阿布利亚塔?
助理:Luciano Abriata 是生物技术专家、化学博士、艺术家和内容创作者。

这个答案是从提供的培训段落中摘录的。

参观者:他创造了什么?
助理:卢西亚诺创建了一个名为 moleculARweb 的网站,这是一个通过增强现实(AR)内容进行化学和结构生物学教育和外联的网站。

该信息是正确的,尽管在培训段落中提供了它,但它与文本中的任何部分都不匹配。GPT-3 可能对它进行了再加工,或者可能它已经知道了,因为我们的网站自 2020 年以来一直在线。

参观者:那看起来很酷!molecularweb 上有出版物吗?
助理:对!该网站被刊登在 2021 年的《化学教育杂志》上:https://pubs.acs/doi/10.1021/acs.jchemed.1c00179

完美,链接是正确的。这条信息是在培训段落中提供的,不完全一样,但它得到了它。

我的特设训练 GPT-3 模型,热情地分享我们关于使用增强现实的化学教育网站的出版物。作者图。

参观者:我需要什么特殊的硬件来使用分子网吗?
助理:不需要,不需要什么特殊的硬件。你只需要一个网络浏览器和一个带摄像头的设备,比如智能手机、平板电脑或电脑。

同样,聊天机器人提供了正确的信息,同样,培训中并没有以这种方式给出,但信息是存在的。

参观者:你的意思是我可以在手机上运行它?
助理:是的!你可以在手机、平板电脑或电脑上运行它。参观者:我如何使用它?
助理:你可以登录网站,按照说明使用。

这里的答案是正确的,但奇怪的是看到聊天机器人增加了第二个问题!我很惊讶,这个问题在真实的对话中是很自然的…到这个时候,用户已经对网站有了相当多的了解,所以他想使用它是很自然的。

访问者:网址是什么?
助手:网址为https://molecular web . epfl . ch

很好,培训中提供了这些数据,但不完全是这样的。甚至没有提到网址,但 GPT-3“明白”用户在找什么。

参观者:太好了,谢谢。我会尝试一下。

(没有回答)

现在让我们看看,如果我们向一个未经训练的 GPT-3 聊天机器人问同样的问题会发生什么。所有的参数和上面的完全一样,除了我在这里没有应用任何的少射学习程序;也就是说,我没有向提示添加任何额外的数据。

参观者:谁是卢西亚诺·阿布利亚塔?

(没有回答)

但如果我问它一个更有名的卢西亚诺,它确实知道!

参观者:参观者:谁是卢西亚诺·帕瓦罗蒂?助理:鲁契亚诺·帕瓦罗蒂是意大利歌剧男高音,也是一名成功的跨界歌手。他是有史以来商业上最成功的男高音之一。

让我们继续问:

参观者:你知道 moleculARweb 吗?助手:我不知道分子网。

参观者:有没有利用增强现实进行化学教育的网站?助手:我不知道任何使用增强现实技术的化学教育网站。

参观者:你知道谷歌吗?助理:是的,我知道谷歌。

参观者:谷歌是什么?助理:谷歌是一家专注于互联网相关服务和产品的跨国科技公司。这些包括在线广告技术、搜索、云计算、软件和硬件。

我认为这足以看出我们的聊天机器人清楚地知道许多事情,事实上是标准 as-3 所知道的一切,但它只是不知道任何它从未见过的事情。

继续阅读,了解如何在你自己的网页中插入你自己的助手,并对其进行微调,以了解任何给定的主题——例如关于你和你的工作,这样它就可以像秘书一样与你网站的访问者聊天。

3.用简单的 PHP 和 JavaScript 将 GPT-3 整合到你的网站中

您首先需要一些东西:一个允许 PHP 和 JavaScript 的服务器、一个 API 密匙和一些您将耦合到 JavaScript 代码的 PHP 库。

I)可以运行 PHP 并允许 JavaScript 代码的服务器

最简单的解决方案是使用宿主服务,该服务本身提供 PHP 运行时并允许 JavaScript。我用的是 Altervista,它的基本免费包已经两者都允许了!你甚至不需要安装 PHP!

但你确实需要激活它(截至 2022 年 3 月,这是一项免费功能)。我使用的是 PHP 版本 8,无限制地启用所有连接很重要(否则它不会连接到 OpenAI API)。

作者截图。

ii)来自 OpenAI 的 API 密钥

大多数国家的人们都可以免费获得一个预先充值的 API 来试用这个系统。查看位于https://beta.openai/signup的官方 OpenAI 网站

关键:不要泄露你的 API 密匙(无论是个人还是因为你把它暴露在你的 JavaScript 代码中!)因为它的使用会烧你的学分!

我在这里给出的例子要求用户输入他/她自己的密钥。web 应用程序将密钥传递给调用 GPT-3 API 的 PHP 包装器(注意,密钥不会被存储,所以可以放心尝试我的代码,没有任何风险!).

关于这一点的更多信息,请参阅本文的其余部分。

iii)一个连接到 OpenAI 的 GPT-3 的 PHP 库,以及一种从你的网页的 HTML+JavaScript 代码使用这个库的方法

OpenAI 本身并不支持 PHP,但是有一个专门的开发人员社区,他们编写库来通过 PHP 调用 GPT-3 API(也可以从其他语言的代码中调用):

我尝试了几个可用的 PHP 库,决定选择这个:

https://github/karamusluk/OpenAI-GPT-3-API-Wrapper-for-PHP-8/blob/master/OpenAI.php

但是我必须对名为 OpenAI.php 的主文件做一些修改。您可以在这里获得我使用的最终文件:

http://lucianoabriata . alter vista . org/tests/GPT-3/open ai-PHP-library-as-used . txt

我做了一些小的修改,需要使它工作。其中一个小的修改是允许以编程方式传递用户的 API 键,而不是固定的。通过这种方式,你的应用程序的用户不会从你的帐户中花费代币!不好的一面是,他们需要自己得到一把钥匙。一个中间的解决方案是让你自己的密钥像最初的 OpenAI.php 文件一样硬编码在 PHP 文件中,然后在用户使用你的应用时向他们收费。

您还需要另一个文件,它将您的 HTML/JavaScript 文件连接到调用 GPT-3 API 的核心 PHP 文件。这是一个简短的 PHP 文件,内容如下:

<?php
//Based on tutorials and scripts at:
// [https://github/karamusluk/OpenAI-GPT-3-API-Wrapper-for-PHP-8/blob/master/OpenAI.php](https://github/karamusluk/OpenAI-GPT-3-API-Wrapper-for-PHP-8/blob/master/OpenAI.php)
// [https://githubhelp/karamusluk/OpenAI-GPT-3-API-Wrapper-for-PHP-8](https://githubhelp/karamusluk/OpenAI-GPT-3-API-Wrapper-for-PHP-8)//Thanks to this for hints about connecting PHP and JavaScript:
// [https://stackoverflow/questions/15757750/how-can-i-call-php-functions-by-javascript](https://stackoverflow/questions/15757750/how-can-i-call-php-functions-by-javascript)require_once “./OpenAI-PHP-library-as-used.php”;$apikey = $_GET[“apikey”];$instance = new OpenAIownapikey($apikey);$prompt = $_GET[“prompt”];$instance->setDefaultEngine(“text-davinci-002”); // by default it is davinci$res = $instance->complete(
 $prompt,
 100,
 [
 “stop” => [“\n”],
 “temperature” => 0,
 “frequency_penalty” => 0,
 “presence_penalty” => 0,
 “max_tokens” => 100,
 “top_p” => 1
 ]
);echo $res;
?>

连接 PHP 和 JavaScript 在 OpenAI 上运行 GPT-3

您的 JavaScript 代码只需异步调用上面 PHP 文件的 complete 函数,该函数在一个 实例 对象上定义(在上面的 PHP 文件中),该对象携带您想要传递的文本提示和参数。

这是在 JavaScript 中进行异步调用的方法(使用 JQuery):

var chatbotprocessinput = function(){
 var apikey = “Bearer <API KEY>“
 var theprompt = “(define prompt)“
 $.ajax({
 url: “phpdescribedfileabove.php?prompt=” + theprompt + “&apikey=” + apikey
 }).done(function(data) {
 console.log(data) //data has the prompt plus GPT-3’s output
 });
}

如果您检查我在本文后面向您展示的示例的源代码,您会发现它有点复杂。那是因为我的 web app 清理了输出(在 数据 )去掉了输入等东西;它还重新格式化文本,以粗体显示用户和聊天机器人的名称,并将整套输入和输出(随着用户与机器人聊天而增长)保存在一个内部变量中,以便 GPT-3 每次执行时都能获得有关对话的上下文。

4.以 OpenAI 提供的形式教授你的模型它不知道的事实数据

换句话说:为了更好地实现你的目标,教 GPT 3 号它需要知道什么

正如我上面所预料的,有两种主要的方法用特别的数据来“训练”你的 GPT-3 模型。一种,在这里使用,也在上面介绍过,叫做“少投学习”。少投学习非常简单:只要用几段相关信息来扩展你的提示(即 GPT-3 问题的输入)。

在我们上面看到的例子中(你可以玩这个,见下面的第 3 部分),用户会问聊天机器人关于我的问题,因为它应该为我回答,我给了它两个段落:

**第一段:**你好,欢迎来到卢西亚诺·阿布利亚塔的网站。我在这里代表卢西亚诺在线。我很了解他——我是一个开放的 GPT-3 模型,用卢西亚诺写的文字扩展。随意问任何问题。Luciano A. Abriata 博士是生物技术学家、化学博士、艺术家和内容创作者。在科学学科方面,Luciano 在结构生物学、生物物理学、蛋白质生物技术、通过增强和虚拟现实的分子可视化以及使用现代技术的科学教育的实验和计算方面拥有丰富的经验。卢西亚诺·阿布利亚塔 1981 年出生于阿根廷罗萨里奥。他在阿根廷学习生物技术和化学,然后移居瑞士,目前在瑞士洛桑联邦理工学院(EPFL)的两个实验室工作。他在 EPFL 的生物分子建模实验室和 EPFL 的蛋白质生产和结构核心实验室工作。他目前致力于基于网络的方法,以实现商品增强现实和虚拟现实工具,用于身临其境地查看和操纵分子结构。他还与多个团队合作研究分子建模、模拟和应用于生物系统的核磁共振(NMR)光谱。

**第二段:**文章标题:MoleculARweb:一个通过在商品设备中开箱即用的交互式增强现实进行化学和结构生物学教育的网站(化学教育杂志,2021:【https://pubs.acs/doi/10.1021/acs.jchemed.1c00179】T2)。文字:molecular web(https://molecular web . epfl . ch)最初是一个通过增强现实(AR)内容进行化学和结构生物学教育和宣传的网站,这些内容在智能手机、平板电脑和电脑等常规设备的网络浏览器中运行。在这里,我们展示了 moleculARweb 的虚拟建模工具包(VMK)的两个版本,用户可以通过定制打印的立方体标记(VMK 2.0)或通过鼠标或触摸手势在模拟场景中移动(VMK 3.0),在 3D AR 中构建和查看分子,并探索它们的机制。在模拟过程中,分子会经历视觉上逼真的扭曲、碰撞和氢键相互作用,用户可以手动打开和关闭它们,以探索它们的效果。此外,通过手动调整虚拟温度,用户可以加速构象转变或“冻结”特定的构象,以便在 3D 中仔细检查。甚至可以模拟一些相变和分离。我们在这里展示新的 VMKs 的这些和其他特征,将它们与普通化学、有机化学、生物化学和物理化学的概念的教学和自学的可能的具体应用联系起来;以及在研究中协助分子建模的小任务。最后,在一个简短的讨论部分,我们概述了未来化学教育和工作的“梦想工具”需要什么样的发展。"

每当用户向我的聊天机器人提问时,我的网页不只是发送问题,而是实际上将两段信息,加上之前的问题和答案,连接到新问题。是的,GPT-3 的输出是基于这些数据的,如果它在其中找到一些相关的内容(你仍然可以问它任何其他的事情,它可能仍然会回答)。

正如我前面提到的,通过“少量学习”来“训练”你的基于 GPT 3 的聊天机器人不是很有效,因为它限制了你可以传递的信息量,也因为它在你每次发送提示时都要消耗很多令牌。但是它非常简单和实用,正如您在上面的例子中看到的以及将在下面的第 3 节中看到的。

快速简单的少量学习的替代方法是执行 OpenAI 的人所说的“微调”。这是一个更稳定的过程,在此过程中,您只需训练您的 GPT-3 模型一次,然后将此训练存储在一个文件中以供以后使用。我还没有尝试过微调,但是你可以在这里查阅 OpenAI 的网站:

5.一个完整的网络应用程序,用户可以使用他们自己的 API 密钥与你定制的 GPT 3 聊天机器人进行交互

(如果你喜欢,也可以是你的 API 密匙,只要把它包含在你的 PHP 文件中,这样它就不会暴露出来——即使这样,注意你的用户会消耗你的信用。)

你可以很容易地编写一个 web 应用程序来实现一个基于 GPT 3 的聊天机器人。一旦从 OpenAI 获得 API 密钥,您也可以立即尝试我的示例。

作者图。

少投学习的例子:http://lucianoabriata . alter vista . org/tests/GPT-3/TDSarticle-Example-with-prompt . html

没有少拍学习的例子:http://lucianabriata . alter vista . org/tests/GPT-3/TDSarticle-Example-without-prompt . html

你可以

快速浏览一下代码:

作者截图。

额外收获:倡导网络编程

你注意到所有这些都是为网络准备的。事实上,本文是我提出的依赖于 web 编程的解决方案和未来工具的系列文章的一部分——从无缝的跨设备和跨操作系统兼容性开始。

以下是一些亮点,你可以查看我的个人资料:

https://pub.towardsai/read-public-messages-from-the-ethereum-network-with-simple-web-programming-70d8650e54e2

www.lucianoabriata我写作并拍摄我广泛兴趣范围内的一切事物:自然、科学、技术、编程等等。 成为媒介会员 访问其所有故事(我免费获得小额收入的平台的附属链接)和 订阅获取我的新故事 通过电子邮件 。到 咨询关于小职位 查看我的 服务页面这里 。你可以 这里联系我

用于危险区域绘图的自定义 Matplotlib 色彩映射表

原文:https://towardsdatascience/custom-matplotlib-colormaps-for-danger-zone-plots-62310983eb67

为更有意义的绘图构建精确的背景颜色渐变

莎伦·皮特韦在 Unsplash 上的照片

背景——这就是一切。在我们对数据进行排序、组织和解释之后,我们制作描述性的可视化来讲述数据的故事。当我们自己使用图表来理解数据时,一切都很好——我们理解了图表的背景。然而,如果我们的目标是通知其他人并在更广泛的受众中推动决策,我们希望图表不仅共享数据,而且明确地推动结论。你的观众不会像你一样了解数据,所以你要引导他们的思维过程。这通常需要像趋势线或颜色编码这样的情节元素,但即使这样,你也从你的观众那里假设了很多——上升趋势是好事还是坏事?他们怎么会知道?

在这里,我们将描述如何创建自定义 matplotlib cmap 对象(彩色地图)来创建“危险区域”图。这些创建的图表可以让技术和非技术受众立即理解,以显示某些数据何时会以对组织“不利”的方式发展。比较下面的两个图——在不知道这个数据是什么的情况下,你会从右边的图中知道第 2 个月和第 3 个月有些“不好”,只是增加了一些自定义的背景颜色渐变。

传统条形图与“危险区域”条形图—作者图片

我们也不总是能控制我们的数据是如何被共享的。一开始可能是向内部和外部客户进行演示(在演示中,您可以解释数据趋势和见解),但可能会变成通过电子邮件发送的数字,而没有更深入的背景。如果剧情可以自我解释表明一个应该得出的结论,那么对于第一次看它的人来说,它会减轻精神负担。这也有助于确保其他人得出和你一样的结论。

什么构成了色彩映射表(cmap)?

在我们开始定制 cmaps 之前,让我们深入了解一下它们是如何工作的。内置的 matplotlib cmaps 是四种颜色渐变组:顺序、发散、循环和定性。在 matplotlib 的文档中有一个坚实的可视化存储库供您选择,这里,但是这里有一个快速的体验:

不同预加载 cmap 类型的示例—图片由作者提供

当我们获取一个 cmap 对象时,python 会生成一个 256 元素的颜色列表,用于定义色彩映射表。每种颜色在内部都表示为红-绿-蓝-阿尔法(RGBA)的 4 元素数组,其中阿尔法最好被认为是颜色的“透明度”。因此,例如,如果我们拉出'Reds' cmap 对象,并研究第 100 种颜色(256 种颜色中的一种),我们会看到:

调查“红色”cmap

当然,没有一个预先制作的 cmaps 会完全符合我们想要制作适当的“危险图”的特定颜色和过渡点。但是有了这种对 cmap 核心元素的直接访问,重写行以得到我们想要的就变成了一个有趣的命题。这当然是一种方法(我在我的 github 笔记中提到了这一点),但这比需要的要难,尤其是当试图手动混合颜色时。

作为一个更加自动化的路径,我们将使用 matplotlib 中的LineasrSegmentedColormap()。但在此之前,我们需要一个轻量级的函数来查看我们创建的色彩映射表,然后再将它们应用到我们的绘图中:

红色渐变图—图片由作者提供

构建自定义 CMAPs

LinearSegmentedColormap()函数允许我们给出一个颜色列表,以及一个“节点”列表。“节点”指的是颜色图上列出的颜色所在的点(从 0 到 1 ),除此之外,它与它的邻居混合。因此,在下面的示例中,256 元素 cmap 的元素 1(节点 0.0)为纯绿色(rgba= 0,1,0,1),元素 128 (0.5)为纯黄色(1,1,0,1),元素 256(1.0)为纯红色(1,0,0,1)。

3 元素 cmap —作者图片

最终,我们将设计一个功能,在这个功能中,我们可以定制颜色过渡的地方。通过添加更多节点,我们可以有效地做到这一点。如果两个相邻的节点是相同的颜色,那么它们之间的空间将自动是相同的颜色。看看下面的例子,我们添加了一些重复的颜色:

5 元素 cmap —作者图片

啊!更紧密的过渡!在这种情况下,我们在 40%和 50%之间从绿色过渡到黄色,在 60%-70%之间从黄色过渡到红色。太好了!这本身可以用来手动创建自定义阴影危险图所需的 cmaps。然而,当我们处理数据时,我们更熟悉我们希望 cmap 改变颜色的 ,而不是总轴长度的百分比。让我们将这个函数化到我们的图中。

双色和三色定制 CMAPs

使用同样的技术,我们将建立 2 色混合和 3 色混合。这两个函数都接受我们想要使用的颜色——请记住,LinearSegmentedColormap()非常灵活,可以接受颜色作为名称(’ Red ')、rgba ([1,0,0,1]),甚至十六进制(#ff0000)。事实上,这里有一个简单的颜色选择器工具可以帮助你得到你想要的颜色。这些函数还接受颜色之间过渡的值、过渡的宽度以及绘图轴限制(最小值和最大值),以便可以适当地缩放过渡值。

目标过渡 cmap —作者提供的图片

在函数调用中,我们假设有一个最小值为 0、最大值为 10 的图,转换正好发生在 5 处,宽度为 2。果然,转换发生在 256 元素生成的 cmap 的中途。

将这个概念扩展到一个三色函数,我们做了很多相同的事情,只是稍微复杂一点。

三色目标过渡 cmap —作者图片

应用于地块

首先,让我们生成一些虚拟数据用于我们的绘图。这是一个数据图表,显示了医院每月接待的患者数量:

要将任何梯度 cmap 添加到 matplotlib 图的背景中,我们使用imshow(),它将数据显示为图像。最难通过的变量是第一个变量(“X”)。这将图中的一个位置映射到 cmap 中的一个元素。因为我们想线性地遍历我们的自定义 cmap,我们将使用np.linspace()来创建一个 0 到 1 之间的 256 个元素的线性数组。我们还想传入extent参数,让imshow()知道我们正在扩展 cmap 的范围。最后,注意ylim()给出了一个(y min,y max)的输出列表,所以在调用two_color_cmap()函数时,我们使用括号符号来访问每一个作为限制。

应用于我们的数据并设置一个任意的转换点,我们得到这个:

双色危险区域图-作者提供的图像

或者,如果想在进入红色“危险”区域之前创建一个“警告”区域,我们可以使用三色函数添加一些黄色:

三色危险区域图—图片由作者提供

厉害!现在,我们的观众的目光被吸引到了第二个月,那里的患者数量高得令人无法接受。

水平条形图

这些自定义 cmaps 也支持水平绘图。我们只需要对我们的绘图代码做一些调整。最重要的是我们如何改变imshow()中的“X”变量。在这种情况下,我们删除了转置.T,它将grad从一组 256 个 1 元素的数组变成了一个 256 元素的数组。用print(grad)研究grad变量,以理解我们如何工作的细微差别。最后,注意np.linspace()现在从 0- > 1。如果我们想要翻转 cmap 的显示方式,我们可以像之前一样将它变回 1- > 0。

水平三色危险区域图—图片由作者提供

应用于线形图

在许多情况下,我们希望突出显示数据的可接受“范围”。在这种情况下,高值和低值都表明有问题,但在中间有一个令人满意的中间值。对于这些情况,线形图更能说明问题。使用我们制作的相同数据和相同的three_color_cmap()函数,我们可以用自定义 cmap 制作一个线图。

具有修改的 cmap 过渡宽度的线图-作者提供的图像

左边是我们的标准“混合”彩色地图,但我们的自定义 cmap 函数非常适合零转换 cmap。如果故障值非常明确,并且没有“回旋余地”,则右侧的零转换 cmap 会更有用。这对于存在已知控制上限和下限的统计过程控制(SPC) 图特别有用。

结论

除了视觉上吸引人之外,危险的情节给我们的情节增加了背景。它们使我们的数据能够自己说话,并可以立即授权任何人识别代表何时出现问题的数据点。有了这些 cmap 生成函数的框架,天空就是极限——制作你自己的,想加入多少颜色就加入多少颜色!正如你所看到的,一旦我们的设置功能到位,它们很容易实现,所以你没有燃烧卡路里产生完美的情节。

像往常一样,整个代码遍历笔记本可以从我的 github 中找到。 如果觉得有用请跟我来! 欢呼声此起彼伏,快乐的编码声此起彼伏。

用 BERT 进行自定义命名实体识别

原文:https://towardsdatascience/custom-named-entity-recognition-with-bert-cf1fd4510804

亚历山德拉在 Unsplash 上的照片

如何使用 PyTorch 和拥抱脸对文本中的命名实体进行分类

命名实体识别(NER)

是信息提取的一个子任务,它试图定位非结构化文本中提到的命名实体并将其分类成预定义的类别,如人名、组织、位置、医疗代码、时间表达式、数量、货币值、百分比等。

作者图片

背景:

在本文中,我们将使用一些我在之前的 文章中介绍过的概念。

BERT 是一种基于 Transformer 编码器的语言模型。如果你对《变形金刚》不熟悉,我推荐你阅读这篇惊人的文章。

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

伯特一言以蔽之:

  • 它将一个或多个句子的嵌入标记作为输入。
  • 第一个令牌总是一个叫做**【CLS】**的特殊令牌。
  • 句子之间由另一个叫做**【SEP】**的特殊记号分隔。
  • 对于每个令牌,BERT 输出一个称为隐藏状态的嵌入。
  • 伯特接受了掩蔽语言模型下一句预测任务的训练。

屏蔽语言模型(MLM) 中,一个输入单词(或标记)被屏蔽,伯特必须试图找出被屏蔽的单词是什么。对于下一个句子预测(NSP) 任务,伯特的输入中给出了两个句子,他必须弄清楚第二个句子是否在语义上跟随第一个句子。

你想想看,解决命名实体识别任务意味着用标签(人,位置,…).因此,完成这项任务最直观的方式是获取每个令牌的相应隐藏状态,并通过一个分类层将其输入。最终的分类器共享权重,因此实际上我们只有一个分类器,但对于演示目的,我认为更容易将它想象成有更多的分类器。

作者图片

从上面的图片中你可以看到我们将使用一个叫做蒸馏伯特的伯特的轻量级版本。这个经过提炼的模型比原来的小了 40%,但是在各种 NLP 任务上仍然保持了大约 97%的性能。
你可以注意到的另一件事是,BERT 的输入不是原始单词而是令牌。BERT 已经关联了一个预处理文本的标记器,以便它对模型有吸引力。分词器通常将单词拆分成子词,此外还会添加特殊的记号:**【CLS】表示句子的开始,【SEP】分隔多个句子,以及【PAD】**使每个句子具有相同数量的记号。

此外,每个标记嵌入与一个嵌入的句子相加,嵌入的句子是一个向量,该向量以某种方式添加了信息,即标记是指作为输入给 BERT 的第一个还是第二个句子
由于与递归神经网络不同,变压器模型中的计算是并行的,因此我们失去了时间维度,即辨别句子的第一个单词和第二个单词的能力等。
因此,每个标记还被加和到一个位置嵌入中,该嵌入考虑了标记在序列中的位置

如果你想了解更多关于 BERT 或他的单词标记器的信息,请查看以下资源:

https://huggingface.co/blog/bert-101 https://huggingface.co/docs/transformers/tokenizer_summary

我们来编码吧!

资料组

我们要用的数据集叫做CoNLL-2003,你可以在 Kaggle 上找到(带开放许可证)。

直接从 Colab 下载 Kaggle 数据集(记得上传你的个人 Kaggle 密钥)。

进口

进口

分割数据集

让我们加载数据帧的前 N 行,并更改列名。然后将数据帧分成训练集、开发集和测试集。

分割数据

自定义类别

我现在要定义这个项目需要的三个类。第一个类定义了我们的蒸馏模型。不需要在预训练的语言模型之上手动构建分类器,因为 HuggingFace 已经为我们提供了一个内置的模型,其中包含作为最后一层的分类器。这个型号叫做DistilBertForTokenClassification*。*

蒸馏器类

init 方法将输入分类的维度,即我们可以预测的令牌数,并实例化预训练的模型。

向前计算简单地采用 输入 _ id(令牌)和 注意 _ 屏蔽 (告诉我们令牌是否是填充符的 0/1 数组)和在输出字典中返回:{loss,logits}

模型类

NerDataset 类

第二类是模块 nn 的扩展。数据集,它使我们能够创建自定义数据集。

给定输入数据帧,该方法将对文本进行标记化,并将额外生成的标记与正确的标记进行匹配(如我们在上一篇文章中所述)。现在您可以索引数据集,它将返回一批文本和标签。

自定义数据集

度量跟踪类

有时计算火车循环中的所有指标很烦人,这就是为什么这个类帮助我们做到这一点。您只需要实例化一个新对象,并在每次对批处理进行预测时调用 update 方法。

自定义方法

  • tags_2_labels :获取标签列表和将标签映射到标签的字典的方法,并返回与原始标签关联的标签列表。

标签对标签

  • tags_mapping :取输入一个数据帧的 tags 列,返回: (1) 一个将标签映射到索引(标签)的字典 (2) 将索引映射到标签的字典 (3) 标签对应的标签O***(4)*一组在训练数据中遇到的唯一标签,这些标签将定义分类器的维数。

标签映射

  • match_tokens_labels :从标记化的文本和原始标记(与单词而非标记相关联)中,它为每个单独的标记输出一个标记数组。它将一个标记与其原始单词的标签相关联。

  • freeze_model :冻结模型的最后几层,防止灾难性遗忘。

  • 列车 _ 环线:通常的列车环线。

主要的

现在让我们使用主作用域中的所有内容。

创建映射标记-标签

标记文本

火车模型

现在,您应该会得到与这些类似的结果

结果

恭喜您,您基于预先训练的语言模型构建了您的命名实体识别模型!

最后的想法

在这篇实践文章中,我们看到了如何利用预训练模型的功能来创建一个简单的模型,从而在很短的时间内解决命名实体识别难题。请记住,当您根据新数据训练整个模型时,您可能会过多地更改原始 DistilBert 权重,从而降低模型的性能。为此,您可以决定冻结除最后一层(分类层)之外的所有层,并防止出现名为 精神错乱遗忘 的问题。在模型选择阶段,你可以通过冻结最后的 k 层来尝试不同的模型。

结束了

马赛洛·波利蒂

Linkedin , Twitter , CV

基于概率模型的客户终身价值评估

原文:https://towardsdatascience/customer-lifetime-value-estimation-via-probabilistic-modeling-d5111cb52dd

深入了解 BG-NBD,这是一个有影响力的分层模型,有助于了解客户的购买行为

耐嚼在 Unsplash 上拍照

客户终身价值的重要性

客户终身价值(CLV)是客户在其关系存续期间对公司的总价值。实际上,这个“价值”可以定义为收入、利润或分析师选择的其他指标。

CLV 是一个重要的跟踪指标,原因有二。首先,一家公司对其整个客户群的 CLV 总和给出了其市场价值的大致概念。因此,一家总 CLV 高的公司对投资者来说是有吸引力的。其次,CLV 分析可以指导客户获取和保留策略的制定。例如,可以特别关注高价值客户,以确保他们对公司保持忠诚。

许多 CLV 模型已经发展到不同的复杂程度和精确度,从粗略的启发式到复杂概率框架的使用。在本系列文章中,我们深入探讨其中之一:贝塔几何负二项分布(BG-NBD)模型。这个模型由 Fader、Hardie 和 Lee 于 2005 年开发,由于其可解释性和准确性,已经成为该领域最有影响力的模型之一。

本系列由三篇文章组成,每一篇都建立在另一篇的基础上。我们的游戏计划如下:

  1. 在第 1 部分(这一部分),我们将实现对 BG-NBD 模型及其假设的 ELI-5 理解。
  2. 在第 2 部分中,我们将研究 Python 库 生存期 ,它允许我们以类似于 scikit-learn 的方式方便地将 BG-NBD 模型拟合到数据集,并且几乎可以立即获得模型参数的最大似然估计。我们还将探索寿命支持的各种下游分析。
  3. 在第三部分中,我们将从贝叶斯的角度来看实现 BG-NBD 模型的另一种方法。我们将看到贝叶斯分级 BG-NBD 模型如何允许我们将我们对客户行为的先验直觉注入到模型中。为此,我们将使用 Python 库 PyMC3

BG-NBD 的范围

在深入挖掘 BG-NBD 的数学之前,我们需要了解它能做什么和不能做什么。需要记住两个主要限制:

该模型仅适用于非合同性的连续采购。

该模型只处理 CLV 计算的一个组成部分,即购买数量的预测。

让我们更详细地了解一下这些限制。

BG-NBD 适用于非合同性的连续采购

根据卖方和买方之间的关系,企业可以是契约性企业,也可以是非契约性企业。

  • 契约性商业,顾名思义,是一种买卖关系受合同约束的商业。当任何一方不再想继续这种关系时,合同终止。由于有了合同,在某一点上某人是否是企业的客户就不会有歧义。
  • 另一方面,在非合同业务中,采购是在没有任何合同的情况下根据需要进行的。

我们可以进一步区分连续离散设置:

  • 连续设置中,购买可以在任何给定的时刻发生。大多数采购情况(如食品杂货采购)都属于这一类别。
  • 在一个离散的环境中,购买通常以某种程度的规律性周期性地发生。这方面的一个例子是每周杂志购买。

BG-NBD 模型处理非合同的、持续的情况,这是四种分析中最常见也是最具挑战性的。在这种情况下,客户流失不是显而易见的,任何时候都可能发生。这使得区分已经无限期流失的顾客和将来还会回来的顾客变得更加困难。正如我们将在后面看到的,BG-NBD 模型能够为这两个选项中的每一个分配概率。

BG-NBD 专注于预测交易数量

给定期间客户的 CLV 可以通过两个数字相乘来计算:

  1. 客户在此期间的预测交易数量。
  2. 每次购买的预测价值。

通常这两个组件是分开处理和建模的。BG-NBD 模型解决了第一个问题——预测交易数量,这在许多方面是两者中更困难的。

第二个组成部分,购买的预期价值,可以通过使用简单的试探法,如取所有过去购买的平均值,或通过更复杂的概率模型,如伽马-伽马模型(也是由 BG-NBD 的作者创建的)。

直觉

在进入模型的数学之前,让我们试着理解模型在概念层面上是如何工作的。

让我们想象以下场景。日期是 2021 年 12 月 31 日,你是一家蛋糕店的经理。您已经仔细记录了今年发生的所有交易,并希望预测您的客户在 2022 年可以完成多少笔交易。

来源: pixabay

您碰巧也是一名熟练的数据科学家,您计划通过将模型与您的数据相拟合来实现这一预测。这个模型应该能够以一种可解释的方式描述客户的购买行为。

在开发模型时,您可以考虑一些假设。

每个顾客有不同的购买率

你已经注意到有些人每天都买蛋糕,有些人每个周末都买。其他人只在平均每六个月一次的特殊场合购买。您的模型需要一种方法来为每个客户分配不同的购买率。

每个客户都可以随时停止成为你的客户

在竞争激烈的蛋糕行业,忠诚度是无法保证的。**在任何时候,你的客户都可能会离开你的公司去另一家公司。**让我们将这种离开称为先前活跃客户的“停用”。

为了方便地建立停用模型,我们可以假设它只能在成功购买之后发生。也就是说,每次在你的商店购物后,顾客会决定是继续购买还是放弃购买。当客户决定选择后者时,就会发生停用。

我们假设停用既是永久的又是潜在的。**永久,**因为客户一旦决定流失,就再也不会回来了。潜伏,因为他不会明确的让你知道他不再是你的客户。

说明

有了这些假设,让我们考虑下面的场景,我们有两个客户,A 和 b,他们每个人都在 2021 年进行了一些交易,每笔交易都用一个红点表示。

我们能否知道哪些客户已经停用,哪些客户仍会经常光顾您的商店并为您的未来收入做出贡献?

答案是肯定的——在一定程度上。例如,看上面 A 的模式,我们看到他过去经常购物,但我们已经有一段时间没见到他了。因为他的事务间时间比他上一次事务后的时间短得多,很可能 A 已经停用了。

另一方面,B 是一个不经常购物的人,与她平均的购物间隔期相比,她最近一次购物的时间并不长。她很可能会回来。

现在让我们将这些假设和直觉发展成一个更复杂的模型!

数学模型

概率建模:导论

传统上,CLV 是使用过去数据的简单函数来计算的。例如,我们可以通过取过去交易价值的固定分数来估计未来交易的价值。毫不奇怪,这样的计算过于简单,不可靠,也无法解释。

资料来源:英国地质调查局-NBD 文件

另一方面,BG-NBD 模型是一个概率模型。在概率模型中,我们假设我们的观察结果(即交易)是由我们可以使用概率分布建模的物理过程产生的。我们的任务是估计能最好地解释我们现有观察结果的参数。一个常用的选择是找到这些参数的最大似然估计量。然后,我们可以使用这些估计的参数来执行未来的预测。与第一个相比,这个概率框架通常更健壮、更准确、更易解释。

有了这些介绍,现在让我们将上面定性描述的假设转换成一个可靠的概率框架。

泊松过程用于模拟交易,指数分布用于模拟购买间隔时间

首先,让我们关注活跃客户的重复购买行为。我们可以假设,只要客户仍然活跃,他们的交易就遵循具有恒定购买率𝜆.的泊松过程有了这个假设,我们可以将下次购买时间δt建模为由𝜆.参数化的指数分布该发行版的 PDF 如下:

每个活跃客户都有自己的指数分布,我们可以用它来预测下次购买的概率。

上图显示了与两个客户相关的两个指数分布的 PDF。第一个顾客(蓝色曲线)通常每天购买一个蛋糕(他的购买率𝜆是 1 个蛋糕/天)。他的下一次购买发生在他当前购买的一天内的概率可以通过在 0 和 1 之间取蓝色曲线下的面积并计算为 0.63 来得到

第二个顾客每周只买一个蛋糕(他的𝜆是 1/7 蛋糕/天)。在进行同样的整合后,我们看到他的下一次购买发生在明天之前的可能性要小得多(P = 0.13)。

伽马分布,用于描述人群中购买行为的变化

有必要考虑一下,所有这些𝜆’s 不同的顾客都有助于整个商店的𝜆配送。我们现在的任务是模拟这个𝜆分布。为此,我们需要遵守以下要求:

  • 这种分布最好是经过充分研究的。
  • 因为𝜆只能取正实数的值,所以选择的分布必须只有正值。
  • 分布需要足够灵活,以模拟具有不同购买行为的不同客户群。

伽马分布勾选了所有方框,是 BG-NBD 中用于模拟𝜆.的分布由形状参数 r 和比例参数α **参数化;**这两个参数的不同组合导致伽马分布呈现不同的形状。这是发行版的 PDF:

值得注意的是,这种伽马分布不仅仅是一些理论上的胡言乱语。事实上,特定的伽马分布定量描述了特定客户群的集体购买行为,并具有重要的商业含义。

例如,下图中的蓝线显示了向下倾斜、左倾的伽玛分布,这是将 r 和α都设置为 1 的结果。如果这种分布符合我的客户群,我不会太高兴——严重的左偏意味着我的大部分客户的购买率𝜆接近于零。也就是说,他们几乎不买蛋糕!

另一个伽玛分布显示为橙色。这是一种更健康的分布,其中𝜆在 2 左右达到峰值,这意味着该客户群中相当大的一部分每天购买两块蛋糕。不算太寒酸!

现在,有一个小小的书呆子注意——泊松/伽马分布的组合,我们一直用来模拟我们客户的购买行为,也被称为负二项分布(NBD )。是的,这就是我们模型名字的由来。

客户的停用被建模为几何过程

现在让我们来处理停用过程。如前所述,每次购买后,客户将决定是否停用。我们可以为这种去激活分配一个概率 p 。因此,根据移动几何分布分配客户停用后的交易。这种离散分布的 PMF 如下所示:

这个 PMF 是非常直观的——它来自于注意到(1)如果一个客户在 xᵗʰ交易后停用,他一定在之前的 x-1 次交易中幸存下来,以及(2)每次幸存都带有概率(1- p )。请注意,根据定义,客户必须在停用之前至少执行过一次交易(否则他一开始就不会成为我们的客户!).

下图比较了两个客户的 p = 0.01 和 p = 0.1。

我们可以看到, p 越高,失活发生得越早。与 p = 0.1(橙色)的客户相比, p = 0.01(蓝色)的客户提前停用的概率要低得多。

描述失活概率变化的贝塔分布

类似于𝜆,考虑顾客群与分布相关联是有用的。然而这一次,我们不能使用伽玛分布,它没有上限。我们需要另一个同样灵活的分布,但是它的取值范围是从 0 到 1(因为 p 只能在 0 到 1 之间)。

这一次,贝塔分布符合我们的需求。这是 Beta 版的 PDF:

我们可以看到,该分布由两个正的形状参数来参数化, ab 。下面是一些贝塔分布的例子:

与 Gamma 分布相似,Beta 分布也有商业含义。你会希望看到一个左偏β分布,它的大部分权重接近 0,这表明你的大部分客户都有较低的 p 值,不太可能提前停用。上图中的橙色线就是一个例子。

另一个快速注意事项——正是这种贝塔/几何分布的组合导致了 BG-NBD 模型中的“BG”。现在你知道了!

将一切联系在一起:个体水平上可能性的数学模型

我们已经研究了定量描述客户行为的所有分布。那么我们如何获得这些分布的最佳参数呢?

一种方法是获得最大似然估计量(MLE),这是一种参数估计量,可以最大化模型产生实际观察到的数据的可能性。

让我们把它说得更具体些。假设我们目前在时间 T ,我们正在回顾一个特定客户的历史交易,该客户的购买率为𝜆.他在tt5 做了第一笔交易,在t做了最后一笔交易。这些画在时间线上的点看起来像这样:**

我们可以通过以下步骤推导出此人的个人水平可能性函数:

  • 第一笔交易发生在 t ₁ 的可能性用我们之前阐述的指数分布来描述:

  • t ₂发生第二笔交易的可能性是客户在 t ₁后保持活跃的概率— (1- p ) —乘以标准指数可能性分量:

  • 这样的可能性模式对每个后续交易重复,即 xᵗʰ交易在 t ₓ发生的可能性为:

  • 现在,让我们分析在ₓ.的最后一笔交易之后发生了什么我们没有观察到 t ₓ与 T 之间的任何交易;这种缺席可能是由于以下两种情况之一:
  1. 客户在 t 完成最后一笔交易后停用。我们知道,这种情况发生的概率是 p
  2. 在这段时间里,他仍然很活跃,但没有进行任何交易。这种情况发生的概率是:

  • 观察我们所观察到的交易模式的可能性就是早期交易的所有可能性乘以两种情况的可能性之和:

  • 以上定义的可能性公式适用于在观察期内进行了一些购买的客户。由于我们假设所有客户一开始都是活跃的,客户在时间[0,T]之间不进行任何购买的可能性是标准的指数函数:

  • 最后,结合上述两个可能性公式,我们获得了一个适用于所有客户的通用公式,而不管他们进行了多少次交易(或没有交易):

然后,我们可以有计划地尝试不同的值 p 和𝜆,并选择一个( p, 𝜆)组合来最大化这种可能性。这些参数值被称为最大似然估计量(MLE ),代表描述个人购买行为和去激活概率的“最佳”参数。

请注意,这个个体水平的似然函数只涉及三个需要由数据提供的未知变量:

  • x : t **重复交易的次数。**这也叫【重复】频率
  • t ₓ: 客户最后一次交易时的年龄,也就是他第一次和最后一次交易之间的时间。这也叫做近因
  • T : **客户在分析点的年龄,**即从他的第一笔交易到分析时间所经过的时间。

有趣的是,我们可以看到早期交易的时间不是公式的一部分。

**其行对应于不同的客户 id 并且其列指示每个客户的 x、t ₓ和 T 的数据集被称为“RFM 格式”。**这里的“R”和“F”分别代表最近和(重复)频率。同时,“M”代表货币价值;这是一个我们不会在分析中使用的列,因为我们不关心交易值。RFM 格式是 CLV 分析中常用的标准格式。

缩小:总体水平上可能性的数学模型

作为一家拥有(希望如此)众多客户的公司,我们常常对关注单个客户不太感兴趣。相反,我们想把我们的客户群作为一个整体来分析。具体来说,我们感兴趣的是获得描述我们整个业务绩效的最佳伽玛和贝塔分布。

就像我们如何使用 MLE 获得个体的最佳 p 和𝜆一样,我们也可以使用 MLE 获得总体的最佳 r、 α **、 a、b。在本文中,我不会推导总体水平的似然方程;它已经够长了。然而,如果你已经理解了上面的数学,你应该可以很好地投入到 BG-NBD 论文中清晰解释的推导中。

结尾部分

BG-NBD 的其他应用

到目前为止,我们已经围绕 CLV 计算进行了讨论,这也是 BG-NBD 最初的目的。然而,BG-NBD 比这更通用。事实上,它可以用于模拟任何涉及不同“用户”进行重复“交易”的现象,并预测(1)如果这些“用户”仍然“活跃”,他们将进行多少次未来“交易”,以及(2)在分析期间他们仍然“活跃”的概率。例如:

  • 通过探索用户的使用历史来预测移动应用的未来使用频率。
  • 通过分析你的远房亲戚的通话模式,计算她还活着的概率。
  • 通过查看你的火绒约会对象发短信的频率来检查他们是否对你不感兴趣。

前进

好吧,我知道我们已经经历了很多数学,这可能是具有挑战性的。您可能会想:有没有一种方法可以跳过所有这些等式,使用这个模型的现成实现来开始从中获得商业价值?

我听到了!在系列的第 2 部分中,我们将查看 Python 库生存期,它允许我们使用几行代码从给定的过去事务记录中获得 r、αT6、a、和 b 的 MLE。该库还包含其他有用的分析和绘图功能,使我们能够从 BG-NBD 模型和其他相关模型中获得业务洞察力。

之后,在第 3 部分中,我们将检查 BG-NBD 的替代实现,它从贝叶斯角度接近参数估计。这个贝叶斯框架将允许我们“注入”我们的领域知识和/或信念到建模过程中。

我希望在那里见到你!

参考

[1]“计算你的顾客”简单的方法:帕累托/NBD 模型的替代方案(布鲁斯·哈迪 et。阿尔,2005)

[2]货币价值的伽玛-伽玛模型(Bruce Hardie et。阿尔,2013)

:所有图像、图表、表格、方程式,除特别注明外,均归本人所有。

如果你对这篇文章有任何意见或者想联系我,请随时通过 LinkedIn 给我发一个联系方式。另外,如果你能支持我,通过我的推荐链接成为一名中级会员,我将非常感激。作为一名会员,你可以阅读我所有关于数据科学和个人发展的文章,并可以完全访问所有媒体上的故事。

用 Python 进行客户细分(实现 STP 框架——第 1/5 部分)

原文:https://towardsdatascience/customer-segmentation-with-python-implementing-stp-framework-part-1-5c2d93066f82

使用 Python 实现层次聚类的分步指南

艾萨克·史密斯在 Unsplash 上拍摄的照片

我正在开始一个新的博客文章系列,在那里我将向你展示如何用 Python 一步一步地应用流行的 STP 营销框架。STP(细分、目标、定位)是现代营销中一种众所周知的战略方法,可以帮助您了解您的客户群(细分),并更有效地将您的产品投放到目标受众中。这是一种从以产品为中心过渡到以客户为中心的方法。STP 框架允许品牌制定更有效的营销策略,高度关注目标受众的需求。

在这个博客系列中,我将描述如何通过细分更好地了解你的客户,我们将看到不同的方法,然后我们将看到如何定位自己,以便更好地为客户服务。

这第一篇文章将着重于理解和预处理数据集,并以一种简单的方式对我们的客户进行细分。
在接下来的帖子中,我们将应用一种更复杂的分割方式。

整个笔记本和参考数据都可以在 Deepnote 笔记本中找到。

**Table of contents** ∘ [STP Framework](#ebab)
  ∘ [Environment Setup](#43f8)
  ∘ [Data Exploration](#c243)
  ∘ [Data Preprocessing](#c124)
  ∘ [Hierarchical Clustering](#7b10)
  ∘ [Conclusion](#a70d)

STP(细分、目标、定位)框架

细分 帮助您根据潜在或现有客户的人口统计、地理、心理和行为特征,将他们划分为不同的群体。每个群体的顾客都有相似的购买行为,并可能在不同的营销活动中采取相似的行动。像“一刀切”的一般营销活动并不是最好的营销策略。

:一旦你了解了你的客户群,下一步就是选择一部分要关注的客户群。很难制造出让每个人都满意的产品。这就是为什么最好专注于最重要的客户群,让他们开心。以后你总是可以扩大你的关注范围,瞄准更多的顾客。

选择目标市场时,最重要的考虑因素是:

  • 分段的大小
  • 增长潜力
  • 竞争对手的目标市场和产品

目标活动利用定性检查,属于广告领域。这就是为什么我们不会在这个博客系列中关注目标阶段。

定位 :在这一步,你把前两步考虑到的不同属性绘制出来,然后定位你的产品。这有助于您确定您想要提供的产品和服务最能满足客户的需求。定位还指导您如何以及通过什么渠道向客户展示这些产品。在这个过程中还使用了另一个框架,叫做营销组合。当我们到达那个模块时,我将简要描述营销组合。

环境设置

在这整个系列教程中,我将使用 Deepnote 作为唯一的开发工具。Deepnote 是一个令人敬畏的基于 web 的类似 Jupiter 的笔记本环境,它支持多用户开发生态系统。默认情况下,Deepnote 安装了最新的 Python 和著名的数据科学库。如果需要,通过“requirements.txt”或常规的 bash 命令安装其他库也非常容易。

以后我会单独写一篇关于如何更有效地使用 Deepnote 的帖子。

数据探索:了解数据集

我们将使用一个预处理过的快速消费品(FMCG)数据集,它包含 2000 个人的客户详细信息以及他们的购买活动。

用 Excel 查看数据集是一个很好的做法,因为在 Excel 中滚动和浏览数据集更容易(只要数据集不是超级大)。谢天谢地,Deepnote 为 CSV 数据集提供了更好的浏览体验。它每列显示一个分布图,让你对每个变量一目了然。

让我们使用 Pandas 库将客户数据集加载到笔记本中。

我们可以看到,对于每个客户,我们有一个 ID 列和七个其他人口统计和地理变量。
以下是每个变量的简短描述:

将数据集加载到 pandas 数据框后,我们可以应用“描述”方法来获得数据集的摘要。这显示了我们数据集的一些描述性统计数据。

对于数值变量,输出包括计数、平均值、标准差、最小值、最大值以及 25、50 和 75 个百分点。对于分类变量,输出将包括计数、唯一、顶部和频率。

让我们继续我们的数据探索,看看变量的成对相关性。相关性描述变量之间的线性相关性,其范围从-1 到+1。其中+1 表示非常强的正相关,而-1 表示强的负相关。相关性为 0 意味着这两个变量不是线性相关的。

相关矩阵中的对角线值总是 1,因为它表示变量与其自身的相关性。不幸的是,仅仅看数字很难对变量之间的关系有一个大致的了解。让我们用 Seaborn 的热图来绘制这些数字。

从这张热图中,我们可以看到年龄与受教育程度或职业与收入之间有很强的正相关关系。这些变量间的相关性在分割过程的特征选择中将是重要的。

为了理解变量之间的成对关系,我们可以使用散点图。在 Deepnote 笔记本里,你会找到散点图的例子。

数据预处理

即使我们正在处理的客户数据是干净的,我们也没有对数据进行任何统计处理。

在计算单个记录之间的距离时,大多数机器学习算法使用两个数据点之间的欧几里德距离。
当我们的数据集中的要素比例不同时,此计算的执行效果不佳。例如,尽管 5 千克和 5000 克的含义相同,但在距离计算中,算法会对高量值特征(5000 克)施加很大的权重。
这就是为什么在将数据输入任何机器学习或统计模型之前,我们必须首先将数据标准化,并将所有特征置于相同的数量级。

*https://medium/greyatom/why-how-and-when-to-scale-your-features-4b30ab09db5e

Scikit-learn 在预处理模块下提供了这个方便的类“StandardScaler ”,这使得标准化我们的数据框架变得非常容易。

分层聚类

我们将使用 Scipy 的层次模块中的树状图和链接对数据集进行聚类。
树状图是数据点的树状层次表示。它最常用于表示层次聚类。另一方面,链接是帮助我们实现聚类的功能。这里我们需要指定计算两个集群之间距离的方法,完成后,链接函数返回链接矩阵中的分层集群。

在 x 轴的底部,我们有观察结果。这些是 2000 个人客户数据点。在 y 轴上,我们看到由垂直线表示的点或簇之间的距离。点之间的距离越小,它们在树中的分组位置就越靠下。

手动确定集群的数量

链接对象可以为我们计算出最佳的聚类数,并且树状图可以用不同的颜色表示这些聚类,这很棒。但是,如果我们必须从树状图中识别这些集群呢?该过程是穿过没有被任何延伸的水平线截取的最大的垂直线。切片后,切片线下的聚类数将是最佳聚类数。在下图中,我们看到两条候选人垂直线,在它们之间,“候选人 2”较高。因此,我们将穿过这条线,这将在下面产生 4 个集群。

结论

分层聚类实现起来非常简单,它可以返回数据中的最佳聚类数,但不幸的是,由于它的速度很慢,所以在现实生活中并不使用。相反,我们经常使用 K-means 聚类。在我们的下一篇文章中,我们将看到如何实现 K-means 聚类,我们将尝试用 PCA 来优化它。*

感谢阅读!如果你喜欢这篇文章一定要给 鼓掌(最多 50!) 让我们 连接上LinkedIn 在 Medium 上关注我 保持更新我的新文章**

通过此 推荐链接 加入 Medium,免费支持我。

**https://analyticsoul.medium/membership **

用 Python 进行客户细分(实现 STP 框架——第 2/5 部分)

原文:https://towardsdatascience/customer-segmentation-with-python-implementing-stp-framework-part-2-689b81a7e86d

使用 Python 实现 k-means 聚类的分步指南

艾萨克·史密斯在 Unsplash 上拍摄的照片

在我的上一篇文章中,我们开始了实施流行的 STP 营销框架的旅程,在这篇文章中,我们将通过使用最流行的无监督机器学习算法之一“k-means 聚类”来克服分层分割的局限性,从而继续我们的旅程。

整个笔记本和数据集都可以在 Deepnote 笔记本中获得。

**Table of contents** ∘ [Introduction](#2740)
  ∘ [Implementing K-Means Clustering](#99d8)
  ∘ [Observations](#9494)
  ∘ [Visualize our customer segments](#2617)
  ∘ [Conclusion](#b70d)

介绍

分层聚类对于小型数据集非常有用,但是随着数据集大小的增长,计算需求也会快速增长。因此,层次聚类是不实际的,在行业中,平面聚类比层次聚类更受欢迎。在这篇文章中,我们将使用 k-means 聚类算法来划分我们的数据集。

有许多无监督聚类算法,但 k-means 是最容易理解的。K-means 聚类可以将未标记的数据集分割成预定数量的组。输入参数“k”代表我们希望在给定数据集中形成的聚类或组的数量。然而,我们必须小心选择“k”的值。如果“k”太小,那么质心就不会位于簇内。另一方面,如果我们选择‘k’太大,那么一些集群可能会过度分裂。

我推荐下面这篇文章来学习更多关于 k-means 聚类的知识:

实现 K 均值聚类

k 均值算法的工作方式是:

  1. 我们选择我们想要的集群数量“k”
  2. 随机指定簇质心
  3. 直到聚类停止变化,重复以下步骤:
    i .将每个观察值分配给质心最近的聚类
    ii。通过取每个聚类中点的平均向量来计算新的质心

https://commons . wikimedia . org/wiki/File:K-means _ convergence . gif

k-means 算法的局限性在于,您必须预先决定您期望创建多少个聚类。这需要一些领域知识。如果您没有领域知识,您可以应用“肘法”来确定“k”(聚类数)的值。
这种方法本质上是一种蛮力方法,在这种方法中,对于某个“k”值(例如 2-10),计算每个聚类成员与其质心之间的平方距离之和,并根据平方距离绘制“k”。随着簇数量的增加,平均失真将会减少,这意味着簇质心将会向每个数据点移动。在图中,这将产生一个肘状结构,因此命名为“肘法”。我们将选择距离突然减小的“k”值。

正如我们在上面的图表中看到的,这条线僵硬地下降,直到我们到达第 4 类的数字,然后下降更加平稳。这意味着我们的肘在 4,这是我们的最佳集群数量。这也符合我们在上一篇文章中所做的层次聚类的输出。

现在,让我们对 4 个集群执行 k-means 聚类,并在我们的数据帧中包含段号。

观察

太好了,我们已经把客户分成了 4 组。现在让我们试着去了解每一组的特点。首先,让我们通过聚类来查看每个特征的平均值:

以下是我的一些观察:

作者图片

根据以上观察,我感觉第 1 和第 4 部分比第 2 和第 3 部分富裕,收入更高,职业更好。
最好给每个组分配合适的名称。

1: 'well-off'
2: 'fewer-opportunities'
3: 'standard'
4: 'career-focused'

让我们来看看每个细分市场的客户数量:

可视化我们的客户群

年龄与收入:

教育与收入:

结论

我们在“年龄与收入”散点图中观察到,高收入(富裕)的高年龄客户是分开的,但其他三个细分市场没有那么明显。

在第二个观察‘学历 vs 收入’小提琴情节中,我们看到没有学历的客户收入较低,毕业的客户收入较高。但是其他部分不是那么可分的。

根据这些观察,我们可以得出结论,k-means 在将数据分成簇方面做得不错。然而,结果并不令人满意。在下一篇文章中,我们将把 k-means 和主成分分析结合起来,看看我们如何能取得更好的结果。

和以前一样,全部代码和所有支持数据集都可以在 Deepnote 笔记本中获得。

感谢阅读!如果你喜欢这篇文章一定要 鼓掌(最多 50!) 和咱们 连上*LinkedIn 关注我上媒 保持更新我的新文章*

通过此 推荐链接 加入 Medium,免费支持我。

*https://analyticsoul.medium/membership *

使用 Python 进行客户细分(实现 STP 框架——第 3/5 部分)

原文:https://towardsdatascience/customer-segmentation-with-python-implementing-stp-framework-part-3-e81a79181d07

使用 PCA 实现 k-means 聚类的分步指南

在 Unsplash 上由 Carlos Muza 拍摄的照片

到目前为止,在我们实施 STP 营销框架(细分、目标和定位)的过程中,我们已经完成了以下工作:

在的第一篇文章中,我们已经
定义了我们的目标并学习了基础知识
设置了 Deepnote 开发环境
探索了我们一直在使用的客户数据集的不同属性/特征
处理并标准化了我们的数据集
使用分层聚类对客户进行了细分

然后,在第篇文章中,我们
学习了 k-means 聚类算法(平面聚类)和 elbow 方法,以确定数据集中的最佳聚类数
使用 k-means 聚类对客户数据集进行细分,并命名我们的客户群
分析了细分结果

正如我们在第二篇文章中看到的,我们的聚类方法无法清楚地区分不同的客户群,在这篇文章中,我们将尝试使用降维来改善这一点。

主成分分析

当数据集中的多个要素高度相关时,会因为冗余信息而扭曲模型的结果。这就是我们的 k 均值模型所发生的情况。这就是所谓的多重共线性问题。我们可以通过降维来解决这个问题。

了解有关多重共线性的详细信息。

第一篇文章中的相关矩阵表明年龄学历相关,并且收入职业也相关。我们将使用主成分分析(PCA)这种降维方法来解决这个问题。

降维是减少数据集的属性数量,同时保留原始数据中有意义的属性的过程。正如莎士比亚所说,“有时少即是多”。降维不完全是这样,但很接近:P

了解更多关于降维的知识。

PCA 将一组相关变量§转换成较少数量的不相关变量 k (k < p) called 主分量,同时尽可能保持原始数据集中的变化。这是在标准化数据集的数据预处理步骤中执行的。

了解更多关于五氯苯甲醚的信息。

识别主要成分

首先,让我们从 sklearn 导入 PCA 库,并用标准化的客户数据集创建我们的 pca 对象。

pca 对象的属性“explained_variance_ratio_”包含七个组件,它们解释了我们数据集的 100%可变性。第一个成分解释了约 36%的可变性,第二和第三个成分分别解释了 26%和 19%的可变性。

经验法则是选择保留 70–80%可变性的组件数量。如果我们选择三个顶级组件,它们将拥有超过 80%的可变性,如果我们选择四个组件,它们将保留几乎 90%的可变性。让我们挑选三个组件并适合我们的 pca 模型。
然后,我们使用原始数据集中的列创建一个包含三个主要成分的数据框架。请注意,数据帧中的所有值都在负 1 和 1 之间,因为它们本质上是相关的。

现在,让我们看看新的相关矩阵。

成分一与*年龄、收入、职业、定居规模正相关。*这些特征与一个人的职业有关。

另一方面,性别、婚姻状况、教育是第二个组成部分最显著的决定因素。我们还可以看到,在这个组件中,所有与职业相关的功能都是不相关的。因此,这个组成部分不是指个人的职业,而是指教育和生活方式。

对于第三个组成部分,我们观察到年龄、婚姻状况、职业是最显著的决定因素。婚姻状况职业有负面影响,但仍然很重要。

用 PCA 实现 K-Means 聚类

好了,现在我们对新的变量或组件代表什么有了一个概念。让我们实现 k-means 聚类,将我们的三个组件视为特征。我们将跳过肘方法,因为我们已经在第二篇文章中学习过了。因此,我们将直接开始用四个集群实现 k-means 算法。笔记本包含详细的实现方法供您参考。

分析分割结果

之前我们已经确定成分一代表事业,成分二代表教育&生活方式,成分三代表生活或工作经历。

现在让我们分析分割结果,并尝试像以前一样标记它们。

细分 0: 低职业和经验价值,高教育和生活方式价值。
标签:标准
细分 1: 高职业但低学历、低生活方式、低经历
标签:以职业为中心
细分 2: 低职业、低学历、低生活方式、高经历
标签:机会少
细分 3: 高职业、高学历、高经历

让我们来看看每个细分市场的客户数量:

现在,让我们来看一下与前两个组件相关的部分。

为了比较,这里是没有 PCA 的原始 k-means 实现的散点图(本系列的第 2 篇)。

作者图片

正如您所看到的,现在这四个部分可以清楚地识别。虽然标准和更少机会有一些重叠,但总体结果仍然远远好于先前的结果。

结论

到目前为止,我们已经将我们的客户分成了四个不同的、可明确识别的群体。至此,我们已经完成了 STP 框架的“细分”部分。由于“定位”主要涉及关注哪个客户群的商业决策,我们将在下一篇文章中跳到“定位”。

与往常一样,完整的代码和所有支持数据集都可以在 Deepnote 笔记本中获得。

感谢阅读!如果你喜欢这篇文章,一定要给 鼓掌(最多 50!) 连线*LinkedIn 在 Medium 上关注我 保持更新我的新文章。*

通过此 推荐链接 加入 Medium,免费支持我。

*https://analyticsoul.medium/membership *

使用熊猫交叉表自定义您的数据框

原文:https://towardsdatascience/customize-your-data-frame-with-pandas-crosstab-843b4ca8fb5b

只需用交叉表透视您的表数据

罗伯特·基恩在 Unsplash 上的照片

介绍

交叉表函数是帮助您在 Pandas 中重塑数据的众多方法之一。乍一看,它的用途似乎与 pivot 类似,您可以使用 Pandas Crosstab 执行许多与 Pandas Pivot Table 相同的操作。然而,有一些特殊的区别:

  • 交叉表支持您规范化生成的数据框并返回百分比值。
  • 函数的输入不一定是数据帧。对于它的行和列,它也可以接受类似数组的对象。

本文将为您提供一个获得大多数特性的简明摘要。

资料组

在本文中,我将使用一个名为“Taxis”的公开可用的seaborn数据集,可以通过以下方式轻松获得:

import seaborn as sns df = sns.load_dataset('taxis')
df.head()

以下是数据集的一瞥:

图 1:出租车数据集——作者提供的图片

图 2:数据信息—作者图片

现在,让我们开始看看我们有什么。

句法

我们可以把一些基本参数的细节解释如下。更多详细信息见 本文档 :

  • index: V 行中作为分组依据的值。
  • **列:**列中作为分组依据的值。
  • **值:**要聚合的数据
  • aggfunc: 要使用的聚合函数
  • **边距:**获取行或列的小计
  • **规格化:**将所有值除以值的总和

下面的例子将帮助你更好地理解这个概念。

分组

可以看出,该数据集包含关于取货地点和付款方式的信息。我想看看每种支付方式在每个地方使用了多少次。使用 Crosstab 可以简单地构建一个频率矩阵表,以不同地方每种支付方式的总计数作为值。

我将 取货地点(区) 传递给index参数,并将 付款方式 设置为columns.

结果显示为多索引交叉表:

图 3:不同地点付款方式的频率表—按作者分类的图片

使用 aggfunc

如果我想计算取货地点在皇后区和布朗克斯区的现金用户的平均距离,那么对备选聚合函数应用aggfunc。在这里,aggfunc = "mean”将被使用。

图 4:平均距离

因此,很容易观察到,顾客使用现金从皇后区旅行的平均距离约为 5.3 英里,这是从布朗克斯区(2.1 英里)开始的距离的两倍多。

小计

交叉表的一个便利特性是为行和列添加小计。例如,我很好奇不同取货地点的人用现金和信用卡支付的总费用。marginsmargins_name在这种情况下应用如下代码。

图 5:不同行政区每个类别的总和——按作者分类的图片

规范化交叉表

以图 5 中的结果为例。对于每一行和每一列,我想找出每个值相对于小计的百分比。要查找行百分比值,normalize = "index"。同时,normalize = "columns"将帮助您找到基于列总数的百分比值。设置normalize = "all"意味着值以整个数据框的百分比计算。

下图 6 描述了不同地点每种支付方式的百分比。

图 6:按列总数的标准化—按作者的图像

为什么交叉表

其实有些任务也可以用 unstackpivot_ table 来解决。然而,有时候,我发现交叉表是最容易使用的方法,因为它的语法容易记忆,转换也很快,而其他两种方法可能需要一些步骤才能得到最终答案。

也就是说,根据不同的情况,使用让你最舒服的工作方法。

参考

数据集

Waskom,m .等人,2017 年。mwaskom/seaborn:v 0 . 8 . 1(2017 年 9 月),芝诺多。可在:https://doi/10.5281/zenodo.883859.以 BSD-3 许可证发布。

其他人

https://datagy.io/pandas-crosstab/

本文标签: 中文翻译一百一十博客TowardsDataScience