使用线程在 C 中从头开始使用 MapReduce:Map
Source: Pixabay
Hadoop 的 MapReduce 不仅仅是一个框架,还是一种解决问题的哲学。
借鉴函数式编程,MapReduce 团队意识到很多不同的问题可以分成两种常见的操作: map ,和 reduce 。
映射和缩减步骤都可以并行完成。
这意味着只要你能以那种特定的方式框定你的问题,就会有一个解决方案,它可以很容易地并行运行。这通常会大大提升性能。
这听起来不错,并行运行通常是一件好事,尤其是在大规模工作时。但是,坐在后面的一些人可能会想,什么是地图和减少?
什么是 MapReduce?
为了理解 MapReduce 框架,我们需要了解它的两个基本操作: Map 和 Reduce 。
它们都是高阶函数:也就是说,它们是可以把其他函数作为自变量的函数。
具体来说,当您需要将 A 类型的某个元素序列转换为 B 类型的一个结果或一系列结果时,您将:
- 将你所有的输入映射到不同的域:这意味着你将用一个选择的函数来转换它们,并应用到每个元素。
- 根据某种标准对映射的元素进行分组,通常是一个分组关键字。
- 用其他函数减少每个组上的映射元素。这个函数需要两个参数并返回一个相同类型的参数,连续运行一个累加器和集合中每个值之间的运算。它应该是交换和关联**,因为并行执行不会保证操作的任何顺序。**
为了更清楚地说明这一点,让我们看一个例子。
MapReduce 解决方案示例
假设你在一家电子商务公司工作,他们给你一个如下形式的日志文件:
John Surname bought 2 apples
Alice Challice bought 3 bananas
John Surname bought 5 pineapples
然后他们让你告诉他们每个顾客买了多少水果。
在这种情况下,在解析该文件并将其转换为实际格式(如 CSV)后,您可以轻松地遍历每一行,并在字典上的每个名称下添加购买的水果的数量。
你甚至可以用一点 Bash 脚本来解决它,或者将 CSV 加载到熊猫数据帧上并获得一些统计数据。
然而,如果日志文件有一万亿行长,bash 脚本并不能真正减少它。尤其是如果你不是永生的。
您需要并行运行这个。让我提出一个 MapReduce-y 的方法:
- 通过解析每个字符串,将每一行映射成一对形式为<的名称、数量>。
- 按名称分组。
- 通过对数量求和来减少。
如果您熟悉 SQL 和关系数据库,您可能会想到类似的解决方案。该查询类似于
select user, sum(bought_fruits)
from fruit_transactions group by user;
为什么 MapReduce 缩放
注意映射器不需要看到整个文件,只需要看到一些行。另一方面,减速器,只需要具有相同名称的线(属于同一组的线)。
你可以在同一台计算机上用许多不同的线程来做这件事,然后把结果连接起来。
或者,您可以让许多不同的进程运行地图作业,并将它们的输出提供给另一个运行归约作业的集合。
如果日志足够大,您甚至可以在许多不同的计算机上运行 Mapper 和 Reducer 进程(比如说,在一个集群上),然后最终在某个湖中加入它们的结果。
这种解决方案在 ETL 作业和其他数据密集型应用程序中非常常见,但我不会深入研究应用程序。
如果你想了解更多关于这种可扩展解决方案的知识,我建议你去看看这本关于大规模设计应用的 O’Reilly 书籍。
用 C 语言编程 MapReduce
现在你已经了解了什么是 MapReduce,以及为什么 MapReduce 会扩展,让我们开门见山吧。
对于这第一篇文章,我们将编写两个不同的实现 Map 函数的程序。
其中一个将是单线程,介绍几个概念并展示一个简单的解决方案。另一个将使用 pthread 库来制作一个真正的多线程,和快得多的版本的 Map 。最后,我们将对二者进行比较,并运行一些基准测试。
像往常一样,所有的代码都可以在这个 C GitHub 项目上获得。
Map 在 C 中的单线程实现
首先,我们先记住地图是做什么的。
Map 函数接收一个 序列 和一个 函数 ,并返回 将该函数应用于序列中每个元素 的结果。
因为这是 C 语言,表示一个序列可以非常直接:我们可以使用一个指针指向我们映射的任何类型!
然而,有一个问题。C 是静态类型的,我们希望我们的 Map 函数尽可能的通用。我们希望它能够映射任何类型的元素序列(假设它们都共享一个类型)。我们不要在这里失去理智,孩子们。
我们如何解决这个问题?这个问题可能有几种不同的解决方案。我选择了看起来最简单的一个,但也可以随意加入其他想法。
我们将使用void*
的序列,并将所有东西都转换成这种类型。这意味着每个元素都将被表示为一个指向随机内存地址的指针,而无需指定类型(或大小)。
我们将相信我们在所有这些序列元素上调用的任何函数都知道如何在使用它们之前将它们转换为正确的类型。我们有效地将那个问题委托出去了。
我们需要解决的一个小问题是序列长度。指向 void 的指针不携带序列有多少元素的信息。它只知道从哪里开始,不知道从哪里结束。
我们将通过传递序列长度作为第二个参数来解决这个问题。知道了这一点,我们的 Map 函数变得非常简单。
您可以看到,该函数接收一个void**
来表示它将映射的序列,以及一个void* (*f)(void*)
函数,该函数将某种泛型类型的元素转换为另一种(或相同的)元素。
之后,我们可以在任何序列上使用我们的映射函数。我们只需要事先做一些笨拙的包装和指针运算。
这里有一个例子,使用一个函数,对于质数返回 1,对于其他的返回 0。
正如所料,结果指针指向一个整数序列:1 对应于质数,0 对应于合数。
现在我们已经完成了单线程 Map 函数,让我们看看如何在 c 语言中并行运行这个函数。
C 语言中的多线程映射函数
(如果您想使用流程和分叉添加一个基准,请随时提出拉取请求!)
为了在 C 语言中使用并行执行,我们可以求助于进程或线程。
对于这个项目,我们将使用线程,因为它们更轻量级,在我看来,它们的 API 对于这类教程来说更直观一些。
如何在 C 语言中使用线程
C 中的 threads API 非常直观,即使一开始有点晦涩。
- 一个指向
pthread_t
的指针:实际的线程。 - 一个配置
struct
。在这种情况下,我们将使用NULL
作为默认配置。 - 我们希望线程运行的函数。与进程不同,线程只会运行一个函数,直到它返回,而不是继续执行任意代码。这个函数必须接受一个
void*
参数,并返回另一个void*
值。 - 前述函数的输入。必须投给
void*
。
要使用它们,我们必须使用#include <pthread.h>
。手册页很好地解释了他们的界面。然而,对于本教程,我们将使用的是pthread_create
函数。
pthread_create
需要四个参数:
在调用pthread_create
之后,一个并行执行线程将开始运行给定的函数。
一旦我们为我们希望映射的每个块调用了pthread_create
,我们将不得不对它们中的每一个调用pthread_join
,这使得父(原始)线程等待直到它旋转的所有线程完成运行。
否则,程序会在映射完成之前结束。
现在,让我们尽情欣赏一些代码。
在 C 语言中使用 pthread 实现并行 MapReduce
为了用 C 语言编写 MapReduce 的 Map 函数,我们要做的第一件事是定义一个struct
来存储它的通用输入和输出,以及我们将要映射的函数。
由于并行执行需要某种方式的切片和分区,我们也将把那个逻辑存储在这个结构中,使用两个不同的索引作为我们切片的开始和结束。
接下来,我们将编写实际执行映射的函数:它将从start
到end
循环输入,将映射函数应用于每个输入的结果存储在输出指针中。
最后,节目的主角,启动线程的函数,给每个线程分配一个map_argument
,等待所有的地图作业运行,最后返回结果。
注意这个函数如何允许我们选择我们想要多少线程,并相应地对数据进行分区。它还处理 pthreads 的创建和加入。
最后,我们在 main 中调用该函数的方式如下所示:
concurrent_map( (void**) numbers, twice, N, NTHREADS)
其中NTHREADS
是我们想要的线程数,N
是numbers
拥有的元素数。
现在代码完成了,让我们运行一些基准测试!这真的会更快吗?所有这些包装代码会使事情变得更慢吗?让我们来了解一下!
C 语言中的映射,基准:单线程与多线程
为了衡量使用并行 Map 带来的性能提升,我测试了一些单线程算法与多线程算法的对比。
第一个基准:slow_twice
对于我的第一个测试,我使用了 slow_twice 函数,它只是将每个数字乘以 2。
你可能会奇怪,“为什么叫慢?”。答案很简单:我们将每个数字翻倍 1000 次。
这使得操作更慢,所以我们可以测量时差,而不必使用太多的数字,初始化需要太长时间。它还让我们对许多内存写入的情况进行基准测试。
因为每个数字的执行时间是恒定的,所以非并行算法的时间随着输入大小几乎成线性增长。
然后我用 2、4 和 8 个线程运行它。我的笔记本电脑有 4 个内核,我发现这也是使用线程的最佳数量。对于其他一些算法,我发现使用我的核心数量的倍数是最佳的,但事实并非如此。
基准测试结果
我将每个基准测试运行 10 次,取平均值,以防万一。
结果如下:
对于这两个测试案例,使用 4 个线程比单线程实现大约快三倍。这证明了使用并行 Map 比使用普通单线程版本要快得多。
添加 4 个以上的线程也是有代价的,可能是由于初始化和上下文切换的开销。
第二个基准:is_prime
对于这个基准测试,我编写了一个朴素的质数测试函数:它简单地遍历所有小于输入的数,如果任何数被整除,则返回 1,否则返回 0。
注意,这个函数对每个元素取 O(n ),而不是 O(1 ),所以数据的一些分区(有序的)会比其他的慢很多。我想知道这会如何影响运行时间?
在这种情况下,并行算法再次击败了单线程算法。没有什么大的意外。然而,这一次当使用超过 4 个线程时有了一个改进!
我认为这是因为在对我们的输入进行分区时,将它分成更小的块会使最慢的分区花费更少的时间,从而使我们的瓶颈变小。
结论
我从这个实验中获得了很多乐趣。
挑选多少线程使用比仅仅“使用相同数量的内核”要困难得多,而且很大程度上依赖于我们的输入,即使是非常愚蠢的算法。
这可能有助于我们理解为什么优化集群的配置对于一个大型应用程序来说是一项如此艰巨的任务。
将来,我可能会添加一个并行的 reduce 实现来完成这个小框架。
其他一些可能会很有趣并且我可能会在未来运行的基准测试是 C 语言中的MapvsPython 列表理解,以及 C 语言 vs SIMD 汇编。
如果你想提升数据科学家的水平,可以看看我的 最佳机器学习书籍 清单和我的 迎头痛击教程 。
记住,你可以以任何你喜欢的方式使用这段代码,或者运行你自己的实验,如果你这样做了,请不要忘记在评论中让我知道你的结果!
如果你想对我说什么或问什么,请随时在 Twitter 、 Medium 或 dev.to 上联系我!
原载于 2019 年 10 月 19 日http://www . data stuff . tech。
马拉松围兜识别和认可
使用深度学习和图像处理来识别马拉松围兜上的数字。
Me after finishing the Mumbai Marathon 2019, and, of course, the bib number recognition (using AWS Rekognition API)
开始
我最近参加了一场马拉松。几天后,我收到了一封电子邮件,里面有一个链接,我可以在那里查看并下载我的比赛日照片。我需要把我的号码放在网页上,它就会调出所有我的照片。这让我思考这是如何成为可能的!
对于那些不熟悉跑步项目的人来说,围兜是一张贴有电子标签的纸。这个标签用来记录运动员在马拉松过程中的准确时间。围兜也有一个独特的围兜号码,用大字体印刷,还有跑步者的名字和其他一些文字。(见照片供参考)
我开始思考标记照片的可能方法。一个显而易见的方法是手动标记——有一组人看着照片,阅读围嘴号码,并用这些围嘴号码标记照片。这是一项繁琐的任务,假设每场马拉松会有超过 5000 张照片。另一种方法是使用计算机视觉。
计算机视觉
我的理念是,任何需要人类看着一幅图像,然后以一种近乎机械的方式跟着它做动作的任务,都可以而且应该用计算机视觉来实现自动化。我们拥有最先进的算法来解决这个问题,并拥有强大的计算能力来实现这些解决方案。为什么不利用它呢?这个项目就是这样开始的。
我开始探索不同的计算机视觉技术,这些技术可以用来给照片贴上号码标签。我将简要描述我能想到的每一种方法:
- EAST text detector+tessera CT text recognition:想法是首先检测图像中的文本区域,然后识别文本以识别 bib 号码。我使用 EAST 文本检测器模型来识别图像中带有文本的区域,然后将这些区域传递给 Tesseract 模型来识别文本。
优点:实施简单快捷。
缺点:不太准确,会检测到照片中的大量其他文本。 - 图像处理使用 OpenCV 识别围脖区域,然后进一步处理围脖区域提取数字。提取的数字可以传递给预先训练的 ML 模型以识别号码。
优点:对计算能力要求不高。
缺点:很难概括不同的围兜设计 - 分割使用深度学习模型,如 MaskRCNN,从图像中分割出 bib。在 bib 上应用图像处理来提取数字。将提取的数字传递给预先训练的 CNN 网络以识别数字。
优点:分割围脖和识别数字的准确性高。
缺点:功耗大,因此速度较慢,难以针对不同的围兜设计推广图像处理方法 - 物体检测使用深度学习模型直接识别 bib 号码区域而不是整个 bib。与以前的方法相比,这可以为我们节省许多图像处理步骤。应用图像处理来提取数字,并将它们传递给预先训练的 CNN 网络来识别它们。
优点:与前面的方法类似,深度学习模型可以非常准确。
缺点:计算要求高 - **人脸识别:**这种方法在识别照片中的跑步者方面具有很大的优势,即使围兜被遮挡,这种情况确实经常发生。有多种方法来实现面部识别,我不会进入它的细节,因为它本身可以是一本书。我会马上提到我能想到的几种方法。一种方法是根据注册时提供的身份证照片匹配人脸(可能使用暹罗网络和三联丢失)。另一种方式可以是上述两种方法的混合。我们可以基于人脸识别将每个跑步者的照片聚集在一起,然后尝试从其中一张照片上读取围兜号码,在那里围兜清晰可见。
- 来自谷歌(Vision)、AWS (Rekognition)或微软 Azure 的基于云的 API:使用这些 API 来检测和识别图像中的文本,然后过滤掉 bib 号码(可能使用所有 bib 号码的数据库)。
项目
首先,我尝试了第一种方法,以了解一个经过一般训练的模型将如何处理这个问题。不出所料,它的表现不是很好。无法保证正确识别 bib 编号,而且在图像中检测到大量假阳性文本。我将在以后的文章中写更多关于它的内容。后来,我开始尝试(第三)种方法,包括使用实例分段。这是这个项目的核心。
当我第一次着手解决这个问题时,它似乎不是一个大任务。只有当我深入到这个项目中,细微之处开始浮出水面时,我才意识到这个问题是多么具有挑战性。只是给你一个想法,唯一可能的方法,我可以得到正确阅读的照片上的数字在顶部是使用 AWS Rekognition API。虽然对人类来说阅读这个围嘴可能很容易,但训练计算机阅读这个数字就没那么简单了。我用自定义图像处理管道得到的最好结果是“1044”而不是“21044”。这是有原因的,比如为不同的围兜设计和配色方案创造一个通用的启发。我将在以后的博客中讨论。
我提出的解决方案可能还不是最好的。我意识到对解决方案的改进是无止境的。这个解决方案的端到端执行是本系列文章的主题。我选择这个项目的主要原因是尝试构建计算机视觉项目的各个方面——收集数据集、注释图像、实现用于分割的深度学习模型、图像处理、创建用于 OCR 的 CNN 模型、为给定的数据集定制训练、将这些不同的部分缝合在一起等。
我会把上面提到的每一个部分都写出来,并分享代码。您可以自由地获取代码并进行改进,或者将其用于您自己的应用程序。从这个项目中学到了很多,我希望社区能从中受益。
请随时留下任何建议/评论/批评。我会尽可能快。
这是这个系列的下一部分:
[## 创建数据集—使用 Selenium 和 BeautifulSoup 抓取马拉松图片
创建用于围兜识别的马拉松图像数据集(第 1 部分)
medium.com](https://medium.com/@kapilvarshney/gathering-data-scraping-marathon-images-using-selenium-and-beautifulsoup-fe52d9cc9023)
三月版:理解如此多的数据
8 篇必读文章
What are the most pressing problems in data science? Take our survey
在 21 世纪,我们有幸在计算、存储和数据方面取得了快速进步。数据可能是新的石油,但如果没有正确的工具、方法和基础设施,它就像坐在油井上无所事事一样毫无用处。随着越来越多的企业试图从其海量数据存储中获取丰富的信息,大数据不再被大肆宣传,而是一种必需品。机器学习和深度学习模型变得越来越好,越来越快,产出比以前更多,当时它们只是在小型玩具数据集的研究论文中展示。
机器学习与云计算相结合可以成为您组织的主力,帮助您解决现实世界的问题,而不必担心购买、设置和维护基础架构。除此之外,它对于利用大规模集群上的深度学习在 GPU 上进行数字运算非常方便。
随着包括 Auto-ML 和元学习在内的机器学习领域的快速发展,我强烈认为,要构建挖掘机器学习最大潜力的端到端项目,数据科学家应该建立一套多样化的技能,不仅要构建模型,还要能够处理大规模数据集,扩展他们的解决方案,还要关注他们的解决方案最终如何在现实世界中部署和使用的端到端工程方面。我希望我们收集的这些不同的文章能让你对这些方面有所了解。
— 迪潘然(DJ)萨卡尔,TDS 编辑
从 0 到百万用户的规模机器学习
由朱利安·西蒙 — 11 分钟读完
我认为大多数机器学习(ML)模型都是在白板或餐巾纸上构思出来的,诞生于笔记本电脑上。当这些羽翼未丰的生物开始咿咿呀呀地说出它们的第一个预言时,我们充满了自豪,并对它们未来的能力寄予厚望。
如何使用 Dask 数据帧在 Python 中运行并行数据分析
由卢西亚诺·斯特里卡 — 5 分钟阅读
有时候,你打开一个包含 Python 熊猫的大型数据集,试图获取一些指标,整个事情就僵住了。
为什么以及如何使用大数据熊猫
由阿德蒙德·李 — 5 分钟阅读
在现实世界中,数据不可避免地是杂乱的。在清理、转换、操作和分析数据方面,Pandas 是真正的游戏规则改变者。简单来说,熊猫帮着收拾残局。
利用深度学习的最新进展预测股价走势
通过鲍里斯 B — 34 分钟读取
在这本笔记本中,我将创建一个预测股票价格变动的完整过程。坚持下去,我们会取得一些不错的成果。为此,我们将使用一种生成式对抗网络(GAN ),其中 LSTM 是一种递归神经网络,作为生成器,而卷积神经网络 CNN 作为鉴别器。
学习足够有用的 Docker
由杰夫·黑尔 — 7 分钟读完
容器对于提高软件开发和数据科学中的安全性、可再现性和可伸缩性非常有帮助。它们的崛起是当今科技领域最重要的趋势之一。
创业公司数据科学项目流程
通过 Shay Palachy — 20 分钟阅读
最近,我正在咨询的一家初创公司( BigPanda )要求我就数据科学项目的结构和流程发表意见,这让我思考是什么让它们独一无二。
让你的神经网络说“我不知道”——使用 Pyro 和 PyTorch 的贝叶斯神经网络
由 Paras Chopra — 17 分钟阅读
构建图像分类器已经成为新的“hello world”。还记得你第一次遇到 Python 的那一天,你的*打印“hello world”*感觉很神奇吗?
不,机器学习不仅仅是美化了的统计学
由乔·戴维森 — 10 分钟阅读
机器学习真的没什么好兴奋的,或者它只是古老的统计技术的修正,这种观点越来越普遍;问题是这不是真的。
我们也感谢最近加入我们的所有伟大的新作家,尤金·西多林,埃申·乔利,约翰·科,保罗·穆尼,兰迪·奥,普拉奇·贾恩,格雷格·萨默维尔,贾维尔·伊达米,亚历克斯·克鲁格,萨姆 路易斯·加文,特恩·波林,吉勒斯·范德维尔,柳文欢·拉松,弗洛里安·林德斯塔德以及许多其他人。 我们邀请你看看他们的简介,看看他们的工作。
三月疯狂-分析视频以检测球员、球队和尝试投篮的人
用数据做很酷的事情!
介绍
这是三月疯狂月!这是一个多么激动人心的赛季。作为数据科学家,让我们利用这个机会对篮球片段做一些分析。通过使用深度学习和 opencv,我们可以从视频剪辑中提取有趣的见解。见下面的例子 gif 的游戏 b/w UCF 和杜克大学,我们可以确定所有的球员+裁判,标签球员到球队的球衣颜色为基础。在博客的后面,我将展示我们如何识别哪个球员试图投篮。所有这些都可以实时完成。
Detecting players and teams
你可以在我的 Github repo 上找到代码
那我们开始吧。
检测玩家
我已经使用了一个预训练的检测模型,如更快的 RCNN 来检测球员。很容易从 Tensorflow 对象检测 API 下载一个在 COCO 数据集上训练的更快的 RCNN 并测试它。API 将图像的每一帧作为输入,并在 80 个不同的类别中进行检测。如果你是 Tensorflow 对象检测的新手,想了解更多,请查看这个博客。该模型在检测人方面做得相当好,但是由于人群中有大量的人,所以在该视频中有许多检测。参见下面的样品检测。我抑制了太大的检测,以更干净地分割出球员。您还可以在 API 中使用分数阈值来过滤掉低可信度检测。查看 Github 上的代码,了解如何抑制分数低和多次错误检测的盒子的提示。
Detection output from Pretrained Tensorflow model
探测队
现在有趣的部分来了。我们如何检测哪些球员是 UCF 对杜克大学?我们可以使用 OpenCV 来做到这一点。
如果您不熟悉 OpenCV,请参阅下面的教程:
OpenCV 允许我们识别特定颜色的面具,我们可以用它来识别白人和黑人球员。主要步骤是:
- 将图像从 BGR 转换到 HSV 色彩空间。
- 在 HSV 空间中,指定白色和黑色的颜色范围。这需要一点实验,你可以在笔记本中想象不同阈值的影响。
- 使用 OpenCV 遮蔽(着色)阈值范围内的像素。
- OpenCV Bitwise_and 将不在蒙版中的任何像素涂成黑色
白色请参见下面的输出。它们被伪装成“粉红色”,背景中的其他东西都是黑色的
Detecting white colour pixels
为了识别每个玩家的团队,我们从 tensorflow 对象检测中提取边界框,并计算边界框中非黑色像素的百分比,以确定该玩家的团队。
整体代码工作得很好。然而,这是一个识别黑白球衣球员的硬编码逻辑。通过使用聚类来查找相似的玩家,可以使其更通用
检测姿势和拍摄者
OpenPose 是一个实时多人姿势检测库。它可以检测图像中的人,并输出每个人的主要关节的关键点——每个人最多可以有 25 个关键点。代码是开源的。你必须按照自述文件中的建议安装这里。安装后,你可以通过它运行图像,并获得场景中所有球员的关键点,如下所示。
Open pose output
那么我们如何识别试图投篮的球员呢?
我们可以寻找手腕关键点在头顶的玩家。意味着举起双手。这可能表明像上面的场景一样准备射击,或者可能是防御性的。此外,球的坐标以及手腕关键点的坐标可用于识别哪个玩家举手将球靠近他们。
结论
深度学习通过链接不同的想法,使得进行真正酷的分析成为可能。有许多开源代码和预训练的模型,您可以使用这些数据开始学习。以上只是起点。其他可以做的很酷的事情包括:
- 使用 OCR 读取比赛分数,让您的系统了解哪个队赢了
- 跟踪球以预测何时射门得分
- 跟踪玩家以获得每个玩家的统计数据
- 检测事件,如灌篮,三分篮球等。
希望你拉代码自己试试。
我有自己的深度学习咨询公司,喜欢研究有趣的问题。我已经帮助许多初创公司部署了基于人工智能的创新解决方案。请到 http://deeplearninganalytics.org/的来看看我们吧。
你也可以在https://medium.com/@priya.dwivedi看到我的其他作品
如果你有一个我们可以合作的项目,请通过我的网站或 info@deeplearninganalytics.org 联系我
参考文献
- Tensorflow 对象检测 API
- 关于使用 OpenCV 检测颜色的好教程
马里奥对瓦里奥——第二轮:CNN 在 PyTorch 和 Google Colab
在 PyTorch 中快速构建卷积神经网络对视频游戏截图进行分类
很长一段时间我都在玩 Google Colab(是的,免费访问 GPU…)。我认为这是一个非常棒的倡议,它使个人电脑上没有 GPU 的人能够玩深度学习和训练模型,否则他们将无法训练。基本上,我们有 12 个小时的时间来玩,然后虚拟机就死了。但是,我们当然可以开始一个新的会议,并且有办法继续以前会议的工作。
在这篇文章中,我想介绍我之前的作品的延伸。然而这一次,我将使用 PyTorch 构建一个 CNN,并在 Google Colab 上对其进行训练。最终,我希望取得比以前更好的成绩!开始吧:)
1。建立谷歌实验室
Medium 上已经有一些关于如何开始使用 Google Colab、如何启用 GPU 等的好文章。我想展示几个有用的命令来检查我们实际上在做什么样的硬件/软件:
我们看到我们正在开发 Tesla K80,并且已经安装了 Cuda 9.2。这样事情就简单多了!
找到如何有效处理存储在 Google Drive 上的大型数据集并不容易。许多课程和帖子使用 PyTorch 或其他库中的内置数据集。但是一开始,我发现使用我自己的一组图像有点棘手。所以我做了以下事情:
- 将数据集(带有训练/测试文件夹的压缩文件)上传到 Google Drive。
这可以通过驱动程序 UI 轻松完成。最初的目录树如下所示:
mario_vs_wario/
training_set/
mario/
mario_1.jpg
mario_2.jpg
...
wario/
wario_1.jpg
wario_2.jpg
...
test_set/
mario/
mario_1.jpg
mario_2.jpg
...
wario/
wario_1.jpg
wario_2.jpg
...
- 安装 Google Drive
使用 Colab 时,重要的是将文件存储在 Colab 目录中,而不是安装在 Google Drive 上。下面的单元格包含连接到 Google Drive 并安装该驱动器的代码,这样我们就可以访问存储在那里的所有文件。然而,用从 Google Drive 加载的数据训练神经网络(即使启用了 GPU)在大多数情况下会比在 CPU 上本地训练它慢得多。这是由于在 Colab 和 Drive 目录之间复制所有数据,这非常慢。
- 将 zip 文件从我的 Google Drive(通过可共享的链接)移动到在 Colab 环境中创建的目录中,然后解压缩。
为了解决上述问题,我分别压缩了训练集和测试集,并通过使用gdown
和 Google Drive 的链接(当您在 Drive 的 UI 中单击 download shareable link)下载文件。然后,我将包含图像的文件夹解压到指定的目录。在最后一步,我删除了一个剩余的目录。
2.加载数据
在这一部分,我加载并预处理数据(图像)。我将一步一步地描述这个过程:
- 首先,我定义了一些参数和我想在图像上执行的转换(调整到 128x128,转换成张量和归一化)。这也是我可以进行图像放大(随机裁剪,剪切,旋转等)的步骤。).然而,由于这个特殊的问题是关于视频游戏图像的分类,我认为应用这些转换没有意义,因为图像将不再类似于原始截图。但是,如果您正在构建一个猫/狗分类器,并且没有真正大的数据集(即使您有),这将是应用转换的地方。
- 我为训练/测试数据指定目录,并应用所选择的转换。
- 我从训练集中随机选择了一个索引子集来使用它们进行验证。我还创建了从给定索引(不是整个数据集)中采样图像的
SubsetRandomSampler
。 - 我通过组合数据集和采样器来创建
DataLoader
。在 GPU 上训练的情况下,我使用pin_memory = True
(推荐设置)。对于test_loader
,我也混洗数据集,否则,它将首先从一个类中取出所有观察值,然后从第二个类中取出所有观察值,而不进行任何混洗。在测试集的情况下,这实际上无关紧要。但是知道这个功能是很好的。
在下面的代码中,我检查了 10 张随机选择的图片。由于DataLoaders
作为迭代器工作,我首先使用iter()
,然后使用next()
来获得随机选择的图像及其标签(来自第一批)。
3.CNN 架构
我提出了两种定义神经网络结构的方法。第一种方法是构建一个继承自nn.Module
的类。第二个更类似于 Keras,我们创建了一系列的层。这里没有对错,完全看个人喜好。
在这两种方法中,我使用了相同的架构,所以在培训之前应该只使用一种。
3.1.课堂教学方法
我定义了一个继承自nn.Module
的类,它与super().__init__()
结合创建了一个跟踪神经网络架构的类,并提供了各种方法和属性。需要注意的是,该类必须继承自nn.Module
。
该类必须包含两个方法:__init__
和forward
。
我会对每一个必需的方法做更多的解释:
__init__
-用于定义类的属性,并在初始化时填充指定的值。一个规则是总是调用super()
方法来初始化父类。除此之外,我们可以定义所有的层,这些层具有一些要优化的参数(要调整的权重)。我们不需要定义激活函数,比如这里的relu
,因为给定相同的输入,它们将总是返回相同的输出。定义的层的顺序并不重要,因为这些纯粹是定义,而不是指定层如何连接的架构。forward
-在这种方法中,我们定义了层之间的连接。我们指定它们连接的顺序,并最终返回网络的输出。另外,变量不一定要被称为x
,重要的是它以正确的顺序通过各层。
3.2.顺序方法
对于那些使用过 Keras 的人来说,Sequential
方法可能很熟悉。我创建了一个OrderedDict
,按照执行的顺序指定了每一层。使用OrderedDict
的原因是我可以给这些层起一个有意义的名字。如果不这样做,它们的名字将是整数。
开始时,我定义了一个Flatten
类,它基本上将矩阵重新整形为一个长向量,就像 CNN 通常做的那样。OrderedDict
放在nn.Sequential
中,它定义了我们的模型。
4.损失函数和优化器
第一步是将模型转移到 Cuda,以防它将在 GPU 上训练。然后,我将二进制分类问题的损失函数和优化器指定为学习率为 0.01 的随机梯度下降。
5.训练网络
网上已经有很多关于训练神经网络所需步骤的资料。我将只概述这些步骤:
- 正向通过网络(如
forward()
方法中所述) - 根据网络输出计算损耗
- 用
loss.backward()
反向通过网络计算梯度 - 通过使用优化器来更新权重
还有其他一些事情值得一提:
optimizer.zero_grad()
-当使用相同的参数进行多次反向传递时,梯度在累积。这就是为什么我们需要在每次向前传递时将梯度归零。- 训练时,我们可能会使用辍学来防止过度适应。然而,对于预测/验证,我们想要使用整个网络,因此我们需要通过使用
model.eval()
将丢失概率更改为 0(关闭它)。要返回训练模式,我们使用model.train()
。 torch.no_grad()
-关闭验证渐变,节省内存和计算
为了有一个可重用的框架来训练 CNN,我将逻辑封装在一个函数中。我假设网络将在训练和验证损失的情况下被训练。当然,它可以进一步参数化,只有当参数不是None
时,才可以考虑验证集。不过对于这款笔记本的情况来说,相信这已经足够了。
那么训练模型就归结为:
我检查了显示培训/估价损失随时代演变的图表。我们的目标不仅是减少培训损失,也是减少验证损失。如果训练损失继续减少,而验证损失增加,我们将观察到过度拟合-模型将不能很好地概括训练期间没有看到的数据。在这种情况下,我们看到模型的损失在第 7 个历元之后(或者更早,取决于偏好)没有显著减少。
鉴于此,我将从第 7 纪元开始加载模型。通过保存所有的中间模型,我能够看到测试集的性能会是什么样子(以防万一,我想比较)。
6.评估测试集的结果
在这一部分,我在测试集上评估网络的结果,即网络在训练期间没有见过的那个。我编写了一个与验证脚本类似的脚本,不同之处在于我存储的用于评估的指标数量。
准确率 99%,甜!让我们来看一些更详细的统计数据:
- 99.2%的召回率——这意味着从数据集中的所有 Wario 截图来看,该模型正确预测了其中的 99.2%。
- 99.3%的精确度——这意味着在所有的 Wario 预测中,99.3%实际上都是 Wario。
- 99.25%的 F1 分数—没有明确的解释,因为 F1 分数是精确度和召回率的加权平均值。在类分布不均匀的情况下,F1 比精度更有用。就像在这种情况下,测试集中有相同数量的 Mario/Wario 类,准确度= F1 分数。
总的来说,该网络在图像分类方面做得非常出色。2000 张照片中只有 15 张分类错误。为了获得更多的洞察力,我们将在下面考察其中的一些。
我不得不说,网络在这些图片上遇到麻烦并不奇怪。有些明显是来自游戏的过渡帧(地图和关卡之间或者屏幕之间加载屏幕)。没有办法从中推断出正确的游戏。其余的是地图或来自 Wario(第三张图片)的特定屏幕。这些游戏的地图非常相似,就像从等轴视图中看到的角色一样。
我不得不说,我对这个网络的表现和 PyTorch 总体上非常满意。它提供了很多可能性,并且非常具有 pythonic 风格。要了解更多关于 PyTorch 的基础知识,我会推荐你去 Udacity 的免费“PyTorch 深度学习简介”MOOC,你可以在这里找到。
如果你对这篇文章有任何反馈,请在评论中告诉我。一如既往,整个笔记本可以在我的 GitHub repo 上找到。
马克·吐温曾写道…或者是爱伦坡?
BiblioEater is all set to identify the writer
作者归属的 StanfordNLP 和 Keras
假设你追随某个作家,吞食了她/他的任何一部作品。如果给你提供一本全新的书,你检查几段就能认出作者的风格吗?
很可能你会。这篇文章的目的是探索一台机器完全做到这一点的可能性。我们将分析两位著名作家作品的一些文学特征,然后训练一个神经网络将新的文本分配给一位或另一位作家。
我们的工具将是刚刚推出的 StanfordNLP Python 包,我们最近写了一个简短介绍以及直观的深度学习 API Keras,在我们的例子中是 Tensorflow。我们将由此产生的模型戏称为食书者。
Edgar Allan Poe and Mark Twain
作者和他们的书
我们选出了两位十九世纪的著名作家。一边是非常有趣的马克·吐温,另一边是才华横溢的讲故事者埃德加·爱伦·坡。两个原因导致我们选择他们。我们需要足够老的作品进入公共领域。我们还希望两位作者使用大致相同的语言变体,在他们的例子中是美国英语。
请记住,StanfordNLP 包括能够处理 53 种不同人类语言的模型,因此您可以使用相同的方法将雨果与左拉或塞万提斯与奎维多进行比较。
我们将从一个稍微更具挑战性的角度来处理这个问题,而不是像机器学习中的规范那样,获取每个作者的一批文本,并在训练和验证桶中分割数据集。我们将用两本小说进行训练,用两本不同的作品进行验证。即:
- 亚瑟·戈登·皮姆的叙述将被用来训练埃德加·爱伦·坡式的读书人。
- 《汤姆·索亚历险记》将被用来为马克·吐温做同样的事情。
- Eureka 将用于验证 Poe 模型。爱伦·坡主要写短篇小说,但我们选择了长篇小说,尽管不太长。
- 而哈克贝利·费恩历险记是吐温为了验证而选的作品。
这种方法有许多障碍:书籍的主题不同,作者可能随着时间的推移而演变,等等。
此外,在所选的吐温小说中,作者使用了当地方言。虽然这可能有助于对文本进行分类,但从解析的角度来看,这将是一个挑战。
另一方面,《尤利卡》不是一部虚构的作品,爱伦坡在写作的时候,他的个性可能已经跨越了天才和疯子之间的界限。
然而,我们打赌,每个作者的作品中都有某种文学特征。
环境
我们在一个 Ubuntu 盒子里用 Python 3.6 工作过。GitHub 中提供了用于生成本文中讨论的结果的完整代码(细节在底部)。
数据集足够小,可以在没有 GPU 的工作站上运行神经网络训练,尽管有一个 GPU 总是有帮助的。下载英文版的 StanfordNLP 模型可能是更耗时的任务。
Polishing the text — but not a lot!
文本准备
只要遵守许可条款,古腾堡计划就有数以千计的免费电子书——大多数情况下,你必须避免分发任何修改过的副本。我们用网络浏览器下载了上面提到的四篇文章。
之后,我们用标准的文本编辑器删除了不需要分析的文本部分。这使得我们无法按照古登堡计划的许可分发它们,所以如果你想运行代码,你需要直接从古登堡计划下载电子书。
我们还在吐温的两部作品中发现了许多弯曲的双引号和下划线。我们在文本编辑器中处理它们。对于我们的目的,这种级别的数据清理就足够了。然而,让我们记住,一流的数据准备+平均算法往往胜过平均数据清理+一流的算法。
从语法上分析
正如我们在上一篇文章中所解释的,我们将应用 StanfordNLP 模型来解析这些书中的每一个句子。这将为每个单词分配的词性和的特征。例如:
他拿起画笔,平静地开始工作
从汤姆·索亚,变成了
He (PRON - Case=Nom|Gender=Masc|Number=Sing|Person=3|PronType=Prs) took (VERB - Mood=Ind|Tense=Past|VerbForm=Fin) up (ADP - _) his (PRON - Gender=Masc|Number=Sing|Person=3|Poss=Yes|PronType=Prs) brush (NOUN - Number=Sing) and (CCONJ - _) went (VERB - Mood=Ind|Tense=Past|VerbForm=Fin) tranquilly (ADV - _) to (PART - _) work (VERB - VerbForm=Inf)
看看不同句子中语言的使用,我们期望捕捉到作者的部分写作风格。例如,这项研究的一个目标曾经写道:
当你抓住一个形容词时,杀死它。(马克·吐温)
所以你不会期望在他的文章中过度使用形容词。事实上,我们会看到坡在形容词的使用上超过了吐温——尽管不是很多。
一些文体特征
我们接受这样的假设,每个作家都有他/她自己的文学风格,这样的个人足迹应该是显而易见的,即使看一些粗糙的特征,就像下面的那些。有一门名为文体学的成熟学科专门研究这些现象。
句子长度
你喜欢简短直接的句子,还是喜欢制造复杂冗长的句子?(哦不,四个形容词,对不起马克!)在所附的图表中,很明显吐温比爱伦·坡更喜欢使用短句——这并不奇怪。
以这样或那样的方式,我们的作者属性算法应该捕捉这个特征,因为它看起来很有鉴别性。
词类的使用
在 StanfordNLP 的帮助下,我们为两本书的每个句子都指定了词类。作者中动词、形容词和名词的比例是多少?数据会有什么不同吗?
Usage of parts-of-speech in both works
Poe 倾向于更重视介词,如中的或到*,以及从属连词,如 if 或 while。这暗示了一种更复杂的风格。吐温的书比坡的书包括更多的标点符号,这与组成更短的句子是一致的。*
这个简单的分析没有显示出词性之间的顺序关系。正如我们将要展示的,这是一份适合阅读者的好工作。
其他功能
以上只是我们可以在两个文本中分析的特征的几个例子。鉴于 StanfordNLP 所提供的,我们可以看看作品的其他特点:
- 单词特征可用于建立动词的使用方式(哪种时态和模式是首选?)以及类似的名词(单数、复数)。
- 词汇的使用。例如,单词 vessel 在《亚瑟·戈登·皮姆》中出现了 96 次,而在《汤姆·索亚》中一次也没有出现。同样,吐温写了 156 次男孩,这个词没有出现在 Pym 中。这似乎是一个棘手的话题,因为词汇可能过多地与情节联系在一起,就像这里的情况,而不是与写作风格联系在一起。
- 我们甚至还没有触及 StanfordNLP 中的依赖解析器。它分析句子,识别主要术语或句法中心。那些词头(动词、名词等)的选择。)可能与每个作家的选择有关。
文本结构分类与作者归属
让我们直接进入分类。给定这些书中的一篇短文,找出它最可能的作者。
战略
有许多关于二进制或多标签文本分类的深入文章。这就是为什么我们在这里做一些稍微不同的事情。我们不是用实际的文本来训练我们的网络,而是只输入它的语法结构。
所以对于 BiblioEater 来说,这两句话看起来是一样的:
红狮子追逐瞪羚
我们年轻的研究员找到了解决办法
因为两者都符合 DET-ADJ-名词-动词-DET-名词的顺序。让我们记住,我们不会使用单词特征,例如限定词是被定义的冠词()还是所有格代词( Our )。
通过这样做,我们忽略了大量的信息,我们的模型可能会因为保留它们而变得更好。然而,这篇文章的目的是要表明,即使信息有限,我们也能捕捉到作者的部分风格。请继续阅读。
一键编码
如果我们是在处理文字,比如加利纳·奥莱尼克 这里所解释的 word2vec 将是一个自然的选择。但是我们只处理 17 种不同的词性。因此,我们可以很容易地提供一个独热编码,将它们中的每一个表示为长度为 17 的向量,所有元素都设置为零,只有一个元素对应于语音的顺序位置。视觉上,
红狮子追赶瞪羚。(DET-ADJ-名词-动词-DET-名词-标点)
转换为
段落,而不是句子
在确定了我们将如何处理文本之后,我们现在来解决我们向模型提供多少文本以确定作者的问题。一次一个句子似乎太少了,因为两个作者产生相同句法结构的几率有时似乎很高。
我们决定输入 3 个连续的句子作为网络的输入。我们称之为一个段落,尽管从技术上来说,大部分时间它并不是一个段落。
Tom Sawyer 是两部作品中最长的一部,我们对所有生成的段落进行采样,因此 Tom 和 Pym 在训练过程中使用相同数量的段落来代表他们。
输出显然是二进制的:要么是 Poe,要么是 Twain 是作者。
Ready to eat books
阅读者——神经网络
在定义神经网络的拓扑结构时,你可以随心所欲地发挥创造力。但是考虑几件事情:
- 我们讨论的是输入层的几千个段落。不完全是大数据,是吗?我们必须小心,不要设计太重的网。
- 整个练习归结为一个简化的文本分类问题,就像 Yoon描述的用词性代替原始单词的问题。
因此,我们选择了相对简单的设计,甚至比 Yoon 论文中的设计还要简单:
BiblioEater topology
它由一系列基本的卷积层和最大池层对组成,最后由两个密集层组成,以获得二进制输出。你也可以找几个脱层作为防止过度合身的安全网。该代码保存了 BiblioEater 类中的所有细节。
驱动训练过程的各种参数,例如过滤器数量、步幅等。可以在代码中找到。它们被隔离为常量,以便于调整和比较结果。
当 StanfordNLP 使用 PyTorch 进行机器学习时,我们自然会选择 PyTorch 而不是 Keras。然而,我们用 Keras,因为它通常更容易阅读。
结果
利用代码中保存的建模值, BiblioEater 有 18,954 个参数,不是很多,因此即使是一个无 GPU 的工作站也可以轻松处理训练工作。我们输入了来自两本书的偶数个带标签的段落,得到了 0.9313 的准确度。不算太坏,我们已经提到了所有的警告。
但是当我们强迫食书者吞下《尤利卡》和《哈克贝利·费恩历险记》时,布丁的真正证据就来了。请记住,这些书中的任何文本都没有包含在培训中。还要记住,每次网络上出现的都是 3 句话的段落,这并不多。这是我们得到的混淆矩阵。
因此,阅读者在 90%以上的时间里都对两本它以前没看过的书。这是考虑到我们决定不包括但在文本中存在的所有特征。如果我们给它输入 4 句话的段落,我们可以为每个作者增加大约 0.015 的准确率。
调整参数,我们得到了类似的结果,但结果有些不稳定,这表明数据集有点小。也许我们应该选择狄更斯的所有作品!
我能运行代码吗?
你当然可以。从 github 下载,请先通读 README.md 文件。
结论
作家在作品中打上自己风格印记并不新鲜。我们在这篇文章中试图传达的是他如何仅仅基于三个连续句子的词性来处理文本归属。添加我们忽略的功能后,我们的结果应该会显著改善。高质量开源软件(Keras、Tensorflow 和最近的 StanfordNLP)的出现使这成为可能。
降价单元格— Jupyter 笔记本
我不知道如何开始。这将是我第一个与编程相关的故事。今天,我将努力缩小 Jupyter 笔记本中的降价单元格描述。我想我不必在 Jupyter 笔记本上花费太多的文字。根据 Jupyter.org的说法,“ Jupyter 笔记本是一个开源的网络应用程序,允许你创建和分享包含实时代码、方程式、可视化和叙述文本的文档”。Markdown 是一种流行的标记语言,被用作数据科学家和分析师的标准。它既不是 HTML Markdown 的超集,也不是 HTML 的替代品,甚至接近它。它的语法非常小,只对应于 HTML 标签的一个非常小的子集。通常在 Jupyter Notebook 中,单元格类型默认为 code。如果我们想要输入任何文本来描述不被认为是代码的任何东西,那么我们需要使用单元格类型作为 Markdown。
要将 code 单元格转换为 markdown 单元格,我们可以使用快捷键 m 从切换工具栏或单元格菜单中更改单元格类型。现在让我们简要讨论 Jupyter Notebook 支持的 markdown 语法。
Convert cell type from Code to Markdown
标题:有六种类型的标题,每种标题都以散列符号**(#)**
开头,后跟一个空格,其中最大的标题使用单个散列,最小的标题使用六个散列符号。
可选地,标题可以以标记标签开始,即,从标题 1 到标题 6,具有以下语法。如果我们单击工具栏上的 run 按钮,输出文本将如下图所示。如果有人想描述几个问题,用几个标题类型来突出问题的重要性是很容易的。
Output for Headings and Headings 2 markdown cell.
**样式和变化(粗体、斜体文本、背景)😗*markdown 中的标题和其他文本都可以是粗体、斜体或常规字体样式,有几种颜色和不同的背景颜色。我们也可以改变字体 像时间新罗马或 Calibri。
**枚举列表:**通过 markdown 可以形成有序列表、项目符号列表或嵌套的条目列表。一个编号列表是由 HTML 的<ol>
标签完成的,也可以应用在 Markdown 中。也可以应用几种其他方法来制作这种有序或无序的列表。
1\. First
1\. First one
2\. First two
2\. Main list <br>
a. Sub-list <br>
b. sub list
3\. Main list
* main list
* A
* B
* C
+ A
+ B
+ C
- A
- B
- C
**内部&外部链接(超链接)😗*以 http 或 https 开头的 Markdown 文本自动呈现超链接。外部和内部超链接都可以通过以下方式添加。Markdown 中的内部链接以<a>
标签开始,该标签具有由属性‘id’定义的唯一 id。
<a> [https://www.google.com](https://www.google.com) </a> <br>
<a href="[http://www.google.com](http://www.google.com)">Google</a><br>[http://typora.io](http://typora.io)<br>
[Google]([https://www.google.com](https://www.google.com))<br><a id =integer> </a>
[Arman](#integer)
**表格:**表格可以在 markdown 单元格中通过管道符号**(|)**
和破折号**(-)**
组成,用来标记列和行。分号(:)
或 das **(-)**
符号用于对齐各列。
**图像:**您可以从工具栏中插入图像,方法是从编辑菜单中选择“插入图像”,并从您的计算机中选择目标文件夹中的图像。我们还可以通过以下命令在 markdown 单元格上添加图像。
<img src="Name.png" width="240" height="240" align="right"/>
Inserting image using the Edit menu.
**方程式:**在 markdown 单元格中提到方程式时,数学符号包含在 ’ $symbol here$
’ 中。内联表达式可以通过用$
包围 latex 代码来添加,而在它们自己的行上的表达式用$$
包围。
$e^{i\pi} + 1 = 0$
$$e^x=\sum_{i=0}^\infty \frac{1}{i!}x^i$$
$$e^{i\pi} + 1 = 0$$<br>
$\sqrt{k}$
**Github 风味降价:使用反引号或左引号 **(
)` 键三次,我们可以获得与降价单元格输出相同的代码风味甲酸盐。
```python
A = "Python syntax highlighting"
print(A)
for i in range(0,10):
print(A)
输出:

GitHub flavored Markdown.
**块引号、换行符和水平线:**换行符使用 2 个空格或该代码进行手动换行符:`<br>`。可以通过使用符号`'>'`或包含预期文本分块列表的`<blockquote>text for blockquote</blockquote>`获得块引号。我们还可以使用`(___) or (***) or (---)`三个连字符或标记标签添加一条水平线`<hr>.`文本或标题可以通过`<center>text<center>`集中。
Professor says
It’s good forThis is good> 1 Blockquotes2 Blockquotes
3 Blockquotes
4 Blockquotes
8 BlockquotesAsterisks
Underscores
hipen— #
This is a centered header
```我已经在 GitHub 库中添加了 markdown 单元格的所有命令。你可以在这里查看。
[## arman-Null/Markdown-Cells-Jupyter-笔记本
此时您不能执行该操作。您已使用另一个标签页或窗口登录。您已在另一个选项卡中注销,或者…
github.com](https://github.com/Arman-Null/Markdown-Cells—Jupyter-Notebook.git)
我感谢丹尼尔·布克、乔纳森·惠、何塞·马西亚尔·波尔蒂利亚和威尔·科尔森以及其他一些人,他们总是激励我在这个神奇的平台上写作。我会试着继续写具体的问题。
参考链接:
- https://www . data camp . com/community/tutorials/markdown-in-jupyter-notebook
- https://www . tutorialspoint . com/jupyter/jupyter _ notebook _ markdown _ cells . htm
- https://jupyter-Notebook . readthedocs . io/en/stable/examples/Notebook/Working % 20 with % 20 Markdown % 20 cells . html # Markdown-basics
- https://medium . com/IBM-data-science-experience/markdown-for-jupyter-notebooks-cheat sheet-386 c05 aeebed
基于购物篮分析的关联规则挖掘
无监督学习&数据库中的知识发现
https://sarit-maitra.medium.com/membership
M 市场购物篮分析 (MB)是一种关联分析,是一种流行的数据挖掘技术。这是一种数据中的知识发现(KDD) 这种技术可以应用于各种工作领域。在这里,我将使用一个零售交易数据,并展示如何向企业提供信息来捕获买方的购买行为。这也可以是决策支持系统的一部分。
各种形式的数据挖掘可应用于这类数据集,例如分类、关联、预测、聚类、异常值分析等。在这里,我将重点介绍关联规则挖掘技术,它发现隐藏在数据集中的有趣关系。如果你有兴趣,可以访问我早前的文章***(1);(2);(3)。***
我们很多人都熟悉 UCI 机器学习数据库发布的以下数据集。MB 分析中使用的数据是事务性数据。在这里,数据具有 MB 分析的所有基本成分。然而,从我的经验来看,交易数据很少或者我们可以说,从来不会以这种格式出现。因此,由于交易数据的复杂性,数据清理和数据读取是 MB 分析的主要活动。
让我们看看数据框的尺寸以及唯一的发票号和客户号
如果我们比较*【发票号】和【描述】*,我们可以看到一些行没有发票号。
data[‘invoiceno’].value_counts()
让我们删除没有发票号的行,并删除信用交易(发票号包含 C 的交易)。
现在,我们需要将商品合并为每行 1 个交易,每个商品 1 个热编码。让我们检查数据框中的国家名称。
让我们看看澳大利亚的销售情况,并将结果与法国的销售情况进行比较。在我们深入分析之前,让我们先了解一下关联规则。
关联规则挖掘
在这种情况下,规则生成是挖掘频繁模式的首要任务。关联规则是形式为 x → y,的蕴涵表达式,其中 x 和 y 是不相交的项目集。为了评估这样一个关联规则的【兴趣】,已经开发了不同的度量标准。我将使用 支持、 和 提升 度量。
韵律学
假设商品 x 正在被客户购买,那么商品 y 在同一个交易 ID 下被客户挑选的几率也就被找出来了。衡量联想有 3 种方式: 支持度、 信心度、 升力度。
Support {freq (x,y) / n,range: [0,1]} 给出包含项目 x 和 y 的交易的分数。它告诉我们经常购买的项目或经常购买的项目组合,我们可以筛选出频率较低的项目。
Confidence {freq(x,y) / freq (x),range: [0,1]} 根据 x 出现的次数,告诉我们 x 项和 y 项一起出现的频率。
Lift { support/support(x) support(y),range: [0,inf]}* 表示一个规则对 x 和 y 的随机出现的强度,它解释了一个规则的强度,Lift 越大就是强度越大。
Apriori 算法
这是一种数据挖掘方法,也是关联规则的基础。Apriori 算法使用*【频繁项集】生成关联规则。它考虑到了一个【频繁项集】的子集也一定是一个【频繁项集】。【频繁项集】* >的值超过一个阈值(即支持度)。
数据中有相当多的零,但我们还需要确保任何正值都被转换为 1,任何小于 0 的值都被设置为 0。因此,让我们对数据应用一种热编码,并删除邮资列;我们不打算探讨邮资。
既然数据的结构是正确的,我们可以生成支持度至少为 7%的频繁项集(选择这个数字是为了让我得到足够多有用的例子)。
数据挖掘中的信心和支持
- 为了选择感兴趣的规则,我们可以使用最著名的约束,这些约束是置信度和支持度的最小阈值。
支持度是项集在数据集中出现频率的指示。信心是规则被发现为正确的频率的指示”
# generate the rules with their corresponding support, confidence
# and lift
frequent_itemsets = apriori(basket_sets, min_support=0.07, use_colnames=True)
print (frequent_itemsets)rules = association_rules(frequent_itemsets, metric=”lift”, min_threshold=1)
rules.head()
如果我们打印关联的数量,我们会看到找到了 800 条关联规则。
support=rules.as_matrix(columns=[‘support’])
confidence=rules.as_matrix(columns=[‘confidence’])
下面的支持度和置信度散点图显示了数据集的关联规则(前 10 个规则)。
让我们看看这告诉了我们什么。例如,我们可以看到有相当多的规则具有很高的值,这意味着它比给定的交易和产品组合的数量所预期的更频繁地出现。我们还可以看到几个 置信度 也很高的地方。在这方面,领域专长有很大优势。我就在这里找一个大 抬 (6)高 信心 (0.8)。
查看规则,我们可以发现," RED retro spot CAKE STAND "和 “36 支铅笔管 RED RETROSPOT” 是一起购买的,而 “4 个传统旋转陀螺”、“闹钟 BAKELIKE GREEN “和” RED DINER WALL CLOCK” 是一起购买的,购买方式高于整体概率。在这一点上,我们可能想看看有多少机会可以利用一种产品的受欢迎程度来推动另一种产品的销售。
我们可以看到,虽然有关联规则存在,但是相比于 385 个数字的“36 支铅笔管红色逆行点”*而言,只有 73 个数字的“*红色逆行点蛋糕摊”所以也许企业必须采取某种策略来使两者不相上下。
同样有趣的是,看看不同购买国家的组合是如何变化的。让我们来看看在法国有哪些流行的组合。
所以,这样我们就可以比较,准备一份分析报告。根据我们定义的关联规则,我们发现一些产品之间存在显著的相关性。这里应用的先验算法具有一定的阈值。我们也可以试验不同阈值。**越大抬抬**意味着联想越有趣。具有高 支持度 的关联规则是潜在有趣的规则。类似地,具有高 置信度的规则 也会是有趣的规则。
购物篮分析——多支持频繁项目集挖掘
对缺省 MSApriori 算法的改进。
Pic credit: Upgrad
简介:
从交易数据库(购物篮)中生成关联规则的问题是许多零售商感兴趣的。关联规则的形式是𝑋 → 𝑌,其中 x,y 是 I(所有产品(或项目)的集合)和𝑋 ∩ 𝑌 = ∅.的子集
可以将上述关联规则的含义理解为,如果顾客购买了集合 X 中的物品,那么他可能购买集合 y 中的物品。关联规则的一些众所周知的例子可以是{ Milk }–> {Bread } 、{ Milk }–> { Bread,Eggs} 、{ Bread,Butter }–> { Jam }和幽默的{ Diapers }–> { Beer }。请注意,关联规则是不可交换的,即𝑋 → 𝑌不等于𝑌 → 𝑋.
为给定的交易数据库(市场篮)寻找关联规则的问题定义如下:
a.给定一个大小为 z 的事务数据库,其中有 n 个不同的项目和一个输入支持度和置信度,找出满足给定支持度和置信度约束的所有规则𝑋 → 𝑌。
b.支持度是一个阈值,该阈值将确定 X 中的项目是否足够频繁以被考虑用于关联规则生成。更具体地说,如果{X}。count / Z >= support,那么 X 被认为是一个频繁项集。
c.置信度是决定购买 y 的条件概率的阈值。更具体地说,如果{𝑋⋃𝑌}.count / {X}。计数> =置信度,则𝑋 → 𝑌被视为有效的关联规则。
假设零售商销售了“N”种不同的商品/产品,那么计数和生成关联规则的强力方法的复杂度为 O(N!).
Apriori 算法是对蛮力算法的改进,它基于这样的观察:只有当 X 的所有真子集都是频繁项目集时,项目集 X 才是频繁项目集。这将减少为生成所有可能的关联规则而需要探索的状态/规则的数量。Apriori 算法根据这一原理工作,分两步执行
a.在大小为 1,2,3…k 的事务数据库中查找所有频繁项集。
b.从频繁项目集 2,3,4…k 中生成所有有效的关联规则。
c.如果不能再生成 k + 1 大小的频繁项集,则停止。
Apriori 算法至少需要对事务数据库进行“k”次扫描。
MSApriori:
MSApriori 代表多重支持 Apriori,它是 Apriori 算法在现实生活场景中的一个更受约束的定义。Apriori 算法只考虑交易中所有项目的一个支持值,而不管项目是频繁销售的项目(如杂货)还是不太频繁销售的项目(高价项目,如家居装饰)。因此,要为经常/很少售出的商品建立称重方案,每件商品都有单独的支撑。MSApriori 算法的问题定义修改如下:
a.给定一个交易数据库和每个项目和置信度的不同最小输入支持(MIS ),找出满足给定支持和置信度约束的所有规则𝑋 → 𝑌。
b.支持度是一个阈值,该阈值将确定 X 中的项目是否足够频繁以被考虑用于关联规则生成。由于 MSApriori 中对不同的项目有不同的支持,任何满足其集合中项目的最小 MIS 的项目集合都被认为是频繁的。更具体地说,如果
a.|X| = 1,
- {X}。count/n > = miss(X),那么 X 被认为是一个频繁项集。
b.|X| >= 1,
- {X}。count / n >= MIN (MIS(X1),MIS(X2),MIS(X3)…MIS(Xk));Xi ∈ X,i=1 到 k
c.为了阻止非常频繁和不太频繁的项目一起出现在任何频繁项目集合中,在候选项目集合 X 上施加支持差异约束φ
a.|X| >= 1,
- MAX(Supp(X1),Supp(X2)…Supp(Xk))–MIN(Supp(X1),Supp(X2) … Supp(Xk)) <= Φ
The task of assigning MIS to items must be done in a meticulous manner. For highly moving items such as daily groceries, a high value of MIS is desired. For not so frequently sold items such as high end electronics, a less value of MIS is desired. One such assignment of MIS can be done using the following approximation.
MIS (item) = δ * Supp (item); δ ∈ [0, 1]
The above assignment is discussed in 刘兵等人的网络挖掘。求解 MSApriori 的算法也是刘兵在上面的书里提出的,姑且称之为默认的 MSApriori 算法。默认的 MSApriori 算法对每个候选项集 Ck 扫描事务数据库;k > =2 来计算频繁项集 Fk。
考虑到数据库中有“Z”个总事务和“N”个唯一项,默认的 MSApriori 算法需要计算对所有 C2 的支持;如果每个项目的 MIS 按上述等式定义,C2 将有 N*(N-1)/2 个项目集。
总时间复杂度= N*(N-1)/2 * Z ~ O(n3)。
类似地,C3、C4… Ck 也会有相应的复杂性,这是基于项集的大小乘以数据库中的事务数量。
对 MSApriori 的改进建议:
对缺省 MSApriori 提出的改进是使用一种前瞻策略,在事务数据库扫描的第一遍中计算每个 Ck 的支持,将它们存储在哈希表(Hk)中,并在需要时检索它们。在事务数据库扫描的第一遍中,对于长度为“L”的每个事务,每个 Ck (k <= L) is generated locally and the count of its support increased in Hk. So, for C2, the proposed modification would work as follows,
/* modified MSApriori Algorithm form Bing et al */Modificaiton-1: init-pass()For each item(i) in Transaction T:a. Compute/increment the support of item(i)b. For each item(j) in Transaction T: // (j>i)
Compute the hashcode of itemset(i,j)
Retrieve the support of itemset(i,j) from the hash table(H2)
Initialize/Increment the support of itemset(i,j).Modificaiton-2: CandidateGen2() //Candidate generation function for 2-itemsetFor each item(i) in list L/C2:
For each item(j) in list L: // (j>i)
Compute the hashcode of itemset(i,j)
Retrieve the support of itemset(i,j) from the hash table(H2)
If support > MIS(item(i)) // and other constrains like Φ
Return itemset(i,j) into F2.
NOTE: CandidateGen2() directly returns the items into F2, no need for scanning the transaction database again!
In the Modificaiton-1, an extra sub-loop is added for each transaction to compute the support for 2-itemsets, assuming the average length of transaction is ‘L’ then this step would add further processing time of L*(L-1)/2 for each transaction in the database. Assuming, there are ‘Z’ transactions in the database, the time required to complete the init-pass() is
Time complexity (init-pass) = L*(L-1)/2 * Z
~= c * Z ~ O(Z) //for all practical reasons L << Z
In the Modificaiton-2, I am adding an extra step for accessing the hash table (H2) which can be done in constant time (TH) in most library implementations. The time required to complete CandidateGen2() is given as
Time complexity = N*(N-1)/2*TH ~ O(n2).
Combining both modifications, total time complexity = O(Z) + O(n2) < O(n3)! (from the default algorithm).
Verification of the result:
The default MSApirioi algorithm is implemented by Phillipe et al in the 开源 java data mining library SPMF )。我已经用我在 MSAprori _ H.java 程序中提出的修改修改了 SPMF 库中的默认 MSApriori 算法。MSApriori 和 MSApriori_H 的执行结果如下:
考虑的数据集:retail1.txt
数据集中不同项目的数量:N = 2603。
数据集中的事务数量:Z = 541909。
关于此算法的哈希函数要求的注释。理想情况下,我们需要一个散列函数,它能为一组整数提供唯一的散列值,而不考虑给定集合中整数的顺序。生成这样一个散列函数肯定不是一件简单的任务,所以我在实现中将散列值放宽为一个 Java 对象。我选择了一个要在 MSApriori_H.java 中实现的 BitSet 对象。
MSApriori_H.java 中的哈希函数:
/* Input k-item set in Integer[] items */BitSet hashcode = new BitSet(Integer.MAX_VALUE);for(int i=0; i<items.length; i++) {hashcode.set(items[i])};return hashcode;
带推荐人的购物篮分析
我对购物篮分析的看法——第 2 部分,共 3 部分
Photo by Victoriano Izquierdo on Unsplash
O verview
最近我想学习一些新的东西,并挑战自己进行端到端的市场篮子分析。为了继续挑战自己,我决定将我的努力成果展示给数据科学界。
这是三柱中的第二柱,排列如下:
加载包
*# Importing libraries
library(data.table)
library(tidyverse)
library(knitr)
library(recommenderlab)*
数据
为了进行分析,我将使用在第 1 部分中准备和清理的retail
数据集。如果你想继续这篇文章,确保你得到了数据集并运行第 1 部分的 R 代码,你可以在我的 Github 简介中找到。
***glimpse(retail)
## Observations: 528,148
## Variables: 10
## $ InvoiceNo <dbl> 536365, 536365, 536365, 536365, ...
## $ StockCode <chr> "85123A", "71053", "84406B", "...
## $ Description <fct> WHITE HANGING HEART T-LIGHT HOLDER, ...
## $ Quantity <dbl> 6, 6, 8, 6, 6, 2, 6, 6, 6, 32, 6, 6, 8, ...
## $ InvoiceDate <dttm> 2010-12-01 08:26:00, 2010-12-01 08:26:00, 2010-12...
## $ UnitPrice <dbl> 2.55, 3.39, 2.75, 3.39, 3.39, 7.65, ....
## $ CustomerID <dbl> 17850, 17850, 17850, 17850, 17850, ...
## $ Country <fct> United Kingdom, United Kingdom, ...
## $ Date <date> 2010-12-01, 2010-12-01, 2010-12-01, ...
## $ Time <fct> 08:26:00, 08:26:00, 08:26:00, 08:26:00, ...***
系统模型化
对于这个项目的分析部分,我使用的是re commender lab,这是一个 R 包,它提供了一个方便的框架来评估和比较各种推荐算法,并快速建立最适合的方法。
创建评级矩阵
在开始之前,我需要在一个评级矩阵中安排购买历史,订单按行排列,产品按列排列。这种格式通常被称为 user_item matrix ,因为“用户”(例如客户或订单)往往位于行上,而“项目”(例如产品)位于列上。**
推荐者实验室接受两种类型的评级矩阵用于建模:
- 实际评分矩阵由实际用户评分组成,需要标准化。
- 二进制评级矩阵,由 0 的和 1 的组成,其中 1 的表示产品是否被购买。这是分析所需的矩阵类型,不需要标准化。
但是,在创建评级矩阵时,很明显有些订单不止一次包含相同的商品,如下例所示。
***# Filtering by an order number which contains the same stock code more than onceretail %>%
filter(InvoiceNo == 557886 & StockCode == 22436) %>%
select(InvoiceNo, StockCode, Quantity, UnitPrice, CustomerID)## # A tibble: 2 x 5
## InvoiceNo StockCode Quantity UnitPrice CustomerID
## <dbl> <chr> <dbl> <dbl> <dbl>
## 1 557886 22436 1 0.65 17799
## 2 557886 22436 3 0.65 17799***
向 UCI 机器学习库 捐赠该数据集的公司可能有一个订单处理系统,该系统允许将一个项目多次添加到同一订单中。对于这个分析,我只需要知道一个项目是否包含在一个订单中,因此需要删除这些重复的项目。
***retail <- retail %>%
# Create unique identifier
mutate(InNo_Desc = paste(InvoiceNo, Description, sep = ' ')) # Filter out duplicates and drop unique identifier
retail <- retail[!duplicated(retail$InNo_Desc), ] %>%
select(-InNo_Desc)# CHECK: total row count - 517,354***
我现在可以创建评级矩阵。
***ratings_matrix <- retail %>%
# Select only needed variables
select(InvoiceNo, Description) %>% # Add a column of 1s
mutate(value = 1) %>%# Spread into user-item format
spread(Description, value, fill = 0) %>%
select(-InvoiceNo) %>%# Convert to matrix
as.matrix() %>%# Convert to recommenderlab class 'binaryRatingsMatrix'
as("binaryRatingMatrix")ratings_matrix
## 19792 x 4001 rating matrix of class 'binaryRatingMatrix' with 517354 ratings.***
评估方案和模型验证
为了确定模型的有效性,推荐者实验室实施了许多评估方案。在这个scheme
中,我选择 train = 0.8 进行 80/20 训练/测试分割,将数据分割成一个训练和一个测试集。我还设置了 method = “cross” 和 k = 5 进行 5 重交叉验证。这意味着数据被分成 k 个大小相等的子集,80%的数据用于训练,剩下的 20%用于评估。模型被递归估计 5 次,每次使用不同的训练/测试分割,这确保了所有用户和项目都被考虑用于训练和测试。然后可以对结果进行平均,以产生单个评估集。
选择 given = -1 意味着对于测试用户来说,除了 1 个项目外,所有随机选择的项目都被保留进行评估。
***scheme <- ratings_matrix %>%
evaluationScheme(method = "cross",
k = 5,
train = 0.8,
given = -1)scheme
## Evaluation scheme using all-but-1 items
## Method: 'cross-validation' with 5 run(s).
## Good ratings: NA
## Data set: 19792 x 4001 rating matrix of class 'binaryRatingMatrix' with 517354 ratings.***
设置算法列表
推荐实验室的主要特性之一是能够一次评估多个算法。首先,我用我想要估计的algorithms
创建一个列表,指定所有的模型参数。在这里,我考虑在二元评级矩阵上评估的方案。我包含了随机项目算法,用于基准测试。**
***algorithms <- list(
"association rules" = list(name = "AR",
param = list(supp = 0.01, conf = 0.01)),
"random items" = list(name = "RANDOM", param = NULL),
"popular items" = list(name = "POPULAR", param = NULL),
"item-based CF" = list(name = "IBCF", param = list(k = 5)),
"user-based CF" = list(name = "UBCF",
param = list(method = "Cosine", nn = 500))
)***
评估模型
我现在要做的就是将scheme
和algoritms
传递给evaluate()
函数,选择 type = topNList 来评估前 N 个产品推荐列表,并使用参数 n = c(1,3,5,10,15,20) 指定要计算多少个推荐。
请注意基于 CF 的算法每种都需要几分钟来估计。
***results <- recommenderlab::evaluate(scheme,
algorithms,
type = "topNList",
n = c(1, 3, 5, 10, 15, 20)
)## AR run fold/sample [model time/prediction time]
## 1 [0.32sec/73.17sec]
## 2 [0.24sec/72.72sec]
## 3 [0.23sec/72.27sec]
## 4 [0.24sec/72.82sec]
## 5 [0.24sec/72.69sec]
## RANDOM run fold/sample [model time/prediction time]
## 1 [0sec/20.08sec]
## 2 [0sec/19.01sec]
## 3 [0sec/18.69sec]
## 4 [0sec/19.26sec]
## 5 [0.02sec/19.41sec]
## POPULAR run fold/sample [model time/prediction time]
## 1 [0.01sec/15.94sec]
## 2 [0sec/16.34sec]
## 3 [0sec/15.91sec]
## 4 [0.02sec/16.02sec]
## 5 [0.01sec/15.86sec]
## IBCF run fold/sample [model time/prediction time]
## 1 [515.11sec/3.11sec]
## 2 [513.94sec/2.88sec]
## 3 [509.98sec/3.05sec]
## 4 [513.94sec/3.13sec]
## 5 [512.58sec/2.81sec]
## UBCF run fold/sample [model time/prediction time]
## 1 [0sec/296.54sec]
## 2 [0sec/291.54sec]
## 3 [0sec/292.68sec]
## 4 [0sec/293.33sec]
## 5 [0sec/300.35sec]***
输出存储为包含所有评估的列表。
***results## List of evaluation results for 5 recommenders:
## Evaluation results for 5 folds/samples using method 'AR'.
## Evaluation results for 5 folds/samples using method 'RANDOM'.
## Evaluation results for 5 folds/samples using method 'POPULAR'.
## Evaluation results for 5 folds/samples using method 'IBCF'.
## Evaluation results for 5 folds/samples using method 'UBCF'.***
想象结果
推荐者实验室有一个基本的plot
功能,可用于比较型号性能。然而,我更喜欢把结果整理成整齐的格式,以增加灵活性和图表定制。
首先,我以一种方便的格式安排一个模型的混淆矩阵输出。
***# Pull into a list all confusion matrix information for one model
tmp <- results$`user-based CF` %>%
getConfusionMatrix() %>%
as.list() # Calculate average value of 5 cross-validation rounds
as.data.frame( Reduce("+",tmp) / length(tmp)) %>% # Add a column to mark the number of recommendations calculated
mutate(n = c(1, 3, 5, 10, 15, 20)) %>%# Select only columns needed and sorting out order
select('n', 'precision', 'recall', 'TPR', 'FPR')## n precision recall TPR FPR
## 1 1 0.06858938 0.07420981 0.07420981 0.0002327780
## 2 3 0.04355442 0.14137351 0.14137351 0.0007171045
## 3 5 0.03354715 0.18148235 0.18148235 0.0012076795
## 4 10 0.02276376 0.24627561 0.24627561 0.0024423093
## 5 15 0.01762715 0.28605934 0.28605934 0.0036827205
## 6 20 0.01461690 0.31627924 0.31627924 0.0049253407***
然后,我把前面的步骤代入一个公式。
***avg_conf_matr <- function(results) {
tmp <- results %>%
getConfusionMatrix() %>%
as.list()
as.data.frame(Reduce("+",tmp) / length(tmp)) %>%
mutate(n = c(1, 3, 5, 10, 15, 20)) %>%
select('n', 'precision', 'recall', 'TPR', 'FPR')
}***
接下来,我使用purrr
包中的map()
函数以一种整齐的格式获得所有结果,为图表制作做好准备。
***# Using map() to iterate function across all models
results_tbl <- results %>%
map(avg_conf_matr) %>% # Turning into an unnested tibble
enframe() %>%# Unnesting to have all variables on same level
unnest()results_tbl## # A tibble: 30 x 6
## name n precision recall TPR FPR
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 association rules 1 0.0428 0.0380 0.0380 0.000197
## 2 association rules 3 0.0306 0.0735 0.0735 0.000579
## 3 association rules 5 0.0266 0.0979 0.0979 0.000944
## 4 association rules 10 0.0224 0.139 0.139 0.00179
## 5 association rules 15 0.0202 0.162 0.162 0.00255
## 6 association rules 20 0.0188 0.176 0.176 0.00325
## 7 random items 1 0.000202 0.000219 0.000219 0.000250
## 8 random items 3 0.000253 0.000820 0.000820 0.000750
## 9 random items 5 0.000242 0.00131 0.00131 0.00125
## 10 random items 10 0.000222 0.00241 0.00241 0.00250
## # ... with 20 more rows***
受试者工作特征曲线
可以使用 ROC 曲线来比较分类模型的性能,该曲线绘制了真阳性率 (TPR)与假阳性率* (FPR)。***
基于项目的协同过滤模型是明显的赢家,因为它在任何给定的 FPR 水平上都实现了最高的 TPR。这意味着,对于相同级别的不相关推荐(误报),该模型正在产生最高数量的相关推荐(真阳性)。
注意使用fct_reorder2()
按最佳最终 FPR 和 TPR 排列情节图例条目,用曲线排列它们,使情节更容易阅读。
***results_tbl %>%
ggplot(aes(FPR, TPR,
colour = fct_reorder2(as.factor(name),
FPR, TPR))) +
geom_line() +
geom_label(aes(label = n)) +
labs(title = "ROC curves", colour = "Model") +
theme_grey(base_size = 14)***
精确回忆曲线
另一种比较分类模型性能的常用方法是使用精度与召回曲线*。Precision 显示模型对假阳性(即推荐不太可能被购买的商品)的敏感程度,而 Recall(TPR 的另一个名称)则显示模型对假阴性(即不推荐极有可能被购买的商品)的敏感程度。***
通常,我们关心的是准确预测哪些商品更有可能被购买,因为这将对销售和收入产生积极影响。换句话说,我们希望在精度相同的情况下,最大化召回*(或最小化 FNs)。***
该图证实了基于项目的协作过滤器* (IBCF)是最好的模型,因为它对于任何给定的精度水平都具有更高的召回率。这意味着 IBCF 将所有级别的第一手资料的 FNs 降至最低(即不建议购买可能性很高的物品)。***
***results_tbl %>%
ggplot(aes(recall, precision,
colour = fct_reorder2(as.factor(name),
precision, recall))) +
geom_line() +
geom_label(aes(label = n)) +
labs(title = "Precision-Recall curves", colour = "Model") +
theme_grey(base_size = 14)***
对新用户的预测
最后一步是生成具有最佳性能模型的预测。为此,我需要创建一个虚构的采购订单。
首先,我创建了一个包含 6 个随机选择的产品的字符串。
***customer_order <- c("GREEN REGENCY TEACUP AND SAUCER",
"SET OF 3 BUTTERFLY COOKIE CUTTERS",
"JAM MAKING SET WITH JARS",
"SET OF TEA COFFEE SUGAR TINS PANTRY",
"SET OF 4 PANTRY JELLY MOULDS")***
接下来,我将这个订单以一种推荐者 lab* 接受的格式放置。***
***new_order_rat_matrx <- retail %>% # Select item descriptions from retail dataset
select(Description) %>%
unique() %>% # Add a 'value' column with 1's for customer order items
mutate(value = as.numeric(Description %in% customer_order)) %>% # Spread into sparse matrix format
spread(key = Description, value = value) %>% # Change to a matrix
as.matrix() %>% # Convert to recommenderlab class 'binaryRatingsMatrix'
as("binaryRatingMatrix")***
现在,我可以创建一个Recommender
。我使用getData
来检索训练数据,并设置 method = “IBCF” 来选择性能最好的模型(“基于项目的协同过滤”)。
***recomm <- Recommender(getData(scheme, 'train'),
method = "IBCF",
param = list(k = 5))recomm## Recommender of type 'IBCF' for 'binaryRatingMatrix'
## learned using 15832 users.***
最后,我可以将Recommender
和生成的订单传递给predict
函数,为新客户创建前 10 名推荐列表。
***pred <- predict(recomm,
newdata = new_order_rat_matrx,
n = 10)***
最后,建议的项目可以作为一个列表进行检查
***as(pred, 'list')## $`1`
## [1] "ROSES REGENCY TEACUP AND SAUCER"
## [2] "PINK REGENCY TEACUP AND SAUCER"
## [3] "SET OF 3 HEART COOKIE CUTTERS"
## [4] "REGENCY CAKESTAND 3 TIER"
## [5] "JAM MAKING SET PRINTED"
## [6] "RECIPE BOX PANTRY YELLOW DESIGN"
## [7] "SET OF 3 CAKE TINS PANTRY DESIGN"
## [8] "GINGERBREAD MAN COOKIE CUTTER"
## [9] "3 PIECE SPACEBOY COOKIE CUTTER SET"
## [10] "SET OF 6 SPICE TINS PANTRY DESIGN"***
评论
这就结束了这个项目的建模和评估部分,我发现这很简单,也很愉快。 recommenderlab 直观易用,我特别欣赏它同时评估和比较几种分类算法的能力。总之,我已经学会了如何在 R 中使用 recommenderlab 进行市场购物篮分析,以解释结果并选择表现最佳的模型。
代码库
完整的 R 代码可以在我的 GitHub 简介中找到
参考
- 有关推荐的实验室包,请参见:https://cran.r-project.org/package=recommenderlab
- 关于推荐者实验室软件包简介,请参见:https://cran . r-project . org/web/packages/re commender lab/vignettes/re commender lab . pdf
原载于 2019 年 3 月 25 日https://diegousei . io。**
市场概况:金融市场的统计观点
关于如何在 Matplotlib 中绘制市场轮廓的简单介绍和简短方法
Market profile is a technique used to contextualize current market conditions.
市场概况方法简介
市场分析是 J. Peter Steidlmayer 在 60 年代开发的一种技术。该方法代表给定市场在给定时期的统计分布。
Steidlmayer,一个富裕农民的儿子,在 60 年代成为 CBOT 的场内交易者,并最终在 80 年代初成为 CBOT 导演之一。他融合了最小相关价格变动、平衡和高斯分布的概念,定义了一种方法,可以跟踪特定市场在特定时间的变动情况。
市场概况理论在几本书中都有适当的介绍,在互联网上也有一些好的资料。这种方法在 80 年代末和 90 年代引起了极大的兴趣,Steidlmayer 是这种方法的主要推动者,他还在 80 年代初负责在 CBOT 提供第一批电子数据服务。虽然它不再是一种主流的分析技术,但它仍然拥有一批积极使用它的追随者。
市场概况使用时间和价格来定位交易时段的价值区域(即参与者认为给定资产/工具的公允价格所在的价格区域)。虽然它不是一种交易方法或系统,但它是一种分析给定市场当前状态的可靠方法,因为它有助于澄清市场是否正在盘整或形成趋势。
市场概况优于交易量概况的一个优点是不需要交易量数据。这对于不受监管的场外交易市场来说尤其有趣,在这种市场中,交易量信息要么不可用,要么没有意义。它还允许使用非昂贵的历史数据进行模拟。
由于市场概况使用 TPO(时间价格机会)的概念来反映感兴趣的领域,这些领域与高交易量领域高度相关。所以最后,这两种方法可以得到相似的结果,有时看到这两种情况如此相似真的令人吃惊。对此的解释是,在市场上以给定的价格移动大量货物需要时间,而这一时间转化为给定价格下的大量 TPO。这有效地有助于关联市场概况和数量概况。
尽管所有主要的交易软件都有一些插件,但市场概况在某种程度上是一个不同的野兽。在 20 世纪 90 年代的巅峰时期,市场概况通常是通过专业软件(如 Steildmayer 曾经发行的软件 Capital Flow)来开发的。这种软件包价格昂贵,因为它们是为基金和机构参与者设计的。
我一直对市场和销量非常感兴趣,在过去的几个月里,我一直在深入研究和研究这些领域。
这些分析技术帮助你确定主要参与者可能在哪里,他们的行动方向是什么。有人可能会说,市场剖析是另一个时代的技术;我们不能忘记,这是 60 年代为大宗商品场内交易构想的一种方法,一些帮助市场概况发光的配套信息(如 CBOT 的流动性数据库)不再可用,但我认为支持该方法的基本统计概念仍然适用。
在我看来,平衡/不平衡和高斯分布概念的使用使该方法强大并得到科学支持,因为这些概念有助于处理无法以确定性方式描述的复杂自然过程——股票市场很适合这一类别。这是个人观点,我可能有偏见,因为我的市场策略是尽可能利用统计数据。
我对市场概况的兴趣特别集中在日内交易上。我特别使用 30 分钟蜡烛线,这是为市场概况定义的原始时间框架。30 分钟是一个没有被广泛使用的时间框架,但它有一个很大的优势,足够大以避免小时间框架的玩家(特别是 HFT),并且足够小以获得足够的日内交易。在不久的将来,我想将市场概况的概念扩展到更大的时间范围,就像 90 年代基金使用的所有主要软件一样。超越 30 分钟时间框架的优点是可以检测到有效周期——不容易但可行——通过这样做,可以预测更大的市场波动。因此,可以计划回转操作。关于如何实现这一点的一些信息包含在下一部分的书籍中。
掌握市场概况
市场概况是一个复杂的方法,需要专注和经验来掌握。对于那些有兴趣了解更多的人,我会指出我所找到的关于这个主题的最相关的书籍。
141 West Jackson and Markets & Market Logic are the classic books writen by Steidlmayer about Market Profile
关于市场概况的经典书籍有“ 141 West Jackson ”(对于那些好奇的人,那是 CBOT 在芝加哥的地址)和“ Markets & Market Logic ”。
一个更现代的重温将是 J. Peter Steidlmayer 和 Steven B. Hawkins 的《 Steidlmayer 谈市场:用市场概况进行交易》。
这三本书都很好地介绍了市场概况。“141 West Jackson ”特别令人愉快,而“Steidlmayer on Markets:Trading with Market Profile”可能是最实用的一个。
作为建设性的批评,我要指出,在某些方面,一些摘录可能过于关注专有软件的功能,而没有对这些功能如何工作进行适当的解释,这可能会给人一种被促销营销所针对的感觉。除此之外,书籍值得任何对该主题感兴趣的人阅读,因为它们是由该方法的关键利益相关者编写的。
Matplotlib 和 Python 中的市场概况
作为一个关于市场概况的实践示例,我使用 Matplotlib 在 Python 中包含了一个获取市场概况分布及其绘图的例程。
假设您在 Python 中有以下市场概况数据:
day_market_profile = [
(Decimal(1.4334), 1, 'n'),
(Decimal(1.4335), 1, 'n'),
(Decimal(1.4336), 1, 'n'),
...
(Decimal(1.4360), 14, 'bcdijklmpqrsuv'),
...
(Decimal(1.4482), 1, 'E'),
(Decimal(1.4483), 1, 'E'),
]
该数据是在定制的 market_profile 例程中获得的,该例程使用 30 分钟 TPOs 生成每日市场概况。
day = datetime(2010,1,5)
day_market_profile = market_profile(day, Decimal('0.0001'))
for i in day_market_profile:
print(str(i[0]) + ' | ' + str(i[1]).rjust(2,' ') + ' | ' + i[2])
打印元组列表会导致:
1.4334 | 1 | n
1.4335 | 1 | n
1.4336 | 1 | n
1.4337 | 1 | n
1.4338 | 1 | n
1.4339 | 1 | n
1.4340 | 1 | n
1.4341 | 1 | n
1.4342 | 3 | noq
1.4343 | 3 | noq
1.4344 | 3 | noq
1.4345 | 4 | noqr
1.4346 | 5 | bnoqr
1.4347 | 6 | bmnoqr
1.4348 | 6 | bmnoqr
1.4349 | 7 | bcmnoqr
1.4350 | 8 | bckmnoqr
1.4351 | 9 | bckmnoqrs
1.4352 | 10 | bckmnoqrst
1.4353 | 11 | bckmnopqrst
1.4354 | 14 | bcklmnopqrstuv
1.4355 | 14 | bcklmnopqrstuv
1.4356 | 13 | bcklmopqrstuv
1.4357 | 13 | bcklmopqrstuv
1.4358 | 12 | bcklmopqrsuv
1.4359 | 12 | bcdklmpqrsuv
1.4360 | 14 | bcdijklmpqrsuv
1.4361 | 14 | bcdijklmpqrsuv
1.4362 | 13 | bcdijklmpqrsu
1.4363 | 11 | bdhijklmpqs
1.4364 | 10 | bdhijklmpq
1.4365 | 11 | bdfhijklmpq
1.4366 | 12 | bdfghijklmpq
1.4367 | 11 | bdefghjklpq
1.4368 | 10 | bdefghklpq
1.4369 | 9 | bdefghklq
1.4370 | 7 | befghkl
1.4371 | 7 | abefghk
1.4372 | 6 | abefgh
1.4373 | 5 | abegh
1.4374 | 2 | ab
1.4375 | 1 | a
1.4376 | 1 | a
1.4377 | 1 | a
1.4378 | 1 | a
1.4379 | 1 | a
1.4380 | 1 | a
1.4381 | 1 | a
1.4382 | 1 | a
1.4383 | 2 | Ya
1.4384 | 2 | Ya
1.4385 | 2 | Ya
1.4386 | 4 | XYZa
1.4387 | 5 | TXYZa
1.4388 | 5 | TXYZa
1.4389 | 5 | TXYZa
1.4390 | 5 | TXYZa
1.4391 | 5 | TXYZa
1.4392 | 5 | TXYZa
1.4393 | 5 | TXYZa
1.4394 | 4 | TXYZ
1.4395 | 4 | TXYZ
1.4396 | 4 | TXYZ
1.4397 | 5 | TWXYZ
1.4398 | 5 | TWXYZ
1.4399 | 4 | TWXY
1.4400 | 5 | MTWXY
1.4401 | 6 | MTUWXY
1.4402 | 6 | MTUWXY
1.4403 | 5 | MTUWY
1.4404 | 5 | MTUWY
1.4405 | 5 | MTUWY
1.4406 | 7 | HMSTUWY
1.4407 | 6 | HMSTUW
1.4408 | 6 | HMSTUW
1.4409 | 8 | HMNSTUVW
1.4410 | 8 | HMNSTUVW
1.4411 | 8 | HMNSTUVW
1.4412 | 8 | HMNSTUVW
1.4413 | 8 | HMNSTUVW
1.4414 | 10 | HILMNSTUVW
1.4415 | 10 | HILMNSTUVW
1.4416 | 11 | AHILMNSTUVW
1.4417 | 12 | AHILMNOSTUVW
1.4418 | 13 | AHIJLMNOSTUVW
1.4419 | 13 | AHIJLMNOSTUVW
1.4420 | 14 | AHIJKLNORSTUVW
1.4421 | 14 | AHIJKLNORSTUVW
1.4422 | 15 | AGHIJKLNORSTUVW
1.4423 | 15 | AGHIJKLNORSTUVW
1.4424 | 15 | ABGHIJKLNORSUVW
1.4425 | 14 | ABGHIJKLNORSUV
1.4426 | 13 | ABGHIJKLORSUV
1.4427 | 13 | ABGHIJKLORSUV
1.4428 | 12 | ABGHIJKLORUV
1.4429 | 11 | BGIJKLOPRUV
1.4430 | 11 | BGIJKLOPRUV
1.4431 | 12 | BGIJKLOPQRUV
1.4432 | 12 | BGIJKLOPQRUV
1.4433 | 12 | BGIJKLOPQRUV
1.4434 | 11 | BGIJKLOPQRU
1.4435 | 10 | BGIJKLOPQR
1.4436 | 9 | BGIJKLPQR
1.4437 | 9 | BGIJKLPQR
1.4438 | 6 | BGIPQR
1.4439 | 5 | BGPQR
1.4440 | 5 | BGPQR
1.4441 | 4 | BGPQ
1.4442 | 4 | BGPQ
1.4443 | 4 | BGPQ
1.4444 | 4 | BGPQ
1.4445 | 4 | BGPQ
1.4446 | 5 | BCGPQ
1.4447 | 5 | BCFGP
1.4448 | 5 | BCFGP
1.4449 | 5 | BCFGP
1.4450 | 5 | BCFGP
1.4451 | 5 | BCFGP
1.4452 | 6 | BCDFGP
1.4453 | 6 | BCDFGP
1.4454 | 6 | BCDFGP
1.4455 | 5 | BCDFP
1.4456 | 4 | BCDF
1.4457 | 4 | BCDF
1.4458 | 4 | BCDF
1.4459 | 5 | BCDEF
1.4460 | 5 | BCDEF
1.4461 | 5 | BCDEF
1.4462 | 5 | BCDEF
1.4463 | 5 | BCDEF
1.4464 | 5 | BCDEF
1.4465 | 3 | BDE
1.4466 | 3 | BDE
1.4467 | 3 | BDE
1.4468 | 3 | BDE
1.4469 | 3 | BDE
1.4470 | 3 | BDE
1.4471 | 3 | BDE
1.4472 | 3 | BDE
1.4473 | 3 | BDE
1.4474 | 3 | BDE
1.4475 | 2 | DE
1.4476 | 2 | DE
1.4477 | 1 | E
1.4478 | 1 | E
1.4482 | 1 | E
1.4483 | 1 | E
这是一个老学校的茎和叶图,它是代表市场概况的规范方式。虽然这些信息是相关的,因为字母代码为您提供了关于“何时何地价格为”的直观指导,但您通常可能只对价格-时间分布感兴趣,这在没有字母代码的图表中很容易查看。如果你的简介涵盖 24 小时,这一点尤其正确,因为很难跟踪这么多的字母代码。在这种简化的情况下,更容易将数据绘制成常规图表,尽管您会丢失交易期间价格变化的信息:
%matplotlib inlinempl.rcParams['interactive'] = False
mpl.rcParams['figure.figsize'] = (16.0, 12.0)
mpl.rcParams['lines.markerfacecolor'] = 'blue'# Define price labels, we print just values ending in 0.0005 or 0.0010
df.loc[df['price'] % Decimal('0.0005') == 0, 'label'] = df['price']
df['label'].fillna('',inplace=True)
df['label']=df['label'].astype(str)df.plot.barh(x='label', y='tpo_count', legend=None)
plt.xlabel('TPO Count')
plt.ylabel('Price')
plt.title('Market Profile | EURUSD | January 5th, 2010')
plt.show()
注意我们是如何创建一个新的标签列来保存 y 轴刻度标签的。我们将只打印以 0.0005 和 0.0010 结尾的价格,因此我们使用*。loc* ,。fillna 并最终转换为 str 以使我们的熊猫系列被用作标签。
The graphical alternative removing the code letters enable a quick read on the areas of interest of the trading session. While the letter code is relevant information, if we want to detect areas of high activity in the session this chart is easier to read.
摘要
在文章中,我简要介绍了市场概况。我已经解释了为什么我认为市场概况在今天仍然有意义,以及我为什么这样想的一些理由。我还列举了三个主要的经典书籍,涵盖了理论和一小部分摘录代码如何绘制市场概况。没有给出获取市场概况的例程,因为它非常具体地说明了如何存储数据,但是在这个例子中,用 Python 编写的原型只用了 50 行代码。这只是一页代码。
取得联系
我对这个特定的领域非常感兴趣,所以如果你在这个行业工作,并且对市场概况感兴趣,请随时联系我。我很乐意探索任何与市场概况和数量概况相关的合作(以及服务/雇佣)。
市场反应模型
使用 Python 实现数据驱动的增长
预测促销活动的增量收益
这一系列文章旨在解释如何以一种简单的方式使用 Python,通过将预测方法应用于您的所有行动来推动您公司的发展。它将是编程、数据分析和机器学习的结合。
我将在以下九篇文章中讨论所有主题:
1- 了解你的衡量标准
2- 客户细分
3- 客户终身价值预测
4- 流失预测
7-市场反应模型
文章将有自己的代码片段,使您可以轻松地应用它们。如果你是编程的超级新手,你可以在这里很好地介绍一下 Python 和 Pandas (一个我们将在任何事情上使用的著名库)。但是仍然没有编码介绍,您可以学习概念,如何使用您的数据并开始从中产生价值:
有时候你必须先跑,然后才能走——托尼·斯塔克
作为先决条件,确保你的电脑上安装了 J upyter Notebook 和 P ython 。代码片段只能在 Jupyter 笔记本上运行。
好吧,我们开始吧。
第 7 部分:市场反应模型
通过使用我们在以前的文章中建立的模型,我们可以轻松地细分客户和预测他们的终身价值 (LTV)以达到目标。顺便提一下,我们也知道我们的销售数字会是多少。但是我们怎样才能增加销售额呢?如果我们今天打折,预计会有多少增量交易?
细分客户和进行 A/B 测试使我们能够尝试许多不同的想法来增加销售额。这是增长黑客技术的基石之一。你需要不断地思考和实验来寻找成长的机会。
将我们要向其发送产品的客户分为测试组和对照组,有助于我们计算增量收益。
让我们看看下面的例子:
在此设置中,目标群体被分为三组,以寻找以下问题的答案:
1-提供报价会增加转化率吗?
2-如果是,什么样的报价表现最好?打折还是买一送一?
假设结果具有统计学意义,折扣(A 组)看起来最好,因为它比对照组增加了 3%的转化率,比买一送一多带来了 1%的转化率。
当然,在现实世界中,事情要复杂得多。一些优惠在特定的细分市场表现更好。因此,您需要为选定的细分市场创建一个产品组合。而且,不能指望转化是成功的唯一标准。总会有成本的权衡。一般来说,当转换率上升时,成本也会增加。这就是为什么有时你需要选择一个成本友好但转化较少的报价。
现在,通过实验,我们知道了哪个报价比其他报价表现得更好。但是预测呢?如果我们预测给出一个报价的效果,我们可以很容易地最大化我们的交易,并对成本有一个预测。市场反应模型帮助我们建立这个框架。但是做这件事的方法不止一种。我们可以把它们分成两类:
1-如果你没有一个控制组(想象你对每个人做了一次公开推广,并在社交媒体上公布),那么你就无法计算增量。对于这种情况,最好建立一个预测整体销售的回归模型。之前的假设是,该模型将为促销日提供更高的销售数字。
为了建立这种模型,您的数据集应该包括促销和非促销日销售数字,以便机器学习模型可以计算增量。
2-如果您有一个控制组,您可以基于细分或个人级别建立响应模型。对他们两人来说,假设是一样的。给出要约应该会增加转化的概率。个人转化概率的上升会给我们带来增量转化。
让我们开始编码,看看我们如何建立一个个体水平的反应模型。在本例中,我们将在这里使用营销数据集和。但是我做了一些修改,使它与我们的案例更相关(你可以在这里找到****)。)
让我们导入我们需要的库并导入我们的数据:
我们数据的前 10 行:
我们的前 8 列提供个人级别的数据,转换列是我们预测的标签:****
- 最近:自上次购买以来的月数
- 历史:历史采购的价值
- used_discount/used_bogo:指示客户是否使用了折扣或先买一送一
- zip_code:邮政编码的分类,如郊区/城市/农村
- is_referral:指示客户是否是从推荐渠道获得的
- 渠道:客户使用的渠道,电话/网络/多渠道
- 报价:发送给客户的报价,折扣/买一送一/无报价
我们将建立一个二元分类模型,对所有客户的转换概率进行评分。为此,我们将遵循以下步骤:
- 构建提升公式
- 探索性数据分析(EDA)和特征工程
- 对转换概率进行评分
- 观察测试集上的结果
隆起公式
首先,我们需要构建一个函数来计算我们的提升。为了简单起见,我们假设每次转换意味着 1 个订单,平均订单价值为 25 美元。
我们将计算三种类型的抬升:
转化率提升:试验组转化率-对照组转化率
订单提升:转换提升 #测试组中已转换的客户*
收入增加:订单增加*平均订单金额
让我们构建我们的calc _ upgrade函数:
def calc_uplift(df):
#assigning 25$ to the average order value
avg_order_value = 25
#calculate conversions for each offer type
base_conv = df[df.offer == 'No Offer']['conversion'].mean()
disc_conv = df[df.offer == 'Discount']['conversion'].mean()
bogo_conv = df[df.offer == 'Buy One Get One']['conversion'].mean()
#calculate conversion uplift for discount and bogo
disc_conv_uplift = disc_conv - base_conv
bogo_conv_uplift = bogo_conv - base_conv
#calculate order uplift
disc_order_uplift = disc_conv_uplift * len(df[df.offer == 'Discount']['conversion'])
bogo_order_uplift = bogo_conv_uplift * len(df[df.offer == 'Buy One Get One']['conversion'])
#calculate revenue uplift
disc_rev_uplift = disc_order_uplift * avg_order_value
bogo_rev_uplift = bogo_order_uplift * avg_order_value
print('Discount Conversion Uplift: {0}%'.format(np.round(disc_conv_uplift*100,2)))
print('Discount Order Uplift: {0}'.format(np.round(disc_order_uplift,2)))
print('Discount Revenue Uplift: ${0}\n'.format(np.round(disc_rev_uplift,2)))
print('-------------- \n')print('BOGO Conversion Uplift: {0}%'.format(np.round(bogo_conv_uplift*100,2)))
print('BOGO Order Uplift: {0}'.format(np.round(bogo_order_uplift,2)))
print('BOGO Revenue Uplift: ${0}'.format(np.round(bogo_rev_uplift,2)))
如果我们将这个函数应用到我们的数据帧,我们将看到下面的结果:
如果我们想获得更多的转化率,折扣似乎是一个更好的选择。与没有收到任何优惠的客户相比,这带来了 7.6%的增长。BOGO(买一送一)也上涨了 4.5%。
让我们开始探索哪些因素是这种增量变化的驱动因素。
EDA 和特征工程
我们逐一检查每个特性,找出它们对转化率的影响
1-新近度
理想情况下,转换率应该下降,而新近度上升,因为不活跃的客户不太可能再次购买:
df_plot = df_data.groupby('recency').conversion.mean().reset_index()
plot_data = [
go.Bar(
x=df_plot['recency'],
y=df_plot['conversion'],
)
]plot_layout = go.Layout(
xaxis={"type": "category"},
title='Recency vs Conversion',
plot_bgcolor = 'rgb(243,243,243)',
paper_bgcolor = 'rgb(243,243,243)',
)
fig = go.Figure(data=plot_data, layout=plot_layout)
pyoff.iplot(fig)
直到最近 11 个月,一切都如预期的那样。然后就增加了。这可能是由于许多原因,如在这些桶中的客户数量较少或给定优惠的影响。
2-历史记录
我们将创建一个历史集群并观察其影响。让我们应用 k 均值聚类来定义历史上的重要群体:
kmeans = KMeans(n_clusters=5)
kmeans.fit(df_data[['history']])
df_data['history_cluster'] = kmeans.predict(df_data[['history']])#order the cluster numbers
df_data = order_cluster('history_cluster', 'history',df_data,True)#print how the clusters look like
df_data.groupby('history_cluster').agg({'history':['mean','min','max'], 'conversion':['count', 'mean']})#plot the conversion by each cluster
df_plot = df_data.groupby('history_cluster').conversion.mean().reset_index()
plot_data = [
go.Bar(
x=df_plot['history_cluster'],
y=df_plot['conversion'],
)
]plot_layout = go.Layout(
xaxis={"type": "category"},
title='History vs Conversion',
plot_bgcolor = 'rgb(243,243,243)',
paper_bgcolor = 'rgb(243,243,243)',
)
fig = go.Figure(data=plot_data, layout=plot_layout)
pyoff.iplot(fig)
聚类和图与转换的概述:
****
历史价值较高的客户更有可能转化。
3-二手折扣& BOGO
我们将结合下面的代码行来检查这两个特性:
df_data.groupby(['used_discount','used_bogo','offer']).agg({'conversion':'mean'})
输出:
之前使用过这两种产品的客户拥有最高的转化率。
4-邮政编码
与其他地区相比,农村地区的转化率更高:
df_plot = df_data.groupby('zip_code').conversion.mean().reset_index()
plot_data = [
go.Bar(
x=df_plot['zip_code'],
y=df_plot['conversion'],
marker=dict(
color=['green', 'blue', 'orange'])
)
]plot_layout = go.Layout(
xaxis={"type": "category"},
title='Zip Code vs Conversion',
plot_bgcolor = 'rgb(243,243,243)',
paper_bgcolor = 'rgb(243,243,243)',
)
fig = go.Figure(data=plot_data, layout=plot_layout)
pyoff.iplot(fig)
5-转介
如下所示,来自推荐渠道的客户转化率较低:
它们显示出几乎少了 5%的转化率。
6 通道
正如我们预期的那样,多通道显示了更高的转化率。使用多个渠道是高参与度的标志。
7-报价类型
获得折扣优惠的客户显示出约 18%的转化率,而 BOGO 的转化率为约 15%。如果客户没有得到优惠,他们的转化率下降约 4%。
这些数据的特征工程将非常简单。我们将应用。get_dummies()** 将分类列转换为数字列:**
df_model = df_data.copy()
df_model = pd.get_dummies(df_model)
是时候建立我们的机器学习模型来评估转换概率了。
评分转换概率
为了构建我们的模型,我们需要遵循我们在文章前面提到的步骤。
让我们从分割特征和标签开始:
#create feature set and labels
X = df_model.drop(['conversion'],axis=1)
y = df_model.conversion
创建训练集和测试集:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=56)
我们将拟合模型并得到转换概率。我们模型的 predit_proba() 函数为每一行分配概率:
xgb_model = xgb.XGBClassifier().fit(X_train, y_train)
X_test['proba'] = xgb_model.predict_proba(X_test)[:,1]
让我们看看概率栏是什么样子的:
从上面可以看出,我们的模型为每个客户分配了转换概率(从 0 到 1)。
最后,我们需要了解我们的模型是否运行良好。
测试集的结果
现在我们假设,折现概率、bogo 和控制组的差异应该类似于它们之间的转换差异。
我们需要使用我们的测试集来找出答案。
让我们计算折扣的预测和实际订单增长:
real_disc_uptick = len(X_test)*(X_test[X_test['offer_Discount'] == 1].conversion.mean() - X_test[X_test['offer_No Offer'] == 1].conversion.mean())pred_disc_uptick = len(X_test)*(X_test[X_test['offer_Discount'] == 1].proba.mean() - X_test[X_test['offer_No Offer'] == 1].proba.mean())
对于实际上涨计算,我们使用了转换列。对于预测的那个,我们换成了 proba 。
结果相当不错。真正的订单上升是 966 ,模型预测为 948 (1.8%误差)。****
收入上涨预测对比: 24150 vs 23700。
我们需要检查结果是否对 BOGO 有利:
real_bogo_uptick = len(X_test)*(X_test[X_test['offer_Buy One Get One'] == 1].conversion.mean() - X_test[X_test['offer_No Offer'] == 1].conversion.mean())pred_bogo_uptick = len(X_test)*(X_test[X_test['offer_Buy One Get One'] == 1].proba.mean() - X_test[X_test['offer_No Offer'] == 1].proba.mean())
BOGO 有希望的结果:
订单上升-实际与预测: 563 与 595
收入增长—实际与预测: 14075 对 14875
误差率在 5.6%左右。该模型可以受益于提高对 BOGO 要约类型的预测分数。
计算转换概率在不同领域对我们也有很大帮助。我们已经预测了不同类型优惠的回报,但它也可以帮助我们找到最大限度提升的目标客户。在下一篇文章中,我们将构建自己的提升模型。
你可以在这里找到这篇文章的 Jupyter 笔记本。
需要帮助来发展你的公司吗?点击此处与我预约免费课程。
R (PCA & K-means 聚类)市场细分—第 1 部分
已经存在了几十年的市场研究方法的数据科学方法
什么是市场细分?
对于那些营销领域的新手,这里有一个方便的维基百科式的解释:市场细分是营销中使用的一个过程,根据客户的特征(人口统计、购物行为、偏好等)将客户分为不同的群体(也称为细分)。)同一细分市场的顾客往往对营销策略的反应相似。因此,细分过程可以帮助公司了解其客户群体,锁定正确的群体,并为不同的目标群体量身定制有效的营销策略。
个案研究
本文将通过使用 r 的样本调查数据集演示数据科学方法进行市场细分的过程。在本例中,便携式手机充电器制造商 ABC company 希望了解其细分市场,因此它通过调查研究从便携式充电器用户那里收集数据。调查问题包括四种类型:1)态度 2)人口统计 3)购买过程和使用行为 4)品牌认知。在这种情况下,我们将只使用态度数据进行细分。在现实中,决策者选择不同类型的输入变量(人口统计、地理、行为等。)基于它们的个别情况进行分段。尽管如此,无论您选择哪种输入,想法都是一样的!
(注:Thomas W. Miller 在他的书*营销数据科学:用 R 和 Python 进行预测分析的建模技术中提出了一个关于使用销售交易数据作为细分输入的很好的观点。*简而言之,他警告不要用销售交易数据进行细分,因为销售信息只对当前客户可用。当你有一个新客户时,如果没有他/她的销售数据,你很难利用你获得的洞察力。)
在我们深入研究方法和模型之前,请记住,作为一名负责任的数据分析师,首先要了解您的数据!
检查数据
# Importing and checking Dataraw <- read.csv(“Chargers.csv”)
str(raw)
head(raw)
Data Structure
A snippet of the data
我们数据中的每一行代表一个回答者,每一列代表他/她对相应调查问题的回答。共有 2500 名受访者和 24 个态度问题。所有这些都是评级问题,询问受访者对某一陈述的看法。答案在 1-5 的范围内。这里有一个例子:
请指出您对以下陈述的同意或不同意程度(1 =完全不同意,5 =完全同意)。
在购买便携式手机充电器时,我最看重款式。
…
理解了问题的本质,我们接下来可以验证数据集中的数据。编写一个简单的函数有时会有用:
# Verifying Data describe(raw)
colSums(is.na(raw)) #Checking NAs
table(unlist(raw[,]) %in% 1:5) #Simple Test
R 中的 validate 包也是一个方便的数据验证工具。它允许您根据自己创建的一组规则来测试数据。然而,我发现在处理大型数据集时,它并不是最方便的。我仍在寻找有效验证数据质量的替代方法(最好是系统)。我将非常感谢任何建议。
现在我们已经验证了我们的数据,我们对它们有信心,让我们继续更有趣的东西!
主成分分析
“维度缩减”这个术语曾经让我感到恐惧。然而,它并不像听起来那么复杂:它只是从无数数据中提取本质的过程,因此新的、更小的数据集可以表示原始数据的独特特征,而不会丢失太多有用的信息。可以把它想象成毕加索的立体主义绘画,他用几条线和几个形状优雅地抓住了一个物体的本质,忽略了许多细节。对我来说,我总是喜欢想起他的*吉他。*如果你有其他的作品,请评论!!
Guitar 1914 by Pablo Picasso
PCA 是一种降维形式。StatQuest 的这个视频(大声说出我最喜欢的统计/数据科学视频频道)非常直观地解释了这个概念。如果这是你第一次听说 PCA,我强烈建议你观看这个视频。简而言之,PCA 允许您获取具有大量维度的数据集,并将其压缩为具有较少维度的数据集,这仍然可以捕获原始数据中的大多数差异。
你会问,为什么 PCA 有助于将客户分成不同的群体?假设您需要根据客户对这些调查问题的回答来区分他们。您遇到的第一个问题是如何根据它们对 24 个变量的输入来区分它们。当然,你可以试着想出几个主要的主题来总结这些问题,并为每个主题给每个回答者分配一个“分数”,然后根据分数将他们分组。但是你怎么能确定你提出的主题在划分人方面是真正有效的呢?你如何决定每个问题的权重?再者,如果你有 5000 个变量而不是 24 个,你会怎么做?人脑根本无法在短时间内处理这么多信息。至少我的大脑肯定不能。
Photo by ME.ME on Facebook
这就是 PCA 可以介入并为您完成任务的地方。对我们的数据执行 PCA,R 可以将相关的 24 个变量转换成更少的不相关变量,称为主成分。有了更小的压缩变量集,我们可以轻松地执行进一步的计算,并且可以研究数据中一些最初难以发现的隐藏模式。
当有大量的文献/视频/文章提供了关于五氯苯甲醚的详尽解释时,我希望为那些认为这些材料过于专业的人提供一些关于五氯苯甲醚的高层次观点:
- 可变性使数据变得有用。想象一个有 10,000 个统一值的数据集。它没有告诉你太多,而且很无聊。😑
- 同样,主成分分析的功能是创建一个更小的变量子集(主成分),以捕捉原始的、大得多的数据集中的可变性。
- 每个主成分是初始变量的线性组合。
- 每个主成分彼此具有正交关系。这意味着它们不相关。
- 第一个主成分(PC1)捕获数据中最大的可变性**。第二主成分(PC2)抓住了第二多的**。第三主成分(PC3)抓住了第三最……等等****
此外,如果您计划为您的项目运行 PCA,您应该知道以下几个术语:
- 加载描述了原变量和新主成分之间的关系。具体来说,它描述了在计算新的主成分时赋予原始变量的权重。
- ****分数描述了原始数据和新生成的轴之间的关系。换句话说,score 是主成分空间中数据行的新值。
- ****方差比例表示每个主成分占总数据可变性的份额。它通常与累积比例一起使用,以评估主成分的有用性。
- ****累计比例代表累计由连续主成分解释的方差比例。所有主成分解释的累积比例等于 1(解释了 100%的数据可变性)。
在 R 中运行 PCA
在运行 PCA 之前,您应该查看一下您的数据相关性。如果您的数据不是高度相关的,您可能根本不需要 PCA!
# Creating a correlation plot library(ggpcorrplot)
cormat <- round(cor(raw), 2)
ggcorrplot(cormat, hc.order = TRUE, type = “lower”, outline.color = “white”)
Correlation Plot
如图所示,我们的变量非常相关。我们可以愉快地前往✌.的 PCA️
# PCA
pr_out <-prcomp(raw, center = TRUE, scale = TRUE) #Scaling data before PCA is usually advisable!
summary(pr_out)
PCA Summary
有 24 个新的主成分,因为我们首先有 24 个变量。第一个主成分占数据方差的 28%。第二主成分占 8.8%。第三种占 7.6%…我们可以用一个 scree 图来形象化这一点:
# Screeplot
pr_var <- pr_out$sdev ^ 2
pve <- pr_var / sum(pr_var)
plot(pve, xlab = "Principal Component", ylab = "Proportion of Variance Explained", ylim = c(0,1), type = 'b')
Scree plot
x 轴描述主成分的数量,y 轴描述每个主成分解释的方差(PVE)的比例。解释的方差在 PC2 后急剧下降。这个点通常被称为拐点,表示应该用于分析的 PC 数量。
# Cumulative PVE plot
plot(cumsum(pve), xlab = "Principal Component", ylab = "Cumulative Proportion of Variance Explained", ylim =c(0,1), type = 'b')
Cumulative Proportion of Variance
如果我们只选择 2 个主成分,它们将产生不到 40%的数据总方差。这个数字也许不够。
选择 PC 数量的另一个规则是选择特征值大于 1 的 PC。这被称为凯泽规则,这是有争议的。你可以在网上找到很多关于这个话题的辩论。
基本上,没有单一的最佳方法来决定电脑的最佳数量。人们出于不同的目的使用 PCA,在做出决定之前,考虑您想从 PCA 分析中获得什么总是很重要的。在我们的案例中,由于我们使用 PCA 来确定有意义且可行的市场细分**,我们应该明确考虑的一个标准是我们决定的电脑在现实世界和商业环境中是否有意义。**
解释结果
现在让我们挑选前 5 台电脑,因为 5 个组件并不太难处理,而且它遵循凯泽法则。
接下来,我们想让这些电脑有意义。还记得在计算新的主成分时,负荷描述了每个原始变量的权重吗?它们是帮助我们解释 PCA 结果的关键。当直接处理主成分分析负荷可能会很棘手和混乱时,我们可以轮换这些负荷以使解释更容易。
有多种旋转方法,我们将使用一种称为“varimax”的方法。(注意,这一步旋转不是 PCA 的一部分。它只是有助于解释我们的结果。这里的是一个很好的话题。)
# Rotate loadings
rot_loading <- varimax(pr_out$rotation[, 1:5])
rot_loading
Varimax-rotated loadings up to Q12
这是截至 Q12 的 varimax 旋转负载的不完整部分。表中的数字对应于我们的问题(原始变量)和所选组件之间的关系。如果数字为正,则变量对分量的贡献为正。如果是负的,那么它们是负相关的。数字越大,关系越密切。
有了这些数据,我们可以参考我们的调查问卷,了解每台电脑的功能。例如,我们来看看 PC1。我注意到 Q10、Q3 和 Q7 对 PC1 有负面影响。另一方面,我发现 Q8 和 Q11 对 PC1 有积极的贡献。查看问卷,我意识到 Q10、Q3 和 Q7 是与充电器的风格相关的问题,而 Q8 & Q11 关注的是产品的功能**。因此,我们可以暂时得出结论,PC1 描述了人们对产品功能的偏好。更看重功能的人可能不太在乎风格,这是有道理的。**
然后,您可以转到 PC2,按照相同的步骤解释每台 PC。我不会在这里介绍完整的过程,我希望你已经明白了。一旦你检查了所有的个人电脑,感觉每一台都描述了独特的、逻辑上连贯的特征,并且你相信它们具有商业意义,你就可以进行下一步了。但是,如果您觉得 PCs 中缺少或重复了一些信息,您可以考虑返回并包含更多的 PCs,或者您可以删除一些。您可能需要经历几次迭代,直到获得满意的结果。
我们完了!!
开玩笑的。但是你已经成功了一半。您已经完成了将大型数据集压缩为较小数据集的过程,其中包含一些变量,可以帮助您使用 PCA 识别不同的客户群。在下一篇文章中,我将介绍如何使用聚类方法,根据我们获得的 PC 对我们的客户进行细分。
最后,祝所有了不起的女超人#国际快乐👯👧 💁 👭!
感谢阅读!💚随时和我联系Linkedin!**
Zalando 的营销 A/B 测试
Zalando Office Tamara-Danz-Straße, Berlin-Friedrichshain
深入分析
使用聚类分析启用基于位置的 A/B 测试
供稿人:卡斯滕·拉希、托马斯·佩尔、马丁·卡斯滕、让·德·布雷西
Zalando 营销 A/B 测试分析的目标是得出营销行动的增量影响。这些分析的结果形成了在所有营销渠道中优化预算分配的基础,从而形成了高效的营销投资回报导向。这是我们在 Zalando 的需求计划&分析的 A/B 测试团队的任务。
我们的主要测试方法之一是地理实验,在这种实验中,我们将一个市场分成高度相关的区域组。测试组和控制组之间高度相关的销售行为是 geo A/B 实验的前提条件,因为测试和控制区域之间的低相关性会导致预测中的噪声,从而降低识别影响(如果有影响的话)的概率。芬兰是唯一一个营销渠道不以投资回报为导向的市场,因为基于地理位置的 A/B 测试在该国并不简单。这是因为大多数芬兰人生活在南部,仅乌西马地区就有约 30%的人口[1]。因此,订单总额的地区差异很大。芬兰北部地区的每日订单数量很少,因此在根据给定的地区总量(如联邦州、市)比较时间序列时,几乎不可能找到高度相关的组。
因此,我们需要为芬兰找到不同的位置定义。克服这个问题的一个解决方案是,与现有的区域定义相比,将国家分成更小的集群。以这种方式,可以增加区域分裂组合,因此也增加了发现“区域双生子”的可能性。这可以通过使用谷歌营销领域(GMA 的)来实现。但是,GMA 定义不适用于芬兰。
出于这个原因,我们创建了城市集群定义,以使芬兰可测试。这是通过 K-均值聚类分析方法实现的。
在城市位置数据中查找聚类
K-Means 聚类算法是在数据集中发现 K 个不同类别的相似对象的常用方法[2]。在这种情况下,聚类方法依赖于公开可用的城市位置数据[3],包括带有芬兰 317 个城市的纬度和经度信息的 GPS 坐标[图 1]。
Figure 1: GPS data of 317 Finnish locations, shown in a Cartesian coordinate system (left) and in a cylindrical projection map [4] (right).
为了显示芬兰城市的区域分布,笛卡尔坐标被转换为柱坐标,并通过地理点绘图仪绘制在芬兰地图上[4]。所使用的数据集仅限于由广告投放系统控制的城市列表,如谷歌和脸书。如果输入数据还包括不可操纵的城市,则可能存在由于不干净的 A/B 测试分割而导致聚类结果不可用的风险,并且聚类内的观测值数量可能不足。
除了原始数据之外,算法[图 2]需要聚类数 K 作为输入。原因是,聚类是一种无监督学习方法【5】*,*意味着算法“从未标记的数据中发现[s]隐藏的结构”[6],并且不会自动导出最佳 K 参数[7]。
Figure 2: K-Means Clustering Algorithm.
定义 K 参数后,K 均值聚类算法分 3 步进行[8]:
- 在随机位置初始化 K 个聚类中心(质心);
- 基于数据点和质心之间的最小欧几里德距离对数据进行分组;
- 通过平均分配给相应聚类的所有数据点来重新计算聚类中心。
最后两步迭代重复,直到算法收敛到稳定的聚类分配。当簇内方差不能再降低时,达到收敛标准[7],使得簇尽可能紧凑[9]。
更正式地说,给定 k 个簇的数据点{x1,…,xn}和质心{c1,…,ck},这意味着“最小化[…]平方误差函数”[7]:
换句话说,等式 1.1 的目标是最小化所有组中聚类内距离平方和的总和。收敛的阈值是 1e-4 [10]。该表达式
上式中是欧氏距离函数,也可以写成[11]:
欧几里德距离是距离测量的常用度量,定义为“两个向量[11] x 和 c 的对应元素之间的平方差之和的平方根”。该度量用于根据数据点之间的最小距离将数据点分配到最近的质心[12]。
值得一提的是,该算法在任何情况下都收敛于局部最小值,而不一定是全局最小值[12]。这意味着不能保证当前结果是可能的最佳输出,因为初始质心的随机选择会导致每次运行的不同聚类结果。为了找到一个可能更好的结果,算法执行应该重复几次,如图 3 所示。
Figure 3: Clustering score for a fixed K (K=7) after 1000 iterations.
图 3 表明,平方距离之和(SSE)在 1000 次迭代步骤中并不完全稳定,这意味着由于随机起始参数,每次运行都可能产生略微不同的 SSE。
选择最佳聚类数
K 参数的值决定了聚类的数量,从而也决定了数据点向聚类的分配。在我们的例子中,“肘方法”被用作估计最佳聚类数 k 的常用技术。第二步,我们通过比较聚类之间的阶数来验证肘方法的结果。平衡聚类的订单量非常重要,因为太小的聚类产生的噪声数据会降低在 geo A/B 测试中获得显著结果的可能性。“弯管法”包括以下步骤[13]:
- 针对不同的 K 值对数据集执行 K 均值算法,在这种情况下,K 的范围在 1 和 20 之间;
- 计算每个 K 的城市和质心之间的 SSE
- 在折线图中绘制结果。
SSE 随着 K 参数的增加而减小。所选择的 K 参数应该在它的值和 SSE 仍然很小的点。有一个 K,上证指数的下跌速度急剧变化,曲线开始变平。这是最佳的 K 参数,称为“肘点”。
Figure 4: Clustering score for a range of different K parameters.
图 4 显示,在这种情况下,拐点并不明显,但可以考虑 K 参数的范围,因为在曲线开始变平之前,从 1 到大约 8 的 K 值越小,SSE 下降的速率越高。为此,对不同 K 参数的这个值范围执行 K 均值算法。这样,通过比较相应的聚类结果并选择 K 值,可以找到最佳 K 参数,该 K 值产生总订单量的聚类内方差尽可能低的聚类分配。组内方差越低,高度相关组的概率越高,从而在 geo A/B 测试中检测到潜在影响的概率越高。
由于至少需要两个集群作为控制或测试组,K 参数 1 可以忽略。介于 2 和 6 之间的 k 值导致城市分组不理想,这反映在聚类之间的订单量差异很大。群集有序级别的高变化意味着该国被划分为人口密集的地区和农村地区。由于地区之间销售行为的差异较大,这可能导致较低的聚类内相关性,从而导致检测潜在影响的可能性较小。对于大于 6 的 K 值,最大的城市坦佩雷和赫尔辛基被单独分组,并且每个聚类的订单数更加均衡,这增加了高度相关的聚类的机会。
K 参数 K=7 被证明是所用数据集的最佳值,因为 K=8 的 K 参数不会导致聚类输出的进一步改善。原因是,这只是导致芬兰北部进一步分裂成一个额外的集群,这意味着该国人口较少的部分被分成更小的集群。结果是订单量的群内方差再次增加。
K 参数的验证
由于初始质心是随机定位的,该算法每次运行都会产生不同的结果[14]。几次迭代之间的平方距离之和的偏差大小可以指示聚类算法的稳定性。为了弄清楚这一点,该算法已经执行了 1000 次,并且对每次运行都绘制了平方距离的总和(见图 3)。图 3 中绘制的线表明,虽然每次运行的算法输出不相同,但是 1000 次迭代之间的平方距离之和的变化很小。这意味着聚类结果仅略有不同,并且对于 K=7,聚类分数相当稳定。
为了分析几次运行的聚类结果之间的聚类分配的可变性,通过对 K=7 的固定 K 值重复该算法,产生了 6 个聚类输出(图 5)。
Figure 5: Clustering results of 6 runs of the K-Means algorithm for K=7 with the 7 city clusters and their centroids (marked as stars).
比较的角度是在 6 次算法运行中质心位置的可变性,以找出哪些聚类变化导致了聚类分数的小波动。聚类输出的比较表明,质心的位置在 6 次运行中保持相当稳定。这意味着聚类分配仅针对几个单独的城市而改变,最大比例的数据点在迭代中保持在同一个聚类中。
只有主要包括拉普兰地区的最北部集群显示出质心位置的可见变化。这是因为该区域的观测数量较少,导致它们之间的距离较大。因此,1000 次迭代之间的平方距离之和的变化(如上所述)可能主要是由位于北部的城市的相对不稳定的组分配引起的。如下所示,由于这些城市的聚类将与其相邻的聚类进行分组,因此可以忽略聚类分配的变化。
聚类结果:可测试的地理分割
K-Means 分类产生了七个城市集群及其中心(图 6 左侧)。为了确保每个聚类中的日订单量处于可测试的水平,五个较小的聚类被分组以形成一个较大的区域(图 6 右侧)。
Figure 6: Result of the K-Means Cluster Analysis, showing the output with 7 clusters (left) and the final grouping into 3 geo testing regions (right).
每日订单数量越少,数据中出现噪音的风险就越高。噪声数据的后果是聚类之间的相关性较低,因此检测潜在影响的概率较低。
结果,我们得到了三个地理分割区域:“赫尔辛基”(区域 1)、“坦佩雷”(区域 2)和“芬兰北部”(区域 3)。这种区域分割证明是一种可测试的设置,因为在 2018 年 10 月至 2019 年 1 月中旬的时间段内,区域时间序列之间的相关性达到约 98%的值(就总订单而言)。此外,在计算 2018 年全年的相关性时,这些值大致保持在相同的范围内(96%-98%)。
由于赫尔辛基是订单总量最大的地区,因此被分配到试验组,坦佩雷和芬兰北部被分配到对照组。测试区域应该具有最高的总订单份额,因为这是被测试通道被打开的组。这确保了测试活动在全国大部分地区运行,从而尽可能减少 geo A/B 测试的总印象数。这对于保证测试后的最佳活动范围非常重要,因为测试总是要求将一些地区从活动目标中排除。
为了验证这种分割,对这些区域进行了可能影响的测试。赫尔辛基(试验组)与两个地区坦佩雷和*芬兰北部(对照组)*之间的相对效应差异通过抬升分析进行测试。分析得出 0.2%的不显著效应(52%显著性),两组之间的相关性为 99.5%。该结果证实了高度相关的总顺序行为,并且在测试之前,组之间没有显著差异,因为测量的效果接近于零,并且明显低于 90%的显著性水平。
聚类结果用于实施芬兰的首个地理分割设置。本次地理测试的目的是测量 2019 年 Zalando 赛季开始活动期间展示计划的增量性能。赫尔辛基区域被定义为测试组,在该测试组中,被测通道被打开并运行 4 周。另外两个区域坦佩雷和芬兰北部被设置为控制区域,这意味着在相应的城市中没有打开显示编程。测试停止后,可以计算对总订单的增量影响,这是测试组中观察到的数据和模型预测之间的累积每日差异。
这是 Zalando 芬兰投资回报指导的开始,因为该国的营销预算分配可能首次基于 A/B 测试结果。
局限性和后续步骤
在 2019 年 Zalando 赛季开始活动期间,对显示程序性地理分割测试的分析产生了重要而可信的结果。这意味着所示方法已被证明是在芬兰为 geo A/B 测试创建测试和控制区域的有用方法。尽管使用的测试设置产生了高度相关的组,但是仍然有优化集群的空间。考虑到当前的划分只包含 3 个组,并且尽管进行了测试,但仍应确保最佳的活动范围,因此重新分组的选项非常有限。这增加了发生系统性错误的风险,因为赫尔辛基地区可能会由于大都市和农村地区的人之间潜在的不同行为而导致大都市效应。重新聚类和使用具有更多可测试组的更可变的聚类有助于克服这个问题。为此,人口最稠密的地区赫尔辛基和坦佩雷可以进一步划分成更小的集群。更多数量的区域增加了区域分割组合,从而增加了 geo A/B 测试能力。
参考
[1]欧盟委员会, 赫尔辛基-Uusimaa 地区(2019 年 2 月 15 日),区域创新监测 Plus。
[2] M. Khan, KMeans 聚类分类(2017 年 8 月 2 日),走向数据科学。
[3] 芬兰城市&城镇人口(2004–2019),Tageo -地理坐标信息。**
[4] D .沃特金斯,地理点绘图仪。一个快速绘制出地理坐标列表的工具 (n. d.) 。
[5] P. Sayak,K-用 scikit 表示 Python 中的聚类-learn(2018 年 7 月 5 日),DataCamp 教程。
[6] G .塞尔丁, 新课程:Python 中的无监督学习(2017 年 2 月 22 日),DataCamp 教程。**
[7] S. Sayad,《数据科学导论》。K-均值聚类(2010–2019)。**
[8] F. Doukkali, 利用 K-means 算法进行聚类 (12/19/2017),走向数据科学。
[9] B. Boehmke, UC 商业分析 R 编程指南。 K-means 聚类分析 (n. d .),辛辛那提大学,大学讲座。**
[10]sci kit-学习开发者。sk learn . cluster . k means(2007–2018)。**
[11] S. Borgatti, 距离与相关性(2007 年春季),多元统计,波士顿学院,大学讲座。
[12] A .特雷维尼奥,学习数据科学,机器学习。K-means 聚类简介(2016 年 12 月 6 日)。**
[13] R. Gove, 利用肘方法确定 k-means 聚类的最优聚类数(2017 年 12 月 26 日),Robert Gove’s Blocks。**
[14] M. V. B. T. Santhi,V. R. N. Sai Leela,P. U. Anitha,& D. Nagamalleswari,增强 K 均值聚类算法 (2011)。国际计算机科学杂志&技术,IJCST , 2 (4),73–77。
营销分析——任何人都可以做到
Marketing Analytics
就在几年前,与营销分析相关的工作还很少。那么现在,这个行业正在蓬勃发展,就业机会正在上升。原因可能是数据科学被广泛采用后,与市场营销自然结合。而且因为直接关系到一个公司的销售业绩,所以越来越受到重视。
我经常收到这些问题。营销分析是做什么的?我该怎么做?营销分析的职业道路是什么?本文就是要解决这些问题。
- 营销分析通过分析营销数据优化公司投资回报率。
- 任何拥有数据分析技能、营销和产品意识的人都可以从事营销分析工作。
- 营销分析部门的工作范围很广,有能力领导一家公司的营销分析职能。
这份工作主要服务于公司的营销部门。就是通过分析各类营销数据来优化回报率,以指导公司的资源配置。营销分析在我看来可以分为两个分支:客户生命周期分析和营销渠道分析(见下图)。
Image Copyright: Dr. Alan Zhang
营销的本质是面对和服务消费者。每个消费者都有他或她的生命周期。因此,营销分析的一个重要分支是客户生命周期分析。就是围绕消费者进行数据分析,产生洞察来指导营销活动。具体来说,它包括市场细分、消费者终身价值分析、获取新客户、维护老客户和提高客户参与度等分析。其中,企业对企业的营销(B2B 营销),由于其购买过程的独特性,会有一些特定的分析,如需求漏斗分析,线索评分,等等。
营销分析的另一个重要分支是营销渠道分析。营销信息的传播依赖于渠道或媒介,如何高效地让消费者接收信息非常重要。信息的制作和渠道的部署需要资金和人力。因此,每个公司的当务之急是分配资源,使投资回报最大化。要做到这一点,需要一系列研究和分析的支持。
信息从渠道传播出去后,我们需要通过技术进行跟踪,衡量每一条信息/渠道的效果。一般来说,消费者在购买之前会通过多种渠道收到多种营销信息。我们需要准确地归因,以帮助公司获得正确的反馈。在这上面,我们会用一些统计模型比如营销组合建模,从各种因素中剔除无关信息,得到相应的影响参数。这些参数最终将用于优化资源分配的决策。
因为每个渠道/媒体都有其独特性,企业往往需要通过数据分析来优化渠道。例如,在电子邮件营销中,何时发送给哪些消费者会影响打开率和点击率。随着消费者在网上花费越来越多的时间,数字营销分析已经成为许多公司的首要任务,涉及搜索引擎优化、社交媒体优化等分析。程序化广告也是一个方向,目标是优化曝光,最大化转化率。
相关技能有哪些?
在最高级别,它是使用分析方法在复杂多样的数据中发现模式的能力,并生成可供营销人员决策使用的商业见解。我具体阐述如下(见下图)。
Image Copyright: Dr. Alan Zhang
第一,你要有分析常见营销问题的能力。与咨询行业的案例研究一样,它遵循一个特定的框架。例如,电子邮件营销团队需要您帮助开展一项活动。首先,你可能想确定事件的性质,是品牌曝光、促销还是事件。第二,你可以决定哪些指标需要优化,是打开率,点击率,购买率,还是用户参与度。再者,你可以确定目标群体是谁,是潜在客户还是长期不购买的老客户。还有,活动举办后,你需要确定用什么方法来分析活动的效果和影响范围。一些传统的分析方法是细分、群组分析、客户终身价值等。
第二,你应该具备基本的统计学知识,以及建立统计学或机器学习模型来解释和预测行为的能力。扎实的统计知识确保产生的业务洞察具有统计意义,例如 A/B 测试。各种类型的机器学习模型可以帮助你从数据中快速提取模式,预测未来的行为。仅仅知道如何使用一个包或者编写算法是不够的。更重要的是能够提出正确的业务问题,并使用这些工具来解决它们。此外,一些具体的营销分析方法,如营销组合模型和流失模型,也是很好的了解。
再者,你也要有技术能力。它包括使用 SQL 从数据库中提取有用的信息,以及使用 R 或 Python 处理数据和建立预测模型。知道数据可视化工具也是很可爱的,比如 Tableau,Looker,PowerBI 等。,并将分析结果呈现给职能合作伙伴。知道如何使用第三方管理工具更好,例如用于网站流量跟踪的 Google Analytics、用于 CRM 的 Salesforce 等等。
最后但同样重要的是,最关键的能力是向你的听众简明扼要地传达复杂的技术问题,以影响决策。如图 1 所示,营销分析服务于营销职能,但也需要与其他部门合作,如销售、产品、工程等。因此,你能否让招聘经理相信你能有效沟通,将是需要评估的软技能。
如何获得这些技能?
熟能生巧!你可以在网上找资源学习基础知识,比如网络课程、博客、书籍等。,并做一些相关的项目。如果你能找到在该领域更有经验的同行作为导师,学习将更有效率,并有助于避免一路上走弯路。
作者简介:Alan Zhang 博士以前是一名市场营销教授,他的研究兴趣是电子邮件营销、客户生命周期管理和客户终身价值。《哈佛商业评论》报道了他对客户温贝克的研究。他现在是 GitHub 的营销分析经理。
在业余时间,他教授营销分析的各种主题。他在 Udemy 上有一门关于客户终身价值的入门课程。补充这篇文章的是 PASS(数据专业人士社区)邀请他做的一次演讲,主题是营销分析:为什么、做什么和如何做。
营销分析:客户 EDA 回归
最近,我一直在做一些数据科学营销咨询,并希望分享一些在此过程中应用的技能。在本文中,我们将回顾探索性数据分析(EDA ),以及应用逻辑回归对客户进行的营销活动。
在营销活动中,客户参与度是衡量营销努力的一个关键指标。例如,电子邮件客户参与度可以通过打开或未打开的电子邮件数量来衡量[1]。有利可图的营销活动将创造大量的参与度。另一方面,糟糕的营销会让顾客远离你的生意。我们都经历过——收到烦人的广告。有时,当我确实需要他们的服务时,我会从他们的直接竞争对手那里购买。
EDA 帮助我们理解为什么要使用数据。借助 EDA,您可以分析对结果至关重要的驱动因素。回归是一种工具,可以用来检查驱动因素和预期结果之间的关系。我们将回顾逻辑回归模型来分析是什么创造了更好的客户参与度。有趣的数据科学访谈事实上,逻辑回归是为寻找二元结果而创建的(例如,是/否、癌症/没有癌症、已购买/未购买等)。) [2].
加载数据
#Load up packages and data
import matplotlib.pyplot as plt
import pandas as pd
import statsmodels.api as smmdata = pd.read_csv('WA_Fn-UseC_-Marketing-Customer-Value-Analysis.csv')
#How big the data set is:
mdata.shape
(9134, 24)
#Taking a look at the values
mdata.head()
客户参与的期望输出是 Response 列,它不会被转换为数字。逻辑回归模型喜欢用数字来表示值,所以我们必须帮助它。
#Converting our target/output variable into a numerical
mdata['Engaged'] = mdata['Response'].apply(lambda x: 0 if x == 'No' else 1)
检查参与率
接下来,我们将检查参与率,即接触过我们营销的客户的百分比。
engagement_rate_mdata = pd.DataFrame(mdata.groupby('Engaged').count()['Response'] / mdata.shape[0] * 100.0)
engagement_rate_mdata
有更多的客户没有参与我们的营销,所以从原始数据进行分析是很困难的。为了使分析更容易,我们将构建饼图。
engagement_by_sales_channel_mdata.plot(kind='pie',figsize=(15, 7),startangle=90,
subplots=True,autopct=lambda x: '%0.1f%%' % x)plt.show()
从这些图表中可以看出,约一半的参与客户来自代理商,而未参与客户分布在不同的渠道。
索赔总额
在我们开始回归分析之前,我们先来看看箱线图中的总索赔额。
ax = mdata[['Engaged', 'Total Claim Amount']].boxplot(
by='Engaged',showfliers=False,figsize=(7,5))ax.set_xlabel('Engaged')
ax.set_ylabel('Total Claim Amount')
ax.set_title('Total Claim Amount Distributions by Engagements')plt.suptitle("")
plt.show()
箱线图是查看连续变量分布的好方法。矩形代表第一个四分位数到第三个四分位数,绿线代表中位数。末端是最小值和最大值。showfliers=False 允许我们发现可疑的异常值,如下所示:
ax = mdata[['Engaged', 'Total Claim Amount']].boxplot(
by='Engaged',showfliers=True,figsize=(7,5))ax.set_xlabel('Engaged')
ax.set_ylabel('Total Claim Amount')
ax.set_title('Total Claim Amount Distributions by Engagements')plt.suptitle("")
plt.show()
圆点是基于四分位距(IQR)的可疑异常值。可疑异常值的公式是第三个四分位数以上 1.5 IQR 或第一个四分位数以下 1.5 IQR。
回归分析
在回归中,特征变量需要是连续的,因此可以找到特征的线性组合来估计输出变量。现在,让我们检查一下特征变量,它们符合我们的逻辑回归模型。
mdata.dtypes
连续变量是没有“对象”数据类型的变量。
continuous_vars = ['Customer Lifetime Value', 'Income', 'Monthly Premium Auto',
'Months Since Last Claim', 'Months Since Policy Inception',
'Number of Open Complaints', 'Number of Policies', 'Total Claim Amount']
接下来,我们需要将分类变量转换成数字变量。一种方法是因式分解。
gender_values, gender_labels = mdata['Gender'].factorize()
print(gender_values)
print(gender_labels)
在因式分解中,变量变成了 1 或 0。但是如果顺序很重要呢?我们可以应用分类函数。
categories = pd.Categorical(
mdata['Education'], categories=['High School or Below', 'Bachelor', 'College', 'Master', 'Doctor'])
现在,数字 0、1、2、3 和 4 分别适用于高中或以下、学士、大学、硕士和博士的教育。这将允许我们将数据放入逻辑模型中。
mdata['GenderFactorized'] = gender_values
mdata['EducationFactorized'] = categories.codes
让我们把分类变量和连续变量结合起来!
logit = sm.Logit(
mdata['Engaged'],
mdata[['Customer Lifetime Value','Income','Monthly Premium Auto',
'Months Since Last Claim','Months Since Policy Inception','Number of Open Complaints',
'Number of Policies','Total Claim Amount','GenderFactorized','EducationFactorized']])logit_fit = logit.fit()logit_fit.summary()
z(z-score 的缩写)是平均值的标准偏差数[3]。P>|z|(表示 P 值)表示偶然观察到关系的可能性。通常,0.05 是 p 值的标准临界值,小于 0.05 的值意味着输入和输出变量之间的这种关系发生巧合的可能性较小。例如,在数字变量中,我们可以看到收入、每月保费汽车、自上次索赔以来的月数、自保单开始以来的月数以及保单数量变量与参与度(输出变量)有显著关系。如果我们观察自上次索赔以来的月份变量,它是显著的(p 值非常低),并且与敬业度负相关(z 值为负)。换句话说,随着索赔时间的推移,客户不太可能参与营销。
从分类变量中,我们可以看到男性(0)不太可能从事营销,这同样适用于较低的教育水平(0 代表高中,4 代表博士)。
结论
很好,现在你有了另一个 EDA 工具——逻辑回归。作为总结,我们以表格形式检查了接洽率,以饼图形式检查了销售渠道以便于解释,以箱线图形式检查了总索赔额以查看范围和潜在异常值,并通过回归分析发现了强劲的趋势。现在,您可以利用逻辑回归来隔离趋势,然后将其输入到另一个机器学习模型中,而不是在输出中使用逻辑回归作为预测模型!
边注:如何堆叠机器学习模型的例子可以看这里: https://towardsdatascience . com/machine-learning-pipelines-nonlinear-model-stacking-668 f2b 720344
免责声明:本文陈述的所有内容都是我个人的观点,不代表任何雇主。
参考
[1] Marketo,参与营销(2019),https://www.marketo.com/engagement-marketing/
[2] S. Swaminathan,logistic Regression(2018),https://towards data science . com/logistic-Regression-detailed-overview-46 C4 da 4303 BC
[3] Y. Hwang,《营销数据科学实践》( 2019 年),派克特出版社