利用特征工程优化数据科学模型
聚类分析、指标开发和婴儿姓名数据的 PCA
正如婴儿名字的文章是即将为人父母者的必读之作一样,美国社会保障(SSA)的婴儿名字数据集应该是初露头角的数据科学家的必修课。数据集可以用许多不同的方式分割,包括语言、基于时间的方法和创造性问题的素材。这是一个健康的基于频率的数据集,具有很长的时间线。例如,网站分析工具统计独立用户的访问次数,零售销售点系统统计按颜色销售的产品,银行跟踪一个月内拖欠的贷款数量。本文是关于频率数据集的特征工程教程,其中特征工程的目标是提取每个名称的特征,并发现名称随时间变化的趋势之间的关系。
我开始探索名字来寻找选择。首先,我假设随着时间的推移,趋势捕捉到了父母选择某些名字的内在品质和社会态度。婴儿名字的趋势能够解释很多关于社会偏好的问题,参见婴儿名字的交叉相关性 NCBI 文章。同样,当你知道某人的名字时,你很可能知道那个人的年龄,见当你只知道某人的名字时,如何说出她的名字,fivethirtyeeight 文章。
我自己关于名字的问题:哪些美国名字与我过去的名字 Pauline 最相似?
我根据旧的时尚假设创建了一个功能列表。这意味着类似的名字将在 20 世纪初达到流行的顶峰,在过去的 50 年左右的时间里不会再出现。然后,我使用峰值检测和频率指标从 SSA 数据中手动提取这些特征。这些被称为“手动”特征。然后,我使用主成分分析(PCA)中的手动特征和趋势来创建“自动”特征。
最后,我使用这些自动化的、手动的和组合的特性比较了聚类分析结果。聚类是一种用相似性度量将对象分组在一起的方法。数据科学方法在汇集相似事物方面有着坚实的记录。例如,产品推荐算法通过购买历史和/或人口统计数据来识别像您这样的人,或者确定彼此最常购买的产品(例如,番茄酱和薯条)。
从 SSA 数据集创建的“手动”特征列表:
- 相似的名字不会不处于或接近它的流行度的峰值(峰值检测),
- 类似的名字在美国比较晦涩(量和加速度),
- 相似的名字不要太独特(数量),而且
- 相似的名字是主观愉悦的(不可量化)。
数据集、Python 代码和分析可在公共和交互式 Kaggle 笔记本中获得:https://www . ka ggle . com/Pauline chow/baby-names-optimize-w-feature-engineering
什么特征会产生“最好”的婴儿名字短列表?
婴儿名字度量:峰值检测、加速度和等级
SSA 的名字数据追踪了从 1910 年到 2017 年名字的使用频率。姓名频率按年份和性别进行汇总和分组。在笔记本中,第 1 至 3 部分是数据的一般检查,包括数据的抽样、测试统计、大小和形状。该笔记本根据最受欢迎程度、年复一年的变化以及过去 3 年中最受欢迎的 500 个名字的出现情况来创建分类指标。笔记本的第 4 部分介绍了创建、组合和分析这些指标的步骤。
(1)峰值人气检测
每一个名字都有高点(“峰值”)和低点(“谷值”)与自身和全球进行比较,以提供重要信息。例如,“伯莎”和“珍妮”这两个名字分别在 1920 年代和 1970 年代达到了最流行的程度,从那以后就逐渐减少了。从巅峰时期开始,伯莎就一直在稳步下降,而珍妮和詹妮弗在 20 世纪 40 年代至 70 年代稳定下降之前是强有力的竞争者。
在我孩子的一生中,她同龄人的名字将会很流行。一个假设是,如果名字相对于当前群体之前的人口达到显著峰值,并且目前处于减速趋势,那么名字就不太可能再次流行。峰值检测与了解当前加速度或年同比变化相结合,有助于缩小符合当前要求的名称范围。
峰值检测还会返回信息来创建指标。
峰值检测用于数字信号处理和语音识别,以在固定和实时数据中找到局部最小值和最大值。对于姓名,峰值检测可以将姓名与事件、人物和文化联系起来。这里,利用峰值检测来确定最近 5 年、10 年、15 年、20 年和 25 年内的任何峰值,这些峰值作为分类特征保存在数据集中。
在这个笔记本中,波峰和波谷是用一种简单和复杂的方法检测出来的。
(a)峰值检测最直接的方法是计算连续周期之间的符号变化。两个周期之间从正到负的符号变化表示从峰值开始下降。简单算法返回与前一个元素相比减少的索引。 peak_detection_simple 函数的输入数据是一个值列表,例如年度或 5 年滚动平均值。此列表的计算在函数返回索引之前完成。
下面简单的峰值检测函数计算连续时间段之间的差值,并返回符号变化的次数。符号变化被定义为从正到负的运动,并且不区分峰值的幅度或时间长度。
这种简单的方法缺乏纵观一个名字的大趋势的能力。这个简单函数的结果引发了一些问题:如果一个名字在一个“峰值”上花了更多的时间会怎么样?我们应该聚集非常接近的相似峰值吗?从正 a 峰到负 a 峰的哪些波动是显著的?从顶峰倾斜或下降的斜率是否有一个阈值?
(b) Scipy 是一个开放源代码的科学计算软件包,提供数学、科学和工程方面的内置功能。该软件包提供了识别峰值的功能,并且通过附加参数可以进一步区分,参见 scipy.signal.find_peaks 。 scipy find_peaks 函数提供了定义峰的绝对最小值和最大值(高度)、设置峰的最小垂直(阈值)和水平(距离)测量值以及峰的相对强度(突出度)的选项。
(2)年复一年(或任何时间段)的加速或同比变化指标是分析和报告环境中的标准。比较的时间周期越长,输出中标准化的季节性因素就越多。在 python pandas 中,计算 x 年之间的百分比变化会创建最近 x 年加速率的代理。基于不同时期的名称创建了许多特征。
(3)500 强排名名称指标
创建一个分类变量来标记一个名称在过去 3 年中是否排在前 500 名列表中。该指标是一种防止使用实际排名赋予过多权重的方法,即使我们扩大了这个数字。可以将此指标更改为聚合或多或少的年份,收集每年的所有前 X 个名称,并对按州分组的名称进行排名。
一份过去 3 年中排名前 500 名的名单将在以后从最终名单中筛选名字时派上用场。
主成分分析(PCA)的特征
仅凭频率很难预测名字的受欢迎程度。模式不一定是可辨别的,因为灵感是随机的。父母可能会受到迪斯尼电影、公众人物或个人生活事件的影响。一项使用婴儿名字作为美国文化特征指标的研究表明,新名字是发明的,而不是使用过去几代人的名字。婴儿名字的交叉相关性代替手动提取指标,整个趋势可以被分解成解释每个趋势的变化的特征。PCA 变换 PCA 背后的数学原理在这里解释。
用 25 个成分运行 PCA,结果显示 3 个和 10 个成分分别累计解释了数据中 80%和 99%的方差。可以在 PCA 结果之前设置累积方差的阈值,尤其是在数据集非常大的情况下。利用主成分分析作为降维技术可以节省计算时间。或者,为后续分析选择的组件数量可以取决于最终模型的结果。这意味着模型可以通过数据输入进一步优化,在这种情况下,它将是组件的数量。
笔记本的第 5 部分转换了功能,并对 4 个和 10 个组件进行了聚类分析。在这种分析中,考虑名称之间的更多差异能够更清晰地将名称划分到聚类中。这里利用的组件越多,最终的聚类轮廓得分就越好。优化聚类质量的结果最符合期望的结果。
使用 k 均值查找聚类
我选择了 Kmeans 聚类分析,这是一种更一般化的姓名分组方法。Kmeans 聚类用于将姓名(观察值)分组为 n 个簇,其中每个姓名被分配给具有最近平均值的簇。聚类的质量可以用剪影分数来度量,范围从-1 到+1,剪影分数确定其自身聚类内以及与其他聚类相比较的观察值的内聚性。
手动、自动和组合数据集的聚类数是根据与 Pauline 相同的聚类中的姓名计数来选择的。
结果婴儿名单
婴儿名字列表的结果是有希望的,因为我们能够从数千到不到 200 个名字。具有手动和自动特征的聚类生成的列表分别包含 155 和 57 个名字。两个名单共有 37 个名字(交叉)。一个集合了人工和自动特征的混合数据集产生了最短的 47 个名字的列表。PCA 特征(自动)识别较少的姓名,但是“遗漏”了由手动特征挑选的潜在姓名。
对于父母来说,为梳理 155 个名字创造一个永远的名字是一个可以接受的任务。我不需要进一步推敲。这与企业形成对比,在企业中,将产量提高两倍可能会导致运营和实质性问题。
特征工程既是一门艺术也是一门科学。聚类分析将产生满足要求的分组,要求越多意味着分组越严格。自动功能更准确地反映了一段时间的趋势。手动特性捕获了内在化的规则或假设,但是导致了大量的噪音。当仅仅依靠自动特性时,感觉就像分析的创造性方面丢失了。我们可以看到特征工程的艺术通过混合数据悄悄渗透到结果中。以下是使用不同功能集生成的名称示例。
这两套特性的完整列表可以从 Jupyter 笔记本上下载。以下是从名单中随机选择的 10 个名字:
关于这些数据,您可以问更多的问题:
- 我们还能从 SSA 数据集中得出什么其他指标?
- SSA 数据集中的男性名字也有类似的趋势和见解吗?
- 聚类分析的哪些属性可以优化以找到相似的名字?此分析仅探究 k 均值分析的聚类数量。如果使用基于连接性或分布的聚类而不是基于质心的聚类会怎样?
阅读 www.fountainofdata.com上的其他数据科学帖子。 Kaggle 笔记本有代码可用 此处 。借助**上传加载。照片由尹新荣在 Unsplash 上拍摄
优化 NVIDIA GPU 性能,实现高效的模型推断
Image from https://www.cgdirector.com/best-hardware-for-gpu-rendering-in-octane-redshift-vray/
GPU 已被证明是加速计算机视觉和自然语言处理(NLP)等深度学习和人工智能工作负载的有效解决方案。现在,许多基于深度学习的应用程序在其生产环境中使用 GPU 设备,如 NVIDIA Tesla 用于数据中心,Jetson 用于嵌入式平台。这就带来了一个问题:如何从你的 NVIDIA GPU 设备中获得最佳的推理性能?
在本文中,我们将一步一步地展示我们如何优化预训练的 TensorFlow 模型,以改善支持 CUDA 的 GPU 上的推理延迟。在我们的实验中,我们使用 SSD MobileNet V2 进行目标检测。我们在 Colab 上进行实验。所有重现结果的源代码和指令都可以在本笔记本中找到。本文组织如下:
- 在 TensorFlow 中下载并运行原始模型
- 通过与 CPU 协作优化模型
- 使用 TensorRT 优化模型
- 比较和结论
TL;速度三角形定位法(dead reckoning)
我们将 Colab GPU 实例上的推理时间改进为:
- 通过将控制流操作放在 CPU 上,提高了 1.3 倍
- 通过转换预训练的 TensorFlow 模型并在 TensorRT 中运行,可提高 4.0 倍
第 0 步:下载并运行 TensorFlow 中的 origin 模型
我们首先从tensor flow Detection Model Zoo下载 SSD MobileNet V2 预训练模型,该模型提供了一组在 COCO 数据集上训练的预训练模型。
在这个解压缩的文件夹中,我们可以找到以下文件:
frozen_inference_graph.pb
是针对任意图像和批次大小的冻结推理图pipeline.config
包含用于生成模型的配置model.ckpt.*
包含预先训练好的模型变量saved_model
文件夹包含 TensorFlow SavedModel 文件
然后,我们使用 TensorFlow 对象检测 API 导出模型。这允许我们固定批量大小和图像大小。对于这个实验,我们使用 300x300 的图像作为输入,批量大小为 1。因此,我们的输入形状是[1, 300, 300, 3]
。
现在我们已经准备好运行模型了。我们首先从互联网上下载输入图像,并预处理成所需的形状。然后,我们使用 TensorFlow 加载模型并执行推理。请注意,我们添加了options
和run_metadata
来记录分析数据,以便进一步分析。
最后,我们执行健全性检查以确保模型做出有意义的推断。注意,SSD MobileNet V2 模型将图像阵列作为每个检测到的对象的输入和输出绑定框[xmin, ymin, xmax, ymax]
。我们使用输出来绘制结合框,并得到以下结果。
Detection result we get from previous run
这个结果听起来很合理。因此,我们可以相信我们的模型工作正常。现在,我们准备分析性能。我们将使用 Chrome 的跟踪工具来分析模型。打开 Chrome 浏览器,输入网址chrome://tracing
。拖动我们从上一个脚本中得到的时间轴 JSON 文件,然后我们可以看到下面的界面。
Inference timeline trace for origin SSD MobileNert V2
从上面的跟踪中,您可能会注意到一些操作是在 CPU 上运行的,即使我们告诉 TensorFlow 在 GPU 上运行所有这些操作。这是因为 TensorFlow 没有为这些操作注册 GPU 内核(例如NonMaxSuppressionV3
)。由于这些操作无法在 GPU 上处理,TensorFlow 必须将中间输出从 GPU 内存传输到 CPU 内存,在 CPU 上处理,并将结果传输回 GPU,然后继续进行。从图中可以看出,这种情况发生了很多次。结果,我们的程序在数据传输上花费了太多时间,变得更慢。
此外,从图表的底部,我们可以看到每种类型的操作的时间成本。花费时间最多的前 3 项操作是GatherV2
、NonMaxSuppressionV3
和Conv2D
。当它对Conv2D
有意义时,因为 MobileNet V2 严重依赖它,并且计算量很大,它对其他人没有意义。我们将在下一节中解决这些问题并优化我们模型的推理性能。
第一步:与 CPU 合作优化模型
许多人认为 GPU 比 CPU 快——这就是为什么我们使用 GPU 来加速我们的程序。然而,这只是部分正确。为了解释这一点,我们需要了解一点 GPU 是如何工作的。
CPU 和 GPU 之间浮点能力的差异背后的原因是 GPU 专门用于计算密集型、高度并行的计算,这正是图形渲染的目的,因此设计了更多的晶体管用于数据处理,而不是数据缓存和流控制,如下图所示:
CPU vs. GPU structure, reference from CUDA Toolkit documentation
因此,对于可以并行处理的操作,如矩阵乘法,GPU 比 CPU 快得多。然而,由于 GPU 具有较少的用于流控制和高速缓存的晶体管,这可能不是流控制操作的情况(例如,if
、where
、while
等)。).
我们在 CPU 和 GPU 上运行时间开销最高的 5 个操作(除了NonMaxSuppressionV3
,因为它只能在 CPU 上处理),并比较它们的性能,我们得到以下结果:
我们可以看到,对输入数据进行矩阵乘法和加法运算的Conv2D
,在 GPU 上的运行速度比预期快了~ 10 倍。但是对于GatherV2
、ConcatV2
和Select
这些访问内存给定索引的,CPU 的表现都优于 GPU。因此,我们可以通过简单地将这些操作放在 CPU 上来提高我们的推理性能:
上述代码将所有操作放在 CPU 的NonMaxSuppression
块中,因为大多数流控制操作都发生在这个块中。然后,我们使用相同的代码测试修改后的模型,并记录时间轴跟踪。我们得到以下结果:
Inference timeline trace for our optimized model
请注意,总推断时间从~50 毫秒减少到~30 毫秒。GatherV2
的时间成本现在是 2.140 毫秒,相比之下原来是 5.458 毫秒。ConcatV2
的时间成本从 3.588 毫秒减少到 1.422 毫秒。此外,在修改后的模型中,GPU 和 CPU 之间的数据传输更少。所以像NonMaxSuppressionV3
这种原本在 CPU 上运行的操作也受益于此。
第二步:使用 TensorRT 优化模型
在本节中,我们将展示如何通过使用 NVIDIA TensorRT 来进一步加速推理。
什么是 tensort
NVIDIA TensorRT 是一个高性能深度学习推理平台。它包括一个深度学习推理优化器和运行时,为深度学习推理应用程序提供低延迟和高吞吐量。
TensorRT overview from NVIDIA TensorRT
为什么使用 tensort
TensorRT 提供了一系列用于深度学习模型优化的工具,如精度校准和层融合。您可以在不了解底层算法细节的情况下使用这些方便的工具。此外,TensorRT 专门为您的 GPU 设备选择内核,进一步优化性能。我们总结了使用 TensorRT 的利弊:
优点:
- 方便的优化工具使用户能够轻松有效地优化生产模型
- 特定于平台的内核选择,最大限度地提高您设备的性能
- 支持 TensorFlow 和 Caffe 等主要框架
缺点:
- TensorRT 中仅支持部分操作。因此,在构建模型时,您必须仔细选择图层,以使其与 TensorRT 兼容
要在 TensorRT 中运行预训练的 TensorFlow 模型,我们需要执行以下步骤:
- 将张量流模型转换为 UFF 格式
- 构建 TensorRT 推理引擎
将张量流模型转换为 UFF 格式
首先,我们使用图形外科医生和 UFF 转换器将 SSD MobileNet V2 TensorFlow 冻结模型转换为 TensorRT 可以解析的 UFF 格式。对于一些简单的模型(例如 Mobilenet V2,Inception v4 用于图像分类),我们可以直接使用 UFF 转换器进行转换。但是,对于包含 TensorRT 不支持的操作的模型(如 SSD MobileNet V2 中的NonMaxSuppression
),我们必须做一些预处理。诀窍是使用 Graph Surgeon 用支持的操作替换不支持的操作。
下面的脚本提供了一个预处理函数并修改了原始图形。关键的操作是用NMS_TRT
操作代替了原图中的NonMaxSuppression
操作,这是一个用于非最大值抑制的 TensorRT 核。然后,它将修改后的图形传递给 UFF 转换器,并输出最终的 UFF 模型。
构建 TensorRT 推理机
现在我们有了 UFF 模型文件。我们准备制造 TensorRT 发动机。您可以构建一次引擎,并将其部署到不同的设备上。但是,由于引擎针对构建它的设备进行了优化,因此建议针对不同的设备重新构建引擎,以最大限度地提高设备性能。
现在我们有了 tensort 引擎,我们准备在 tensort 上运行我们的模型。请注意,TensorRT 需要 NCHW 格式的输入图像。因此,我们的输入格式应该是[1, 3, 300, 300]
,而不是 TensorFlow 中的[1, 300, 300, 3]
。
在我们的实验中,这次运行的平均推断时间是 4.9 毫秒。
比较和结论
我们比较我们实验的推理时间,得到如下的情节:
Inference time comparison
我们可以看到,通过简单地将控制流操作放在 CPU 上,与原始模型相比,我们获得了 1.3 倍的性能提升。通过使用 TensorRT,与原始模型相比,我们可以获得 4 倍的改进。
总之,使用各种技术可以进一步提高 GPU 性能。在我们的实验中,我们通过以下方式优化预训练的 SSD Mobilenet V2 张量流模型:
- 将控制流操作放在 CPU 上并获得 1.3 倍的改进
- 跑合 TensorRT,增益 4x 提升
当 TensorRT 达到最佳性能时,它支持有限的操作。考虑在您生产环境中使用这些技术来最大化您的 GPU 性能。最后,我们强烈建议在 Colab 上运行这个实验,看看如何实现性能。
为深度学习优化您的 CPU
在过去的几年里,深度学习已经在学术界和工业界加快了步伐。每个公司现在都在寻找基于人工智能的问题解决方案。这种繁荣有其自身的优点和缺点,但这是另一篇文章,另一天。机器学习从业者的激增已经渗透到了学术界的根部,几乎每个领域的每个学生都可以通过课程、MOOCs、书籍、文章和课程论文获得人工智能和人工智能知识。
然而,这种增长受到硬件资源可用性的限制。有人建议,并证明了图形处理器是最好的设备之一,你可以有执行你的 ML 任务的速度。但是,一个好的高性能 GPU 的价格标签甚至可以高达 20,449.00 美元一个英伟达特斯拉 V100 32GB GPU ,它具有类似服务器的计算能力。此外,一台配有像样 GPU 的消费笔记本电脑价格约为2000 美元,GPU 为 1050Ti 或 1080Ti。为了减轻痛苦,谷歌、Kaggle、英特尔和英伟达免费提供基于云的高计算系统,对空间、计算能力、内存或时间都有限制。但是这些在线服务有其缺点,包括管理数据(上传/下载)、数据隐私等。这些问题引出了我的文章的主要观点,“为什么不优化我们的 CPU,以提高深度学习任务的速度?”。
英特尔已经为 Python、Tensorflow、Pytorch 等提供了优化。拥有一整套英特尔优化支持库,如 NumPy、scikit-learn 等。这些都可以免费下载和设置,并在英特尔酷睿 i7 这样的 CPU 上提供 2 倍甚至 5 倍的速度,而英特尔酷睿 i7 也不是至强系列这样的高性能 CPU。在本文的剩余部分,我将演示如何在您的 PC/笔记本电脑中设置英特尔优化,并将提供我观察到的加速数据。
获得性能提升
对于下面提到的各种实验,我将展示我观察到的时间和利用率提升。
- 用于 CIFAR-100 图像分类的 10 层深度 CNN。
- 用于 IMDB 情感分析的 3 层深度 LSTM。
- 6 层深密 ANN 用于 MNIST 影像分类。
- 用于 MNIST 的 9 层深度全卷积自动编码器。
这些任务已使用 tensorflow 后端在 Keras 中编码,数据集与代码和可执行库位于同一硬盘中。使用的硬盘是 SSD。
我们将考虑以下六种优化组合。
- 英特尔酷睿 i7 处理器。
- 英特尔至强处理器 E3–1535m V6。
- 英特尔酷睿 i7 和英特尔 Python(英特尔 i7*)。
- 英特尔至强处理器 E3–1535m V6,采用英特尔 Python(英特尔至强*)。
- 采用英特尔 Python 和处理器线程优化的英特尔酷睿 i7(英特尔 i7(O))。
- 英特尔至强处理器 E3–1535m V6,采用英特尔 Python 和处理器线程优化(英特尔至强处理器)。
对于每项任务,历元数固定为 50。在下面的图表中,我们可以看到,对于一个英特尔®酷睿™i7–7700 HQ CPU @ 2.80 GHz CPU,每个时期的平均时间接近 4.67 秒,经过适当的优化后,它下降到 1.48 秒,提升了 3.2 倍。对于一个英特尔至强处理器 E3–1535m V6 @ 3.10 GHz CPU,每个周期的平均时间接近 2.21 秒,经过适当优化后下降到 0.64 秒,提升了 3.45 倍。
Average time per epoch
优化不仅是及时的,优化的分布还优化了 CPU 利用率,最终导致更好的热量管理,并且您的笔记本电脑不会像过去那样受热,而训练深度神经网络。
Utilization
我们可以看到,在没有任何优化的情况下,训练时的 CPU 利用率达到了 100%,降低了所有其他进程的速度,并使系统发热。然而,通过适当的优化,i7 的利用率下降到 70%,至强的利用率下降到 65%,尽管在时间方面提供了性能增益。
这两个指标可以相对概括如下。
在上图中,值越低越好,也就是说,相对而言,经过所有优化的英特尔至强处理器是性能指标评测的基准,在优化使用后,英特尔酷睿 i7 处理器每周期的时间几乎是至强处理器的两倍。上图清楚地显示了英特尔 Python 优化在训练神经网络所用时间和 CPU 使用方面的光明面。
设置英特尔的 Python 发行版
英特尔软件提供了关于如何设置的详尽资源列表,但我们可能会经常遇到一些问题。更多关于发行的细节可以在这里找到。您可以选择安装类型,即本机 pip 或 conda。我更喜欢 conda,因为它为我节省了大量的麻烦,我可以专注于 ML 而不是解决我的库的兼容性问题。
1)下载并安装 Anaconda
你可以从这里下载 Anaconda。他们的网站列出了在 windows、ubuntu 和 macOS 环境下安装 Anaconda 的所有步骤,并且很容易掌握。
2)在您的 Anaconda 发行版中设置英特尔 python
这一步通常会变得棘手。最好为英特尔分发创建一个虚拟环境,这样您就可以随时在一个地方添加/更改您的优化库。让我们创建一个名为“ intel”的新虚拟环境
conda create -n intel -c intel [intelpython3_ful](https://anaconda.org/intel/intelpython3_full)l
这里 -c 代表通道,所以我们不把 Intel 加为通道,而是把那个通道称为 via -c 。在这里,intelpython3_full 将自动从英特尔的发行版中获取必要的库,并将它们安装到您的虚拟环境中。此命令将安装下列库。
The following NEW packages will be INSTALLED:asn1crypto intel/win-64::asn1crypto-0.24.0-py36_3
bzip2 intel/win-64::bzip2-1.0.6-vc14_17
certifi intel/win-64::certifi-2018.1.18-py36_2
cffi intel/win-64::cffi-1.11.5-py36_3
chardet intel/win-64::chardet-3.0.4-py36_3
cryptography intel/win-64::cryptography-2.3-py36_1
cycler intel/win-64::cycler-0.10.0-py36_7
cython intel/win-64::cython-0.29.3-py36_1
daal intel/win-64::daal-2019.3-intel_203
daal4py intel/win-64::daal4py-2019.3-py36h7b7c402_6
freetype intel/win-64::freetype-2.9-vc14_3
funcsigs intel/win-64::funcsigs-1.0.2-py36_7
icc_rt intel/win-64::icc_rt-2019.3-intel_203
idna intel/win-64::idna-2.6-py36_3
impi_rt intel/win-64::impi_rt-2019.3-intel_203
intel-openmp intel/win-64::intel-openmp-2019.3-intel_203
intelpython intel/win-64::intelpython-2019.3-0
intelpython3_core intel/win-64::intelpython3_core-2019.3-0
intelpython3_full intel/win-64::intelpython3_full-2019.3-0
kiwisolver intel/win-64::kiwisolver-1.0.1-py36_2
libpng intel/win-64::libpng-1.6.36-vc14_2
llvmlite intel/win-64::llvmlite-0.27.1-py36_0
matplotlib intel/win-64::matplotlib-3.0.1-py36_1
menuinst intel/win-64::menuinst-1.4.1-py36_6
mkl intel/win-64::mkl-2019.3-intel_203
mkl-service intel/win-64::mkl-service-1.0.0-py36_7
mkl_fft intel/win-64::mkl_fft-1.0.11-py36h7b7c402_0
mkl_random intel/win-64::mkl_random-1.0.2-py36h7b7c402_4
mpi4py intel/win-64::mpi4py-3.0.0-py36_3
numba intel/win-64::numba-0.42.1-np116py36_0
numexpr intel/win-64::numexpr-2.6.8-py36_2
numpy intel/win-64::numpy-1.16.1-py36h7b7c402_3
numpy-base intel/win-64::numpy-base-1.16.1-py36_3
openssl intel/win-64::openssl-1.0.2r-vc14_0
pandas intel/win-64::pandas-0.24.1-py36_3
pip intel/win-64::pip-10.0.1-py36_0
pycosat intel/win-64::pycosat-0.6.3-py36_3
pycparser intel/win-64::pycparser-2.18-py36_2
pyopenssl intel/win-64::pyopenssl-17.5.0-py36_2
pyparsing intel/win-64::pyparsing-2.2.0-py36_2
pysocks intel/win-64::pysocks-1.6.7-py36_1
python intel/win-64::python-3.6.8-6
python-dateutil intel/win-64::python-dateutil-2.6.0-py36_12
pytz intel/win-64::pytz-2018.4-py36_3
pyyaml intel/win-64::pyyaml-4.1-py36_3
requests intel/win-64::requests-2.20.1-py36_1
ruamel_yaml intel/win-64::ruamel_yaml-0.11.14-py36_4
scikit-learn intel/win-64::scikit-learn-0.20.2-py36h7b7c402_2
scipy intel/win-64::scipy-1.2.0-py36_3
setuptools intel/win-64::setuptools-39.0.1-py36_0
six intel/win-64::six-1.11.0-py36_3
sqlite intel/win-64::sqlite-3.27.2-vc14_2
tbb intel/win-64::tbb-2019.4-vc14_intel_203
tbb4py intel/win-64::tbb4py-2019.4-py36_intel_0
tcl intel/win-64::tcl-8.6.4-vc14_22
tk intel/win-64::tk-8.6.4-vc14_28
urllib3 intel/win-64::urllib3-1.24.1-py36_2
vc intel/win-64::vc-14.0-2
vs2015_runtime intel/win-64::vs2015_runtime-14.0.25420-intel_2
wheel intel/win-64::wheel-0.31.0-py36_3
win_inet_pton intel/win-64::win_inet_pton-1.0.1-py36_4
wincertstore intel/win-64::wincertstore-0.2-py36_3
xz intel/win-64::xz-5.2.3-vc14_2
zlib intel/win-64::zlib-1.2.11-vc14h21ff451_5
您可以看到,对于每个库,轮盘的描述以“***【Intel/…”***开头,这表示该库正在从英特尔的分销渠道下载。一旦您同意安装这些库,它们将开始被下载和安装。
这一步是第一个问题的来源。有时,这些库没有被下载,列表传播,或者我们得到一个 SSL 错误,命令退出。这个问题甚至可能会被延迟,也就是说,现在所有的东西都会被下载和安装,但是稍后如果你想添加任何新的库,提示会抛出 SSL 错误。如上所述,在为英特尔创建虚拟环境之前,有一个简单的方法可以解决这个问题。
在您的 shell 或命令提示符下,通过以下命令关闭 anaconda 的默认 SSL 验证
conda config --set ssl_verify false
关闭 SLL 验证后,您可以通过删除之前创建的环境并重新启动来重复步骤 2。
3)建立张量流
恭喜你!!现在,您已经在您的 PC/笔记本电脑中设置了英特尔的 python 发行版。是时候进入 ML 管道了。
英特尔已通过所有分销渠道为 tensorflow 提供了优化,安装非常顺利。你可以在这里了解更多信息。让我们看看如何为我们的 CPU 安装优化的 tensorflow。英特尔软件提供了一个优化的数学内核库(mkl ),它可以优化数学运算,并为用户提供所需的加速。因此,我们将如下安装 tensorflow-mkl。
conda install tensorflow-mkl
或者使用 pip,可以按如下方式进行设置。
pip install intel-tensorflow
瞧啊。!Tensorflow 现已启动并在您的系统中运行,并进行了必要的优化。如果你是一个 Keras 粉丝,你可以用一个简单的命令来设置它
conda install keras -c intel
4)建立 Jupyter
由于我们已经创建了一个新的虚拟环境,默认情况下它不会与 spyder 或 jupyter 笔记本一起提供。然而,设置这些是很简单的。只要一句话,我们就能创造奇迹。
conda install jupyter -c intel
5)激活环境,开始实验
现在我们已经设置好了所有的东西,是时候开始在我们优化的 CPU 系统上用各种 ML 和 DL 方法进行编码和实验了。首先,在执行任何代码之前,确保您正在使用正确的环境。您需要激活虚拟环境,然后才能使用其中安装的库。这个激活步骤是一个全天候的过程,而且毫不费力。在 anaconda 提示符下编写以下命令,就可以开始了。
conda activate intel
要对您的环境进行健全性检查,请在激活环境后,在命令提示符/shell 中键入以下内容。
python
键入 python 后按 enter 键,命令提示符中应该会出现以下文本。确保在管道之间有“英特尔公司”字样,并有消息“英特尔公司为您提供 Python 的英特尔®发行版。”。这些验证了英特尔 Python 发行版的正确安装。
Python 3.6.8 |Intel Corporation| (default, Feb 27 2019, 19:55:17) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
Intel(R) Distribution for Python is brought to you by Intel Corporation.
Please check out: [https://software.intel.com/en-us/python-distribution](https://software.intel.com/en-us/python-distribution)
现在,您可以使用命令行来试验或在其他地方编写您的脚本,并使用。py 扩展名。然后,可以通过“cd”命令导航到文件的位置并通过以下方式运行脚本来访问这些文件:-
(intel) C:\Users\User>python script.py
通过执行步骤 1 到 4,您的系统将具备上述性能指标评测图表中提到的*英特尔 xyz** 水平。这些仍然不是基于多处理器的线程优化。我将在下面讨论如何为您的多核 CPU 实现进一步的优化。
多核优化
要为您的多核系统添加进一步的优化,您可以将以下代码行添加到您的。py 文件,它将相应地执行脚本。这里NUM _ PARALLEL _ EXEC _ UNITS代表你拥有的核心数;我有一台四核 i7。因此数字是 4。对于 Windows 用户,您可以通过导航到任务管理器- >性能- > CPU - >核心来检查任务管理器中的核心数。
from keras import backend as K
import tensorflow as tfNUM_PARALLEL_EXEC_UNITS = 4
config = tf.ConfigProto(intra_op_parallelism_threads=NUM_PARALLEL_EXEC_UNITS, inter_op_parallelism_threads=2,
allow_soft_placement=True, device_count={'CPU': NUM_PARALLEL_EXEC_UNITS})session = tf.Session(config=config)K.set_session(session)os.environ["OMP_NUM_THREADS"] = "4"os.environ["KMP_BLOCKTIME"] = "30"os.environ["KMP_SETTINGS"] = "1"os.environ["KMP_AFFINITY"] = "granularity=fine,verbose,compact,1,0"
如果您不使用 Keras,而更喜欢使用 core tensorflow,那么脚本几乎保持不变,只需删除以下两行。
from keras import backend as K
K.set_session(session)
在您的代码中添加这些行之后,速度应该可以与上面性能图表中的英特尔 xyz(O) 条目相媲美。
如果您的系统中有一个 GPU,并且它与当前的库集冲突或抛出 cudnn 错误,那么您可以在代码中添加以下行来禁用 GPU。
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
结论
就是这样。你现在有一个优化的管道来测试和开发机器学习项目和想法。这一渠道为参与学术研究的学生提供了很多机会,让他们可以利用自己的系统继续工作。该管道还将防止从业者可能正在处理的私人数据的隐私担忧。
还可以观察到,通过适当的微调,人们可以在工作流程中获得 3.45 倍的加速,这意味着如果您正在试验您的想法,您现在的工作速度可以是以前的三倍。
使用深度 Q 学习优化空间入侵者:Tensorflow 2.0 中的实现。
探索数据预处理的效果
介绍
在过去几篇关于 GradientCrescent 的文章中,我们花了大量时间探索在线学习领域,这是一个高度反应性的强化学习算法家族,背后隐藏着许多通用人工智能的最新成就。在线学习属于基于样本的学习类方法,reliant 允许简单地通过重复观察来确定状态值,消除了对转换动态的需要。与它们的离线对应方式,不同,在线学习方法允许在环境事件期间对状态和动作的值进行增量更新,允许观察到持续的、增量的性能改进。
除了时间差异学习(TD ),我们还讨论了 Q-learning 的理论和实际实现,这是 TD 的一种发展,旨在允许增量估计和状态-动作值的改进。Q-learning 因成为模拟游戏环境的强化学习方法的支柱而闻名,如在 OpenAI 的健身房中观察到的那些。因为我们已经在过去的文章中涉及了 Q-learning 的理论方面,所以这里不再重复。
Q-learning powered Miss Pacman, a implemented in our previous article.
在我们之前对 OpenAI 的 Pacman 小姐健身房环境的实现中,我们依赖于一组单个游戏帧的观察实例(状态)作为我们训练过程的输入。然而,这种方法存在部分缺陷,因为它没有考虑 Atari 游戏环境的许多特性,包括:
- 经典 Atari 游戏中观察到的游戏环境的跳帧渲染。
- 环境中存在多个快速移动的参与者。
- 在代理和环境中观察到的特定于帧的闪烁。
总的来说,这些问题可能会大大降低代理的性能,因为一些数据实例实际上已经超出了领域,或者与实际的游戏环境完全无关。此外,这些问题只会随着更复杂的游戏环境和现实世界的应用(如自动驾驶)而变得更加复杂。我们在之前的实施中观察到,在培训过程中,这种情况表现为高水平的变化和绩效持平。
Reward versus training episodes for our Q-learning trained Miss Pacman agent, trained over 600+800 cycles.
为了克服这些问题,我们可以利用由 Deepmind 团队在 2015 年首次引入的几项技术。
- 帧堆叠:将几个游戏帧连接在一起,为我们的游戏环境提供一个时间参考。
- **帧合成:**两个游戏帧的元素最大化,以提供一个运动参考,也克服了部分渲染的问题。
让我们在更复杂的 Atari Space Invader 环境中实现检查这些技术的效果。
实施
我们的 Google 协作实现是利用 Tensorflow Core 用 Python 编写的,可以在 GradientCrescent Github 上找到。我们已经使用新的 compat 包将我们的代码转换为 TF2 兼容的。首先,让我们简要回顾一下 Q-learning 实现所需的操作。
- 我们定义我们的深度 Q 学习神经网络。这是一个 CNN,它拍摄游戏中的屏幕图像,并输出 Ms-Pacman gamespace 中每个动作的概率,或 Q 值。为了获得概率张量,我们在最后一层不包括任何激活函数。
- 由于 Q-learning 要求我们了解当前和下一个状态,我们需要从数据生成开始。我们将表示初始状态 s 的游戏空间的预处理输入图像输入到网络中,并获取动作的初始概率分布,或 Q 值。在训练之前,这些值将是随机的和次优的。请注意,我们的预处理现在还包括堆叠和合成。
- 利用我们的概率张量,我们然后使用 argmax()函数选择具有当前最高概率的动作,并使用它来构建ε贪婪策略。
- 使用我们的策略,我们将选择动作 a ,并评估我们在健身房环境中的决定,以让**接收关于新状态s’的信息、奖励 r ,以及该集是否已经结束。
- 我们以列表形式
将该信息组合存储在一个缓冲区中,并重复步骤 2-4 预设次数,以建立一个足够大的缓冲区数据集。 - 一旦步骤 5 完成,我们转到生成损失计算所需的目标 y 值R’和A’。虽然前者只是从 R 中减去,但我们通过将*S’*输入到我们的网络中来获得 A’。
- 所有的组件都准备好了,我们就可以计算训练网络的损耗了。
- 培训结束后,我们将通过图形和演示来评估代理的表现。
作为参考,让我们首先演示使用普通数据输入方法的结果,这与我们之前为 Pacman 小姐实施的中观察到的结果基本相同。在对我们的代理人进行了 800 集的训练后,我们观察到以下的报酬分布。
Reward distribution for the vanilla data input approach for the Space Invaders environment.
请注意性能的变化如何表现出高度的变化,在 650 次发作后观察到非常有限的改善。
同样,我们代理的性能也不太好,几乎没有检测到任何逃避行为。如果你仔细观察,你会注意到出射和入射激光轨迹的闪烁——这是游戏环境中有意的一部分,导致某些帧中根本没有投射物,或者只有一组投射物可见。这意味着我们输入数据的元素具有高度误导性,并对代理绩效产生负面影响。
让我们检查一下改进后的实现。
我们首先导入所有必要的包,包括 OpenAI gym 环境和 Tensorflow 核心。
import numpy as npimport gymimport tensorflow as tffrom tensorflow.contrib.layers import flatten, conv2d, fully_connectedfrom collections import deque, Counterimport randomfrom datetime import datetime
接下来,我们定义一个预处理函数,从我们的健身房环境中裁剪图像,并将它们转换成一维张量。在我们的 Pong 自动化实现中,我们已经看到了这一点。
def preprocess_observation(obs): # Crop and resize the image img = obs[25:201:2, ::2] # Convert the image to greyscale img = img.mean(axis=2) # Improve image contrast img[img==color] = 0 # Next we normalize the image from -1 to +1 img = (img - 128) / 128 - 1 return img.reshape(88,80)
接下来,让我们初始化健身房环境,检查几个游戏画面,并了解 gamespace 中可用的 9 个动作。当然,我们的代理人无法获得这些信息。
env = gym.make(“SpaceInvaders-v0”)n_outputs = env.action_space.nprint(n_outputs)print(env.env.get_action_meanings())observation = env.reset()import tensorflow as tfimport matplotlib.pyplot as pltfor i in range(22):if i > 20:plt.imshow(observation)plt.show()observation, _, _, _ = env.step(1)
您应该遵守以下几点:
我们可以借此机会比较我们的原始和预处理输入图像:
接下来,我们将输入堆叠和输入组合引入预处理管道。在新的一集里,我们从获取两个输入帧开始,并返回这两个帧的元素式最大总和 maxframe (注意,从技术上讲这是不必要的,因为这两个帧是相同的,但这是一种很好的实践)。堆叠的帧存储在队列中,当引入新的条目时,队列会自动删除旧的条目。最初,我们复制预处理的 maxframe 来填充我们的 deque。随着剧集的进展,我们通过获取新的帧来创建新的 maxframes ,将它与我们的 dequee 中最近的条目进行元素式最大求和,然后将新的 maxframe 附加到我们的 dequee。然后,我们在流程的最后堆叠这些帧。
stack_size = 4 # We stack 4 composite frames in total# Initialize deque with zero-images one array for each image. Deque is a special kind of queue that deletes last entry when new entry comes instacked_frames = deque([np.zeros((88,80), dtype=np.int) for i in range(stack_size)], maxlen=4)def stack_frames(stacked_frames, state, is_new_episode):# Preprocess frameframe = preprocess_observation(state) if is_new_episode: # Clear our stacked_frames stacked_frames = deque([np.zeros((88,80), dtype=np.int) for i in range(stack_size)], maxlen=4) # Because we’re in a new episode, copy the same frame 4x, apply elementwise maxima maxframe = np.maximum(frame,frame) stacked_frames.append(maxframe) stacked_frames.append(maxframe) stacked_frames.append(maxframe) stacked_frames.append(maxframe) # Stack the frames stacked_state = np.stack(stacked_frames, axis=2) else: #Since deque append adds t right, we can fetch rightmost element maxframe=np.maximum(stacked_frames[-1],frame) # Append frame to deque, automatically removes the oldest frame stacked_frames.append(maxframe) # Build the stacked state (first dimension specifies different frames) stacked_state = np.stack(stacked_frames, axis=2) return stacked_state, stacked_frames
接下来,让我们定义我们的模型,一个深度 Q 网络。这本质上是一个三层卷积网络,它获取预处理的输入图像,展平并将其馈送到一个全连接层,并输出在游戏空间中采取每个行动的概率。如前所述,这里没有激活层,因为激活层的存在会导致二进制输出分布。
**def q_network(X, name_scope):**# Initialize layersinitializer = tf.compat.v1.keras.initializers.VarianceScaling(scale=2.0)with tf.compat.v1.variable_scope(name_scope) as scope:# initialize the convolutional layerslayer_1 = conv2d(X, num_outputs=32, kernel_size=(8,8), stride=4, padding=’SAME’, weights_initializer=initializer)tf.compat.v1.summary.histogram(‘layer_1’,layer_1)layer_2 = conv2d(layer_1, num_outputs=64, kernel_size=(4,4), stride=2, padding=’SAME’, weights_initializer=initializer)tf.compat.v1.summary.histogram(‘layer_2’,layer_2)layer_3 = conv2d(layer_2, num_outputs=64, kernel_size=(3,3), stride=1, padding=’SAME’, weights_initializer=initializer)tf.compat.v1.summary.histogram(‘layer_3’,layer_3)flat = flatten(layer_3)fc = fully_connected(flat, num_outputs=128, weights_initializer=initializer)tf.compat.v1.summary.histogram(‘fc’,fc)#Add final output layeroutput = fully_connected(fc, num_outputs=n_outputs, activation_fn=None, weights_initializer=initializer)tf.compat.v1.summary.histogram(‘output’,output)vars = {v.name[len(scope.name):]: v for v in tf.compat.v1.get_collection(key=tf.compat.v1.GraphKeys.TRAINABLE_VARIABLES, scope=scope.name)}#Return both variables and outputs togetherreturn vars, output
让我们也借此机会为我们的模型和训练过程定义超参数。注意,由于我们的堆叠框架,X_shape 现在是*(无,88,80,4)* 。
num_episodes = 800batch_size = 48input_shape = (None, 88, 80, 1)learning_rate = 0.001X_shape = (None, 88, 80, 4)discount_factor = 0.97global_step = 0copy_steps = 100steps_train = 4start_steps = 2000
回想一下,Q-learning 要求我们选择具有最高行动值的行动。为了确保我们仍然访问每一个可能的状态-行为组合,我们将让我们的代理遵循一个ε贪婪策略,探索率为 5%。我们将这个探索率设置为随时间衰减,因为我们最终假设所有的组合都已经被探索过了——在那个点之后的任何探索只会导致次优行动的强制选择。
epsilon = 0.5eps_min = 0.05eps_max = 1.0eps_decay_steps = 500000#**def epsilon_greedy(action, step):** p = np.random.random(1).squeeze() #1D entries returned using squeeze epsilon = max(eps_min, eps_max — (eps_max-eps_min) * step/eps_decay_steps) #Decaying policy with more steps if np.random.rand() < epsilon: return np.random.randint(n_outputs) else: return action
回想上面的等式,Q-learning 的更新函数要求如下:
- 当前状态 s
- 当前动作一个
- 当前动作后的奖励 r
- 下一个状态s’
- 下一个动作a’
为了以有意义的数量提供这些参数,我们需要按照一组参数评估我们当前的策略,并将所有变量存储在一个缓冲区中,我们将在训练期间从该缓冲区中提取迷你批次中的数据。让我们继续创建我们的缓冲区和一个简单的采样函数:
buffer_len = 20000#Buffer is made from a deque — double ended queueexp_buffer = deque(maxlen=buffer_len)**def sample_memories(batch_size):** perm_batch = np.random.permutation(len(exp_buffer))[:batch_size] mem = np.array(exp_buffer)[perm_batch] return mem[:,0], mem[:,1], mem[:,2], mem[:,3], mem[:,4]
接下来,让我们将原始网络的权重参数复制到目标网络中。这种双网络方法允许我们在使用现有策略的训练过程中生成数据,同时仍然为下一次策略迭代优化我们的参数。
# we build our Q network, which takes the input X and generates Q values for all the actions in the statemainQ, mainQ_outputs = q_network(X, ‘mainQ’)# similarly we build our target Q network, for policy evaluationtargetQ, targetQ_outputs = q_network(X, ‘targetQ’)copy_op = [tf.compat.v1.assign(main_name, targetQ[var_name]) for var_name, main_name in mainQ.items()]copy_target_to_main = tf.group(*copy_op)
最后,我们还将定义我们的损失。这就是我们的目标动作(具有最高动作值)和我们的预测动作的平方差。我们将使用 ADAM 优化器来最大限度地减少我们在训练中的损失。
# define a placeholder for our output i.e actiony = tf.compat.v1.placeholder(tf.float32, shape=(None,1))# now we calculate the loss which is the difference between actual value and predicted valueloss = tf.reduce_mean(input_tensor=tf.square(y — Q_action))# we use adam optimizer for minimizing the lossoptimizer = tf.compat.v1.train.AdamOptimizer(learning_rate)training_op = optimizer.minimize(loss)init = tf.compat.v1.global_variables_initializer()loss_summary = tf.compat.v1.summary.scalar(‘LOSS’, loss)merge_summary = tf.compat.v1.summary.merge_all()file_writer = tf.compat.v1.summary.FileWriter(logdir, tf.compat.v1.get_default_graph())
定义好所有代码后,让我们运行我们的网络并检查培训过程。我们已经在最初的总结中定义了大部分,但是让我们为后代回忆一下。
- 对于每个时期,在使用ε-贪婪策略选择下一个动作之前,我们将输入图像堆栈输入到我们的网络中,以生成可用动作的概率分布
- 然后,我们将它输入到网络中,获取下一个状态和相应奖励的信息,并将其存储到我们的缓冲区中。我们更新我们的堆栈,并通过一些预定义的步骤重复这一过程。
- 在我们的缓冲区足够大之后,我们将下一个状态输入到我们的网络中,以便获得下一个动作。我们还通过贴现当前的奖励来计算下一个奖励
- 我们通过 Q 学习更新函数生成我们的目标 y 值,并训练我们的网络。
- 通过最小化训练损失,我们更新网络权重参数,以便为下一个策略输出改进的状态-动作值。
with tf.compat.v1.Session() as sess: init.run() # for each episode
history = [] for i in range(num_episodes): done = False obs = env.reset() epoch = 0 episodic_reward = 0 actions_counter = Counter() episodic_loss = [] #First step, preprocess + initialize stack obs,stacked_frames= stack_frames(stacked_frames,obs,True) # while the state is not the terminal state
while not done: #Data generation using the untrained network # feed the game screen and get the Q values for each action actions = mainQ_outputs.eval(feed_dict={X:[obs], in_training_mode:False}) # get the action
action = np.argmax(actions, axis=-1) actions_counter[str(action)] += 1 # select the action using epsilon greedy policy
action = epsilon_greedy(action, global_step) # now perform the action and move to the next state, next_obs, receive reward next_obs, reward, done, _ = env.step(action) #Updated stacked frames with new episode next_obs, stacked_frames = stack_frames(stacked_frames, next_obs, False) # Store this transition as an experience in the replay buffer! Quite important exp_buffer.append([obs, action, next_obs, reward, done]) # After certain steps, we train our Q network with samples from the experience replay buffer if global_step % steps_train == 0 and global_step > start_steps: #Our buffer should already contain everything preprocessed and stacked o_obs, o_act, o_next_obs, o_rew, o_done = sample_memories(batch_size) # states o_obs = [x for x in o_obs] # next states o_next_obs = [x for x in o_next_obs] # next actions next_act = mainQ_outputs.eval(feed_dict={X:o_next_obs, in_training_mode:False}) # discounted reward: these are our Y-values y_batch = o_rew + discount_factor * np.max(next_act, axis=-1) * (1-o_done) # merge all summaries and write to the file mrg_summary = merge_summary.eval(feed_dict={X:o_obs, y:np.expand_dims(y_batch, axis=-1), X_action:o_act, in_training_mode:False}) file_writer.add_summary(mrg_summary, global_step) # To calculate the loss, we run the previously defined functions mentioned while feeding inputs train_loss, _ = sess.run([loss, training_op], feed_dict={X:o_obs, y:np.expand_dims(y_batch, axis=-1), X_action:o_act, in_training_mode:True}) episodic_loss.append(train_loss) # after some interval we copy our main Q network weights to target Q network if (global_step+1) % copy_steps == 0 and global_step > start_steps: copy_target_to_main.run() obs = next_obs epoch += 1 global_step += 1 episodic_reward += rewardnext_obs=np.zeros(obs.shape)exp_buffer.append([obs, action, next_obs, reward, done])obs= env.reset()obs,stacked_frames= stack_frames(stacked_frames,obs,True)history.append(episodic_reward)print('Epochs per episode:', epoch, 'Episode Reward:', episodic_reward,"Episode number:", len(history))
一旦训练完成,我们就可以根据增量情节绘制奖励分布图。前 800 集如下所示:
Reward distribution for the stacked and composited approach in the Space Invaders environment.
请注意奖励分布的核心变化是如何显著减少的,从而可以观察到更加一致的剧集间分布,并且表现的增加在统计上变得更加显著。从 550 集开始可以观察到性能的明显提高,比普通数据方法早了整整 100 集,验证了我们的假设。
为了在实验室环境的限制下评估我们的结果,我们可以录制整个情节,并使用基于 IPython 库的包装在虚拟显示器中显示:
“””Utility functions to enable video recording of gym environment and displaying it. To enable video, just do “env = wrap_env(env)””“”**def show_video():**mp4list = glob.glob(‘video/*.mp4’)if len(mp4list) > 0:mp4 = mp4list[0]video = io.open(mp4, ‘r+b’).read()encoded = base64.b64encode(video)ipythondisplay.display(HTML(data=’’’<video alt=”test” autoplayloop controls style=”height: 400px;”><source src=”data:video/mp4;base64,{0}” type=”video/mp4" /></video>’’’.format(encoded.decode(‘ascii’))))else:print(“Could not find video”)
**def wrap_env(env):**env = Monitor(env, ‘./video’, force=True)return env
然后,我们使用我们的模型运行一个新的环境会话,并记录它。
Evaluate model on openAi GYMenvironment = wrap_env(gym.make('SpaceInvaders-v0'))done = Falseobservation = environment.reset()new_observation = observationprev_input = Nonewith tf.compat.v1.Session() as sess: init.run() observation, stacked_frames = stack_frames(stacked_frames, observation, True) while True: #set input to network to be difference image # feed the game screen and get the Q values for each action actions = mainQ_outputs.eval(feed_dict={X:[observation], in_training_mode:False}) # get the action action = np.argmax(actions, axis=-1) actions_counter[str(action)] += 1 # select the action using epsilon greedy policy action = epsilon_greedy(action, global_step) environment.render() new_observation, stacked_frames = stack_frames(stacked_frames, new_observation, False) observation = new_observation # now perform the action and move to the next state, next_obs, receive reward new_observation, reward, done, _ = environment.step(action) if done: breakenvironment.close()show_video()
我们来考察几轮玩法。
我们的特工已经学会了防守和进攻,有效地利用掩护和躲避。这两个事件之间行为的巨大差异可以归因于 Q 学习的工作方式——早先选择的行为获得了 Q 值,这往往有利于ε贪婪策略。随着进一步的训练,我们希望这两种行为会趋于一致。
这就结束了优化 Q-learning 的介绍。在我们的下一篇文章中,我们将带着我们所学到的一切,从 Atari 的世界继续前进,去解决世界上最著名的 FPS 游戏之一。
我们希望你喜欢这篇文章,并希望你查看 GradientCrescent 上的许多其他文章,涵盖人工智能的应用和理论方面。为了保持对 GradientCrescent 的最新更新,请考虑关注该出版物并关注我们的 Github 资源库。
参考文献
萨顿等人。强化学习
怀特等人。阿尔伯塔大学强化学习基础
席尔瓦等人。阿尔,强化学习,UCL
Ravichandiran 等人。al,用 Python 实践强化学习
Takeshi 等人。艾尔, Github
训练神经网络的各种优化算法
正确的优化算法可以成倍地减少训练时间。
许多人可能在训练神经网络时使用优化器,而不知道该方法被称为优化。优化器是用来改变神经网络属性的算法或方法,如权重和学习速率,以减少损失。
Optimizers help to get results faster
你应该如何改变你的神经网络的权重或学习速率来减少损失是由你使用的优化器定义的。优化算法或策略负责减少损失,并尽可能提供最准确的结果。
我们将了解不同类型的优化器及其优势:
梯度下降
梯度下降是最基本但最常用的优化算法。它大量用于线性回归和分类算法。神经网络中的反向传播也使用梯度下降算法。
梯度下降是一种一阶优化算法,它依赖于损失函数的一阶导数。它计算出应该以何种方式改变权重,以使函数达到最小值。通过反向传播,损耗从一层转移到另一层,并且模型的参数(也称为权重)根据损耗进行修改,以便损耗可以最小化。
算法: θ=θ−α⋅∇J(θ)
优点:
- 容易计算。
- 容易实现。
- 很好理解。
缺点:
- 可能陷入局部最小值。
- 计算整个数据集的梯度后,权重会发生变化。因此,如果数据集过大,可能需要数年时间才能收敛到最小值。
- 需要大量内存来计算整个数据集的梯度。
随机梯度下降
这是梯度下降的一个变种。它试图更频繁地更新模型的参数。在这种情况下,在计算每个训练样本的损失之后,改变模型参数。因此,如果数据集包含 1000 行,SGD 将在数据集的一个周期内更新模型参数 1000 次,而不是像梯度下降那样更新一次。
θ=θ−α⋅∇j(θ;x(一);y(i)),其中{x(i),y(i)}为训练示例。
由于模型参数频繁更新,参数在不同强度的损失函数中具有高方差和波动。
优点:
- 因此,模型参数的频繁更新在更短的时间内收敛。
- 需要较少的存储器,因为不需要存储损失函数值。
- 可能会得到新的最小值。
缺点:
- 模型参数的高方差。
- 甚至在达到全局最小值后也可以射击。
- 为了获得与梯度下降相同的收敛性,需要缓慢降低学习率的值。
小批量梯度下降
它是所有梯度下降算法中最好的。这是对 SGD 和标准梯度下降的改进。它会在每次批处理后更新模型参数。因此,数据集被分成不同的批次,在每一批次之后,参数被更新。
θ=θ−α⋅∇j(θ;B(i)),其中{B(i)}为训练样本的批次。
优势:
- 经常更新模型参数,并且方差较小。
- 需要中等大小的内存。
所有类型的梯度下降都有一些挑战:
- 选择学习率的最佳值。如果学习率太小,梯度下降可能需要很长时间才能收敛。
- 对所有参数都有一个恒定的学习率。可能有些参数我们不想以同样的速度改变。
- 可能会陷入局部最小值。
气势
动量是为了减少 SGD 中的高方差和软化收敛而发明的。它加速了向相关方向的收敛,减少了向无关方向的波动。该方法中还使用了一个超参数,称为动量,用“ γ 表示。
V(t)=γV(t1)+α。∇J(θ)
现在,权重更新为θ=θV(t)。
动量项 γ 通常设置为 0.9 或类似值。
优点:
- 减少参数的振荡和高方差。
- 比梯度下降收敛得更快。
缺点:
- 增加了一个需要手动精确选择的超参数。
内斯特罗夫加速梯度
动量可能是一个好方法,但是如果动量太高,算法可能错过局部最小值,并且可能继续上升。因此,为了解决这个问题,开发了 NAG 算法。这是一种前瞻方法。我们知道我们将使用**γV(t1)来修改权重,因此θγV(t1)**大致告诉我们未来的位置。现在,我们将基于这个未来参数而不是当前参数来计算成本。
V(t)=γV(t1)+α。∇j(θγv(t1)),然后使用θ=θv(t)更新参数。
NAG vs momentum at local minima
优点:
- 不会错过局部最小值。
- 如果出现最小值,速度会变慢。
缺点:
- 尽管如此,超参数仍需要手动选择。
阿达格拉德
所解释的所有优化器的缺点之一是,对于所有参数和每个周期,学习率是恒定的。这个优化器改变了学习率。它改变每个参数的学习率**‘η’和每个时间步长‘t’。**这是一种二阶优化算法。它对误差函数的导数起作用。
A derivative of loss function for given parameters at a given time t.
Update parameters for given input i and at time/iteration t
η 是一个学习率,在给定的时间,基于为给定的参数 θ(i)计算的先前梯度,对给定的参数 θ(i) 进行修改。
我们存储梯度的平方和 w.r.t. θ(i) 直到时间步长 t ,而 ϵ 是一个避免被零除的平滑项(通常约为 1e 8)。有趣的是,如果没有平方根运算,该算法的性能会差得多。
它对不太频繁的参数进行大的更新,对频繁的参数进行小的更新。
优点:
- 每个训练参数的学习率变化。
- 不需要手动调整学习率。
- 能够在稀疏数据上训练。
缺点:
- 因为需要计算二阶导数,所以计算成本高。
- 学习率总是下降,导致训练缓慢。
阿达德尔塔
它是阿达格勒的扩展,倾向于消除它的衰减学习率问题。 Adadelta 将累积的过去梯度的窗口限制为某个固定大小 w ,而不是累积所有先前平方的梯度。在此,使用指数移动平均值,而不是所有梯度的总和。
我们将 γ 设置为与动量项相似的值,大约为 0.9。
Update the parameters
优点:
- 现在学习速度不衰减,训练不停止。
缺点:
- 计算开销很大。
圣经》和《古兰经》传统中)亚当(人类第一人的名字
Adam (自适应力矩估计)适用于一阶和二阶动量。Adam 背后的直觉是,我们不希望滚动得太快,因为我们可以跳过最小值,我们希望稍微降低速度,以便仔细搜索。除了存储类似于 AdaDelta 、Adam、、*、*的过去平方梯度的指数衰减平均值之外,还保存过去梯度的指数衰减平均值 M(t)。
M(t)和 V(t) 分别是梯度的 均值 和 无中心方差 的一阶矩值。
First and second order of momentum
这里,我们取 M(t) 和 V(t) 的平均值,使得**E[M(t)】**可以等于 E[g(t)] 其中,**E[f(x)】**是 f(x) 的期望值。
要更新参数:
Update the parameters
β1 的值是 0.9,β2 的值是 0.999,而’ ϵ’ 的值是(10 x exp(-8))。
优点:
- 方法太快,收敛很快。
- 纠正消失学习率,高方差。
缺点:
计算成本高。
各种优化器之间的比较
Comparison 1
comparison 2
结论
亚当是最好的优化者。如果一个人想在更短的时间内比亚当更有效地训练神经网络,那么他就是优化者。
对于稀疏数据,使用具有动态学习率的优化器。
如果,想使用梯度下降算法比 min-batch 梯度下降是最好的选择。
我希望你们喜欢这篇文章,并且能够对不同优化算法的不同行为有一个很好的直觉。
用蒙特卡罗方法优化 21 点策略
强化学习的基础
介绍
强化学习已经席卷了人工智能世界。从 AlphaGo 到 AlphaStar ,越来越多的传统人类主导的活动现在已经被由强化学习驱动的人工智能代理所征服。简而言之,这些成就依赖于在一个环境中优化一个主体的行为以获得最大的回报。在过去几篇关于 GradientCrescent 的文章中,我们讨论了强化学习的各个基本方面,从基本的 bandit 系统和 p 基于策略的方法,到在马尔可夫环境中优化基于奖励的行为。所有这些方法都要求我们完全了解我们的环境——例如,动态编程要求我们拥有所有可能状态转换的完整概率分布。然而,在现实中,我们发现大多数系统是不可能完全知道的,并且由于复杂性、固有的不确定性或计算限制,概率分布不能以显式形式获得。打个比方,考虑一下气象学家的任务——预测天气背后涉及的因素可能如此之多,以至于不可能知道确切的概率。
Can you guarantee a certain probability for hurricane formation?
对于这些情况,基于样本的学习方法,如蒙特卡罗,是一个解决方案。术语蒙特卡罗通常用于描述任何依赖于随机抽样的估计方法。换句话说,我们并不假设我们的环境知识,而是通过从与环境的互动中获得的状态、动作和回报的样本序列,从经验中学习。这些方法是通过直接观察模型在正常运行时返回的回报来判断其状态的平均值。有趣的是,已经证明即使没有环境的动态(可以认为是状态转换的概率分布)的任何知识,我们仍然可以获得最佳行为以最大化回报。
作为一个例子,考虑投掷 12 次骰子的回报。通过将这些滚动视为单个状态,我们可以对这些回报进行平均,以接近真实的预期回报。随着样本数量的增加,我们就越能准确地接近实际的预期收益。
The average expected sum of throwing 12 dice rolls 60 times (University of Alberta)
这种基于抽样的估值对我们的忠实读者来说可能很熟悉,因为抽样也是针对 k-bandit 系统进行的。蒙特卡罗方法不是用来比较不同的强盗,而是用来比较马尔可夫环境中的不同政策,通过确定一个状态的值,同时遵循一个特定的政策直到终止。
用蒙特卡罗方法估计状态值
在强化学习的背景下,蒙特卡罗方法是一种通过平均样本回报来估计模型中状态值的方法。由于终端状态的需要,蒙特卡罗方法固有地适用于情节环境。由于这种限制,蒙特卡罗方法通常被认为是“离线”的,在这种情况下,所有的更新都是在到达终端状态之后进行的。一个简单的类比是在迷宫中随机导航——离线方法会让代理到达终点,然后使用经验来尝试减少迷宫时间。相比之下,在线方法会让代理不断修改其在迷宫中的行为——也许它注意到绿色走廊通向死胡同,并决定在迷宫中避开它们。我们将在下一篇文章中讨论在线方法。
蒙特卡洛程序可总结如下:
Monte Carlo State-Value Estimation (Sutton et. al)
为了更好地理解蒙特卡罗是如何工作的,请看下面的状态转换图。每个状态转换的奖励显示为黑色,适用的折扣系数为 0.5。让我们暂时把实际的状态值放在一边,专注于计算一轮收益。
State transition diagram. State number is shown in red, returns are shown in black.
假设终端状态的返回值为 0,让我们从终端状态(G5)开始计算每个状态的返回值。请注意,我们已经将折扣因子设置为 0.5,从而对最近的状态进行加权。
或者更一般地说,
为了避免将所有返回保存在一个列表中,我们可以使用一个与传统梯度下降有一些相似之处的等式,增量地执行蒙特卡罗状态值更新过程:
Incremental Monte Carlo update procedure. S stands for state, V its value, G it return, and alpha is a step size parameter.
在强化学习中,蒙特卡罗方法可以进一步分为“首次访问”或“每次访问”。简而言之,两者之间的区别在于在 MC 更新之前,一个状态在一集内可以被访问的次数。首次访问 MC 方法将所有状态的值估计为终止前首次访问每个状态后的平均回报,而每次访问 MC 方法将终止前访问一个状态的次数n-后的平均回报。 由于相对简单,本文中我们将使用首次访问蒙特卡罗。
用蒙特卡罗方法进行策略控制
如果一个模型不能用来提供政策,MC 也可以用来估计状态-行动值。这比单独的状态值更有用,因为给定状态中每个动作(q)的值的想法允许代理根据未知环境中的观察自动形成策略。
更正式地说,我们可以使用蒙特卡洛来估计 q(s,a,pi),即从状态 s 开始,采取行动 a,然后遵循政策 pi 时的预期收益。蒙特卡洛方法保持不变,只是我们现在增加了对某个状态采取行动的维度。如果访问了状态 s 并且在其中采取了动作 a,则称一个状态-动作对(s,a)在一个情节中被访问。类似地,可以通过首次访问或每次访问的方法进行状态动作值估计。****
如同在动态编程中一样,我们可以使用广义策略迭代来从状态-动作值的观察中形成策略。
通过交替执行策略评估和策略改进步骤,并结合探索启动以确保所有可能的操作都被访问,我们可以为每个状态实现最优策略。对于蒙特卡洛 GPI 来说,这种交替一般在每集结束后进行。
Monte Carlo GPI (Sutton et. al)
了解 21 点策略
为了更好地理解蒙特卡罗在评估不同状态值和状态动作值的实践中是如何工作的,让我们用 21 点游戏进行一步一步的演示。首先,让我们定义游戏的规则和条件:
- 我们将只和庄家对打,没有其他玩家参与。这使得我们可以将经销商的手视为环境的一部分。
- 数字卡的价值是按面值计算的。牌 J、K 和 Q 的值是 10。ace 的值可以是 1 或 11,取决于玩家的选择
- 双方都发了两张牌。玩家的两张牌正面朝上,而庄家的一张牌正面朝上。
- 目标是在第一轮拿到所有牌的总和。
- 你一共画了 19 张。但是你得寸进尺,抽到了 3,然后破产了。当你破产时,庄家只有一张可见的牌,总共 10 元。这可以想象如下:
- 当我们破产时,我们这一轮的奖励是-1。让我们相应地将此指定为倒数第二个状态的回报,格式为[代理商总和,经销商总和,ace?]:
那真是不幸。我们再来一轮吧。
第二轮。
你一共画了 19 张。这一次,你决定留下。庄家得到 13,命中,破产。倒数第二个状态可以描述如下。
Round 1.
让我们描述一下这一轮发生的状态和奖励:
随着剧集的结束,我们现在可以使用计算的回报来更新这一轮中所有状态的值。假设贴现因子为 1,我们只需像之前的状态转换一样,在之前的手牌中传播新的奖励。由于状态 V(19,10,no)之前的收益为-1,我们计算预期收益,并将其分配给我们的状态:
履行
让我们通过使用基于 Sudharsan 等人的 Python 方法,使用首次访问蒙特卡罗来实现 21 点游戏,以了解游戏中所有可能的状态值(或不同的手牌组合)。艾尔。像往常一样,我们的代码可以在 gradient crescentGithub上找到。
Round 2.
我们将使用 OpenAI 的健身房环境来实现这一点。把环境想象成用最少的代码运行 21 点游戏的界面,让我们专注于实现强化学习。方便的是,所有收集到的关于状态、动作和奖励的信息都保存在“观察”变量中,这些变量是通过运行游戏的会话积累的。
让我们从导入所有需要获取和绘制结果的库开始。
Final state values for the Blackjack demonstration.
接下来,让我们初始化我们的健身房环境,并定义指导我们的代理行动的策略。基本上,我们会继续打,直到我们的手牌总数达到 19 或更多,之后我们会站起来。
接下来,让我们定义一种方法,使用我们的策略为一集生成数据。我们将存储状态信息、采取的行动以及行动后立即获得的奖励。
最后,让我们定义首次访问蒙特卡罗预测函数。首先,我们初始化一个空字典来存储当前的状态值,同时初始化另一个字典来存储跨集的每个状态的条目数。
对于每一集,我们调用前面的 generate_episode 方法来生成关于状态值和状态后获得的奖励的信息。我们还初始化了一个变量来存储我们的增量回报。接下来,我们获得该集期间访问的每个州的奖励和当前状态值,并使用该步骤的奖励增加我们的 returns 变量。
import gym
import numpy as np
from matplotlib import pyplot
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from collections import defaultdict
from functools import partial
%matplotlib inline
plt.style.use(‘ggplot’)
回想一下,因为我们正在执行首次访问蒙特卡洛,所以我们在一集内只访问一个州一次。因此,我们在状态字典上执行条件检查,以查看该状态是否已经被访问过。如果满足这个条件,我们就可以使用前面定义的蒙特卡罗状态值更新过程来计算新值,并将该状态的观测值增加 1。然后我们在下一集重复这个过程,以便最终获得平均回报。
#Observation here encompassess all data about state that we need, as well as reactions to itenv = gym.make(‘Blackjack-v0’)
#Define a policy where we hit until we reach 19.
# actions here are 0-stand, 1-hitdef sample_policy(observation):
score, dealer_score, usable_ace = observation
return 0 if score >= 19 else 1
让我们跑起来看看我们的结果吧!
def generate_episode(policy, env):
# we initialize the list for storing states, actions, and rewards
states, actions, rewards = [], [], []# Initialize the gym environment
observation = env.reset()
while True:# append the states to the states list
states.append(observation)
# now, we select an action using our sample_policy function and append the action to actions list
action = sample_policy(observation)
actions.append(action)# We perform the action in the environment according to our sample_policy, move to the next state
observation, reward, done, info = env.step(action)
rewards.append(reward)# Break if the state is a terminal state (i.e. done)
if done:
break
return states, actions, rewards
显示 21 点各手牌状态值的输出示例。
def first_visit_mc_prediction(policy, env, n_episodes):
# First, we initialize the empty value table as a dictionary for storing the values of each state
value_table = defaultdict(float)
N = defaultdict(int)
我们可以继续观察蒙特卡洛 5000 集,并绘制描述玩家和庄家手牌的任意组合的值的状态值分布。
for _ in range(n_episodes):
# Next, we generate the epsiode and store the states and rewards
states, _, rewards = generate_episode(policy, env)
returns = 0# Then for each step, we store the rewards to a variable R and states to S, and we calculate
for t in range(len(states) — 1, -1, -1):
R = rewards[t]
S = states[t]
returns += R# Now to perform first visit MC, we check if the episode is visited for the first time, if yes,
#This is the standard Monte Carlo Incremental equation.
# NewEstimate = OldEstimate+StepSize(Target-OldEstimate) if S not in states[:t]:
N[S] += 1
value_table[S] += (returns — value_table[S]) / N[S]
return value_table
所以让我们总结一下我们所学到的。
基于样本的学习方法允许我们在没有任何转换动力学知识的情况下,简单地通过采样来估计状态和状态动作值。
value = first_visit_mc_prediction(sample_policy, env, n_episodes=500000)
for i in range(10):
print(value.popitem())
Sample output showing the state values of various hands of blackjack.
蒙特卡罗方法依赖于模型的随机抽样,观察模型返回的回报,并在正常操作期间收集信息,以定义其状态的平均值。
通过蒙特卡罗方法,广义策略迭代是可能的。
def plot_blackjack(V, ax1, ax2):
player_sum = np.arange(12, 21 + 1)
dealer_show = np.arange(1, 10 + 1)
usable_ace = np.array([False, True]) state_values = np.zeros((len(player_sum), len(dealer_show), len(usable_ace))) for i, player in enumerate(player_sum):
for j, dealer in enumerate(dealer_show):
for k, ace in enumerate(usable_ace):
state_values[i, j, k] = V[player, dealer, ace]
X, Y = np.meshgrid(player_sum, dealer_show)
ax1.plot_wireframe(X, Y, state_values[:, :, 0])
ax2.plot_wireframe(X, Y, state_values[:, :, 1]) for ax in ax1, ax2: ax.set_zlim(-1, 1)
ax.set_ylabel(‘player sum’)
ax.set_xlabel(‘dealer sum’)
ax.set_zlabel(‘state-value’)fig, axes = pyplot.subplots(nrows=2, figsize=(5, 8),subplot_kw={'projection': '3d'})
axes[0].set_title('state-value distribution w/o usable ace')
axes[1].set_title('state-value distribution w/ usable ace')
plot_blackjack(value, axes[0], axes[1])
State-value visualization of different blackjack hand combinations
在 21 点游戏中,玩家和庄家所有可能组合的价值可以通过反复的蒙特卡洛模拟来判断,从而为优化策略开辟了道路。
- 这就结束了蒙特卡罗方法的介绍。在我们的下一篇文章中,我们将继续讨论基于样本学习的在线方法,以时间差异学习的形式。
- 我们希望您喜欢这篇关于走向数据科学的文章,并希望您查看我们的母出版物 GradientCrescent 上的许多其他文章,这些文章涵盖了应用人工智能。
- 参考文献
- 萨顿等人。强化学习
怀特等人。阿尔伯塔大学强化学习基础
席尔瓦等人。阿尔,强化学习,UCL
Sutton et. al, Reinforcement Learning
White et. al, Fundamentals of Reinforcement Learning, University of Alberta
Silva et. al, Reinforcement Learning, UCL
Platt et. Al, Northeaster University
全渠道零售中优化顾客旅程的艺术和科学
嗨,我是一家全球美容零售公司的营销科学分析师。我工作的公司是该地区最成熟的全渠道零售商之一。
随着数据分析的出现,全渠道零售正处于历史上最激动人心的前沿。通过客户的线上和线下旅程收集的数据提供了对客户行为的独特见解。当有效使用时,这种见解为公司提供了改善顾客购物体验的机会。这将反过来增加客户的转换和公司的收入和利润。
在本文中,我们将尝试使用纯粹的 web/app cookie 数据(来自 Google Analytics)来了解客户的旅程。这将为我们提供关于适当营销信息的见解,以便在正确的时间接触到每个客户。
转换漏斗
Figure 1 — Conversion Funnel
我们将使用一个由四个阶段组成的简单转换漏斗:意识>兴趣>欲望>行动来描述客户的旅程。客户的购买意图在每个阶段都不同,并且随着不同阶段而增加。因此,重要的是首先要确定客户所处的意向阶段,以便能够有效地针对客户的需求。
使用的数据和见解
我们能够从网络 cookies 收集的数据中识别出客户的购买意向所处的阶段。下面的表 1 显示了可以捕获的各种类型的数据以及可以从这些数据中获得的见解。
Table 1 — Data Used and Insights
在确定了客户的意向阶段后,公司可以应用适当的营销策略,将客户推向转化漏斗。该公司将能够向客户发送相关的和有针对性的营销信息。这有助于最大限度地提高公司的营销效率。
客户之旅
Figure 2 — Customer Journey (Unassisted)
鉴于其全渠道零售业务的性质,该公司客户的转换漏斗横跨物理和数字空间。客户可能通过数字渠道了解新产品,但通过实体店购买,反之亦然。
如上面的图 2 所示,客户首先在脸书上看到一个正在他/她的手机上销售的产品的广告。他/她知道该产品,并且对该产品感兴趣,在线浏览该产品。几天后,客户决定安装该公司开发的移动应用程序,并前往一家零售店实地查看产品。在商店中,顾客搜索产品的用户评论,最终对产品满意,在线购买产品。
图 2 中的图解显示了理想的顾客旅程,在顾客购买意向的各个阶段,不需要公司的任何干预。因此,只需要最少的营销支出。然而,在大多数情况下,公司需要干预转化漏斗的每一步,以“推动”客户进入下一步。
将客户旅程与洞察力相结合
Figure 3 — Customer Journey (Assisted)
因此,重要的是,我们首先要准确识别客户所处的意向阶段。如上所述,这是通过分析获得的 cookie 数据来执行的。如上面图 3 所示,自动触发器(即营销信息)被设置到位,以将客户“推向”转化漏斗的下一步。各阶段的意图和相关的营销行动,该公司可以执行如下所述。
**认知:**在认知阶段,让顾客意识到产品的存在是很重要的。新产品尤其如此。因此,公司可以在社交媒体网站上购买广告空间,显示针对所需受众的预期广告,以提高产品的公众认知度。
关注度:点击潜在客户广告的客户表示对广告中的产品感兴趣。根据客户在第一次访问中点击的产品,公司可以通过以横幅或视频的形式推送更多类似的类别/品牌广告来进一步增加和激起客户的兴趣。
**兴趣到欲望:**为了让顾客从兴趣到欲望阶段,在大多数情况下,顾客需要测试产品,尝试各种功能,感受产品的质地和质量。因此,为了让顾客进入这一阶段,顾客去实体店做上述事情是很重要的。有了位置数据,如果客户在商店附近,公司可以发送推送通知或 SMS 来吸引客户,并将其重定向到实体店。
在商店里,零售顾问可以鼓励顾客访问该公司的网页,或者使用该公司的移动应用程序来阅读独立用户对产品的评论,或者找到任何其他附加信息。当顾客在商店中访问网页时,位置数据或 IP 地址将被捕获,并且将指示顾客已经访问了商店。这将有助于公司了解客户很可能已经发展到转换漏斗中的期望阶段。
行动欲望:了解了顾客在此阶段的经历后,如果顾客没有在店内购物,公司可以制定行动策略,将顾客从欲望阶段转变为行动阶段。
哪里可以找到谷歌分析数据
Google Analytics 与 Bigquery 进行了 360 度集成,这使我们能够在会话和点击率级别深入了解网站性能。但是,如果您无法访问 Google Analytics 或 Bigquery,也可以从销售 Google 商品的电子商务网站 Google 商品商店访问 Google Analytics 数据样本。
结论
Cookie 数据能够为我们提供关于客户对各种产品的兴趣以及他/她购买各种产品的意图的广泛见解。这些数据与来自客户旅程的其他信息(如交易数据、会员数据)相结合时至关重要,可用于制定业务行动战略,以便将客户移至转化漏斗下方。当数据得到有效利用时,公司将能够提高其营销效率以及相应的收入和利润。
优化特征生成
Image by jimmikehank Sager from Pixabay
要素生成是从一个或多个现有要素创建新要素的过程,可能用于统计分析。该过程增加了在模型构建期间可访问的新信息,因此有望产生更准确的模型。在本文中,我描述了如何使用基于 H 统计的特征交互检测算法来改进特征生成过程。这篇文章是我在 Tapreason 工作的一部分。
动机
在机器学习和模式识别中,特征是正在观察的现象的单个可测量的属性或特征。收集和处理数据可能是一个昂贵且耗时的过程。因此,在模式识别、分类和回归的有效算法中,选择信息丰富的、有区别的和独立的特征是至关重要的一步。在我们生活的信息时代,数据集在实例数量和特征数量上都变得越来越大。拥有数万个或更多要素的数据集已经变得很常见。此外,算法开发人员经常使用一种称为特征生成的过程。特征生成是从一个或多个特征创建新特征的过程,可能用于统计分析。通常,这个过程是向模型中添加新的信息,使其更加准确。当存在特征交互时,特征生成可以提高模型精度。通过添加封装了特征交互的新特征,新信息变得更容易被预测模型(PM) [1]访问。在这篇文章中,我描述了如何通过检测有意义的交互和忽略无意义的交互来使用特征交互检测来改进特征生成过程。
方法学
如果作为改变 xⱼ 的值的结果的 F(x)的值的差异依赖于 xₖ 的值,则函数 F(x)被认为展示了其两个变量 xⱼ 和 xₖ 之间的相互作用。对于数值变量,这可以表示为:
为了避免在特征生成过程中生成无意义的特征,我们使用 H -statistic 来检测特征交互并评估其强度。弗里德曼&波佩斯库【2】将 H 统计量定义为相互作用强度的度量。背后的思想是,如果两个特征 xⱼ 和 xₖ 不相互作用,则 F(x) 对集合*(xⱼ,xₖ)**【fⱼₖ(xⱼ,xₖ)*的偏依赖可以分解为对每个变量的各自偏依赖之和:
Fⱼ ( xⱼ )是一个函数 F(x) 的部分依赖。
Fⱼₖ(xⱼ,xₖ) 是联合( xⱼ和 xₖ) 部分依赖 F(x)
如果fⱼₖ(xⱼ,xₖ)-fⱼ(xⱼ)-fₖ(xₖ)=0那么创造出包含 xⱼ 和 xₖ 的新特征就毫无意义。
如果特征 xⱼ 和 xₖ 确实相互作用,则另一项应该被添加到等式(1)的左侧,表示相互作用的效果。
此外,如果给定变量 xⱼ 不与任何其他特征交互,那么:
这里 F_ ⱼ (x_ ⱼ )是 F(x) 对除 xⱼ 以外的所有特征的部分依赖。
如果fⱼₖ(xⱼ,xₖ)-fⱼ(xⱼ)-f_ⱼ(x_ⱼ)=0根本没有必要创造涉及 xⱼ 的新功能*。*
部分依赖
函数 F(x) 的部分相关性是一个或多个特征对机器学习模型【3】的预测结果的边际效应。给定预测器特征的任何子集 xₛ ,由 s ⊂ {1,2,.。。,n} ,函数 F(x) 对 xₛ 的偏相关定义为:
其中 xₛ 是子集中变量的一组规定的联合值,期望值超过所有变量的边际(联合)分布 x_ₛ 未在 xₛ 中表示。p_ₛ(z_ₛ)是 z _ ₛ 的边际概率密度。等式(2)可以通过以下方式从一组训练数据中进行估计:
其中 zᵢ,_ₛ (i = 1,2,.。。,n) 是训练样本中出现的 z_ₛ的值;也就是说,我们平均出模型中所有其他预测因素的影响。在实践中,构造部分依赖关系(3)相当简单。为了简化,让 zₛ=x₁ 是具有唯一值的感兴趣的预测变量{ x ₁₁,x ₁₂,。。。,x₁ₖ }。响应对 x₁ 的部分依赖性可以通过以下算法 1【4】来构建:
算法 1 :
上述算法可能计算量很大,因为它涉及对训练记录进行 k 次遍历。幸运的是,该算法可以很容易地并行化,也可以很容易地扩展到两个或更多特征的更大子集。
部分相关性图显示了一个或两个特征对机器学习模型的预测结果的边际影响。部分相关性图可以显示目标和特征之间的关系是线性的、单调的还是更复杂的。这些图让分析师更容易理解和解释“黑盒”复杂模型。
Figure 1: Monotonically increasing partial dependence plot [2].
图中的红色虚线代表平均值。
H 统计量
部分相关函数的性质用于构造统计量,以测试各种类型的交互作用效应。从等式(1) + (2)如果在特征 j 和k之间存在二阶相互作用,并且只有二阶相互作用, ∆Fⱼₖ(xⱼ,xₖ) 不为零。
Fⱼ(xⱼ) 是一个函数的部分依赖 F(x) 。
由 Friedman 和 pope scu【2】引入的 H 统计量、 Hⱼₖ 测量预测的变化有多少取决于特征的相互作用。 Fⱼₖ(xⱼ,xₖ) 未被 Fⱼ(xⱼ)+Fₖxₖ 捕获的方差分数范围从 0 到 1,值越大表明交互作用效应越强。对于双向相互作用(二阶),Hⱼₖ₂定义为:
其中 i=1,2,…,N 是数据中的观察次数。相互作用强度 Hⱼₖ 然后被计算为 Hⱼₖ= (H ⱼₖ) ⁻。 H- 统计并不局限于双向相互作用,可以推广到任意阶的相互作用效应。类似地,测试指定特征 xⱼ 是否与任何其他特征相互作用的统计数据为:
这里 F_ ⱼ (x_ ⱼ )是 F(x) 对除 xⱼ 之外的所有特征的部分依赖。
在两个特征的情况下,xₗ 和 xₖ与 xⱼ 互动。等式(6)和(7)仅用于双向相互作用( xⱼ,xₖ )和( xⱼ,xₗ ),但是它们可以扩展到三向(三阶)相互作用,包括( xⱼ,xₗ,xₖ )。
评估H-统计量是昂贵的,因为它在所有数据点上迭代,并且在每个点上,必须评估部分相关性,这又通过所有 N 数据点来完成。在最坏的情况下,我们需要对机器学习模型预测函数的 2N 个调用来计算双向 H 统计量 ( j 对 k,等式 6 和 3N 用于总的 H 统计量 ( j 对 all,等式 7 )。为了加速计算,我们可以从 N 个数据点进行采样。这具有增加部分相关性估计的方差的缺点,这使得 H 统计量不稳定。因此,当使用采样来减少计算负担时,应该采样足够的数据点。
使用 H-统计量生成有意义的特征
有效的特征生成不会生成对预测没有贡献的无意义的特征,但仍会增加特征选择的复杂性和计算时间。以下算法(算法 2 )使用 H 统计作为特征交互检测算法,并使用结果作为特征生成过程的输入。因此,特征生成过程将生成更少的无意义特征,并减少计算时间。
[1] Suzanne van den Bosch:“预测分析解决方案中的自动特征生成和选择”,Radboud 大学计算科学学院硕士论文
随机森林分类中超参数的优化
什么是超参数,如何选择超参数值,以及它们是否值得您花费时间
在本文中,我将使用 scikit-learn 的几个分类和模型选择包,深入研究随机森林分类模型的超参数调优。我将分析来自 UCI 机器学习库的葡萄酒质量数据集。出于本文的目的,我将红葡萄酒和白葡萄酒的单个数据集进行了合并,并为两者分配了一个额外的列来区分葡萄酒的颜色,其中 0 代表红葡萄酒,1 代表白葡萄酒。这种分类模式的目的是确定葡萄酒是红葡萄酒还是白葡萄酒。为了优化这个模型以创建最准确的预测,我将只关注超参数调整和选择。
什么是超参数?
通常,超参数是在学习过程开始之前设置的模型参数。不同的模型有不同的可以设置的超参数。对于随机森林分类器,有几个不同的超参数可以调整。在这篇文章中,我将研究以下四个参数:
- n _ estimators:n _ estimators 参数指定了模型森林中的树的数量。该参数的默认值是 10,这意味着将在随机森林中构建 10 个不同的决策树。
2.max _ depth:max _ depth 参数指定每棵树的最大深度。max_depth 的默认值是 None,这意味着每棵树都将扩展,直到每片叶子都是纯的。纯叶子是叶子上的所有数据都来自同一个类。
3.min _ samples _ split:min _ samples _ split 参数指定分割内部叶节点所需的最小样本数。此参数的默认值为 2,这意味着内部节点必须至少有两个样本,才能被拆分为更具体的分类。
4.*min _ samples _ leaf:*min _ samples _ leaf 参数指定在叶节点上所需的最小样本数。这个参数的缺省值是 1,这意味着每个叶子必须至少有 1 个它要分类的样本。
关于 RandomForestClassifier()的超参数的更多文档可以在这里找到。
如何调整超参数?
当您调用创建模型的函数时,可以手动调整超参数。
forest = RandomForestClassifier(random_state = 1, n_estimators = 10, min_samples_split = 1)
您如何选择要调整的超参数?
在开始调整超参数之前,我对我的数据进行了 80/20 的训练/测试分割。将在训练集上测试不同的超参数,并且一旦选择了优化的参数值,将使用所选择的参数和测试集来构建模型,然后将在训练集上测试,以查看该模型能够多准确地对酒的类型进行分类。
forest = RandomForestClassifier(random_state = 1)
modelF = forest.fit(x_train, y_train)
y_predF = modelF.predict(x_test)
当使用超参数的默认值对训练集进行测试时,预测测试集的值的准确度为 0.991538461538。
验证曲线
有几种不同的方法可以为您的模型选择要调整的超参数。直观检查模型超参数的潜在优化值的一个好方法是使用验证曲线。可以在图表上绘制验证曲线,以显示模型在单个超参数的不同值下的表现。运行下面的代码来创建这里看到的四条验证曲线,其中 param_name 和 param_range 的值针对我们正在研究的四个参数中的每一个进行了相应的调整。
train_scoreNum, test_scoreNum = validation_curve(
RandomForestClassifier(),
X = x_train, y = y_train,
param_name = 'n_estimators',
param_range = num_est, cv = 3)
该验证曲线是使用[100,300,500,750,800,1200]值创建的,这些值是要对 n_estimators 进行测试的不同值。在此图中,我们看到,在测试这些值时,最佳值似乎是 750。值得注意的是,尽管训练和交叉验证得分之间似乎存在很大的差异,但训练集对三个交叉验证中的每一个都具有 100%的平均准确性,而交叉验证集对 n_estimators 的所有值都具有 99.5%至 99.6%的准确性,这表明无论使用多少个估计值,该模型都非常准确。
在此图中,我们看到当 max_depth 设置为 15 时,交叉验证的最高准确度值接近 99.3%,这是我们将放入模型中的值。总的来说,选择 max _ depth 30 似乎更好,因为该值对于训练分数具有最高的准确性,我们选择不选择它,以防止我们的模型过度拟合训练数据。
在该图中,我们看到,在 min_samples_split 的值较高时,训练集和交叉验证集的准确性实际上都下降了,因此我们将选择 5 作为 min_samples_split 的值。在这种情况下,我们希望 min_samples_split 有一个较低的值是有意义的,因为这个参数的默认值是 2。由于在分割内部节点之前,我们为所需的最小样本数选择了较高的值,因此我们将拥有更多通用叶节点,这将对我们模型的整体准确性产生负面影响。
在此图中,我们看到,min_samples_leaf 值每增加一次,训练集和交叉验证集的准确性都会下降,因此我们将选择 1 作为参数值,考虑到此参数的默认值为 1,这也是有意义的。
值得注意的是,在构建验证曲线时,其他参数保持默认值。出于本文的目的,我们将在一个模型中一起使用所有的优化值。构建了新的随机森林分类器,如下所示:
forestVC = RandomForestClassifier(random_state = 1,
n_estimators = 750,
max_depth = 15,
min_samples_split = 5, min_samples_leaf = 1) modelVC = forestVC.fit(x_train, y_train)
y_predVC = modelVC.predict(x_test)
该模型的精度为 0.993076923077,比我们的第一个模型更精确,但只差 0.0015。
穷举网格搜索
选择要调整的超参数的另一种方法是进行彻底的网格搜索或随机搜索。随机搜索将不会在这篇文章中讨论,但是可以在这里找到关于其实现的更多文档。
详尽的网格搜索会尽可能多地接收超参数,并尝试超参数的每一种可能组合以及尽可能多的交叉验证。彻底的网格搜索是确定要使用的最佳超参数值的好方法,但是随着每个额外的参数值和您添加的交叉验证,它会很快变得非常耗时。
n_estimators = [100, 300, 500, 800, 1200]
max_depth = [5, 8, 15, 25, 30]
min_samples_split = [2, 5, 10, 15, 100]
min_samples_leaf = [1, 2, 5, 10]
hyperF = dict(n_estimators = n_estimators, max_depth = max_depth,
min_samples_split = min_samples_split,
min_samples_leaf = min_samples_leaf)
gridF = GridSearchCV(forest, hyperF, cv = 3, verbose = 1,
n_jobs = -1)
bestF = gridF.fit(x_train, y_train)
这里显示的代码运行了 25 分钟,但是选择的超参数在预测训练模型时具有 100%的准确性。得到的“最佳”超参数如下: max_depth = 15, min_samples_leaf = 1, min_samples_split = 2, n_estimators = 500。
再次,使用这些值作为超参数输入来运行新的随机森林分类器。
forestOpt = RandomForestClassifier(random_state = 1, max_depth = 15, n_estimators = 500, min_samples_split = 2, min_samples_leaf = 1)
modelOpt = forestOpt.fit(x_train, y_train)
y_pred = modelOpt.predict(x_test)
当使用测试集进行测试时,该模型还产生了 0.993076923077 的准确度。
调整超参数值得吗?
仔细而有条理地调整超参数可能是有利的。它可以使您的分类模型更加准确,这将导致整体预测更加准确。然而,这并不总是值得你去做。让我们来看看不同测试的结果:
最值得注意的是准确性的整体提高。当模型应用于我们的测试集时,基于网格搜索和验证曲线的结果选择的超参数产生了相同的精度:0.995386386386 这将我们的原始模型在测试集上的准确度提高了 0.0015。考虑到在我们需要的 4 个超参数上进行彻底的网格搜索花费了 25 分钟,在这种情况下可能不值得花时间。此外,我们的网格搜索给出的两个“优化”超参数值与 scikit-learn 的随机森林分类器的这些参数的默认值相同。当查看两个优化模型的混淆矩阵时,我们看到两个模型对红葡萄酒和白葡萄酒的错误预测数量相同,如下所示:
结论
超参数调整有利于创建更擅长分类的模型。在随机森林的情况下,可能没有必要,因为随机森林已经非常擅长分类。使用穷举网格搜索来选择超参数值也非常耗时。但是,在超参数只有几个潜在值的情况下,或者当初始分类模型不太准确时,最好至少调查一下更改模型中某些超参数值的影响。
关键术语/概念:超参数、验证曲线、穷举网格搜索、交叉验证
为群体水平预测优化个体水平模型
第一部分——偏倚分析
介绍
这篇文章的目的是提出我对机器学习(特别是医疗保健)中一个非常普遍但很少解决的问题的一些想法:如何在只给定个体水平数据的情况下,以优化预定损失函数(例如,MAE 或 MSE)的方式构建群体水平的预测?
如果您首先创建一个针对某个损失函数进行优化的个人级模型,然后取预测值的平均值,那么您是否会自动针对群体级的相同损失函数进行优化?事实证明,虽然 MSE 的答案恰好是“是”,但 MAE 的答案却是响亮的“不是”!
在这篇文章中,我解释了为什么在个体层面使用相同损失函数的下意识方法是错误的。
这是一个关于揭开这个问题真正本质的故事。作为这个探索的一部分,我使用了彼得·霍尔的一个有趣的定理,我发现这个定理的证明有点难以理解。作为对社区的服务,在第 2 部分中,我将展示该定理的背景故事,并提供一个比他在论文中提供的稍微更自然的证明,并填充原始论文中遗漏的所有关键细节。
这篇文章中看到的所有情节代码都可以在这个 GitHub repo 中找到。
示例和假设
为了使这种探索更容易理解,我将使用一个具体的例子并修正我的假设。
- 示例:一家医疗保险公司希望估算下一年雇主团体的人均成本(即团体的总成本除以成员数量),其中每个雇主团体由一定数量的个人成员组成。
- 基本假设:给你训练和测试数据,包括个人层面的数据和成员到团队的映射。训练数据具有由每个人在下一年发生的成本表示的目标值,而测试数据没有目标值。测试数据和训练数据可能具有不同的组。
剩下的工作就是指定在集团层面上与什么“真实值”进行比较。为了说明为什么在个体层面优化与在群体层面优化相同的损失函数是错误的,最简单的方法是使用以下假设:
- 假设 A :对于每个组,将其真值设为其成员的平均目标值。
我们稍后还将考虑另一个假设,如果你允许成员的变化,这个假设会更自然地出现。
为什么要有一个独立的模型呢?
在组级优化特定损失函数的一种方法是简单地创建优化该损失函数的组级模型,其特征是工程聚合的个体级特征。但是群体的数量大概比个体的数量小得多,所以人们会认为这样的模型会有很大的差异。个人层面和团体层面相结合的方法将是最明智的。
“为什么不用递归神经网络?这样,您就可以创建一个使用所有数据的组级模型!”,我听到你哭了。嗯……这将使用所有个体水平的特征,而不是未聚集的目标值,除非使用特殊的损失函数。即使这样,RNN 的是顺序依赖的,而组成员不是!所以让我们继续称之为“实验性的”。
无论哪种方式,你的群体层面的预测只能从做好个体层面的模型中获益。所以让我们开始吧!
关于 MAE/MSE 优化的直觉
底线是:为 MSE 优化意味着你在估计平均值;针对 MAE 进行优化意味着您正在估计中值。
这到底是什么意思?设 Y 为目标值,设 X_1,…,X_n 为特征。如果你的特征值是 X_1=x_1,…,X_n=x_n ,那么给定那些特征的目标值 Y|X_1=x_1,…,X_n=x_n 是一个随机变量,而不是一个常数。换句话说,你的特征值并不能决定目标。例如,如果你正在预测成本,那么完全可以想象两个具有相同特征值的个体具有不同的成本;尽管知道这些特征值确实会改变成本的分布。
如果你是针对 MSE 进行优化,那么在你的模型中插入 (x_1,…,x_n) 会试图预测 E(Y|X_1=x_1,…,X _ n = X _ n);然而,如果您正在为 MAE 进行优化,您的模型将尝试预测 中值(Y|X_1=x_1,…,X_n=x_n) 。
事实上,如果 f(x_1,…,x_n) 是损失函数为 MSE 的机器学习模型在这些特征值处的模型预测,那么它将试图近似使以下各项最小化的 a :
这最后一个表达式是 a 中具有全局最小值 E(Y|X_1=x_1,…,X_n=x_n) 的抛物线。
类似地,如果 f(x_1,…,x_n) 是损失函数为 MAE 的机器学习模型在这些特征值处的模型预测,那么它将尝试近似将以下各项最小化的 a :
最小化该表达式的 a 为 median(Y|X_1=x_1,…,X_n=x_n) 。(要看到这一点,你必须摆弄积分;这是一个简单但令人讨厌的练习。)
技术提示:给定一个事件的随机变量的条件概率,只有当你设定的事件有正概率时才有意义。试图将定义扩展到零概率事件注定是不明确的,除非我们指定一个限制程序;参见Borel-Kolmogorov 悖论。如果 X_i 中至少有一个是连续的,那么 P(X_1=x_1,…,X_n=x_n)=0 ,这就暗示 Y|X_1=x_1,…,X_n=x_n 没有任何意义。(本科教材中常见的定义为 Y|X_1=x_1,…,X_n=x_n 不是坐标不变的。对于更高级的读者来说:对次∑代数而不是事件进行调节会产生相同的问题,因为最终的随机变量在“几乎确定相等”之前是唯一的,这意味着零概率事件可以是例外。)我们可以也将会通过将特性的值限制为计算机能够表示的值来优雅地避免这个问题。这样,即使是“连续的”变量实际上也是离散的,一切都是定义明确的。
问题是
固定一组尺寸为 m 的,设
成为第 i 人的特征。如果我们正在创建一个优化 MSE 的个体水平模型,那么结果的平均值就是
根据期望的线性度,这等于
太好了!换句话说,通过在个人层面优化 MSE,您也在团队层面优化 MSE!
现在让我们对梅做同样的分析。如果我们正在创建一个优化 MAE 的个体水平模型,那么结果的集合就是对
但那是非常非常非常遥远的事
这是一个简单的例子,说明总和的中位数与中位数之和相差甚远,即使随机变量都是独立同分布的:
为了设置这个例子,我将使用比例为 200 和形状为 1 的伽玛分布。这个分布看起来是这样的:
(本帖中看到的剧情代码可以在 this GitHub repo 中找到。)
Figure 1
现在将 X_i 作为遵循该分布的 i.i.d。下面是他们的中位数与平均值的中位数的比较:
Figure 2
你也看到了,这个差距挺大的!
对于群体级 MAE,优化个体级 MSE 优于优化个体级 MAE
如果一个群体很大,那么可以合理地假设
是近似正常的;正态分布的中值就是它的平均值。因此,如果您在个人层面优化 MSE,然后取预测的平均值,您将近似估计大群体的中值!乍一看,这似乎是反直觉的:不仅对于组级 MSE,而且对于组级 MAE,在个体级优化 MSE 都优于在个体级优化 MAE。
但是,我们应该期望对小群体的偏见有多严重呢?(事实将会证明,秘密地关键是使用中心极限定理的误差估计。)
彼得·霍尔的一个结果
这激起了我的兴趣。所以我开始寻找关于 i.i.d .总和的中位数的结果。我找到了彼得·霍尔的《关于独立变量和的众数和中位数的极限行为》,在那里他证明了下面的结果。
(见本帖第二部分更深入的理解为什么这个定理是真的。)由于 X_i 都是同分布的,为了便于标注,我们就简单地让 X := X_1 吧。如果我们不假设X _ Is 有均值 0 和方差 1 的话,一个快速的回包络计算,还原到归一化的情况下,表明这还原到如下的近似值:
请注意,这是一个渐近结果!为了能够在上面讨论的机器学习环境中使用它,我们必须首先确保这个近似对于小的 n 是合理的。让我们做一个快速的概念验证:
(本帖中看到的剧情代码可以在这个 GitHub repo 中找到。)
Figure 3
这是相当准确的!
机器学习环境中的偏差估计
对于每一组,我们将尝试估计
为了那个团体。被加数不是独立同分布的,所以霍尔的结果不能直接应用。为此,我们可以尝试将假设 A 替换为:
- 假设 B: 对于每一组,假设其“真值”是其成员真值的 m 个样本重复的均值的期望值。
(可以说,假设 B 在现实生活中出现得更多。在组级预测中,成员通常不是固定的,但是当前成员的目标值表示未来成员的分布目标值。)
既然目标值是 i.i.d .的平均值(因为采样是重复进行的),我们可以采用霍尔近似值。固定一个组,设 T_1,…,T_m 为采样,重复,如假设 b 中所述。 T_i 现在是 I . I . d,T:= T _ 1。我们现在面临的挑战是为每个组逼近***V(T)***E((T-μ))。这可能很困难,因为我们最想纠正偏差的群体是小群体。
一种简单化的做法是将估计为**【Y】,将 E((T-μ) ) 估计为 E((Y-E(Y)) ) 。进而,通过取我们数据中目标值的平均值来估计【Y】和 E((Y-E(Y)) ) ,并将这些估计值分别表示为ˇ和ζζζ_ 3***。这些估计是否有效取决于各组之间的差异——它们越相似,偏差校正就越好。无论哪种方式,这些近似值足以获得一个相对清晰的图片,说明一个小组需要有多小,才能使偏差超出你的舒适水平。也就是说,粗略的估计是,如果一个组的大小是 m ,那么对使用 MSE 训练的单个模型进行平均应该具有大约*
对于这个数字大得令人无法忍受的组,我建议格外小心:和ζζ_ 3**可能不是足够好的估计,并且可能导致糟糕的偏差校正。我只是建议将这种规模的组标记为需要偏差校正,并根据数据和您想到的特定应用对所需的偏差校正进行更多研究。
(请注意,虽然使用贝叶斯方法使您能够从 Y|X_1=x_{i,1},…,X_n=x_{i,n} 的分布中进行采样很有吸引力,但这些方法的普通版本假设 Y|X_1=x_{i,1},…,X_n=x_{i,n} 的分布为如果是这样的话,就不会有任何偏差需要校正……这也适用于在神经网络中使用预测时的漏失,根据亚林·加尔和邹斌·加赫拉马尼的工作,这大致相当于在适当定义的高斯过程设置中对后验样本进行采样——但该设置也假设误差呈正态分布。)**
原贴于 卢米亚塔
如果这个帖子引起了你的兴趣,你想用类似的问题挑战自己,Lumiata 正在招聘!请检查 Lumiata 的打开位置。
GitHub:https://github.com/lumiata/tech_blog
在 www.lumiata.com拜访卢米娅塔,并通过@卢米娅塔在推特上关注。
在 LinkedIn 上找到卢米娅塔:www.linkedin.com/company/lumiata
引文 :
1。Hall,P. (1980)关于独立随机变量和的众数和中位数的极限行为。安。概率 8 419–430。
2。Gal,y .和 Ghahramani,Z. (2016)辍学作为贝叶斯近似:表示深度学习中的模型不确定性。
ICML。
为群体水平预测优化个体水平模型
第 2 部分——独立同分布变量之和的中位数
介绍
在第一部分 —偏倚分析中,我探讨了来自个体水平模型的群体水平预测中的偏倚。在那里,我使用了彼得·霍尔的以下定理(“关于独立变量和的众数和中位数的极限行为”):
但是为什么会这样呢?我发现霍尔的论文有些难以理解,但非常有趣。作为对社区的一种服务,在这篇文章中,我将尽可能详细地介绍用于证明这一结果的工具,并完整地提供霍尔证明的一个变体。我已经改变了证明中的一些步骤,使证明更加自然,消除了他对 Esseen 定理的依赖,而是直接使用一种称为 Edgeworth expansions(我介绍的)的数学工具来证明他的结果。我还补充了原始证据中缺失的一些关键细节。证明的基本轮廓非常吸引人:用一个中心极限定理的误差估计来证明结果。
Edgeworth 展开式——中心极限定理的误差估计
证明中心极限定理 (CLT)及其许多变种最简单的方法就是看随机变量的特征函数。随机变量 X 的特征函数定义为
特征函数有用的原因有三:
- 独立变量和的特征函数是它们各自特征函数的乘积:
这使得计算变得简单。
2.知道了随机变量的特征函数,就决定了它的分布。其实 X 的特征函数无非是 X (如果有)的概率密度函数的傅里叶变换,所以这只是傅里叶对偶——傅里叶变换的逆傅里叶变换才是原函数!(注意:由于实数的群与其对偶群被认定为“非自然的”,在该词的范畴理论意义上,傅立叶对偶遭受多种约定。我们将遵循这样的约定,函数的傅立叶变换为
而函数 f(t) 的逆变换是
按照这个约定, X 的特征函数的逆变换就是 X 的 pdf,如果有的话。)
3.最后,
*这就是李维连续性定理。
CLT 的标准证明完全遵循这种方法。通过先归一化,您可以将 CLT 简化为:*
有人证明了这一点
*就要点而言。
关键的一步是*
因此:
*,其中最后一步是因为 X_i 都是同分布的。为了简化符号,让 X:=X_1 。
显示出*
逐点相对容易,尽管细节与讨论的其余部分无关。Edgeworth expansions 是霍尔用作激励的数学工具(尽管没有直接使用),其背后的思想是相似的:
具体来说,就是写一个 X 的特征函数Log的麦克劳林展开,其中 Log 是 log 的主分支,如
对于喜欢给事物命名的人来说,这些 κ_j 通常被称为 X 的[累积量。一般来说,累积量没有直观的解释,但前三个有:【κ_ 1 = E(X)****【κ_ 2 = V(X)***κ_ 3 = E((X—E(X)))。特别是对于我们: κ_1 = 0 和 κ_2 = 1 。
因此 X 的特征函数的展开式为:](https://en.wikipedia.org/wiki/Cumulant)*
把那个插进去
然后得到
这最后一个等式仅仅是利用了**【eˣ】***的麦克劳林展开式;每个 r_j 都是实系数的多项式 3j ,如果需要可以计算。
现在剩下的就是应用一个逆傅立叶变换来得到一个 S_n. 的概率密度的展开式,这是相对容易的——这里有一个技巧:*
*(为什么?度 0 格为标准,单项格通过反复推导度 0 格而得出;一般情况下遵循单项式的情况。)诀窍继续:推导标准正态分布 j 倍的概率密度函数实际上很容易计算(通过交换导数和积分),对于任何 j 它总是标准正态的 pdf 的一些多项式倍。(以这种方式产生的多项式被称为埃尔米特多项式,但是确定它们的性质对于我们的目的来说并不重要。)
这一切的底线是:*
所以我们只是得到了一个中心极限定理的误差估计!weeee lll……事实证明,Edgeworth 扩张几乎从不收敛…所以以上是我们讲述的更多关于 Edgeworth 扩张的激励性睡前故事。但事实证明,在极其温和的条件下,Edgeworth 展开式是“渐近展开式”,也就是说对于每个 k:
只要**【e(|x|^(k+2】)<∞**和 X 满足称为“克莱姆条件”的条件,这是比具有连续 pdf 更弱的条件。证明这一点需要一点努力,我参考了彼得·霍尔的书“自举和 Edgeworth 展开”,第 2.4 节和第 5 章,以了解细节。
从 Edgeworth 展开式到中位数的近似值
*在霍尔的论文中,相当于这一整节的短语是“它遵循那个”,没有给出解释。正如你将看到的,这个论点需要一些细微的差别。但是细节确实解决了…!
让:*
因为
由此可见:
让我们把它代入上面的 Edgeworth 展开式:
请注意,由于标准正态的 pdf 是指数型的,因此的分子
是有界的,因此这个分数收敛到 0。这意味着
因此:
*(我们最终想证明 m_n 收敛,但这是证明其余部分所需的第一步。)
现在让我们用麦克劳林展开式:*
它的收敛半径是无穷大。由此可见:
可以看出,Edgeworth 展开式中的**P1(x)**项等于 -κ_3(x -1)/6 ,因此:
特别是:
因此,我们知道:
其中,乘以 n 的平方根 后暗示:
我们准备好大开杀戒了。使用 Maclaurin 展开式:
我们可以看到:
特别是:
因此当 n 接近无穷大时:
我们完事了。
关于非 i.i.d .的霍尔式结果的最后说明
可以理解的是,独立但不同分布的随机变量和的中值没有渐近结果。然而,令人惊讶的是,确实存在一种可用于非独立同分布随机变量的 Edgeworth 展开式的变体,它在某种相当复杂的理想定义下是理想的。(见白志东、赵林成《独立随机变量分布函数的 Edgeworth 展开式》。)可以想象的是,可以按照上面的方法从 Edgeworth 展开式到中位数的估计。
原贴于 卢米亚塔
如果这个帖子引起了你的兴趣,你想用类似的问题挑战自己,Lumiata 正在招聘!请检查 Lumiata 的未平仓。
*GitHub:【https://github.com/lumiata/tech_blog *
在 www.lumiata.com拜访卢米娅塔,并通过@卢米娅塔在推特上关注。
在 LinkedIn 上找到卢米娅塔:www.linkedin.com/company/lumiata
引文 :
1。Hall,P. (1980)关于独立随机变量和的众数和中位数的极限行为。安。概率 8 419–430。
2。Hall,P. (1992)自助和 Edgeworth 扩展。纽约施普林格出版社。
3。白,赵,李(1984)独立随机变量分布函数的 Edgeworth 展开式。中国科学 291–22。
优化 Jupyter 笔记本:提示、技巧和 nbextensions
Jupyter 笔记本是一种基于网络的交互式工具,机器学习和数据科学社区经常使用。它们用于快速测试,作为报告工具,甚至作为在线课程中高度复杂的学习材料。
所以在这个博客中,我将列出一些快捷方式、魔法命令和扩展。
快捷指令
按Ctrl+Shift+p
键或点击菜单栏中的小键盘图标,获得命令面板列表
命令和编辑模式下的快捷键:
Shift + Enter
运行当前单元格,选择下图Ctrl + Enter
运行选定的单元格Alt + Enter
运行当前单元格,在下面插入Ctrl + S
保存和检查点
编辑模式下的快捷键:
Esc
进入命令模式Tab
代码完成或缩进Shift + Tab
工具提示Ctrl + ]
缩进Ctrl + [
德登Ctrl + A
全选Ctrl + Z
撤销Ctrl + Shift + Z
或Ctrl + Y
重做Ctrl + Home
转到单元格开始Ctrl + End
转到单元格末端- 向左走一个单词
Ctrl + Right
向右走一个字
一旦进入命令模式,按下H
(帮助)获得键盘快捷键列表:
我列出了一些最常用的快捷方式。通过点击Esc
确保您处于命令模式:
- 用
Up
和Down
键上下滚动你的单元格。 - 按下
A
或B
在当前单元格的上方或下方插入一个新单元格。 M
将当前单元格转换为降价单元格。Y
将当前单元格设置为代码单元格。X
将剪切选中的单元格C
将复制选中的单元格V
将粘贴正在被复制/剪切的单元格Shift + V
粘贴细胞上方S
将保存笔记本F
将查找/替换O
将切换输出D + D
(D
两次)将删除活动单元格。Z
将撤消单元格删除。- 要一次选择多个单元格,按住
Shift
并按下Up
或Down
Shift + Space
将笔记本向上滚动Space
向下滚动笔记本
选择多个单元格:
- 按下
Shift + M
合并您的选择 - 要拆分光标处的活动单元格,在编辑模式下按
Ctrl + Shift + -
- 您也可以在单元格左边的空白处单击和
Shift + Click
来选择它们
在笔记本之间复制和粘贴单元格:
- 笔记本 1: —按住 Shift 键选择多个单元格,然后点击
Ctrl+c
进行复制 - 笔记本 2: —按 Esc 键进入命令模式
Ctrl + v
粘贴
使用! pip install <package>
在当前内核中安装软件包
通过在 shell 命令中添加一个$
符号来使用 Python 变量:
魔法命令
魔术命令是显著扩展笔记本电脑功能的快捷方式
共享笔记本中的代码示例:
- 使用
%pastebin
魔法功能选择单元格范围 - Jupyter 给你一个秘密的网址来分享
Note- This link gets expired in 7 days
使用%whos
或%who_ls
获得已定义变量的列表
%whos
显示变量类型和一些额外信息:大小、内容等。%who_ls
仅显示变量名称
在 Jupyter 笔记本中使用外部文件:
%pycat file.py
➡在传呼机中打开脚本%load file.py
➡将脚本插入单元格%run file.py
➡运行脚本%run file.ipynb
➡经营笔记本%notebook filename
➡将当前 IPython 历史导出到笔记本文件
获取、设置或列出环境变量:
%env
➡lists 所有环境变量%env var
➡得到 var 的值%env var val
➡为 var 设定值
在 shell 中运行命令:
%system
➡使用 shell(主要用于获取当前目录、日期等)
使用%autosave
将笔记本自动保存到其检查点:
Autosaving every 120 seconds (2 minutes)
执行不同的语言:
%%HTML
➡执行 HTML 代码
%%perl
➡在子进程中执行 Perl%%javascript
或%%js
➡来执行 Javascript 代码块%%python3
➡用 python3 在子进程中执行代码%%ruby
➡执行 Ruby 代码
其他魔法命令:
%history
➡打印输入历史%lsmagic
➡列出当前可用的魔法功能%magic
➡打印关于魔法功能系统的信息%matplotlib
➡设置 matplotlib 交互工作%pwd
➡返回当前工作目录- ➡展示了一份快速参考表
%time
➡时间执行一个 Python 语句或表达式(它既可以用作行魔术,也可以用作单元格魔术)
nb 扩展
这个扩展的好处是它改变了默认值。
要安装 nbextensions,请在 Anaconda 提示符下执行以下命令:
conda install -c conda-forge jupyter_contrib_nbextensions
conda install -c conda-forge jupyter_nbextensions_configurator
或者,您也可以使用 pip 安装 nbextensions:
pip install jupyter_contrib_nbextensions
- 运行
pip show jupyter_contrib_nbextensions
找到笔记本扩展的安装位置 - 运行
jupyter contrib nbextensions install
安装新的扩展
安装完成后,重启 Jupyter 笔记本,您可以观察到一个新标签 Nbextensions 添加到菜单中:
同样的 nbextension 也可以位于编辑菜单中:
现在,让我们来看几个 nb 扩展:
- 腹地 -它为代码单元中的每一次按键启用代码自动完成菜单,而不是仅用 tab 启用它
2.拆分单元格笔记本 -在 Jupyter 笔记本中启用拆分单元格
进入命令模式(Esc
),使用Shift + s
将当前单元格切换为拆分单元格或全幅单元格。
3.目录-TOC 扩展能够收集所有运行的标题,并在浮动窗口中显示它们,作为侧边栏或导航菜单。该扩展也是可拖动的,可调整大小的,可折叠的,可停靠的,并具有自动编号和唯一的链接 id,以及一个可选的 toc 单元。
- Autopep8 - 使用特定于内核的代码来重新格式化/修饰代码单元格的内容
5.snippet-添加一个下拉菜单,将 snippet 单元格插入当前笔记本。
Jupyter 笔记本主题
我们可以从 Jupyter 笔记本的默认主题转换到黑暗模式。
为此,我们需要安装jupyterthemes
:
jt -l
将给出可用主题的列表
jt -t <theme name>
会改变主题。让我们试着用切斯特风格主题把它改成黑暗模式。
jt -r
会将其恢复为默认主题
我们还可以用 Jupyter 笔记本做很多我们没有提到的事情。还是留到下一篇博客吧。
我的其他博客文章
- 使用 TensorFlow 建立你的第一个机器学习模型
- 不同机器学习算法的用例
- 通过 Visual Studio 开发基础程序激活免费访问 Datacamp、Pulralsight、LinkedIn Learning 等的步骤
- 我的 MozFest 经历和第一次演讲
疑问?评论?欢迎在评论区留言,也可以在LinkedIn上关注并联系我。 如果你喜欢这个博客,可以 给我买杯咖啡 。
优化技术:遗传算法
一种自适应且众所周知的优化技术
在复杂的机器学习模型中,性能通常取决于多个输入参数。为了得到最佳模型,必须适当地调整参数。然而,当有多个参数变量时,每个变量的取值范围很宽,每组参数有太多可能的配置需要测试。在这些情况下,应使用优化方法来获得最佳输入参数,而无需花费大量时间来寻找它们。
在上图中,它显示了仅基于两个参数的模型分布。如示例所示,找到曲线的最大值或最小值并不总是一件容易的事情。这就是优化方法和算法在机器学习领域至关重要的原因。
遗传算法
最常用的优化策略是遗传算法。遗传算法是基于达尔文的自然选择理论。它相对容易实现,并且算法的设置有很大的灵活性,因此它可以应用于广泛的问题。
选择健身功能
首先,必须有一个适应度函数来衡量一组输入参数的表现。从适应度函数得到的具有较高适应度的解将比具有较低适应度的解更好。
例如,如果一个解决方案的成本为 x + y + z,那么适应度函数应该尝试最小化成本。这可以通过以下健身功能来实现:
该适应度函数将为每组输入参数生成一个适应度值,并用于评估每组参数的执行情况。
产生一个群体
要运行遗传算法,首先从一群个体开始,其中每个个体都是一个解。解决方案由一组基因表示,其中解决方案中的每个基因都是模型中的一个变量。每个解决方案都是随机生成的,并根据适应度函数进行评估,以生成适应度分数。选择一个合适的人口规模是非常重要的。如果群体规模太低,那么就很难探索问题的整个状态空间。如果群体太大,那么将需要很长时间来处理每一代,因为计算适应度和生成每个群体的系统成本将需要指数级的更多时间来处理。
亲代选择
接下来,群体经历一个称为父代选择的过程,其中最佳解决方案(最适合的个体)将被选择以创建下一代解决方案。有许多方法可以做到这一点,如**健身比例选择(FPS),基于排名的选择,以及锦标赛选择。**这些方法各有利弊,应根据模型进行选择。
交叉
一旦亲本被选中,亲本将经历一个叫做交叉的过程。交叉是指两个父母为了创造一个新的解决方案而交叉他们的基因(也称为孩子)。这促进了在问题的状态空间中的探索,并且潜在地产生了从未被测试过的新的解决方案。
这是根据解决方案的数据类型以不同方式完成的。对于基因用二进制表示的解,有 1 点交叉、n 点交叉、和均匀交叉等方法。
对于基因是实值的解决方案,有诸如单算术交叉和全算术交叉的方法。
此外,对于排列问题(如旅行推销员问题等),有一些方法,如部分映射交叉(又名 PMX)、边交叉、顺序 1 交叉和循环交叉。
变化
在交叉阶段之后,产生的子代经历一个突变阶段。突变是指每个基因都可能根据随机概率发生变化。这允许利用,因为子解决方案不会像交叉阶段那样剧烈变化,但仍然能够在其当前解决方案的邻域内探索。
对于二进制解决方案,变异过程相对简单,每个位根据某种变异概率进行切换。
对于实值解,可以根据该变量的一些可接受的范围或者通过添加一些以 0 为中心并且根据一些高斯分布变化的噪声来选择不同的基因。
最后,对于排列问题,主要有四种方法;插入突变、交换突变、倒位突变、和争夺突变。
幸存者选择
一旦创建了儿童群体,下一个阶段就是幸存者选择。这一阶段决定了哪些个体可以继续传给下一代。有多种方法可以做到这一点,因为新一代可以从父母和子女中选择。选择每一代幸存者的方法主要有两种,基于年龄的选择和基于适应度的选择(FBS) 。在 FBS 中,有精英主义,其中每个群体中最适合的被选择,还有天才,其中每个群体中最不适合的被淘汰。
算法重复
一旦选择了新一代,整个过程重复进行,直到算法根据某些收敛标准收敛。在整个算法运行之后,返回所有代中的最佳解决方案。
履行
结论
遗传算法因其广泛的适用问题而被广泛应用。简单版本的遗传算法相对容易实现,但有更复杂的变化。例如,这些遗传算法可以并行执行,其中整组可调参数可以在并行遗传算法上分开。根据问题的计算量,这些并行 GAs 可以被配置为细粒度的或粗粒度的。算法也可以采用主从方式,从机控制计算,主机控制选择过程。此外,解决方案还可以被配置为在不同的并行遗传算法之间结合不同的拓扑和迁移策略。所有这些变化使得遗传算法具有广泛的灵活性,并且绝对是一个需要了解的重要算法。
优化神经网络——从哪里开始?
通过使用 Google Colab 中的 Keras 构建和调整神经网络来发展直觉
Photo by Adi Goldstein on Unsplash
T 神经网络要调优的参数和超参数(以下都称为参数)非常多,那么从哪里开始呢?
在吴恩达教授的深度学习专业化课程中,他给出了以下指导方针:
- 从学习率开始;
- 然后试隐藏单元数量 s、小批量尺寸 e 和动量项;
- 最后,调整层数和学习率衰减。
这些都是很好的建议。但是,为了使它们成为我们技能的一部分,我们需要直觉:)为了实现这一点,我用 Python 构建了一个可定制的神经网络类,并进行了一系列实验来验证这些想法。让我们看看!
设置环境
我们将在这个项目中使用 Google Colab ,所以大部分库都已经安装好了。因为我们将训练神经网络,所以使用 GPU 来加速训练是很重要的。
要启用 GPU,只需进入下拉菜单中的“运行时”并选择“更改运行时类型”。然后,您可以将鼠标悬停在右上角的“已连接”上进行验证:
获取数据
在本项目中,我们将使用皮马印第安人糖尿病数据集,原因如下:
- 数据集具有挑战性,最高精度结果只有 77%左右,这给了我们做大量模型调整的机会;
- 数据集很小,只有 768 行和 9 列。这使得我们可以更快地训练,从而可以进行 10 重交叉验证,以更好地表示模型性能。
虽然我们可以手动下载数据集,但为了重现性,还是从 Kaggle 下载吧。因为我们需要使用 Kaggle 的 API,所以我们将首先通过访问 Kaggle 上的“我的帐户”页面来创建 API 令牌。这会将一个kaggle.json
文件下载到您的计算机上。
接下来,我们需要将这个凭证文件上传到 Colab:
from google.colab import files
files.upload()
然后我们可以安装 Kaggle API 并将凭证文件保存在。kaggle”目录。
!pip install -U -q kaggle
!mkdir -p ~/.kaggle
!cp kaggle.json ~/.kaggle/
现在我们可以下载数据集了:
!kaggle datasets download -d uciml/pima-indians-diabetes-database
该数据集将被下载到您当前的工作目录,即 Colab 中的“content”文件夹。由于每次重启 Colab 会话时文件都会被删除,因此将文件保存在 Google Drive 中是个好主意。您只需要使用下面的代码安装驱动器并保存在那里:
from google.colab import drive
drive.mount('/content/gdrive')
一旦安装完毕,你就可以通过“/content/gdrive”路径直接从 Google Drive 加载数据。当您需要保存绘图文件时,安装 Google Drive 也会派上用场。
带有 XGBoost 的基线模型
XGBoost 因其高准确性和高效率而被称为 go-to 算法。让我们试一试!
t1 = time()
clf = xgb.XGBClassifier()
cv = StratifiedKFold(n_splits=10, shuffle=True, random_state=seed)
scores = cross_val_score(clf, X, y, cv=cv)
t2 = time()
t = t2 - t1print("Mean Accuracy: {:.2%}, Standard Deviation: {:.2%}".format(scores.mean(), scores.std()))
print("Time taken: {:.2f} seconds".format(t))
然后我们得到了 74.88%的准确率,只用了 0.35 秒!如果我们将特征标准化并再次测试,我们将得到 76.31%的结果!这个结果已经非常接近这个数据集上的最新精度。
创建模型
为了能够测试不同的模型,我们需要动态创建模型的能力。同时,我们还需要测试模型并提供结果。这两种需求都让我想到了面向对象编程。然后我创建了下面的测试类。我将在另一篇文章中解释这一部分和下一部分的技术细节。
自动化测试
因为我们需要测试许多不同的参数组合,并且需要保存结果,所以自动化测试过程很重要。同样,让我展示而不是讲述,因为细节将在后面的帖子中解释:
基线神经网络模型
让我们从具有以下默认参数的基线模型开始:
- 输入尺寸=8
- 层数=2
- 数量单位=8
- 激活='relu ’
- activation_out='sigmoid ’
- 损失= ‘二元交叉熵’
- 初始值设定项='random_uniform ’
- 优化器='adam ’
- 学习率=0.001
- 度量=[‘准确性’]
- 纪元=10
- batch_size=4
- one_hot=False
如果我们跑:
param_dict_defaults, param_dict = get_defaults(), get_defaults()
accuracy_baseline = run_test(X=X, y=y, param_dict=param_dict_defaults)
我们会得到:
Finished cross-valiation. Took 1.5 mintues. Mean Accuracy: 71.61%, Standard Deviation: 2.92%
还不错,但肯定离 77.7%的顶级成绩差远了。
不同参数的重要性
为了理解不同参数对模型调整的影响,让我们一次调整一个参数,同时保持其他参数不变(因此不同于 sklearn 中的 GridSearchCV 等穷举搜索)。运行测试将为我们提供以下结果:
首先,有趣的是,上面的参数调优指南中没有提到的一些参数可能是重要的因素,例如优化器和时期。
第二,学习率确实是最有影响力的参数之一。
第三,对于这个特定的实验(包括参数选择),似乎层数比隐藏单元的数量更重要。这与上述准则相违背。
下面是调整趋势,可用于查找要调整的范围。
重要的是要注意,这里的测试只是为了提供一些直觉,不应该作为正式的规则。这是由于至少两个原因——一,各种参数及其候选值不一定具有可比性;第二,神经网络中存在天生的随机性,因此,如上图所示的结果可能会改变。
虽然参数值之间的相互作用很可能很重要,即 40 个历元在与非 0.001(例如 0.1)的学习率配对时可能会产生更差的准确性,但我们仍将在此尝试一种简单的方法——组合独立调整的最佳参数值并训练一个模型,这为我们提供了:
Finished cross-valiation. Took 49.3 mintues. Mean Accuracy: 78.00%, Standard Deviation: 4.59%
哇,这是一个残酷的 50 分钟!虽然我们不能抱怨结果,因为这是最先进的!看起来天真的方法确实有效。
参数调谐
现在我们已经看到了参数的相对重要性,是时候调整模型了。因为学习速度是最重要的,所以让我们先解决它。我们将使用下面的代码来生成 6 个介于 0.0001 和 0.01 之间的随机学习率值,因为根据上面的优化趋势可视化,这是最有希望的区域。
bases = np.repeat(10, 3)
exponents_1 = -(np.random.rand(3) + 3)
exponents_2 = -(np.random.rand(3) + 2) learning_rate = np.power(bases, exponents_1).tolist() + np.power(bases, exponents_2).tolist()
运行测试后,我们得到了:
这使我们认为 0.0006716184352348816 是最佳学习率。让我们利用这一点,继续用 6 个选项来调整批量大小,因为我们肯定要相信 ng 教授的指导方针,即批量大小是第二重要的参数:)
batch_size = [2 ** e for e in range(6)]
尽管批量大小为 2 的结果更准确,但是时间成本远远超过了收益,所以我们将采用批量大小为 16 的结果。
在更新了参数字典中的批处理大小值之后,我们现在可以继续调整时期数了。由于训练和测试的时间会随着时期数的增加而增加,所以最好在以后的阶段调整这个参数,以避免长时间的运行。
这给了我们最好的 200 个历元。接下来,让我们构建具有标准化功能的最终模型:
run_test(X=X_std, y=y, param_dict=param_dict)
这给了我们:
Finished cross-valiation. Took 8.3 mintues.
Mean Accuracy: 78.53%, Standard Deviation: 3.64%
绝对伟大的结果!所用时间不算太差,虽然比 XGBoost 多了 1422 倍😂
现在,如果我们不调整参数,只标准化特性会怎么样呢?
Finished cross-valiation. Took 1.7 mintues. Mean Accuracy: 76.95%, Standard Deviation: 2.88%
因此,参数调整的效果似乎有点微不足道,但标准化,即使特征具有零均值和单位方差,对于神经网络模型调整来说是巨大的。
摘要
- 学习率是需要调整的最重要的参数,因为它可以产生很大的性能改进,同时不会对训练时间产生负面影响。
- 较小的批量可能会提供更好的结果,但也更耗时!同样,针对更多纪元的训练通常有助于提高准确度,但时间成本也很高。
- 优化器可能是一个需要优化的重要参数。
- 更深更广的神经网络可能并不总是有用的。
- 特征标准化可以极大地提高模型性能,与参数调整相比,这是一项轻而易举的任务。
- 神经网络很棒,但不是万能的。如上所述,训练和调整神经网络模型的时间比非神经网络要多几千倍甚至几百万倍!神经网络最适合计算机视觉和自然语言处理等用例。
你可以在 GitHub 上的我的项目报告中找到完整的代码。一定要试一试,看看你能得到什么结果!
感谢您的阅读!有什么我可以改进的吗?请在下面告诉我。我们都通过相互学习变得更好!
为 Postgres 优化 pandas.read_sql
将 SQL 查询读入 Pandas 数据帧是一项常见的任务,可能会非常慢。根据所使用的数据库,这可能很难避免,但是对于我们这些使用 Postgres 的人来说,我们可以使用 COPY 命令大大加快速度。然而,有几种方法可以使用 COPY 命令将 SQL 中的数据放入 pandas,但需要不同的内存/速度权衡。在本文中,我们将测试几种不同的方法。
测试数据集只是样本 Triage 预测表的前五百万行,这只是我手边的一个。我试图使用本地 Postgres 数据库中的 1300 万行,但是 pandas.read_sql 崩溃了,所以我决定将数据集降低到它可以作为基准处理的水平。
每种方法都包括三种统计数据:
峰值内存—SQL 读取代码期间使用的最高内存量。这是重要的一点,看看你的程序是否会崩溃!
增量内存—SQL 读取代码结束时仍在使用的内存量。理论上,这对于所有的方法都是一样的,但是内存泄漏会使不同的方法保留更多的内存。
经过时间 —程序使用的时钟时间。
这里用的熊猫版本是 0.24.1。
首先,快速概述一下正在测试的不同方法:
- pandas.read_sql —基线
- 临时文件—使用临时文件模块在磁盘上创建一个临时文件,以便在数据帧读入复制结果之前,将它们存放在其中
- StringIO——使用 StringIO 代替磁盘;使用更多内存,但磁盘 I/O 更少
- 压缩 BytesIO,pandas 解压——用 BytesIO 代替 StringIO,压缩数据;应该使用更少的内存,但需要更长的时间
- 压缩字节,gzip 解压缩—与其他压缩字节相同,但是使用 GzipFile 而不是 pandas 来解压缩
- 压缩的临时文件——将压缩思想应用于磁盘文件;应该会减少所需的磁盘 I/O
- 压缩字节数,低压缩级别—尝试分割未压缩方法和压缩方法之间差异的较低压缩级别
pandas.read_sql
这是基线。这里没什么特别的。
峰值内存:3832.7 MiB /增量内存:3744.9 MiB /运行时间:35.91s
使用临时文件
这是我们第一次尝试使用复制命令。来自 COPY 命令的数据必须使用 filehandle:还有比使用临时文件更简单的方法吗?
峰值内存:434.3 MB /增量内存:346.6 MB /运行时间:8.93 秒
那……好多了。对于运行时间比 read_sql 快得多,我并不感到惊讶,但我有点惊讶的是,内存使用量相差如此之大。不管怎样,我们继续吧
使用弦乐器
磁盘 I/O 可能很昂贵,尤其是取决于可用的磁盘类型。我们可以通过使用 StringIO 作为文件句柄来加速它吗?当然,这会占用更多的内存,但也许这是我们可以做的一个折衷。
峰值内存:434.2 MB /增量内存:346.6 MB /运行时间:9.82 秒
这是一个令人惊讶的结果。我本以为这会占用更多的内存,速度会更快,但事实并非如此。我的假设是,StringIO 使用的内存峰值最终会在数据帧创建过程中被一个峰值超过。
还要注意:增量内存与临时文件版本相同,这可能告诉我们,346.6 MB 是在没有任何内存泄漏的情况下内存基线的一个很好的参考。
使用压缩字节,熊猫解压。
我们能降低内存选项所需的内存吗?鉴于之前的结果,这可能看起来像一个傻瓜的差事,但我已经写了代码,所以我不会提前停止测试!Python 的 GzipFile 接口包装了一个 filehandle(在本例中是一个 BytesIO)并处理压缩。我们让 pandas 通过将“compression='gzip '”传递给 read_csv 来处理解压缩
峰值内存:613.6 MB 增量内存:525.8 MB,耗时:1:30 分钟
不好!与未压缩版本相比,它实际上使用了更多的内存(并泄漏了一些)。
使用压缩字节,Gzip 解压缩
和上一个一样,除了我们绕过熊猫的解压程序,以防它们带来问题。GzipFile 也可以为我们处理解压缩!
峰值内存:504.6 MB 增量内存:416.8 MB,耗时:1:42m
当然,这比熊猫的解压缩版本在内存方面要好,但是这仍然比未压缩的版本差。
使用压缩的临时文件
压缩思想也可以应用到以前的 tempfile 方法。在这种情况下,压缩应该可以帮助我们减少磁盘 I/O。
峰值内存:517.2 MB 增量内存:429.5 MB,耗时:1 分 35 秒
类似于其他 gzip 示例。不是一个好的选择。
使用压缩字节,低压缩级别
既然我们正在尝试,我们还有一个途径可以探索:gzip 压缩级别。前面所有示例的默认值是 9,这是可能的最高压缩率。在这样做的过程中,除了额外的时间之外,还可能需要额外的内存来进行压缩。如果我们将其中一个翻转到最低压缩级别(1)会怎么样?
峰值内存:761.5 MB 增量内存:673.8 MB,运行时间:1 分 13 秒
在时间上稍微好一点,但是在 RAM 上更差:看起来 gzipping 进程无论如何都要使用大量的内存,并且不能很好地传输。
结论
我们在这里学到了什么?
- pandas.read_sql 很烂,无论是时间还是内存。
- 使用带有 COPY 的 StringIO 或 tempfile 可以执行类似的操作。很容易将 tempfile 称为赢家,但我要强调的是,这完全是基于对您来说便宜的东西。这个测试在我的笔记本电脑上运行,使用本地磁盘。根据设置的不同,使用磁盘 I/O 可能会更昂贵!但是这两个例子一般来说都非常快,应该是你的首选!
- 压缩的想法似乎是在转移视线,至少在用 gzip 实现的时候是这样。GzipFile,它的内部我并不完全熟悉。可能有其他更复杂的方法可以工作(例如 zlib.compressobj,或者其他完全的压缩类型),但是这里没有。
优化你被医学院录取的机会
2016 年,全美共有 27772 名学生申请了医学院。这些学生中,8883 人,录取率 32% 。但当然,总体录取率并没有太大启发。我们对这篇文章真正感兴趣的数据是关于录取率如何根据 GPA 和 MCAT(医学院入学考试)分数变化的。
我们当然大致知道,在其他条件不变的情况下,更高的 GPA 和/或更高的 MCAT 分数会导致更高的录取机会,但我们想在这里真正了解细节。我们的分析将通过提出一系列问题进行:
录取率与 MCAT、录取率与 GPA 有什么模式?
给定这些模式,我们能建立一个包罗万象的模型来预测给定 MCAT 和 GPA 的录取率吗?
鉴于这种模式,能否为准医学院学生推荐一条优化录取机会的路径?
为了回答这些问题,我们将使用来自美国医学院协会的数据,该数据给出了 GPA 和 MCAT 分数选择范围的录取率。
让我们从简单的开始。基于 MCAT 和 GPA 的录取率如何变化?
我们都期望随着 MCAT 和 GPA 分数的增加,录取机会或录取率也会增加,但是怎么增加呢?线性? 二次?或者干脆是别的什么?
让我们先来看看在 MCAT 保持不变的情况下,由于各年级 GPA 的变化而引起的录取率的变化。请注意, MCAT 的得分介于 472 和 528 之间,中间值为 500 。下面,我们将 MCAT 固定为三个范围:514–517(高),498–501(中),486–489(低)。对于每个范围,我们绘制了录取率与 GPA 的关系图。
录取率 vs. GPA 似乎有一个 (大致)线性 的趋势。
这种线性关系的斜率随着MCAT 分数的增加而增加。换句话说,你的 MCAT 分数越高,平均绩点每增加一分,你被录取的机会就越大。这是 GPA 和 MCAT 决定录取率的一些初步证据。**
给定一个足够低的 MCAT 分数,在获得医学院的录取上没有 GPA 可以弥补 。也就是说,看看底部的红线,MCAT 在 486 和 489 之间,2.5 比 3.9 的 GPA 给出了大约 3%的录取率。
现在让我们转向一个更有趣的趋势,对于一些固定的 GPA 值,录取率与 MCAT 的对比。这里我们把绩点固定在三个水平:3.8–4.0(高),3.4–3.6(中),3.0–3.2(低)。请注意,尽管 3.0 的平均绩点被标为低*,但你应该认为这意味着相对较低,而不是绝对低。事实上,如果 MCAT 分数足够高,即使那些平均绩点在低范围的学生也能达到 50%的最高录取率。*
这个图形最引人注目的地方在于,这些曲线并不遵循线性或二次趋势,而是遵循一种被称为 s 形 的 数学形状。Sigmoids,通常是 S 形曲线,出现在许多其他情况下,也许最流行的是在人口模型。他们描述了一些过程,即 缓慢回升 (开始时缓慢增长) 然后获得动力 (中间快速增长),在 之前最后在某一水平封顶 (最后缓慢增长)。
事实上,它们在我们的上下文中也很有意义!给定一个非常低的 MCAT 分数,增加一点分数在开始会有一点帮助,但不会有太多帮助。一旦你不断提高 MCAT 分数,你就会获得越来越大的收益,导致你的录取率越来越快。最后,当你的 MCAT 分数已经相当高的时候,额外增加你的 MCAT 分数并没有多大帮助。
还要注意的是,GPA 值越高,sigmoid 形状就越明显。我们还可以看到不同 GPA 水平对乙状结肠的影响,例如,通过查看三个 GPA 等级中 MCAT 最高值的录取率。我们看到对于低 GPA 曲线,这里的录取率在 50% 左右,对于中 GPA 曲线在 60% 左右,对于最高 GPA 曲线在 85% 左右。
酷炫潮流!但是我们能给它们装上模型吗?
现在我们对基于 GPA 和 MCAT 的录取率趋势有了一些了解,我们想知道我们能否用一个(不太复杂的)数学模型来预测这些趋势的录取率,无论 GPA 和 MCAT 分数的组合如何,都有相当高的准确度。在解决整个问题之前,让我们先看看从最后一个数字中预测这些 sigmoids 有多好。
在这里,我们从上一节中选取了三条录取率与 MCAT 曲线, (仔细地)选择了一些 s 形曲线来最好地拟合这些数据 。s 形拟合用红色虚线表示。回想一下,这三条曲线分别代表 高、中、低 GPA 段 。我们看到至少 对于 GPA 高的波段,我们的预测几乎是提督 。当我们到达较低的 GPA 段时, 预测没有那么强,但仍然捕捉到数据的动态 。
利用我们的模型似乎可以很好地预测更高的 GPA 值这一事实,我们将限制我们对 GPA 为 3.1 或更高的申请人的预测。我们还将我们的预测限于 MCAT 分数为 492 或更高的申请人,以减少来自低 MCAT 申请人的噪音。
完整的模型
所有的部分都在这里。但是我们需要把它们放在一起,以创建一个无所不包的模型,根据你的 GPA 和 MCAT 分数,这个模型将给出(希望是准确的)你被美国医学院录取的预测。概括地说,让我们列出这样一个模型需要包括的内容:
给定一些固定的 GPA 值,模型需要为 sigmoidal,以改变 MCAT 分数的值。
给定一些固定的 GPA 值,这种 sigmoid 的性质(斜率、偏移量等。)需要随着 GPA 的变化而变化。
给定 MCAT 分数的某个固定值,对于变化的 GPA 值,模型需要(大致)线性。
该模式将仅限于平均绩点 3.1 或以上,MCAT 分数 492 或以上的申请者。
考虑到所有这些规则,并使用数学拟合技术来最小化我们的误差,我们得到了两个相互作用的变量 GPA 和 MCAT 的预测函数。给定我们的预测函数,我们现在可以从有效范围中选择任何一组 GPA 和 MCAT,并绘制相应的录取比率表面。
这里的 x 轴代表 GPA , y 轴代表 MCAT 分数 , z 轴代表录取率 。
结果表明,该模型的均方根误差(RMSE)约为 3.8% ,即平均而言,在所有 GPA 和 MCAT 值中,我们与录取率的真实值相差 3.8%。不算太坏!
那么一个未来的医学院学生如何使用这个呢?
所以我们有一个相当准确的模型来预测只给 GPA 和 MCAT 分数的录取率。一个正在努力优化自己医学院录取机会的学生如何使用这个工具?
假设你是一名在校学生,平均绩点为 3.4,MCAT 分数为 512。你的自然问题应该是“我应该花时间提高我的 MCAT 还是我的平均绩点?”毕竟,你一天只有 24 小时。
回答这个问题的一个自然的方法是关注给你最大录取率提升的那个。如果这似乎是一个奇怪的想法,假设你是一个处于更极端情况下的学生。你是一名平均绩点为 3.96,MCAT 分数为 485 的学生。当你的平均绩点已经很高的时候,把所有的空闲时间都花在考试学习上,试图提高你的平均绩点是没有意义的。考虑到你的 MCAT 分数低于中位数,如果你不努力的话,可能会严重阻碍你被录取的机会,这尤其没有意义。
所以,问题是,我们如何衡量平均绩点的提高和 MCAT 分数的提高带来的录取率提升?
我们可以很容易做到这一点,如果我们看看上面的表面,然后把它当作一种“山”。也就是说,我们的目标是在那座山上越爬越高,因为这意味着获得越来越高的录取机会。假设你站在那座山上的某个地方,想知道获得一定高度的最佳方法。你可以通过在 GPA 方向走几步看看你的录取率会发生什么,然后当你在 MCAT 方向走几步看看它会发生什么。
当我们说几步的时候,我们需要稍微小心一点。我们注意到的 GPA 范围从 0.0 到 4.0 (长度 4),而 MCAT 的范围从 472 到 528 (长度 56)。代替任何关于 GPA 增益与 MCAT 增益相对难度的详细信息,我们做出简化假设,将 GPA 中的 1 点增益与 MCAT 的 14 点增益相匹配(因为 56 除以 4 等于 14)。
我们在下面显示了 MCAT 和 GPA 的一些区间的预测录取比率值的表格,其中相对 GPA 和 MCAT 步长由上面讨论的大约为 14 的缩放因子确定。
使用这个表格,假设你的平均绩点是 3.4,MCAT 分数是 512。你最好的行动是什么?嗯,目前你的录取几率是 47.35% 。如果你把你的平均绩点提高一点,你被录取的几率会跃升至 55.6%,而把你的 MCAT 提高一点,你的录取几率只会上升至 50.25%。因此,你现在应该专注于你的平均绩点。事实上,这是有道理的,因为你的 MCAT 分数远高于 500 分的中位数,但你的平均绩点仍需要努力。
我们可以对表格中的每个单元格进行这种分析,并将该决定简化为一个 右箭头 、指示您应该提高您的 GPA ,或者一个 下箭头、指示您应该提高您的 MCAT 。
我们得到下面的结果。
现在假设我们是一个 GPA 为 3.4,MCAT 为 494 的学生。我们可以从初始状态开始跟随箭头开辟出一条通往成功的道路(越来越高的录取率)。
对于 GPA 和 MCAT 分数的任何组合,我们都可以这样做。只需从初始状态开始,跟随箭头找到通过 GPA-MCAT 空间的最佳路径。
概括一下,
我们使用原始录取统计数据来捕捉 GPA、MCAT 和录取率之间的趋势
我们使用这个预测模型,根据未来医学生目前的排名,为他们建议一个行动方案。
最后,我们展示了来自预测模型的更精细的数值表。你可能需要放大!。****
最后,请注意,如果你查看你的平均绩点和 MCAT,发现这个比例并不高,你就没必要紧张! *为什么?*因为首先,这是一个有一定误差的模型(3.8% RMSE) 。第二,这个没有考虑课外、性别、种族等其他录取标准。,同样有影响。因此,与其说这是一个终极预测,不如说是一个基准测试。 祝你好运!**
数据集引用
[1] 美国医学院申请人和被录取人的 MCAT 和 GPA 表格 (2017),美国医学院协会