对象检测:在 Karens 攻击 Keras 和 OpenCV 之前阻止他们
关于如何构建自己的面部检测系统的演练,该系统可以确定某人是否戴着面具,或者他们是快乐、中立还是愤怒。
模型演示(作者照片)
在新冠肺炎时代,我们已经看到戴口罩可以大大减少病毒的传播。然而,正如我们有时在网上看到的,有很多人强烈反对这种说法。网上的视频显示,当人们被要求遵守这个协议时,他们变得非常不满。在这个项目中,我将向您展示如何创建一个神经网络,它可以检测某人是否戴着面具,如果没有,我们将检测他们脸上的面部表情。随着规模的扩大,这可以应用于当地企业,以便经理们可以检测到某人是否戴着面具,以及他们是否确实是一个准备罢工的卡伦。
照片来自 Giphy
模型演示(作者照片)
** 本教程的代码可以在我的GitHub** **上找到
要求
- 克拉斯
- OpenCV
- NumPy
- Matplotlib
- tqdm
- Sklearn
数据
掩模检测
这个项目的第一步是建立一个神经网络,可以检测一个人是否戴着面具。对于这一部分,我们将使用 Mobilenet。
在构建模型之前,我们需要提取每张图像并对其进行预处理,以便可以将它输入 Mobilenet。在上面的数据集中,我们看到在最外层有三个目录:(1)训练,(2)测试,和(3)验证。下面是如何提取每个图像,为 mobilenet 预处理它们,并将它们保存到 NumPy 数组的代码。
运行上面的单元格后,您应该会看到这样的窗口。
列车测试拆分(作者供图)
在后面的小节中,我们将重用函数 get_image_value,这就是为什么我们要传递一个参数,说明应该为哪个模型类型检索图像。
现在我们有了图像值,是时候建立神经网络来检测遮罩了。确保在当前目录中有一个名为 ModelWeights 的文件夹。在这个文件夹中,我们将保存我们训练的每个模型的权重。
上面,我们指定模型应该训练 2000 个纪元。因为模型可能不需要 2000 个历元,我们包括了一个早期停止参数,以防止模型在连续 5 次迭代后过度拟合,而损失没有变化。运行上面的单元格后,您应该会看到如下窗口:
训练完成后,您应该会在 ModelWeights 文件夹中看到一个名为 Mobilenet_Masks.h5 的. h5 文件。在该文件中,存储了模型的权重,稍后当我们将该神经网络应用于实时视频时,将会使用该文件。
上面,我们看到了 mobilenet 培训的 ROC 得分、混淆矩阵和损失/准确性。考虑到我们使用的数据量,这些指标已经很不错了。对于验证集,只有 13 幅图像被错误分类。至于损耗和准确度,损耗能够低于 0.1,准确度远高于 94%。最后,ROC 分数显示了巨大的成功,因为每个班级都有 1.0 的满分,而每个班级的 f 1 分数都大于 0.98。
情感检测
与上一个模型一样,我们必须首先提取图像值,并将它们放入一个 NumPy 数组中。正如我前面提到的,我们将在一个新的函数中重用 get_image_value 函数,该函数被设计为只提取情感图像。该数据集包含 7 个类别:愤怒、快乐、中性、悲伤、厌恶、恐惧和惊讶。对于这个项目,我们将只关注前三类:愤怒、快乐和中立。此外,我们将用于训练的模型将采用大小为(48,48,3)的输入图像,这比(224,224,3)的 mobilenet 维度小得多。
情绪训练测试分裂(作者照片)
正如你在上面看到的,我们限制每个类最多只能包含 4000 张图片。这样做是为了让训练更快,这样我们就可以在平衡班级的情况下正确地跟踪学生的表现。
运行上面的代码后,您应该会看到一个与之前类似的窗口,如下所示:
情绪训练测试分裂(作者照片)
现在我们有了训练测试分离阵列,我们将建立神经网络来检测一个人脸上的情绪。下面是如何建立这个神经网络的代码。与 mobilenet 不同,我们不会对数据集应用增强。
情绪训练(作者供图)
运行上面的代码后,您应该会看到一个类似这样的窗口:
情绪训练(作者供图)
一旦训练完成,您应该找到一个名为 Normal_Emotions.h5 的. h5 文件,它将包含模型的权重。与我们之前训练的模型一样,这将用于下面的实时视频部分。
上面,我们看到了情绪模型的 ROC 分数、混淆矩阵和损失/准确性。考虑到我们使用的数据量,这些指标相当不错,但不如 mobilenet。对于训练集,在大约 12,000 幅图像中,只有 1,786 幅图像被错误分类。至于损耗和准确度,损耗能够低于 0.7,准确度保持在 70-75%之间。最后,ROC 分数显示了相当好的成功,因为每个类都保持了大于 0.9 的分数,而每个类的 F1 分数都在 0.7 到 0.9 之间。
部署
现在我们已经训练好了每个模型,我们将使用 OpenCV 将它们应用于现场视频。对于这一部分,您必须在本地机器上安装网络摄像头。
在我们进入代码之前,让我先解释一下这是如何工作的。
- 使用哈尔级联分类器,我们将在一个视频帧中找到人脸的坐标(下载链接
- 在找到人脸的坐标后,我们将提取该部分,也称为我们的感兴趣区域(ROI)。
- 因为我们想弄清楚是否存在一个遮罩,所以我们将首先将 ROI 重新整形为(224,224),以便可以将其输入到我们的 mobilenet 模型中。
- 如果 mobilenet 模型预测有一个掩码,那么它将继续而不使用情感模型。
- 但是,如果 mobilenet 模型预测没有遮罩,那么它将再次使用 ROI 并将其输入到情感模型中。
下面是它如何工作的代码。乍一看,for 循环中似乎有很多条件,但只要慢慢读,就会明白。
运行上面的代码后,会弹出一个如下所示的窗口。要停止此过程,请按键盘上的“q”按钮。
模型演示(作者照片)
限制
- 据我所知,如果画面中的人戴着眼镜,这个模型很难区分面具是否存在。
- 更多包含戴口罩戴眼镜的人的数据将改善这种局限性
未来方向
- 我打算找一台能探测热量的相机(如果我能找到一台价格实惠的话)
- 使用摄像头来检测热量以及这些其他功能有利于防止人们在发现发烧时进入商店。
此外,我目前正在寻找一份数据科学方面的工作,所以如果你在这方面有任何建议,请通过LinkedIn联系我!
本教程的代码可以在我的GitHub* **上找到
自定义熊猫统计功能
生成统计数据的三个有用的 Pandas 函数
Pandas 是一个有用的 python 库,可用于各种数据任务,包括统计分析、数据插补、数据争论等等。在本帖中,我们将介绍三个有用的自定义函数,它们允许我们从数据中生成统计数据。
我们开始吧!
出于我们的目的,我们将使用葡萄酒评论数据集,可以在这里找到。
首先,让我们导入 pandas 和 numpy 包:
import pandas as pd
import numpy as np
接下来,让我们将显示列和行的最大数量设置为“None”:
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)
现在,让我们读入数据:
df = pd.read_csv('winemag-data-130k-v2.csv')
接下来,我们将打印前五行数据,以了解列类型及其值(列结果被截断):
print(df.head())
我们可以考虑一个有趣的函数,它为分类列中的给定类别生成数值列的平均值。该函数将接受一个分类列名、该分类列的分类值和一个数字列名:
def get_category_mean(categorical_column, categorical_value, numerical_column):
在函数中,我们需要过滤该类别值的数据帧,并取该类别数值的平均值:
def get_category_mean(categorical_column, categorical_value, numerical_column):
df_mean = df[df[categorical_column] == categorical_value]
mean_value = df_mean[numerical_column].mean()
最后,我们可以使用 python“f 字符串”来打印结果:
def get_category_mean(categorical_column, categorical_value, numerical_column):
df_mean = df[df[categorical_column] == categorical_value]
mean_value = df_mean[numerical_column].mean()
print(f"Mean {categorical_column} for {numerical_column}", np.round(mean_value, 2))
现在,让我们调用包含分类列“国家”、国家值“意大利”和数字列“价格”的函数:
get_category_mean('country', 'Italy', 'price')
我们也可以调用类别为“品种”、品种值为“黑皮诺”、数值列为“价格”的函数:
get_category_mean('variety', 'Pinot Noir', 'price')
我们可以考虑的另一个函数是为分类列中的每个分类值生成数字列的平均值。我们可以使用熊猫的“分组”方法来实现这一点:
def groupby_category_mean(categorical_column, numerical_column):
df_groupby = df.groupby(categorical_column)[numerical_column].mean()
df_groupby = np.round(df_groupby,2)
print(df_groupby)
如果我们用“国家”和“价格”调用我们的函数,我们将打印每个国家的平均价格(为了清楚起见,结果被截断):
groupby_category_mean('country', 'price')
为了进行健全性检查,我们看到我们得到了与上一个函数中相同的意大利平均价格。我们还可以将该函数应用于葡萄酒品种和价格(为了清楚起见,结果被截断):
groupby_category_mean('variety', 'price')
我们将讨论的最后一个函数为另一个分类列的每个值生成分类列的模式。我们也可以使用熊猫的“分组”方法来实现这一点:
def groupby_category_mode(categorical_column1, categorical_column2):
df_groupby = df.groupby(categorical_column1)[categorical_column2].agg(pd.Series.mode)
print(df_groupby)
让我们找到每个国家的葡萄酒品种。这将告诉我们每个国家最常出现的葡萄酒品种:
groupby_category_mode('country', 'variety')
我将在这里停下来,但是您可以自己随意摆弄代码和数据。
结论
总之,在这篇文章中,我们讨论了如何使用 Pandas 定义三个自定义函数来从数据中生成统计洞察。首先,我们展示了如何定义一个函数,在给定分类列和类别值的情况下,计算数字列的平均值。然后,我们展示了如何使用“groupby”方法为分类列中的每个类别生成数值列的平均值。最后,我们展示了如何为一个分类列的每个值生成另一个分类列的模式。如果你有兴趣学习更多关于熊猫的数据操作、机器学习甚至只是 python 编程的一些基础知识,请查看Python for Data Science and Machine Learning:Python 编程、熊猫和 Scikit-初学者学习教程 。我希望你觉得这篇文章有用/有趣。这篇文章中的代码可以在 GitHub 上找到。感谢您的阅读!
定制 PySpark 累加器
约书亚·索蒂诺在 Unsplash 上拍摄的照片
pyspark 蓄能器的字典、列表和设置类型
默认情况下,Spark 提供支持交换和关联操作的 int/float 累加器。尽管 spark 也提供了一个类 AccumulatorParam 来继承,以支持不同类型的累加器。只需要实现两个方法 zero 和 addInPlace。zero 定义累加器类型的零值,addInPlace 定义累加器类型的两个值如何相加。
在上一篇文章中,我讨论了管理 pyspark 累加器,这也提供了一个很好的累加器概述:
以及如何管理它们
towardsdatascience.com](/broadcasting-pyspark-accumulators-343104c18c44)
在这篇文章中,我将讨论三种不同类型的自定义累加器:dict、list 和 set。
口述积累器
字典累加器的目标是累加字典。现在,当我们积累字典时,有多种考虑因素:
- 现有键:如果键已经存在于累积字典中,我们该怎么办?
- Key 的值类型:如何处理 list 和 set 等键的不同类型的值?
现有密钥
我们至少有三种策略:
- 用新值替换该项的值
- 保持旧值
- 添加到现有值
注意,前两个操作是不可交换的,因为它取决于要累加的字典的顺序。如果我们想为一个给定的键保留所有的或者唯一的值,那么我们可以分别用一个列表或者集合来表示值。
键的值类型
如上所述,值的类型对于跟踪给定键的所有或所有唯一值可能是有用的。在这种情况下,我们需要一个列表或集合。可能有其他场景需要其他类型,但这超出了本文的范围。
所以现在我们已经讨论了五种不同风格的录音机。我们没有为每个实现一个类,而是实现了枚举来跟踪组合方法。
白藓积累法
- REPLACE:如果键存在,则用新值替换当前值,否则将键值添加到字典中
- KEEP:如果键存在,则保留旧值,否则将键值添加到字典中
- ADD:如果键存在,则向现有值添加新值,否则向字典添加键值
- LIST:如果键存在,将新列表添加到现有列表中,否则将键和列表作为值添加到字典中
- SET:如果关键字存在,则将新集合(从新列表转换)与现有集合值(从现有集合转换为值)联合,否则将关键字和列表值添加到字典中
init 拯救世界
现在我们知道了不同风格的 DictAccumulator,那么如何使用一个单独的类实现来创建呢?很少宣传的事实是用于 *AccumulatorParam,*的 init 方法,但是它不需要被指定,首先,因为它是 python 中的类的构造函数。
用 DictAccumulatorMethod 初始化 DictAccumulator 解决了给它调味的问题。除非指定,否则将使用 REPLACE。在 addInPlace 方法中,我们只是用需要合并到 first ( v1 )中的 seconds ( v2 )字典中的键和值来调用方法。
单元测试指令累积器
首先只是设置单元测试。将累加器管理器设置为广播累加器,通过测试设置中的 get_spark 方法和 input_data 、 rdd 产生火花。
更换
在这个单元测试中,我们首先为作为字典的键添加双倍的键值,然后添加相同的键值。相同的密钥值会替换双倍的密钥值。
保持
首先在下面的测试中,我们将字典添加到累加器中,其中值是键的两倍。然后将字典添加到值与键相同的累加器中。在第二遍之后,键值应该是键值的两倍,因为我们将累加器初始化为 keep。
增加
在下面的测试中,我们只是将所有的值添加到定义为“ count ”的键中。count键的值与 input_data 的 sum 相同。
列表
在此测试中,我们将所有奇数和偶数数据组合到其各自的密钥中,该密钥是从数字除以 2 的余数中导出的。每个值都被附加到相应键的列表中。
设置
在下面的测试中,残差被添加到键中。因为它的设置只有值 0 和 1 在列表中(键值)。如果是列表,我们会看到多个 0 和 1。
列表累加器
ListAccumulator 的实现相当简单。在这个场景中,我们只添加两个列表,并在 addInPlace 方法中返回它。
单元测试列表累加器
在单元测试中,我只是在加倍后将一个数字列表添加到 ListAccumulator 中。我在驱动程序上创建了相同的数据并进行比较,结果是匹配的。
设定累加器
在 SetAccumulator 实现中,它只是两个集合的并集,作为 addInPlace 方法的返回。
单元测试设置累加器
测试方法与测试过的带 set 类型的录音机相似。多次添加剩余的列表,最后,我们只得到两个剩余的 0 和 1,而不是 0 和 1。
结论
在这篇文章中,讨论了基于列表和集合的 pyspark 累加器,并围绕实现进行了推理。替换和保留字典的累加器是不可交换的,所以使用它们时要小心。下面是 GitHub 上的实现。
[## salil Jain/PySparkCustomAccumulators
在 GitHub 上创建一个帐户,为 salil Jain/PySparkCustomAccumulators 的开发做出贡献。
github.com](https://github.com/SalilJain/PySparkCustomAccumulators)
scikit-learn 中带有最近邻回归的自定义用户定义指标
自定义度量教程:预测日平均温度
阿道夫·费利克斯在 Unsplash 上拍摄的照片
在过去的几年里,公众可用的数据科学工具的数量急剧增加。一个这样的工具是 Python 库 scikit-learn (通常被称为 sklearn)。对于 scikit-learn 最近的介绍性概述,您可以看看最近由 Sadrach Pierre 发表的帖子。
在这篇文章中,我将讨论 k 近邻回归。具体来说,我们将了解如何使用用户定义的指标。这个领域受到的关注少得惊人。我将给出一个简单的例子,在这个例子中,实现我们自己的距离度量是有益的。我们将详细了解如何实现这一点,并评论这种方法的优点。
教程概述
任何具有 Python 基础知识的人都可以阅读本教程,完整的代码作为参考。教程中使用的所有数据、代码和图像都在这个 Github 资源库中提供。本教程分为不同的部分:
- kNN 回归简介
- 自定义指标的应用示例
- 创建数据集
- 实施自定义指标
- 交叉验证以优化 k
- 不同指标的准确度比较
kNN 回归简介
当试图预测新点的目标属性( y )时,kNN 对近邻的目标属性值进行加权平均,以计算新点的 y 值。通常,可以通过考虑最近的近邻具有更显著的影响来提高预测精度。这是通过用与其距离成反比的权重对所有 k 个最近的邻居进行加权来实现的。在 scikit-learn 中,我们只需在 kNN 回归器中选择选项**weights =‘distance’**就可以做到这一点。这意味着更近的点(更小的距离)在预测中将具有更大的权重。形式上,目标资产在新点 n 处的价值,与 k 最近的邻居,计算如下:
关于 kNN 回归的更详细的解释,以及权重函数的影响,请随意参考我最近关于这些主题的博客文章。
自定义指标的应用示例
作为对定义自定义指标感兴趣的一个例子,我们使用一个简单的数据集,该数据集包含加拿大气象站 2019 年每天的平均温度。我们的数据集由一个描述符(一年中的某一天)和一个目标属性(气象站的日平均温度)组成。
在 2019 年,地球与太阳的最远距离(远日点)为 1.521×10⁸公里,最近距离(近日点)为 1.471×10⁸公里。这两个值非常相似(相差~3%),所以为了简单起见,我们在这里假设地球的轨道是圆形的。这种近似可以在下图中可视化,其中围绕太阳的实际轨道以橙色显示,圆形近似轨道以白色显示。
图片由 Sch 在维基共享下 CC BY-SA 3.0
在一年中,地球绕太阳转一圈。因此,一年中两个日期之间的距离可以计算为地球在此期间沿其轨道运行的距离。沿着轨道的两点之间的距离,与我们最初可能天真地计算出的直接欧几里得距离不同。
在下图中,我们显示了两点 B 和 C 相对于初始点 A 的相对距离。例如,当我们直接使用欧几里德度量(红色)计算 A-B 和 A-C 距离的比率时,我们得到大约 0.7,这意味着 A-B 距离大约是 A-C 距离的 70%。但是,如果我们考虑沿圆周(绿色)计算的距离,我们得到 A-B 距离是 A-C 距离的 50%。我们可以很容易地观察到最后一个度量是如何正确描述我们的问题的:1 月和 4 月之间的距离是的 50%
作者图片
创建数据集
我们从美国国家海洋和大气管理局 climate.gov 站获取温度数据。我们重点关注加拿大马尼托巴省天鹅河站(CA00504K80K 站)2019 年的数据。
原始数据由日期 (YYYYMMDD)和 T (十分之一摄氏度)组成。我们将数据转换为天(从 1 到 365)和 T (摄氏度)。我们提供了这四列的数据,虽然代码中只使用了最后两列,2019 年的 364 天(缺少 7 月 30 日的数据)。下面我们展示这些数据的样子,完整的数据集在这个 Github 仓库中提供。
实施自定义指标
在 scikit-learn 中,k-NN 回归默认使用欧几里德距离,尽管还有更多的距离度量可用,例如曼哈顿和切比雪夫。此外,我们可以使用关键字 metric 来使用用户定义的函数,该函数读取两个数组 X1 和 X2 ,其中包含我们想要计算其距离的两个点的坐标。
下面,我们展示了一个带有函数 mimic_minkowski 的简单代码,因此使用 metric=mimic_minkowski 会产生与使用默认 minkowski(欧几里得)度量和**metric =‘Minkowski’**完全相同的结果。
因为我们想研究圆周上各点的距离,所以使用极坐标非常方便。我们在下面展示了由此产生的距离方程。
请注意,我们的函数将用于计算距离之间的相对差异,因此我们可以将上述等式简化为:
在我们的数据集中,我们有每天的日期,所以我们可以很容易地将时差转换成角度差(θ₁-θ₂).假设地球的轨道是圆形的,并且以恒定的速度运动,我们可以将一年中 Day₁和 Day₂两天之间的差异转换为角度差异,如下所示。
最后,我们可以创建我们的 custom_metric 函数,如下所示。
交叉验证以优化 k
选择 k 个最近邻的最佳数量以进行精确预测的简单方法是使用网格搜索。网格搜索包括尝试不同的 k 值,最后选择一个最小化预测误差的值。
与任何机器学习模型一样,人们面临着选择将用于训练模型的数据和将用于测试模型准确性的数据的任务。
由于我们有相对少量的低维数据,我们将使用留一交叉验证(对于更复杂的数据集,可以选择 k 倍交叉验证)。为了对具有 N 个样本的数据集使用留一交叉验证,人们用 N-1 个点训练机器学习模型,并使用剩余的点作为测试。这被重复 N 次,所以每个点正好被用作一次测试。这种交叉验证的示意图如下所示。
作者图片
结果,每个点都有一个预测值。然后,将每个点的预测值与其实际值进行比较。作为误差指标,我们使用了均方根误差(rmse)。最后,我们可以比较针对不同的 k 值获得的(rmse ),并选择最小化 rmse 的值作为最佳的 k 值。包括这个网格搜索在内的完整代码如下所示。
使用该代码,我们得到最小 rmse 是在 k=2 时获得的,结果是 rmse=2.725 C 。这意味着我们可以估计 2019 年任何一天的日平均温度,只需知道今年剩余时间的日平均温度,平均误差为 2.725 摄氏度。
不同指标的准确度比较
我们可以合理地问,使用这一新指标的好处是什么。如果我们在上面代码的第 39 行更改为 metric= ‘minkowski’ ,我们得到最佳邻居的数量为 k=2,平均误差为 rmse=2.740 C 。因为我们使用一个非常简单的例子,只有一个描述符,使用一个更合理的度量标准的改进很小:大约 0.5%。
然而,人们应该始终确保使用合理的度量标准。此外,这 0.5%的误差可能对你的问题至关重要。此外,随着数据集复杂性的增加,采用适当指标的改进可能会增加。
结论
我们已经介绍了 k-最近邻回归的基础知识以及如何将其应用于简单的数据集。我们还看到了如何实现用户定义的定制度量函数,以及如何使用正确的度量来提高您的预测准确性。
请记住,这里展示的数据集、代码、图像等。在这个 Github 库中提供。
这个教程对你有帮助吗?让我知道你是否能够成功实现你自己的距离度量!
客户行为建模:聚合统计的问题
使用综合统计数据来判断客户行为模型的强度不是一个好主意
作为一名数据科学家,我花了相当多的时间思考客户终身价值(CLV)以及如何对其建模。一个强大的 CLV 模型实际上是一个强大的客户行为模型——你越能预测下一步行动,你就能越好地量化 CLV。
在这篇文章中,我希望通过一个玩具和现实世界的例子来证明,为什么使用综合统计来判断客户行为模型的强度是一个坏主意。
相反,最好的 CLV 模型是在个体层面上有最强预测的模型。探索客户生命周期价值的数据科学家应该主要,也可能是唯一,使用个人层面的指标来充分理解 CLV 模型的优势和劣势。
CLV 模型本质上是猜测一个人在你店里购物的频率以及他们会花多少钱。rupixen.com 在 Unsplash 上拍照
第 1 章什么是 CLV,为什么它很重要?
虽然这是为数据科学家准备的,但我想说明本文的业务分支,因为理解业务需求将告知我为什么持有某些观点,以及为什么对我们所有人来说掌握好的 CLV 模型的额外好处是重要的。
CLV:顾客将来会花多少钱
CLV 是一个商业关键绩效指标,在过去的几年里,它的受欢迎程度激增。原因是显而易见的:如果你的公司能够准确预测客户在未来几个月或几年的花费,你就可以根据预算调整他们的体验。从市场营销到客户服务,再到整体商业战略,这都有着巨大的应用空间。
以下是“精确 CLV”可以帮助实现的业务应用的快速列表:
- 营销受众生成
- 断代分析
- 客户服务票订购
- 营销提升分析
- CAC 投标封顶营销
- 折扣活动
- VIP 购买体验
- 忠诚度计划
- 分割
- 董事会报告
还有很多,这些只是我想到最快的。
伟大的数字营销源于对客户的深刻理解。活动创建者在 Unsplash 上的照片
有如此多的商业计划处于危险之中,精通技术的公司正忙于寻找哪种模式最能抓住他们客户群的 CLV。最流行和最常用的客户终身价值模型使用总收入百分比误差(ARPE)等统计数据,根据总指标来衡量它们的实力。我之所以知道这一点,是因为我的许多客户已经用汇总统计数据将他们的内部 CLV 模型与我的模型进行了比较。
我认为这是一个严重的错误。
下面的两个例子,一个是玩具,一个是真实的,将有望展示总体统计数据是如何把我们引入歧途,并隐藏在个体水平上非常明显的模型缺陷的。这是特别有先见之明的,因为大多数业务用例在个体层面上需要一个强大的 CLV 模型,而不仅仅是在集合层面上。
第二章一个玩具例子
当你依赖于总体指标而忽略个体层面的不准确性时,你就错过了很大一部分技术叙述。考虑以下 4 个客户及其 1 年 CLV 的示例:
这个示例包括高、低、中 CLV 客户,以及一个不满意的客户,为智能模型创建了一个很好的分布来捕获。
现在,考虑以下验证指标:
-
MAE :平均绝对误差(预测之间的平均差异)
-
ARPE :总收入百分比误差(总收入和预测收入之间的总体差异)
MAE 是在客户层面上,而 ARPE 又是一个聚合统计。这些验证指标的值越低越好。
这个例子将展示一个集合统计如何掩盖低质量模型的缺点。
要做到这一点,可以将一个虚拟的平均值猜测与 CLV 模型进行比较,两者相差 20%。
模型 1:假人
虚拟模型只会猜测每个顾客 40 美元。
车型二:CLV 车型
该模型试图在客户层面做出准确的模型预测。
我们可以使用这些数字来计算三个验证指标。
这个例子说明了一个总体上相当差的模型(CLV 模型差 20%以上)在个体层面上实际上是更好的。
为了让这个例子更好,让我们给预测添加一些噪声。
# Dummy Sampling:
# randomly sampling a normal dist around $40 with a SD of $5np.random.normal (40,5,4)OUT: (44.88, 40.63, 40.35, 42.16)#CLV Sampling:
# randomly sampling a normal dist around answer with a SD of $15
max (0, np.random.normal (0,15)),
max (0, np.random.normal (10, 15)),
max (0, np.random.normal (50,15)),
max (0, np.random.normal (100, 15))OUT: (0, 17.48, 37.45, 81.41)
上面的结果表明,即使一个人的统计数据比你希望的百分比高,这些 CLV 数的分布也更符合我们所寻找的:一个区分高 CLV 客户和低 CLV 客户的模型。如果你只看 CLV 模型的综合指标,你就错过了故事的主要部分,你可能最终为你的企业选择了错误的模型。
但是,即使汇总一个在个人层面上计算的误差指标,比如 MAE 或 MAPE 等替代指标,也可能隐藏关于你的模型的优点和缺点的关键信息。主要是,其创建 CLV 分数的精确分布的能力。
为了进一步探讨这个问题,让我们来看一个更现实的例子
第三章一个真实的例子
恭喜你!你,读者,被我刚刚虚构的电子商务公司 BottleRocket Brewing Co 聘为数据科学家。(我们将使用的数据基于我为这篇文章挑选的一家真实的电子商务公司)
有趣(虚假)的事实:BottleRocket Brewing 在加州是一个相当受欢迎的品牌。照片由海伦娜·洛佩斯在 Unsplash 拍摄
作为数据科学家,您的首要任务是:为 BottleRocket 的业务选择最佳的 CLV 模型…
……但是“最好”是什么意思呢?
但你没有被吓住,你用以下模型做了一个实验:
- 帕累托/NBD 模型(PNBD)
帕累托/NBD 模型是一个非常受欢迎的选择,也是当今大多数数据驱动的 CLV 预测的模型。引用文档:
1987 年推出的帕累托/NBD 模型将活跃客户交易的[负二项分布]与异质退出过程结合在一起,至今仍被认为是购买直到死亡模型的黄金标准 【链接】
但另一方面,该模型学习两种分布,一种是流失概率,另一种是交易时间间隔(ITT),并通过从这些分布中取样来进行 CLV 预测。
**用更多的技术细节描述 BTYD 模型有点超出了本文的范围,本文主要讨论错误度量。如果你对关于 BTYD 模型的更深入的文章感兴趣,请发表评论,我很乐意写一篇后续文章!更新:查看我关于 BTYD 车型的文章 **
2。梯度增压机(GBM)
梯度增强机器模型是一种流行的机器学习模型,其中弱树被训练和组装在一起以形成强的整体分类器。
**与 BTYD 模型一样,我不会详细介绍 GBM 是如何工作的,但是如果您想让我写一些关于方法/模型的东西,请在下面再次发表评论
3。虚拟 CLV
这个模型被简单地定义为:
Calculate the average ITT for the business
Calculate the average spend over 1yrIf someone has not bought within 2x the average purchase time:
Predict $0
Else:
Predict the average 1y spend
4。非常哑的假人模型(Avg 假人)
该模型仅猜测所有客户超过 1 年的平均支出。包括作为模型性能的基线
第 3.1 章汇总指标
我们可以将所有这些模型的预测整合到一个漂亮的小熊猫数据框架“combined_result_pdf”中,如下所示:
combined_result_pdf.head()
给定这个客户表,我们可以使用下面的代码计算误差指标:
from sklearn.metrics import mean_absolute_erroractual = combined_result_pdf['actual']
rev_actual = sum(actual)for col in combined_result_pdf.columns:
if col in ['customer_id', 'actual']:
continue
pred = combined_result_pdf[col]
mae = mean_absolute_error(y_true=actual, y_pred=pred)
print(col + ": ${:,.2f}".format(mae))
rev_pred = sum(pred)
perc = 100*rev_pred/rev_actual
print(col + ": ${:,} total spend ({:.2f}%)".format(pred, perc))
通过这四个模型,我们尝试预测瓶装旧酒客户的 1 年 CLV,按 MAE 评分排序:
这张表格中有一些有趣的见解:
- GBM 似乎是 CLV 的最佳模式
- PNBD,尽管是一个受欢迎的 CLV,似乎是最糟糕的。事实上,它比一个简单的 if/else 规则列表更糟糕,只比一个只猜测平均值的模型稍微好一点!
- 尽管 GBM 是最好的,但它只比虚拟的 if/else 规则列表模型好几个美元
如果数据科学家/客户接受的话,第三点特别有意思。如果这些结果的解释者真的认为一个简单的 if/else 模型可以捕捉 GBM 可以捕捉的几乎所有复杂性,并且比常用的 PNBD 模型更好,那么一旦成本、训练速度和可解释性都被考虑在内,显然“最佳”模型将是虚拟 CLV。
这让我们回到了最初的主张——**,即总体误差指标,即使是在个体层面上计算的,也会掩盖模型的一些缺点。**为了证明这一点,让我们将数据框架重新加工成混淆矩阵。
统计数据有时非常令人困惑。对于混淆矩阵,它们是字面上的混淆。照片由内森·杜姆劳在 Unsplash 上拍摄
迷你章:什么是混淆矩阵?
单从它的名字来看,理解一个混乱矩阵听起来令人困惑&具有挑战性。但是理解这篇文章中的观点是至关重要的,也是添加到您的数据科学工具包中的一个强大工具。
一个混淆矩阵是一个表格,它概括了分类的准确性,以及模型常见的错误分类。一个简单的混淆矩阵可能如下所示:
上面混淆矩阵的对角线,突出显示的绿色,反映了正确的预测——当它实际上是一只猫的时候预测它是一只猫,等等。这些行的总和将达到 100%,这使我们能够很好地了解我们的模型捕捉回忆行为的情况,或者我们的模型在给定特定标签的情况下正确猜测的概率。
从上面的混淆矩阵中我们还可以看出…
- 在给定真实标签为猫的情况下,该模型在预测猫方面表现出色(猫召回率为 90%)
- 这个模型很难区分狗和猫,经常把狗误认为猫。这是模型最常犯的错误。
- 虽然有时会把猫误认为狗,但这远没有其他错误常见
记住这一点,让我们探索我们的 CLV 模型如何使用混淆矩阵捕捉客户行为。一个强大的模型应该能够正确地将低价值客户和高价值客户进行分类。我更喜欢这种可视化的方法,而不是像 CLV 分数直方图那样的东西,因为它揭示了建模分布的哪些元素是强的和弱的。
为此,我们将把货币价值预测转换为低、中、高和最佳的量化 CLV 预测。这些将从每个模型预测生成的分位数中提取。
最佳模型将正确地将客户分为低/中/高/最佳这四类。因此每个模型我们将制作一个如下结构的混淆矩阵:
并且最佳模型将具有落在该对角线内的最多数量的预测。
Ch。3.2 各项指标
这些混淆矩阵可以用下面的代码片段从我们的 Pandas DF 中生成:
from sklearn.metrics import confusion_matrix
import matplotlib.patches as patches
import matplotlib.colors as colors# Helper function to get quantiles
def get_quant_list(vals, quants):
actual_quants = []
for val in vals:
if val > quants[2]:
actual_quants.append(4)
elif val > quants[1]:
actual_quants.append(3)
elif val > quants[0]:
actual_quants.append(2)
else:
actual_quants.append(1)
return(actual_quants)# Create Plot
fig, axes = plt.subplots(nrows=int(num_plots/2)+(num_plots%2),ncols=2, figsize=(10,5*(num_plots/2)+1))fig.tight_layout(pad=6.0)
tick_marks = np.arange(len(class_names))
plt.setp(axes, xticks=tick_marks, xticklabels=class_names, yticks=tick_marks, yticklabels=class_names)# Pick colors
cmap = plt.get_cmap('Greens')# Generate Quant Labels
plt_num = 0
for col in combined_result_pdf.columns:
if col in ['customer_id', 'actual']:
continue
quants = combined_result_pdf[col]quantile(q=[0.25,0.5,0.75])
pred = combined_result_pdf[col]
pred_quants = get_quant_list(pred,quants)
# Generate Conf Matrix
cm = confusion_matrix(y_true=actual_quants, y_pred=pred_quants)
ax = axes.flatten()[plt_num]
accuracy = np.trace(cm) / float(np.sum(cm))
misclass = 1 - accuracy
# Clean up CM code
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis] *100
ax.imshow(cm, interpolation='nearest', cmap=cmap)
ax.set_title('{} Bucketting'.format(col))
thresh = cm.max() / 1.5
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])): ax.text(j, i, "{:.0f}%".format(cm[i, j]), horizontalalignment="center", color="white" if cm[i, j] > thresh else "black")
# Clean Up Chart
ax.set_ylabel('True label')
ax.set_xlabel('Predicted label')
ax.set_title('{}\naccuracy={:.0f}%'.format(col,100*accuracy))
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.spines['left'].set_visible(False)
for i in [-0.5,0.5,1.5,2.5]:
ax.add_patch(patches.Rectangle((i,i),
1,1,linewidth=2,edgecolor='k',facecolor='none')) plt_num += 1
这会生成以下图表:
图表的颜色表示某个预测/实际分类的集中程度——绿色越深,落入该方块的示例越多。
与上面讨论的混淆矩阵示例一样,对角线(用黑线突出显示)表示客户的适当分类。
第 3.3 章:分析
假人模特(第一排)
Dummy1 ,每次只预测平均值,分布只有平均值。它没有区分高价值客户和低价值客户。
稍微好一点的是, Dummy2 要么预测 0 美元,要么预测平均值。这意味着它可以对分销提出一些要求,事实上,分别获得 81%的最低价值客户和 98%的最高价值客户。
但是这些模型的主要问题是,当涉及到区分客户群时,这些模型没有什么复杂性,这一点在研究 MAE 时并不明显(但是如果你知道它们的标签是如何生成的,这一点就很明显了)。对于第 1 章中列出的所有业务应用程序,这是构建一个强大的 CLV 模型的全部要点,区分客户类型是成功的关键
CLV 车型(底排)
首先,不要让整体的准确性吓到你。同样,我们可以通过汇总统计数据来隐藏事实,我们也可以通过推出的准确性度量来隐藏分布建模的强度。
其次,与上表相反,从这张图中可以很清楚地看出,虚拟模型如此命名是有原因的——这些第二排模型实际上是在捕捉一个分布。即使 Dummy2 捕获了更高比例的低价值客户,这也可能是长尾 CLV 分布的产物。很明显,这些是你想要选择的模型。
看对角线,我们可以看到 GBM 在预测大多数类别方面都有重大改进。主要的错误标注——少了两个方格——大大减少了。GBM 方面最大的增长是对中等水平客户的认可,这是一个很好的迹象,表明分布是健康的,我们的预测是现实的。
Ch4。结尾
如果你只是浏览了这篇文章,你可能会认为 GBM 是一个更好的 CLV 模型。这可能是真的,但模型选择更复杂。您可能想问的一些问题:
- 我想预测未来很多年吗?
- 我想预测客户流失吗?
- 我想预测交易的数量吗?
- 我有足够的数据来运行监督模型吗?
- 我在乎可解释性吗?
虽然这些问题与本文的主题无关,但是在将您的模型换成 GBM 之前,需要回答这些问题。
选择正确的模型需要对您的业务和用例有深刻的理解。由布雷特·乔丹在 Unsplash 上拍摄的照片
选择模型时要考虑的第一个基本变量是您正在处理的公司数据。通常 BTYD 模型工作良好,并可与 ML 替代方案相媲美。但是 BTYD 模型对客户行为做了一些强有力的假设,所以如果这些假设被打破,它们的表现就不是最优的。进行模型比较对于做出正确的模型决策至关重要。
虽然个体层面的问题对于虚拟模型来说是显而易见的,但公司通常会通过运行“幼稚的”/“简单的”/“基于 excel 的”模型来做这件事,从而成为这些问题的牺牲品,即尝试在您的整个客户群中应用一个聚合数字。在一些公司,CLV 可以简单地定义为将收入平均分配给所有客户。这可能适用于一两份董事会报告,但实际上,这并不是计算如此复杂数字的适当方法。事实是,并非所有的客户都是平等的,你的公司越早将注意力从总体客户指标转移到强有力的个人层面预测,你就能越有效地营销、制定战略并最终找到你的最佳客户。
希望这本书读起来和写起来一样令人愉快,内容丰富。
感谢阅读!
客户案例研究:使用 AssemblyAI 在 PyTorch 中构建端到端语音识别模型
本文由assembly ai的机器学习研究工程师 Michael Nguyen 和comet . ml的 Niko Laskaris 撰写。AssemblyAI 使用 Comet 来记录、可视化和理解他们的模型开发管道。
随着端到端模型的引入,深度学习改变了语音识别领域的游戏。这些模型接收音频,并直接输出转录。目前最流行的两种端到端模型是百度的 Deep Speech 和谷歌的 Listen accept Spell(LAS)。深度语音和 LAS 都是基于递归神经网络(RNN)的架构,具有不同的建模语音识别的方法。深度语音使用连接主义者时间分类(CTC)损失函数来预测语音转录本。LAS 使用序列对网络体系结构进行序列预测。
这些模型通过利用深度学习系统从大型数据集学习的能力,简化了语音识别管道。有了足够的数据,从理论上讲,你应该能够建立一个超级健壮的语音识别模型,它可以考虑语音中的所有细微差别,而不必花费大量的时间和精力手工设计声学特征,或者处理更老式的 GMM-HMM 模型架构中的复杂管道。
深度学习是一个快速发展的领域,深度语音和 LAS 风格的架构已经很快过时了。你可以在下面的最新进展部分了解该行业的发展方向。
如何在 PyTorch 中构建自己的端到端语音识别模型
让我们看看如何在 PyTorch 中构建自己的端到端语音识别模型。我们将构建的模型受到 Deep Speech 2(百度对其著名模型的第二次修订)的启发,并对架构进行了一些个人改进。该模型的输出将是字符的概率矩阵,我们将使用该概率矩阵来解码音频中最有可能说出的字符。你可以在谷歌联合实验室找到完整的代码,也可以在 GPU 的支持下运行它。
准备数据管道
数据是语音识别最重要的方面之一。我们将原始音频波转换成 Mel 光谱图。
你可以从这篇精彩的文章这里阅读更多关于这一转变的细节。在这篇文章中,你可以把 Mel 声谱图想象成声音的图像。
为了处理音频数据,我们将使用一个名为 torchaudio 的非常有用的工具,这是 PyTorch 团队专门为音频数据构建的库。我们将在 LibriSpeech 的子集上进行训练,这是一个从有声读物中获得的阅读英语语音数据的语料库,包括 100 个小时的转录音频数据。您可以使用 torchaudio 轻松下载该数据集:
import torchaudio train_dataset = torchaudio.datasets.LIBRISPEECH("./", url="train-clean-100", download=True)
test_dataset = torchaudio.datasets.LIBRISPEECH("./", url="test-clean", download=True)
数据集的每个样本都包含波形、音频的采样率、话语/标签以及关于样本的更多元数据。你可以从源代码这里查看每个样本的样子。
数据扩充— SpecAugment
数据扩充是一种用于人为增加数据集多样性以增加数据集大小的技术。当数据不足或模型过拟合时,这种策略尤其有用。对于语音识别,您可以使用标准的增强技术,如改变音调、速度、注入噪声以及给音频数据添加混响。
我们发现谱图增强(SpecAugment)是一种更简单、更有效的方法。SpecAugment 在论文中首次介绍:SpecAugment:一种用于自动语音识别的简单数据扩充方法,其中作者发现,简单地剪切连续时间和频率维度的随机块显著提高了模型的泛化能力!
在 PyTorch 中,您可以使用 torchaudio 功能 FrequencyMasking 屏蔽频率维度,使用 TimeMasking 屏蔽时间维度。
torchaudio.transforms.FrequencyMasking()
torchaudio.transforms.TimeMasking()
现在我们有了数据,我们需要将音频转换成 Mel 频谱图,并将每个音频样本的字符标签映射成整数标签:
class TextTransform:
"""Maps characters to integers and vice versa"""
def __init__(self):
char_map_str = """
' 0
<SPACE> 1
a 2
b 3
c 4
d 5
e 6
f 7
g 8
h 9
i 10
j 11
k 12
l 13
m 14
n 15
o 16
p 17
q 18
r 19
s 20
t 21
u 22
v 23
w 24
x 25
y 26
z 27
"""
self.char_map = {}
self.index_map = {}
for line in char_map_str.strip().split('\n'):
ch, index = line.split()
self.char_map[ch] = int(index)
self.index_map[int(index)] = ch
self.index_map[1] = ' 'def text_to_int(self, text):
""" Use a character map and convert text to an integer sequence """
int_sequence = []
for c in text:
if c == ' ':
ch = self.char_map['']
else:
ch = self.char_map[c]
int_sequence.append(ch)
return int_sequencedef int_to_text(self, labels):
""" Use a character map and convert integer labels to an text sequence """
string = []
for i in labels:
string.append(self.index_map[i])
return ''.join(string).replace('', ' ')train_audio_transforms = nn.Sequential(
torchaudio.transforms.MelSpectrogram(sample_rate=16000, n_mels=128),
torchaudio.transforms.FrequencyMasking(freq_mask_param=15),
torchaudio.transforms.TimeMasking(time_mask_param=35)
)valid_audio_transforms = torchaudio.transforms.MelSpectrogram()text_transform = TextTransform()def data_processing(data, data_type="train"):
spectrograms = []
labels = []
input_lengths = []
label_lengths = []
for (waveform, _, utterance, _, _, _) in data:
if data_type == 'train':
spec = train_audio_transforms(waveform).squeeze(0).transpose(0, 1)
else:
spec = valid_audio_transforms(waveform).squeeze(0).transpose(0, 1)
spectrograms.append(spec)
label = torch.Tensor(text_transform.text_to_int(utterance.lower()))
labels.append(label)
input_lengths.append(spec.shape[0]//2)
label_lengths.append(len(label))spectrograms = nn.utils.rnn.pad_sequence(spectrograms, batch_first=True).unsqueeze(1).transpose(2, 3)
labels = nn.utils.rnn.pad_sequence(labels, batch_first=True)return spectrograms, labels, input_lengths, label_lengths
定义模型—深度演讲 2(但更好)
我们的模型将类似于 Deep Speech 2 架构。该模型将具有两个主要的神经网络模块——N 层残差卷积神经网络(ResCNN)来学习相关的音频特征,以及一组双向递归神经网络(BiRNN)来利用学习到的 ResCNN 音频特征。该模型以一个完全连接的层结束,该层用于在每个时间步长对角色进行分类。
卷积神经网络(CNN)擅长提取抽象特征,我们将把同样的特征提取能力应用于音频频谱图。我们选择使用残留的 CNN 层,而不是普通的 CNN 层。残差连接(又名跳过连接)首次在论文图像识别的深度残差学习中介绍,作者发现,如果你将这些连接添加到 CNN 中,你可以建立具有良好精度增益的真正深度网络。添加这些剩余连接也有助于模型更快地学习和更好地概括。论文可视化神经网络的损失景观表明,具有剩余连接的网络具有“更平坦”的损失表面,使模型更容易浏览损失景观,并找到更低和更可概括的最小值。
递归神经网络(RNN)天生擅长序列建模问题。RNN 一步一步地处理音频特征,对每一帧进行预测,同时使用前一帧的上下文。我们使用 BiRNN 是因为我们不仅想要每一步之前的帧的上下文,还想要每一步之后的帧的上下文。这可以帮助模型做出更好的预测,因为在做出预测之前,音频中的每一帧都将具有更多信息。我们使用 RNN 的门控循环单元(GRU 的)变体,因为它比 LSTM 的需要更少的计算资源,并且在某些情况下工作得一样好。
该模型输出字符的概率矩阵,我们将使用该矩阵输入到我们的解码器中,以提取模型认为最有可能说出的字符。
class CNNLayerNorm(nn.Module):
"""Layer normalization built for cnns input"""
def __init__(self, n_feats):
super(CNNLayerNorm, self).__init__()
self.layer_norm = nn.LayerNorm(n_feats)def forward(self, x):
# x (batch, channel, feature, time)
x = x.transpose(2, 3).contiguous() # (batch, channel, time, feature)
x = self.layer_norm(x)
return x.transpose(2, 3).contiguous() # (batch, channel, feature, time)class ResidualCNN(nn.Module):
"""Residual CNN inspired by [https://arxiv.org/pdf/1603.05027.pdf](https://arxiv.org/pdf/1603.05027.pdf)
except with layer norm instead of batch norm
"""
def __init__(self, in_channels, out_channels, kernel, stride, dropout, n_feats):
super(ResidualCNN, self).__init__()self.cnn1 = nn.Conv2d(in_channels, out_channels, kernel, stride, padding=kernel//2)
self.cnn2 = nn.Conv2d(out_channels, out_channels, kernel, stride, padding=kernel//2)
self.dropout1 = nn.Dropout(dropout)
self.dropout2 = nn.Dropout(dropout)
self.layer_norm1 = CNNLayerNorm(n_feats)
self.layer_norm2 = CNNLayerNorm(n_feats)def forward(self, x):
residual = x # (batch, channel, feature, time)
x = self.layer_norm1(x)
x = F.gelu(x)
x = self.dropout1(x)
x = self.cnn1(x)
x = self.layer_norm2(x)
x = F.gelu(x)
x = self.dropout2(x)
x = self.cnn2(x)
x += residual
return x # (batch, channel, feature, time)class BidirectionalGRU(nn.Module):def __init__(self, rnn_dim, hidden_size, dropout, batch_first):
super(BidirectionalGRU, self).__init__()self.BiGRU = nn.GRU(
input_size=rnn_dim, hidden_size=hidden_size,
num_layers=1, batch_first=batch_first, bidirectional=True)
self.layer_norm = nn.LayerNorm(rnn_dim)
self.dropout = nn.Dropout(dropout)def forward(self, x):
x = self.layer_norm(x)
x = F.gelu(x)
x, _ = self.BiGRU(x)
x = self.dropout(x)
return xclass SpeechRecognitionModel(nn.Module):
"""Speech Recognition Model Inspired by DeepSpeech 2"""def __init__(self, n_cnn_layers, n_rnn_layers, rnn_dim, n_class, n_feats, stride=2, dropout=0.1):
super(SpeechRecognitionModel, self).__init__()
n_feats = n_feats//2
self.cnn = nn.Conv2d(1, 32, 3, stride=stride, padding=3//2) # cnn for extracting heirachal features# n residual cnn layers with filter size of 32
self.rescnn_layers = nn.Sequential(*[
ResidualCNN(32, 32, kernel=3, stride=1, dropout=dropout, n_feats=n_feats)
for _ in range(n_cnn_layers)
])
self.fully_connected = nn.Linear(n_feats*32, rnn_dim)
self.birnn_layers = nn.Sequential(*[
BidirectionalGRU(rnn_dim=rnn_dim if i==0 else rnn_dim*2,
hidden_size=rnn_dim, dropout=dropout, batch_first=i==0)
for i in range(n_rnn_layers)
])
self.classifier = nn.Sequential(
nn.Linear(rnn_dim*2, rnn_dim), # birnn returns rnn_dim*2
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(rnn_dim, n_class)
)def forward(self, x):
x = self.cnn(x)
x = self.rescnn_layers(x)
sizes = x.size()
x = x.view(sizes[0], sizes[1] * sizes[2], sizes[3]) # (batch, feature, time)
x = x.transpose(1, 2) # (batch, time, feature)
x = self.fully_connected(x)
x = self.birnn_layers(x)
x = self.classifier(x)
return x
选择合适的优化器和调度器——AdamW 具有超强的融合能力
优化器和学习率计划在让我们的模型收敛到最佳点方面起着非常重要的作用。选择正确的优化器和调度器还可以节省您的计算时间,并帮助您的模型更好地推广到现实世界的用例。对于我们的模型,我们将使用 AdamW 和单周期学习率调度器。 Adam 是一个广泛使用的优化器,它可以帮助你的模型更快地收敛,从而节省计算时间,但它因不能像随机梯度下降又名 SGD 一样泛化而臭名昭著。
AdamW 首次在解耦权重衰减正则化中引入,被认为是对 Adam 的“修正”。论文指出,最初的 Adam 算法有一个错误的权重衰减实现,AdamW 试图修复这个错误。这个修正有助于解决亚当的泛化问题。
单周期学习率调度器首次在论文中介绍:超收敛:使用大学习率非常快速地训练神经网络。这篇论文表明,使用一个简单的技巧,你可以训练神经网络的速度提高一个数量级,同时保持它们的泛化能力。你从一个低的学习速率开始,升温到一个大的最大学习速率,然后线性衰减到你最初开始的同一点。
因为最大学习率比最小学习率高很多,所以您也获得了一些正则化的好处,这有助于您的模型在数据集较少的情况下更好地进行概化。
对于 PyTorch,这两个方法已经是包的一部分了。
optimizer = optim.AdamW(model.parameters(), hparams['learning_rate'])
scheduler = optim.lr_scheduler.OneCycleLR(optimizer,
max_lr=hparams['learning_rate'],
steps_per_epoch=int(len(train_loader)),
epochs=hparams['epochs'],
anneal_strategy='linear')
CTC 丢失功能—将音频与抄本对齐
我们的模型将被训练来预测我们馈入模型的声谱图中每一帧(即时间步长)字母表中所有字符的概率分布。
图片取自 distill.pub
传统的语音识别模型会要求你在训练之前将抄本文本与音频对齐,并且该模型会被训练来预测特定帧处的特定标签。
CTC 丢失功能的创新之处在于它允许我们跳过这一步。我们的模型将在训练过程中学习对齐脚本本身。这其中的关键是由 CTC 引入的“空白”标签,它使模型能够说某个音频帧没有产生一个字符。你可以从这篇精彩的帖子中看到关于 CTC 及其工作原理的更详细的解释。
PyTorch 还内置了 CTC 丢失功能。
criterion = nn.CTCLoss(blank=28).to(device)
评估你的语音模型
在评估您的语音识别模型时,行业标准使用单词错误率(WER)作为度量标准。单词错误率确实如其所言——它获取模型输出的转录和真实转录,并测量它们之间的错误。你可以在这里看到这是如何实现的。另一个有用的指标叫做字符错误率(CER)。CER 测量模型输出和真实标签之间的字符误差。这些指标有助于衡量模型的性能。
对于本教程,我们将使用“贪婪”解码方法将模型的输出处理成字符,这些字符可以组合起来创建副本。“贪婪”解码器接收模型输出,该模型输出是字符的 softmax 概率矩阵,并且对于每个时间步长(谱图帧),它选择具有最高概率的标签。如果标签是空白标签,我们会将其从最终抄本中删除。
def GreedyDecoder(output, labels, label_lengths, blank_label=28, collapse_repeated=True):
arg_maxes = torch.argmax(output, dim=2)
decodes = []
targets = []
for i, args in enumerate(arg_maxes):
decode = []
targets.append(text_transform.int_to_text(labels[i][:label_lengths[i]].tolist()))
for j, index in enumerate(args):
if index != blank_label:
if collapse_repeated and j != 0 and index == args[j -1]:
continue
decode.append(index.item())
decodes.append(text_transform.int_to_text(decode))
return decodes, targets
使用 Comet.ml 训练和监控您的实验
Comet.ml 提供了一个平台,允许深度学习研究人员跟踪、比较、解释和优化他们的实验和模型。Comet.ml 提高了我们在 AssemblyAI 的工作效率,我们强烈推荐团队使用这个平台进行任何类型的数据科学实验。Comet.ml 设置起来超级简单。只需几行代码就能完成。
# initialize experiment object
experiment = Experiment(api_key=comet_api_key, project_name=project_name)
experiment.set_name(exp_name)# track metrics
experiment.log_metric('loss', loss.item())
Comet.ml 为您提供了一个非常高效的仪表盘,您可以在其中查看和跟踪模型的进度。
您可以使用 Comet 来跟踪度量、代码、超级参数、您的模型的图表,以及其他许多东西!Comet 提供的一个非常方便的特性是能够将您的实验与许多其他实验进行比较。
Comet 有丰富的特性集,我们不会在这里一一介绍,但是我们强烈建议使用它来提高生产率和健全性。
这是我们培训脚本的其余部分。
class IterMeter(object):
"""keeps track of total iterations"""
def __init__(self):
self.val = 0def step(self):
self.val += 1def get(self):
return self.valdef train(model, device, train_loader, criterion, optimizer, scheduler, epoch, iter_meter, experiment):
model.train()
data_len = len(train_loader.dataset)
with experiment.train():
for batch_idx, _data in enumerate(train_loader):
spectrograms, labels, input_lengths, label_lengths = _data
spectrograms, labels = spectrograms.to(device), labels.to(device)optimizer.zero_grad()output = model(spectrograms) # (batch, time, n_class)
output = F.log_softmax(output, dim=2)
output = output.transpose(0, 1) # (time, batch, n_class)loss = criterion(output, labels, input_lengths, label_lengths)
loss.backward()experiment.log_metric('loss', loss.item(), step=iter_meter.get())
experiment.log_metric('learning_rate', scheduler.get_lr(), step=iter_meter.get())optimizer.step()
scheduler.step()
iter_meter.step()
if batch_idx % 100 == 0 or batch_idx == data_len:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, batch_idx * len(spectrograms), data_len,
100\. * batch_idx / len(train_loader), loss.item()))def test(model, device, test_loader, criterion, epoch, iter_meter, experiment):
print('\nevaluating…')
model.eval()
test_loss = 0
test_cer, test_wer = [], []
with experiment.test():
with torch.no_grad():
for I, _data in enumerate(test_loader):
spectrograms, labels, input_lengths, label_lengths = _data
spectrograms, labels = spectrograms.to(device), labels.to(device)output = model(spectrograms) # (batch, time, n_class)
output = F.log_softmax(output, dim=2)
output = output.transpose(0, 1) # (time, batch, n_class)loss = criterion(output, labels, input_lengths, label_lengths)
test_loss += loss.item() / len(test_loader)decoded_preds, decoded_targets = GreedyDecoder(output.transpose(0, 1), labels, label_lengths)
for j in range(len(decoded_preds)):
test_cer.append(cer(decoded_targets[j], decoded_preds[j]))
test_wer.append(wer(decoded_targets[j], decoded_preds[j]))avg_cer = sum(test_cer)/len(test_cer)
avg_wer = sum(test_wer)/len(test_wer)
experiment.log_metric('test_loss', test_loss, step=iter_meter.get())
experiment.log_metric('cer', avg_cer, step=iter_meter.get())
experiment.log_metric('wer', avg_wer, step=iter_meter.get())print('Test set: Average loss: {:.4f}, Average CER: {:4f} Average WER: {:.4f}\n'.format(test_loss, avg_cer, avg_wer))def main(learning_rate=5e-4, batch_size=20, epochs=10,
train_url="train-clean-100", test_url="test-clean",
experiment=Experiment(api_key='dummy_key', disabled=True)):hparams = {
"n_cnn_layers": 3,
"n_rnn_layers": 5,
"rnn_dim": 512,
"n_class": 29,
"n_feats": 128,
"stride": 2,
"dropout": 0.1,
"learning_rate": learning_rate,
"batch_size": batch_size,
"epochs": epochs
}experiment.log_parameters(hparams)use_cuda = torch.cuda.is_available()
torch.manual_seed(7)
device = torch.device("cuda" if use_cuda else "cpu")if not os.path.isdir("./data"):
os.makedirs("./data")train_dataset = torchaudio.datasets.LIBRISPEECH("./data", url=train_url, download=True)
test_dataset = torchaudio.datasets.LIBRISPEECH("./data", url=test_url, download=True)kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {}
train_loader = data.DataLoader(dataset=train_dataset,
batch_size=hparams['batch_size'],
shuffle=True,
collate_fn=lambda x: data_processing(x, 'train'),
**kwargs)
test_loader = data.DataLoader(dataset=test_dataset,
batch_size=hparams['batch_size'],
shuffle=False,
collate_fn=lambda x: data_processing(x, 'valid'),
**kwargs)model = SpeechRecognitionModel(
hparams['n_cnn_layers'], hparams['n_rnn_layers'], hparams['rnn_dim'],
hparams['n_class'], hparams['n_feats'], hparams['stride'], hparams['dropout']
).to(device)print(model)
print('Num Model Parameters', sum([param.nelement() for param in model.parameters()]))optimizer = optim.AdamW(model.parameters(), hparams['learning_rate'])
criterion = nn.CTCLoss(blank=28).to(device)
scheduler = optim.lr_scheduler.OneCycleLR(optimizer, max_lr=hparams['learning_rate'],
steps_per_epoch=int(len(train_loader)),
epochs=hparams['epochs'],
anneal_strategy='linear')iter_meter = IterMeter()
for epoch in range(1, epochs + 1):
train(model, device, train_loader, criterion, optimizer, scheduler, epoch, iter_meter, experiment)
test(model, device, test_loader, criterion, epoch, iter_meter, experiment)
训练功能根据完整的数据时段训练模型。测试功能在每个时期后根据测试数据评估模型。获取模型的 test_loss 以及 cer 和 wer 。您现在可以在 Google 联合实验室的 GPU 支持下开始运行训练脚本。
如何提高准确率
语音识别需要大量的数据和计算资源。展示的示例是在 LibriSpeech (100 小时的音频)的子集和单个 GPU 上训练的。为了获得最先进的结果,你需要对分布在许多机器上的数十个 GPU 上的数千小时的数据进行分布式训练。
另一种大幅提高精度的方法是使用语言模型和 CTC 波束搜索算法解码 CTC 概率矩阵。CTC 类型的模型非常依赖于这个解码过程来获得好的结果。幸运的是,有一个方便的开源库可以让你这么做。
本教程更容易理解,因此与 BERT(3.4 亿个参数)相比,它是一个相对较小的模型(2300 万个参数)。看起来你的网络越大,它的表现就越好,尽管回报是递减的。然而,正如 OpenAI 的研究 Deep Double Descent 所证明的那样,更大的模型等同于更好的性能并不总是如此。
这个模型有 3 个剩余的 CNN 层和 5 个双向 GRU 层,这应该允许您在一个至少有 11GB 内存的 GPU 上训练一个合理的批量大小。您可以调整 main 函数中的一些 hyper 参数,以根据您的用例和计算可用性来减少或增加模型大小。
深度学习语音识别的最新进展
深度学习是一个快速发展的领域。似乎你不能一个星期没有一些新的技术得到最先进的结果。在语音识别的世界里,这里有一些值得探索的东西。
变形金刚(电影名)
变形金刚席卷了自然语言处理世界!在论文中首先介绍的是,变形金刚已经被采用和修改来击败几乎所有现存的 NLP 任务,淘汰 RNN 的类型架构。转换器看到序列数据的完整上下文的能力也可以转换成语音。
无监督预训练
如果你密切关注深度学习,你可能听说过伯特、GPT 和 GPT2。这些 Transformer 模型首先与带有未标记文本数据的语言建模任务相关,然后在各种 NLP 任务上进行微调,并获得最先进的结果!在预训练期间,模型学习语言统计的一些基本知识,并利用这种能力在其他任务中表现出色。我们相信这项技术在语音数据上也有很大的前景。
单词片段模型
我们上面定义的模型输出字符。这样做的一些好处是,在对语音进行推理时,该模型不必担心词汇以外的单词。所以对于单词 c h a t 来说,每个字符都有自己的标签。使用字符的缺点是效率低,而且模型容易出错,因为您一次只能预测一个字符。已经探索了使用整个单词作为标签,并取得了一定程度的成功。使用这种方法,整个单词聊天将成为标签。但是使用全词,您将不得不保存所有可能词汇的索引来进行预测,这是内存低效的,在预测期间可能会用完词汇。最佳点是使用单词片段或子单词单元作为标签。您可以将单词分割成子单词单元,并使用这些子单词单元作为标签,即 ch at ,而不是单个标签的字符。这解决了词汇表之外的问题,并且更有效,因为它需要比使用字符更少的解码步骤,并且不需要所有可能单词的索引。单词片段已经成功地用于许多 NLP 模型,如 BERT,并且也可以自然地用于语音识别问题。
客户流失分析
决策树分类器在客户流失分析和预测中的应用。
客户流失是全球信用卡公司、有线电视服务提供商、SASS 和电信公司等企业面临的最重要和最具挑战性的问题之一。尽管看起来不是最有趣的,但客户流失指标可以帮助企业提高客户保持率。
freestocks 在 Unsplash 上拍摄的客户流失照片
我们可以通过将客户分成不同的类别来对客户流失进行分类。合同流失适用于有线电视公司和 SAAS 服务提供商等企业,是指客户决定不再继续使用到期的合同。另一方面,自愿流失是指客户决定取消现有服务,这适用于预付费手机和流媒体订阅提供商等公司。也有消费者没有完成交易就离开可能的购买的时候。我们可以将这些情况归类为非合同流失,这适用于依赖零售场所、在线商店或在线借贷服务的企业。最后,还有非自愿流失,例如,客户不能支付他们的信用卡账单,不再留在信用卡公司。
客户流失的原因可能各不相同,并且需要领域知识来正确定义,但是一些常见的原因是:产品使用率低,服务差,而其他地方的价格更好。不管不同行业的具体原因是什么,有一点适用于每个领域,那就是获取新客户的成本要高于留住现有客户的成本。这对公司的运营成本和营销预算有直接影响。
由于客户流失在企业中的重要性,利益相关者正在投入更多的时间和精力来找出他们组织内部的原因,他们如何准确预测可能停止与他们做生意的现有客户的类型,以及他们可以做些什么来最大限度地减少客户流失。
避免客户流失的最好方法是了解你的客户,而了解你的客户的最好方法是通过历史和新的客户数据。
在本文中,我们将浏览一些消费者数据,看看我们如何利用数据洞察和预测建模来提高客户保持率。在我们的分析中,我们将使用 Python 和各种机器学习算法进行预测。
我们的第一个客户数据集来自一家信用卡公司,在那里我们可以查看客户属性,如性别、年龄、任期、余额、他们订阅的产品数量、他们的估计工资以及他们是否停止了订阅。
我们可以看到我们的数据集,但我们也希望确保数据是干净的,因此作为清理过程的一部分,我们会查看缺失的值和数据类型。
当我们查看统计数据时,我们看到我们客户的平均年龄为 39 岁,客户成为会员的平均月数为 5,预计平均工资为 10 万英镑。
当我们观察预计薪资的性别和地理分布时,我们发现在法国和西班牙,男性客户的预计平均薪资高于女性,而在德国,女性客户的预计平均薪资更高。
当我们看年龄和信用评分之间的关系时,为了明确定义相关性,线性关系非常弱。
基于我们的基本探索性分析,我们可以定义重要的客户属性,这些属性可以为我们提供最佳的洞察力,以便预测可能流失的客户类型。我们可以继续这一分析来回答一些基本问题,例如,“较低的估计工资会增加流失率吗?”或者“较低的信用评分会增加客户流失率吗?”诸如此类。我们可以用不同的方式对数据集进行分组和汇总,以从客户属性中获得更多的洞察力。我们将在下一个数据集中深入探讨这些问题。现在,让我们开始考虑预测哪些客户会流失。
在这种情况下,我们可以将目标(响应)变量标记为客户流失。这意味着我们可以创建一个分类模型,并执行不同的算法方法,如决策树、随机森林、逻辑回归或支持向量机。当谈到机器学习模型时,我们寻找两个主要条件;1-特征集的正态分布,2-特征集的相同尺度。
在这个数据集中,我们可以选择信用评分,地理位置,性别,年龄,任期和估计工资属性作为特征集,流失作为目标变量。
我们必须确保将分类变量更新为数字变量,因为我们将应用的机器学习技术要求所有客户属性都是数字的。
我们可以进一步随机地将我们的数据集分为训练和测试数据集,以便用训练数据集拟合我们的模型,并用测试数据集测试预测。其思路是用训练数据集训练模型,用测试数据集测试预测。如果我们不使用训练和测试数据集,而是使用整个数据集,该算法将只对我们的数据集进行准确的预测,而不会对任何新的数据进行预测。
在此数据集中,让我们使用 DecisionTreeClassifier 和 RandomForestClassifier 来创建我们的模型和预测,进一步评估它们,看看哪一个更好。
根据度量评估,虽然决策分类器模型有 73%的预测是准确的,但 RandomForestClassifier 模型有 82%的预测是准确的。在这种情况下,我们更喜欢使用随机森林。
当我们观察流失和未流失客户的分布时,我们看到数据是公正的。这意味着我们不能仅仅依赖预测模型的准确性度量分数。让我们看看第二个客户数据集,看看我们是否可以做更好的分析和预测模型。
这次,我们将查看一家电信公司及其现有的客户属性,例如他们当前的计划、费用、所在州的位置、客户服务呼叫量、客户时长和客户流失率。
数据集中没有缺失数据,并且数据类型正确。让我们看看分类值和它们的独特值。
当我们查看州和流失率时,我们看到加利福尼亚州和新泽西州是流失率最高的州。
我们还发现,国际计划客户的流失率较高,而语音邮件计划客户的流失率较低。
留在公司的顾客比离开公司的顾客多得多。如果您还记得之前对信用卡公司的分析,这意味着数据中的不平衡,并对预测模型的开发产生影响。(我们之前没有提到的一个重要方面是,我们没有使用电话号码、客户 id 或账号等唯一标识符来选择功能。)
糟糕的客户服务是客户流失的众所周知的原因之一。在我们的案例中,我们可以看到客户服务呼叫量和客户流失率之间存在很强的正线性关系。
有了这个数据集,让我们开发多个不同的模型,并对它们进行评估,看看哪一个最适合解决我们的客户流失业务问题。
与之前的信用卡客户数据集类似,我们需要执行预处理,并将分类变量更新为数值变量,以便创建我们的模型。
现在,我们准备分割数据集来训练/测试和创建我们的模型。先说随机森林。
我们为预测电信公司客户流失而创建的随机森林模型的准确率为 0.89。然而,我们应该进一步分析这一点,因为数据是公正的。
我们可以查看其他评估指标,如交叉验证矩阵,它将为我们提供真阳性、假阳性、真阴性和假阴性的数量、精确度、召回率和 f1 分数。通过查看哪些特征对预测贡献最大,我们还可以了解我们可以做些什么来改进模型。
该模型预测 560 个真阴性,13 个假阳性,54 个假阴性,40 个真阳性。
当我们用随机森林分类器评估模型时,我们看到:
精确度分数是 0.729
回忆分数是 0.372
ROC 曲线如下:
AUC 分数(roc 曲线下的面积)是 0.83,f1 分数是 0.49。
我们还发现,为了从模型中获得最佳性能,我们需要将 n 估计量设置为 30。(目前,我们的模型使用 100)
我们可以进一步查看特征的重要性,看看哪些特征对预测的影响最大。
最重要的是,我们可以明确地从模型中移除状态。
让我们使用支持向量机创建另一个模型。
当我们创建模型并查看准确性时,我们已经看到支持向量机的准确性分数低于随机森林分类。
当我们创建模型并查看准确性时,我们已经看到支持向量机的准确性分数低于随机森林分类。
该模型预测 567 个真阴性,6 个假阳性,83 个假阴性,11 个真阳性。尽管假阳性计数略有下降,但与 RandomForestClassifier 相比,真阳性明显较少。
精确度分数(0.647)和召回分数(0.11)都比随机分类器低得多。roc 曲线下面积(auc)为 0.83,与随机森林分类器相同。支持向量机的最佳选择度是 1。(当前设置为默认值 3)。
基于我们创建的两个预测模型,我们用随机森林分类器创建的第一个模型将是更好的选择。我们还可以调整这个模型,并通过更新 n_estimator 和从特征集中删除状态变量来改进它,以便更好地预测。
借助现有的数据消费者洞察,公司可以预测客户可能的需求和问题,针对他们制定适当的战略和解决方案,满足他们的期望并保留他们的业务。基于预测分析和建模,企业可以通过细分和提供定制的解决方案,有针对性地集中注意力。分析客户在使用服务的生命周期中流失是如何发生的以及何时发生的,将使公司能够采取更多先发制人的措施。
利用分类建模预测客户流失
第 1 部分:探索性数据分析
在今天的商业世界里,竞争是激烈的,每个顾客都是有价值的。理解客户是最重要的,包括能够理解客户的行为模式。客户流失率是商业客户(在 SaaS 平台上非常普遍)离开商业业务并把钱带到别处的比率。了解客户流失对公司的成功至关重要,而流失分析是了解客户的第一步。
我决定从一个 Kaggle 数据集中执行客户流失分析,该数据集给出了一家电信公司(Telcom)的客户信息数据,试图更好地了解他们的客户流失可能性。虽然我们最终将构建一个分类模型来预测客户流失的可能性,但我们必须首先深入探索数据分析(EDA)流程,以更好地了解我们的数据。包含代码和笔记本的 Github 库可以在这里找到。
数据
如上所述,数据来源于 Kaggle。在我们的数据集中,我们有 7043 行(每行代表一个唯一的客户)和 21 列:19 个特征,1 个目标特征(客户流失)。数据由数字和分类特征组成,因此我们需要分别处理每种数据类型。
目标:
- 客户流失—客户是否流失(是,否)
数字特征:
- 任期——客户在公司工作的月数
- 月度费用—每月向客户收取的费用
- 总费用——向客户收取的总费用
分类特征:
- CustomerID
- 性别—男/女
- 老年人—客户是否是老年人(1,0)
- 合作伙伴—客户是否有合作伙伴(是,否)
- 受抚养人——客户是否有受抚养人(是,否)
- 电话服务—客户是否有电话服务(是,否)
- 多条线路—客户是否有多条线路(是,否,无电话服务)
- 互联网服务—客户的互联网服务类型(DSL、光纤、无)
- 在线安全—客户是否有在线安全插件(是、否、无互联网服务)
- OnlineBackup —客户是否有在线备份插件(是、否、无互联网服务)
- 设备保护—客户是否有设备保护附件(是、否、无互联网服务)
- 技术支持—客户是否有技术支持附加服务(是、否、无互联网服务)
- 流媒体电视—客户是否有流媒体电视(是,否,无互联网服务)
- 流媒体电影—客户是否有流媒体电影(是,否,无互联网服务)
- 合同—客户合同的期限(每月、1 年、2 年)
- 无纸账单—客户是否有无纸账单(是,否)
- 支付方式—客户的支付方式(电子支票、邮寄支票、银行转账(自动)、信用卡(自动))
目标
我们可以从左边的饼图中看到,我们的数据集中大约有 27%的电信客户最终会流失。这看起来确实是一个相当高的数字,但是由于我不在电信公司工作,也没有电信领域知识的经验,我将只考虑它的价值,而不会过多地考虑它。由于这是我们的目标变量,我们将在大多数变量的 EDA 中使用 Churn 作为一个元素。
数字特征
使用数字特征时,我们可以查看的最具信息性的统计数据之一是数据的分布。我们将使用一个核密度估计图来显示相关变量的概率分布。此图将向我们展示新数据点落在数据集上的可能性最大的位置。我们将为所有数字要素创建一个 KDE。
为了以稍微不同的方式查看我们的数据以获得更多的信息,并且因为“任期”由月份表示,这可能是一个 bi 噪音,我决定根据他们的任期对客户进行分组。
结论:
- 客户流失最有可能发生在任期的 20 个月之前。
- 流失的客户最有可能每月收费超过 60 美元。
- 一般来说,随着月费的增加,客户流失的可能性也会增加
- 总费用的分布非常普遍,因此我们将重点关注“每月费用”的显著性
分类特征
性别
结论:
- 我们的数据中,老年人明显少于非老年人
- *总的来说,*更多的非老年人会比老年人流失
- 老年公民比非老年公民流失的比例更高
- 一旦月费超过 60 美元,老年人和非老年人都开始流失
- 非老年人最有可能每月收费 20 美元左右
- 与老年人相比,非老年人在每月收费低于 60 美元的情况下更容易流失
合作伙伴和家属
结论:
- 对于有合作伙伴的客户,数据集是分开的
- 那些没有伴侣的人比那些有伴侣的人流失率略高
- 没有家眷的客户比有家眷的客户流失率略高
- 对于合作伙伴价值和从属价值而言,流失和不流失客户的月费用非常相似
电话服务和线路数量
结论:
- 只有电话服务的客户不会比其他客户流失更多
- 只有电话服务的人 25%的时间会流失
- 使用电话服务的客户只需支付更高的月平均费用
- 拥有多条线路的客户与拥有单一线路的客户的流失率大致相同
- 拥有多条电话线的用户比拥有单一电话线的用户更频繁地支付更高的月费
互联网服务
结论:
- 光纤是最受欢迎的互联网选择
- 光纤互联网客户流失率远远高于 DSL 或无互联网客户
- 光纤是一项非常昂贵的服务,当客户拥有这项服务时,他们的流失率会比没有这项服务时稍高
- 当 DSL 用户的月费用在 40 到 60 美元之间时,他们最有可能流失。
附加服务
结论:
- 拥有电视流媒体和/或电影流媒体服务的客户比所有其他附加服务的客户流失更多
- 大多数类别的客户流失率将在每月 100 美元左右达到峰值
契约
结论:
- 超过一半的顾客选择每月付款
- 越来越多的客户因月度计划而流失
- 计划越长,流失率越低
- 合同时间越长,月费通常越高
无纸化账单和支付
支付结论:
- 非无纸化计费的客户流失率比无纸化客户高出近 15%
- 当每月价格低于 60 美元时,无纸客户的流失率与非无纸客户相似,一旦超过 60 美元,无纸客户的流失率就比非无纸客户高
- 使用电子支票支付的客户流失率比使用所有其他支付方式的客户高出 10%
- 无论月费多少,用信用卡支付的顾客都有稳定的流失率,而用银行转账、电子支票或邮寄支票支付的顾客,一旦月费超过 60 英镑,他们的流失率就会上升。
后续步骤
因此,我们刚刚浏览了数据集的每个要素,并探索了每个要素的至少几个不同方面。正如任何项目中的 EDA 一样,总是有更多的方法来处理数据集以继续学习。与任何数据科学项目一样,拥有业务领域和底层数据理解的坚实基础至关重要。既然我们已经看了关键特征和它们相互作用的方式,我们可以开始构建我们的分类模型了。看看我的下一篇博客来看看它的实际效果吧!
电信领域的客户流失
快门架
开发用于流失预测的机器学习模型
公司通常更关注客户的获取,而把留住客户放在第二位。然而,吸引一个新客户的成本是留住一个现有客户的五倍。根据贝恩公司的研究,T2 的客户保持率提高 5%,T4 的利润会增加 25%到 95%。
客户流失是一项指标,显示客户停止与某家公司或某项服务进行交易,也称为客户流失。通过遵循这一指标,大多数企业可以做的是试图了解流失数字背后的原因,并通过反应行动计划解决这些因素。
但是,如果你能提前知道某个特定的客户可能会离开你的企业,并有机会及时采取适当的行动以防止其发生,那该怎么办?
导致客户做出取消决定的原因可能很多,包括服务质量差、客户支持延迟、价格、新的竞争对手进入市场等等。通常,没有单一的原因,而是一系列事件的组合,最终导致顾客的不满。
如果你的公司不能识别这些信号并在点击取消按钮之前采取行动,就没有回头路了,你的客户已经走了。但是你还有一些有价值的东西:数据。你的顾客留下了很好的线索,让你知道你的需求在哪里。它可以成为有意义的见解的宝贵来源,并培训客户流失模型。从过去学习,并且手头有战略信息到改善未来经验,这都是关于机器学习的。
说到电信领域,这里有巨大的机会空间。运营商收集的财富和数量的客户数据可以为从被动转变为主动做出很大贡献。复杂的人工智能和数据分析技术的出现进一步帮助利用这些丰富的数据以更加有效的方式解决客户流失问题**。**
在本文中,我将使用一个匿名运营商的客户群数据集,由平台 IBM 开发者提供。该数据集总共包含 7,043 个客户和 21 个属性,来自个人特征、服务签名和合同细节。在这些条目中,5,174 个是活跃客户,1,869 个是不稳定客户,这表明数据集非常不平衡。该评估的目标变量将是特性Churn
。
主要目标是开发一个机器学习模型,能够根据客户的可用数据预测客户流失。对于这个实现,我将主要使用 Python、 Pandas 和 Scikit-Learn 库。完整的代码你可以在我的 GitHub 上找到。
为此,我将完成以下步骤:
- 探索性分析
- 数据准备
- 训练、调整和评估机器学习模型
探索性分析
由于本实验的目的是识别可能导致客户流失的模式,我将主要关注数据集的流失部分,以进行探索性分析。
客户生命周期(以月为单位)由特征Tenure
表示,客户流失率由特征Churn
表示,这是本实验的目标变量**。下面的柱状图可以很好地揭示客户流失在客户生命周期中的分布情况。**
我们可以看到,最大的大多数客户在第一个月取消或不续订,总计 20.3%的客户退出。这种高费率的原因可能是糟糕的首次体验、试用期或预付费帐户,如果在预定义的期限内没有充值,这些帐户将自动过期。
继续分析数据集的变动部分,我现在将重点关注分类特征和它如何将与目标特征Churn
关联起来。为此,我将特性分为三组:个人属性、订阅的服务和合同属性。
个人属性
数据集上可用的个人属性有:Gender, SeniorCitizen, Partner, Dependents
。下图可以提供一些有意义的见解,例如:
- 没有家眷的客户流失的可能性是普通客户的四倍
- 老年人流失的可能性降低了三倍
- 伴侣离开的可能性几乎减少了两倍
流失客户的个人属性
服务属性
这组功能表明客户订购了哪种服务,或者是否不适用。这个群体聚集的特征列表PhoneService, MultipleLines, InternetService, OnlineSecurity, OnlineBackup, DeviceProtection, TechSupport, StreamingTV and StreamingMovie
。
下图显示了可以注意到的类别之间高差异的特征。它提供了关于客户更有可能使用哪种运营商服务的见解:
- 取消订购的大多数客户都启用了电话服务
- 使用光纤网络服务的客户比使用 DSL的客户更有可能取消**
- 没有启用在线安全、设备保护、在线备份和技术支持服务的客户更有可能离开
流失客户的服务属性
合同属性
这组特征表明了合同方面并收集了属性Contract, PaperlessBilling
和PaymentMethod.
下面的图表提供了关于可能使订户更容易流失的合同方面的见解:
- 大多数取消订阅的客户都启用了逐月合同类型和无纸化计费
- 使用电子支票付款方式的顾客更有可能离开
流失客户的合同属性
数据准备
不平衡数据
将用于 ML 模型训练的目标变量将是Churn
。我们需要确保两个Churn
类(‘Yes’和‘No’)具有相等的分布,否则,它会在模型中引入偏差。
流失率
从左侧的图表中,我们可以看到数据集非常不平衡,包含 73.5%的“否”条目(表示活跃账户),而 26.5%的“是”条目(表示失败账户)。
有不同的技术来处理不平衡数据。在这个特殊的例子中,我使用了欠采样,简单地说,这意味着移除大多数类的一些观察值。之后,条目的数量平均分布在训练数据集的每个类中的 1,406 次出现中。
特征编码
机器学习算法只能读取数值。因此,数据准备的一个重要部分是将分类特征编码成数值,这个过程称为特征编码。
对于只有两个类(二进制)的分类特征,比如gender
、SeniorCitizen
、Partner
、Dependents
、PhoneService
和PaperlessBilling
,我使用了 Scikit-Learn 的 LabelEncoder ,这是一个实用程序类,用于帮助标准化标签,使它们只包含 0 和 n_classes-1 之间的值。
当涉及到具有两个以上类别的分类特征时,例如MultipleLines
、InternetService
、OnlineSecurity
、OnlineBackup
、DeviceProtection
、TechSupport
、StreamingTV
、StreamingMovies
、Contract
和PaymentMethod
,我使用了方法从 Pandas Dataframe 中获取 _dummies ,该方法将分类变量转换为哑变量/指示变量。
分割训练和测试数据
订户的数据集被分成 75%用于训练,25%用于测试。当暴露于新数据时,训练集将用于生成所选算法将使用的模型。测试集是我将接触的最终数据集,用于根据一些指标测量模型性能。
训练、调整和评估机器学习模型
韵律学
对于客户流失问题,理想的衡量标准是召回。Recall 回答以下问题:正确识别出实际阳性的比例是多少?在这种情况下,召回衡量的是在所有搅动中被正确分类的搅动的百分比,这也是我们在分析 ML 分类器的性能时所要寻找的。
例如,考虑一个免费赠送 1 GB 数据使用量的再参与活动。您可能希望确保这份额外礼物的精确度很高。换句话说,你会想尽可能减少将获得奖金的快乐用户的数量,而不是让这种奖金几乎只击中处于危险中的用户。
模型
在这个实验中,我应用了四种不同的 ML 算法来分析和比较它们各自获得的召回分数。下面列出了这些:
为了比较它们的性能,作为第一步,我应用了交叉验证方法,这是一种将数据划分为子集的技术,在一个子集上训练数据,并使用另一个子集来评估模型的性能。表现最好的是 SVM (0.78 回忆分数)和物流回归(0.79 回忆分数)。但仍有优化的空间。
通过超参数调谐
为了提高整体性能,在谈到召回指标时,我使用网格搜索为 SVM 和逻辑回归调整了分类器超参数。
最有表现力的改进来自 SVM 车型,从 0.78 变为 0.93。这意味着基于 SVM 实施的 ML 模型在预测客户流失时提供了 94%的精确度。另一方面,它的误报率很高,这意味着 63%的满意客户可能被错误地预测为流失。这些结果可以在下面的相关矩阵中看到,其中 1 表示流失,0 表示不流失。
SVM 和逻辑回归分类器的相关矩阵
逻辑回归模型经过调整后略有改善,召回分数从 0.79 提高到 0.82。尽管如此,它的误报率更低。实际上,这意味着你将能够吸引 82%的客户,而这些客户将会流失**,但你将会错过另外的 18%。此外,你可能有 28%的人被错误地预测为搅动。**
根据重新参与活动的情况,锁定有流失风险的尽可能多的客户,同时无意中接触到一些满意的客户,这可能是一个良好的权衡**,而不是在没有采取适当行动的情况下让大量客户取消。**
结论
没有哪种算法能 100%准确地预测客户流失。在精确度和召回率之间总会有一个权衡。这就是为什么测试和理解每个分类器的优点和缺点并充分利用它们是很重要的。
如果目标是吸引和接触客户以防止他们搅动,与那些被错误标记为“没有搅动”的人接触是可以接受的,因为这不会造成任何负面影响**。这可能会让他们对这项服务更加满意。如果对其产生的有意义的信息采取适当的行动,这是一种可以从第一天起增加价值的模型。**
感谢您的宝贵时间!
我希望这个项目能有助于你的目标。如果你有任何问题或任何类型的反馈,请随时在 LinkedIn 上联系我,并在 GitHub 上查看我的其他项目。
使用 PySpark 预测音乐流中的客户流失
Unsplash 照片由 Cezar Sampaio 拍摄
音乐流媒体业务**的关键是识别可能流失的用户,即有可能从付费和免费订阅降级到取消服务的用户。**如果一家音乐流媒体公司提前准确地识别出这些用户,他们可以向他们提供折扣或其他类似的激励措施,从而节省数百万美元的收入。众所周知,获得一个新客户比留住一个现有客户的成本更高。这是因为回头客可能会在你公司的产品和服务上多花 67%的钱。
1.1 项目概述
我们希望 确定可能会取消其帐户并离开服务 的用户。我们在这个项目中的目标是帮助一个虚构的企业(类似于 Spotify 和 Pandora),方法是建立和训练一个二进制分类器,该分类器能够根据从用户过去的活动和与该服务的交互中获得的模式,准确识别取消音乐流媒体服务的用户。
- 定义流失变量: 1 —在观察期内取消订阅的用户,以及 0 —始终保持服务的用户
由于数据集的规模,该项目是通过利用 Apache Spark 分布式集群计算框架功能,使用 Python API for Spark,PySpark 来实施的。
1.2 加载数据
**# import libraries**from pyspark import SparkContext, SparkConf
from pyspark.sql import SparkSession
from pyspark.sql import Window
from pyspark.sql.functions import udf, col, concat, count, lit, avg, lag, first, last, when
from pyspark.sql.functions import min as Fmin, max as Fmax, sum as Fsum, round as Fround
from pyspark.sql.types import IntegerType, DateType, TimestampTypefrom pyspark.ml import Pipeline
from pyspark.ml.feature import VectorAssembler, Normalizer, StandardScaler
from pyspark.ml.regression import LinearRegression
from pyspark.ml.classification import LogisticRegression, RandomForestClassifier, GBTClassifier
from pyspark.ml.clustering import KMeans
from pyspark.ml.tuning import CrossValidator, ParamGridBuilder
from pyspark.ml.evaluation import BinaryClassificationEvaluator,**# create a Spark session**
spark = SparkSession \
.builder \
.appName(‘CustomerChurn’) \
.getOrCreate()**# Check Spark configuration**
spark.sparkContext.getConf().getAll()path = "mini_sparkify_event_data.json"
df = spark.read.json(path)
2.数据理解
该数据集包含 2018 年 10 月 1 日至 2018 年 12 月 1 日期间记录的用户活动日志。完整数据集包含大约 2600 万行/日志,而子集包含 286 500 行。完整的数据集收集了 22,277 个不同用户的日志,而子集仅涵盖 225 个用户的活动。子集数据集包含 58 300 个免费用户和 228 000 个付费用户。两个数据集都有 18 列,如下所示。
root
|-- artist: string (nullable = true)
|-- auth: string (nullable = true)
|-- firstName: string (nullable = true)
|-- gender: string (nullable = true)
|-- itemInSession: long (nullable = true)
|-- lastName: string (nullable = true)
|-- length: double (nullable = true)
|-- level: string (nullable = true)
|-- location: string (nullable = true)
|-- method: string (nullable = true)
|-- page: string (nullable = true)
|-- registration: long (nullable = true)
|-- sessionId: long (nullable = true)
|-- song: string (nullable = true)
|-- status: long (nullable = true)
|-- ts: long (nullable = true)
|-- userAgent: string (nullable = true)
|-- userId: string (nullable = true)
每个活动日志都属于一个特定的用户。数据集中的七列代表静态的用户级信息(对于属于特定用户的所有日志是固定的):
**艺术家:**用户正在听的艺术家
userId :用户标识;
sessionId: 标识服务的用户的单个连续使用时段的唯一 Id。多个用户可以使用相同的 sessionId
名:用户的名
姓:用户的姓
性别:用户的性别;2 类( M 和 F )
位置:用户位置
userAgent :用户访问流媒体服务使用的代理;57 不同类别
注册:用户的注册时间戳
级别(非静态):订阅级别;2 类(免费和付费 )
**页面:**该事件生成时用户正在访问哪个页面。不同类型的页面将在下一节中详细介绍
页面列包含用户在应用程序中访问过的所有页面的日志。
>>> df.select('page').distinct().show(10)
+--------------------+
| page|
+--------------------+
| Cancel|
| Submit Downgrade|
| Thumbs Down|
| Home|
| Downgrade|
| Roll Advert|
| Logout|
| Save Settings|
|Cancellation Conf...|
| About|
+--------------------
根据执行的分析,仍然属于同一个会话的两个连续日志之间的最大时间似乎是一个小时。
# Explore the auth column
df.groupby('auth').count().show()+----------+------+
| auth| count|
+----------+------+
|Logged Out| 8249|
| Cancelled| 52|
| Guest| 97|
| Logged In|278102|
+----------+------+
我们还可以看到用户相当活跃,其中一个顶级用户总共列出了大约 8000 首歌曲。下面的图表表明,被搅动的用户通常来自加州和新泽西州,大多数付费用户正在离开音乐应用程序,而更多的男性比女性倾向于取消他们的订阅。加利福尼亚州和纽约州的人口往往更密集,因此可以预期更高的流失率和更高的整体参与度。从下图中很容易看出,所提供的 Sparkify 数据集是一个不平衡数据集的例子,因为与 174 相比,被搅动用户的份额仅略高于 20% (52)。
分类特征(last level 0-免费,1-付费;性别 0-男性,1-女性)
3.特征工程
首先,我们必须将原始数据集(每个日志一行)转换成具有用户级信息或统计数据的数据集(每个用户一行)。我们通过执行几个映射实现了这一点(例如,获取用户的性别、观察期的长度等。)和聚合步骤。
3.1 转换
对于 10 月 1 日之后注册的少数用户,注册时间与实际日志时间戳和活动类型不一致。因此,我们必须通过在页面列中查找提交注册日志来识别延迟注册。这一步并不简单,因为此类日志事件没有映射到任何用户 Id ,所以必须从会话 Id 信息中提取这些事件。
对于少数注册较晚的用户,观察开始时间被设置为他们第一次日志的时间戳,而对于所有其他用户,则使用默认的 10 月 1 日。
# Lag the page column
windowsession = Window.partitionBy('sessionId').orderBy('ts')
df = df.withColumn("lagged_page", lag(df.page).over(windowsession))windowuser = Window.partitionBy('userId').orderBy('ts').rangeBetween(Window.unboundedPreceding, Window.unboundedFollowing)# Identify users that registered after the start of observation, and infer the start date accordingly
df = df.withColumn("beforefirstlog", first(col('lagged_page')).over(windowuser))
df = df.withColumn("firstlogtime", first(col('ts')).over(windowuser))
df = df.withColumn("obsstart",
when(df.beforefirstlog == "Submit Registration", df.firstlogtime).otherwise(obs_start_default))# For each log compute the time from the beginning of observation...
df = df.withColumn("timefromstart", col('ts')-col("obsstart"))
# ...and time before the end of observation
df = df.withColumn("timebeforeend", col('obsend')-col('ts'))
与上面类似的还有在默认观察期结束前取消服务的用户,即所谓的被搅动用户。对于每个这样的用户,相应观察期的结束已经被设置为他/她的最后日志条目的时间戳,而对于所有其他用户,默认为 12 月 1 日。
3.2 特征工程—汇总统计数据
新创建的用户级数据集包括以下栏目:
lastlevel :用户最后的订阅级别,转换为二进制格式(1 —付费层,0 —免费层)
性别 : 性别,转换为二进制格式(1 —女性,
obsstart,obsend :用户特定观察期的开始和结束
endstate :用户在观察期内的最后一次交互
nact :用户在观察期内的总交互次数
nsongs、ntbup、ntbdown、nfriend、nplaylist、ndgrade、nupgrade、nhome、nadvert、nhelp、nsettings、nerror :播放的歌曲数、竖起大拇指、竖起大拇指向下拇指、添加的朋友、添加到播放列表的歌曲、降级、升级、主页访问
nact_recent,nact_oldest: 用户在观察窗最后一天和第一天 k 的活动,分别为 nsongs_recent,nsongs_oldest :观察窗最后一天和第一天 k 播放的歌曲,分别为
# Aggregation by userId
df_user = df.groupby(‘userId’)\
.agg(
# User-level features
first(when(col(‘lastlevel’) == ‘paid’, 1).otherwise(0)).
alias(‘lastlevel’),
first(when(col(‘gender’) == “F”, 1).otherwise(0)).alias(‘gender’),
first(col(‘obsstart’)).alias(‘obsstart’),
first(col(‘obsend’)).alias(‘obsend’),
first(col(‘endstate’)).alias(‘endstate’),
# Aggregated activity statistics
count(col(‘page’)).alias(‘nact’),
Fsum(when(col(‘page’) == “NextSong”, 1).otherwise(0)).alias(“nsongs”),
Fsum(when(col(‘page’) == “Thumbs Up”, 1).otherwise(0)).alias(“ntbup”),
Fsum(when(col(‘page’) == “Thumbs Down”, 1).otherwise(0)).alias(“ntbdown”),
Fsum(when(col(‘page’) == “Add Friend”, 1).otherwise(0)).alias(“nfriend”),
Fsum(when(col(‘page’) == “Add to Playlist”, 1).otherwise(0)).alias(“nplaylist”),
Fsum(when(col(‘page’) == “Submit Downgrade”, 1).otherwise(0)).alias(“ndgrade”),
Fsum(when(col(‘page’) == “Submit Upgrade”, 1).otherwise(0)).alias(“nugrade”),
Fsum(when(col(‘page’) == “Home”, 1).otherwise(0)).alias(“nhome”),
Fsum(when(col(‘page’) == “Roll Advert”, 1).otherwise(0)).alias(“nadvert”),
Fsum(when(col(‘page’) == “Help”, 1).otherwise(0)).alias(“nhelp”),
Fsum(when(col(‘page’) == “Settings”, 1).otherwise(0)).alias(“nsettings”),
Fsum(when(col(‘page’) == “Error”, 1).otherwise(0)).alias(“nerror”),
# Aggregated activity statistics in different periods
Fsum(when(col(‘timebeforeend’) < trend_est, 1).otherwise(0)).alias(“nact_recent”),
Fsum(when(col(‘timefromstart’) < trend_est, 1).otherwise(0)).alias(“nact_oldest”),
Fsum(when((col(‘page’) == “NextSong”) & (col(‘timebeforeend’) < trend_est), 1).otherwise(0)).alias(“nsongs_recent”),
Fsum(when((col(‘page’) == “NextSong”) & (col(‘timefromstart’) < trend_est), 1).otherwise(0)).alias(“nsongs_oldest”) )
汇总活动统计
4.探索性数据分析
完成特征工程步骤后,我们分析了构建特征之间的相关性。
# For visualization purposes we switch to pandas dataframes
df_user_pd = df_user.toPandas()# Calculate correlations between numerical features
cormat = df_user_pd[['nact_perh','nsongs_perh', 'nhome_perh', 'ntbup_perh','ntbdown_perh', 'nfriend_perh','nplaylist_perh',
'nadvert_perh', 'nerror_perh', 'upgradedowngrade', 'songratio', 'positiveratio','negativeratio',
'updownratio', 'trend_act', 'trend_songs', 'avgsessionitems', 'avgsessionlength','avgsongs']].corr()# Plot correlations
plt.rcParams['figure.figsize'] = (10,10)
plt.subplots_adjust(left=0.20, right=0.9, top=0.95, bottom=0.15)
sns.heatmap(cormat, cmap = "YlGnBu", square = True, vmin = -1, vmax = 1);
plt.title('Feature correlations');
plt.savefig('correlations.png')
上面的热图描述了变量 nact_perh 和 **nsongs_perh 之间的高度相关性。**这是意料之中的,因为听歌显然是最常见的用户活动。出于同样的原因,在 trend_act 和 trend_songs 之间有很高的相关性。在这两种情况下,我们决定简单地从所有进一步的分析中删除,只保留衡量最重要的交互的变量——播放歌曲。
为了进一步减少数据中的多重共线性,我们还决定在模型中不使用 nhome_perh 和 nplaylist_perh 。此外, avgsessionlength 与每个会话中的平均项目高度相关,因此也可以忽略。
4.1 与搅动变量的关系
从下面呈现的可视化中,可以得出以下观察结果:
- 平均而言,喝醉的用户每小时会播放更多的歌曲;
- 受刺激的用户每小时明显会拒绝更多的广告,平均不得不看更多的广告;
- 对于喝醉的用户,歌曲和积极互动相对于总活动的比率通常较低
- 被搅动的用户平均每次对话互动较少
- 免费订阅计划的用户流失率更高
- 男性用户的流失率略高
根据此分析,没有删除任何功能。
4.建模和评估
我们首先通过交叉验证执行网格搜索来测试几个参数组合的性能,所有这些都是基于从较小的 Sparkify 用户活动数据集获得的用户级数据。基于在交叉验证中获得的性能结果(通过 AUC 和 F1 分数衡量),我们确定了性能最好的模型实例,并在整个训练集上对它们进行了重新训练。
4.1 网格搜索方法
逻辑回归
- maxIter (最大迭代次数,默认= 100):【10,30】
- regParam (正则化参数,默认= 0.0):【0.0,0.1】
- elasticNetParam(混合参数—L2 罚 0,L1 罚 1,默认= 0.0):【0.0,0.5】
随机森林分类器
- (最大树深,默认= 5):【4,5,6,7】
- (树的数量,默认= 20):【20,40】****
梯度增强树分类器
- max depth(最大树深,默认= 5):【4,5】****
- (最大迭代次数,默认= 20):【20,100】
在定义的网格搜索对象中,每个参数组合的性能默认通过在四重交叉验证中获得的平均 AUC 分数(ROC 下的面积)来衡量。下文第 4.4 节简要解释了 AUC。**
***numeric_columns = [‘nsongs_perh’, ‘ntbup_perh’,’ntbdown_perh’, ‘nfriend_perh’,
‘nadvert_perh’, ‘nerror_perh’, ‘upgradedowngrade’, ‘songratio’, ‘positiveratio’,’negativeratio’,
‘updownratio’, ‘trend_songs’, ‘avgsessionitems’,’avgsongs’]# Combining multiple numerical features using VectorAssembler
numeric_assembler = VectorAssembler(inputCols = numeric_columns, outputCol = “numericvectorized”)# Standardizing numerical features
scaler = StandardScaler(inputCol = “numericvectorized”, outputCol = “numericscaled”, withStd = True, withMean = True)# Adding the two binary features
binary_columns = [‘lastlevel’, ‘gender’]
total_assembler = VectorAssembler(inputCols = binary_columns + [“numericscaled”], outputCol = “features”)# Defining three different pipelines with three different classifiers, all with default parameters
# Logistic regression
lr = LogisticRegression()
pipeline_lr = Pipeline(stages = [numeric_assembler, scaler, total_assembler, lr])# Random forest classifier
rf = RandomForestClassifier()
pipeline_rf = Pipeline(stages = [numeric_assembler, scaler, total_assembler, rf])# Gradient-boosted tree classifier
gb = GBTClassifier()
pipeline_gb = Pipeline(stages = [numeric_assembler, scaler, total_assembler, gb])***
4.2 绩效指标
F1 分数是这个问题的首选性能指标。输入的用户级数据集不平衡。音乐流媒体服务旨在识别大部分可能流失的用户(目标是高召回,但同时不希望无缘无故给予太多折扣(目标是高精度),即给予实际上对服务满意的用户(误报)——这可以帮助音乐流媒体业务防止财务损失。
***class F1score(Evaluator):
def __init__(self, predictionCol = “prediction”, labelCol=”label”):
self.predictionCol = predictionCol
self.labelCol = labelColdef _evaluate(self, dataset):
# Calculate F1 score
tp = dataset.where((dataset.label == 1) & (dataset.prediction == 1)).count()
fp = dataset.where((dataset.label == 0) & (dataset.prediction == 1)).count()
tn = dataset.where((dataset.label == 0) & (dataset.prediction == 0)).count()
fn = dataset.where((dataset.label == 1) & (dataset.prediction == 0)).count()
# Add epsilon to prevent division by zero
precision = tp / (tp + fp + 0.00001)
recall = tp / (tp + fn + 0.00001)
f1 = 2 * precision * recall / (precision + recall + 0.00001)
return f1def isLargerBetter(self):
return True***
在完整数据集上测试
表现最好的模型的 AUC 值为 0.981,F1 值为 0.855。
如上图所示,识别混乱用户最重要的特性是 nerror_perh ,它测量每小时向用户显示多少个错误页面。用户经历的错误越多,他/她对服务不满意的可能性就越大。
第二个和第三个最重要的特征也是如此, ntbdown_perh 和 nadvert_perh 分别测量每小时给出的拇指向下数和每小时看到的广告数。
最有趣的功能是 trend_songs 变量,它测量用户的歌曲收听活动趋势,这是第四个最重要的功能。
5.结论和改进
梯度增强的树分类器的 F1 值(精度和召回率)为 0.855,可以根据过去的用户活动和与音乐流媒体服务的交互来识别被搅动的用户,这可以帮助企业防止严重的财务损失。
一些改进是在完整的 Sparkify 数据集上对模型执行全面的网格搜索*。利用到目前为止已经被忽略的歌曲级特征,例如,根据在指定的观察期内收听的不同歌曲/艺术家来计算用户的收听多样性等。建立新特征,例如歌曲收听会话的平均长度、跳过或部分收听歌曲的比率等。***
关于这个项目的更多细节,请点击这里查看我的 Github 链接。
使用 DBSCAN 进行客户聚类
Rajendra Biswal 在 Unsplash 上的照片
在这篇博客中,我们将学习我最喜欢的聚类算法之一,那就是 DBSCAN 算法。我们将首先理解理论,然后我将借助一个非常简单的例子演示 DBSCAN 的工作原理。
DBSCAN 代表对有噪声的应用进行基于密度的空间聚类。
它在 1996 年左右被引入,并且由于其识别不同类型的聚类形状的有效聚类能力以及即使在存在噪声的情况下也能正确识别聚类的能力,在社区中获得了显著的普及。
让我们跳到一些有助于理解算法基本工作原理的关键术语。
有两个参数在算法中起着至关重要的作用。1)最小点数和 2)ε。
该算法通过单独处理每个数据点来工作,特别是对于每个点。它会构建一个以点为中心,半径等于ε的圆。
敏点:
最小点是距离该点ɛ距离内必须存在的点数。
Epsilon(ɛ) :
它是每个物体周围的距离或半径。
核心点:
考虑下图,画一个以查询点为圆心,以ɛ.为半径的圆如果圆内的点数大于 MinPoints(本例中为min points = 2),查询点将成为核心点。所以查询点有资格成为核心点。
(图片由作者提供)核心点演示:1。圆的半径= ɛ, 2。中心是考虑中的点
边界点:
如果查询点在ɛ内小于最小点,但在ɛ距离内具有核心点,则它是边界点。
噪声点:
既不是核心点也不是边界点的点称为噪声点。见下图。
(图片由作者提供)噪点演示
DBSCAN 将以这种方式处理每一个对象/点,并且最终它将获得所有点的分类,作为核心、边界或噪声点。
一旦获得了点的分类,下一步就是使用它们来构建聚类。DBSCAN 选取一个核心点,然后查看其ε半径圆内的点,并为这些点分配一个聚类标签。
所以关键的思想是给一个核心点的圆内的所有点以相同的标号。
将为不同的核心点运行多次迭代以分配聚类标签,请注意算法不会为那些在早期迭代中已经考虑的点分配新的聚类标签。
现在让我们看一下 DBSCAN 在客户数据上的工作示例。
问题陈述:
你拥有商场,想根据顾客过去的购买数据了解他们。这种分析将有助于营销团队采取一些策略来锁定目标客户。
数据:
您的数据由客户 ID、年龄、性别、年收入和支出分数等列组成。支出分数是您根据您定义的参数(如客户行为和购买数据)分配给客户的分数。
你可以从 Kaggle.com 得到这个数据集。
https://www . ka ggle . com/vjchoudhary 7/customer-segmentation-tutorial-in-python
Python 代码:
# DBSCAN Clustering
# Importing the libraries
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd# Importing the dataset
dataset = pd.read_csv(Mall_customers.csv')
data = dataset.iloc[:, [3, 4]].values
dataset.head()
该数据集中有 200 行
# visualizing the dataset
plt.scatter(data[:, 0], data[:, 1], s = 10, c = 'black')
(图片由作者提供)年收入和支出分数散点图
# Fitting DBSCAN to the dataset and predict the Cluster label
from sklearn.cluster import DBSCANdbscan = DBSCAN(eps=5.5, min_samples=4)
labels = dbscan.fit_predict(data)
np.unique(labels)
聚类标签。注意 Cluster = -1 表示噪声点
# Visualising the clusters
plt.scatter(data[labels == -1, 0], data[labels == -1, 1], s = 10, c = 'black')plt.scatter(data[labels == 0, 0], data[labels == 0, 1], s = 10, c = 'blue')
plt.scatter(data[labels == 1, 0], data[labels == 1, 1], s = 10, c = 'red')
plt.scatter(data[labels == 2, 0], data[labels == 2, 1], s = 10, c = 'green')
plt.scatter(data[labels == 3, 0], data[labels == 3, 1], s = 10, c = 'brown')
plt.scatter(data[labels == 4, 0], data[labels == 4, 1], s = 10, c = 'pink')
plt.scatter(data[labels == 5, 0], data[labels == 5, 1], s = 10, c = 'yellow')
plt.scatter(data[labels == 6, 0], data[labels == 6, 1], s = 10, c = 'silver')plt.xlabel('Annual Income')
plt.ylabel('Spending Score')
plt.show()
(图片由作者提供)黑点是噪点。
祝贺您,您的第一次客户聚类迭代已经完成。
结论:
由于聚类是无监督的学习,您需要分析每个聚类并有一个关于业务数据的定义,因为聚类总是受一些业务规则的指导。一旦您的集群接近业务规则,您的模型就有意义了。
我们还可以更改 eps 和 Min_sample 的值来调整模型,使聚类的形状更好。
感谢您的阅读。
参考:https://www . ka ggle . com/vjchoudhary 7/customer-segmentation-tutorial-in-python
竞争激烈的商业环境中的客户参与
赢得客户的忠诚度并降低流失率
了解你的客户——作者图片
这篇文章强调了中小型企业如何使用预测方法来预测客户行为。企业必须赢得客户的忠诚度,降低流失率。
目的是帮助线下商家(特别是中小企业)更好地了解他们的客户,降低他们的流失率,增加客户对品牌的忠诚度。通过忠诚度和接送选项等多种产品选项,商家可以识别他们的最佳客户,并知道如何最好地吸引他们。
这篇文章需要结合机器学习、数据分析和编程。将审查关于客户及其订单的信息,并确定任何不同的细分市场或趋势。
概述
客户忠诚度和保留率对任何业务的发展都至关重要。线下商家(特别是中小商店)努力为他们的顾客提供个性化服务。同样,顾客希望得到与网飞、亚马逊等主要品牌相同的参与度。有无数的选择供客户选择,这些企业必须发展,否则就有被甩在后面的风险。
小型企业面临的个性化挑战是什么?
中小型商家没有足够的资源来收集和解释客户数据。他们面临的一些挑战包括:
- 数据不足
- 无法获得可靠的数据
- 无法解释数据
客户为什么会离开(客户流失)?
客户流失是指客户不再光顾某个品牌或企业的趋势。各种原因可能会影响客户的决定,如果在同一时期有太多的客户放弃某个品牌,该业务就有彻底关闭的风险。以下是客户流失的常见原因;
- 企业主无法与大客户建立关系
- 企业主不能向客户提供任何形式的个性化服务/互动
- 企业主缺乏与大企业竞争的资源
为了实现这一点,店主了解顾客行为并根据购买行为和人口统计数据对顾客进行分类至关重要。这将有助于建立一种能带来客户忠诚度的关系。
案例分析
星巴克是一个利用顾客数据的优秀品牌范例。他们在一周的特定时间向特定的客户发送特定的报价。
- 他们向最有可能改变购买习惯的顾客提供最好的交易
- 他们只为客户提供投资回报率最高的交易
- 他们试图把不常光顾的顾客变成常客
假设和假设
我们应该思考“应该问什么类型的问题?数据应该怎么审核?”
我们的假设是:
- 忠诚的顾客可能会比新顾客花费更多
- 获得新客户的成本可能高于留住客户的成本
- 上午的交易量高于晚上
- 甜点和其他产品受到的影响更大
- 如果能提供个性化的体验,顾客更有可能做生意
- 奖励会让顾客再次光顾
我们将使用 Nugttah 数据集(忠诚度应用程序)的子集来测试我们的假设
风险和限制
这些是本项目中可能预期的潜在限制。但是,在数据分析过程中会适当考虑这些因素:
- 缺乏足够和准确的数据是这个项目的最大限制。分析的结果只会和用来执行分析的数据一样好。
- 另一个限制是商店不会记录他们交易的每一个方面。因此,根据分析需要,可能必须做出一些假设。
- 还存在用错误的模型分析数据的风险。
- 由于广泛的新冠肺炎疫情引起的顾客行为的改变可能导致错误的销售预测。
进场和流程
机器学习和数据科学项目的一个优秀方法如下:
- 加载和预处理数据
- 干净的数据
- 对数据进行数据可视化和探索性数据分析(EDA)
- 最后,分析所有模型,找出最有效的模型。
加载&数据的预处理
在此步骤中,我们将导入数据集并将其签出
在这里,数据集将被导入和检查。 read_csv 功能将用于读取工作表
% matplotlib inline将用于在笔记本中生成内联图。 sns.set() 将被添加到代码中,以将可视化样式修改为基本 Seaborn 样式:
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import numpy as np # linear algebra
import seaborn as sns # data visualization library
import matplotlib.pyplot as plt
import seaborn as sns
import datetime
import datetime as dt
from matplotlib.ticker import FuncFormatter
import matplotlib.ticker as ticker
import json# Figures inline and set visualization style
%matplotlib inline
sns.set()plt.rcParams[‘figure.dpi’] = 150
np.set_printoptions(suppress=True)
np.set_printoptions(precision=3)orders_df = pd.read_csv('data/orders.csv')
我们将使用 head() 函数来显示数据集的前 5 个观察值,并使用 shape 特性来找出数据帧中有多少列和多少行。
orders_df.head()
orders_df.head()
使用形状的数据集中的行和列的总数
orders_df.shape
orders_df.shape
数据由 104850 个观察值和 29 个特征组成。
此时,可以使用 describe() 返回平均值、计数、最小值和最大值、标准偏差和数据质量来确定汇总统计数据。
orders_df.describe()
orders_df.describe() —作者照片
清除数据
数据在被分析之前必须经过清理过程。一些数据清理过程包括:
- 缺少值
- 空值
- 重复值
- 噪声值
- 格式化数据
- 极端值
- 将数据分成单列和多列
- 转换数据类型
- 其他数据完整性问题
已加载下列数据文件:
- 订单数据
- 客户数据
- 商业数据
- 产品数据
- 奖励数据
- 最终 _ 单项 _ 订单
注意:原始数据最初在各种数据表中被打乱。必须使用 pd.merge 来收集和合并数据帧。
删除有重复数据的数据字段
# Redundant data exist in the dataset so we need to delete columns that are all same to reduce complexity
def is_unique(s):
a = s.to_numpy()
return (a[0] == a[1:]).all()for col in orders_df:
if is_unique(orders_df[col]):
del orders_df[col]
print("{} is deleted because it's redundant".format(col))
使每一笔采购成为一个单行
数据是从 MongoDB 中提取的,有些列是 JSON 数据格式,整个数据类型将显示为字符串。为了改变这一点,使用 json.loads 将其转换为 json,作为字典数据结构进行访问。
new_df = pd.DataFrame(columns=['_id', 'earned_points', 'visits_count',
'business', 'brand', 'branch',
'customer', 'cashier', 'reference', 'products', 'payment_amount',
'price', 'final_price', 'transaction_type', 'purchased_at',
'created_at', 'updated_at'])
new_list = []
def get_product(row):
prod_js = json.loads(row['products'])
for p in prod_js:
row['products'] = p
vals = list(row.to_numpy())
new_list.append(vals)
_ = orders_df.apply(lambda row: get_product(row), axis=1)keys = ['_id', 'earned_points', 'visits_count', 'business', 'brand', 'branch',
'customer', 'cashier', 'reference', 'products', 'payment_amount',
'price', 'final_price', 'transaction_type', 'purchased_at',
'created_at', 'updated_at']
new_df = pd.DataFrame(new_list, columns=keys)prod_id = new_df.apply(lambda row: row['products']['product']["$oid"], axis=1)
quantity = new_df.apply(lambda row: row['products']['quantity'], axis=1)
original_price = new_df.apply(lambda row: row['products']['original_price'], axis=1)
final_price = new_df.apply(lambda row: row['products']['final_price'], axis=1)# insert these product information to dataset
new_df.insert(1, 'prod_final_price', final_price)
new_df.insert(1, 'prod_original_price', original_price)
new_df.insert(1, 'prod_quantity', quantity)
new_df.insert(1, 'prod_id', prod_id)
del new_df['products']
del new_df['payment_amount']
del new_df['price']
检查空值
final_df = pd.read_csv('data/final_with_new_unified_names.csv')pd.isna(final_df).any()
如果有空值,要么删除它,要么替换它
final_df[‘city_text’] = final_df[‘city_text’].fillna(‘unknown’)
cities = [city.strip() for city in list(final_df[‘city_text’])]
final_df[‘city_text’] = cities
#final_df.to_csv(‘data/final_merged_single_item_orders.csv’,index=False)
# final_df.head()# fill business - brand - cashier with 'unknown'
final_df[['brand', 'business', 'cashier']] = final_df[['brand', 'business', 'cashier']].fillna(value="unknown")final_df.columns
更改任何没有意义的值
删除异常值的功能
# def drop_outliers(df, field_name):
# distance = 6.0 * (np.percentile(df[field_name], 75) - np.percentile(df[field_name], 25))
# df.drop(df[df[field_name] > distance + np.percentile(df[field_name], 75)].index, inplace=True)
# df.drop(df[df[field_name] < np.percentile(df[field_name], 25) - distance].index, inplace=True)def drop_outliers(df, field_name):
df_copy = df.copy()
Q1 = np.percentile(df[field_name].values,25)
Q2 = np.percentile(df[field_name].values,50)
Q3 = np.percentile(df[field_name].values,75)
IQR = Q3 - Q1
lower_fence = Q1 - 1.5 * (IQR)
upper_fence = Q3 + 1.5 * (IQR)print("lower_fence: ",lower_fence)
print("upper_fence: ",upper_fence)
df_copy = df_copy[~((df_copy[field_name] < lower_fence)|(df_copy[field_name] > upper_fence))]return df_copy
现在,商店数据、忠诚度数据、客户数据和其他可用数据集都有了很好的描述。
EDA &数据可视化
数据清理过程完成后,将对数据进行数据可视化和统计分析。通过单独的数据集来可视化分类数据是一个很好的做法。
现在将从分析的数据中得出模式和见解。
客户分析| 性别分布
customers_data = final_df.drop_duplicates(['customer'])# See the distribution of gender to recognize different distributions
print(customers_data.groupby('gender')['gender'].count())
chart = sns.countplot(x='gender', data=customers_data)
chart.set_xlabel("Gender")
chart.set_ylabel("Counts (Number of Orders)")
plt.title('Distribution of Gender')
这表明我们的数据集中有 1711 名女性和 5094 名男性
直方图是发现每个属性分布的好方法。
图表 1 —作者提供的照片
图表显示,男性顾客的比例略高于女性顾客。
为了获得更多信息,我们将根据注册年份对他们进行划分
plt.figure(figsize=(9, 5))
chart = sns.countplot(data = customers_data, x = 'year', hue = 'gender')
chart.set_title('Membership Since Year Distibution By Gender', fontsize = 15, y = 1.02)
chart.set_ylabel("Counts (Number of Orders)")
chart.set_xlabel("Year")
图表 2 —作者照片
客户分析| 年龄分析
在年龄分析中,我们首先检查的是异常值和,并发现了一些异常值,因此任何发现的异常值都从数据集中删除。
chart = sns.boxplot(x=final_df[‘cust_age’])
chart.set_xlabel(“Customer Age”)
图表 3 —作者照片
# drop outliers
final_df_without_outliers = drop_outliers(final_df, 'cust_age')chart = sns.boxplot(x=final_df_without_outliers.cust_age)
chart.set_xlabel("Customer Age")
图表 4 —作者照片
如下图所示,年龄范围在 20 到 40 岁之间。突出显示 describe() 调用结果的有效性。
feature = final_df_without_outliers['cust_age']
sns.distplot(feature)
plt.title('Normal Distribution of Customer Age', fontsize=15);
plt.xlabel('Customer Age',fontsize=15)
plt.show()
图表 5 —作者照片
平均年龄为 26 岁。这表明,与其他群体相比,年轻人更喜欢咖啡。较长的右尾表明这种分布是右偏的。
统计与客户年龄相关的客户
图表 6 —作者照片
消费习惯
该图描述了女性比男性花费更多,尽管我们的女性用户较少。
图表 7
【引用分析】| 按用户居住地排名前 20 的城市
图表 8 —作者照片
达曼的人咖啡消费量最高。
引用分析| 前 20 名活跃客户所在城市(最频繁最大订单数)
图表 9 —作者照片
可视化成对关系
活跃客户重要变量之间的相关矩阵图
相关性描述了两个变量之间的相似变化。当这种变化发生在同一个方向时,就被认为是正相关。如果变化发生在不同的方向,它被认为是负相关。
简单来说,相关矩阵就是每一对属性之间的相关性。它可以绘制在图表上,以辨别几个变量之间的相关程度。
了解这种计算是很有用的,因为如果数据中存在高度相关的输入变量,一些机器学习算法(如线性和逻辑回归)的性能可能会很差
相关矩阵
上面,矩阵可以被看作是对称的,即矩阵的左下和右上是相同的。此外,它在一个图中显示相同数据的 6 个不同视图。还可以推断出,最强的相关变量在最终价格(总支出)和积分之间。这是一种正相关关系,客户在该数据集中获得的最终价格越高,他们的盈利点就越高。然而,其他变量没有这种相同的相关性。然而,它们提供了有用的信息,并且基于相同的基本原理工作。
业务和产品分析
当审查平均价格的偏斜度时,可以推断出有少数昂贵的项目,但一般来说,价格属于一组。此外,负偏度意味着中位数低于平均值——这通常是由低价产品引起的。
十大热门类别
产品描述—作者照片
p_desc = product_desc.loc[product_desc['total_revenue'] >= sorted(product_desc.total_revenue, reverse=True)[10]]
sns.set_style('whitegrid')
plt.rcParams['figure.dpi'] = 250
chart = sns.scatterplot('counts', # Horizontal axis
'average_price', # Vertical axis
data=p_desc, # Data source
size = 10,
legend=False)chart.set_xlabel("Counts (Number of Orders)")
chart.set_ylabel("Avg. Price")
plt.title("Most Revenue Production 10 items")for line in range(0, p_desc.shape[0]):
chart.text(p_desc.counts[line]+300, p_desc.average_price[line],
p_desc.index[line], verticalalignment='top', horizontalalignment='left',
size=5, color='black', weight='normal')
十大产品—作者照片
订单分析
total_data = final_df.drop_duplicates(['_id'])import calendar
calendar.setfirstweekday(calendar.SATURDAY)
months_names = {i:calendar.month_name[i] for i in range(1,13)}
total_data['month'] = total_data['month'].map(months_names)
total_data.groupby(['month', 'year'])['customer'].count()
每月分析-作者照片
如图所示,2020 年 4 月记录了 264 笔销售,可以归类为 2018 年数据中的单一异常值。
订单分析|年度
plt.figure(figsize=(13,8))
chart = sns.countplot(x='year', hue='month', data=total_data)
chart.set_xlabel("Year")
chart.set_ylabel("Counts (Number of Orders)")
plt.title('Distribution of Orders over Years with Months')
图表 11 —作者照片
还有,2019 年 5 月销量大幅下滑。这种由疫情引起的下降从 2020 年初开始,到 2020 年 4 月急剧下降。
订单分析|月度
订单 _ 月刊—作者照片
订单分析|每周
订单 _ 周刊—作者照片
在沙特阿拉伯,周四和周五被视为周末。然而,星期五比星期四低得多。
新冠肺炎·疫情对顾客行为(POS)的改变:
在分析了过去 3 个月沙特阿拉伯所有食品和饮料的统计数据并将其与我们的数据集输出进行比较后,我们发现了以下结果
last_3_months_orders = total_data[(total_data['month'].isin(['January','February','March'])) & \
(total_data['year'].isin(['2020']))]last_3_months_orders_month_group = last_3_months_orders.groupby(['month', 'year']).agg({
'_id': ['count'],
'final_price':['sum']})last_3_months_orders_month_group.columns = ['number_of_transactions', 'total_sales']
last_3_months_orders_month_group = last_3_months_orders_month_group.reset_index()
last_3_months_orders_month_group.head()
过去 3 个月订单输出-作者提供的照片
SAMA 统计—作者照片
stats _ 统计 _ 输出—作者提供的照片
RFM 分析
在这里,我们将执行以下操作:
- 对于最近,计算当前日期和每个客户的最后订单日期之间的天数。
- 对于频率,计算每个客户的订单数量。
- 对于货币,计算每个客户的购买价格的总和。
# Filter on Dammam city (the most active city)
Dammam_orders_df = final_df[final_df['city_text']=='Dammam']# present date
PRESENT = dt.datetime.utcnow()
# print(PRESENT)
# print(Dammam_orders_df['created_at'].iloc[0])Dammam_orders_df['created_at'] = pd.DatetimeIndex(Dammam_orders_df['created_at']).tz_convert(None)# drop duplicates
Dammam_orders_df = Dammam_orders_df.drop_duplicates()# Transform to proper datetime
Dammam_orders_df['created_at'] = pd.to_datetime(Dammam_orders_df['created_at'])# Remove records with no CustomerID
Dammam_orders_df = Dammam_orders_df[~Dammam_orders_df['customer'].isna()]# Remove negative/0 prices
Dammam_orders_df = Dammam_orders_df[Dammam_orders_df['final_price'] > 0]Dammam_rfm = Dammam_orders_df.groupby('customer').agg({
'created_at': lambda date: (PRESENT - date.max()).days,
'_id': lambda num: len(num),
'final_price': lambda price: price.sum()})Dammam_rfm.columns = ['Recency (days)','Frequency (times)','Monetary (CLV)']
Dammam_rfm['Recency (days)'] = Dammam_rfm['Recency (days)'].astype(int)Dammam_rfm.head()
RFM _ 输出—作者提供的照片
RFM 分析| 计算 RFM 值的分位数
新近发生率、频率和金额最低的客户被视为顶级客户。
qcut() 是基于分位数的离散化函数。qcut 根据样本分位数对数据进行分类。例如,4 个分位数的 1000 个值将产生一个分类对象,指示每个客户的分位数成员资格。
Dammam_rfm['r_quartile'] = Dammam_rfm['Recency (days)'].apply(lambda x: r_score(x))
Dammam_rfm['f_quartile'] = Dammam_rfm['Frequency (times)'].apply(lambda x: fm_score(x, 'Frequency (times)'))
Dammam_rfm['m_quartile'] = Dammam_rfm['Monetary (CLV)'].apply(lambda x: fm_score(x, 'Monetary (CLV)'))Dammam_rfm.head()
qcut_output —作者提供的照片
RFM 分析| RFM 结果解释
将所有三个四分位数(r_quartile,f_quartile,m_quartile)组合在一列中,此排名将帮助您对客户进行细分。
Dammam_rfm['RFM_Score'] = Dammam_rfm.r_quartile.map(str) + Dammam_rfm.f_quartile.map(str) + Dammam_rfm.m_quartile.map(str)Dammam_rfm.head()
combine _ output 作者提供的照片
【RFM 分析】| 筛选出顶级/最佳客户
top5 —作者照片
top5_output —作者提供的照片
装置类型
图表 13 —作者照片
Nuggtah 似乎更受 IOS 用户的欢迎,尽管它分布在沙特阿拉伯
解决方案
机器学习主要建立在进行预测和分类的概念上。然而,要理解的最重要的事情是,它不是关于一个方法有多奇特,而是它与测试数据的兼容性。
预测时,只选择预测结果所需的最重要的属性是至关重要的。此外,必须选择适当的 ML 方法。关于客户讨论,建议以下建议:
推荐和客户细分 of 忠诚度活动,如奖金奖励、积分充值、免费等级升级,以及专门优化的奖励计划,以提高收入和投资回报率。因此,对客户进行了细分,以发现最佳群体。
细分对于大规模个性化至关重要。此外,该系统应该分析与其他用户没有直接关联但在搜索和购买模式中共享相同标准的志同道合的用户的数据(借助于 ML 算法来提供更多细节并显示用户简档之间的相似性)。
销售预测可以用来预测未来的销售量。通过进一步分析,可以推导出单店销售额、品类销售额、产品销售额等数据。
终身价值预测可用于根据细分客户和行为模式识别最合适的客户。
流失预测可用于确定特定时期内的流失量。
最后一步:设计预测模型
- 估计系数
- 设计模型
- 进行预测并存储结果。
- 分析模型
- 初始化随机数发生器
继续将随机数发生器初始化为固定值(7)。
这一步对于保证这个模型的结果可以再次被复制是至关重要的。它还确保训练神经网络模型的随机过程可以被复制。
结论
有了正确的数据,个性化是可以实现的。今天,中小型企业不需要一个大型的分析师或营销人员团队来预测客户行为和趋势。
细分可以帮助企业识别正确的客户,并在正确的时间向他们发送正确的信息。
资源
名称:作为商业模式的推荐系统
- 角色:进行订单/交易的用户。
- 描述:提供建议并捕捉用户之间的关系。
- 利用显性和隐性特征/模式以及用户之间的关系来构建一个好的推荐系统的最佳方法之一是多方面的协作过滤模型,本演示对此进行了清晰的描述。
- 演示文稿包含所有需要的信息(问题、愿景、技术、使用的数据集和评估)。
名称:采购预测模型
- 角色:店主。
- 描述:预测用户下一个订单中的下一个购买产品
- 数据集:关于“如何赢得数据科学竞赛”的 Kaggle 期末项目
名称:未来销售预测模型
- 角色:店主。
- 描述:预测特定商店的未来销售额。
- 数据集:一个叫做( Instacart 市场篮子分析)的 Kaggle 竞赛
走向数据科学篇