使用 Flexdashboard 在 R 中构建 HR 仪表板
The HR Dashboard
假设你是一名人力资源经理,你的老板要求你为首席执行官准备一份关于本年度公司员工招聘和流失的演示文稿。哦,你很兴奋!你已经在这个项目上工作了一段时间,并且已经整理好了所有的数据。你迫不及待地想用你的洞察力让 CEO 眼花缭乱。但是等一下,她非常忙,而你只有五分钟的时间。你想给她看的东西时间太少了!
你是做什么的?什么工具对你最有帮助?你会带着 25 张幻灯片参加 5 分钟的会议吗?您是否会将多个 Excel 工作簿投射给一个擅长分析简明数据的人?
请记住:
枪战时不要带刀
在这种情况下,我的朋友,你唯一需要的枪就是仪表板。当您希望可视化大量复杂数据,并向用户提供与特定目标或业务流程 相关的关键绩效指标的一览视图时,仪表板是完美的工具。
r 给了我们漂亮的软件包“flexdashboard ”,它为我们提供了一个易于创建的动态仪表板。我们将在一个 R markdown 文件中写出代码,这是一种在 R 中创建动态文件的文件格式
以下是我们将要介绍的内容:
- 仪表板结构概述
- 创建一个 R 降价文件
- 使用 flexdashboard 的仪表板布局
- 使用 dplyr 进行数据操作
- 使用 plotly 合并情节
仪表板结构概述
假设您的公司在三个地区拥有子公司:
- 美洲
- APAC(亚太地区)
- 欧洲
因此,我们有三个区域文件(Excel 工作簿),其中包含相关的数据字段,如姓名、国家、电子邮件地址等。区域团队为每个新员工和离职(自然减员)输入数据。我们需要一个仪表板,它能有效地为执行管理层可视化这些信息。这包括月度趋势、跨国家和内部职能部门的划分等。
您可以在这里访问包含模拟数据的文件:https://github . com/sagarkulkarny/HR-Movement-Dashboard-using-flex Dashboard-
这个想法是读取单个文件,合并它们,然后操纵数据得到图。所以让我们开始吧!
创建一个 R 降价文件
打开 RStudio 并创建一个新的 R 脚本文件。使用install.packages(“flexdashboard”)
安装 flexdashboard 库
软件包安装完成后,通过选择“文件”->“新建文件”->“R markdown”创建一个新的 R Markdown 文件,如下所示:
Fig. 2.1: Create a new R markdown file
系统会提示您输入文件名。现在,如果你想为任何目的创建一个降价文件,你可以点击 OK,你就可以开始了。
Fig. 2.2: Creating a new R markdown file
然而,Flex Dashboard 也为我们的仪表板提供了一个模板。如果您选择“来自模板”,然后选择“Flex Dashboard”,您将获得一个新的带有占位符的降价文件,如下所示:
Fig. 2.3: Selecting flex dashboard template
Fig. 2.4 flex dashboard template
您可以看到该文件没有标题,因此我们必须将它保存在我们的系统中。将它保存在与我们的区域文件相同的文件夹中是有意义的,但是您可以将它存储在您喜欢的任何地方。保存文件后,我们可以试着运行它,看看模板占位符有什么作用。
通常,为了运行 R 脚本,我们只需选择代码并在控制台中运行它。然而,R Markdown 文件在控制台之外运行*,因为它们根据文件输出 HTML 内容或 PDF 或 word 文件。要查看这个输出,我们需要通过点击文件顶部附近的编织图标或按下Ctrl+Shift+K
来"*编织"文件。
Fig 2.5: Basic layout using the template place holders
恭喜你!我们刚刚创建了第一个仪表板布局!它什么都没有的事实应该无关紧要!
使用 flexdashboard 的仪表板布局
在这一步中,我们将决定仪表板的布局,并通过在 R markdown 文件中编码来实现它。
我们先概念化一下布局。我们可以有一个单页的仪表板,但这将意味着在一个小空间里塞满了大量的信息。Flexdashboard 为我们提供了一个选项,通过使用===
标题或仅使用#
来划分代码中的部分,让多页出现在我们的仪表板上。让我们在模板中尝试一下。让我们在三个位置添加多个页眉:第一个Column {}
前的“第一页”,第二个Column{}
前的“第二页”,最后的“第三页”。要命名页面,您只需在===
标题上方的行中写下名称,如下所示:
编织代码现在在仪表板上显示三页,第一页包含图表 A,第二页包含图表 B 和 C,第三页是空白的。你能相信给仪表板添加“多页”功能如此简单吗?简单是 R Markdown 和 flex dashboard 的旗舰工具之一,使用 R Markdown 意味着您可以简单地完成许多令人惊叹的事情。
在仪表板上有一个主页来显示新员工和自然减员的要点,并为新员工和自然减员提供单独的页面是有意义的,如果我们想深入了解一下,可以去那里。让我们相应地重命名我们的页面,即第 1 页为“Dash”,第 2 页为“New Hires”,第 3 页为“Attrition”。
现在,在一个页面中,布局方向基于行或列(或行和列)。可以使用---
标题或##
添加新的行或列。在这个项目中,我将使用行布局。为此,我们将对代码进行两处修改:
- 将块封装在顶部的
---
中的 YAML 标题中的方向从列改为行 - 将列/行标题中的所有列更改为行
The YAML header
Column changes to Row
您可以在这里查看可能的选项:https://rmarkdown . r studio . com/flex dashboard/using . html # layout
布局的下一步是放置实际的图表。我们可以为地块创建空间,并使用###
标题后跟空间名称来命名它们。这些空间被称为盒子,你可以在上面的代码中看到名为图表 A、图表 B 和图表 c 的例子。
总结布局标题:
- 多页页眉或一级页眉:
===
或#
- 行列表头或二级表头:
---
或##
- 箱式表头或三级表头:
###
数据操作
现在我们已经有了布局,在开始在仪表板上绘制图表之前,我们需要将数据整理好。到目前为止,我们所有的代码都是 HTML 布局。但是,我们在哪里编写负责所有幕后工作的实际 R 脚本代码呢?
在 R markdown 中,这样的代码需要由分隔符````r{}` 和`````封装,也称为代码块分隔符。R markdown 的工作方式是将代码块末尾的结果嵌入到 HTML dashboard 输出中。
在您的新文件中,您可以看到有四个这样的代码块。第一个代码块在已经有library(flexdashboard)
的{r setup}
下。我们将使用这个块来编写代码,以读取和合并我们的区域文件。我们将使用其他块为情节编写代码。
让我们从调用所有必需的包并读取我们各自的数据文件开始:
Packages and reading the data files
上面的代码还读取一个文件“country coord ”,该文件包含数据中国家的经度和纬度数据。在绘制世界地图的时候会很有用。
rbind()
函数合并来自三个文件的数据,并将它们一个贴在另一个之上,以创建一个全局数据集。建议去过一遍资料,熟悉一下结构。注意数据的类型(连续的、离散的)、列的值(例如,进入/退出在列“运动类型”中被捕获,我们将广泛使用该列来过滤数据)等。
现在我们已经有了必要的数据,我们可以开始用可视化填充我们的仪表板。我们第一页“Dash”上的第一个组件是一个值框。
一个值框就像一个标签,显示一个简单的值和一个图标。这些是我们仪表板中的价值箱:
Fig. 3.1 Value box
我们将在第一个代码块中编写这些代码,如下所示:
让我们一行一行地回顾一下。
- 您会注意到 Dash 页面标题的标签旁边有一个小小的地球图标。那是由
{data-icon=“fa-global”}
获得的。“fa”代表字体牛逼这是一个免费图标的网站。您还可以查看所有受 Flex Dashboard 支持的免费选项。 - 注意不同的输出使用了不同的标题(页面、列和代码块)。
- 使用 dplyr 的过滤函数和管道运算符计算新雇员数
ValueBox()
函数接受数值、图标、标题和颜色参数。注意页眉中类似于地球图标的图标的“fa-user-plus”
值。- “净变化”值框的逻辑略有不同。由于净变化(新聘人数-离职人数)可能是正数,也可能是负数,我想相应地改变图标,即向上箭头表示正净值,向下箭头表示负净值。这是通过使用 r 中的
if-else
循环完成的
使用 plotly 合并情节
在值框之后是我们的两个图:趋势图和世界地图图。
Fig 3.2: Movement by month and by region plots
让我们看看下面的代码:
对于“按月移动”图,我们首先按“月”和“移动类型”对数据集进行分组,然后进行总结。这将创建下表:
Fig. 3.3: Tibble for “Movement by Region” plot
我们先试着在脑海中构建剧情。我们将月作为 x 轴,计数作为 y 轴,两条线显示趋势,一条代表进场,一条代表出场。一旦我们框定了这个顺序,使用 plotly(或 plot_ly()编写代码块)创建一个情节就非常简单了。
我们需要记住这一点:
- 它试图建立在 ggplot2 所使用的图形语法之上。因此,每个后续函数都将前一个函数作为第一个参数。因此,我们需要为 plotly 函数使用管道操作符
%>%
来代替 ggplot2 中使用的+
- 因此,创建上述情节的最简单方法是:
我们包含了hoverinfo
和text
参数,当鼠标指针悬停在图上时,该图显示计数。
此外,记住通过调用 plot 来结束代码块,在本例中是p1
,否则仪表板输出上的绘图空间将保持空白。
我们“Dash”页面上的最后一个图是世界地图图。这个绘图函数将地理坐标(经度和纬度)作为 x 和 y 坐标。因此,我通过执行左连接将“country coord”excel 工作簿中的两列添加到全局数据集。对此的解释包含在代码中。
在 plotly 中指定世界地图的方法是将locationmode
参数设置为“world”
。然后我们将 long 和 lat 值分别作为x
和y
坐标传递。圆的size
设置为count
的值,而color
设置为Entry
或Exit
。
不知不觉中,您已经完成了我们仪表板的整个“Dash”页面!
Fig. 3.3: The “Dash” page
与 ggplot2 相比,使用 plotly 的一个好处是前者为我们提供了图形的动态表示,我们可以在其中切换标签,图形相应地做出响应。例如,点击“按月移动”图图例中的“退出”,取消选择它,只显示“进入”趋势。
Fig. 3.4: Dynamic nature of plotly plots (you can see that “Exit” has grayed out in the legend)
我填充了接下来的两页,在这两页中,我加入了几个甜甜圈图,作为对通常的值框和条形图的补充。逻辑非常简单,与上面解释的相似。
Fig. 3.5: New Hire page
Fig. 3.6: Attrition page
结论
祝贺您创建了第一个仪表板!我希望这是以后更多的开始!
如果您有兴趣进一步研究这个问题,您可以在此处访问整个rmd
文件和支持工作簿:https://github . com/sagarkulkarny/HR-Movement-Dashboard-using-flex Dashboard-
Flex Dashboard 包含了很多我在这篇文章中没有涉及到的组件,比如标尺、导航条、故事板等等。它是在 r 中创建仪表板的更易于使用和直观的包之一。
我希望我至少能够对所涉及的概念进行一些澄清。不言而喻,我们欢迎并感谢所有的建议和反馈。
参考
- https://rmarkdown.rstudio.com/flexdashboard/using.html
- https://rmarkdown.rstudio.com/lesson-1.html
- 【https://plotly-r.com/
附:这是我的第一篇帖子,我感谢大卫·罗宾逊方差解释、马纳利·辛德、德里克·姆维蒂、朱利安·塔格尔和卡特诺里亚他们令人振奋的文章。如果你喜欢我的帖子,你也会喜欢他们的!
使用单词和句子嵌入构建新闻文章的图像推荐系统
Source: https://miro.medium.com/max/1200/1*9B7S9mipwLURUX9wdZN7Pw.jpeg
作者:Karina Huang、Dianne Lee、Abhimanyu Vasishth 和黄家仪(按姓氏字母顺序排列)
本文所表达的观点仅代表作者的观点,不代表美联社的观点。
简介
这篇博文展示了我们在哈佛 AC297r 课堂上与美联社 (AP)合作的顶点项目的一部分。我们为任何给定的新闻标题建立了一个文本到图像的推荐系统。该项目的目标如下:
给定一篇文章的标题,使用标题推荐一组最匹配的图片。
由于机器学习方法不能直接优化文本,我们需要一种方法来将文本转换为数字表示。对于这项任务,我们使用单词嵌入,这是一种可以将单词表示为数字向量的方法。
在下面的部分中,我们使用两种不同类型的嵌入来讨论我们的方法 1)平均手套单词嵌入,和 2)通用句子编码器(USE)来编码文章标题和图像标题,随后是推荐过程和例子。
数据
由于我们用于项目的数据是我们的行业合作伙伴的专有数据,为了这篇博文的目的,我们使用了一个包含新闻文章及其相关图像的公开数据集,以使我们的方法具有可重复性。我们下载了由 Biten,A. F .、Gomez,l .、Rusinol,m .、& Karatzas,D. (2019) 提供的数据。然后,我们使用下面的代码块提取文章标题和图片标题。图片由原作者在这里提供。
在通过排除标题中少于 5 个单词的文章和标题少于 5 个单词的图像来清理数据之后,最终的数据集有 276596 篇新闻文章,431841 幅相关图像。
我们使用了 WordCloud Python 库来创建文章标题(左)和图片标题(右)中的单词云。
这是数据集汇总统计数据的一些可视化效果。
手套词嵌入
来自斯坦福 NLP 网站关于手套嵌入:
“GloVe 是一种无监督学习算法,用于获得单词的矢量表示。在来自语料库的聚合的全局单词-单词共现统计上执行训练,并且产生的表示展示了单词向量空间的有趣的线性子结构。”
一种简单的单词嵌入方法是为英语中所有可能的单词创建一个长的 0 向量。然后,给定一个单词,在该单词对应的向量的索引中填入 1。这方面的一个例子如下:
使用这个例子,单词“water”的嵌入将是[0,1,0,0,…,0],单词“table”的嵌入将是[0,0,0,1,…,0]。但我们可以立即看出,这不是一个好主意,原因有多种:
- 向量好长啊!英语中有成千上万的单词(包括专有名词),将每个单词存储为如此长的向量将占用大量的内存,并使任何 ML 算法都非常慢。
- 语义信息不会被保留。这意味着对应于该单词的索引并不真正传达任何意义。“气球”和“水”相差一个位置,但这是非常不同的词。
- 同样,两个单词之间的距离并不能让你知道这两个单词在语义上有多相似。
由于这些原因,开发了手套嵌入物。这些是在一个巨大的单词语料库上以无监督的方式进行训练的,使用单词的共现,即不同的单词对一起出现的次数。这让你感觉到这两个词是如何相关的。生成的嵌入集具有一些简洁的属性,如下图所示:
来自手套镶嵌的创作者:
“区分男人和女人的基本概念,即性或性别,可以由各种其他词对等同地指定,例如国王和王后或兄弟和姐妹。为了从数学上说明这一观察结果,我们可能会认为,男人——女人、国王——王后和兄弟——姐妹之间的矢量差可能都大致相等。这类似于矢量强-更强、清晰-更清晰等的差异。”
我们下载了预训练的维基百科 2014 + Gigaword 5 手套单词嵌入,其词汇表中有超过 400000 个单词。预先训练好的嵌入可以从斯坦福 NLP 网站下载。叫做 glove.6B.zip. 在这个文件夹里解压后,你会发现几个文件。这些包含 50 维、100d、200d、& 300d 的嵌入,下载量为 822 MB。要加载下载的手套嵌入,请遵循以下代码:
平均手套单词嵌入以创建句子嵌入
现在的问题是:鉴于我们知道如何将单个单词作为数字向量嵌入,我们如何将其扩展到新闻标题,即由多个单词组成的句子?
对句子嵌入的直观期望是,它应该从句子中的所有单词中捕获隐藏的特征。因此,我们可以通过连接或平均单词的单词嵌入来为给定的句子生成嵌入。然而,取决于单词嵌入的维度,拼接可能并不理想。句子嵌入的维数将随着字数的增加而成倍增加。此外,连接将要求所有输入句子具有相同数量的单词,这可能会由于单词减少而导致重要信息的丢失。
我们通过平均预训练的手套单词嵌入来为文章标题和图像标题生成句子嵌入。我们在进行一些预处理后这样做,其中包括:
- 将句子中的所有单词转换成小写
- 去掉句子中不必要的标点
- 从句子中删除停用词。这些词包括“the”、“not”、“my”等。我们之前做词云的时候,已经从词云库下载了一个停用词列表。
有关实现,请参见下面的代码:
为了避免输入文章标题和图像标题之间的重复成对比较,我们在一个矩阵中检索并保存所有的图像标题嵌入。以下代码创建了带有手套嵌入的图像标题嵌入矩阵:
通用语句编码器
使用平均手套嵌入当然是获得句子嵌入的好方法,但是,你可能已经注意到这是一种上下文无关的方法。也就是说,我们不考虑句子中单词的顺序。
一句“老虎吃人”的平均手套嵌入和一句“人吃老虎”的平均手套嵌入是一样的!
因此,我们需要一种方法来使用单词在句子中的上下文,以便创建一个更全面的句子嵌入集。
谷歌研究在 2017 年推出了通用句子编码器(USE),它被训练成一个通用的语言理解模型。下面的流程图演示了使用深度平均网络(DAN)训练的训练过程,深度平均网络是一系列前馈神经网络。给定一个输入句子,DAN 被训练成将句子中所有单词的平均嵌入编码成 1×N 向量,这实际上是 DAN 的最后一个 softmax 层。通过评估不同语言任务(例如,情感分析)中的句子嵌入向量来更新网络权重。
实际上,网络可以学习仅从平均单词嵌入中捕获句法和语义信息。
Figure source (the paper on USE embeddings by Cer et al.)
我们嵌入了文章标题和图片标题,并预先训练了用法。该模型在 tensorflow hub 上开源。对于任意长度的输入文本,使用 outputs 语句嵌入的维度为 1 x 512。下面的代码显示了如何访问模型。用户端不需要任何预处理!
同样,我们可以检索和保存所有图像标题嵌入:
请注意,tensorflow hub 上有种不同的使用模型,以及不同版本的使用模型。模型随 tensorflow 版本更新而更新,并针对不同的语言任务进行了优化。我们使用的版本是版本 4 。或者,你也可以探索一种使用 Transformer encoder 训练的变体(版本 5 )。据报道,Transformer 编码器在迁移学习任务上的表现优于 DAN 编码器,但消耗了大量的内存和计算资源。
推荐
为了推荐图像,我们计算图像标题嵌入和文章标题嵌入之间的余弦相似度。然后根据计算的余弦相似度按降序排列推荐图像。余弦相似度越高,图像标题与文章标题的语义相似度越高。来自维基百科页面关于余弦相似度:
我们将嵌入向量归一化为单位范数,因此比较将只是点积。下面的代码显示了使用 GloVe 和 USE 嵌入的推荐过程:
结果
以下是手套和使用嵌入模型推荐的图片。因为这个数据集由实际的新闻文章组成,所以我们提供了相应新闻文章的 URL。
第 1 条标题:在瓦拉瓦拉的葡萄酒之乡重塑形象
手套预测:
使用预测:
正如您所看到的,两个模型都推荐与葡萄酒或葡萄酒葡萄园相关的图像,这些图像在语义上与新闻标题中的“葡萄酒”相似。
结论
在这篇博文中,我们演示了如何使用 GloVe 和嵌入来编码一个句子。使用句子嵌入,我们可以计算句子之间的语义相似度,并使用它来推荐新闻文章的图像。完整的实现在我们的 jupyter 笔记本上这里。
感谢
我们要感谢我们在哈佛的导师 Pavlos Protopapas 和 Isaac Slavitt,感谢他们整个学期的指导。我们还要感谢我们的合作者,美联社的韦罗妮卡·兹琳斯卡和戴维·福克斯,感谢他们的支持。
参考
Biten,A. F .,Gomez,l .,Rusinol,m .,& Karatzas,D. (2019)。好消息,各位!新闻图像的上下文驱动实体感知字幕。在IEEE 计算机视觉和模式识别会议记录(第 12466–12475 页)。
Cer,d .,Yang,y .,Kong,S. Y .,Hua,n .,Limtiaco,n .,John,R. S .,和 Sung,Y. H. (2018 年)。通用句子编码器。 arXiv 预印本 arXiv:1803.11175 。
在 AWS DeepLens 和 Slack 上用几个小时构建一个交互式计算机视觉演示
一步一步的指南来建立一个演示,可以帮助您解释您对孩子做了什么,给客户留下深刻印象,或通过检测人来自动操作
几个月前,我发表了一篇文章,向我女儿 3 岁的学前班解释我作为技术顾问的工作。这些天我所做的事情中比较容易理解的一部分是研究计算机视觉问题。人们(甚至是蹒跚学步的孩子)天生就知道识别你面前的东西。有趣的是,正如我通过这次经历所了解到的,计算机视觉对于一个蹒跚学步的孩子来说实际上比这篇文章的大多数读者更容易理解。我女儿这一代人将是第一代带着这样的期望长大的人,他们可以像与他人互动一样与电脑互动。当电脑能说出孩子们的名字时,他们一点也不惊讶,这让我很震惊。
随着今天深度学习、云平台和 IOT 相机技术的进步,我们能够快速轻松地将这种能力带到计算机上。在本文中,我将介绍我用来构建实时计算机视觉系统来问候人们的架构和代码。这个想法、架构和代码的大部分功劳归于桑德·范·德格拉夫和他的看门人参加 AWS DeepLens 挑战赛。我将假设您理解编码原则,我的目标是您可以遵循这些原则来构建自己的演示。让我们开始吧…
架构概述
这个系统使用了大量的 AWS 无服务器技术(这里不需要配置网络或操作系统)。 DeepLens 已经通过 GreenGrass 接口与 AWS Lambda 集成,该接口将功能代码运行到设备上。该架构的其余部分使用云原生的事件驱动处理。从 DeepLens 上传的人的图像在 S3 到处移动,这使用 Lambda 函数触发适当的动作。这有一个很好的副作用,就是为了下游调试和分析的目的对图像进行排序。类似地, API 网关处理来自 Slack API 的入站 HTTP 请求,再次委托 lambda 函数进行处理。实际的人检测功能由 AWS Rekognition 处理,它为面部识别提供全自动的机器学习服务。SNS 主题和 SQS 队列允许附加应用对检测到的人做出反应。在这个例子中,一个控制台应用程序使用 AWS Polly 与被发现的人对话。
实际的工作流程是这样的:
- DeepLens 正在制作一个 Find Person Lambda 函数,该函数通过一个模型循环每一帧,以检测一个人,然后识别图像中是否有人脸。一旦 DeepLens 在画面中识别出一个人,它就会将图像上传到一个加密的 S3 桶位置。
- Guess Lambda 函数被配置为在 S3 上传时触发,它将图像传递给 Rekognition,以使用亚马逊预先训练的面部识别算法来确定图像与哪个 Slack 用户匹配。
- 当一个新人的第一张图像到达时,Rekognition 很可能找不到匹配,因此 Lambda 函数将图像移动到 S3 的一个未知文件夹中进行进一步处理,设置适当的权限以便 Slack 能够显示它。
- 一旦图像被放入 S3,未知的λ函数被触发。它向附加未知图像的 Slack API 发送一条消息,Slack API 向预先配置的 Slack 通道发送一条消息,询问图像中是哪个 Slack 用户。
- 一旦团队中的某个人识别出与图像相关联的 slack 用户,Slack 就向 AWS API Gateway 发回一条消息,这将触发 Train Lambda 函数来告诉 Rekognition 用户是谁。
- 下一次 DeepLens 上传 Slack 中已识别的人的图像时,Rekognition 可能会在 Guess Lambda 函数中匹配该人,这两个函数都会将已识别的人发布到 Slack 频道,以允许用户在必要时纠正识别。
- 该应用还向 SNS 主题发送消息,包括用户信息和附加的情绪检测信息。订阅 SNS 主题的 SQS 队列允许应用程序处理这些信息。一个简单的控制台应用程序监听 SQS 队列,并使用 AWS Polly 来问候这个人,并记录他们表现出的情绪。
设置服务
首先,您的开发环境需要具备以下条件:
- Git 访问:您必须生成一个 SSH 密钥并将其添加到您的 GitHub 帐户中
- NodeJS&python 3(带 pip3 & pipenv)
- 无服务器框架 &无服务器-python-需求插件。
- AWS CLI ,以及它们的依赖项,安装在您的开发机器上。
以下说明应适用于 AWS Cloud9 。它预装了 NodeJS、Python 和 AWS CLI(更新到您的电子邮件):
#Setup SSH Key for Git Access
ssh-keygen -t rsa -b 4096 -C "[your_email@example.com](mailto:your_email@example.com)"
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa
cat ~/.ssh/id_rsa.pub
#Copy paste the resulting token to add it to your account
您现在需要将生成的令牌复制粘贴到并添加到您的 GitHub 帐户中,这样您就可以认证您的 Cloud9 环境了。请注意,这将允许您的 Cloud9 环境中的任何人访问您的 GitHub 帐户,因此使用 Cloud9 共享功能时要小心。
# Clone Github repo
git clone [git@github.com](mailto:git@github.com):ryano144/doorman.git
cd doorman#Install Serverless and the AWS CLI
npm install -g serverless
sudo yum install jq
pip install --upgrade awscli
最后,您将需要访问 Slack 。如果你还没有在工作中使用它,你可以建立一个免费账户来测试这个功能(当我写这篇文章的时候,你只需要输入你的电子邮件,然后点击开始)。然后你需要在 Slack API 上创建一个与你的工作空间相关的应用。现在,您需要的只是一个名称和指向您将使用的工作区的指针。在本文的后面,您将配置 Slack 应用程序指向您的 AWS 后端。
您将需要激活传入的网络挂钩。这将允许应用程序发布到 Slack。
然后,您需要转到 OAuth 和 Permissions 部分,并选择以下范围(channels:read,chat:write:bot,incoming-webhook,users:read):
接下来,您需要将应用程序安装到您的工作空间:
这将把你带到一个可以安装应用程序的屏幕。你需要记住你为应用程序选择的 slack 频道。
安全说明:该应用程序将获得您工作区中所有用户个人资料的访问权限。这将允许您为 DeepLens 相机上传的每张图片选择一个用户。只有不透明的 Slack 用户 ID 将被发送到 AWS Rekognition,但是通过您的访问令牌,用户可以从 slack API 获得额外的配置文件信息。故事的寓意:一定要保护好你的 Slack API 令牌。为了增加安全性,如果 Slack 在 GitHub 这样的公共网站上发现你的访问令牌,他们会自动为你停用它。
一旦设置了 OAuth 权限,您就可以从安装应用程序或 OAuth & Permissions 页面复制您的访问令牌。
无服务器部署包使用几个环境变量来允许部署指向多个帐户的相同代码。为了简化部署,您可以创建一个 shell 脚本。我称之为 mine environment.sh,并将在下面的脚本中引用它。你会在许多脚本中看到对的引用
安全说明:不要将此文件提交给源代码管理。另外,如果您使用共享的 Cloud9 环境,那么您应该只在终端中直接输入 slack API token 环境变量。
export name=<your-slack-app-name>
export BUCKET_NAME=doorman-$name
export REKOGNITION_COLLECTION_ID=doorman-$name
export SLACK_API_TOKEN=<your-slack-api-token>
export SLACK_CHANNEL_ID=guidepost-$name
export SLACK_TRAINING_CHANNEL_ID=guidepost-$name
export AWS_ACCOUNT_NUMBER=$(curl -s [http://169.254.169.254/latest/dynamic/instance-identity/document](http://169.254.169.254/latest/dynamic/instance-identity/document) | jq -r .accountId)
按照上面链接的指南,你应该有一个工作的开发环境。您可以通过运行以下命令将完整的架构部署到 AWS 来测试它:
source environment.sh
aws s3 mb s3://doorman-$name
aws rekognition create-collection --collection-id doorman-$namepip install --user pipenv
sls plugin install -n serverless-python-requirementssls deploy
一旦完成部署,您就可以浏览到 CloudFormation 控制台来查看资源。你现在需要更新你的 Slack 应用程序,并将 Lambda 函数部署到 DeepLens。对于 slack 应用程序,您需要捕获 API 网关 URL,它应该在成功部署后出现在控制台上:
然后,您需要在 Slack 应用程序中更新此 URL,并从安装应用程序屏幕重新安装它。
你需要的最后一件事是从亚马逊购买一台 DeepLens 相机。如果你参加了 re:Invent 2017 并参加了任何 ML 会议,你很可能有一个。如果你没有,你可以在亚马逊上花 250 美元买一个。相机是一个预包装的包,本质上是一台运行 AWS GreenGrass 的迷你电脑,连接到一台摄像机。这款相机可以轻松部署计算机视觉模型来运行直播视频,特别是如果你已经熟悉 AWS Lambda 的话。我假设你已经完成了设置指南,你的 DeepLens 现在已经连接到互联网。您肯定应该获取客户端证书,以便在浏览器中查看视频提要。
完成这些步骤后,您应该浏览到 DeepLens 控制台。您需要使用对象检测模板创建一个项目。一旦你选择了这个,你将点击创建应用程序,然后编辑它以关联查找人 lambda 函数。接下来,在项目列表中选择它,并单击 Deploy to Device。从那里,选择您注册的 DeepLens,并将其推送到设备。完成后,您会看到 DeepLens 控制台顶部有一个绿色条,让您知道项目已经成功部署。
恭喜你!此时,您应该拥有一个功能齐全的系统。此时,您可以连接到设备的本地视频源。为此,首先在设备详细信息页面上找到 IP 地址,然后浏览到 https:// :4000 查看视频馈送。如果一个人走到摄像机前,摄像机会在他们周围放一个盒子。一旦您看到这种情况发生,您可以浏览到您发送消息的 Slack 通道,并查找带有提示的图像上传,以选择要关联的用户。
That’s me testing detection in the dark (it works)
如果它对你不起作用,请在下面的评论中自由提问。如果有足够多的问题,我将发布后续文章,详细介绍如何在 AWS 上调试这些类型的应用程序。
在 R 中建立线性回归模型预测 AFL 人群
介绍
现代 AFL 有很多关于群体行为和群体问题的讨论。我个人认为,人群问题是一个一切照旧的例子;近 30 年来,我一直断断续续地去看 AFL 的比赛,今天的观众行为与 20 年前没有什么不同……只是现在每个有手机和 Twitter 账户的人都是记者。
无论如何,那是我的两分钱。这种分析不是基于群体行为。这篇文章主要是关于建立一个模型来预测赛季期间 AFL 的人群。虽然数据是自 2000 赛季开始以来收集的,但该模型将建立在从 2013 赛季开始到 2018 赛季结束(总决赛系列除外)的训练数据基础上,然后用训练好的模型来预测 2019 赛季直到 2019 年 7 月 21 日周日结束的第 18 轮 AFL 比赛的出席人数。线性模型将在 RStudio 中使用 R(版本 3.5.3)构建。
这篇文章是我对跟风粉丝所做的介绍性分析的后续。这里可以找到。
这篇文章的灵感来自托尼·考克的惊人工作,特别是他在 AFL 人群的可预测性上的文章。
这个项目的所有代码都可以在 GitHub 这里找到
数据
该项目使用了三个数据来源:
- AFL 表格被用作主要信息来源。这无疑是任何想做 AFL 分析的人的首选
- 澳大利亚体育博彩用于获取 2013 赛季比赛的博彩数据
- 澳大利亚气象局用于气候数据(降雨和温度)。BOM 数据的一部分是从 James Day 的 fitzRoy R 包中取出的。非常感谢他。降雨和温度数据有一些遗漏的观测数据。缺失的雨量数据通过对之前缺失数据点的下一个读数取平均值来填充。缺失的温度用该月的平均温度来填充。
特征工程
AFL 表格中包含的数据是一个丰富的信息来源,但是需要一些步骤来从数据中收集一些额外的信息。
最初的三个数据源包含大约 35 个潜在的变量。然后进行特征工程(创建在预测模型中使用的额外解释变量的过程),总共产生约 95 个特征,以观察它们是否最终会被添加到最终模型中使用。
创建的功能
根据从众文章中得到的一些反馈和我想探索的其他东西,创建了一些新功能来看看它们是否有任何预测能力。
以游戏开始时间为例——有时间是很好的,但回归模型可能会发现将时间分类为一天中的一段时间更好,以减少一些噪音。因此,考虑到这一点,创建了一个特性(time_period
)——如果游戏在下午 5 点开始,我将它归类为下午的游戏。在下午 5 点至 7 点之间开始的游戏被归类为晚间游戏,而在此之后的游戏是夜间游戏。
此外,星期几和time_period
被加入到一个变量game_time
中以在模型中使用。例如,如果是星期六下午 5:30 开始的比赛,则被归类为“星期六晚上”。
不经常使用的场馆,或者更准确地说,不是球队的主要“主场”体育场被标为“其他”。在这些场馆中共有 94 场比赛,其中包括像尤里卡体育场、卡扎利体育场、特雷格公园以及新西兰的国际场馆惠灵顿和中国的江湾体育场等场馆。
另一个特点是确定竞争游戏——当两个激烈的对手面对面时,观众就会来了。作为竞争游戏,有几种不同的选择。第一个包括四个最老的传统对手,都来自墨尔本——科林伍德、里士满、埃森登和卡尔顿(保留你的卡尔顿观点,我知道他们很糟糕,但他们的球迷仍然会去看这些对抗赛)。南美洲的德比(阿德莱德和阿德莱德港之间的对决)和西澳大利亚的德比(西海岸和弗里曼特尔之间的西部德比)也被归类为对抗赛。
几年前,我听到一位重要的 AFL 工作人员谈论博彩市场是上座率的一个强有力的预测因素,我收集了博彩数据来测试这一假设。除了收集的原始赔率和线数据之外,还创建了许多特征。这些包括计算主队和客队赔率之间的差异,以及计算一周内赌注的变化。
围绕主队和客场队的平均体验也创造了一些特色,里程碑式的比赛也是如此(每支球队的球员都参加了他们的第 100 场、200 场、250 场或 300 场比赛)。
计算每个队的最后一场比赛结果,以及每个队在前三场比赛中的获胜次数。此外,球队是否在前一个赛季打总决赛也被确定。
每轮都创建了一个阶梯,并探索了阶梯位置、百分比和得分作为最终模型的可能特征。
还创建了一个功能来识别来自同一州或不同州的团队所玩的游戏。
探索性数据分析
在我们开始构建模型之前,我们需要了解响应变量和预测变量。最有效的方法是通过可视化,什么包比ggplot2
更好用!
这一部分将包含一些探索性的数据分析(通常称为“EDA”),这是任何数据分析项目中自然的第一步。这是一个极其重要的步骤,它允许分析师检查数据——目标变量和预测变量的形状,预测模型中可能包含的变量之间是否有任何相关性,数据中是否有任何异常值,或任何缺失的数据……您明白了,EDA 非常重要!
嗯,在所使用的数据集中没有丢失的数据……这些数据都已经在预处理阶段被清除,可以在这个 R 代码中找到。
响应变量— Attendance
让我们看看我们的反应变量——或者我们试图预测的东西——attendance
在 AFL 超级联赛赛季比赛中。
我们可以看到出席人数多少有些偏正。我们将会看到一些高上座率的游戏是否会引起一些问题。这个变量可能需要进行某种形式的转换,但稍后会详细介绍。
自 2013 年以来,AFL 常规赛的平均上座率为 1340 场,略低于 32500 场。
一些关键预测
data_for_model <- afl_premiership_season %>%
select(season, attendance, team1, team2, round, venue, game_time, home_median_experience, home_mean_experience, away_median_experience, away_mean_experience, home_300, home_milestone, away_300, away_milestone, count_milestones, rainfall_clean, min_temp, max_temp, last_five_days_rain, temperature_diff, HomeOddsOpen, AwayOddsOpen, HomeLineOpen, AwayLineOpen, odds_diff, rivalry_game, HomeTeamFav, team1_last_result, team2_last_result, split_round, home_team_finals_last_season, away_team_finals_last_season, season_stage, temperature, IsHomeMilestone, IsAwayMilestone, count_milestones, last_results, home_team_state, away_team_state, is_same_state, temperature_diff, home_odds_change, away_odds_change, home_line_change, away_line_change, second_home_game, home_season_points, home_score_for, home_percentage, home_ladder_pos, away_wins_last_three, away_season_points, away_score_for, away_percentage, away_ladder_pos, teams_in_eight, finals_last_season, home_wins_last_three, away_wins_last_three) %>% na.omit()
正如在本系列的第一篇文章中所看到的,参赛队伍之间的出席率有明显的差异,而且参赛队伍在之前的比赛中是赢是输。此外,第一个帖子显示,主队的偏袒地位似乎也与出勤率有关。
分类特征:
我们预计会对人群数据产生影响的一些关键分类变量如下所示。
一些观察:
- 比赛地点和出席人数之间似乎有关系。被归类为
Other
的体育场包括第二主场地和 2019 年未使用的一些椭圆形场地。在这些体育场举行的 1340 场比赛中有 238 场 - 去年是主队还是客场打总决赛,似乎和上座率关系不大。在我们的模型中包含以下内容可能是有用的
- 玩游戏的时间和出勤率似乎是相关的。毫无疑问,
Weekday Afternoon
和Friday Afternoon
因为埃森登和科林伍德之间的澳新军团日冲突而高涨。周五和周日晚上的比赛似乎吸引了更多的观众。可能是一个有用的预测 - 在一个赛季的不同轮次之间,出席人数似乎有一些差异。可能是一个有用的预测
- 被归类为“竞争游戏”的游戏显然比非竞争游戏吸引了更多的观众。可能是一个有用的预测
- 这一轮是否是分轮(有些队轮休,或者休息一周)似乎与出席率无关。可能不是一个有用的预测
- 当主队有一名球员打一场里程碑比赛(无论是第 100 场、200 场、250 场还是第 300 场比赛)时,观众人数似乎略高,但这种关系显然不会很强。可能不是一个有用的预测
- 当两支参赛队伍来自同一个州时,观众人数会更多。可能是一个有用的预测
- 不出所料,主队在第二主场(塔斯马尼亚的霍桑和诺斯,墨尔本,达尔文的西部牛头犬队等)比赛的观众较少。这可能与体育场容量较低有关,但可能仍然是一个有用的功能
数字特征:
为了探索数字特征和attendance
之间的关系,将使用皮尔逊相关。
使用 dplyr 仅选择数字特征,然后计算转换矩阵的相关性,得出以下与attendance
的相关性。
numeric_features <- data_for_model %>% select_if(is.numeric)
numeric_features %>%
as.matrix() %>%
cor() %>% .[,"attendance"] %>% sort(decreasing = T)
虽然所有相关性似乎都相当弱,但可以得出以下结论:
- 主队的平均经验(
home_mean_experience
)计算为每个球员在他们参加的每场比赛中参加的平均比赛,产生了与出勤率的最高皮尔逊相关性 0.264 - 主队的百分比(得分/反对得分)也具有高于 0.2 的相关性,为 0.236
- 主队在过去三场比赛中获胜的次数具有 0.175 的皮尔逊相关性,而客队平均比赛经验的相关性为 0.159
- 奇怪的是,主队的阶梯位置呈负相关,皮尔逊相关系数为-0.273。客队的阶梯位置也有负相关-0.150
- 投注数据显示了一些关系;随着
HomeOddsOpen
的增大,attendance
趋于减小,这多少有些意料之中。odds_diff
变量(主队开局赔率与客场球队的差距)也有微弱的负相关关系(相关性-0.168) - 天气数据显示了一些关系。不出所料,当天的最高气温和降雨量以及最近五天的累积降雨量与上座率呈负相关(分别为-0.091、-0.081 和-0.65)
构建模型
在任何旨在建立一个机器学习模型来预测一些响应变量的结果的项目中,在训练模型时保留数据的子集是很重要的。这两组通常被称为训练集和测试集。训练集是您传递给模型进行构建的内容,然后根据模型尚未“看到”的数据进行预测,以最好地模拟现实生活中的例子。
在这种情况下,模型将根据 2013 年至 2018 年赛季的数据进行训练(afl_pre_2019
),然后根据模型尚未看到的 2019 年数据进行预测,afl_2019
。这样做是为了确保模型不会“过度拟合”数据。过度拟合数据的模型通常在看不见的事件上表现更差。
尝试过但未包含在模型中的事物
以下特征被认为具有一定的有效性,但是当运行该模型时,发现没有预测能力:
- 这两个队在过去三场比赛中的获胜次数并没有起作用
- 当天的降雨量没有预测能力(但是比赛日之前五天的总降雨量有)
- 里程碑游戏使模型表现更差
缺少什么/最好有什么
有一些信息是不公开的(或者说我没有试图去获取),这些信息可能会在未来的建模工作中派上用场。其中包括:
- 每个赛季的门票定价,以探索门票定价对上座率的影响
- 比赛门票是否发给了球迷,这是否对上座率有影响
- 任何社交媒体帖子/报纸文本分析,涉及游戏中玩家的团队/话题等
afl_pre_2019 <- data_for_model %>% filter(season < 2019, season >= 2013)
afl_2019 <- data_for_model %>% filter(season == 2019)
基线模型
在建立一个预测游戏的模型之前,我们希望有一个基线模型可以比较。使用每场比赛的平均人群作为基线的诱惑是存在的,但是不同的场馆容量使得这不太相关。因此,为了计算基线模型,我将使用每个场馆的出席率中值(将标有“其他”的场馆的平均值作为一个整体),用于每个主客场球队组合。比如科林伍德(H) vs 埃森登(A)和埃森登(H) vs 科林伍德(A)是不一样的。使用中位数是因为 AFL 人群有轻微的偏斜。
median_attendances <- afl_pre_2019 %>%
group_by(team1, team2, venue) %>%
summarise(baseline_attendance = median(attendance)) %>% ungroup()
baseline <- afl_2019 %>% select(team1, team2, venue, attendance) %>%
left_join(median_attendances, by = c("team1", "team2", "venue")) %>%
filter(!is.na(baseline_attendance))
使用每个场地的平均上座率作为我们的基线,获得了 6816 的 RMSE 和 4958 的平均绝对误差。从这里建立的任何模型都需要比这更好。
转换响应变量
由于我们的响应变量有点倾斜的性质,进行某种转换以使其更加正常化可能是明智的。对数变换通常用于“消除”数据偏差。下面是 log 转换考勤变量的结果。
它看起来没有未转换的变量偏斜,然而(剧透),模型表现更差,出席率被对数转换。
使用逐步方法的特征选择
选择哪些特征进入模型的一种流行方法是使用逐步回归。这可以通过以下方式进行:
- forward ,变量一个一个加,只保留那些提高模型可解释性(R 平方)的变量,
- 向后,从所有变量开始,如果它们不能增加模型的可解释性,就删除它们,或者
- both ,是前进和后退特征选择的集合。
在和两个方向上逐步使用最大化模型调整后的 R 平方值为 0.87。
执行线性回归时,我们希望确保变量之间没有共线性。评估的一个方法是计算可变通货膨胀系数(VIF)。这通过car
包中的vif
函数变得很容易。高于 5 的 VIF 被认为是过高的。
虽然游戏进行的回合刚刚超过 5 (5.7),我们将保持这一点,但是odds_diff
和home_score_for
将从使用逐步选择的完整模型中删除并重新运行。
full_model <- lm(attendance ~ ., data = afl_pre_2019)
*# Set seed for reproducibility*
set.seed(123)
step.model <- MASS::stepAIC(full_model, direction = "both",
trace = FALSE)car::vif(step.model)
*# the backward selection model include a variable with hi collinearity (VIF = 12). Will recreate model without odds_diff*
full_model_clean <- lm(attendance ~ team1 + team2 + round + venue + game_time +
home_milestone + rainfall_clean + min_temp + max_temp + last_five_days_rain +
HomeOddsOpen + AwayOddsOpen + HomeLineOpen +
rivalry_game + split_round + last_results + is_same_state +
home_season_points + away_season_points +
away_ladder_pos + teams_in_eight + home_team_finals_last_season,
data = afl_pre_2019)
car::vif(full_model_clean)
清洁模型的调整后 R 平方为 0.8652,而 RMSE 为 6,474,MAE 为 4,855。这些结果优于基线模型,但也仅仅如此。
最佳模特
确定“最佳”模型是一项有趣的工作。如果模型的可解释能力是我们所追求的,那么我们希望最大化调整后的 R 平方度量。这种分析旨在最大限度地成功预测人群,因此,均方根误差(RMSE)或平均绝对误差(MAE)是我们将重点关注的指标。这两种方法都可以追溯到响应变量——出勤率,区别在于 MAE 是模型的平均绝对误差(预测和实际之间的差异),而 RMSE 惩罚误差越大的模型。
对于 RMSE 和 MAE,当应用于 2019 赛季比赛时,以下模型导致最低的 RMSE 和 MAE。
set.seed(123)
fit_lm <- lm(attendance ~ team1 + team2 + round + venue + game_time + I(HomeLineOpen * home_ladder_pos) + rivalry_game + last_results + last_five_days_rain + max_temp + min_temp + home_mean_experience + away_mean_experience + is_same_state + home_score_for + I(home_percentage * home_ladder_pos) + I(home_wins_last_three * round) + round * finals_last_season, data = afl_pre_2019)
summary(fit_lm)$adj.r.squared
该模型的调整后 R 平方为 0.858,表明该模型可以解释训练集(2013-2018 年)中 AFL 出勤的几乎 86%的可变性。
交互术语
在这个模型中,特性不仅仅是单独使用。还采用了相互作用术语。以下特征组合在一起形成了一个特征,提高了模型的预测能力:
I(HomeLineOpen * home_ladder_pos)
I(home_percentage * home_ladder_pos)
I(home_wins_last_three * round)
round * finals_last_season
分析最佳模型
我们要做的第一件事是确保残差是正态分布的,以满足线性模型的正态性条件。
下面的直方图表明,误差呈正态分布,以零为中心。一个好迹象。
ggplot(data = data.frame(fit_lm$residuals), aes(x= fit_lm.residuals)) +
geom_histogram() +
ggtitle("ERRORS ARE CENTRED AROUND ZERO") +
scale_x_continuous(labels = comma, name = "Model Residuals")
然后我们可以看看每个预测变量的系数。
为了使检查更容易,使用了broom
包。使用tidy()
功能,汇总 lm 输出的结果可以很好地显示在 DF 中。
每个预测变量的系数(除了主队和客场队)如下所示。
a <- broom::tidy(fit_lm) %>% mutate(p.value = round(p.value, 5))
a %>% filter(!str_detect(term, "team1"), !str_detect(term, "team2")) %>%
kableExtra::kable(format = "html", escape = F) %>%
kableExtra::kable_styling("striped", full_width = F)
actual_2019crowds <- afl_2019$attendance
*# fit linear model*
afl_2019$predicted_attendance <- predict(fit_lm, afl_2019)
*# put a floor on AFL attendances - surely the AFL doesn't let the crowd fall below 6,000*
afl_2019$predicted_attendance <- ifelse(afl_2019$predicted_attendance < 6000, 6000, afl_2019$predicted_attendance)
*# calculate the errors*
error <- afl_2019$predicted_attendance - afl_2019$attendance
然后我们要分析模型与关系的拟合程度。这可以通过在散点图上绘制实际值和预测值来实现。
我可以看到,线性模型平均来说做得相当好。该模型确实低估了一些大型抽奖游戏,同时也预测了第八轮在 MCG 举行的山楂与 GWS 比赛的上座率超过 30,000 人,该比赛仅吸引了 14,636 名球迷。
afl_2019 %>%
ggplot(aes(x=attendance, y= predicted_attendance)) +
geom_point() +
geom_abline(slope = 1, intercept=0) +
ggtitle("MODEL UNDER-PREDICTED SOME\nLARGE DRAWING GAMES") +
scale_x_continuous(labels = comma, name = "Actual Attendance") +
scale_y_continuous(labels = comma, name = "Predicted Attendance") +
annotate(geom = "text", x=83000, y= 55000, label = "Model under-predicted\non these games")
我们还可以看看模型如何预测每个场馆的人群。
afl_2019 %>%
mutate(error = predicted_attendance - attendance) %>%
ggplot(aes(x= error)) +
geom_density() +
geom_vline(xintercept = 0, linetype = 2) +
facet_wrap(~ venue, scales = "free_y", ncol = 2) +
scale_x_continuous(labels = comma, name = "Error") +
ggtitle("Exploring Errors by Venue") +
theme(axis.text.y = element_blank(), axis.title.y = element_blank())
该模型倾向于高估加巴、珀斯体育场、悉尼展览场甚至 MCG 的出席人数,而低估卡拉拉和“其他”的出席人数。
模型的整体性能
最终选定的模型实现了 5953 的 RMSE 和 4466 的平均用户体验,这意味着在预测上座率时,模型平均误差不到 4500 个粉丝。与基线模型(RMSE 为 6,816,平均平均误差为 4,958)相比,该模型比基线模型平均高出 500 多人,但也没有太多的极端误差。
结论
希望你已经到这里了!
我很乐意在评论中听到您的反馈/更正或进入 touch 。
本帖原创,发布在不怪数据博客【https://www.dontblamethedata.com】
在 Pyspark 中使用 MLlib 构建 ML 应用程序
本教程将指导您如何在 apache spark 中创建 ML 模型,以及如何与它们交互
介绍
Apache Spark 是一种按需大数据工具,全球许多公司都在使用它。它的内存计算和并行处理能力是这个工具流行的主要原因。
Spark Stack
MLlib 是一个可扩展的机器学习库,它与 Spark SQL、Spark Streaming 和 GraphX 等其他服务一起出现在 Spark 之上。
数据集相关介绍
在本文中,我们将专注于一个叫做笔画数据集的数据集。中风是一种流向大脑的血流停止或血流过多的情况。中风的危险因素有
- 吸烟
- 高血压
- 糖尿病
- 血液胆固醇水平高
- 酗酒
- 高脂肪(尤其是饱和脂肪)和高盐,但低纤维、水果和蔬菜的饮食
- 缺乏经常锻炼
- 肥胖
所以我这里得到了一个不错的数据集:https://bigml . com/user/Francisco/gallery/model/508 b 2008570 c 6372100000 B1 # info
以下是数据集:
Stroke Dataset
该数据集几乎包含了上述中风的所有风险因素。因此,选择具有适当风险因素的数据集非常重要。
我们将把列的字符串值变成数字值。这样做的原因将在后面解释。使用 Excel 中的替换功能,我将数据集更改为以下内容
- 性别列—男性=1,女性=0
2.吸烟史——从未=0,曾经=0.25,当前= 0.5,以前= 0.75,当前= 1.0
使用的服务和库
- Google cloud——我们将在 Dataproc 中建立我们的 spark 集群,并在 Jupyter 笔记本中编写代码
- Jpmml(pyspark2pmml) —用于将我们的模型转换成 pmml 文件。
- open scoring——一个为 PMML 模型评分的 REST web 服务。
- VS 代码——我们将使用 React JS 构建一个与 REST 服务器通信的交互式网站。
架构图:
下图展示了我们整个项目的简要架构。
The architecture diagram of our project
步骤 1:设置谷歌云
Google cloud 有一个名为 Dataproc 的服务,用于创建预装 Apache Spark 的集群。我们可以随时调整集群的大小。谷歌云提供免费的 300 美元信用作为入门优惠。因此,我们将使用这些免费配额来建立我们的集群。
Google Cloud Console
点击“激活”获得 300 美元的免费点数。
Registration Step-1
选择您的国家,然后点击“继续”。在下一页,您将被提示输入您的帐单细节和信用卡或借记卡细节。填写它们,然后点击底部的按钮。
Google Cloud Console - Dataproc
将打开控制台页面。在页面顶部,在搜索栏中键入 Dataproc,上面的页面就会打开。单击 create a cluster 开始创建集群。
GC — Creating a cluster-1
GC — Creating a cluster-2
GC — Creating a cluster-3
请确保您输入了与上述相同的设置。点击高级选项,按照上面的图像设置,然后点击创建。创建一个集群可能需要 2 到 3 分钟。
Google cloud — Dataproc clusters
导航到群集,然后单击虚拟机实例。在 VM 实例下,我们可以看到创建了一个主节点和两个工作节点。主节点的作用是它通常请求集群中的资源,并使它们对 spark 驱动程序可用。它监视和跟踪工作节点的状态,这些工作节点的工作是托管 executor 进程,该进程存储来自任务的输出数据并托管 JVM。详细描述可以在这里找到
现在单击主节点的 SSH 按钮。
SSH
一个新的终端在一个新的 chrome 标签中打开。这是命令行界面,通过它我们可以与我们的集群进行交互。键入“pyspark”检查 spark 上的安装及其版本。确保 spark 版本在 2.2 以上,python 版本为 3.6。
Firewall Rules
现在设置 jupyter 笔记本,我们需要创建一个防火墙规则。按照图片设置新的防火墙规则。确保在协议和端口中选择“全部允许”。
Firewall Rules
点击保存并导航至“外部 IP 地址”。
External IP addresses
将“spark-cluster-m”的类型改为静态。给出任意一个名字,点击“保留”。
现在导航到“SSH”并键入以下命令。
sudo nano ~/.jupyter_notebook_[config.py](https://www.youtube.com/redirect?event=comments&stzid=UgwMLhVicKWXmwMzyJ54AaABAg&q=http%3A%2F%2Fconfig.py%2F&redir_token=7XHzrHJ0cqu2HG4iRpSCumF2asJ8MTU2MDUzMTgyMEAxNTYwNDQ1NDIw)
复制下面的线并粘贴它。按 CTRL+o,回车,CTRL+x。
c=get_config()c.NotebookApp.ip=’*’c.NotebookApp.open_browser=Falsec.NotebookApp.port=5000
现在我们可以通过下面的命令打开 jupyter 笔记本
jupyter-notebook --no-browser — port=5000
在 SSH 中键入上述命令,然后打开一个新的选项卡,并在 google chrome 中键入“https://localhost:5000”以打开 Jupyter notebook。在我的例子中,本地主机是 35.230.35.117
Jupyter Notebook
第二步:在 Jupyter 笔记本的 Pyspark 中编码
在进入这一部分之前,我们需要安装一些外部库。
我们需要 Imblearn 库来执行 SMOTE,因为我们的数据集高度不平衡。更多关于 smote 的信息可以在这个链接中找到。
在 SSH 中,键入
sudo -i
然后键入下面一行
conda install -c glemaitre imbalanced-learn
退出根文件夹,然后打开 Jupyter 笔记本。开始编码吧。
导入重要库
from pyspark.sql import SQLContext
from pyspark.sql import DataFrameNaFunctions
from pyspark.ml import Pipeline
from pyspark.ml.classification import DecisionTreeClassifier
from pyspark.ml.classification import LogisticRegression
from pyspark.ml.feature import Binarizer
from pyspark.ml.feature import OneHotEncoder, VectorAssembler, StringIndexer, VectorIndexer
from pyspark.ml.classification import RandomForestClassifier
from pyspark.sql.functions import avgimport pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inlinefrom pyspark.ml.evaluation import MulticlassClassificationEvaluator
from pyspark.mllib.evaluation import MulticlassMetrics
from pyspark.ml.evaluation import BinaryClassificationEvaluatorfrom imblearn.over_sampling import SMOTE
from imblearn.combine import SMOTEENN
from sklearn.model_selection import train_test_split
from collections import Counter
现在我们需要创建一个 spark 会话。
from pyspark.context import SparkContext
from pyspark.sql.session import SparkSession
sc = SparkContext(‘local’)
spark = SparkSession(sc)
我们需要从存储中访问我们的数据文件。导航到 google 云控制台中的“bucket”并创建一个新的 bucket。我命名为“data-stroke-1 ”,并上传修改后的 CSV 文件。
Google Cloud Bucket
现在我们需要加载已经上传到我们的 bucket 中的 CSV 文件。
input_dir = ‘gs://data-stroke-1/’
df = spark.read.format(‘com.databricks.spark.csv’).options(header=’true’, inferschema=’true’).load(input_dir+’stroke.csv’)
df.columns
我们可以通过使用下图所示的命令打印数据帧来检查它。
现在,我们需要创建一个列,其中包含所有负责预测中风发生的特征。
featureColumns = [‘gender’,’age’,‘diabetes’,‘hypertension’,
‘heart disease’,‘smoking history’,‘BMI’]
确保所有列都是双精度值。接下来,让我们删除所有 2 岁以下的条目。
df = df.filter(df.age >2)
df.count()
现在让我们打印一个条形图来检查数据中存在的类的类型
responses = df.groupBy(‘stroke’).count().collect()
categories = [i[0] for i in responses]
counts = [i[1] for i in responses]
ind = np.array(range(len(categories)))
width = 0.35
plt.bar(ind, counts, width=width, color=’r’)
plt.ylabel(‘counts’)
plt.title(‘Stroke’)
plt.xticks(ind + width/2., categories)
步骤 3:数据预处理
步骤 3A。缺失数据管理
现在,进行适当的缺失数据管理以最终得到一个非常好的模型是非常重要的。使用“df.na.drop()”并不总是好的,它会删除所有丢失数据的行。用适当合理的价值观来填充它们是我们可以实现的一个想法。
如我们所见,我们在身体质量指数列和吸烟史列中缺少值。填充这些身体质量指数值的一种可能方法是使用年龄值来填充它们。
taken from — https://dqydj.com/bmi-distribution-by-age-calculator-for-the-united-states/
对于吸烟史,很难找到合理的数值来填补。通常情况下,16 岁以下的人对吸烟并没有那么上瘾,因此我们可以用 0 来填充那些年龄组的人的值。年龄在 17 到 24 岁之间的人一生中可能至少尝试过一次吸烟,所以我们可以给这些人 0.25 的价值。现在,一些人过了一定年龄就戒烟了,即使他们有健康问题,也很少有人继续吸烟。我们不能决定给它们取什么值,所以默认情况下,我们给它们赋值 0。
我们既可以删除这些列中缺少值的所有行,也可以按照上面的逻辑填充这些行。但是出于本教程的目的,我已经用上面的逻辑填充了丢失的行,但是实际上篡改数据而没有数据驱动的逻辑来备份通常不是一个好主意。
我们将对此数据帧执行一些操作,而 spark 数据帧不支持任何操作。因此,我们将我们的数据帧复制到熊猫数据帧,然后执行操作。
imputeDF = dfimputeDF_Pandas = imputeDF.toPandas()
我们将根据年龄将完整的数据帧分成许多数据帧,并用合理的值填充它们,然后,将所有数据帧合并成一个数据帧,并将其转换回 spark 数据帧。
df_2_9 = imputeDF_Pandas[(imputeDF_Pandas[‘age’] >=2 ) & (imputeDF_Pandas[‘age’] <= 9)]
values = {‘smoking history’: 0, ‘BMI’:17.125}
df_2_9 = df_2_9.fillna(value = values)df_10_13 = imputeDF_Pandas[(imputeDF_Pandas[‘age’] >=10 ) & (imputeDF_Pandas[‘age’] <= 13)]
values = {‘smoking history’: 0, ‘BMI’:19.5}
df_10_13 = df_10_13.fillna(value = values)df_14_17 = imputeDF_Pandas[(imputeDF_Pandas[‘age’] >=14 ) & (imputeDF_Pandas[‘age’] <= 17)]
values = {‘smoking history’: 0, ‘BMI’:23.05}
df_14_17 = df_14_17.fillna(value = values)df_18_24 = imputeDF_Pandas[(imputeDF_Pandas[‘age’] >=18 ) & (imputeDF_Pandas[‘age’] <= 24)]
values = {‘smoking history’: 0, ‘BMI’:27.1}
df_18_24 = df_18_24.fillna(value = values)df_25_29 = imputeDF_Pandas[(imputeDF_Pandas[‘age’] >=25 ) & (imputeDF_Pandas[‘age’] <= 29)]
values = {‘smoking history’: 0, ‘BMI’:27.9}
df_25_29 = df_25_29.fillna(value = values)df_30_34 = imputeDF_Pandas[(imputeDF_Pandas[‘age’] >=30 ) & (imputeDF_Pandas[‘age’] <= 34)]
values = {‘smoking history’: 0.25, ‘BMI’:29.6}
df_30_34 = df_30_34.fillna(value = values)df_35_44 = imputeDF_Pandas[(imputeDF_Pandas[‘age’] >=35 ) & (imputeDF_Pandas[‘age’] <= 44)]
values = {‘smoking history’: 0.25, ‘BMI’:30.15}
df_35_44 = df_35_44.fillna(value = values)df_45_49 = imputeDF_Pandas[(imputeDF_Pandas[‘age’] >=45 ) & (imputeDF_Pandas[‘age’] <= 49)]
values = {‘smoking history’: 0, ‘BMI’:29.7}
df_45_49 = df_45_49.fillna(value = values)df_50_59 = imputeDF_Pandas[(imputeDF_Pandas[‘age’] >=50 ) & (imputeDF_Pandas[‘age’] <= 59)]
values = {‘smoking history’: 0, ‘BMI’:29.95}
df_50_59 = df_50_59.fillna(value = values)df_60_74 = imputeDF_Pandas[(imputeDF_Pandas[‘age’] >=60 ) & (imputeDF_Pandas[‘age’] <= 74)]
values = {‘smoking history’: 0, ‘BMI’:30.1}
df_60_74 = df_60_74.fillna(value = values)df_75_plus = imputeDF_Pandas[(imputeDF_Pandas[‘age’] >75 )]
values = {‘smoking history’: 0, ‘BMI’:28.1}
df_75_plus = df_75_plus.fillna(value = values)
组合所有数据帧
all_frames = [df_2_9, df_10_13, df_14_17, df_18_24, df_25_29, df_30_34, df_35_44, df_45_49, df_50_59, df_60_74, df_75_plus]
df_combined = pd.concat(all_frames)
df_combined_converted = spark.createDataFrame(df_combined)
imputeDF = df_combined_converted
步 3B。处理不平衡数据
我们将执行 SMOTE 技术来处理不平衡数据。SMOTE 可以从这里引用:
X = imputeDF.toPandas().filter(items=[‘gender’, ‘age’, ‘diabetes’,’hypertension’,’heart disease’,’smoking history’,’BMI’])
Y = imputeDF.toPandas().filter(items=[‘stroke’])X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.1, random_state=0)
sm = SMOTE(random_state=12, ratio = ‘auto’, kind = ‘regular’)x_train_res, y_train_res = sm.fit_sample(X_train, Y_train)print(‘Resampled dataset shape {}’.format(Counter(y_train_res)))
请参考此链接了解参数
X_train 包含除 Stroke 列之外的所有数据列。
Y_train 包含笔画列数据。
将重新采样的数据组合成一个火花数据帧
dataframe_1 = pd.DataFrame(x_train_res,columns=[‘gender’, ‘age’, ‘diabetes’, ‘hypertension’, ‘heart disease’, ‘smoking history’, ‘BMI’])
dataframe_2 = pd.DataFrame(y_train_res, columns = [‘stroke’])# frames = [dataframe_1, dataframe_2]
result = dataframe_1.combine_first(dataframe_2)
将其改回火花数据帧
imputeDF_1 = spark.createDataFrame(result)
检查重新采样的数据。这与我们之前使用的代码相同。
如我们所见,我们成功地对数据进行了重新采样。现在我们将进入下一部分。
第四步。构建 Spark ML 管道
下面是一个 spark ml 项目的通用管道,除了我们没有使用字符串索引器和 oneHotEncoder。
Spark ML Pipeline
现在要构建一个汇编器,为此,我们需要一个二进制化器。
binarizer = Binarizer(threshold=0.0, inputCol=”stroke”, outputCol=”label”)
binarizedDF = binarizer.transform(imputeDF_1)binarizedDF = binarizedDF.drop(‘stroke’)
这将创建一个名为“label”的新列,其值与 stroke 列中的值相同。
assembler = VectorAssembler(inputCols = featureColumns, outputCol = “features”)
assembled = assembler.transform(binarizedDF)print(assembled)
汇编程序将预测笔画所需的所有列组合起来,产生一个称为特征的向量。
现在开始拆分数据
(trainingData, testData) = assembled.randomSplit([0.7, 0.3], seed=0)
print(“Distribution of Ones and Zeros in trainingData is: “, trainingData.groupBy(“label”).count().take(3))
培养
dt = DecisionTreeClassifier(labelCol="label", featuresCol="features", maxDepth=25, minInstancesPerNode=30, impurity="gini")
pipeline = Pipeline(stages=[dt])
model = pipeline.fit(trainingData)
测试
predictions = model.transform(testData)
AUC-ROC
from pyspark.mllib.evaluation import BinaryClassificationMetrics as metric
results = predictions.select(['probability', 'label'])
## prepare score-label set
results_collect = results.collect()
results_list = [(float(i[0][0]), 1.0-float(i[1])) for i in results_collect]
scoreAndLabels = sc.parallelize(results_list)
metrics = metric(scoreAndLabels)
print("Test Data Aread under ROC score is : ", metrics.areaUnderROC)
from sklearn.metrics import roc_curve, auc
fpr = dict()
tpr = dict()
roc_auc = dict()
y_test = [i[1] for i in results_list]
y_score = [i[0] for i in results_list]
fpr, tpr, _ = roc_curve(y_test, y_score)
roc_auc = auc(fpr, tpr)
%matplotlib inline
plt.figure()
plt.plot(fpr, tpr, label='ROC curve (area = %0.2f)' % roc_auc)
plt.plot([0, 1], [0, 1], 'k--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic Graph')
plt.legend(loc="lower right")
plt.show()
AUC — ROC Curve
正如我们所看到的,我们得到了 98 左右的 AUC-ROC 分数,这是非常好的。由于 SMOTE 技术的使用,模型可能会过拟合。(但是逻辑回归对这个数据集很有效。但是 pyspark2pmml 库中似乎有一些错误,不能正确导出逻辑回归模型。)因此,出于演示目的,我将使用决策树模型文件。
第五步。保存模型文件
为此,我们将使用一个名为 PySPark2PMML 的库,它的细节可以在这里找到(https://github.com/jpmml/pyspark2pmml)
保存 jupyter 文件并退出 jupyter 笔记本。
从https://github.com/jpmml/jpmml-sparkml/releases下载jpmml-spark ml-executable-1 . 5 . 3 . jar文件
上传到 SSH
上传后,如果我们运行“ls”命令检查,我们会看到我们的文件。
现在我们需要设置 jupyter notebook,当我们在 ssh 中键入 pyspark 时,我们需要打开 jupyter notebook。为此,我们需要更改环境变量。
更新 PySpark 驱动程序环境变量:
将下面几行添加到您的~/.bashrc
(或~/.zshrc
)文件中。按“I”插入新行。复制下面的代码并使用“CTRL+V”粘贴它。
export PYSPARK_DRIVER_PYTHON=jupyter
export PYSPARK_DRIVER_PYTHON_OPTS='notebook'
要保存并退出 vi 编辑器,请按“ESC”和“:wq”保存。
重启你的终端,然后输入“pyspark”。你应该可以运行 jupyter 笔记本。
您应该能够在其中一行中看到端口号。
现在要使用 pmml 库,通过调用下面的命令打开 jupyter 笔记本。
pyspark --jars /home/yashwanthmadaka_imp24/jpmml-sparkml-executable-1.5.3.jar
打开 jupyter 笔记本后,运行我们之前写的所有单元格。现在,添加下面这段代码。
trainingData = trainingData.drop(“features”)from pyspark.ml.feature import RFormula
formula = RFormula(formula = "label ~ .")
classifier = DecisionTreeClassifier(maxDepth=25, minInstancesPerNode=30, impurity="gini")
pipeline = Pipeline(stages = [formula, classifier])
pipelineModel = pipeline.fit(trainingData)from pyspark2pmml import PMMLBuilder
pmmlBuilder = PMMLBuilder(sc, trainingData, pipelineModel) \
.putOption(classifier, "compact", True)pmmlBuilder.buildFile("dt-stroke.pmml")
运行上述代码后,将在提到的位置创建一个新文件。将这个文件下载到您的本地桌面,让我们开始构建一个网站来与我们的模型文件进行交互。
整个 jupyter 笔记本可以在这里找到。
第六步。构建一个前端 ReactJS 应用程序与 PMML 文件交互。
步骤 6a。从我们的模型文件构建一个 REST 服务器:
对于与模型文件交互的应用程序,我们需要将应用程序公开为 REST web 服务。为此,我们将借助 Openscoring 库。
我们需要使用 maven 安装 Openscoring。确保将我们从 Google clouds 虚拟机下载的模型文件放入PATH/open scoring/open scoring-client/target 文件夹。
其中 PATH = open scoring 文件所在的路径。
安装后,我们需要按照下面的命令启动服务器。
首先,通过进入服务器文件夹并键入下面的命令来启动服务器
cd openscoring-server/targetjava -jar openscoring-server-executable-2.0-SNAPSHOT.jar
接下来,打开客户端文件夹,输入下面的命令。接下来,打开一个新的 cmd 并键入以下命令。
cd openscoring-client/targetjava -cp openscoring-client-executable-2.0-SNAPSHOT.jar org.openscoring.client.Deployer --model [http://localhost:8080/openscoring/model/stroke](http://localhost:8080/openscoring/model/stroke) --file dt-stroke.pmml
当我们访问http://localhost:8080/open scoring/model/stroke时可以看到下面的结构
步骤 6b。下载 ReactJS 前端并运行:
现在访问 this Github 链接并克隆这个项目。
下载后,使用 VS 代码打开这个项目文件夹。打开里面的终端,输入
npm install
在启动 ReactJS 应用程序之前,我们需要启用 CORS。为此,我们可以添加一个 chrome 扩展。
打开 CORS 后,在 VS 代码终端中键入以下命令。
npm start
将打开一个 web 界面,如下所示。
ReactJS Frontend
我们可以输入任何值并测试它。我们会得到预测,中风发生的概率和不发生中风的概率。
所有与 REST 服务器交互的方法都编码在 index.js 文件中。
附言
现实模型的 98 分是不可能达到的,这个博客的主要意义是展示如何与 pyspark 制作的 ML 模型进行交互。
我们的数据预处理越好,我们的模型就越好。模型的质量直接取决于我们使用的数据的质量和多样性。因此,最好花更多的时间进行适当的数据清理和数据过滤技术。
有用的链接
- SMOTE—https://medium . com/coin monks/SMOTE-and-adasyn-handling-unbalanced-data-set-34f 5223 e167
- pyspark 2 pmml—https://github.com/jpmml/pyspark2pmml
- 开场得分—https://github.com/openscoring/openscoring
- ReactJs 前端—https://github.com/yashwanthmadaka24/React-Js-Website
现在,是休息的时候了😇
在两周内构建和部署数据科学项目
数据科学冲刺
通过构建有趣的东西来学习自然语言处理、Flask 和 ML 模型部署的基础知识!
End product: a sentiment analysis web application with built-in ongoing learning capacity
我是一个动觉型学习者;我通过做、建造和破坏东西来学习。在这篇文章中,我将分享我如何建立一个基本的情感分析机器学习模型+web 应用程序,并在不到两周的时间内部署它,以学习三种新技术-自然语言处理(NLP),flask 和增量/持续学习。
学习成果
我从事这项事业的主要目标如下:
- 学习 NLP 基础知识并训练情感分析分类器
- 了解如何使用 flask 构建 web 应用程序
- 了解基于 ML 的 web 应用程序部署
我想以一种快速构建/学习的模式来完成这一切,重点是功能性,而不是美观性。我最终探索和学习的一些其他主题包括模型优化、增量/持续学习和 jQuery。本文使用的所有 Jupyter 笔记本和代码都可以在我的 Github 上找到。
数据集
对于这个项目,我需要一个数据集,它包含文本数据,但已经被标记和结构化。这将使我能够专注于基于 NLP 的预处理,同时浏览其他形式的数据准备。在看了几个不同的选项后,我最终选择了 yelp 评论数据集,因为它是预先标记的(文本评论和 1-5 星评级),但评论中的实际文本是混乱和不一致的。您可以从下面的原始 JSON 数据集中看到一个示例记录
Example record from raw JSON data
对于我的模型,我只对文本评论和 1-5 颗星感兴趣(也许将来我可以尝试用其他属性做一些有趣的事情)。此外,为了进一步简化我的培训流程并坚持快速发展的心态,我决定将标签从 1-5 分修改为与文本审查相关的积极(4 或 5 星)或消极(1 或 2 星)情绪。我放弃了 3 星评级的评论,因为它们的分类很难验证。几行代码之后,我把我的数据放在一个熊猫的数据框架中,如下图所示
Pandas dataframe with just the text review and positive/negative sentiment
这个文本有许多潜在的问题。有些字符如 *** 和 \n 对帮助理解文本没有实际作用,而其他字符如 $ 和*!*,可以洞察评价者的感受。此外,有许多人类可以理解的单词/俚语(8Gs = 8000 美元),但对机器来说几乎没有意义。所有这些以及更多的场景将在下一节中解决。
自然语言处理流水线
为了准备分类数据,我们必须首先设计一个 NLP 管道来准备文本数据。NLP 管道涉及几个步骤,其中一些步骤如下所示
Sample NLP Pipeline
在我们的例子中,像词性标注和命名实体识别这样的步骤不是很有用,因为手头的任务是一个样本分类问题。因此,我们将把重点放在最划算的技术上:标记化、停用词去除和 ngram 利用(下面讨论)。
对于我的 NLP 模型的第一次迭代,我决定使用 sklearn 的CountVectorizer()
方法实现一个简单的单词袋模型,并在大约 100,000 行上训练它。我利用 sklearn 的train_test_split()
方法将我的数据分解成训练和测试块。在尝试了一些不同的分类器(如逻辑回归、多项式朴素贝叶斯和线性支持向量机)后,我发现逻辑回归模型最适合这些数据。这是各个分类器的混淆矩阵-
Confusion Matrices for the various classifiers
进一步思考,我发现逻辑回归模型将无法在项目后期利用增量学习,而线性 SVM(由 sklearn 的SGDClassifier()
提供支持)可以。精确度的折衷很小,逻辑回归的平均 F1 值为 93%,而 SVM 的平均 F1 值为 92%。因此,我决定继续进行线性 SVM,以便在项目后期利用增量学习。
另外,我选择用HashingVectorizer()
替换CountVectorizer()
,因为它使用一种叫做哈希的技术来将输入处理模型的大小减少 99%。唯一的主要损失是失去了查看模型中显式单词的能力(哈希将所有单词转换为数字),这对我来说不是一个大问题。你可以在这里阅读更多关于这款车型的信息。
模型优化
我做了一些事情来优化我的模型。正如你从上面的困惑矩阵中看到的,我的培训和测试数据中负面评价的数量都比正面评价少。这导致我的负标签 F1 分数为 82%,正标签 F1 分数为 94%。为了减少这种差异,我必须通过确保对应于两个标签的记录数量是可比较的来平衡我的数据集。在平衡我的数据集后,我开始省略停用词,如和,一个等。从单词袋模型来看。
在这两个步骤之后,我的模型的 F1 分数从 92%下降到 91%,但是之前两个标签之间的差距变小了。我的负面和正面评论的 F1 分数分别是 91%和 90%,所以总体上分类器表现更好。接下来,我决定利用 ngrams 来进一步优化我的模型。
Ngrams 简单地指的是一组连续的 n 个共现的单词。比如说-
In the sentence "I love data science", the 1-grams would be ["I", "love", "data", "science"]. The 2-grams would be ["I love", "love data", "data science"] and so on.
从上面的例子中,您甚至可以看出为什么使用 ngrams 会有所帮助。出现在一个句子中的两个单词“数据科学”会比一个单词“数据”和“科学”提供更多的信息。现在,回到优化:我利用 1.2 克来进一步加强我的模型。优化前后的混淆矩阵如下-
Before and after the ngram optimization
正如您所看到的,这是一个重大的改进,因为正面和负面的错误猜测都减少了。该模型的最终 F1 分数上升到 92%,两个班级之间的差距可以忽略不计。此时,模型已经准备好。接下来,增量/持续学习。
增量学习
正如你们已经看到的,我们的情感分类模型远非完美。这就是增量学习的魅力所在。
增量学习是一种机器学习范式,其中每当新的示例出现时就进行学习过程,并根据新的示例调整已经学习的内容。
如果你想一想人类是如何学习语言的,你会很快发现他们是在渐进地学习。每当父母纠正一个孩子,老师纠正一个学生,或者两个朋友互相纠正,一种增量学习的形式就发生了。在这一节中,我们将为我们的 NLP 模型构建一个类似的系统,这样当它出现分类错误时,用户可以“纠正”它。
还记得我们之前因为这个功能选择了 SGDClassifier()吗?SGDClassifier 有一个内置的partial_fit()
方法,它将帮助我们用几行代码实现增量学习,如下所示
# Incremental training# example x
X_instance = cv.transform(["I like this place, but not much"]) # user determined label
y_instance = ['n']# max iterations of training until the classifier relearns
max_iter = 100# loop for a maximum of max_iter times
# partially fit the classifier over the learning instance
# stop when classifier relearns (or max_iter is reached)
for i in range (0,max_iter):
clf.partial_fit(X_instance, y_instance)
if(clf.predict(X_instance) == y_instance):
break
利用这一点,无论何时出现错误,我们都可以在新数据上训练我们的模型,同时仍然保持初始训练。下面可以看到一个例子——
Example of incremental learning
总结和第 2 部分
到目前为止,我们已经取得了相当大的成就:我们已经创建了一个基本的 NLP 分类器,它可以读取一段文本,并将情绪分为积极或消极。我们已经使用停用词和 ngrams 等 NLP 技术优化了我们的模型,以实现 92%的基线 F1 得分。最后,我们已经编写了代码来增量训练我们的分类器,只要它出错,学习就不会停止!
在下一部分中,我们将在前面介绍的基础上,使用 Flask 构建一个基本的 web 应用程序。特别是,我们将构建 API 来对用户输入的文本进行分类,并逐步教授/纠正我们的模型。这将使我们能够作为用户与我们的模型进行交互,并进一步了解机器学习模型部署!
我希望你喜欢读这篇文章,就像我喜欢把它放在一起一样。如果您有任何问题、反馈或意见,请随时通过 LinkedIn 或我的网站与我联系。
[1]:耿 x .,史密斯-迈尔斯 K. (2009)增量学习。载于:李世泽,贾恩(编)《生物识别百科全书》。马萨诸塞州波士顿斯普林格
在 Azure 上使用自动化 ML 构建和部署机器学习模型。
Overview of the automated machine learning process.
这篇文章讲述了使用 Azure 的自动化机器学习功能的基础知识。我们将训练和部署一个可操作的机器学习模型。在这篇文章之后,你将能够构建一个端到端的解决方案来部署一个机器学习模型,而无需编写任何代码。
目标是帮助您理解构建和部署机器学习模型的基础。它不会涵盖建模技术/最佳实践、安全性/授权等概念,但对于以下人员来说是一个起点:(1)希望在不编码的情况下构建 ML 模型,或者(2)希望了解自动化机器学习的可能性以及这对您和您的组织有何价值。
在这个练习中,我们将使用微软 Azure 的自动机器学习平台。我们设置环境/资源,在数据集上训练多个模型,并在 Azure 上部署这个模型,这样我们就可以实际使用它了。
本文共分三个部分:
1 .【无聊的东西】(在 Azure 中设置资源)
2。【酷玩意儿】(训练模特)
3。“我现在可以展示我的朋友”(部署模型)
所以事不宜迟,让我们来构建一些很酷的东西(又名 无聊的东西)!
建立
注意事项
使用 Azure 上的服务需要花钱。有办法获得免费学分或试用期(例如:通过 这个链接 )。)
整个实验花了我 3.02€(没有使用任何免费信用):
Total costs for Azure Automated Machine learning experiment.
在本文的最后,我们将介绍如何清理,这样我们就不会产生任何意外的成本。
让我们开始吧!
一笔账
(在此注册如果您还没有 azure 帐户,并确保您设置了支付方式,这应该是注册过程的一部分)。
注册后,你应该会看到你的默认 Azure 主页:
Azure Home Page
创建资源组
资源组是“保存 Azure 解决方案相关资源的容器”。它将帮助我们容易地区分属于我们的 ML 模型和部署的资源(,并将帮助我们以后容易地删除它)。**
从 Azure 主页,点击左侧菜单中的“资源组”并创建一个新的资源组:
Create new resource group in Azure Portal
在这个练习中,地区并不重要:根据你在这个世界上的位置选择一个。
创建机器学习服务工作区
Azure 机器学习服务是一个资源集合,可以帮助你创建、测试和部署机器学习模型。自动化 ML 是这个集合的一部分,这就是我们在这里使用的。确保将新资源分配给上面创建的“autoML”资源组。
Create new resource in Azure Portal
在这个练习中,地区并不重要:根据你在这个世界上的位置选择一个。
下载 数据集 。
在本文中,我们将构建一个回归模型来预测纽约市 AirBnB 的正确价格。数据来自 Kaggle,可通过链接获得。
我们现在可以把这个文件保存在本地:我们将在下一节把它上传到 Azure。
重述
我们已经为 Azure 创建了一个帐户,建立了一个资源组来组织我们与此练习相关的资源,并创建了一个 Azure 机器学习服务工作区,我们可以从我们的主页上看到:
A brand new ‘autoML’ workspace is visible under our recent resources (or by searching in the top bar)!
恭喜恭喜!我们已经做了无聊的事情!让我们赶紧过去做一些实际的数据相关的工作。
培养
在我们的 Azure 主页上,我们可以点击“autoML”资源进入资源页面。我们将通过点击“立即启动预览”横幅使用新界面,或者通过此链接直接进入新界面。所有的训练步骤都将在新的 Azure 机器学习界面上执行。
选择最近创建的订阅和 Azure 机器学习资源:
Your subscription and machine learning workspace.
上传数据集 导航至“数据集”(左侧菜单),上传解压缩后的数据集“Output.csv”,然后点击“创建数据集”。
从本地机器选择文件。所有其他设置可以保持原样。单击完成后,数据集将上传:
Uploading a dataset into Azure Machine Learning Services
现在,我们的数据集将从“数据集”页面可见:
datasets for Automated Machine Learning
创建并运行实验
从菜单中,转到“自动化 ML”并点击蓝色的“创建实验”按钮:
create automated machine learning experiment
在下一个屏幕中,我们必须提供一个名称和训练计算。你可能想知道“训练计算”是什么意思。我们想要训练的模型需要一台计算机来运行。Azure 提供虚拟计算机,这些计算机位于世界各地的大型数据中心。这使得我们可以在需要的时候启动任何大小的计算机,并在完成后再次关闭,只使用我们需要的东西。
在本练习中,我们将启动一个小型计算资源(虚拟机)。单击“创建新计算机”并为其命名,如 autoML。我们将使用默认的 标准 _DS12_V2 — — 4 个 vCPUs,28 GB 内存,56 GB 存储 (保留其他设置不变):
Creating a new compute. After clicking create it takes up to a few minutes to provision the resource.
当我们选择一个实验名称时(我选了——惊喜!-“autoML”)新计算已创建,请确保选择我们刚刚创建的培训计算,然后单击“next”。
接下来,我们选择之前上传的数据集,我们将被要求输入更多内容。现在,对于我们的用例,目标是根据数据集中的其他信息预测价格(目标列)。
Dataset settings for automated machine learning
我们可以检查 profile 选项卡来进行一些数据探索,但是为了简洁起见,我们将直接选择要包含的列,即预测任务和目标列:
Data model for Automated Machine Learning
*首先,我们排除模型中不需要的列。您可能会在将来探索/包括其他专栏,但是按照本教程:选择与我所做的相同的专栏。我们包含的列有:邻居 _ 组、邻居、房间 _ 类型、价格(目标列 **)、*最低 _ 夜数、评论数、最后 _ 评论、每月评论数。
我们正在预测一个数量(每晚的价格),所以我们的预测任务* 是回归,目标列 是价格。*
我们可以使用高级设置,在将来的实验中你可能会用到,但是现在我们让它保持原样。这意味着我们选择“Spearman 相关性”作为衡量和比较模型的主要指标。我们把培训工作时间设定为 30 分钟。
向下滚动,点击开始,恭喜你自己!您现在是数据科学家了!*
Azure 现在将增加合适的资源。之后,ML 的实际“自动化”部分开始了:Azure 将尝试并适应不同的模型和设置,以找到最适合我们的数据和练习类型(回归)。我们将很快开始看到不同的模型和它们的准确度分数。此外,在运行期间,我们不必停留在任何特定的页面上;所以请随意浏览 Azure ML 界面。
请记住,Azure 将需要一些时间来旋转资源并获得第一批模型的结果,因此整个过程需要的时间比我们选择的 30 分钟训练时间更长。
但是不要告诉你真正的数据科学家朋友和同事,因为他们可能不会同意。
排行榜
在实验运行期间和之后,我们可以通过排行榜查看每次运行的结果:
We can check out our experiment leaderboard! Here we can check out the different models that have been tested on our dataset.
通过点击任何已完成的迭代,我们可以看到一组更广泛的结果指标和图表:
Model results
一旦实验完成,我们可以四处看看,看看运行了什么类型的模型,它们的表现如何等等。之后我们将进入下一环节:部署
部署
酷!我们有一个排行榜和一个成功的模型。现在怎么办?我们还不能真正展示任何东西:我们希望给我们的模型输入列(如邻居、每月评论数和平均评论数),然后接收每晚价格的估计。进入本教程的“我现在可以展示我的朋友”部分!
为了获得每晚价格的估计,我们需要“部署”模型。这将成为一个“容器”,能够在指定的环境(python)上运行我们的模型代码(获胜的模型),并处理传入的请求。Azure 机器学习服务有一个非常棒的功能,包含在自动机器学习服务中,在部署时为我们做所有的工作:“部署最佳模型”。它在我们之前查看的排行榜页面上,以了解不同型号的表现。
使用您喜欢的任何部署名称和描述,并让评分脚本和环境脚本“自动生成”。点击部署,这将需要大约 20 分钟才能完成。
Deploying with Azure Automated Machine Learning
当我们的模型被部署时,这个模型将会出现在“模型”页面上。模型包括一个端点。API 端点通常被定义为“通信通道的末端”。这个定义本身并没有真正帮助我们(完全没有),但是请这样想:我们有一个模型,我们希望通过发送输入*(我们在模型中包括的列)并获得输出(每晚价格的估计)作为回报来与这个模型进行通信。我们不能对我们的模型/端点抛出任何东西,然后期望得到正确的响应,所以下一步是找到我们的端点并向我们的模型发送一个 HTTP 请求!*
当我们部署模型时,Azure ML services 创建了一个 python 脚本,该脚本已经部署在一个“容器”中。这个容器运行在 Azure cloud 的某个地方,现在可以接收来自我们的 HTTP 请求了。
为此,我们需要做三件事:
确定终点计分 URI
在 Azure ML 界面中,可以通过从左侧菜单转到“端点”来获得端点
Azure Automated machine learning endpoints.
我们单击我们新部署的模型,将会看到一个属性列表,其中相关的一个是“得分 URI”。**
The scoring URI in azure ml. Not showing up? Your deployment might not be fully completed yet. The Scoring URI shows up when the complete deployment is finished.
还记得谈论 API 端点吗?这个评分 URI 是我们已经创建的模型的端点,这意味着我们可以向这个端点(输入)发送(“做请求”)一些东西,并将接收一个响应(输出)。如果我们正确地格式化请求,我们将得到我们想要的:给定输入列的每晚估计价格。
创建一个 HTTP 请求
对于那些没有处理 HTTP 请求经验的人来说,这一部分可能会有点混乱,但是只要继续按照步骤操作,您就可以让它正常工作了!
*从功能上来说,我们想要做的是发送一个 http 请求,在这里我们为已经包含在模型中的列输入值(*除了目标列,当然这是我们想要返回的一列)。这意味着我们想发送以下信息:
“邻居 _ 组”、“邻居”、“房间 _ 类型”、“最低 _ 夜数”、“评论数”、“最后 _ 评论”、“每月评论数”。
这些信息被称为我们的 http 请求的“主体”, Azure 在他们的文档中描述了我们应该如何格式化这些信息以使其能够被模型解释:
REST API 期望请求的主体是一个 JSON 文档,其结构如下:
*{
"data":
[
<model-specific-data-structure>
]
}*
其中,我们的“特定于模型的数据结构”看起来像这样:
*{
"data": [
{
"neighbourhood_group": "Queens",
"neighbourhood": "Ridgewood",
"room_type": "Entire home/apt",
"minimum_nights": 3,
"number_of_reviews": 9,
"last_review": "2019-06-26T00:00:00.000Z",
"reviews_per_month": 2.1
}
]
}*
一个 HTTP 请求还包括一个或多个“头”,根据文档,我们需要添加一个“内容类型”头:
*“Content-Type” : “application/json”*
总而言之,基于:(1)端点/评分 URI,(2)正文(输入)和(3)标题,我们可以创建一个请求,该请求将返回(如果一切正常)每晚的估计价格!让我们来测试一下吧!
对于好奇的人(我假设你们所有人都做到了这一步):以下是完整的请求:
*POST /score HTTP/1.1
Host: *<scoring-uri>*
Content-Type: application/json
User-Agent: PostmanRuntime/7.17.1
Accept: */*
Cache-Control: no-cache
Postman-Token: *<token-value>*
Host: *<scoring-uri>*
Accept-Encoding: gzip, deflate
Content-Length: 335
Connection: keep-alive
cache-control: no-cache{
"data": [
{
"neighbourhood_group": "Queens",
"neighbourhood": "Ridgewood",
"room_type": "Entire home/apt",
"minimum_nights": 3,
"number_of_reviews": 9,
"last_review": "2019-06-26T00:00:00.000Z",
"reviews_per_month": 2.1
}
]
}*
这里面有一些我们没有涉及到的信息;不要担心,因为这对理解我们的端到端工作流并不重要。进行到最后一部分!
发送 HTTP 请求
此时,您已经有了一个可通过 API 端点(评分 URI)调用的部署模型。任何人(实际上是任何人,因为我们没有对 API 进行任何授权)都可以通过 API 端点在他们的应用程序、笔记本或其他(商业)应用程序中使用您的漂亮模型。当我从两个段落向上发送请求时,我得到以下结果:
*"{\"result\": [125.5851758821278]}"*
厉害!因此,这告诉我们,给定模型输入(Queens,Ridgewood,whole home/apt,3 晚,9 条评论,2019 年 6 月 26 日的最后一条评论,平均每月 2.1 条评论),估计每晚的价格将为 125.58 美元
很简单!显然你想看到这一点来相信它,所以我们开始吧:
下载邮差,API 开发免费 app。我们将使用 his 向我们的模型发送 HTTP 请求。一旦安装。我们将重复以下三个步骤:设置 URL(为 URI 评分)、添加内容类型标题和添加正文:
打开 Postman,从“新建”选项卡中选择“请求”:
Postman Create New Request
提供名称并创建收藏/文件夹,然后点击保存:
您将看到一个新屏幕,我们可以在其中创建 API 请求。
将“http 方法”设置为 POST,并在放置“输入请求 URL”占位符的位置复制评分 URI:
当我们转到 postman 中的“Body”选项卡时,我们采取两个动作:
1。将主体类型设置为“raw ”,并将类型设置为 JSON
2。将先前显示的请求中的请求列和值复制到正文字段:
*{
"data": [
{
"neighbourhood_group": "Queens",
"neighbourhood": "Ridgewood",
"room_type": "Entire home/apt",
"minimum_nights": 3,
"number_of_reviews": 9,
"last_review": "2019-06-26T00:00:00.000Z",
"reviews_per_month": 2.1
}
]
}*
Body and Response
我们之前讲的 header(“Content-Type”)是 postman 自动添加的,因为我们把 raw body 的类型设置为“JSON”。您可以在标题选项卡上看到这一点。
这意味着我们已经涵盖了请求的所有部分!是时候点击蓝色的发送按钮,完成我们的第一个请求了!
Postman response
你做到了!给自己一个击掌,放上你最喜欢的果酱(我推荐DMX——派对(在这里)为了这个场合)并向你自己的部署的自动机器学习模型发出几个请求!
当您准备好时,请确保删除资源组,这样您就不会在 Azure 上产生任何意外费用:
Delete Azure Resource group
考虑和想法
通过阅读本教程,您可能已经注意到,当涉及到使数据科学可操作时,有相当多的工作不一定与数据科学相关。我坚信对于任何数据工作者来说,实验都是有价值的。我们有一套令人惊叹的云服务提供商(开源)框架,可以快速轻松地开始测试新想法。最重要的是入门!
何时使用自动化 ML
似乎有一场关于自动化机器学习的持续辩论,人们大致分为两类:“那不是数据科学!”以及“我们根本不再需要数据科学家了!”。真相可能介于两者之间,但需要一定的细微差别。
它总是依赖于您的特定需求和可用的能力,但是考虑到您在选择 autoML 供应商/框架时拥有的选择数量以及它得到的关注数量,我们可以有把握地假设它会一直存在。
我认为了解并可能应用自动化 ML 将有助于组织利用新技术,这可能是有价值的,尤其是在以下情况下:
- 您没有数据科学团队或者想要通过自动化“更简单”的数据科学任务来利用稀缺的数据科学资源。
- 您想要迭代完整的端到端流程,以便快速开发分析用例。我们应该将时间分配到流程的所有部分,而不仅仅是建模部分,以确保我们最终得到的解决方案不仅准确,而且有效(可操作)。
- 你想建立一个*‘基线模型’**。自动化的 ML 模型不一定是最终的模型。*
我希望这是一个对你开始自动机器学习有用的教程!请在回复中留下你的想法/建议!
(随意伸手上LinkedIn也是)。
建议进一步阅读:
数据科学方法论—https://medium . com/forward-artificial-intelligence/the-Data-Science-methodology-50d 60175 a06a
为数据科学项目构建和标注影像数据集
构建影像数据集的一些提示和技巧
使用标准化数据集对于新模型/管道的基准测试或竞争非常有用。但对我来说,至少当你把东西应用到你自己选择的项目中时,数据科学的许多乐趣就来了。这个过程的一个关键部分是建立一个数据集。
所以有很多方法可以建立图像数据集。对于某些事情,我只是合理地截取了一些截图,比如当我生病的时候,用《闪电侠》第四季建立了一个面部识别数据集,并用 labelimg 对其进行了注释。我采取的另一条路线是手动下载一堆图像,只显示图像并在 excel 电子表格中标记它们……对于某些项目,你可能只需要用手机拍一堆照片,就像我制作骰子计数器时的情况一样。
这些天来,我想出了更多的技巧,使这一过程变得更容易一些,并一直在努力改进。
获取数据集的方法:
我下载图片管道中的第一个重大升级是使用一些基于 PyimageSearch 教程的 javascript 从谷歌获取图片 URL,并为圣诞老人探测器制作数据集。
这个的主要思想是你可以使用搜索作为那个类别的“标签”。所以你可以在谷歌上搜索圣诞老人,拉下这些网址,下载这些图片,用“圣诞老人”作为标签。这个过程非常好,我已经在一些个人项目中使用过。但是我们在技术上可以做得更好,另一个 PyimageSearch 教程为此铺平了道路。
作为前一种必须手动搜索并输入 javascript 的方法的替代方法,PyimageSearch 提供了另一个使用 Bing 搜索 api 快速制作图像数据集的教程这里。这是一个我非常喜欢的方法,它很容易与 Bing api 交互,以自动化的方式收集大量的图像非常好。我只在试用期间玩过 Bing API,但对结果很满意,所以如果真的是这样,我可能会根据需要付费。
这两种方法都可以让你相当快速地下载图像,如果你只是在做一个多类类型的问题,那么你已经有你的标签了。然而,需要采取一些额外的步骤来清理数据集,这种标注方法在多任务或多标签类型问题的情况下帮助不大。
下载完图像后
对于这两种方法,你通常每次搜索只能得到几百张与你给的搜索词相关的图片。我根据 PyimageSearch 教程采取了几个步骤。
- 过滤掉小图片:当我下载图片时,我会检查它们的大小,过滤掉低于某个阈值的图片,比如任何一边低于 300 像素。由于大多数图像模型拍摄的图像介于 224x224 和 512x512 之间,这有助于剪切掉您可能已经提取的超低质量图像
- 消除重复:如果看起来有很多完全相同的内容,你可以使用 Resnet18 之类的工具,通过检查重复的特征向量,把它们过滤掉。这在大型数据集上不是最实用的,但当有 1-10K 影像时,也不是那么糟糕。这样做的想法是,复制的图像可能会允许模型在性能指标上“作弊”,如果它们被放入训练和测试分割中,那么如果可能的话减少它们是好的。相位化是另一种潜在的重复数据删除方法,但由于图像的尺寸缩小得相当小,并且变成了黑白图像,因此非重复图像可能会出现重复,因此需要权衡。
- 手动修剪…这不是最有趣的活动,但非常重要。在我摄影的日子里,我最喜欢使用的工具是 adobe bridge,据我所知,我似乎可以在新的 adobe 系统上免费使用它。不过,我也只是在 Windows 或 Mac 的 Finder 中用大图标做了这些。这是尝试从数据集中的不同类中移除低质量或不相关的数据。我发现这一步有点级联到最终模型和类的质量,所以如果你想要定义良好的类,我建议在删除图像时要非常积极。
Sample adobe bridge review mode with critical role!
我喜欢 adobe bridge 的原因之一是,我基本上可以用箭头键过滤图像,并使用向下箭头扔掉图像。当你完成审查模式时,你剩下的所有你没有丢弃的图像仍然被选中,所以我通常只是把它们复制到一个新的目录中,这个目录只包含干净的图像。对于一个项目,我在一两个小时内看完了 7K 张图片……如果使用默认的 Mac 界面,时间会长得多。
标记
因此,使用 PyimageSearch 方法,如果你正在处理一个简单的多任务问题,那么你可以为你下载的每一组 URL 标记标签,并将它们用作类别标签。然而,根据项目的类型,除了那些基本的类名之外,你可能还需要额外的标签。
对于我使用的管道对象检测模型,我更喜欢 labelimg ,它有一组很好的热键,这样你就可以很快地通过标注数据集。
对于图像分割,我使用来自牛津的注释器,通过 (VGG 图像注释器)。我猜他们是发明 VGG 建筑的牛津小组?
直到最近,我还没有真正想出在一张图片上标注多个标签的好方法,但是一位同事向我展示了一个叫做鸽子的注释器。它有很好的功能来注释 jupyter 笔记本中的图像和文本数据,用于标准的多类分类或回归类型问题。这个图书馆很酷,建造这个图书馆归功于阿格马尼迪斯。
Sample pigeon usage for image classification
然而,我不怎么处理回归,也很少需要构建直接的多类数据集,因为我可以使用基于 PyimageSearch 方法的标签。许多现实世界的问题并不像只有一个标签那么简单,所以我倾向于做多标签和多任务类型的问题。所以我做了一个扩展了标准鸽子库的包来做多标签图像标注!
多标签鸽子
出于某种原因,当我看到鸽子库时,我觉得用它来标记我的多标签/多任务问题真的很酷。我做了我的修改,并将其添加到 pypi 中(这是我以前没有做过的,我基本上是按照这个教程做的)。所以我想我正在介绍我的多标签鸽子库!
在它目前的状态下,我所做的只是增加了一次标记多个任务的功能,用户可以选择他们想要的标签。这个想法是,与我以前主要靠手动操作的过程相比,它让一次标记一堆不同的问题变得更简单。也因为我不是最成人的,所以我用了赤壁命运系列的人物来显示注释。
Easy labeling is “good civilization”
基本上,演示显示的是如何标记 3 个任务问题。要标记的不同任务由一个字典定义,其中每个任务名称是一个键,一个潜在答案列表是值。
对于不同的任务,我只是标记一个图像是否。
- 可爱:是或不是
- 军刀(用剑?):是或否
- 颜色:蓝色,金色,白色,红色。
前两个是二进制的,我只回答是或不是,但是对于颜色,我为一些标记了多种颜色。所有这些结果都记录在一个字典中,其中键是图像的路径,值是包含任务名称和为每个任务按下的按钮的字典。
对我来说,这是对我自己生活质量的提升,我想我可以把它扔进深渊,如果其他人认为它有用,他们可以随意使用它!
结束语
我最终建立了许多数据集,并像某种数据集龙一样囤积起来,所以我得到了一些不错的实践,并在此过程中学到了一些东西。因为它是项目中非常基础的一部分,但并不经常被谈论,所以我想发布一个关于它的帖子可能会很酷,但我仍然不确定它是否对人们有用(也许每个人都已经知道这些东西了?谁知道呢?).
但是在一天结束的时候,就像大多数写博客的事情一样,我认为把它扔出去是值得的,如果它能帮助一些人,那就太好了!如果没有,那也很酷!
构建和共享闪亮的应用程序
如何尽快上手
支持本文的所有代码都可以从 MatrixDS 项目中派生出来
Photo by Stephen Dawson on Unsplash
数据科学流程中一个经常被忽略的部分是部署一个可供非技术决策者使用的完整解决方案。RStudio Shiny 是构建和部署该解决方案的优秀工具。Shiny 为数据科学家提供了用 R 编程语言构建反应式 web 应用程序的能力。它也非常灵活,如果你想构建更健壮的东西,它允许你添加 HTML 和 CSS。作为一名数据科学家,我希望专注于构建能够产生价值的模型,并希望尽可能少地处理托管和基础架构需求。有几个工具可以帮助你托管一个闪亮的应用程序,最常见的是 RStudio Connect、shinyapps.io 和 MatrixDS。对于本教程,我们主要关注 MatrixDS,因为它是自包含的(您不需要使用任何其他工具来构建、托管和部署)。它还为用户提供了轻松协作创建模型和应用程序的能力。
TL;DR:这篇文章的其余部分大致遵循这个视频的结构。
在任何 MatrixDS 项目中,我们都可以创建一个闪亮的工具和一个 RStudio 工具。
如果你的项目还没有,Shiny 工具会立即创建一个“shiny-server”文件夹,并加载一个应用程序。r 文件。这个文件是闪亮应用程序模板的修改版本,当你点击“新建”,然后点击“闪亮 Web 应用程序…”时,RStudio 就会构建这个模板。Shiny 工具创建了一个容器,可以挂载和部署“shiny-server”文件夹中的任何内容。
以下代码行被添加到 Shiny 模板中以帮助部署:
.libPaths( c( .libPaths(), “/srv/.R/library”) )
这一行告诉托管闪亮应用程序的容器使用安装在 RStudio 实例中的相同库。
production <- TRUEif(production == FALSE) {
#if you using the RStudio tool
shiny_path <- “~/shiny-server/”
home_path <- “~/”
} else {
#if you are using the shiny tool
shiny_path <- “/srv/shiny-server/”
home_path <- “/srv/”
}
这里我们创建了一个名为 production 的变量。当 production 设置为 TRUE 时,应用程序将在闪亮的容器上运行。当 production 设置为 FALSE 时,应用程序将在 RStudio 中运行。因为每个工具都在一个单独的容器中,所以我们必须使用绝对路径,这样它们才能找到我们在代码中引用的文件或对象。
最后,你可以通过点击闪亮工具上的私人/公共切换来分享你的应用。当设置为公共时,任何具有该 URL 的人都可以看到生产中的应用程序,并且您可以轻松地将其与消费者或客户端共享。查看公开的闪亮应用程序不需要 MatrixDS 帐户。
如果您打开 RStudio 工具,您会立即进入我们创建闪亮应用程序的环境,无需安装。在这里,您可以进行任何修改,这些修改会立即显示在您的客户或同事正在使用的已部署版本上。
现在就在 MatrixDS 上测试构建和部署您闪亮的应用程序
在 LinkedIn 上联系我:【https://www.linkedin.com/in/alejandromartinezm/
在 MatrixDS 上联系我:https://community . platform . matrix ds . com/community/Alejandro/overview
监督学习的构建模块
监督学习的目标是根据大量输入预测输出。在监督学习问题中,输入和输出变量都是可测量的。这一系列技术由几个关键要素组成:
A)学习算法——以动态方式响应输入生成输出的过程,即输入/输出关系根据原始输出和生成输出之间的差异进行自我修改。
b)教师——用统计学术语来说,是帮助指导学习过程的可测量的结果变量。
c)数据集——在机器血管中循环的血液。我们的算法必须从中学习的一组输入和输出。
本文旨在说明和探索三种简单而强大的预测/分类方法:普通最小二乘法拟合的线性模型、k 近邻预测规则和朴素贝叶斯分类器。这些技术的范围是巨大的。我是说,巨大的。事实证明,今天使用的流行技术的一个大子集仅仅是上述技术的变体(无论如何是针对低维问题的)。
我们开始工作吧。
我们需要从数据中得到什么?
人们通常认为预测的力量在于模型,但是也应该强调高质量数据的重要性。质量是什么意思?在基本层面上,为了避免正式的数学定义,我们需要一组可测量的输入变量, X={X1,X2,…} ,以及一个输出变量, Y 。由于我们关注技术,我们假设对于我们输出的每个观察值, Y ,在数据集中我们有一个完整的输入集, X ,在收集、精度、偏差等方面没有错误。
打响指
Figure 1 — Bivariate Training Data Scatterplot
图 1 显示了我们训练数据的散点图。对于每个观察,我们有一对连续的输入,用一个彩色十字表示, X=(X1,X2) ,还有一个离散的输出变量, G ,它决定了每个十字的颜色——鲑鱼蓝或矢车菊蓝。我们将这两个组称为类,我们的目标是建立一个可以将任何观察分类的模型,即任何一对输入( *x1,x2),*分类为鲑鱼类或矢车菊蓝类。
我们有了数据集,现在让我们建立模型。
线性模型
线性建模(或线性回归)的基本原理是找到输入变量的线性组合,它能最好地预测/描述输出变量。
- 我们如何适应我们的模型?
我们使用关键成分 c) —我们的数据集!给定我们已经观察到的对 (X1,X2) 的集合,我们可以开始构建我们的模型 f ,作为 (X1,X2) 的某种组合,由原始输出(由我们的数据给出)和f生成的输出之间的差异指导。如果需要,我们的线性组合需要足够灵活,以允许部分(非整数)和多个变量。为了方便起见,创建了辅助变量—这些变量也被称为回归系数。
因此,“拟合模型”相当于优化一组系数值,以产生最接近Y 的估计值——等等!,我听到你喊,你说关是什么意思?问得好,需要一个度量标准来量化距离。通用指标也称为损失函数(或误差函数),试图提供一个数字来概括特定模型在数据集上测试的不准确性。在这里,我们将使用一种非常简单但非常流行的方法,叫做普通最小二乘法(或者只是‘最小二乘法’)。该指标对我们训练集中每个观察值的模型估计值和原始输出值(平方误差)之间的平方差进行求和。最小化这个误差导致我们模型的最佳系数集。
注意,线性模型是一种回归技术,与分类技术相反。也就是说,它们的原始应用是用于定量变量而不是定性变量建模。对于我们的例子,在我们的连续回归输出之上还需要一个层。
设整数 1 代表三文鱼类,0 代表矢车菊蓝类。我们现在面临一个二元分类问题,如果模型输出大于 0.5,则选择在鲑鱼类中分类,否则在矢车菊蓝类中分类。这就是所谓的决定边界。图 2 描述了我们的线性模型及其决策边界。线以上的所有点被分类为矢车菊蓝类,其余的点被分类为鲑鱼类。
Figure 2 — Linear Regression Decision Boundary
因此,对于任何坐标(X 1,X2) ,我们的模型立即吐出一种颜色——太棒了!完成颜色,发现行为,正确完成工作?不对。
这里做了一个关键的结构假设——基础过程表现出全局线性。换句话说,假设对于所有的 X ,Y 相对于 X 表现为恒定的变化率。如果这些数据是从以不同平均值为中心的两个独立分布中生成的,那么线性决策边界可能是最佳的。在这种情况下,没有提到生成分布。如果这两个类中的训练数据都来自 10 个低方差双变量高斯分布的混合,每个分布都有单独的、独立分布的双变量高斯均值,会怎么样?在这种情况下,没有理由建议最佳边界应该是线性的。虽然我们的视觉检查向我们显示了边界两侧明显的错误分类,但它没有向我们表明我们对潜在关系的误解有多严重。这举例说明了线性回归中的一个关键缺陷——严重依赖线性结构假设或统计术语,高偏差。
这就引出了另一种分类技术,它更适合我们刚刚介绍的场景。
k 近邻(kNN)
k-最近邻(或 kNN )的基本原理是基于来自训练集中在输入空间附近的观察集合的最常见输出来分配类别, X 。
- 我们如何拟合模型?
kNN 输出定义为 k 个最近输入的响应变量的算术平均值。同样,最近的意味着度量,所以我们将使用欧几里德距离(https://en.wikipedia.org/wiki/Euclidean_distance)。对于给定点 X=x ,仅需要数据集的 k- 大小的子集,而不是像线性回归中那样汇集整个训练集。本质上,该方法将分类视为多数投票,并假设行为是局部恒定的,即行为在每个邻域内不会改变。
Figure 3–12 Nearest Neighbour Classification Boundary
与线性模型的情况一样,我们将整数值 1 分配给鲑鱼类,0 分配给矢车菊蓝类。那么对于任何给定的 (X1,X2) ,kNN 输出 f 被定义为一组 12 个二进制数的算术平均值。很快注意到,输出可能不是整数,因此再次需要边界条件,以便提供我们想要的输出{鲑鱼,矢车菊蓝}。如果 f 大于 0.5,我们选择归入鲑鱼类,否则归入矢车菊蓝类。
图 3 显示了 k =12 的实现。显而易见的是,从线性结构假设中解放出来,可以实现更灵活的边界,甚至可以在大部分矢车菊蓝色区域中容纳鲑鱼分类区域。这种灵活性是 kNN 方法的主要优点,但是如果我们改变 k 会发生什么呢?
Figure 4–1 Nearest Neighbour Classification Boundary
图 4 显示了算法的特殊情况,其中模型仅基于数据集中最近的观察值分配输出,即 k =1 的情况。请注意,训练集中没有错误分类,这乍一看可能很好,但会导致训练集之外的分类/预测的高错误率,尤其是在模型训练数据集很小的情况下。
请注意,不同的 k 值的边界是不同的,这会引入噪声和选择 k 的额外任务(这里我们不讨论)。当模型偏差较低时,kNN 受到高方差的影响。换句话说,该模型可能对训练数据非常敏感,并且可能过度拟合该模型,而不是描述真实的输入/输出关系。
由于这种技术对数据的假设更少,因此算法有更多的变化。更深入的研究将探索不同的距离度量、加权平均值和 k 的参数选择技术对决策边界的影响。
朴素贝叶斯分类器
朴素贝叶斯分类器的基本原理是使用我们的先验知识分类到最可能的类别,并且基于我们应该在多大程度上信任观察到的证据的数学定理(贝叶斯定理—https://en.wikipedia.org/wiki/Bayes’_theorem)。
- 我们如何拟合模型?
该模型的程序需要计算和比较后验概率,该概率代表我们相信模型在给定所有输入 X. 的情况下准确描述 Y 的程度。这是一个封闭形式的计算,需要了解 Y 的分布(分布类型及其参数)。实际上,这限制了这种方法的使用——生成分布很少是已知的,但我们也要求它在某种程度上易于处理(易于评估)。
因此,为了使贝叶斯模型适合我们的例子,我们需要知道数据来自哪里。因为我自己模拟了这些数据,所以我可以告诉你,这些数据是由 10 个低方差高斯分布混合生成的,每个低方差高斯分布都具有单独的、独立分布的高斯平均值(就像前面讨论的场景一样!).
图 5 显示了这种方法的决策边界。
Figure 5 — Naive Bayes Classification Boundary
我们观察到边界看起来类似于 k=12 kNN 方法,尽管看起来是一种非常不同的技术。
概率论认为,如果几个变量不以任何方式相互依赖,那么一起观察它们的概率就是独立观察每个变量的结果。事实上,这种假设很少成立,因为我们感兴趣的建模系统中的变量彼此之间往往存在某种联系。因为这个原因,这个方法是幼稚的。
最后一个提示
任意点 X=x 处 Y 的最佳预测是给定所有可用信息的期望值。
我们已经看到,这三种技术中的每一种都为我们的初始分类问题提供了一个解决方案,但是它们在过程和决策边界上都有很大的不同。事实上,所有这三个都是由上面的陈述支撑的,其中最佳是通过前面描述的平方误差损失函数来测量的。这种说法已经在统计决策理论中得到证明,被称为回归函数。每种技术的差异可以通过它们对回归函数的解释来很好地总结。
线性模型:Y 在任一点 X=x 的最佳预测是给定所有可用信息的线性组合的期望。
kNN: 对任意点 X=x 的 Y 的最佳预测是给定 X=x 周围 k 大小的邻域的期望
朴素贝叶斯:任意点 X=x 处 Y 的最佳预测是给定所有可用信息的期望值(假设生成密度已知)。
这些构件是统计推断的重要基础。它们不仅易于实现,而且突出了统计建模的复杂性和在建模真实系统中出现的问题。自然延伸出现在以下问题中:
- 随着输入变量数量的增加,这些模型的性能会发生什么变化(维数灾难)?
- 我们如何比较模型的相对性能?
- 我们如何测试模型假设是否成立?为什么这些假设很重要?
- 如果我们用不同的损失函数重复我们的分析会怎么样?
欢迎反馈和评论!
构建块:文本预处理
在我们系列的上一篇文章中,我们介绍了自然语言处理的概念,你可以在这里阅读,现在你大概也想自己尝试一下吧?太好了!事不宜迟,让我们深入研究统计自然语言处理的构建模块。
在本文中,我们将介绍关键概念、Python 中的实际实现以及应用时要记住的挑战。完整的代码可以在 GitHub 的 Jupyter 笔记本上获得。
文字规范化
规范化文本意味着在将文本转换为更高级建模的特征之前,将其转换为更方便的标准形式。把这一步想象成把人类可读的语言转换成机器可读的形式。
标准化文本的标准框架包括:
- 标记化
- 停止单词删除
- 形态标准化
- 配置
数据预处理由许多步骤组成,任何数量的步骤可能适用于也可能不适用于给定的任务。更一般地说,在本文中,我们将讨论一些预先确定的文本主体,并执行一些基本的转换分析,这些分析可用于执行更进一步的、更有意义的自然语言处理[1]
标记化
给定一个字符序列和一个已定义的文档单元(文本的简介),标记化的任务是将它分割成小块,称为 标记 ,可能同时丢弃某些字符/单词,如标点符号【2】。通常,有两种类型的标记化:
- **单词标记化:**用于通过唯一的空格字符分隔单词。根据应用程序的不同,单词标记化也可以标记多单词表达式,如 New York 。这往往与一个叫做命名实体识别的过程紧密相关。在本教程的后面,我们将看看搭配(短语)建模,这有助于解决这个挑战的一部分
- 句子分词和单词分词一样,是文本处理中至关重要的一步。这通常是基于标点符号,如“.”, “?”, "!"因为它们倾向于标记句子的边界
挑战:
- 缩写的使用可以促使标记器检测没有边界的句子边界。
- 数字、特殊字符、连字符和大写。在“不要”、“我愿意”、“约翰的”这些表达中,我们有一个、两个还是三个代币?
实现示例:
from nltk.tokenize import sent_tokenize, word_tokenize#Sentence Tokenization
print ('Following is the list of sentences tokenized from the sample review\n')sample_text = """The first time I ate here I honestly was not that impressed. I decided to wait a bit and give it another chance.
I have recently eaten there a couple of times and although I am not convinced that the pricing is particularly on point the two mushroom and
swiss burgers I had were honestly very good. The shakes were also tasty. Although Mad Mikes is still my favorite burger around,
you can do a heck of a lot worse than Smashburger if you get a craving"""tokenize_sentence = sent_tokenize(sample_text)print (tokenize_sentence)
print ('---------------------------------------------------------\n')
print ('Following is the list of words tokenized from the sample review sentence\n')
tokenize_words = word_tokenize(tokenize_sentence[1])
print (tokenize_words)
输出:
Following is the list of sentences tokenized from the sample review
['The first time I ate here I honestly was not that impressed.', 'I decided to wait a bit and give it another chance.', 'I have recently eaten there a couple of times and although I am not convinced that the pricing is particularly on point the two mushroom and \nswiss burgers I had were honestly very good.', 'The shakes were also tasty.', 'Although Mad Mikes is still my favorite burger around, \nyou can do a heck of a lot worse than Smashburger if you get a craving']
---------------------------------------------------------
Following is the list of words tokenized from the sample review sentence
['I', 'decided', 'to', 'wait', 'a', 'bit', 'and', 'give', 'it', 'another', 'chance', '.']
停止单词删除
通常,作为停用词移除过程的一部分,有几个普遍存在的词被完全从词汇表中排除,这些词看起来对帮助分析的目的没有什么价值,但是增加了特征集的维度。通常有两个原因促使这种移除。
- **不相关:**只允许对有内容的单词进行分析。停用词,也称为空词,因为它们通常没有太多意义,会在分析/建模过程中引入噪声
- **维数:**去除停用词还可以显著减少文档中的标记,从而降低特征维数
挑战:
在停用字词删除过程之前将所有字符转换为小写字母会在文本中引入歧义,有时会完全改变文本的含义。例如,“美国公民”将被视为“美国公民”,或者“it 科学家”将被视为“IT 科学家”。由于us和it通常都被认为是停用词,这将导致不准确的结果。因此,通过词性标记步骤,通过识别“us”和“IT”不是上述示例中的代词,可以改进关于停用词处理的策略。
实施例:
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize# define the language for stopwords removal
stopwords = set(stopwords.words("english"))
print ("""{0} stop words""".format(len(stopwords)))tokenize_words = word_tokenize(sample_text)
filtered_sample_text = [w for w in tokenize_words if not w in stopwords]print ('\nOriginal Text:')
print ('------------------\n')
print (sample_text)
print ('\n Filtered Text:')
print ('------------------\n')
print (' '.join(str(token) for token in filtered_sample_text))
输出:
179 stop words
Original Text:
------------------
The first time I ate here I honestly was not that impressed. I decided to wait a bit and give it another chance.
I have recently eaten there a couple of times and although I am not convinced that the pricing is particularly on point the two mushroom and
swiss burgers I had were honestly very good. The shakes were also tasty. Although Mad Mikes is still my favorite burger around,
you can do a heck of a lot worse than Smashburger if you get a craving
Filtered Text:
------------------
The first time I ate I honestly impressed . I decided wait bit give another chance . I recently eaten couple times although I convinced pricing particularly point two mushroom swiss burgers I honestly good . The shakes also tasty . Although Mad Mikes still favorite burger around , heck lot worse Smashburger get craving
形态标准化
一般来说,形态学是研究单词是如何由更小的有意义的单位组成的,词素。比如狗由两个语素组成: 狗 和 s
两种常用的文本规范化技术是:
- **词干化:**该过程旨在识别单词的词干,并用它来代替单词本身。提取英语词干最流行的算法是波特算法,这种算法已经被反复证明是非常有效的。整个算法太长太复杂,无法在这里呈现[3],但是你可以在这里找到细节
- **词汇化:**这个过程指的是使用词汇和词形分析来正确地做事情,通常旨在仅移除屈折词尾,并返回单词的基本或词典形式,这被称为 词汇 。
如果遇到令牌 saw ,词干可能只返回 s *,而词汇化将根据令牌是用作动词还是名词【4】*来尝试返回 see 或 saw
实施例:
*from nltk.stem import PorterStemmer
from nltk.stem import WordNetLemmatizer
from nltk.tokenize import word_tokenizeps = PorterStemmer()
lemmatizer = WordNetLemmatizer()tokenize_words = word_tokenize(sample_text)stemmed_sample_text = []
for token in tokenize_words:
stemmed_sample_text.append(ps.stem(token))lemma_sample_text = []
for token in tokenize_words:
lemma_sample_text.append(lemmatizer.lemmatize(token))
print ('\nOriginal Text:')
print ('------------------\n')
print (sample_text)print ('\nFiltered Text: Stemming')
print ('------------------\n')
print (' '.join(str(token) for token in stemmed_sample_text))print ('\nFiltered Text: Lemmatization')
print ('--------------------------------\n')
print (' '.join(str(token) for token in lemma_sample_text))*
输出:
*Original Text:
------------------
The first time I ate here I ***honestly*** was not that impressed. I decided to wait a bit and give it ***another*** ***chance***. I have recently eaten there a ***couple*** of times and although I am not convinced that the pricing is particularly on point the two mushroom and swiss burgers I had were honestly very good. The shakes were also tasty. Although Mad Mikes is still my favorite burger around, you can do a heck of a lot worse than ***Smashburger*** if you get a ***craving***.
Filtered Text: Stemming:
------------------
the first time I ate here I ***honestli*** wa not that impress . I decid to wait a bit and give it ***anoth*** ***chanc*** . I have recent eaten there a ***coupl*** of time and although I am not convinc that the price is particularli on point the two mushroom and swiss burger I had were honestli veri good . the shake were also tasti . although mad mike is still my favorit burger around , you can do a heck of a lot wors than ***smashburg*** if you get a ***crave*** .Filtered Text: Lemmatization
--------------------------------
The first time I ate here I ***honestly*** wa not that impressed . I decided to wait a bit and give it ***another*** ***chance*** . I have recently eaten there a ***couple*** of time and although I am not convinced that the pricing is particularly on point the two mushroom and swiss burger I had were honestly very good . The shake were also tasty . Although Mad Mikes is still my favorite burger around , you can do a heck of a lot worse than ***Smashburger*** if you get a ***craving*** .*
挑战:
通常,完整的形态学分析至多产生非常有限的分析益处。从相关性和维度缩减的角度来看,两种形式的标准化都不能提高语言信息的总体性能,至少在以下情况下是这样:
实施例:
*from nltk.stem import PorterStemmer
words = ["operate", "operating", "operates", "operation", "operative", "operatives", "operational"]ps = PorterStemmer()for token in words:
print (ps.stem(token))*
输出:
*oper
oper
oper
oper
oper
oper
oper*
作为可能出错的一个例子,请注意,波特词干分析器将以下所有单词的词干转换为 oper
然而,由于以各种形式操作是一个常见的动词,我们预计会失去相当的精确性[4]:
- 运营和研究
- 操作和系统
- 手术和牙科
对于这种情况,使用词汇归类器并不能完全解决问题,因为在特定的搭配中使用了特定的屈折形式。从术语规范化中获得更好的价值更多地取决于单词使用的语用问题,而不是语言形态学的形式问题[4]
对于所有用于生成上述结果的代码,点击此处。
就是这样!现在你知道梯度下降,线性回归和逻辑回归。"
即将到来…
在下一篇文章中,我们将详细讨论搭配(短语)建模的概念,并一起演练它的实现。敬请关注,继续学习!
参考文献:
[1]预处理文本数据的一般方法— KDnuggets。https://www . kdnugges . com/2017/12/general-approach-预处理-text-data.html
[2]记号化—斯坦福 NLP 组。https://NLP . Stanford . edu/IR-book/html/html edition/token ization-1 . html
[3]牛疾病中的文本挖掘—ijcaonline.org。https://www.ijcaonline.org/volume6/number10/pxc3871454.pdf
[4]词干化和词汇化—斯坦福大学 NLP 组。https://NLP . Stanford . edu/IR-book/html/html edition/stemming-and-lemma tization-1 . html
如果你有任何反馈,请在本文中发表评论,在 LinkedIn 上给我发消息,或者给我发邮件(shmkapadia[at]gmail.com)
使用 SAS 和 Python 构建信用记分卡
无论你是在申请你的第一张信用卡,还是在购买第二套房子,或者在这两者之间的任何地方,你都可能会遇到申请过程。作为这一过程的一部分,银行和其他贷方使用记分卡来确定你偿还贷款的可能性。
自然,这意味着信用评分对于银行和任何与银行业合作的企业来说都是一个重要的数据科学主题。
由于我以前有过客户分析的经验,但不是专门的金融风险,我一直在学习如何开发信用记分卡,我想分享我所学到的东西,包括我的想法和代码实现。
记分卡和信用评分的价值
记分卡有两种基本类型:行为记分卡和应用记分卡。
- 行为记分卡更多的是对当前客户及其违约可能性进行预测或评分。
- 当新客户申请贷款时,应用记分卡用于预测他们成为盈利客户的可能性,并将分数与他们相关联。
对银行来说,信用评分有助于管理风险。作为消费者,我们被各种优惠轰炸。由企业评估消费者的信用价值和信用评分,根据风险、周转时间、不正确的信用拒绝等因素确定最佳产品解决方案。
如果在不该放贷的时候放贷,那么未来很可能会亏损。如果批准或拒绝信贷的周转时间有很长的滞后时间,或者银行不准确地拒绝了一个良好的客户信用,他们可能会失去这些客户的竞争对手。在这种情况下,你可能需要很长时间才能找回它们。
使用信用评分可以优化风险,最大限度地提高企业的盈利能力。
信用评分数据
本帖中信用评分示例的训练数据是真实的客户银行数据,出于显而易见的原因,这些数据被篡改和匿名化了。这些特征——在信用评分中被称为特征——包括孩子数量、家庭成员数量、年龄、居住时间、当前工作时间、拥有电话、收入等。我们的目标变量将是一个二元变量,其值为“坏”或“好”,与给定的某个历史时期的客户违约相关。
信用评分代码
对于这个分析,我使用名为 SWAT(用于分析传输的脚本包装器)的 SAS 开源库来用 Python 编码并执行 SAS CAS 动作集。SWAT 充当 python 语言和 CAS 动作集之间的桥梁。 CAS 动作集与 Python 中的库或 r 中的包同义。一个主要的区别和好处是,这些动作集中的算法已经高度并行化,可以在 CAS(云分析服务)服务器上运行。CAS 服务器是一个分布式内存引擎,我可以在其中完成所有繁重的工作或计算。代码和 Jupyter 笔记本在 GitHub 上都有。
信用评分法
证据的权重
我首先使用证据权重(WOE)方法转换我的数据。该方法试图通过将每个要素分割成多个条柱并为每个条柱分配一个权重来找到输入要素和目标变量之间的单调关系。假设收入水平上的 WOE 变换包括收入水平$ 100,000 到$ 150,000,那么该区间内的所有观察值将接收相同的 WOE 值,该值可以使用下面的公式计算。
Weight of Evidence Calculation
考虑收入水平在 10 万美元到 15 万美元之间的区间。在我们数据中所有“好”的观察结果中,30%来自这个收入水平区间,而只有 10%的“坏”观察结果来自这个区间。使用这些比例,你可以说,我们有 3:1 的赔率,收入在 10 万美元和 15 万美元之间的人是一个好的信用候选人,而不是坏的。然后,我们取自然对数和 100 的倍数,以便于用数字表示,我们得到了收入水平区间内所有观察值的收入水平权重值。
Weight of Evidence Example
数据管道
让我们将这种转换应用于整个数据集。使用 SAS 中的数据预处理动作集使得构建数据管道变得非常容易(图 1)。数据管道有助于常见手动数据科学步骤的自动化流程。该动作集可以为跨任何连续或标称特征的各种转换构建大型数据管道。
构建单一管道只需几个步骤:
- 将变量分配给角色。
- 构建变量转换。
- 附加转换以便以后应用于数据。
首先,我们将特性分配给它们的角色,关于转换和建模,分离名义变量和连续变量以及目标(图 1)
Figure 1: Assign features to roles
接下来,我们创建名为 req_pack1 的第一个转换,它是 request package 的缩写,是 datapreprocess.transform 操作中的参数。我给转换命名,传递特性列表和目标,并指定感兴趣的事件,在本例中是“坏的”。
我在 Python 中调用 discretize 来绑定连续值并指定 WOE 转换。在该变换中有一个正则化参数,您可以在该参数中使用最小和最大 NBins 参数指定一个范围。这使得能够使用信息值(IV)在那些箱中搜索以找到最佳箱号。IV 是分类模型中常用的统计数据,用于衡量特征集的预测能力。
第二个转换,我将其标记为 req_pack2,除了我正在转换名义输入,因此需要使用 cattrans 而不是 discretize 之外,它几乎是相同的。cattrans 参数代表分类转换。
然后,我们将这些列表附加在一起,以便稍后将转换大纲传递给我们的转换操作。
Figure 2: Set up weight of evidence transformation
数据转换
现在我们已经有了数据管道,让我们来转换数据(图 3)。我首先使用 table 参数引用我们的数据。然后我提供了我在图 2 中创建的 req_packs 列表以及所有的转换。我将输出表(casout)指定为 woe_transform。接下来,我使用 copyVars 将 target 和 _customerID 特性从原始表复制到新的转换表中。然后我给我们所有新转换的特征一个全局前缀“woe”。code 参数将转换保存为代码表。这将在以后用于对新数据进行评分。这有利于希望进行模型协作或为重复出现的工作构建更深的端到端管道的团队。
最后,我们将预览一下新的 WOE 表(图 3)。请注意我们一些客户的相同值。请记住,发生这种情况是因为对于一个给定的变量,这些观察值属于同一个箱,因此得到相同的权重。
Figure 3: Transform data and view new dataset.
可视化转化结果
转换操作在 CAS 服务器上创建了几个输出表,如图 3 所示。其中一个表叫做 VarTransInfo,它包含我们特性的 IV 统计信息。查看 IV 以了解我们的特征的预测能力并确定是否有必要将这些特征包括在我们的模型中,这是一种很好的做法。下面是对 IV 的计算。图 4 是我们每个特性的 IV 值的曲线图。强特征通常具有大于 0.3 的 IV,弱特征< 0.02, and anything > 0.5 可能是可疑的,需要仔细观察。图 4 显示了我们的特性几乎分成了两部分,一部分是强特性,另一部分是中间特性。此外,年龄变量看起来非常强大。目前,我们将保留所有功能。
Figure 4: Information value (IV) calculation
我们还可以根据分配给特征的箱来绘制特征权重值。除了查看这些区间的 WOE 值之外,数据科学家可能希望返回并手动配置每个特定变量的区间数(合并或拆分),这样更符合逻辑。重要的是要知道,WOE 试图在单个特征内建立分离,因为它与一个目标变量相关,所以应该有跨箱的差异。图 5 显示了您期望在表示这些特征内的分离的多个条块上看到的 WOE 值。
Figure 5: Plot of WOE value by bin for several features
这正是数据科学家需要了解业务的地方,以确保各条块之间的加权趋势符合逻辑。例如,对一家银行来说,客户可用的现金越多,它应该在各个箱中看到更高的权重,这具有商业意义。同样的逻辑也适用于工作、年龄或职业群体的时间。我们预计所有这些权重相对于箱来说会更高。
记分卡的逻辑回归
我们的下一步是使用新转换的 WOE 数据集拟合逻辑回归模型。我将展示非常简单的代码来训练模型并解释参数。
下面的图 6 显示了训练代码。以下是训练模型所涉及的步骤:
- 分配模型输入并将“woe_”连接到所有原始列名,以便它正确引用 woe_transform 数据集。
- 使用 table 参数引用 woe_transform 数据集。
- 指定目标和参考(根据模型参考“好”或“坏”)。
- 然后,指定变量选择的正向选择方法。正向选择从一个空模型开始,并根据特定标准(AIC、AICC、SBC 等)在每次迭代中添加一个变量。).
- 使用 code 参数保存我们的逻辑回归代码
- 使用 casout 为新的输出表创建一个名称,然后再次复制 target 和 _customerID 变量。
Figure 6: Logistic regression model
创建记分卡
最后一步是将模型扩展到记分卡中。我们将使用一种通用的缩放方法。我们需要通过拟合模型得到的逻辑回归系数,以及带有转换后的 WOE 值的 WOE 数据集。我们将对我们的训练表进行评分,以得出 logit 或 log odds 值。因为我们从逻辑回归中得到的分数是对数比值形式,所以我们需要将其转换成记分卡的积分系统。我们通过应用一些缩放方法来进行转换。
第一个值是目标分数。这可以被认为是一个基线分数。对于这张记分卡,我们将分数定为 600 分。600 的目标分数对应于 30 比 1 的好/坏目标赔率(target_odds = 30)。缩放不会影响记分卡的预测力度,因此如果您选择 800 作为缩放分数,就不会有问题。
下一个变量叫做 pts_double_odds,意思是双倍赔率的点数。这意味着,分数增加 20 分,我们认为申请人在违约方面表现良好的可能性就会增加一倍。例如,如果你有 600 分,你有 30 比 1 的机会成为一个好的信用候选人。但是 620 分会让你被认为好的几率提高到 60:1。下面的图 8 显示了预测赔率和得分之间的指数关系。在下面的图 7 中,您可以看到简单的计算,以及如何使用它们来导出我们的记分卡分数变量。
Figure 7: Scorecard scaling and logic
Figure 8: Predicted odds by score plot
形象化
我们可以在图 9 中看到记分卡分数变量的分布,平均分数为 456。
Figure 9: Distribution of scores with mean score
然后,我们还可以看到这些分数如何与我们的业务成为好客户或坏客户的概率相关联,如图 10 所示。你可以看到很好的曲线形状。
Figure 10: Plot of scores by predicted probability
最后,我们可以使用这些分数来根据消费者的信用分数确定他们的等级。取决于产品、贷款等。这些层级将有不同的级别。对于我的分层系统,我只选择了四分位数来说明临界值的构建,但您可以根据产品或服务选择任何变化。
Figure 11: Customer groups based on quartile scores
拒绝推理
我想简单提一下拒绝推断,因为这是信用评分中的一个重要步骤。到目前为止,我们已经根据好坏的标签拟合了一个逻辑回归模型,并将这些分数换算成记分卡。整个过程着眼于当前的客户群,这些客户群拥有大部分完整的数据和已知的信用(好的或坏的)。然而,信贷申请往往会丢失大量数据,从而导致信贷被拒。在这种情况下,拒绝信用是由于我们有偏见的模型,它只查看我们知道是好是坏的人的完整记录。我们需要包含一些方法来调查这些拒绝,并将这些信息包含回我们的模型中,这样就更少偏见,更好地概括。
这就是拒绝推理所达到的效果。简而言之,我们查看那些被拒绝的客户,他们的信用状况未知,并分别处理这些数据,然后将他们重新分类为好或坏。这通常通过基于规则的方法、类似于原始 logit 模型的比例分配、增加否定的 logit 的原始分数等来实现。这个主题可以是一个独立的讨论,因为有各种各样的方法和思想流派。现在,我只想说,为了优化你的模型,使其不偏不倚,更好地概括,应该调查和整合由于缺失数据而导致的否定。
摘要
总的来说,使用证据权重转换和逻辑回归模型来为客户获得分数,对于同样使用业务逻辑感的数据科学家来说,是一个非常强大的工具。有许多方法可以建立这些评分模型,虽然这只是其中一种,但我希望它有助于提供指导或激发新的想法。此外,任何时候,数据科学家都可以处理复杂的问题,如转换和信用评分,并说明结果,这对从业者和组织都是一种胜利。
奖励:计分函数或 Rest API
我创建了一个函数,它使用来自转换和逻辑回归的 code 参数来对新的输入数据进行批量评分。它可以节省时间,并展示如何使用您的代码为新数据评分。
请看图 12,第一步是生成一个小的测试集,我通过从训练数据中获取一些观察数据来完成这个任务(只是为了测试)。然后将数据加载到 CAS 服务器。在这里你可以看到我构建的名为 model_scoring 的函数。它有 5 个参数:CAS 连接的名称、来自 woe 转换的代码、来自逻辑回归模型的代码、测试表名称和评分表名称。如果您查看 model_scoring 函数,会发现有三个步骤:
- runcodetable — woe 转换。
- 估算-用 0 替换缺失的 woe 值。
- runcodetable 使用 woe 变换值的逻辑回归。
我使用评分函数对新数据进行评分。此时,您可以应用这些简单的记分卡计算方法,无论您想如何扩展,您都可以随时使用记分卡。
Figure 12: Scoring new data with scoring function
所有这些也可以使用 REST API 来完成。CAS 上的每个分析资产都使用 REST 端点进行抽象。这意味着您的数据和数据处理只需要几次 REST 调用。这允许将 SAS 技术轻松集成到您的业务流程或其他应用程序中。我使用 python 访问这些动作集和动作,但是使用 REST,您可以用自己选择的语言访问任何这些资产。
参考文献
- 西迪奇,纳伊姆。信用风险记分卡:开发实施智能信用评分。第一版。,威利,2005 年。
- “数据处理”动作集的 SAS 开发者,非常感谢 Biruk Gebremariam
- 在 GitHub 上完整的 Jupyter 笔记本演示
- 附加感谢:韦恩·汤普森
原载于 2019 年 1 月 18 日blogs.sas.com。