Kaggle-之书-全-

Kaggle 之书(全)

原文:annas-archive.org/md5/ef46492e324ad2e4ffd771274a9405da

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

在 Kaggle 上竞争了超过十年,我们俩在许多竞赛中都经历了高潮和低谷。我们经常发现自己将精力重新集中在与 Kaggle 相关的不同活动上。随着时间的推移,我们不仅致力于竞赛,还致力于根据数据科学市场的需求和我们的职业抱负创建内容和代码。在我们旅行的这个阶段,我们觉得我们共同的经验和对竞赛的热情真的可以帮助那些刚刚开始或希望获得灵感的参与者,帮助他们掌握他们需要的核心专业知识,以便他们可以开始自己的数据科学竞赛之旅。

我们随后决定有目的地编写这本书:

  • 在一个地方提供成为竞争者并解决您在 Kaggle 和其他数据科学竞赛中可能遇到的大部分问题的最佳建议。

  • 提供足够的建议,使任何人在任何 Kaggle 学科(竞赛、数据集、笔记本或讨论)中至少达到专家级别。

  • 提供关于如何从 Kaggle 中学习最多,并利用这一经验在数据科学领域实现职业发展的建议。

  • 通过采访 Kaggle 大师和特级大师,收集关于参与竞赛经验的最多视角,并将他们的故事汇集在一个来源中。

简而言之,我们编写了一本展示如何成功参与竞赛并充分利用 Kaggle 提供的一切机会的书籍。本书还旨在作为一本实用的参考书,通过其精选的许多难以在互联网或 Kaggle 论坛上学习和找到的竞赛技巧和窍门,为您节省时间和精力。然而,本书不仅限于提供实际帮助;它还希望帮助您了解如何通过参与竞赛来提升您的数据科学职业生涯。

请注意:本书不会从基础知识教授数据科学。我们不详细解释线性回归、随机森林或梯度提升如何工作,而是如何以最佳方式使用它们,并在数据问题中获得最佳结果。我们期望读者具备扎实的基础和至少对数据科学主题和 Python 使用的基本熟练度。如果您仍然是数据科学初学者,您需要用其他关于数据科学、机器学习和深度学习的书籍来补充本书,并在在线课程上进行培训,例如 Kaggle 本身提供的课程或 edX 或 Coursera 等 MOOCs 提供的课程。

如果您想以实际的方式开始学习数据科学,如果想要挑战自己解决棘手和有趣的数据问题,并同时建立一个与您一样对数据科学工作充满热情的杰出数据科学家网络,这本书确实适合您。让我们开始吧!

本书面向的对象

在本书完成时,有 96,190 名 Kaggle 新手(刚刚在网站上注册的用户)和 67,666 名 Kaggle 贡献者(刚刚填写了个人资料的用户)参加了 Kaggle 竞赛。本书是为他们所有人以及任何想要打破僵局并开始参加 Kaggle 竞赛并从中学习的人所写的。

本书涵盖的内容

第一部分:竞赛简介

第一章介绍 Kaggle 和其他数据科学竞赛,讨论了竞技编程如何演变为数据科学竞赛。它解释了为什么 Kaggle 平台是这些竞赛最受欢迎的网站,并为您提供了关于其工作方式的想法。

第二章使用数据集组织数据,向您介绍 Kaggle 数据集,这是平台上的标准数据存储方法。我们讨论了设置、数据收集以及在 Kaggle 上的工作中的应用。

第三章使用 Kaggle 笔记本工作和学习,讨论了 Kaggle 笔记本,这是基准编码环境。我们谈论了笔记本使用的基础知识,以及如何利用 GCP 环境,以及如何使用它们来构建您的数据科学作品集。

第四章利用讨论论坛,使您熟悉 Kaggle 上的主要沟通和思想交流方式——讨论论坛。

第二部分:提高竞赛技能

第五章竞赛任务和指标,详细说明了某些类型问题的评估指标如何强烈影响您在数据科学竞赛中构建模型解决方案时的操作方式。本章还讨论了 Kaggle 竞赛中可用的各种指标。

第六章设计良好的验证方法,将向您介绍数据竞赛中验证的重要性,讨论过拟合、震荡、泄露、对抗验证、不同类型的验证策略以及最终提交的策略。

第七章表格竞赛建模,讨论了表格竞赛,主要关注 Kaggle 的最新现实——表格游乐场系列。表格问题是大多数数据科学家的标准实践,从 Kaggle 中可以学到很多东西。

第八章超参数优化,探讨了如何在 Kaggle 竞赛的压力和资源稀缺的情况下,如何将交叉验证方法扩展到寻找模型的最佳超参数——换句话说,那些在私有排行榜上以最佳方式泛化的参数。

第九章使用混合和堆叠解决方案进行集成学习,解释了针对多个模型(如平均、混合和堆叠)的集成技术。我们将为您提供一些理论、一些实践和一些代码示例,您可以在 Kaggle 上构建自己的解决方案时作为模板使用。

第十章计算机视觉建模,我们讨论了与计算机视觉相关的问题,这是人工智能中最受欢迎的主题之一,在 Kaggle 上尤其如此。我们展示了构建图像分类、目标检测和图像分割挑战解决方案的完整流程。

第十一章自然语言处理建模,专注于与自然语言处理相关的 Kaggle 挑战中经常遇到的问题类型。我们展示了如何构建针对开放域问答等流行问题的端到端解决方案。

第十二章模拟与优化竞赛,概述了模拟竞赛,这是一类在 Kaggle 上近年来逐渐流行起来的新型比赛。

第三部分:利用竞赛为你的职业生涯助力

第十三章创建你的项目和想法组合,探讨了通过在 Kaggle 本身和其他网站上以适当的方式展示你的工作来脱颖而出的方法。

第十四章寻找新的职业机会,通过讨论如何利用你在 Kaggle 上的所有经验来寻找新的职业机会,总结了 Kaggle 如何积极影响你的职业生涯。

为了充分利用本书

本书中的 Python 代码已被设计为在 Kaggle 笔记本上运行,无需在本地计算机上进行安装。因此,无需担心你有什么可用的机器或应该安装哪个版本的 Python 包。

你只需要一台可以访问互联网的电脑和一个免费的 Kaggle 账户。实际上,要在 Kaggle 笔记本上运行代码(你将在第三章中找到关于该过程的说明),你首先需要在 Kaggle 上开设一个账户。如果你还没有,只需访问www.kaggle.com并遵循网站上的说明即可。

我们在本书中链接到了许多我们认为你会觉得有用的不同资源。当被提及链接时,请探索它:你将找到可在公共 Kaggle 笔记本上重用的代码,或进一步的材料来阐述我们在书中讨论的概念和想法。

下载示例代码文件

本书代码包托管在 GitHub 上,网址为github.com/PacktPublishing/The-Kaggle-Book。我们还有其他来自我们丰富图书和视频目录的代码包,可在github.com/PacktPublishing/找到。查看它们吧!

下载彩色图像

我们还提供了一个包含本书中使用的截图/图表彩色图像的 PDF 文件。您可以从这里下载:static.packt-cdn.com/downloads/9781801817479_ColorImages.pdf

使用的约定

本书使用了一些文本约定。

CodeInText: 表示文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。例如:“数据集将被下载到Kaggle文件夹中,作为一个.zip存档 – 解压后即可使用。”

代码块设置如下:

from google.colab import drive
drive.mount('/content/gdrive') 

任何命令行输入或输出都如下所示:

I genuinely have no idea what the output of this sequence of words will be - it will be interesting to find out what nlpaug can do with this! 

粗体: 表示新术语、重要词汇或您在屏幕上看到的词汇,例如在菜单或对话框中。例如:“写作时的具体限制是每个私有数据集100 GB,以及100 GB的总配额。”

进一步的说明、参考资料和链接到有用的地方如下所示。

技巧和窍门如下所示。

联系我们

我们欢迎读者的反馈。

一般反馈: 请发送电子邮件至feedback@packtpub.com,并在邮件主题中提及书籍标题。如果您对本书的任何方面有疑问,请通过questions@packtpub.com发送电子邮件给我们。

勘误: 尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,如果您能向我们报告,我们将不胜感激。请访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入详细信息。

盗版: 如果您在互联网上以任何形式发现我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过copyright@packtpub.com与我们联系,并提供材料的链接。

如果您有兴趣成为作者: 如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com

分享您的想法

一旦您阅读了《Kaggle 书籍》,我们非常希望听到您的想法!请点击此处直接访问此书的亚马逊评论页面并分享您的反馈。

您的评论对我们和科技社区都至关重要,并将帮助我们确保我们提供高质量的内容。

第一部分

竞赛简介

第一章:介绍 Kaggle 和其他数据科学竞赛

数据科学竞赛已经存在很长时间了,随着时间的推移,它们经历了不断增长的成功,从一个小众的充满热情的竞争者社区开始,吸引了越来越多的关注,并达到了数百万数据科学家的更大受众。作为最受欢迎的数据科学竞赛平台 Kaggle 上的长期竞争者,我们多年来亲眼目睹并直接体验了所有这些变化。

目前,如果你在寻找有关 Kaggle 和其他竞赛平台的信息,你很容易找到大量的聚会、讨论小组、播客、访谈,甚至在线课程,这些课程解释了如何在这样的竞赛中获胜(通常建议你使用不同比例的毅力、计算资源和投入的时间)。然而,除了你现在正在阅读的这本书之外,你不会找到任何关于如何导航这么多数据科学竞赛以及如何从中获得最大利益的系统指南——不仅是在分数或排名方面,还包括专业经验。

在这本书中,我们不仅仅是要打包一些关于如何在 Kaggle 和其他数据科学竞赛中获胜或获得高分的提示,我们的意图是为你提供一份如何在 Kaggle 上更好地竞争并获得竞赛体验最大可能的指南,尤其是从你职业生活的角度。此外,书中还附有 Kaggle 大师和特级大师的访谈。我们希望他们能为你提供一些关于在 Kaggle 上竞争的具体方面的不同视角和见解,并激励你以测试自己和学习竞技数据科学的方式。

到这本书结束时,你将吸收我们从自己的经验、资源和竞赛中学到的知识,以及你需要为自己铺路学习和成长的一切,一场比赛接着一场比赛。

作为起点,在本章中,我们将探讨竞技编程如何演变为数据科学竞赛,为什么 Kaggle 平台是此类竞赛最受欢迎的网站,以及它是如何运作的。

我们将涵盖以下主题:

  • 数据科学竞赛平台兴起

  • 常见任务框架范式

  • Kaggle 平台和一些其他替代方案

  • Kaggle 竞赛的工作方式:阶段、竞赛类型、提交和排行榜动态、计算资源、网络等

数据科学竞赛平台兴起

竞技编程有着悠久的历史,始于 20 世纪 70 年代,当时是国际大学生程序设计竞赛ICPC)的第一轮。在最初的 ICPC 中,来自大学和公司的小型团队参加了一项比赛,要求使用计算机程序解决一系列问题(最初,参与者使用 FORTRAN 编程)。为了获得良好的最终排名,团队必须在团队合作、问题解决和编程方面展示出色的技能。

参加这样竞赛的热烈体验和站在招聘公司聚光灯下的机会为学生提供了充足的动力,这使得竞赛多年以来一直很受欢迎。在 ICPC 决赛中,有几位选手成为了知名人士:有 Facebook 前首席技术官和 Quora 创始人亚当·达格诺(Adam D’Angelo),Telegram Messenger 的联合创始人尼古拉伊·杜罗夫(Nikolai Durov),以及 Apache Spark 的创造者马泰伊·扎哈里亚(Matei Zaharia)。他们与其他许多专业人士一样,都分享了同样的经历:参加过 ICPC。

在 ICPC 之后,编程竞赛蓬勃发展,尤其是在 2000 年之后,远程参与变得更加可行,使得国际竞赛更容易进行且成本更低。大多数这些竞赛的格式相似:有一系列问题,你必须编写代码来解决它们。获胜者会获得奖品,同时也会让招聘公司或简单地成名。

通常,竞技编程中的问题范围从组合数学和数论到图论、算法博弈论、计算几何、字符串分析和数据结构。近年来,与人工智能相关的问题成功出现,特别是在KDD 杯(知识发现与数据挖掘竞赛)启动之后,该竞赛由计算机协会ACM)的特别兴趣小组SIG)在其年度会议期间举办(kdd.org/conferences)。

首届 KDD 杯比赛于 1997 年举行,涉及电梯曲线优化的直接营销问题,这开启了一系列持续至今的竞赛。您可以在www.kdd.org/kdd-cup找到包含数据集、说明和获奖者的存档。以下是撰写本文时的最新资料:ogb.stanford.edu/kddcup2021/。KDD 杯在建立最佳实践方面证明非常有效,许多发表的论文描述了解决方案、技术和竞赛数据集共享,这些都对许多从业者进行实验、教育和基准测试非常有用。

竞赛编程事件和 KDD 杯的成功案例激发了公司(如 Netflix)和企业家(如 Kaggle 的创始人安东尼·戈德布卢姆)创建第一个数据科学竞赛平台,公司可以在这些平台上举办难以解决的数据科学挑战,并可能从众包中受益。事实上,鉴于没有适用于数据科学中所有问题的黄金方法,许多问题需要耗时的方法,可以总结为尝试所有你能尝试的方法

事实上,从长远来看,没有任何算法能在所有问题上击败其他所有算法,正如大卫·沃尔珀特(David Wolpert)和威廉·麦克雷迪(William Macready)提出的“无免费午餐”定理所述。该定理告诉你,每个机器学习算法只有在它的假设空间包含解决方案时才会表现良好。因此,由于你事先无法知道某个机器学习算法是否最适合解决你的问题,你必须尝试它,直接在你问题上进行测试,以确保你正在做正确的事情。在机器学习中没有理论上的捷径或其他圣杯——只有经验实验才能告诉你什么有效。

更多细节,你可以查阅“无免费午餐”定理,以了解这一实际真理的理论解释。以下是 Analytics India Magazine 关于该主题的完整文章:analyticsindiamag.com/what-are-the-no-free-lunch-theorems-in-data-science/

在需要广泛测试算法和数据转换以找到最佳可能组合,但缺乏人力和计算机能力的情况下,众包证明是理想的。这就是为什么政府和企业会求助于比赛,以便在某些领域取得进展:

  • 在政府方面,我们可以引用 DARPA 及其围绕自动驾驶汽车、机器人操作、机器翻译、说话人识别、指纹识别、信息检索、OCR、自动目标识别等许多其他领域的众多比赛。

  • 在商业方面,我们可以引用 Netflix 这样的公司,它将比赛的成果委托给改进其预测用户电影选择的算法。

Netflix 比赛基于改进现有协同过滤的想法。其目的仅仅是根据用户对其他电影的评分来预测用户对电影的潜在评分,而不需要具体知道用户是谁或电影是什么。由于没有用户描述或电影标题或描述可用(所有这些都用身份代码代替),比赛要求参赛者开发智能方法来使用可用的过去评分。只有当解决方案能够将现有的 Netflix 算法 Cinematch 提升到一定阈值以上时,才会颁发 100 万美元的巨额奖金。

该比赛从 2006 年持续到 2009 年,最终获胜的是由多个先前比赛团队融合而成的团队:来自 Commendo Research & Consulting GmbH 的团队,包括安德烈亚斯·托舍尔迈克尔·耶雷尔,他们在 Kaggle 比赛中也相当知名;来自 AT&T 实验室的两名研究人员;以及来自 Yahoo!的两名研究人员。最终,赢得比赛需要如此多的计算能力和不同解决方案的集成,以至于团队不得不合并以保持竞争力。这种情况也反映在 Netflix 实际使用该解决方案上,Netflix 更愿意不实施它,而是简单地从中提取最有趣的见解来改进其现有的 Cinematch 算法。您可以在这篇 Wired 文章中了解更多信息:www.wired.com/2012/04/netflix-prize-costs/.

在 Netflix 比赛的最后,重要的是解决方案本身,因为 Netflix 的业务重点从 DVD 转向在线电影,该解决方案很快就被取代了。对于在协同过滤中获得巨大声誉的参与者,以及能够将其改进的推荐知识转移到新业务的该公司来说,真正的益处是从比赛中获得的见解。

Kaggle 比赛平台

除了 Netflix 之外,其他公司也从数据科学比赛中受益。名单很长,但我们可以引用一些例子,其中举办比赛的公司的报告显示从中获得了明确的好处。例如:

  • 保险公司 Allstate 能够通过涉及数百名数据科学家的比赛改进他们自己专家构建的精算模型(www.kaggle.com/c/ClaimPredictionChallenge)。

  • 作为另一个有详细记录的例子,通用电气通过类似的比赛(www.kaggle.com/c/flight)将预测航空公司航班到达时间的行业标准性能(通过均方根误差指标衡量)提高了 40%。

到目前为止,Kaggle 比赛平台已经举办了数百场比赛,这两个只是公司成功使用 Kaggle 的几个例子。让我们暂时从具体的比赛中退一步,谈谈 Kaggle 公司,这是贯穿本书的线索。

Kaggle 的历史

Kaggle 的起步是在 2010 年 2 月,归功于安东尼·戈德布卢姆,一位受过经济学和计量经济学学位训练的澳大利亚经济学家。在澳大利亚财政部和澳大利亚储备银行的研究部门工作后,戈德布卢姆在伦敦的《经济学人》国际时事、国际商业、政治和技术周刊实习。在《经济学人》杂志,他有机会撰写一篇关于大数据的文章,这激发了他建立一个竞赛平台的想法,该平台可以众包最佳分析专家来解决有趣的机器学习问题(www.smh.com.au/technology/from-bondi-to-the-big-bucks-the-28yearold-whos-making-data-science-a-sport-20111104-1myq1.html)。由于众包动态在这个平台商业理念中发挥了重要作用,他创造了名字Kaggle,这个名称通过押韵回忆了gaggle这个词,意为一群鹅,鹅也是该平台的象征。

在搬到美国硅谷后,他的 Kaggle 初创公司从知名风险投资公司 Khosla Ventures 和 Index Ventures 的一轮投资中获得了 1125 万美元的 A 轮融资。推出了第一批竞赛,社区规模扩大,一些最初的竞争者变得相当突出,例如澳大利亚数据科学家和企业家杰里米·豪沃德,他在 Kaggle 上赢得了几场比赛后,成为了公司的总裁和首席科学家。

杰里米·豪沃德在 2013 年 12 月辞去了总裁的职位,并成立了一家新的初创公司fast.aiwww.fast.ai),为程序员提供机器学习课程和深度学习库。

在当时,还有一些其他杰出的 Kagglers(这个名字表明了经常参加 Kaggle 举办的竞赛的参与者),例如杰里米·阿钦托马斯·德·戈多伊。在达到平台上的全球前 20 名排名后,他们迅速决定退休并创立了自己的公司,DataRobot。不久之后,他们开始从 Kaggle 竞赛中的最佳参与者中招聘员工,以便将最好的机器学习知识和实践融入到他们正在开发的软件中。如今,DataRobot 是开发 AutoML 解决方案(自动机器学习软件)的领先公司之一。

Kaggle 竞赛吸引了越来越多的观众的关注。即使是深度学习的“教父”杰弗里·辛顿,也在 2012 年参加了由默克公司主办的 Kaggle 竞赛(www.kaggle.com/c/MerckActivity/overview/winners),并赢得了比赛。Kaggle 也是弗朗索瓦·肖莱特奥托集团产品分类挑战赛www.kaggle.com/c/otto-group-product-classification-challenge/discussion/13632)期间推出他的深度学习包 Keras,以及田奇希格斯玻色子机器学习挑战赛www.kaggle.com/c/higgs-boson/discussion/10335)中推出 XGBoost,这是一种更快、更精确的梯度提升机版本的平台。

除了 Keras,弗朗索瓦·肖莱特还在 Quora 网站上提供了一份关于如何在 Kaggle 竞赛中获胜的最有用和最有洞察力的观点:www.quora.com/Why-has-Keras-been-so-successful-lately-at-Kaggle-competitions

通过多次尝试的快速迭代,以经验(而非理论)证据为指导,实际上这就是你所需要的全部。我们认为,赢得 Kaggle 竞赛的秘密并不比他在回答中指出的更多。

值得注意的是,弗朗索瓦·肖莱特还在 Kaggle 上举办了属于自己的竞赛(www.kaggle.com/c/abstraction-and-reasoning-challenge/),这被广泛认为是世界上第一个通用人工智能竞赛。

一场又一场的比赛,围绕 Kaggle 的社区在 2017 年增长到一百万,同年,在谷歌 Next 大会的演讲中,谷歌首席科学家李飞飞宣布谷歌 Alphabet 将收购 Kaggle。从那时起,Kaggle 成为了谷歌的一部分。

今天,Kaggle 社区仍然活跃并持续增长。在他的推文中(twitter.com/antgoldbloom/status/1400119591246852096),安东尼·戈德布卢姆报告说,除了参加比赛外,大多数用户还下载了公共数据(Kaggle 已成为一个重要的数据枢纽),在 Python 或 R 中创建了一个公共 Notebook,或在提供的课程中学习了一些新知识:

图 1.1:一个条形图显示了用户在 2020 年、2019 年和 2018 年使用 Kaggle 的情况

几年来,Kaggle 为其参与者提供了更多的机会,例如:

最重要的是,学习更多关于数据科学中涉及到的技能和技术细节。

其他竞赛平台

虽然这本书主要关注 Kaggle 上的竞赛,但我们不能忘记,许多数据竞赛是在私人平台或其他竞赛平台上举行的。实际上,你在这本书中找到的大部分信息也适用于其他竞赛,因为它们本质上都遵循相似的原则,参与者的好处也或多或少相同。

尽管许多其他平台仅限于特定国家或仅针对某些类型的竞赛进行本地化,为了完整性,我们将简要介绍其中一些,至少是我们有一些经验和知识的一些:

其他较小的平台包括来自瑞士洛桑联邦理工学院的 CrowdAI (www.crowdai.org/),InnoCentive (www.innocentive.com/),Grand-Challenge (grand-challenge.org/) 生物医学成像,DataFountain (www.datafountain.cn/business?lang=en-US),OpenML (www.openml.org/),等等。你总能在俄罗斯社区 Open Data Science (ods.ai/competitions) 找到大量正在进行的主要竞赛,甚至不时发现新的竞赛平台。

你可以在 mlcontests.com 网站上看到正在进行的竞赛概述,以及租用 GPU 的当前费用。该网站经常更新,是快速了解不同平台上的数据科学竞赛动态的便捷方式。

Kaggle 始终是你可以找到最有趣竞赛并获得最广泛认可的平台。然而,在它之外接受挑战是有意义的,我们建议当你找到一个符合你个人和职业兴趣的竞赛时,将其作为一种策略。正如你所看到的,除了 Kaggle 之外,还有很多替代方案和机会,这意味着如果你考虑更多的竞赛平台与 Kaggle 一起,你可以更容易地找到一个可能因为其专业或数据而引起你兴趣的竞赛。

此外,你可以在这些挑战中期待较少的竞争压力(因此排名更好,甚至可能赢得一些东西),因为它们不太为人所知且宣传较少。只需预期参与者之间的分享较少,因为没有任何其他竞赛平台像 Kaggle 一样拥有如此丰富的分享和网络机会。

介绍 Kaggle

到目前为止,我们需要更深入地了解 Kaggle 是如何运作的。在接下来的段落中,我们将讨论 Kaggle 平台及其竞赛的各个方面,你将了解在 Kaggle 竞赛中的体验。之后,我们将在本书的剩余章节中更详细地讨论许多这些话题,并提供更多建议和策略。

竞赛的各个阶段

Kaggle 上的竞赛被安排成不同的步骤。通过查看每个步骤,你可以更好地理解数据科学竞赛是如何运作的,以及你可以期待什么。

当竞赛启动时,社交媒体上通常会有一些帖子,例如在 Kaggle 的 Twitter 个人资料上,twitter.com/kaggle,宣布它,并且在竞赛页面上的 Kaggle 部分会出现一个新的标签页关于活跃竞赛www.kaggle.com/competitions)。如果你点击某个特定竞赛的标签页,你将被带到其页面。一眼就能看出,你可以检查竞赛是否有奖品(以及它是否颁发分数和奖牌,这是参与竞赛的次要后果),目前有多少团队参与,以及你还有多少时间来工作解决方案:

图片

图 1.2:Kaggle 上的竞赛页面

在那里,你可以首先探索概览菜单,它提供了以下信息:

  • 竞赛的主题

  • 其评估指标(你的模型将根据此进行评估)

  • 竞赛的日程安排

  • 奖项

  • 法律或竞争要求

通常时间表会被忽视,但它应该是您首先检查的事项之一;它不仅告诉您比赛开始和结束的时间,而且还会提供规则接受截止日期,这通常是在比赛结束前七天到两周。规则接受截止日期是您可以加入比赛的最后一天(通过接受其规则)。还有团队合并截止日期:您可以在该截止日期之前安排与另一位竞争对手的团队合并,但在此之后将不可能合并。

规则菜单也经常被忽视(人们只是跳到数据),但检查它很重要,因为它可以告诉您比赛的各项要求。从规则中您可以获得的关键信息包括:

  • 您是否有资格获奖

  • 您是否可以使用外部数据来提高您的分数

  • 您每天可以提交多少次(对您的解决方案的测试)

  • 您可以选择多少个最终解决方案

一旦您接受了规则,您可以从数据菜单下载任何数据,或者直接从代码菜单开始工作在Kaggle 笔记本(在线、基于云的笔记本),重用他人提供的代码或从头开始创建自己的代码。

如果您决定下载数据,也要考虑您有一个Kaggle API,它可以帮助您以几乎自动化的方式运行下载和提交。如果您在本地计算机或云实例上运行模型,这是一个重要的工具。您可以在www.kaggle.com/docs/api找到更多关于 API 的详细信息,您可以从 GitHubgithub.com/Kaggle/kaggle-api获取代码。

如果您仔细检查 Kaggle GitHub 仓库,您还可以找到他们用于在线笔记本的 Docker 镜像,Kaggle 笔记本:

图片

图 1.3:一个准备编码的 Kaggle 笔记本

在这个阶段,随着您开发解决方案,我们热切建议您不要独自一人继续,而是通过讨论论坛联系其他竞争对手,在那里您可以提出和回答与比赛具体相关的问题。通常您还会找到关于数据特定问题的有用提示,甚至有助于改进您自己解决方案的想法。许多成功的 Kagglers 报告说,在论坛上找到的想法帮助他们表现更好,更重要的是,他们更多地了解了数据科学中的建模。

一旦您的解决方案准备就绪,您可以根据比赛的规格将其提交给 Kaggle 评估引擎。一些比赛将接受 CSV 文件作为解决方案,而另一些比赛则要求您在 Kaggle 笔记本中编写代码并生成结果。您可以在整个比赛期间提交解决方案。

每次你提交一个解决方案后,排行榜会很快为你提供一个得分和你在竞争对手中的位置(等待时间取决于评分评估所需的计算)。这个位置只是一个大致的指示,因为它反映了你的模型在测试集的一部分,即公共测试集上的表现,因为你在竞赛期间的表现是公开的,以便让每个人都知道。

在竞赛关闭之前,每位参赛者可以选择他们用于最终评估的解决方案数量(通常是两个)。

图片

图 1.4:一个展示数据如何转化为公共和私人排行榜得分的图表

只有当竞赛关闭时,基于参赛者决定评分的模型,他们的得分才会被揭示在测试集的另一部分,称为私人测试集。这个新的排行榜,即私人排行榜,构成了竞赛的最终、有效得分,但其在排名上仍然不是官方和最终的。事实上,Kaggle 团队将花一些时间来检查一切是否正确,以及所有参赛者是否都遵守了竞赛规则。

经过一段时间(有时由于取消资格而导致排名变化),私人排行榜将变为官方和最终版,获胜者将被宣布,许多参与者将在竞赛讨论论坛上公布他们的策略、解决方案和代码。此时,取决于你检查其他解决方案并尝试改进自己的方案。我们强烈建议你这样做,因为这是 Kaggle 中另一个重要的学习来源。

竞赛类型及示例

Kaggle 竞赛根据竞赛类别进行分类,每个类别在如何竞争以及预期内容方面都有不同的含义。类别内部的数据类型、问题难度、奖金以及竞赛动态都相当多样,因此事先了解每个类别的含义非常重要。

这里是你可以用来筛选不同竞赛的官方类别:

  • 特色

  • 大师级

  • 年度竞赛

  • 研究

  • 招聘

  • 入门

  • 操场

  • 分析学

  • 社区

特色竞赛是最常见的一种类型,涉及赞助公司提出的与商业相关的问题以及给顶尖表现者的奖金。获胜者将授予其作品给赞助公司的非独家许可;他们必须准备一份关于其解决方案的详细报告,有时甚至需要参加与赞助公司的会议。

每次访问 Kaggle 时,都会有一些特色竞赛的例子。目前,许多竞赛都与将深度学习方法应用于非结构化数据(如文本、图像、视频或声音)的应用相关。在过去,表格数据竞赛很常见,即基于可以在数据库中找到的结构的化数据问题的竞赛。最初是通过随机森林,然后是使用巧妙特征工程的梯度提升方法,从 Kaggle 得出的表格数据解决方案确实可以改善现有的解决方案。如今,这些竞赛举办得越来越少,因为众包解决方案通常不会比一支优秀的数据科学家团队或甚至 AutoML 软件做得更好。鉴于更好软件和良好实践的普及,竞赛结果质量的提高确实微乎其微。然而,在非结构化数据领域,一个好的深度学习解决方案仍然可以产生重大影响。例如,预训练网络如 BERT 为许多著名的 NLP 任务基准带来了两位数的标准提升。

大师级竞赛现在较少见,但它们是私人的、仅限邀请的竞赛。其目的是为专家(通常是根据 Kaggle 奖牌排名被评为大师或特级大师的竞争者)创建仅针对他们的竞赛,基于他们在 Kaggle 上的排名。

年度竞赛是一年中的特定时期内总会出现的竞赛。在年度竞赛中,我们有圣诞老人竞赛(通常基于算法优化问题)和三月机器学习狂热竞赛,自 2014 年以来每年在美国大学篮球锦标赛期间举行。

研究竞赛意味着具有研究或科学目的,而不是商业目的,有时是为了服务于公共利益。这就是为什么这些竞赛并不总是提供奖品。此外,这些竞赛有时要求获胜者将他们的解决方案作为开源发布。

Google 过去发布过一些研究竞赛,例如Google 地标识别 2020 (www.kaggle.com/c/landmark-recognition-2020),其目标是标记图像中的著名(和不那么著名)地标。

想要测试潜在求职者能力的赞助商举办招聘竞赛。这些竞赛仅限于单人团队,并为排名靠前的竞争者提供与赞助商面试的机会作为奖品。如果竞争者希望在竞赛结束后被考虑联系,他们必须在竞赛结束时上传他们的简历。

招聘竞赛的例子包括:

入门比赛不提供任何奖品,但提供友好且简单的题目,让初学者熟悉 Kaggle 的原则和动态。它们通常是半永久性的比赛,其排行榜会不时更新。如果您正在寻找机器学习的教程,这些比赛是开始的好地方,因为您可以在一个高度协作的环境中找到许多 Kaggle 笔记本,这些笔记本展示了如何处理数据并创建不同类型的机器学习模型。

著名的正在进行中的入门比赛包括:

Playground比赛比入门比赛稍微难一些,但它们也是为了让参赛者学习并测试他们的能力,而不受完整 Featured 比赛的压力(尽管在 Playground 比赛中,竞争的激烈程度有时也可能相当高)。此类比赛的常规奖品只是周边产品(“我们都能得到的东西”的缩写,例如,例如,一个杯子、一件 T 恤或 Kaggle 品牌的袜子;参见www.kaggle.com/general/68961)或一点钱。

一个著名的 Playground 比赛是原始的Dogs vs. Cats比赛(www.kaggle.com/c/dogs-vs-cats),任务是为区分狗和猫创建一个算法。

应当提及数据分析比赛,其中评估是定性的,参与者需要提供想法、解决方案草案、PowerPoint 幻灯片、图表等;以及社区(之前称为 InClass)比赛,这些比赛由学术机构和 Kagglers 举办。您可以在www.kaggle.com/product-feedback/294337上了解社区比赛的启动情况,您可以在www.kaggle.com/c/about/hostwww.kaggle.com/community-competitions-setup-guide上获得举办自己比赛的建议。

Parul Pandey

www.kaggle.com/parulpandey

我们采访了 Parul Pandey,Kaggle 笔记本大师、数据集大师以及 H2O.ai 的数据科学家,她分享了她在数据分析比赛中的经验和更多内容。

您最喜欢的竞赛类型是什么?为什么?在技术和解决方法方面,您在 Kaggle 上的专长是什么?

我真的很喜欢数据分析竞赛,这些竞赛要求您分析数据,并在最后提供一个全面的分析报告。这些包括数据科学竞赛(DS4G)、体育数据分析竞赛(NFL 等)以及一般的调查挑战。与传统竞赛不同,这些竞赛没有排行榜来跟踪您与其他人的表现;您也不会得到任何奖牌或分数。

另一方面,这些竞赛要求提供端到端的解决方案,涉及数据科学的多个方面,如数据清洗、数据挖掘、可视化以及传达见解。这类问题提供了一种模拟现实场景的方式,让您提供自己的见解和观点。可能没有唯一的最佳答案来解决这些问题,但它给了您深思熟虑和权衡潜在解决方案的机会,并将它们融入您的解决方案中。

您是如何处理 Kaggle 竞赛的?这种处理方式与您日常工作的方式有何不同?

我的第一步始终是分析数据作为 EDA(探索性数据分析)的一部分。这也是我日常工作流程中遵循的事情。通常,我会探索数据以寻找潜在的红旗,如数据的不一致性、缺失值、异常值等,这些都可能在以后造成问题。下一步是创建一个良好且可靠的交叉验证策略。然后我会阅读讨论论坛,并查看一些人们分享的 Notebooks。这通常是一个良好的起点,然后我可以从过去的经验中在这个工作流程中融入一些东西。跟踪模型性能也是至关重要的。

然而,对于数据分析竞赛,我喜欢将问题分解成多个步骤。例如,第一步可能是理解问题,这可能需要几天时间。之后,我喜欢探索数据,然后创建一个基本的基线解决方案。然后我通过逐步添加来增强这个解决方案。这可能与逐步添加乐高积木以创建最终杰作相似。

告诉我们您参加的一个特别具有挑战性的竞赛,以及您使用了哪些见解来应对这项任务。

正如我之前提到的,我大多数时候喜欢参加数据分析竞赛,尽管偶尔也会尝试参加常规的竞赛。我想特别指出一个非常有吸引力的数据科学竞赛,名为“环境洞察探索者”(https://www.kaggle.com/c/ds4g-environmental-insights-explorer)。这项任务要求使用遥感技术来理解环境排放,而不是从当前的方法中计算排放因子。

真正让我印象深刻的是用例。我们的地球正在应对气候变化问题,这个竞赛触及了这一方面。在我为竞赛进行研究时,我惊讶地发现在这个卫星图像领域取得的进展,这给了我一个机会去理解和更深入地研究这个话题。它给了我一个机会去了解像 Landsat、Modis 和 Sentinel 这样的卫星是如何工作的,以及它们如何使卫星数据可用。这是一个很好的竞赛,让我了解了一个我之前几乎一无所知的话题。

在你的经验中,不经验的 Kagglers 经常忽略什么?你现在知道什么,而你在最初开始时希望知道的?

我将列举一些我在 Kaggle 初期年份中犯的错误。

首先,大多数新手认为 Kaggle 只是一个竞赛平台。如果你喜欢竞赛,这里有很多,但 Kaggle 也为其他专业的人士提供了东西。你可以编写代码并与他人分享,参与健康的讨论,并建立人脉。为社区整理和分享好的数据集。我最初只使用 Kaggle 下载数据集,直到几年前我才真正活跃起来。现在当我回顾过去,我发现自己当初是多么的错误。很多人被竞赛吓到了。你可以先熟悉平台,然后逐渐开始参与竞赛。

我想提到的另一件重要的事情是,许多人独自工作,失去动力,并最终放弃。在 Kaggle 上组队有许多看不到的优势。它教会你团队合作,从经验中学习,并在有限的时间内共同追求一个目标。

你使用其他竞赛平台吗?它们与 Kaggle 相比如何?

虽然 我现在大部分时间都花在 Kaggle 上,但在过去,我曾使用过 Zindi,这是一个专注于非洲用例的数据科学竞赛平台。这是一个获取专注于非洲的数据集的好地方。Kaggle 是一个多功能的平台,但来自世界各地的竞赛题目相对较少。最近,我们也看到了一些多样化的题目,比如最近举办的 chaii 竞赛——这是一个专注于印度语言的 NLP 竞赛。我相信类似专注于不同国家的竞赛将对研究和整个数据科学社区都有帮助

在 Kaggle 竞赛的分类中,你还得考虑竞赛可能具有不同的格式。通常的格式是所谓的简单格式,其中你提供解决方案,然后按照我们之前描述的方式进行评估。更复杂的是两阶段竞赛,它将比赛分为两个部分,并且只有在第一阶段完成后,才会向第一阶段参与者发布最终数据集。两阶段竞赛格式出现是为了限制某些竞争者作弊和违反规则的机会,因为评估是在一个完全未尝试的测试集上进行的,而这个测试集只短暂可用。与原始的 Kaggle 竞赛格式相反,在这种情况下,竞争者有更短的时间,更少的提交来从测试集中找出任何有用的模式。

由于同样的原因,最近出现了代码竞赛,在这种竞赛中,所有提交都是通过 Kaggle 笔记本完成的,并且禁止直接上传提交。

对于处于不同竞赛阶段的 Kagglers,参与任何类型的竞赛都没有限制。然而,根据你在数据科学方面的经验水平和计算资源,我们有一些关于竞赛格式或类型的建议:

  • 对于完全的初学者来说,入门游乐场竞赛是开始的好地方,因为你可以轻松地了解更多关于 Kaggle 如何工作,而不必面对高竞争压力。话虽如此,许多初学者都是从特色和研究竞赛开始的,因为压力帮助他们更快地学习。因此,我们的建议是根据你的学习风格来决定:一些 Kagglers 需要通过探索和协作来学习(入门或游乐场竞赛非常适合这种情况),而其他人则需要快速竞争的热度来找到他们的动力。

  • 对于特色研究竞赛,也要考虑到这些竞赛通常涉及人工智能和机器学习的边缘应用,因此,你通常需要扎实的背景知识或愿意研究该竞赛应用领域的所有相关研究。

最后,请记住,大多数竞赛要求你能够访问通常在工作场所中大多数数据科学家都无法获得的计算资源。如果你使用 Kaggle 以外的云平台,这可能会变成不断增长的支出。因此,代码竞赛和有时间或资源限制的竞赛可能就是投入你努力的理想场所,因为它们努力将所有参与者置于相同的资源水平。

提交和排行榜动态

Kaggle 的工作方式看似简单:测试集对参与者保密;你拟合你的模型;如果你的模型在测试集上的预测最佳,那么你的得分会很高,你可能会赢得比赛。不幸的是,这种描述过于简化了 Kaggle 竞赛的内部运作。它没有考虑到竞争者之间直接和间接的互动动态,或者你所面临的问题及其训练集和测试集的细微差别。

解释共同任务框架范式

对 Kaggle 工作原理的更全面描述实际上是由斯坦福大学统计学教授大卫·多诺霍web.stanford.edu/dept/statistics/cgi-bin/donoho/)在其论文《数据科学 50 年》中给出的。该论文最初发表在《计算与图形统计杂志》上,随后被发布在麻省理工学院计算机科学与人工智能实验室(courses.csail.mit.edu/18.337/2015/docs/50YearsDataScience.pdf)。

多诺霍教授并没有具体提到 Kaggle,而是提到了所有数据科学竞赛平台。引用计算语言学家马克·利伯曼的话,他将数据科学竞赛和平台视为共同任务框架CTF)范式的一部分,这种范式在过去几十年中默默而稳步地推进了许多领域的科学数据。他指出,CTF 可以从经验角度非常有效地改善数据科学问题的解决方案,引用 Netflix 竞赛和许多 DARPA 竞赛作为成功的例子。CTF 范式对许多领域问题的最佳解决方案进行了重塑。

CTF 由成分秘密配方组成。成分很简单:

  1. 一个公开可用的数据集和相关的预测任务

  2. 一组共同承担着为任务提供最佳预测的预测任务的竞争者

  3. 一个系统,以公平和客观的方式对参与者的预测进行评分,同时不提供过于具体的解决方案提示(或者至少限制它们)

如果任务定义得很好且数据质量良好,系统工作得最好。从长远来看,解决方案的性能通过小的增益逐渐提高,直到达到一个渐近线。通过允许参与者之间进行一定程度的共享(如在 Kaggle 上通过讨论和共享 Kaggle Notebooks 以及数据集部分中找到的额外数据)可以加快这个过程。根据 CTF 范式,竞赛中的竞争压力足以产生不断改进的解决方案。当竞争压力与参与者之间的一定程度的共享相结合时,改进的速度会更快——这就是为什么 Kaggle 引入了许多共享激励措施的原因。

这是因为在 CTF 范式中的秘密配方就是竞赛本身,在解决一个需要提高经验性能的实际问题的框架内,它总是导致新的基准、新的数据和建模解决方案的出现,总的来说,是提高了机器学习在竞赛提出的问题上的应用。因此,一个竞赛可以提供一种解决预测问题的新方法,新的特征工程方法,以及新的算法或建模解决方案。例如,深度学习并非仅仅源于学术研究,它首先因为成功的竞赛而获得了巨大的推动力,这些竞赛标志着其有效性(例如,我们已经提到了杰弗里·辛顿的团队赢得的默克竞赛:www.kaggle.com/c/MerckActivity/overview/winners)。

结合开放软件运动,它允许每个人都能访问强大的分析工具(如 Scikit-learn、TensorFlow 或 PyTorch),CTF 范式带来了更好的结果,因为所有竞争者从开始就处于同一水平。另一方面,竞赛解决方案对专用或改进硬件的依赖可能会限制可达到的结果,因为它可能阻止无法访问这些资源的竞争者正确参与并直接贡献解决方案,或者通过在其他参与者身上施加竞争压力间接贡献。可以理解的是,这就是为什么 Kaggle 开始向其竞赛的参与者免费提供云服务的原因,我们将在计算资源部分介绍 Kaggle Notebooks。它可以平息一些硬件密集型竞赛(如大多数深度学习竞赛)中的某些差异,并增加整体竞争压力。

理解竞赛中可能出错的地方

根据我们之前对 CTF 范式的描述,你可能倾向于想象,所有比赛需要的只是在一个合适的平台上设置,并且好的结果,如参与者积极的参与和赞助公司出色的模型,将自动出现。然而,也有一些事情可能会出错,并可能导致比赛结果令人失望,无论是对于参与者还是组织该比赛的机构:

  • 数据泄露

  • 从排行榜(评分系统)进行探测

  • 过度拟合和随之而来的排行榜动荡

  • 私有共享

当解决方案的一部分可以在数据本身中追踪到时,你就有数据泄露。例如,某些变量可能是目标变量的后继变量,因此它们揭示了关于它的某些信息。这在欺诈检测中发生,当你使用在欺诈发生后更新的变量时,或者在销售预测中处理与产品有效分布相关的信息时(更多的分布意味着对产品的更多请求,因此更多的销售)。

另一个问题可能是训练和测试示例以可预测的方式排序,或者示例标识符的值暗示了解决方案。例如,当标识符基于目标的排序时,或者标识符值与时间的流动相关,而时间影响目标发生的概率。

这种解决方案泄露,有时被竞争对手称为金特征(因为数据中这样的细微差别可能会变成参与者的金奖),不可避免地导致一个不可重复使用的解决方案。这也意味着对于赞助商来说,结果可能不是最优的,但他们至少能够了解一些可能影响他们问题解决方案的泄露特征。

另一个问题是有可能从排行榜中探测到一个解决方案。在这种情况下,你可以利用展示给你的评估指标,通过在排行榜上重复提交试验来窃取解决方案。同样,在这种情况下,该解决方案在不同的环境中完全不可用。一个明显的例子发生在比赛Don’t Overfit II中。获胜者Zachary Mayers将每个单独的变量作为一个单独的提交,从而获得了关于每个变量可能权重的信息,这使他能够估计他模型中正确的系数(你可以在 Zach 的详细解决方案中阅读:www.kaggle.com/c/dont-overfit-ii/discussion/91766)。一般来说,时间序列问题或其他测试数据中存在系统性变化的問題可能会受到探测的严重影响,因为它们可以帮助竞争对手成功定义某种后处理(如将他们的预测乘以一个常数),这对于在特定测试集上获得高分最为合适。

另一种形式的排行榜窥探(即,获取关于测试集的线索并对其过度拟合)发生在参与者更多地依赖公共排行榜的反馈而不是他们自己的测试时。有时这会导致比赛的完全失败,造成剧烈的动荡——最终排行榜位置的完全不可预测的重新洗牌。在这种情况下,获胜的解决方案可能并不那么适合问题,甚至可能只是由机会决定的。这导致了分析训练集和公共测试集之间潜在差距的技术扩散。这种分析被称为对抗性测试,可以提供关于应该依赖排行榜多少的见解,以及是否存在训练集和测试集之间差异如此之大,以至于最好完全避免的特征。

例如,您可以查看Bojan Tunguz的此笔记本:www.kaggle.com/tunguz/adversarial-ieee

防止排行榜过度拟合的另一种方式是选择安全策略,以避免提交过于依赖排行榜结果的解决方案。例如,由于(通常)每个参与者允许选择两个解决方案进行最终评估,一个好的策略是提交基于排行榜表现最佳的一个,以及基于你自己的交叉验证测试表现最佳的一个。

为了避免排行榜探测和过度拟合的问题,Kaggle 最近推出了基于代码竞赛的不同创新,正如我们之前讨论的那样,评估被分为两个不同的阶段,参与者对实际测试数据完全不知情,因此被迫更多地考虑他们自己的本地验证测试。

最后,竞争可能出现的另一种扭曲是由于私下分享(在参与者封闭圈中分享想法和解决方案)以及其他非法行为,如通过多个账户或多个团队进行游戏并窃取想法。所有这些行为都会在参与者之间造成信息不对称,这对少数人有利,而对大多数人有害。再次强调,由于比赛期间分享并不完美,且较少的团队能够充分发挥竞争压力,因此产生的解决方案可能会受到影响。此外,如果参与者意识到这些情况(例如,参见www.kaggle.com/c/ashrae-energy-prediction/discussion/122503),可能会导致对比赛或后续比赛的信任度降低和参与度减少。

计算资源

一些竞赛为了使可行的解决方案可用于生产而设定了限制。例如,博世生产线性能竞赛(www.kaggle.com/c/bosch-production-line-performance)对解决方案的执行时间、模型文件输出和内存限制有严格的限制。基于笔记本(之前称为 Kernel-Only)的竞赛,要求在 Kaggle 笔记本上执行训练和推理,对所需资源没有问题。这是因为 Kaggle 会为你提供所有需要的资源(这也是为了使所有参与者站在同一起跑线上,以获得更好的竞赛结果)。

当竞赛仅限制笔记本的使用时间为推理时间时,问题就出现了。在这些情况下,你可以在自己的机器上训练模型,唯一的限制是在测试时间,即你产生的模型的数量和复杂性。由于目前大多数竞赛都需要深度学习解决方案,你必须意识到,为了取得有竞争力的成绩,你需要专门的硬件,如 GPU。

即使在一些现在很少见的表格竞赛中,你很快就会意识到,你需要一台强大的机器,拥有相当数量的处理器和大量内存,以便轻松应用特征工程到数据,运行实验,并快速构建模型。

标准变化迅速,因此很难指定一个标准硬件,以便至少与其他团队在同一联赛中竞争。我们可以通过观察其他竞争对手使用什么硬件来获得关于当前标准的线索,无论是他们自己的机器还是云上的机器。

例如,惠普推出了一项计划,通过提供 HP Z4 或 Z8 服务器作为奖励,以换取品牌曝光度,奖励给一些精选的 Kaggle 参与者。例如,Z8 机器最多拥有 72 个核心,3TB 内存,48TB 存储(按照固态硬盘的标准,这是一个不错的配置),通常配备双 NVIDIA RTX 作为 GPU。我们理解这对很多人来说可能有些难以触及;即使在谷歌的 GCP 或亚马逊的 AWS 云实例上短期租赁类似的机器,考虑到即使是中等使用量的费用,这也超出了讨论的范围。

每个竞赛的云成本自然取决于要处理的数据量以及你构建的模型的数量和类型。Kaggle 竞赛中,GCP 和 AWS 云平台通常提供的免费信用额度通常在 200 美元到 500 美元之间。

因此,当你开始攀登 Kaggle 参与者排名的旅程时,我们的建议是使用 Kaggle 提供的免费机器,Kaggle 笔记本(之前被称为 Kaggle Kernels)。

Kaggle 笔记本

Kaggle 笔记本是基于在云机器上运行的 Docker 容器版本化的计算环境,允许你使用 R 和 Python 语言编写和执行脚本和笔记本。Kaggle 笔记本:

  • 集成到 Kaggle 环境中(你可以从它们提交并跟踪哪些提交与哪个笔记本相关)

  • 预装了大多数数据科学包

  • 允许一些定制(你可以下载文件并安装更多包)

基本 Kaggle 笔记本仅基于 CPU,但你可以使用由 NVIDIA Tesla P100 或 TPU v3-8 增强的版本。TPU 是专门针对深度学习任务硬件加速器。

尽管受到使用数量和时间配额限制的约束,Kaggle 笔记本仍然为你提供了访问计算工作站的权限,以便在 Kaggle 竞赛中构建基准解决方案:

笔记本类型 CPU 核心 内存 同时可运行的笔记本数量 每周配额
CPU 4 16 GB 10 无限制
GPU 2 13 GB 2 30 hours
TPU 4 16 GB 2 30 hours

除了总运行时间外,CPU 和 GPU 笔记本可以在会话中运行最多 12 小时(TPU 笔记本为 9 小时)然后停止,这意味着除了你在磁盘上保存的内容外,你不会从运行中获得任何结果。你有一个 20 GB 的磁盘保存额度来存储你的模型和结果,还有一个额外的临时使用磁盘,可以在脚本运行期间超过 20 GB。

在某些情况下,Kaggle 笔记本提供的 GPU 增强机器可能不足以满足需求。例如,最近的Deepfake Detection Challenge (www.kaggle.com/c/deepfake-detection-challenge) 需要处理大约 500 GB 的视频数据。这尤其具有挑战性,因为每周使用时间限制为 30 小时,并且你不能同时运行超过两台带有 GPU 的机器。即使你可以通过将代码修改为利用 TPU 而不是 GPU(你可以在以下位置找到一些易于实现的指导:www.kaggle.com/docs/tpu)来加倍你的机器时间,这也许仍然不足以在数据密集型竞赛如Deepfake Detection Challenge中进行快速实验。

因此,在第三章使用 Kaggle 笔记本工作和学习中,我们将为你提供一些技巧,帮助你成功应对这些限制,在不购买高性能机器的情况下产生令人满意的结果。我们还将向你展示如何将 Kaggle 笔记本与 GCP 集成,或者,在第二章使用数据集组织数据中,如何将所有工作迁移到另一个基于云的解决方案,Google Colab。

团队和网络

虽然计算能力在其中扮演了一定的角色,但在 Kaggle 竞赛中,真正能产生差异的还是人类的专长和能力。为了成功应对竞赛,有时需要一群参赛者的协作努力。除了招募竞赛,赞助商可能需要个别参与者以更好地评估他们的能力外,通常没有限制组建团队。通常,团队可以由最多五名参赛者组成。

团队合作有其自身的优势,因为它可以放大努力以找到更好的解决方案。一个团队可以共同花更多时间解决问题,不同的技能可以大有裨益;并不是所有数据科学家在处理不同模型和数据操作时都会拥有相同的技能或相同水平的技能。

然而,团队合作并非全是积极的。协调不同个体和努力以实现共同目标可能并不容易,可能会出现一些次优情况。一个常见的问题是,一些参与者没有参与或只是闲置,无疑最糟糕的是当有人违反竞赛规则——这对所有人都是一种损害,因为整个团队可能会被取消资格——或者甚至间谍活动,以给另一队提供优势,正如我们之前提到的。

尽管有负面影响,但在 Kaggle 竞赛中团队合作是一个了解其他数据科学家、为了共同目标协作并取得更多成就的绝佳机会,因为 Kaggle 规则确实奖励团队而非孤独的竞争者。事实上,对于较小的团队,你获得的总额百分比要高于平均份额。虽然在 Kaggle 中建立联系的唯一可能性并不是团队合作,但这无疑对参与者来说更有利和有趣。你还可以通过论坛上的讨论或通过在竞赛期间共享数据集和笔记本来与其他人建立联系。平台上的所有这些机会都可以帮助你了解其他数据科学家并在社区中获得认可。

在 Kaggle 平台之外,也有很多机会与其他 Kagglers 建立联系。首先,有几个 Slack 频道可能会有所帮助。例如,KaggleNoobs (www.kaggle.com/getting-started/20577) 是一个在 2016 年开放的频道,其中包含许多关于 Kaggle 竞赛的讨论。他们有一个支持性的社区,如果你在代码或模型上遇到特定问题,他们可以提供帮助。

还有许多其他频道专门用于交换关于 Kaggle 比赛和数据科学相关主题的意见。有些频道是在地区或国家层面组织的,例如,日本的频道 Kaggler-ja (kaggler-ja-wiki.herokuapp.com/) 或 2015 年成立的俄罗斯社区 Open Data Science Network (ods.ai/),后来也向非俄语使用者开放。Open Data Science Network 不仅提供 Slack 频道,还提供如何赢得比赛的课程、活动,以及对所有已知数据科学平台上正在进行的活跃比赛的报道(见 ods.ai/competitions)。

除了 Slack 频道,围绕 Kaggle 主题或特定比赛的地方聚会也相当多,有些只是临时性的,而有些则更加正式。一个专注于 Kaggle 比赛的聚会,通常围绕一位想要分享经验或建议的竞争者的演讲而建立,这是面对面结识其他 Kagglers、交流意见和共同参加数据科学竞赛联盟的最好方式。

在这个联盟中,应该提到由 Maria ParyszPaweł Jankiewicz 建立的 Kaggle Days (kaggledays.com/)。Kaggle Days 组织在世界各地的主要地点安排了一些活动(kaggledays.com/about-us/),目的是聚集一场 Kaggle 专家的会议。它还在不同国家建立了当地聚会网络,这些网络仍然非常活跃(kaggledays.com/meetups/)。

Pawel_Jankiewicz

Paweł Jankiewicz

www.kaggle.com/paweljankiewicz

我们有机会与 Paweł 交流他在 Kaggle 上的经验。他是一位竞赛大师,也是 LogicAI 的联合创始人。

你最喜欢的比赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

代码竞赛是我最喜欢的竞赛类型,因为在受限的环境中工作迫使你考虑不同类型的预算:时间、CPU、内存。在之前的许多比赛中,我需要利用多达 3-4 台强大的虚拟机。我不喜欢为了获胜而不得不利用这些资源,因为这使比赛变得非常不平等。

你是如何处理 Kaggle 比赛的?这种处理方式与你在日常工作中所做的工作有何不同?

我对待每个比赛都有一些不同的方法。我倾向于为每个比赛构建一个框架,使我能够尽可能多地创建实验。例如,在一个我们需要创建深度学习卷积神经网络的比赛中,我创建了一种通过指定格式 C4-MP4-C3-MP3(其中每个字母代表不同的层)来配置神经网络的方法。那是在很多年前,所以现在神经网络的配置可能是由选择骨干模型来完成的。但规则仍然适用。你应该创建一个框架,使你能够快速更改管道中最敏感的部分。

在日常工作方面,建模方法和适当的验证与 Kaggle 比赛有一些重叠。Kaggle 比赛教给我的重要性包括验证、防止数据泄露等。例如,如果数据泄露在许多比赛中发生,当准备它们的人是该领域的最佳人选时,你可以问问自己有多少生产模型在训练中存在数据泄露;我个人认为 80%以上的生产模型可能没有正确验证,但不要引用我的话。

在日常工作中的另一个重要区别是,没有人真正告诉你如何定义建模问题。例如:

  1. 您报告或优化的指标应该是 RMSE、RMSLE、SMAPE 还是 MAPE?

  2. 如果问题是基于时间的,你如何分割数据以尽可能真实地评估模型?

而且,这些并不是商业中唯一重要的事情。您还必须能够传达您的选择以及您为什么做出这些选择。

告诉我们您参加的一个特别具有挑战性的比赛,以及您使用了哪些见解来应对任务。

最具挑战性和趣味性的是 Mercari 价格预测代码比赛。它与任何其他比赛都大不相同,因为它仅限于 1 小时的计算时间,只有 4 个核心和 16GB 的内存。克服这些限制是挑战中最激动人心的部分。我从这次比赛中得到的启示是更加相信网络在表格数据中的应用。在与我的队友 Konstantin Lopukhin (www.kaggle.com/lopuhin)* 合并之前,我有一系列复杂的模型,包括神经网络,还有一些其他提升算法。合并后,结果证明 Konstantin 只使用了一种非常优化的架构(epoch 数量、学习率)。这次比赛另一个相当独特的地方是,仅仅平均团队解决方案是不够的。我们必须重新组织我们的工作流程,以便我们有一个单一的连贯解决方案,而不是快速拼凑起来的东西。我们花了三周时间将我们的解决方案结合起来。*

在您的经验中,没有经验的 Kagglers 通常忽略了什么?您现在知道什么,而您希望在开始时就知道?

软件工程技能可能被低估了很多。每个比赛和问题都有所不同,需要一些框架来简化解决方案(看看 https://github.com/bestfitting/instance_level_recognition 以及他们的代码是如何组织的)。良好的代码组织可以帮助你更快地迭代,并最终尝试更多的事情。

当人们参加比赛时,他们应该记住或做些什么最重要的事情?

最重要的是要享受乐趣。

性能层级和排名

除了货币奖金和其他物质物品,如奖杯、T 恤、连帽衫和贴纸外,Kaggle 还提供了许多非物质奖励。Kagglers 在比赛中投入了大量的时间和精力(更不用说在发展他们用于竞争的技能上,这些技能在普通人中实际上相当罕见)。通常,货币奖金覆盖了排名前几位 Kagglers 的努力,如果不是只有排名第一的人,那么其他人就会自愿投入大量的时间而几乎没有什么回报。从长远来看,参加没有实质性结果的比赛可能会导致不满和兴趣下降,降低竞争强度。

因此,Kaggle 找到了一种通过基于奖牌和积分的荣誉体系来奖励竞争者的方法。其理念是,你拥有的奖牌和积分越多,你的技能就越相关,这为你提供了在求职或其他基于你声誉的相关活动中的机会。

首先,有一个通用排行榜,它结合了所有个人比赛的排行榜(www.kaggle.com/rankings)。根据他们在每个比赛中达到的位置,Kagglers 获得一定数量的积分,所有积分加起来提供了他们在通用排行榜上的排名。乍一看,比赛中积分计分的公式可能看起来有点复杂:

然而,在现实中,它仅仅基于几个要素:

  • 在比赛中你的排名

  • 你的团队规模

  • 比赛的受欢迎程度

  • 比赛的历史

直觉上,在流行的比赛中排名靠前会带来很多积分。不那么直观的是,团队的大小以非线性方式影响排名。这是因为公式中的倒数平方根部分,因为你必须放弃的积分比例随着参与人数的增加而增长。

如果你的团队相对较小(最多 2 至 3 人),由于协作带来的智慧和计算能力的优势,这仍然相当有利。

另一点需要记住的是,积分会随着时间的推移而减少。这种减少不是线性的,但可以作为一个经验法则记住,一年后,你获得的积分所剩无几。因此,在 Kaggle 的一般排行榜上的荣耀是短暂的,除非你继续参加与之前相似结果的竞赛。作为安慰,在你的个人资料中,你将始终保留你曾经达到的最高排名。

比起排行榜,更持久的是涵盖 Kaggle 竞赛所有四个方面的奖牌系统。你将根据你的结果获得竞赛、笔记本、讨论和数据集的奖牌。在竞赛中,奖牌是根据你在排行榜上的位置颁发的。在其他三个领域,奖牌是根据其他竞争者的点赞数颁发的(这实际上可能导致一些次优情况,因为点赞是一个不那么客观的指标,也取决于人气)。你获得的奖牌越多,你能够进入的 Kaggle 精通等级就越高。等级包括新手贡献者专家大师宗师www.kaggle.com/progression页面解释了如何获得奖牌以及需要多少以及什么类型的奖牌才能进入不同的等级。

请记住,这些排名和荣誉总是相对的,并且会随时间而变化。实际上,几年前,评分系统和排名相当不同。很可能会在未来,排名将再次改变,以使更高的排名更加罕见和有价值。

批评和机会

自 Kaggle 开始以来,它已经受到了不少批评。参与数据科学竞赛至今仍是一个有争议的话题,外界有各种各样的观点,既有积极的也有消极的。

在负面批评方面:

  • Kaggle 给人一种关于机器学习真正是什么的错觉,因为它只是专注于排行榜的动态

  • Kaggle 只是一个超参数优化和集成多个模型的游戏,只是为了获得一点更高的准确率(而实际上是在过度拟合测试集)

  • Kaggle 充斥着缺乏经验的热心爱好者,他们愿意尝试任何可能的事情,只为获得分数和聚光灯,希望被招聘者发现

  • 作为进一步的后果,竞赛解决方案过于复杂,通常过于特定于测试集,以至于无法实施

许多人认为 Kaggle,就像许多其他数据科学竞赛平台一样,与数据科学的实际情况相去甚远。批评者提出的问题是,商业问题并非凭空而来,你很少一开始就有一个准备好的数据集来开始,因为你通常是在根据细化后的商业规格和对当前问题的理解过程中逐步构建它的。此外,许多批评者强调,Kagglers 并不擅长或精通创建生产就绪的模型,因为一个获胜的解决方案不能受限于资源限制或技术债务的考虑(尽管这并不总是适用于所有竞赛)。

所有这些批评最终都与雇主眼中 Kaggle 排名如何与其他类型的经验相比较有关,特别是与数据科学教育和工作经验相比较。一个持续的神话是,Kaggle 竞赛不能帮助你获得工作或更好的数据科学工作,而且它们并不使你比完全不参加竞赛的数据科学家处于更高的层次。

我们对此的看法是,认为 Kaggle 排名在 Kaggle 社区之外没有自动价值的观点是误导性的。例如,在求职过程中,Kaggle 可以为你提供一些非常有用的建模数据和问题的能力以及有效的模型测试。它还可以让你接触到许多技术和不同的数据/商业问题,这些超出了你的实际经验和舒适区,但它不能为你提供作为数据科学家成功进入公司的所有所需。

你可以用 Kaggle 来学习(网站上也有一个专门用于学习的部分,即课程部分)和在求职中区别于其他候选人;然而,这一点如何被考虑在不同的公司中差异很大。无论如何,你在 Kaggle 上学到的知识将不可避免地在你的整个职业生涯中证明是有用的,并在你需要用数据建模解决复杂和不同寻常的问题时为你提供保障。通过参加 Kaggle 竞赛,你将建立起强大的建模和验证能力。你还可以与其他数据科学家建立联系,这可以使你更容易地获得工作推荐,并为你提供另一种处理超出你技能范围困难问题的方法,因为你可以接触到其他人的能力和观点。

因此,我们的观点是,Kaggle 以更间接的方式帮助你作为数据科学家在职业生涯中取得成功,以各种不同的方式。当然,有时 Kaggle 会帮助你基于你的成功直接作为求职者被联系,但更常见的是,Kaggle 会为你提供成功所需的智力技能和经验,首先是作为候选人,然后是作为从业者。

实际上,在 Kaggle 上玩弄数据和模型一段时间后,你将有机会看到足够多的不同数据集、问题和在时间压力下处理它们的不同方式,当面对现实环境中的类似问题时,你将能够快速有效地找到解决方案。

正是这种技能提升的机会激励我们最初写这本书,这本书实际上也是关于这个主题的。你不会在这里找到关于如何在 Kaggle 竞赛中获胜或得分很高的指南,但你绝对会找到关于如何在 Kaggle 上更好地竞争以及如何从竞赛经验中获得最大回报的指南。

智能地使用 Kaggle 和其他竞赛平台。Kaggle 不是一个万能的解决方案——在竞赛中取得第一并不能保证你获得高薪工作或超越 Kaggle 社区的荣耀。然而,持续参与竞赛是一张明智的牌,可以展示你对数据科学职位的兴趣和热情,并提高一些可以让你作为数据科学家脱颖而出而不是被 AutoML 解决方案淘汰的特定技能。

如果你跟随我们阅读这本书,我们将向你展示如何做到这一点。

摘要

在本章的开头,我们首先讨论了数据科学竞赛平台是如何兴起以及它们实际上是如何运作的,特别是提到了 David Donoho 教授讨论过的令人信服的 CTF 模式。

我们展示了 Kaggle 的工作原理,同时不忘提及其他值得注意的竞赛平台,以及如何在你参加 Kaggle 以外的挑战中也能派上用场。关于 Kaggle,我们详细介绍了竞赛的不同阶段是如何工作的,竞赛之间有何不同,以及 Kaggle 平台可以为你提供哪些资源。

在接下来的几章中,我们将更详细地探讨 Kaggle,从如何处理数据集开始。

加入我们本书的 Discord 空间

加入本书的 Discord 工作空间,参加每月一次的作者“问我任何问题”活动:

packt.link/KaggleDiscord

第二章:使用数据集组织数据

在他的故事《铜 Beeches 的冒险》中,亚瑟·柯南·道尔让夏洛克·福尔摩斯喊道“数据!数据!数据!没有粘土我无法制造砖块。”这种心态为文学中最著名的侦探提供了很好的服务,每个数据科学家都应该采纳这种心态。因此,我们以一个专门介绍数据的章节开始这本书的更技术性部分:具体来说,在 Kaggle 的背景下,利用 Kaggle 数据集功能为我们服务。

在本章中,我们将涵盖以下主题:

  • 设置数据集

  • 收集数据

  • 与数据集一起工作

  • 在 Google Colab 中使用 Kaggle 数据集

  • 法律注意事项

设置数据集

原则上,你可以使用的任何数据都可以上传到 Kaggle(受限制;请参阅后面的 法律注意事项 部分)。写作时的具体限制是 每个私有数据集 100 GB总共 100 GB 的配额。请注意,单个数据集的大小限制是计算未压缩的;上传压缩版本可以加快传输速度,但不会帮助克服限制。你可以通过此链接检查数据集的最新文档:www.kaggle.com/docs/datasets

Kaggle 自称为“数据科学之家”,并且从该网站提供的令人印象深刻的数据集集合确实为这一说法增添了一些可信度。你不仅可以找到从石油价格到动漫推荐等主题的数据,而且数据迅速出现在那里的速度也令人印象深刻。当 2021 年 5 月根据《信息自由法》发布 安东尼·福奇 的电子邮件时(www.washingtonpost.com/politics/interactive/2021/tony-fauci-emails/),它们仅仅在 48 小时后就被上传为一个 Kaggle 数据集。

图 2.1:Kaggle 上的趋势和热门数据集

在将你的项目数据上传到数据集之前,请确保检查现有内容。对于几个流行的应用(图像分类、NLP、金融时间序列),有可能它已经被存储在那里。

为了介绍的目的,让我们假设你将在项目中使用的数据尚未存在,因此你需要创建一个新的数据集。当你点击左侧带有三条线的菜单并点击 数据 时,你将被重定向到 数据集 页面:

包含文本的图像 自动生成的描述

图 2.2:数据集页面

当你点击 + 新数据集 时,你将被提示输入基本信息:上传实际数据和给它一个标题:

包含文本的图像 自动生成的描述

图 2.3:输入数据集详细信息

左侧的图标对应于您可以为数据集利用的不同来源。我们按页面显示的顺序描述它们:

  • 从本地驱动器上传文件(如图所示)

  • 从远程 URL 创建

  • 导入 GitHub 仓库

  • 使用现有笔记本的输出文件

  • 导入 Google Cloud Storage 文件

关于 GitHub 选项的一个重要观点:当涉及到实验性库时,此功能特别有用。虽然它们经常提供以前不可用的功能,但它们通常不包括在 Kaggle 环境中,因此如果您想在代码中使用此类库,您可以将其作为数据集导入,如下所示:

  1. 前往数据集并点击新建数据集

  2. 选择 GitHub 图标。

  3. 插入仓库链接以及数据集的标题。

  4. 在右下角点击创建

包含文本的图像 自动生成的描述

图 2.4:来自 GitHub 仓库的数据集

创建按钮旁边,还有一个标记为私有的按钮。默认情况下,您创建的任何数据集都是私有的:只有您,即创建者,可以查看和编辑它。在数据集创建阶段保持此设置为默认值,并在稍后阶段将其公开(可供选定列表的参与者或所有人使用)可能是一个好主意。

请记住,Kaggle 是一个流行的平台,许多人上传他们的数据集——包括私有的——所以尽量想一个非通用标题。这将增加您的数据集真正被注意到的机会。

完成所有步骤并点击创建后,哇!您的第一个数据集就准备好了。然后您可以前往数据选项卡:

图 2.5:数据选项卡

上面的截图展示了您可以提供有关数据集的不同信息;您提供的信息越多,可用性指数就越高。这个指数是一个综合指标,总结了您的数据集描述得有多好。具有更高可用性指数的数据集在搜索结果中显示得更高。对于每个数据集,可用性指数基于多个因素,包括文档水平、相关公共内容(如笔记本)的可用性、文件类型和关键元数据的覆盖范围。

在原则上,您不需要填写上图所示的所有字段;您新创建的数据集无需它们即可完全使用(如果它是私有的,您可能也不在乎;毕竟,您知道里面有什么)。然而,社区礼仪建议填写您公开的数据集的信息:您指定的信息越多,数据对他人就越有用。

收集数据

除了法律方面,在数据集中存储的内容类型实际上没有真正的限制:表格数据、图像、文本;如果它符合大小要求,就可以存储。这包括从其他来源收集的数据;在撰写本文时,按标签或主题收集的推文是流行的数据集之一:

包含文本的图像 自动生成的描述

图 2.6:推文是最受欢迎的数据集之一

讨论从社交媒体(Twitter、Reddit 等)收集数据的不同框架超出了本书的范围。

安德烈·马拉尼亚奥的图片

安德烈·马拉尼亚奥

www.kaggle.com/andrewmvd

我们采访了安德烈·马拉尼亚奥(又名 Larxel),数据集大师(在撰写本文时数据集排名第一)和圣保罗艾伯特·爱因斯坦医院的资深数据科学家,关于他如何取得数据集的成功,他创建数据集的技巧以及他在 Kaggle 上的总体经验。

你最喜欢的比赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

医学影像通常是我在 Kaggle 上最喜欢的。它与我的人生目标和职业相关。在医学竞赛中,NLP 受语言限制,表格数据在医院之间差异很大,但影像学大多是相同的,因此在这个背景下任何进步都可以为世界上的许多国家带来好处,我热爱这种影响潜力。我也喜欢 NLP 和表格数据,但我想这很常见。

告诉我们你参加的一个特别具有挑战性的比赛,以及你用来应对任务的见解。

在 X 光图像结核病检测比赛中,我们有大约 1,000 张图像,这对于捕捉疾病的全部表现来说数量相当少。我提出了两个想法来弥补这一点:

  1. 在外部肺炎检测数据(约 20k 张图像)上进行预训练,因为肺炎可能会被误诊为肺结核。

  2. 在肺异常的多标签分类(约 600k 张图像)上进行预训练,并使用简单的 SSD 与 grad-CAM 生成分类标签的边界框注释。

最终,这两种简单混合的方法比第二名团队的结果高出 22%。这发生在一次医学会议上,大约有 100 个团队参加。

你已经成为数据集大师,并在数据集上取得了第一名。你是如何选择主题,以及如何在 Kaggle 上找到、收集和发布数据集的?

这是一个很大的问题;我会一点一点地把它分解。

  1. 设定自己的目标

在选择主题时,我首先考虑的是我最初为什么要做这件事。

当有一个更深层次的原因时,伟大的数据集只是作为一个结果出现,而不是作为一个目标本身。ImageNet 创建实验室的负责人李飞飞在 TED 演讲中透露,她 希望创造一个世界,机器能够用他们的视觉以她孩子的方式推理和欣赏世界。

有一个目的在心中会使你更有可能参与并随着时间的推移而改进,这也会使你和你数据集与众不同。你当然可以靠日常主题的表格数据为生,尽管我发现这不太可能留下持久的影响。

  1. 一个伟大的数据集是一个伟大问题的体现

如果我们看看当前文献中最伟大的数据集,比如 ImageNet 和其他数据集,我们可以看到一些共同的主题:

  • 这是一个大胆、相关的问题,对我们所有人(科学或现实世界应用)都有巨大的潜力。

  • 数据收集得很好,质量得到控制,并且有很好的记录

  • 当前硬件的数据量和多样性是足够的

  • 它有一个活跃的社区,不断改进数据并/或在此基础上构建问题

正如我之前提到的,我认为提问是数据科学家的主要角色,并且随着自动机器学习和深度学习解决方案的进步,它可能会变得更加突出。这正是数据集可以肯定地发挥你技能的独特之处。

  1. 创造你的成功过程,而不仅仅是追求成功本身

质量远远超过数量;你只需要 15 个数据集就可以成为大师,而 AI 的旗舰数据集很少且制作精良。

我已经丢弃了与我发布的同样多的数据集。这需要时间,而且它不是许多人所认为的一次性事情——数据集有维护和持续改进的一面。

经常被忽视的一点是支持围绕你的数据聚集的社区。笔记本和数据集是共同努力的结果,因此支持那些花时间分析你数据的人对你的数据集也有很大的帮助。分析他们的瓶颈和选择可以为你提供指导,关于哪些预处理步骤可以完成和提供,以及你文档的清晰度。

总的来说,我推荐的过程是从设定你的目的开始,将其分解为目标和主题,制定问题以满足这些主题,调查可能的数据来源,选择和收集,预处理,记录,发布,维护和支持,最后是改进措施。

例如,假设你想要提高社会福利;你将其分解为一个目标,比如,种族平等。从那里,你分析与目标相关的主题,并找到“黑人的命也是命”运动。从这里,你提出问题:我如何理解数百万人在谈论它?

这缩小了你的数据类型到 NLP,你可以从新闻文章、YouTube 评论和推文中(你选择的,因为它似乎更能代表你的问题和可行性)收集数据。你预处理数据,去除标识符,并记录收集过程和数据集用途。

完成这些后,你发布它,一些 Kagglers 尝试主题建模,但很难做到,因为一些推文中包含许多外语,这造成了编码问题。你通过提供建议和突出他们的工作来支持他们,并决定回到推文,将其缩小到英语,以彻底解决这个问题。

他们的分析揭示了与运动相关的需求、动机和恐惧。通过他们的努力,有可能将数百万条推文分解成一系列可能改善社会种族平等的建议。

  1. *做好工作是你在控制范围内的所有事情

最终,是其他人让你成为大师,投票并不总是转化为努力或影响。在我的一个关于《赛博朋克 2077》的数据集中,我总共工作了大约 40 小时,时至今日,它仍然是我最少点赞的数据集之一。

但这没关系。我付出了努力,我尝试了,我学到了我能学到的东西——那是我能控制的,下周无论发生什么,我都会再次尝试。尽你所能,继续前进。

你会推荐哪些特定的工具或库用于数据分析/机器学习?

奇怪的是,我既推荐也不建议使用库。LightGBM 是一个出色的表格机器学习库,具有出色的性能与计算时间的比率,CatBoost 有时可以超越它,但代价是增加了计算时间,在这段时间里,你可以尝试和测试新的想法。Optuna 非常适合超参数调整,Streamlit 适用于前端,Gradio 适用于 MVP,Fast API 适用于微服务,Plotly 和 Plotly Express 适用于图表,PyTorch 及其衍生品适用于深度学习。

虽然图书馆很棒,但我还建议在职业生涯的某个阶段,你花些时间自己实现它。我第一次从 Andrew Ng 那里听到这个建议,后来又从许多同等水平的人那里听到。这样做可以创造非常深入的知识,这些知识可以让你对模型的功能以及它如何响应调整、数据、噪声等有新的认识。

在你的经验中,不经验的 Kagglers 经常忽视什么?你现在知道什么,而你在最初开始时希望知道的呢?

多年来,我最希望早点意识到的事情是:

  1. 在竞赛结束时吸收所有知识

  2. 在完成竞赛中复制获胜的解决方案

在一场即将结束的竞争压力下,你可以看到排行榜比以往任何时候都更加动荡。这使得你不太可能冒险,也不太愿意花时间去细致观察。当比赛结束后,你不再有那种紧迫感,可以花尽可能多的时间去思考;你也可以复制获胜者的逻辑,让他们知道他们的解决方案。

如果你有自律,这将对你的数据科学技能产生神奇的效果,所以底线是:在你完成时停止,而不是在比赛结束时停止。我还从 Andrew Ng 的闭幕演讲中听到过这个建议,他建议复制论文是他作为 AI 实践者自我发展的最佳方式之一。

此外,在比赛结束时,你可能会感到筋疲力尽,只想结束这一天。没问题;但请记住,比赛结束后,讨论论坛是地球上知识最丰富的地方之一,主要是因为 许多获胜解决方案的理据和代码都公开发布在那里。花时间去阅读和学习获胜者所做的事情;不要屈服于想要转向其他事物的欲望,因为你可能会错过一个极好的学习机会。

Kaggle 是否帮助了你的职业生涯?如果是的话,是如何帮助的?

Kaggle 通过提供丰富的知识、经验和建立我的作品集来帮助了我的职业生涯。我作为数据科学家的第一份工作很大程度上得益于 Kaggle 和 DrivenData 竞赛。在我的整个职业生涯中,我研究了竞赛解决方案,并参与了一些更多的竞赛。在数据集和笔记本上的进一步参与也在学习新技术和提出更好的问题方面证明非常有益。

在我看来,提出优秀问题是数据科学家面临的主要挑战。回答它们当然也很棒,尽管我相信我们离一个自动化解决方案在建模中越来越普遍的未来并不遥远。建模始终有空间,但我认为在这方面会有很多工作流程简化。然而,提出优秀问题要远比自动化困难——如果问题本身不好,即使是最好的解决方案也可能毫无意义。

你是否曾使用在 Kaggle 竞赛中完成的项目来构建你的作品集,以展示给潜在雇主?

绝对如此。我于 2017 年通过 Kaggle 作为知识证明,获得了我的第一份数据科学家工作。时至今日,它仍然是一个极好的简历组成部分,因为教育背景和学位在数据科学知识和经验方面不如作品集来得有代表性。

拥有竞赛项目的作品集不仅展示了额外的经验,还显示了超越常规进行发展的意愿,这在长期成功中可能更为重要。

你使用其他竞赛平台吗?它们与 Kaggle 相比如何?

我也使用 DrivenData 和 AICrowd。它们的好处是允许那些没有相同财务资源访问权的组织,如初创公司和研究机构,创建比赛。

伟大的竞赛来自于优秀的问题和优秀的数据的结合,这无论公司规模大小都是可能的。Kaggle 拥有一个更大、更活跃的社区,以及他们提供的硬件,结合数据集和笔记本功能,使其成为最佳选择;然而,DrivenData 和 AICrowd 也提供了同样有趣挑战,并允许更多样化的选择。

当人们参加比赛时,他们应该记住或做什么最重要的事情?

假设你的主要目标是开发,我的建议是选择一个你感兴趣的主题和之前没有做过的任务的比赛。批判性思维和技能需要深度和多样性。专注于并全力以赴将保证深度,而多样性是通过做你以前没有做过的事情或以不同的方式做事情来实现的。

与数据集一起工作

一旦你创建了一个数据集,你可能希望将其用于你的分析。在本节中,我们将讨论不同的实现方法。

很可能,最重要的是在参加比赛时,应该记住或做的事情是开始一个笔记本,其中你使用你的数据集作为主要来源。你可以通过访问数据集页面,然后点击新建笔记本来完成:

图 2.7:从数据集页面创建笔记本

完成这些操作后,你将被重定向到你的笔记本页面:

包含文本的图像 自动生成的描述

图 2.8:使用你的数据集开始笔记本

关于这一点,这里有一些提示:

  • 阿拉伯数字和字母标题是自动生成的;你可以通过点击它来编辑它。

  • 在右侧的数据部分,你可以看到附加到你的笔记本上的数据源列表;我选择的数据集可以在../input//kaggle/input/下访问。

  • 开头块(包含导入的包、描述性注释和打印可用文件列表)会自动添加到新的 Python 笔记本中。

在这个基本设置下,你可以开始编写用于分析的笔记本,并将你的数据集作为数据源使用。我们将在第四章利用讨论论坛中更详细地讨论笔记本。

在 Google Colab 中使用 Kaggle 数据集

Kaggle 笔记本免费使用,但并非没有限制(更多内容请见第四章),你很可能会遇到的时间限制。一个流行的替代方案是迁移到 Google Colab,这是一个完全在云端运行的免费 Jupyter Notebook 环境:colab.research.google.com

即使我们将计算移动到那里,我们可能仍然需要访问 Kaggle 数据集,因此将它们导入 Colab 是一个相当方便的功能。本节剩余部分将讨论使用 Colab 通过 Kaggle 数据集所需的步骤。

假设我们已经在 Kaggle 上注册,我们首先去账户页面生成API 令牌(一个包含登录会话安全凭证、用户标识、权限等的访问令牌):

  1. 前往你的账户,可以在 https://www.kaggle.com/USERNAME/account 找到,然后点击创建新的 API 令牌

包含文本的图片 自动生成的描述

图 2.9:创建新的 API 令牌

将包含你的用户名和令牌的名为 kaggle.json 的文件创建出来。

  1. 下一步是在你的 Google Drive 中创建一个名为 Kaggle 的文件夹,并将 .json 文件上传到那里:

![图片 B17574_02_10.png]

图 2.10:将 .json 文件上传到 Google Drive

  1. 完成后,你需要在 Colab 中创建一个新的笔记本,并通过在笔记本中运行以下代码来挂载你的驱动器:

    from google.colab import drive
    drive.mount('/content/gdrive') 
    
  2. 从 URL 提示中获取授权代码,并在出现的空框中提供,然后执行以下代码以提供 .json 配置的路径:

    import os
    # content/gdrive/My Drive/Kaggle is the path where kaggle.json is 
    # present in the Google Drive
    os.environ['KAGGLE_CONFIG_DIR'] = "/content/gdrive/My Drive/Kaggle"
    # change the working directory
    %cd /content/gdrive/My Drive/Kaggle
    # check the present working directory using the pwd command 
    
  3. 现在我们可以下载数据集了。首先前往数据集在 Kaggle 上的页面,点击新建笔记本旁边的三个点,然后选择复制 API 命令

![图片 B17574_02_11.png]

图 2.11:复制 API 命令

  1. 运行 API 命令以下载数据集(对命令细节感兴趣的读者可以查阅官方文档:www.kaggle.com/docs/api):

    !kaggle datasets download -d ajaypalsinghlo/world-happiness-report-2021 
    
  2. 数据集将以 .zip 归档的形式下载到 Kaggle 文件夹中——解压后即可使用。

如上表所示,在 Colab 中使用 Kaggle 数据集是一个简单的过程——你只需要一个 API 令牌,切换后你就有可能使用比 Kaggle 授予的更多 GPU 小时数。

法律免责声明

只因为你可以在 Kaggle 上放置一些数据,并不意味着你一定应该这样做。一个很好的例子是 Tinder 的人 数据集。2017 年,一位开发者使用 Tinder API 爬取了网站上的半私密资料,并将数据上传到了 Kaggle。问题曝光后,Kaggle 最终将该数据集下架。你可以在这里阅读完整的故事:www.forbes.com/sites/janetwburns/2017/05/02/tinder-profiles-have-been-looted-again-this-time-for-teaching-ai-to-genderize-faces/?sh=1afb86b25454

通常,在将任何内容上传到 Kaggle 之前,问问自己两个问题:

  1. 从版权角度来看,这是否允许? 记得始终检查许可证。如有疑问,您始终可以咨询opendefinition.org/guide/data/或联系 Kaggle。

  2. 这个数据集是否有隐私风险? 仅因为发布某些类型的信息在严格意义上并不违法,但这可能对另一人的隐私造成伤害。

这些限制符合常识,因此不太可能妨碍您在 Kaggle 上的努力。

摘要

在本章中,我们介绍了 Kaggle 数据集,这是在平台上存储和使用数据的标准化方式。我们讨论了数据集创建、在 Kaggle 之外工作的方法以及最重要的功能:在您的笔记本中使用数据集。这为我们下一章提供了一个很好的过渡,我们将重点关注 Kaggle 笔记本。

加入我们本书的 Discord 空间

加入本书的 Discord 工作空间,参加每月一次的作者“问我任何问题”活动:

packt.link/KaggleDiscord

二维码

第三章:使用 Kaggle 笔记本学习和工作

Kaggle 笔记本——直到最近还被称为内核——是浏览器中的 Jupyter 笔记本,可以免费运行。这意味着您可以从任何有互联网连接的设备执行实验,尽管可能比手机大一些会更好。以下是从 Kaggle 网站引用的环境技术规格(截至本文写作时);最新版本可以在www.kaggle.com/docs/notebooks验证:

  • CPU/GPU 执行时间为 12 小时,TPU 为 9 小时

  • 20GB 的自动保存磁盘空间(/kaggle/working)

  • 额外的临时磁盘空间(在/kaggle/working 之外),这些空间在当前会话之外不会被保存

CPU 规格:

  • 4 个 CPU 核心

  • 16GB 的 RAM

GPU 规格:

  • 2 个 CPU 核心

  • 13GB 的 RAM

TPU 规格:

  • 4 个 CPU 核心

  • 16GB 的 RAM

在本章中,我们将涵盖以下主题:

  • 设置笔记本

  • 运行笔记本

  • 将笔记本保存到 GitHub

  • 充分利用笔记本

  • Kaggle 学习课程

不再拖延,让我们直接进入正题。我们首先需要做的是弄清楚如何设置笔记本。

设置笔记本

创建笔记本主要有两种方法:从首页或从数据集创建。

要进行第一种方法的操作,请转到www.kaggle.com/着陆页左侧的代码菜单部分,并点击新建笔记本按钮。如果您计划进行涉及上传自己的数据集的实验,这是一个首选方法:

图片

图 3.1:从代码页面创建新的笔记本

或者,您可以转到您感兴趣的数据集页面,并点击那里的新建笔记本按钮,正如我们在上一章中看到的:

图片

图 3.2:从数据集页面创建新的笔记本

无论您选择哪种方法,在点击新建笔记本后,您将被带到您的笔记本页面:

图片

图 3.3:笔记本页面

在上面显示的新笔记本页面的右侧,我们有一些可以调整的设置:

图片

图 3.4:笔记本选项

我们将简要讨论设置。首先,是编码语言。截至本文写作时,Kaggle 环境仅允许 Python 和 R 作为可用的编码选项。默认情况下,新的笔记本初始化时语言设置为 Python——如果您想使用 R,请点击下拉菜单并选择R

接下来是环境:这个切换按钮允许你决定是否始终使用最新的 Docker 环境(风险选项;更新快,但依赖项可能会因未来的更新而损坏)或者将笔记本固定到 Kaggle 提供的原始环境版本(安全选择)。后者是默认选项,除非你正在进行非常活跃的开发工作,否则没有真正的原因要去修改它。

加速器允许用户选择如何运行代码:在 CPU 上(无加速)、GPU 上(对于几乎所有涉及深度学习的严肃应用都是必要的),或者 TPU 上。请记住,从 CPU 迁移到(单个)GPU 只需要对代码进行最小改动,并且可以通过系统设备检测来处理。

将你的代码迁移到 TPU 需要更详细的重新编写,从数据处理开始。需要记住的一个重要点是,当你正在你的笔记本上工作时,你可以切换到 CPU/GPU/TPU,但每次你这样做,环境都会重新启动,你需要从头开始运行所有代码。

最后,我们有互联网切换按钮,它启用或禁用在线访问。如果你连接了网络并且需要,例如,安装额外的包,依赖项的下载和安装将在后台自动进行。你需要明确禁用互联网访问的最常见情况是在提交比赛时,比赛明确禁止在提交时使用在线访问。

使用笔记本的一个重要方面是,你可以始终取一个现有的笔记本(由你自己或另一个 Kaggler 创建)并将其克隆以修改和调整以满足你的需求。这可以通过点击笔记本页面右上角的复制并编辑按钮来实现。在 Kaggle 术语中,这个过程被称为分支

图片

图 3.5:分支现有的笔记本

关于礼仪的说明:如果你之前参加过 Kaggle 比赛,你可能已经注意到排行榜上充斥着得分很高的笔记本的分支。在别人的工作上建立自己的东西并没有什么不妥——但如果你这样做,记得要给原作者点赞,并明确指出参考作品的创作者。

默认情况下,你创建的笔记本是私有的(只有你能看到)。如果你想让它对其他人可用,你可以选择添加协作者,这样只有被明确添加到名单中的用户才能查看或编辑内容,或者将笔记本公开,在这种情况下,任何人都可以看到它。

运行你的笔记本

所有编码工作已完成,笔记本看起来运行良好,你现在可以执行了。为此,请前往笔记本页面的右上角并点击保存版本

图片

图 3.6:保存你的脚本

保存并运行所有通常用于执行脚本,但还有一个快速保存选项,可以在脚本准备好提交之前保存脚本的中间版本:

图 3.7:保存版本的选项

一旦您启动了您的脚本,您可以前往左下角并点击活动事件

包含文本的图像,屏幕截图,显示器,屏幕  自动生成的描述

图 3.8:监控活动事件

以这种方式,您可以监控您的笔记本行为。正常执行与消息运行相关联;否则,将显示为失败。如果您决定出于任何原因(例如,您意识到您忘记使用最新的数据)想要终止一个正在运行的会话,您可以通过点击活动事件下脚本条目右侧的三个点来完成,您将收到如图下所示的弹出窗口:

图 3.9:取消笔记本执行

将笔记本保存到 GitHub

最近引入的一项功能(见www.kaggle.com/product-feedback/295170)允许您将您的代码或笔记本存储到版本控制存储库 GitHub (github.com/))。您可以将您的作品存储在公共和私有存储库中,并且当您保存代码版本时,这将自动发生。这个功能对于与您的 Kaggle 队友分享您的作品,以及向更广泛的公众展示您的作品来说可能非常有用。

为了启用此功能,您需要打开您的笔记本;在文件菜单中,选择链接到 GitHub选项。

00.PNG

图 3.10:启用 GitHub 功能

选择选项后,您需要将 GitHub 账户链接到笔记本。您将在第一次选择链接时明确被要求提供链接权限。对于任何后续链接到新笔记本的操作,操作将自动执行。

01.PNG

图 3.11:链接到 GitHub

只有在链接了您的笔记本之后,当您保存时,您才被允许将您的作品同步到您选择的存储库:

02.PNG

图 3.12:将您的作品提交到 GitHub

在决定存储库和分支(这样您可以存储您工作的不同开发阶段)之后,您可以更改您要推送到存储库的文件名,并修改提交信息。

如果你决定不再在 GitHub 上同步特定的 Notebook,你只需要回到文件菜单并选择从 GitHub 断开连接。最后,如果你想让 Kaggle 停止连接到你的 GitHub 仓库,你可以从我的链接账户下的 Kaggle 账户页面或 GitHub 的设置页面(github.com/settings/applications)断开你的账户链接。

充分利用 Notebooks

Kaggle 每周会提供一定量的免费资源,配额会重置。你可以使用一定数量的 GPU 和 TPU 时间;对于 TPU 是 30 小时,但对于 GPU,这个数字每周都可能变化(你可以在这里找到描述“浮动”配额政策的官方声明:www.kaggle.com/product-feedback/173129)。你可以在自己的个人资料中始终监控你的使用情况:

包含文本的图像 自动生成的描述

图 3.13:加速器配额的当前状态

虽然乍一看这些数量可能很大,但这种初步印象可能会误导人;实际上,很容易很快就用完你的配额。以下是一些可以帮助你控制资源使用的实用建议:

  • 配额计数器(衡量你使用所选加速器,GPU 或 TPU 的时间)从你初始化Notebook 的那一刻开始运行。

  • 这意味着你应该始终先检查设置中是否已禁用 GPU(参见上面的图 3.6)。首先编写模板代码,检查你的语法,并在添加实际依赖于 GPU 初始化的代码部分时启用/禁用 GPU。提醒:当你更改加速器时,Notebook 将重新启动。

  • 通常,在小部分数据上从头到尾运行代码以了解执行时间是一个好主意。这样,你可以最大限度地减少你的代码因超过此限制而崩溃的风险。

有时候,Kaggle 提供的免费资源不足以完成手头的任务,你需要迁移到一个更强大的机器。一个很好的例子是最近的一个肿瘤分类竞赛:www.kaggle.com/c/rsna-miccai-brain-tumor-radiogenomic-classification/data

如果你的原始数据超过 100GB,你需要调整/下采样你的图像(这可能会对你的模型性能产生不利影响),或者在一个能够处理高分辨率图像的环境中训练模型。你可以自己设置整个环境(例如,第二章中的使用 Kaggle 数据集在 Google Colab 中部分就是一个这样的设置示例),或者你可以在 Notebooks 框架内保持,但更换底层的机器。这就是 Google Cloud AI Notebooks 发挥作用的地方。

升级到 Google Cloud Platform (GCP)

升级到 GCP 的明显好处是获得更强大的硬件:由 Kaggle 免费提供的 Tesla P100 GPU 对于许多应用来说相当不错,但在性能方面并不是顶级的,16GB 的 RAM 在资源密集型应用(如大型 NLP 模型或高分辨率图像处理)中也可能相当有限。虽然执行时间的改进是明显的,导致开发周期中迭代速度更快,但这是有代价的:你需要决定你愿意花多少钱。对于强大的机器处理数字,时间可以说是实实在在的钱。

为了将你的笔记本迁移到 GCP 环境,请转到右侧的侧边菜单并点击升级到 Google Cloud AI 笔记本

包含文本的图像 自动生成的描述

图 3.14:升级到 Google Cloud AI 笔记本选项

你将看到以下提示:

包含文本的图像 自动生成的描述

图 3.15:升级到 Google Cloud AI 平台笔记本提示

当你点击继续时,你将被重定向到 Google Cloud Platform 控制台,在那里你需要配置你的计费选项。提醒:GCP 不是免费的。如果是你第一次使用,你需要完成一个教程,引导你完成必要的步骤。

更进一步

如本章前面所述,Kaggle 笔记本是教育和参加比赛的一个极好的工具;但它们也充当了一个非常有用的组件,即你可以用来展示你的数据科学技能的资料夹。

在构建你的数据科学资料夹时,有许多潜在的考虑因素(品牌、受众范围、向潜在雇主推销等),但如果没有人能找到它,那么它们都不重要。因为 Kaggle 是 Google 的一部分,笔记本由世界上最受欢迎的搜索引擎索引;所以如果有人正在寻找与你代码相关的主题,它将出现在他们的搜索结果中。

下面,我展示了一个个人例子:几年前,我为一项比赛编写了一个笔记本。我想解决的问题是对抗验证(对于那些不熟悉这个话题的人来说:一个相当简单的方法来查看你的训练集和测试集是否有相似分布,就是构建一个二元分类器,训练它来区分它们;这个概念在第六章设计良好的验证中有更详细的介绍)。在撰写本章时,我试图搜索这个笔记本,结果它出现在搜索结果的高位(注意我在查询中没有提到 Kaggle 或任何个人细节,如我的名字):

包含文本的图像 自动生成的描述

图 3.16:Konrad 的笔记本出现在 Google 上

接下来谈谈使用 Notebooks 展示技能集的其他好处:就像竞赛数据集讨论一样,笔记本也可以获得投票/奖牌,从而在进步系统和排名中定位你。你可以远离竞赛赛道,仅通过专注于社区欣赏的高质量代码,成为专家、大师或大师级。

最新的进步要求版本可以在www.kaggle.com/progression找到;以下是我们提供的与专家和大师级别相关的快照:

包含文本的图像 自动生成的描述

图 3.17:等级进步要求

在笔记本类别中进步可能是一个具有挑战性的经历;虽然比竞赛容易,但绝对比讨论难。最受欢迎的笔记本是与特定竞赛相关的:探索性数据分析、端到端的概念验证解决方案,以及排行榜追逐;遗憾的是,人们通常会克隆得分最高的公开笔记本,调整一些参数以提高分数,然后广受赞誉(如果点赞可以被视为情绪的衡量标准)。这并不是要阻止读者在 Kaggle 上发布高质量的作品——大多数 Kagglers 都欣赏新颖的工作,而且从长远来看,质量确实占上风——但需要对期望进行现实的调整。

你的 Kaggle 个人资料附带关注者,并为你提供了链接其他专业网络如 LinkedIn 或 GitHub 的可能性,这样你就可以利用在社区中获得的联系:

![B17574_03_18.png]

图 3.18:Konrad 的 Kaggle 个人资料

在这个时代,对“社区建设”的宣称持怀疑态度是很常见的,但在 Kaggle 的情况下,这竟然是真的。他们在数据科学领域的品牌认知度无人能及,无论是对于从业者还是那些真正做足功课的招聘者来说都是如此。在实践中,这意味着一个(足够好的)Kaggle 个人资料已经可以让你迈过门槛;正如我们都知道的,这通常是最难的一步。

马丁·亨泽的图片

Martin Henze

www.kaggle.com/headsortails

我们有幸采访了 Martin Henze,也就是 Heads or Tails,他是 Kaggle 的 Notebooks 和 Discussion 领域的 Kaggle 大师,同时也是 Edison Software 的数据科学家。Martin 还是《每周笔记本精选:隐藏的宝石》的作者,这是一周精选的最好笔记本,它们都逃过了公众的注意。你可以通过关注他的 Kaggle 个人资料或 Twitter 和 LinkedIn 上的账户来获取新的隐藏宝石帖子。

你最喜欢的比赛类型是什么?为什么?在技术、解决方法方面,你在 Kaggle 上的专长是什么?

长期以来,我的重点是 EDA(探索性数据分析)笔记本,而不是排行榜预测本身。在我加入 Kaggle 之前的大部分经验都与表格数据有关,我的大多数 EDA 笔记本都涉及从新推出的表格挑战中提取复杂的见解。我仍然认为这是我在 Kaggle 上的专长,我花了很多时间来构建我的笔记本的结构、数据可视化和叙事。

你是如何处理 Kaggle 竞赛的?这种处理方式与你在日常工作中所做的是如何不同的?

即使 Kaggle 已经从表格竞赛转向,我仍然坚信数据本身是任何挑战最重要的方面。很容易过早地专注于模型架构和超参数调整。但在许多竞赛中,成功的关键仍然是一个以对数据集及其怪癖和独特性有详细了解为中心的方法。这对于图像数据、NLP、时间序列以及你能想到的任何其他数据结构都是正确的。因此,我总是在构建一个简单的基线模型、一个 CV 框架之前,先进行广泛的 EDA(探索性数据分析)。

与我的数据科学日常工作相比,主要的不同可能在于,大多数有经验的人在新挑战的第一周内就能构建出的基线模型可能被认为是足够投入生产的。在许多情况下,在那几天之后,我们在得分指标方面已经超过了最终获胜者的解决方案的 80%以上。当然,Kaggle 的乐趣和挑战在于找到创造性的方法来获得最后几个百分点的准确性。但在行业工作中,你的时间通常更有效地用于处理新的项目。

Kaggle 是否帮助了你在职业生涯中?如果是的话,是如何帮助的?

Kaggle 对我的职业生涯产生了巨大的影响。在 Kaggle 社区中的良好经历激励我从学术界转向行业。如今,我在一家科技初创公司担任数据科学家,并通过 Kaggle 挑战不断成长和磨练我的技能。

在我的情况下,我对构建广泛的 Kaggle Notebooks 的关注帮助了我很多,因为我可以很容易地使用它们作为我的作品集。我不知道招聘经理实际上会多频繁地查看这些资源,但我经常有一种印象,我的大师级称号可能比我的博士学位为我打开更多的门。或者可能是两者的结合。无论如何,我强烈推荐拥有一个公开 Notebooks 的作品集。此外,在我的求职过程中,我使用了在 Kaggle 上学到的策略来解决各种带回家的作业,并且它们对我很有帮助。

根据你的经验,没有经验的 Kagglers 通常忽略了什么?你现在知道的事情,你希望在你刚开始的时候就知道?

我认为我们都在 不断积累经验。现在我们都比十年前、五年前甚至一年前更聪明了。抛开这些不说,一个经常被忽视的关键方面是,你需要为你要做的事情制定一个计划,并执行和记录这个计划。对于新加入 Kaggle 的人来说,这是一个完全可以理解的错误,因为一切都是新颖的、复杂的,至少有些令人困惑。我知道当我第一次加入 Kaggle 时,它对我来说很困惑。你可以做很多事情:论坛、数据集、挑战、课程。而且比赛可能会让人感到非常害怕:神经元细胞实例分割;股票市场波动预测。这些到底是什么东西?但比赛也是开始的最佳地方。

因为当一项比赛启动时,没有人真正了解它。是的,可能有一个人的博士论文几乎与这个主题完全相同。但这种情况很少见。其他人,我们几乎都是从零开始的。深入研究数据,玩转损失函数,运行一些简单的入门模型。当你一开始加入比赛时,你会以社区成员的身份以加速的方式经历所有学习曲线。你与其他人一起学习,他们会给你提供大量的想法。但你仍然需要一个计划。

这个计划很重要,因为盲目地运行一些实验,看到所有的 GPU RAM 都被使用,会让人感到很满足。但随后你会忘记哪个版本的你模型表现最好,局部验证和排行榜之间是否有相关性?我已经测试过这个参数组合了吗?所以写下你要做什么,然后记录结果。现在有越来越多的工具可以为你做记录,但这也可以通过一个自定义脚本来轻松完成。

机器学习仍然主要是一门实验科学,高效实验的关键在于精心规划实验并记录所有结果,以便进行比较和分析。

你在过去比赛中犯过哪些错误?

我犯过很多错误,我希望我已经从中学到了东西。没有建立健壮的交叉验证框架是其中之一。没有考虑到训练集和测试集之间的差异。做了太多的 EDA(探索性数据分析)而忽略了模型构建——这可能是我在前几次比赛中犯下的标志性错误。没有做足够的 EDA 而遗漏了某些重要内容——是的,我也犯过这样的错误。没有选择我的最后两个提交。(最终并没有带来太大的变化,但我仍然不会忘记这一点。)

关于错误,这与我之前关于实验和有计划的观点相似。如果你从错误中学习,并且它们帮助你成长和进化,那么错误是可以接受的。但你应该尽量避免那些可以通过预见避免的简单错误。但在机器学习(和科学!)中,失败几乎是过程的一部分。不是所有的事情都会一直有效。这是可以的。但你不想一次又一次地犯同样的错误。所以,唯一的真正错误就是没有从错误中学习。这在 Kaggle 竞赛和生活中都是正确的。

您是否推荐使用特定的工具或库来进行数据分析或机器学习?

我知道我们越来越生活在 Python 的世界里,但说到表格处理和数据分析可视化,我仍然更喜欢 R 及其 tidyverse:dplyr、ggplot2、lubridate 等。新的 tidymodels 框架是 sklearn 的有力竞争者。即使你是 Python 的死忠粉丝,偶尔看看 pandas 之外的工具也是有益的。不同的工具往往能带来不同的视角和更多的创造力。在深度学习方面,我发现 PyTorch 及其 FastAI 界面最为直观。当然,现在每个人都喜欢 huggingface;而且有很好的理由。

当人们参加比赛时,他们应该记住或做些什么最重要的事情?

最重要的是要记得享受乐趣并学到东西。在比赛期间和之后,有如此多的宝贵见解和智慧被分享,如果不吸收并从中成长,那就太可惜了。即使你唯一关心的是获胜,你也只能通过学习和实验,站在这个社区的基础上才能实现。但 Kaggle 远不止排行榜那么简单,一旦你开始为社区做出贡献并回馈,你将以更全面的方式成长。我保证。

Kaggle Learn 课程

Kaggle 的许多事情都与获取知识有关。无论是你在比赛中学到的东西,还是你在不断增长的存储库中找到的数据集,或者是展示一个以前未知的模型类别,总有新的东西可以探索。这个集合中最新的补充是汇集在Kaggle Learn标签下的课程:www.kaggle.com/learn。这些是 Kaggle 作为“获得独立数据科学项目所需技能的最快方式”进行营销的微课程,核心统一主题是跨各种主题的快速入门介绍。每个课程都分为小章节,后面跟着编码实践问题。这些课程使用 Notebooks 进行交付,其中必要的理论和阐述部分与预期你编码和实现的部分交织在一起。

下面,我们提供了一些最有用的简要概述:

  • 机器学习入门/中级机器学习: www.kaggle.com/learn/intro-to-machine-learningwww.kaggle.com/learn/intermediate-machine-learning

    这两门课程最好看作是一个两集系列:第一集介绍了机器学习中使用的不同类别的模型,随后讨论了不同模型共有的主题,如欠拟合/过拟合或模型验证。第二集深入探讨特征工程,处理缺失值和处理分类变量。对于刚开始机器学习之旅的人来说很有用

  • pandas: www.kaggle.com/learn/pandas

    这门课程提供了对现代数据科学中最基本工具之一的快速入门介绍。你首先学习如何创建、读取和写入数据,然后继续学习数据清洗(索引、选择、组合、分组等)。对于初学者(有时 pandas 的功能可能会令人不知所措)和实践者(作为复习/参考)都很有用

  • 游戏人工智能: www.kaggle.com/learn/intro-to-game-ai-and-reinforcement-learning

    这门课程是对 Kaggle 在学习模块中引入的技术课程部分的完美总结。你将编写一个游戏代理,调整其性能,并使用最小-最大算法。这可能是强化学习实践导向的入门介绍

  • 机器学习可解释性: www.kaggle.com/learn/machine-learning-explainability

    建立模型很有趣,但在现实世界中并非每个人都是数据科学家,所以你可能会发现自己需要向他人解释你所做的工作。这就是这个关于模型可解释性的迷你课程发挥作用的地方:你将学习如何使用三种不同的方法来评估你的特征的相关性:置换重要性、SHAP 和部分依赖图。对于在商业环境中使用机器学习的人来说,这极为有用,因为项目的成败取决于信息传达的好坏

  • AI 伦理: www.kaggle.com/learn/intro-to-ai-ethics

    这最后一门课程是对提案的一个非常有趣的补充:它讨论了指导 AI 系统道德设计的实用工具。你将学习如何识别 AI 模型中的偏差,检验 AI 公平性的概念,并了解如何通过传达 ML 模型信息来提高透明度。对于从业者来说非常有用,因为“负责任的 AI”这个短语我们将越来越常听到

除了 Kaggle 原创内容外,平台还有其他通过用户创建的笔记本提供的学习机会;鼓励读者自行探索。

Andrada_Olteanu

Andrada Olteanu

www.kaggle.com/andradaolteanu

Andrada Olteanu 是一位非常鼓励从笔记本中学习的 Kaggle 笔记本大师。Andrada 是 HP 全球数据科学大使,Endava 的数据科学家,以及 Weights & Biases 的 Dev 专家。我们与 Andrada 讨论了笔记本竞赛、她的职业生涯以及其他内容。

你最喜欢的竞赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

我会说我擅长的 Kaggle 领域更偏向于数据可视化,因为它使我能够将艺术和创意与数据相结合。

我不会说我有一个最喜欢的竞赛类型,但我会说我更喜欢偶尔变换一下,选择我觉得有趣的内容。

Kaggle 的美丽之处在于,一个人可以在学习多个数据科学领域(计算机视觉、NLP、探索性数据分析与统计学、时间序列等)的同时,也熟悉和舒适地了解许多主题(如体育、医疗领域、金融和加密货币、全球事件等)。

另一件很棒的事情是,例如,如果有人想提高处理文本数据的能力,几乎总有一个需要自然语言处理(NLP)的 Kaggle 竞赛。或者,如果有人想学习如何预处理和建模音频文件,也有竞赛可以培养这种技能。

告诉我们您参加的一个特别具有挑战性的竞赛,以及您使用了哪些见解来应对这项任务。

我参加过的最具挑战性的“竞赛”是“Kaggle 数据科学和机器学习年度调查”。我知道这并不是一个“真正的”竞赛——涉及排行榜和强大的机器学习——然而对我来说,这是我参与并学到最多的一次竞赛。

这是一个笔记本竞赛,用户必须发挥创意才能赢得 Kaggle 提供的 5 个奖项之一。我连续参加了两年。在第一年(2020 年),它挑战了我的更“基础”的视觉化技能,并迫使我跳出思维定式(我获得了第三名);在第二年(2021 年),我通过学习 D3 为它准备了大约 4 个月,试图在我的数据可视化技能上达到一个新的水平(仍在审查中;到目前为止,我已经赢得了“早期笔记本奖”奖项)。我可以提供的最佳见解是:

  • 首先,不要迷失在数据中,并尝试创建尽可能准确的图表;如果需要,建立双重验证方法以确保你所展示的是清晰和简洁的。没有什么比一个展示不准确见解的美丽图表更糟糕了。

  • 尝试从你周围寻找灵感:从自然、从电影、从你的工作中。你可以借鉴惊人的主题和有趣的方式来美化你的可视化。

Kaggle 是否帮助你在职业生涯中取得进步?如果是,如何?

是的。非常。我相信我现在在职业生涯中所处的位置很大一部分要归功于 Kaggle,为此我永远感激。通过 Kaggle,我成为了 HP 的 Z 大使;我还发现了 Weights & Biases,这是一个惊人的机器学习实验平台,现在我是他们的一名自豪的 Dev 专家。最后但同样重要的是,通过这个平台,我与我现在的 Endava 首席数据科学家建立了联系,他招募了我,并且我从那时起一直与他合作。简而言之,我在 Endava 的职位以及我与两家大型公司(HP 和 Weights & Biases)的联系都是我 Kaggle 平台活动直接的结果。

我认为 Kaggle 最被忽视的方面是社区。Kaggle 拥有最大的群体,所有的人都聚集在一个方便的地方,你可以从中建立联系、互动和学习。

最好的利用方式是,例如,从每个 Kaggle 部分(比赛、数据集、笔记本——如果你愿意,还可以是讨论)中选出前 100 人,然后在 Twitter/LinkedIn 上关注那些在个人资料上分享这些信息的人。这样,你可以开始定期与这些充满洞察力和知识的人互动。

你在过去比赛中犯过哪些错误?

我在过去比赛中犯的最大错误是没有参加比赛。我相信这是初学者进入平台时犯的最大、最根本的错误。

出于恐惧(我是在个人经验的基础上说的),他们认为他们还没有准备好,或者他们不知道如何开始。幸运的是,如果你遵循一个简单的系统,进入任何比赛都会变得非常容易:

  • 参加任何你喜欢的或听起来有趣的比赛。

  • 探索描述页面和数据。

  • 如果你没有任何开始的方法,不用担心!只需进入“代码”部分,寻找获得大量点赞或由经验丰富的人,如大师级人物制作的笔记本。开始做一个“代码跟随”笔记本,在那里你查看别人做了什么,并“复制”它,研究并尝试自己改进。在我看来,这是最好的学习方式——你永远不会卡住,你通过在一个具体项目中实践来学习。

当人们参加比赛时,他们应该记住或做最重要的事情是什么?

他们应该记住,失败是可以接受的,因为通常这是最好的学习方式。

他们还应该记住的是,始终向竞赛大师学习,因为他们通常是那些分享和解释可能从未想到过的机器学习技术的人。学习某样东西的最好方式是观察那些“已经成功”的人,这样你的成功之路就不会那么颠簸,而是更加无痛、顺畅和迅速。选择 2-3 位你真正钦佩的大师,让他们成为你的老师;研究他们的笔记,一起编写代码,尽可能多地学习。

你是否使用其他竞赛平台?它们与 Kaggle 相比如何?

我从未使用过任何其他竞赛平台——仅仅因为我感觉 Kaggle 已经拥有一切。

摘要

在本章中,我们讨论了 Kaggle 笔记,这是一种多用途、开放的编码环境,可用于教育、实验,以及推广你的数据科学项目组合。你现在可以创建自己的笔记,有效地利用可用资源,并将结果用于竞赛或个人项目。

在下一章中,我们将介绍讨论论坛,这是在 Kaggle 上交换想法和意见的主要形式。

加入我们书的 Discord 空间

加入本书的 Discord 工作空间,参加每月一次的作者“问我任何问题”活动:

packt.link/KaggleDiscord

第四章:利用讨论论坛

讨论论坛是 Kaggle 上信息交流的主要手段。无论是讨论正在进行的竞赛,还是关于数据集或笔记本的对话,Kagglers 总是在谈论事情。

在本章中,我们介绍了讨论论坛:它们的组织方式以及管理其中丰富信息的守则。我们涵盖了以下主题:

  • 论坛如何运作

  • 竞赛讨论的方法

  • 网络礼仪

论坛如何运作

您可以通过多种方式进入讨论论坛。最直接的方式是在左侧面板中点击讨论

包含文本的图片 自动生成的描述

图 4.1:从主菜单进入讨论页面

顶部部分包含论坛,它们是一般主题的聚合。浏览这些内容对您来说很有用,无论您是第一次参加竞赛,还是提出建议,或者只是因为感到迷茫而有一个一般性的问题。

在论坛下方,您可以找到 Kaggle 上讨论的合并视图:主要是与竞赛相关的对话(这构成了 Kaggle 上大部分的活动),但也包括笔记本或值得注意的数据集。默认情况下,它们按热度排序;换句话说,参与度和活动量最高的内容显示在顶部附近。在这个部分,您可以找到与该领域动态性质更相关的内容:来自 Kaggle 不同子集的讨论集合,您可以根据特定标准进行筛选:

包含文本的图片 自动生成的描述

图 4.2:来自 Kaggle 的讨论

根据您的兴趣,您可以通过使用筛选器开始个性化内容。根据您的偏好,您可以按以下方式筛选:

  • 最近性:允许您控制您正在追赶的信息范围

  • 我的活动:如果您需要查看所有论坛中的评论/出版物/观点的概览;如果您同时参与多个讨论,这将非常有用

  • 管理员:提供 Kaggle 管理员公告的快速概览

  • 类型:讨论可以在一般论坛、特定竞赛或数据集周围进行

  • 标签:虽然并非无处不在,但有几个讨论被标记,并且这种功能允许用户利用这一事实:

包含文本的图片 自动生成的描述

图 4.3:讨论可用的筛选器

下一个图显示了在初学者标签上筛选讨论的示例输出:

包含文本的图片 自动生成的描述

图 4.4:筛选标记为“初学者”的讨论

作为一种替代,你也可以专注于一个特定的主题;由于像计算机视觉这样的主题吸引了大量关注,因此对主题进行排序可能是有用的。你可以按热度最近评论最近发布最多投票最多评论排序:

包含文本的图像 自动生成的描述

图 4.5:通用讨论论坛中的计算机视觉主题子集

人们来到 Kaggle 出于各种原因,但尽管 Notebooks 的流行度有所增长,比赛仍然是主要的吸引力。每个 Kaggle 比赛都有自己的专用讨论论坛,你可以通过进入比赛页面并选择讨论来进入:

包含文本的图像 自动生成的描述

图 4.6:比赛的讨论论坛

并非总是如此,但如今几乎所有的比赛都在其专用讨论论坛的顶部固定了一个 FAQ 主题。从这里开始是两个主要原因的好主意:

  • 这可以节省你的时间;最流行的查询可能在那里得到了解答。

  • 你可以避免在论坛的其余部分提出重复或重复的问题,从而使每个人的体验都变得更好。

与 Notebooks 一样,讨论论坛有一个选项,允许你将特别相关的主题标记为书签以供以后参考:

图 4.7:在讨论论坛中标记一个主题

你可以在个人资料页面上找到所有标记主题的概述:

包含文本的图像 自动生成的描述

图 4.8:在讨论论坛中标记一个主题

示例讨论方法

在某个时刻在比赛中感到迷茫是完全正常的事情:你来了,尝试了一些想法,在排行榜上获得了一些进展,然后你遇到了 Kaggle 版本的跑步者墙。这是讨论论坛成为咨询场所的时刻。

例如,我们将查看Optiver 实现波动率预测比赛(www.kaggle.com/c/optiver-realized-volatility-prediction),组织者是这样描述的:

在这个比赛的最初三个月,你将构建预测不同行业数百只股票短期波动的模型。你将拥有数亿行高度细粒度的金融数据在你的指尖,你将利用这些数据设计你的模型,预测 10 分钟期间的波动。你的模型将在训练后的三个月评估期内收集的真实市场数据上进行评估。

这里有很多东西需要解释,因此我们将概述这个挑战的主要组成部分,并展示它们如何通过讨论论坛来处理。首先,参加这个比赛需要一定程度的金融知识;可能不是经验丰富的交易员水平,但理解计算波动性的不同方式对于外行人来说当然不是微不足道的(在这个特定问题上,大多数 Kagglers 都是外行人)。幸运的是,对于参赛者来说,组织者在比赛期间非常活跃,并提供了旨在帮助新进入该领域的人的资源:www.kaggle.com/c/optiver-realized-volatility-prediction/discussion/273923

如果入门知识仍然不足以开始,请不要犹豫,在公共场合弄清楚事情并寻求帮助,就像这里:www.kaggle.com/c/optiver-realized-volatility-prediction/discussion/263039

或者这里:www.kaggle.com/c/optiver-realized-volatility-prediction/discussion/250612

随着比赛的进行,人们开始开发越来越复杂的模型来处理这个问题。这里需要找到一个平衡点:一方面,如果你从分享发现的前辈那里学到了东西,你可能想回馈一些东西;另一方面,你不想通过发布所有优秀的代码作为笔记本来泄露你的(潜在的)优势。一个合理的折衷方案是在论坛比赛的帖子中讨论,例如,你的特征想法,就像这个帖子一样:www.kaggle.com/c/optiver-realized-volatility-prediction/discussion/273915

近年来,越来越多的比赛正在摆脱固定的测试数据集格式,并引入某种变化:有时它们强制使用 Kaggle API(这些比赛需要从笔记本提交),其他则引入一个特殊的日程表,分为训练阶段和对实时数据的评估。Optiver 就是这种情况:

在最终提交截止日期之后,将定期更新排行榜,以反映将针对所选笔记本运行的市场数据更新。更新将大约每两周进行一次,并调整以避免冬季假期。

虽然制定起来很简单,但这种设置在重新训练和更新模型时产生了一些挑战。如果你遇到这种情况,请随时提问,就像在这个比赛中参赛者所做的那样:www.kaggle.com/c/optiver-realized-volatility-prediction/discussion/249752

在 Kaggle 竞赛中,为你的训练模型制定一个验证方案始终是一个重要的话题,通常与永久的“CV vs LB”(交叉验证与排行榜)讨论相结合。Optiver 竞赛也不例外:www.kaggle.com/c/optiver-realized-volatility-prediction/discussion/250650

除非已经存在这样的帖子——并且检查一下总是个好主意,以最大限度地减少冗余——你可能还想考虑一种相关的帖子类型:单一模型性能。迟早,每个人都会开始使用模型集成,但没有好的单一模型组件,它们效率并不高。对知识的协作追求不会就此停止:如果你认为你找到了处理问题的更好方法,分享它可能是个好主意。要么你会为他人做些有用的事情,要么你会发现自己错了(节省你的时间和精力);无论如何,正如这个讨论所示,这是一个胜利,例如:www.kaggle.com/c/optiver-realized-volatility-prediction/discussion/260694

除了显而易见的个人好处(你可以窥见其他竞争对手的表现),这样的帖子还允许社区内进行信息交流,促进协作元素,对初学者也有帮助。这样的讨论例子可以在www.kaggle.com/c/optiver-realized-volatility-prediction/discussion/250695找到。

如果你已经阅读了上述话题,你仍然可能会想知道:我遗漏了什么重要的东西吗? Kaggle 是一个可以完美提问的地方:www.kaggle.com/c/optiver-realized-volatility-prediction/discussion/262203

让我们拓宽我们的关注范围,到其他竞赛来结束这一部分。我们上面提到了验证,这对于 Kaggler 来说总是与信息泄露和过拟合的话题相关联。在第六章中广泛讨论了泄露问题,该章节专门讨论设计验证方案。在这里,我们简要地讨论了它们是如何通过讨论来处理的。由于 Kaggle 是一个好奇的人组成的社区,如果有泄露的怀疑,有人很可能会提出这个话题。

例如,文件名或记录的 ID 可能包含时间戳,这意味着它们可以被逆向工程,有效地窥视未来,并产生一个不切实际的低误差指标值。这种情况发生在Two Sigma Connect竞赛中(www.kaggle.com/c/two-sigma-connect-rental-listing-inquiries/)。你可以在 Kazanova 的帖子中了解更多详情:www.kaggle.com/c/two-sigma-connect-rental-listing-inquiries/discussion/31870#176513

另一个例子是空中客车船检测挑战赛(www.kaggle.com/c/airbus-ship-detection),参赛者需要在卫星图像中定位船只。结果发现,相当一部分测试图像是训练图像中的随机裁剪,匹配这两者相对简单:www.kaggle.com/c/airbus-ship-detection/discussion/64355#377037

一系列相当臭名昭著的竞赛是由桑坦德银行赞助的。在这家公司组织的三次 Kaggle 竞赛中,两次涉及数据泄露:www.kaggle.com/c/santander-value-prediction-challenge/discussion/61172

接下来发生的事情因竞赛而异:有几次 Kaggle 决定用新的或清理后的数据重置竞赛,但也有时候他们允许竞赛继续(因为他们认为影响很小)。处理这种情况的一个例子可以在预测红帽商业价值竞赛中找到:www.kaggle.com/c/predicting-red-hat-business-value/discussion/23788

尽管数据泄露可能会严重扰乱竞赛,但好消息是,在过去的 2-3 年里,Kaggle 上的数据泄露几乎已经消失——所以,希望这部分内容只会被阅读一次,而不会成为你在平台上经验的常态。

在平台上经验的主题是进行大师访谈的一个很好的过渡。

谢一帆

www.kaggle.com/yifanxie

谢一帆是一位讨论和竞赛大师,同时也是 Arion.ai 的联合创始人。以下是他对参加竞赛和与其他 Kagglers 合作的一些看法。

你最喜欢的竞赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

我实际上没有特别喜欢的类型;我喜欢解决各种类型的问题。在技术方面,我已经建立了一个坚实的机器学习模块流水线,使我能够快速地将典型技术和算法应用于大多数数据问题。我认为这对我来说是一种竞争优势:专注于标准化,无论是工作流程还是随着时间的推移技术工件。这允许更快地进行迭代,从而有助于提高进行数据实验的效率,这是 Kaggle 的核心组成部分。

你是如何处理 Kaggle 竞赛的?这种处理方式与你在日常工作中所做的是如何不同的?

随着时间的推移,我发展了一种特定的方式来管理和收集我大多数主要数据项目的信息。这适用于工作、Kaggle 竞赛和其他副项目。通常,我会以标准化的格式捕获有用的信息,如书签、数据字典、待办事项列表、有用的命令和实验结果,并为每个竞赛分配一个格式,当在团队中竞赛时,我会与我的队友分享这些信息。

请告诉我们你参加的一个特别具有挑战性的竞赛,以及你使用了哪些见解来处理这项任务?

对我来说,了解竞赛的更广泛背景总是很有用的;例如,支撑和产生我们正在工作的数据的社交/工程/金融流程是什么?对于可以有意义地观察个别数据点的竞赛,例如深度伪造检测挑战赛,我会构建一个特定的仪表板(通常使用 Streamlit),使我能够检查个别数据点(在这种情况下,是一对真实和虚假的视频),以及将简单的统计收集整合到仪表板中,以便我更好地了解数据。

Kaggle 是否帮助你在职业生涯中取得进步?如果是,是如何帮助的?

我认为 Kaggle 是我作为数据科学咨询公司共同所有者的当前职业道路中最有帮助的平台。它使我能够在几年内建立解决不同领域数据问题的技能和方法。我从 Kaggle 竞赛的团队组建中认识了一些客户和同事,尽管我现在在 Kaggle 上的活动较少,但它始终是我知识来源的好帮手。

在你的经验中,没有经验的 Kaggle 参赛者通常忽略了什么?你现在知道什么,而当时你刚开始时希望知道的?

对于 Kaggle 的新手来说,我能看到的错误是忽略了关键的非技术问题:团队规则、数据使用、私人信息的共享、出于无害原因使用多个账户等。这些错误可能会完全无效化一个人通常需要数月才能完成的竞赛努力。

我最初希望知道的一件事就是不要担心在公共排行榜上的日常位置——这给自己带来了不必要的压力,并导致过度拟合。

对于数据分析或机器学习,有没有任何特定的工具或库推荐使用?

通常:Scikit-learn、XGB/LGB、PyTorch 等。我推荐大家学习并精通的一个工具是 NumPy,特别是对于更高级的信息排序和子集操作;这些是 pandas 通过懒惰方法可以轻松完成的事情,但 NumPy 中的更复杂版本会带来更高的效率。

当人们参加竞赛时,他们应该牢记或做些什么最重要的事情?

在我的书中,从事任何数据科学相关工作的原因有四个:为了盈利、为了知识、为了乐趣和为了公益。对我来说,Kaggle 始终是一个知识宝库,而且经常是一个很好的记忆来源,所以我的建议总是提醒自己,排名是暂时的,但知识和记忆是永恒的:)

你是否使用其他竞赛平台?它们与 Kaggle 相比如何?

我是一个 非常活跃的 Numerai 参与者。对我来说,基于我从事数据科学的四个原因,它更多的是为了盈利,因为他们通过其加密货币提供收益。它更多的是一种个人努力,因为在像 Numerai 这样的交易竞赛平台上,团队协作并没有真正的好处;他们既不鼓励也不禁止,但就是这样,更多的人力资源并不总是等同于更好的盈利。

对我来说,在忙碌的工作日程期间,Numerai 比 Kaggle 更可持续,因为每轮的训练数据通常保持不变,我可以在构建初始模型后实现高度的生产化,以自动化预测和提交。

Numerai 的连续性功能使其更适合那些想要为表格数据集构建专用机器学习管道的人。

网络礼仪

任何在线超过 15 分钟的人都知道这一点:在讨论中,无论话题多么无辜,总有可能人们会变得情绪化,对话会离开文明光谱的文明部分。Kaggle 也不例外,因此社区有适当的举止指南:www.kaggle.com/community-guidelines

这些原则不仅适用于讨论,也适用于 Notebooks 和其他形式的沟通。在 Kaggle 互动时,你应该牢记以下要点:

  • 不要陷入斯科特·亚当斯所说的心灵感应错觉:Kaggle 是一个来自世界各地(其中许多人英语不是他们的第一语言)的极其多样化的社区,所以保持细微差别是一个巨大的挑战。不要做出假设,并在可能的情况下尝试澄清。

  • 不要让事情变得个人化;上帝 wins 定律有其存在的理由。特别是,对受保护的不变特征的引用是一个绝对不能触碰的区域。

  • 你的里程可能不同,但事实仍然是:这并非 1990 年代的互联网狂野西部,当时在网上告诉某人去查阅手册(RTFM)是完全正常的;贬低往往会疏远人们。

  • 不要试图操纵进度系统(这是授予 Kaggle 奖牌的基础):这一方面涵盖了平台滥用的整个范围,从明确要求点赞,到串通,再到直接的作弊行为。

简而言之,以你希望别人如何对待你的方式对待他人,事情应该会顺利解决。

摘要

在本章中,我们讨论了讨论论坛,这是 Kaggle 平台上主要的沟通方式。我们展示了论坛的机制,向你展示了如何在更高级的比赛中利用讨论的例子,并简要总结了讨论的礼仪。

这本书的第一部分,也就是介绍部分到此结束。下一章标志着对如何最大化从 Kaggle 中获得收益的更深入探索的开始,并探讨了如何应对比赛中必须应对的众多不同任务和指标。

加入我们书籍的 Discord 空间

加入书籍的 Discord 工作空间,每月与作者进行一次“问我任何问题”的会议:

packt.link/KaggleDiscord

第二部分

锻炼技能以备竞赛

第五章:竞赛任务和指标

在竞赛中,你首先检查目标指标。理解你的模型错误是如何评估的是在每一场竞赛中取得高分的关键。当你的预测提交到 Kaggle 平台时,它们将与基于目标指标的真实数据进行比较。

例如,在泰坦尼克号竞赛(www.kaggle.com/c/titanic/)中,你所有的提交都将根据准确率进行评估,即正确预测的幸存乘客百分比。组织者选择这个指标是因为竞赛的目的是找到一个模型,该模型可以估计在类似情况下乘客生存的概率。在另一个知识竞赛房价 - 高级回归技术(www.kaggle.com/c/house-prices-advanced-regression-techniques)中,你的工作将根据你的预测与真实数据之间的平均差异进行评估。这涉及到计算对数、平方和开方,因为模型预计能够尽可能准确地量化待售房屋价格的高低顺序。

在现实世界的数据科学中,目标指标也是你项目成功的关键,尽管现实世界与 Kaggle 竞赛之间肯定存在差异。我们可以简单地总结说,现实世界中有更多的复杂性。在现实世界的项目中,你将经常面对不止一个而是多个指标,你的模型将根据这些指标进行评估。通常,一些评估指标甚至与你的预测与用于测试的真实数据之间的表现无关。例如,你正在工作的知识领域,项目的范围,你的模型考虑的特征数量,整体内存使用量,对特殊硬件(例如 GPU)的任何要求,预测过程的延迟,预测模型的复杂性,以及许多其他方面,最终可能比单纯的预测性能更重要。

现实世界的问题确实比你在参与其中之前想象的要更多地受到商业和技术基础设施的关注。

尽管如此,你无法回避这样一个事实:无论是现实世界项目还是 Kaggle 竞赛的核心基本原则都是相同的。你的工作将根据某些标准进行评估,理解这些标准的细节,以智能的方式优化你模型的拟合度,或者根据这些标准选择其参数,这些都将为你带来成功。如果你能更多地了解 Kaggle 中模型评估的流程,你的现实世界数据科学工作也将从中受益。

在本章中,我们将详细说明某些类型问题的评估指标如何强烈影响你在数据科学竞赛中构建模型解决方案时的操作方式。我们还讨论了 Kaggle 竞赛中可用的各种指标,以给你一个关于什么最重要的概念,并在边缘讨论了指标对预测性能的不同影响以及如何正确地将它们转化为你的项目。我们将涵盖以下主题:

  • 评估指标和目标函数

  • 基本任务类型:回归、分类和序型

  • Meta Kaggle 数据集

  • 处理未见过的指标

  • 回归指标(标准型和序型)

  • 二元分类指标(标签预测和概率)

  • 多类分类指标

  • 目标检测问题的指标

  • 多标签分类和推荐问题的指标

  • 优化评估指标

评估指标和目标函数

在 Kaggle 竞赛中,你可以在竞赛的概述页面的左侧菜单中找到评估指标。通过选择评估选项卡,你可以获得关于评估指标的具体信息。有时你会找到指标公式、重现它的代码以及一些关于指标的讨论。在同一页面上,你还可以获得关于提交文件格式的说明,提供文件的标题和一些示例行。

评估指标与提交文件之间的关联很重要,因为你必须考虑到指标在训练模型并产生一些预测后基本上才会工作。因此,作为第一步,你必须考虑评估指标目标函数之间的区别。

将一切简化到基本原理,目标函数在训练过程中服务于你的模型,因为它涉及到错误最小化(或根据问题进行分数最大化)。相比之下,评估指标在模型训练后提供服务,通过提供一个分数。因此,它不能影响模型如何拟合数据,但以间接方式影响它:通过帮助你选择模型中最出色的超参数设置,以及竞争中的最佳模型。在继续本章的其余部分之前,这部分将向你展示这如何影响 Kaggle 竞赛以及为什么分析 Kaggle 评估指标应该是你在竞赛中的首要行动,让我们首先讨论一些你可能在讨论论坛中遇到的术语。

你经常会听到关于目标函数、损失函数和损失函数的讨论,有时可以互换使用。然而,它们并不完全相同,我们在这里解释了区别:

  • 损失函数是在单个数据点上定义的函数,它考虑了模型的预测和该数据点的真实值,计算一个惩罚。

  • 代价函数考虑了用于训练的整个数据集(或其一部分批次),通过计算其数据点的损失惩罚的总和或平均值。它可以包括进一步的约束,例如 L1 或 L2 惩罚,例如。代价函数直接影响到训练过程。

  • 目标函数是与机器学习训练中优化范围最通用(且安全使用)的术语:它包括代价函数,但不仅限于它们。实际上,目标函数还可以考虑与目标无关的目标:例如,要求估计模型的稀疏系数或系数值的极小化,如在 L1 和 L2 正则化中。此外,虽然损失和代价函数暗示基于最小化的优化过程,但目标函数是中立的,可以暗示学习算法执行的是最大化或最小化活动。

同样,当涉及到评估指标时,你会听到关于评分函数和误差函数的讨论。区分它们很容易:一个评分函数如果函数的分数更高,则暗示更好的预测结果,这意味着一个最大化过程。

误差函数则暗示如果函数报告较小的误差量,则会有更好的预测,这意味着一个最小化过程。

基本任务类型

并非所有目标函数都适用于所有问题。从一般的角度来看,你会在 Kaggle 竞赛中找到两种类型的问题:回归任务和分类任务。最近,也出现了强化学习RL)任务,但 RL 不使用指标进行评估;相反,它依赖于与其他竞争对手的直接对抗产生的排名,假设这些竞争对手的解决方案与你的表现相当(在这个对抗中表现优于你的同伴将提高你的排名,表现较差将降低它)。由于 RL 不使用指标,我们仍将继续提到回归-分类的二分法,尽管序数任务,其中你预测由整数表示的有序标签,可能逃避这种分类,并且可以使用回归或分类方法成功处理。

回归

回归需要你构建一个可以预测实数的模型;通常是一个正数,但也有预测负数的例子。一个经典的回归问题示例是 房价 - 高级回归技术,因为你需要猜测房屋的价值。回归任务的评估涉及计算你的预测与真实值之间的距离。这种差异可以通过不同的方式来评估,例如通过平方它来惩罚较大的错误,或者通过对其应用对数来惩罚错误尺度的预测。

分类

当你在 Kaggle 面对分类任务时,需要考虑更多的细微差别。实际上,分类可以是 二元多类多标签

二元问题中,你必须猜测一个示例是否应该被分类到特定的类别中(通常称为 类,并与 类进行比较)。在这里,评估可能包括对类归属的直接预测,或者对这种归属概率的估计。一个典型的例子是 泰坦尼克号 比赛,你必须猜测一个二元结果:是否幸存。在这种情况下,比赛的要求仅仅是预测,但在许多情况下,提供概率是必要的,因为在某些领域,特别是医疗应用中,需要对不同选项和情况下的阳性预测进行排序,以便做出最佳决策。

虽然在二元分类中计算正确匹配的确切数量似乎是一个有效的方法,但当正负类之间存在不平衡时,这实际上并不会很好地工作,也就是说,正负类中的示例数量不同。基于类别不平衡分布的分类需要考虑不平衡的评估指标,如果你想正确跟踪模型上的改进。

当你有超过两个类别时,你面临的是一个 多类 预测问题。这也需要使用合适的评估函数,因为需要跟踪模型的总体性能,同时也要确保跨类别的性能是可比的(例如,你的模型可能在某些类别上表现不佳)。在这里,每个案例只能属于一个类别,而不能属于其他任何类别。一个很好的例子是 叶分类 (www.kaggle.com/c/leaf-classification),其中每张叶片样本的图像都必须与正确的植物物种相关联。

最后,当你的类别预测不是互斥的,并且你可以为每个示例预测多个类别所有权时,你就遇到了一个多标签问题,这需要进一步的评估来控制你的模型是否正在预测正确的类别,以及正确的类别数量和组合。例如,在希腊媒体监测多标签分类(WISE 2014)www.kaggle.com/c/wise-2014)中,你必须将每篇文章与它所涉及的所有主题关联起来。

有序

在一个涉及对有序尺度预测的问题中,你必须猜测有序的整数数值标签,这些标签自然是按顺序排列的。例如,地震的震级就属于有序尺度。此外,市场调研问卷中的数据通常也记录在有序尺度上(例如,消费者的偏好或意见一致性)。由于有序尺度由有序值组成,因此有序任务可以被视为介于回归和分类之间的一种任务,你可以用这两种方式来解决它们。

最常见的方法是将你的有序任务视为一个多类问题。在这种情况下,你将得到一个整数值(类别标签)的预测,但预测不会考虑这些类别有一定的顺序。如果你查看类别的预测概率,你会感觉到将问题作为多类问题处理可能存在问题。通常,概率将分布在所有可能值的整个范围内,描绘出一个多模态且通常不对称的分布(而你应该期望最大概率类周围有一个高斯分布)。

解决有序预测问题的另一种方法是将它视为一个回归问题,然后对结果进行后处理。这样,类之间的顺序将被考虑在内,尽管预测输出不会立即用于评估指标上的评分。实际上,在回归中,你得到的是一个浮点数作为输出,而不是表示有序类的整数;此外,结果将包括你有序分布中的整数之间的全部值,甚至可能还包括它之外的值。通过裁剪输出值并将它们通过单位舍入转换为整数可能可行,但这可能会导致一些需要更复杂后处理的误差(我们将在本章后面进一步讨论这个问题)。

现在,你可能想知道为了在 Kaggle 上取得成功,你应该掌握哪种类型的评估。显然,你必须掌握你所参加的竞赛的评估指标。然而,一些指标比其他指标更常见,这是你可以利用的信息。最常见的指标是什么?我们如何确定在使用了类似评估指标的竞赛中寻找洞察力的地方?答案是查阅 Meta Kaggle 数据集。

Meta Kaggle 数据集

Meta Kaggle 数据集(www.kaggle.com/kaggle/meta-kaggle)是 Kaggle 社区和活动的丰富数据集合,由 Kaggle 本身作为公共数据集发布。它包含 CSV 表格,其中充满了来自竞赛、数据集、笔记本和讨论的公共活动。你只需要开始一个 Kaggle 笔记本(如你在第二章和第三章中看到的),添加 Meta Kaggle 数据集,并开始分析数据。CSV 表格每天都会更新,因此你将不得不经常刷新你的分析,但考虑到你可以从中获得的见解,这是值得的。

我们有时会在这本书中提到 Meta Kaggle 数据集,既作为许多有趣竞赛动态的灵感来源,也是为了收集有用的例子用于你的学习和竞赛策略。在这里,我们将用它来确定在过去七年里最常用于竞赛的评价指标。通过查看本章中最常见的指标,你将能够从稳固的起点开始任何竞赛,然后通过在论坛中找到的讨论来细化你对指标的了解,并掌握竞赛特有的细微差别。

在下面,我们介绍了生成指标及其每年计数的表格所需代码。它设计为可以直接在 Kaggle 平台上运行:

import numpy as np
import pandas as pd
comps = pd.read_csv("/kaggle/input/meta-kaggle/Competitions.csv")
evaluation = ['EvaluationAlgorithmAbbreviation',
              'EvaluationAlgorithmName',
              'EvaluationAlgorithmDescription',]
compt = ['Title', 'EnabledDate', 'HostSegmentTitle']
df = comps[compt + evaluation].copy()
df['year'] = pd.to_datetime(df.EnabledDate).dt.year.values
df['comps'] = 1
time_select = df.year >= 2015
competition_type_select = df.HostSegmentTitle.isin(
						['Featured', 'Research'])
pd.pivot_table(df[time_select&competition_type_select],
               values='comps',
               index=['EvaluationAlgorithmAbbreviation'],
               columns=['year'],
               fill_value=0.0,
               aggfunc=np.sum,
               margins=True
              ).sort_values(
                by=('All'), ascending=False).iloc[1:,:].head(20) 

在此代码中,我们读取包含竞赛相关数据的 CSV 表格。我们关注代表评估的列以及告诉我们竞赛名称、开始日期和类型的列。我们将行限制在自 2015 年以来举行且为特色或研究类型的竞赛(这些是最常见的类型)。我们通过创建 pandas 交叉表,结合评估算法和年份,并计算使用该算法的竞赛数量来完成分析。我们只显示前 20 个算法。

这里是生成的表格(截至写作时):

年份 2015 2016 2017 2018 2019 2020 2021 总计
评估算法
AUC 4 4 1 3 3 2 0 17
LogLoss 2 2 5 2 3 2 0 16
MAP@ 1 3 0 4 1 0 1 10
CategorizationAccuracy 1 0 4 0 1 2 0 8
MulticlassLoss 2 3 2 0 1 0 0 8
RMSLE 2 1 3 1 1 0 0 8
QuadraticWeightedKappa 3 0 0 1 2 1 0 7
MeanFScoreBeta 1 0 1 2 1 2 0 7
MeanBestErrorAtK 0 0 2 2 1 1 0 6
MCRMSLE 0 0 1 0 0 5 0 6
MCAUC 1 0 1 0 0 3 0 5
RMSE 1 1 0 3 0 0 0 5
Dice 0 1 1 0 2 1 0 5
GoogleGlobalAP 0 0 1 2 1 1 0 5
MacroFScore 0 0 0 1 0 2 1 4
分数 0 0 3 0 0 0 0 3
CRPS 2 0 0 0 1 0 0 3
OpenImagesObjectDetectionAP 0 0 0 1 1 1 0 3
MeanFScore 0 0 1 0 0 0 2 3
RSNAObjectDetectionAP 0 0 0 1 0 1 0 2

使用我们刚刚实例化的相同变量来生成表格,你还可以检查数据以找到采用你选择指标的竞赛:

metric = 'AUC'
metric_select = df['EvaluationAlgorithmAbbreviation']==metric
print(df[time_select&competition_type_select&metric_select]
        [['Title', 'year']]) 

在上面的片段中,我们决定表示使用 AUC 指标的竞赛。你只需更改表示所选指标的字符串,结果列表将相应更新。

回到生成的表格,我们可以检查在 Kaggle 主办的竞赛中最受欢迎的评估指标:

  • 两个最重要的指标彼此之间以及与二进制概率分类问题密切相关。AUC 指标有助于衡量你的模型预测概率是否倾向于以高概率预测阳性案例,而 Log Loss 指标有助于衡量你的预测概率与真实值之间的差距(并且当你优化 Log Loss 时,你也在优化 AUC 指标)。

  • 在第 3 位,我们发现 MAP@{K},这是推荐系统和搜索引擎中常用的一个指标。在 Kaggle 竞赛中,这个指标主要用于信息检索评估,例如在 座头鲸识别 竞赛(www.kaggle.com/c/humpback-whale-identification)中,你需要在五个可能的猜测中精确识别一只鲸鱼。MAP@{K} 的另一个应用示例是在 快速绘画!涂鸦识别挑战www.kaggle.com/c/quickdraw-doodle-recognition/)中,你的目标是猜测所画草图的内容,并且你有三次尝试的机会。本质上,当 MAP@{K} 是评估指标时,你可以评分的不仅仅是能否正确猜测,还包括你的正确猜测是否在一定的其他错误预测数量(函数名称中的“K”)中。

  • 只有在第 6 位,我们才能找到一个回归指标,即 RMSLERoot Mean Squared Logarithmic Error,在第 7 位是 Quadratic Weighted Kappa,这是一个特别有用的指标,用于估计模型在涉及猜测递增整数数(有序尺度问题)的问题上的性能。

当你浏览顶级指标列表时,你将不断发现这些指标在机器学习教科书中经常被讨论。在接下来的几节中,首先讨论当你遇到从未见过的指标时应该做什么(这种情况在 Kaggle 竞赛中发生的频率可能比你预期的要高),然后我们将回顾回归和分类竞赛中最常见的指标。

处理从未见过的指标

在继续之前,我们必须考虑的是,前 20 名表格并没有涵盖所有比赛中使用的指标。我们应该意识到,近年来有一些指标只被使用过一次。

让我们继续使用之前代码的结果来找出它们是什么:

counts = (df[time_select&competition_type_select]
          .groupby('EvaluationAlgorithmAbbreviation'))
total_comps_per_year = (df[time_select&competition_type_select]
                        .groupby('year').sum())
single_metrics_per_year = (counts.sum()[counts.sum().comps==1]
                           .groupby('year').sum())
single_metrics_per_year
table = (total_comps_per_year.rename(columns={'comps': 'n_comps'})
         .join(single_metrics_per_year / total_comps_per_year)
         .rename(columns={'comps': 'pct_comps'}))
print(table) 

因此,我们得到了以下表格,展示了每年有多少比赛使用了此后从未再被使用的指标(n_comps),以及这些比赛在每年中的比例(pct_comps):

 n_comps pct_comps
year                   
2015       28  0.179
2016       19  0.158
2017       34  0.177
2018       35  0.229
2019       36  0.278
2020       43  0.302
2021        8  0.250 

观察从未再被使用过的指标的竞赛相对份额,我们立即注意到它逐年增长,并在近年来达到了 25%-30%的水平,这意味着通常每三到四个竞赛中就有一个需要你从头开始研究和理解一个指标。

你可以通过一个简单的代码片段获取过去发生过的此类指标列表:

print(counts.sum()[counts.sum().comps==1].index.values) 

通过执行代码,你会得到一个类似这样的列表:

['AHD@{Type}' 'CVPRAutoDrivingAveragePrecision' 'CernWeightedAuc'
'FScore_1' 'GroupMeanLogMAE' 'ImageNetObjectLocalization'
'IndoorLocalization'  'IntersectionOverUnionObjectSegmentationBeta'
'IntersectionOverUnionObjectSegmentationWithClassification'
'IntersectionOverUnionObjectSegmentationWithF1' 'Jaccard'
'JaccardDSTLParallel' 'JigsawBiasAUC' 'LaplaceLogLikelihood'
'LevenshteinMean' 'Lyft3DObjectDetectionAP' 'M5_WRMSSE' 'MASpearmanR' 'MCRMSE' 'MCSpearmanR' 'MWCRMSE' 'MeanColumnwiseLogLoss' 'MulticlassLossOld' 'NDCG@{K}' 'NQMicroF1' 'NWRMSLE' 'PKUAutoDrivingAP' 'R2Score' 'RValue' 'RootMeanSquarePercentageError' 'SIIMDice' 'SMAPE' 'SantaResident' 'SantaRideShare' 'SantaWorkshopSchedule2019' 'TrackML'
 'TravelingSanta2' 'TwoSigmaNews' 'WeightedAUC' 'WeightedMulticlassLoss' 'WeightedPinballLoss' 'WeightedRowwisePinballLoss' 'YT8M_MeanAveragePrecisionAtK' 'ZillowMAE' 'football' 'halite' 'mab'] 

通过仔细检查,你可以找到许多与深度学习和强化学习比赛相关的指标。

当你遇到一个以前从未使用过的指标时,你会怎么做?当然,你可以依赖 Kaggle 讨论论坛中的讨论,在那里你总能找到好的灵感,以及许多愿意帮助你的 Kagglers。然而,如果你想建立自己对这一指标的知识,除了在谷歌上搜索之外,我们建议你尝试通过自己编写评估函数来实验它,即使是不完美的,并尝试模拟指标对模型产生的不同类型错误的反应。你也可以直接测试它在比赛训练数据样本或你准备好的合成数据上的功能。

我们可以引用一些 Kagglers 使用此方法的一些例子:

这可以让你对评估有更深入的了解,并比那些只依赖谷歌搜索和 Kaggle 论坛答案的竞争对手有优势。

罗汉·拉奥

www.kaggle.com/rohanrao

在我们开始探索不同的指标之前,让我们了解一下罗汉·拉奥(又名 Vopani)本人,他是H2O.ai的超级大师级高级数据科学家,关于他在 Kaggle 上的成功以及他要与我们分享的智慧。

你最喜欢的比赛类型是什么?为什么?在 Kaggle 上,你在技术和解决方法方面有什么专长?

我喜欢尝试不同类型的比赛,但我的最爱无疑是时间序列比赛。我不太喜欢行业中对时间序列的典型方法和概念,所以我倾向于通过以非常规的方式构建解决方案来创新和跳出思维定势,这对我的成功非常有帮助。

你如何处理 Kaggle 比赛?这种处理方式与你的日常工作有何不同?

对于任何 Kaggle 比赛,我的典型工作流程如下:

  • 理解问题陈述,并阅读所有与规则、格式、时间表、数据集、指标和交付成果相关的信息。

  • 深入数据。以任何可能的方式切割和分解它,并探索/可视化它,以便能够回答关于它的任何问题。

  • 使用基线模型构建一个简单的流水线,并提交以确认流程是否有效。

  • 工程师特征,调整超参数,并且 尝试多个模型以了解哪些通常有效,哪些无效。

  • 不断地回到分析数据,阅读论坛上的讨论,并尽可能调整特征和模型。也许在某个时刻可以组建团队。

  • 集成多个模型,并决定哪些提交作为最终版本。

在我的数据科学日常工作中,这些事情也经常发生。但还有两个额外需要的关键要素:

  • 为问题陈述准备和整理数据集。

  • 将最终模型或解决方案部署到生产环境中。

我在过去参与的大多数项目中,大部分时间都花在这两个活动上。

Kaggle 是否帮助了你的职业生涯?如果是,是如何帮助的?

我在机器学习中学到的绝大多数东西都来自 Kaggle。社区、平台和内容都是纯金,你可以学到的东西非常多。

对我最有益的是参加 Kaggle 比赛的经验;它使我在理解、构建和解决跨领域问题方面有了巨大的信心,我能够成功地将其应用于 Kaggle 之外的公司和项目中。

许多招聘人员联系我,寻求查看我的 Kaggle 成就的机会,主要是在竞赛方面。它相当好地表明了候选人在解决数据科学问题方面的能力,因此这是一个展示你的技能和建立作品集的绝佳平台。

你在过去比赛中犯过哪些错误?

我在每一场比赛中都犯过一些错误!这就是你学习和进步的方式。有时是编码错误,有时是验证设置有缺陷,有时是提交选择不正确!

重要的是要从这些经验中学习,并确保你不会重复它们。自动迭代这个过程有助于提高你在 Kaggle 上的整体表现。

你会推荐使用哪些特定的工具或库来进行数据分析/机器学习?

我坚信永远不要与一项技术结婚。使用最好的,最舒适和最有效的,但始终开放学习新的工具和库。

回归的度量(标准和序数)

当处理回归问题时,即涉及估计一个连续值(可能从负无穷大到正无穷大)的问题时,最常用的误差度量是RMSE(根均方误差)和MAE(平均绝对误差),但你也可以发现一些稍微不同的误差度量很有用,例如 RMSLE 或 MCRMSLE。

均方误差(MSE)和 R 平方

根均方误差是均方误差(MSE)的平方根,这实际上就是你在学习回归如何工作时学到的平方误差和(SSE)的平均值。

这是 MSE 的公式:

图片

让我们先解释一下公式是如何工作的。首先,n表示案例数量,是真实值,是预测值。你首先得到你的预测值和真实值之间的差异。然后你对差异进行平方(这样它们就变成正数或简单地为零),然后将它们全部相加,得到你的 SSE。然后你只需将这个度量除以预测的数量,以获得平均值,即 MSE。通常,所有回归模型都最小化 SSE,所以你不会在尝试最小化 MSE 或其直接导数(如R 平方,也称为确定系数)时遇到太大问题,它由以下公式给出:

图片

在这里,SSE(平方误差和)与平方和总(SST)进行比较,这实际上是响应的方差。实际上,在统计学中,SST 被定义为你的目标值与它们的平均值之间的平方差:

图片

换句话说,R 平方比较了模型平方误差与可能的最简单模型(响应平均值)的平方误差。由于 SSE 和 SST 具有相同的尺度,R 平方可以帮助你确定转换目标是否有助于获得更好的预测。

请记住,线性变换,如 minmax(scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html)或标准化(scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html),不会改变任何回归器的性能,因为它们是目标值的线性变换。非线性变换,如平方根、立方根、对数、指数及其组合,则应该肯定地修改你的回归模型在评估指标上的性能(如果你选择了正确的变换,希望是更好的)。

MSE 是用于比较应用于同一问题的回归模型的一个很好的工具。坏消息是 MSE 在 Kaggle 竞赛中很少使用,因为 RMSE 更受欢迎。实际上,通过取 MSE 的根,其值将类似于你的目标原始尺度,这将更容易一眼看出你的模型是否做得很好。此外,如果你正在考虑跨不同数据问题(例如,跨各种数据集或数据竞赛)的相同回归模型,R 平方更好,因为它与 MSE 完美相关,其值介于 0 和 1 之间,这使得所有比较都更容易。

根均方误差(RMSE)

RMSE 只是 MSE 的平方根,但这意味着一些微妙的变化。以下是它的公式:

图片

在上述公式中,n表示案例数量,图片是真实值,图片是预测值。在 MSE 中,由于平方操作,大的预测误差会受到极大的惩罚。在 RMSE 中,由于根效应,这种主导性减弱(然而,你应该始终注意异常值;无论你是基于 MSE 还是 RMSE 进行评估,它们都可能对你的模型性能产生很大影响)。

因此,根据问题,你可以在将目标值开平方(如果可能的话,因为这需要正值)然后平方结果之后,使用均方误差(MSE)作为目标函数来获得更好的算法拟合。Scikit-learn 中的TransformedTargetRegressor等函数可以帮助你适当地转换回归目标,以便在评估指标方面获得更好的拟合结果。

最近使用 RMSE 的竞赛包括:

根均方对数误差 (RMSLE)

MSE(均方误差)的另一种常见转换是根均方对数误差RMSLE)。MCRMSLE 只是由 COVID-19 预测竞赛推广的一种变体,它是当存在多个单一目标时,每个目标 RMSLE 值的列平均值。以下是 RMSLE 的公式:

在公式中,n表示案例数量,是真实值,是预测值。由于你在所有其他平方、平均和开根运算之前对预测值和真实值应用了对数变换,因此你不会对预测值和实际值之间的大差异进行惩罚,尤其是在两者都是大数的情况下。换句话说,当你使用 RMSLE 时,你最关心的是预测值与真实值规模的比例。与 RMSE 一样,如果你在拟合之前对目标应用对数变换,机器学习算法可以更好地优化 RMSLE(然后使用指数函数逆转效果)。

最近使用 RMSLE 作为评估指标的竞赛包括:

到目前为止,RMSLE(Root Mean Squared Log Error,根均方对数误差)是 Kaggle 竞赛中回归问题最常用的评估指标。

均值绝对误差 (MAE)

MAE平均绝对误差)评估指标是预测值与目标之间的差异的绝对值。以下是 MAE 的公式:

在公式中,n代表案例数量,是真实值,而是预测值。MAE 对异常值不敏感(与 MSE 不同,其中误差是平方的),因此你可能会发现它是在许多竞赛中使用的评估指标,这些竞赛的数据集包含异常值。此外,你可以轻松地与之合作,因为许多算法可以直接将其用作目标函数;否则,你可以通过仅对目标的平方根进行训练,然后对预测值进行平方来间接优化它。

在下行方面,使用 MAE 作为目标函数会导致收敛速度大大减慢,因为你实际上是在优化预测目标的中位数(也称为 L1 范数),而不是均值(也称为 L2 范数),正如 MSE 最小化所发生的那样。这导致优化器需要进行更复杂的计算,因此训练时间甚至可以根据你的训练案例数量呈指数增长(例如,参见这个 Stack Overflow 问题:stackoverflow.com/questions/57243267/why-is-training-a-random-forest-regressor-with-mae-criterion-so-slow-compared-to)。

最近一些使用 MAE 作为评估指标的著名竞赛包括:

在之前提到了 ASHRAE 竞赛之后,我们也应该提到回归评估指标与预测竞赛的相关性。例如,最近举办了 M5 预测竞赛(mofc.unic.ac.cy/m5-competition/),而且所有其他 M 竞赛的数据也是可用的。如果你对预测竞赛感兴趣,其中 Kaggle 上有几个,请参阅robjhyndman.com/hyndsight/forecasting-competitions/以了解 M 竞赛的概述以及 Kaggle 在从这些竞赛中获得更好的实际和理论结果方面的重要性。

从本质上讲,预测竞赛并不需要与回归竞赛非常不同的评估方法。在处理预测任务时,确实可以获取一些不寻常的评估指标,例如加权均方根误差www.kaggle.com/c/m5-forecasting-accuracy/overview/evaluation)或对称平均绝对百分比误差,更广为人知的缩写为sMAPEwww.kaggle.com/c/demand-forecasting-kernels-only/overview/evaluation)。然而,最终它们只是常规 RMSE 或 MAE 的变体,你可以通过正确的目标转换来处理。

分类(标签预测和概率)的指标

在讨论了回归问题的指标之后,我们现在将说明分类问题的指标,从二元分类问题(当你需要预测两个类别之间)开始,然后到多类别(当你有超过两个类别),最后到多标签(当类别重叠时)。

准确率

在分析二元分类器的性能时,最常用且易于获取的指标是准确率。误分类错误是指你的模型对一个例子预测了错误的类别。准确率仅仅是误分类错误的补数,它可以计算为正确数字的数量除以答案数量的比率:

图片

这个指标已经在例如木薯叶病分类www.kaggle.com/c/cassava-leaf-disease-classification)和文本规范化挑战 - 英语语言www.kaggle.com/c/text-normalization-challenge-english-language)中使用过,在这些挑战中,只有当你的预测文本与实际字符串匹配时,你才被认为做出了正确的预测。

作为指标,准确率强烈关注模型在实际环境中的有效性能:它告诉你模型是否按预期工作。然而,如果你的目的是评估和比较,并清楚地了解你的方法实际上有多有效,那么在使用准确率时你必须谨慎,因为它可能导致错误的结论,尤其是在类别不平衡(它们有不同的频率)时。例如,如果一个特定的类别仅占数据的 10%,那么一个只预测多数类别的预测器将会有 90%的准确率,尽管准确率很高,但它实际上相当无用。

如何发现这样的问题?您可以通过使用混淆矩阵轻松做到这一点。在混淆矩阵中,您创建一个双向表,比较行上的实际类别与列上的预测类别。您可以使用 Scikit-learn 的confusion_matrix函数创建一个简单的混淆矩阵:

sklearn.metrics.confusion_matrix(
    y_true, y_pred, *, labels=None, sample_weight=None,
    normalize=None
) 

提供y_truey_pred向量就足以返回一个有意义的表格,但您也可以提供行/列标签和考虑中的示例的样本权重,并在真实示例(行)、预测示例(列)或所有示例上归一化(将边缘设置为求和为 1)。一个完美的分类器将所有案例都位于矩阵的主对角线上。如果对角线上的某个单元格中案例很少或没有,则突出显示预测器的有效性问题。

为了让您更好地了解其工作原理,您可以尝试 Scikit-learn 提供的图形示例,网址为scikit-learn.org/stable/auto_examples/model_selection/plot_confusion_matrix.html#sphx-glr-auto-examples-model-selection-plot-confusion-matrix-py

图 5.1:混淆矩阵,每个单元格归一化到 1.00,以表示匹配的份额

您可以通过考虑每个类别的相对精度并取平均值来尝试提高准确性的可用性,但您会发现依赖其他指标如精确度召回率F1 分数更有用。

精确度和召回率

为了获得精确度和召回率指标,我们再次从混淆矩阵开始。首先,我们必须命名每个单元格:

预测
实际
假阴性

表 5.1:带有单元格名称的混淆矩阵

这里是我们定义单元格的方式:

  • TP真阳性):这些位于左上角单元格中,包含被正确预测为正例的示例。

  • FP假阳性):这些位于右上角单元格中,包含被预测为正但实际上是负的示例。

  • FN假阴性):这些位于左下角单元格中,包含被预测为负但实际上是正的示例。

  • TN真阴性):这些位于右下角单元格中,包含被正确预测为负例的示例。

使用这些单元格,您可以实际上获取更多关于您的分类器如何工作以及如何更好地调整模型的信息。首先,我们可以轻松地修改准确度公式:

然后,第一个有意义的度量指标被称为精度(或特异性),它实际上是正例的准确率:

图片

在计算中,只涉及真正例的数量和假正例的数量。本质上,这个度量指标告诉你,当你预测正例时,你有多正确。

显然,你的模型可以通过只对它有高置信度的示例预测正例来获得高分。这实际上是这个度量指标的目的:迫使模型只在它们确定并且这样做是安全的时候预测正类。

然而,如果你还希望尽可能多地预测出正例,那么你还需要关注召回率(或覆盖率灵敏度甚至真正例率)指标:

图片

在这里,你还需要了解假阴性。这两个度量指标有趣的地方在于,由于它们基于示例分类,而分类实际上是基于概率(通常在正负类之间设置在0.5阈值),你可以改变阈值,其中一个度量指标会得到改善,而另一个则会付出代价。

例如,如果你提高阈值,你会得到更高的精度(分类器对预测更有信心),但召回率会降低。如果你降低阈值,你会得到较低的精度,但召回率会提高。这也被称为精度/召回率权衡

Scikit-learn 网站提供了一个简单实用的概述,介绍了这个权衡:scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html,帮助你绘制精度/召回率曲线,从而理解这两个度量指标如何相互交换以获得更适合你需求的结果:

图片

图 5.2:具有特征步骤的两类精度-召回率曲线

与精度/召回率权衡相关的一个度量指标是平均精度。平均精度计算从 0 到 1 的召回值对应的平均精度(基本上,当你将阈值从 1 变到 0 时)。平均精度在涉及对象检测的任务中非常受欢迎,我们稍后会讨论这一点,但它对于表格数据的分类也非常有用。在实践中,当你想要以更精确和准确的方式监控模型在非常罕见的类别(当数据极度不平衡时)上的性能时,它非常有价值,这在欺诈检测问题中通常是这种情况。

对于这方面的更具体见解,请阅读Gael Varoquaux的讨论:gael-varoquaux.info/interpreting_ml_tuto/content/01_how_well/01_metrics.html#average-precision

F1 分数

到目前为止,你可能已经意识到使用精确度或召回率作为评估指标并不是一个理想的选择,因为你在优化一个的同时必须牺牲另一个。因此,没有 Kaggle 竞赛只使用这两个指标中的任何一个。你应该将它们结合起来(如平均精确度)。一个单一的指标,F1 分数,即精确度和召回率的调和平均数,通常被认为是最佳解决方案:

如果你得到一个高的F1 分数,那是因为你的模型在精确度或召回率或两者方面都有所提高。你可以在Quora Insincere Questions Classification竞赛中找到一个使用此指标的精彩示例(www.kaggle.com/c/quora-insincere-questions-classification)。

在某些竞赛中,你还会得到F-beta分数。这实际上是精确度和召回率之间的加权调和平均数,而 beta 决定了召回率在组合分数中的权重:

由于我们已经介绍了阈值和分类概率的概念,我们现在可以讨论对数损失和 ROC-AUC,这两个都是相当常见的分类指标。

对数损失和 ROC-AUC

让我们从对数损失开始,它在深度学习模型中也被称为交叉熵。对数损失是预测概率与真实概率之间的差异:

在上述公式中,n 代表示例数量, 是第 i 个案例的真实值,而 是预测值。

如果一个竞赛使用对数损失,这意味着目标是尽可能准确地估计示例属于正类的概率。你实际上可以在很多竞赛中找到对数损失。

我们建议你看看最近的Deepfake Detection Challengewww.kaggle.com/c/deepfake-detection-challenge)或较老的Quora Question Pairswww.kaggle.com/c/quora-question-pairs)。

ROC 曲线,或受试者工作特征曲线,是一种用于评估二元分类器性能并比较多个分类器的图表。它是 ROC-AUC 指标的基础,因为该指标简单地是 ROC 曲线下方的面积。ROC 曲线由真正例率(召回率)与假正例率(错误地将负例分类为正例的负例比例)的对比组成。它等同于 1 减去真正例率(正确分类的负例比例)。以下是一些示例:

图 5.3:不同的 ROC 曲线及其 AUC 值

理想情况下,表现良好的分类器的 ROC 曲线应该在低假阳性率的情况下迅速上升真阳性率(召回率)。ROC-AUC 在 0.9 到 1.0 之间被认为是非常好的。

一个不好的分类器可以通过 ROC 曲线看起来非常相似,如果不是完全相同,与图表的对角线相似来识别,这代表了一个纯粹随机分类器的性能,如图中左上角所示;ROC-AUC 分数接近 0.5 被认为是几乎随机的结果。如果您正在比较不同的分类器,并且您正在使用曲线下面积AUC),则面积更大的分类器性能更好。

如果类别是平衡的,或者不平衡程度不是太高,AUC 的增加与训练模型的效率成正比,并且可以直观地认为这是模型输出更高概率的正例的能力。我们也可以将其视为从正例到负例更正确地排序示例的能力。然而,当正例类别很少时,AUC 的起始值很高,其增量在预测稀有类别方面可能意义不大。正如我们之前提到的,在这种情况下,平均精度是一个更有帮助的指标。

最近,AUC 已经被用于许多不同的竞赛。我们建议您查看以下三个:

您可以在以下论文中阅读详细的论述:Su, W., Yuan, Y., and Zhu, M. 平均精度与 ROC 曲线下面积之间的关系. 2015 年国际信息检索理论会议论文集。2015。

马修斯相关系数(MCC)

我们通过介绍马修斯相关系数MCC)来完成对二元分类指标的概述,它在VSB 电力线故障检测 (www.kaggle.com/c/vsb-power-line-fault-detection) 和 Bosch 生产线性能 (www.kaggle.com/c/bosch-production-line-performance) 中首次出现。

MCC 的公式是:

图片

在上述公式中,TP 代表真阳性,TN 代表真阴性,FP 代表假阳性,FN 代表假阴性。这与我们在讨论精确率和召回率时遇到的命名法相同。

作为相关系数的行为,换句话说,从+1(完美预测)到-1(反向预测),这个指标可以被认为是分类质量的一个度量,即使类别相当不平衡。

尽管公式复杂,但它可以被重新表述和简化,正如神经元工程师(www.kaggle.com/ratthachat)在他的笔记本中所展示的:www.kaggle.com/ratthachat/demythifying-matthew-correlation-coefficients-mcc

神经元工程师在理解评估指标比率方面所做的工作确实值得称赞。事实上,他重新表述的 MCC 变为:

公式中的每个元素是:

重新表述有助于更清晰地阐明,通过提高正负类别的精确度,你可以获得更高的性能,但这还不够:你还需要有与真实标签成比例的正负预测,否则你的提交将受到严重惩罚。

多类别分类的指标

当转向多类别分类时,你只需将我们刚刚看到的二进制分类指标应用于每个类别,然后使用一些常用的多类别情况下的平均策略来总结它们。

例如,如果你想根据 F1 分数评估你的解决方案,你有三种可能的平均选择:

  • 宏平均:简单地对每个类别的 F1 分数进行计算,然后对所有结果进行平均。这样,每个类别的权重与其他类别相同,无论其正例出现的频率如何,或者它们对于你的问题的重要性如何,因此当模型在任一类别上表现不佳时,将产生相等的惩罚:

  • 微平均:这种方法将每个类别的所有贡献相加来计算一个综合的 F1 分数。由于所有计算都是不考虑每个类别的,因此它不会特别偏向或惩罚任何类别,因此它可以更准确地反映类别不平衡:

  • 加权:与宏平均一样,你首先计算每个类别的 F1 分数,但然后使用一个依赖于每个类别的真实标签数量的权重,对所有这些分数进行加权平均。通过使用这样一套权重,你可以考虑每个类别的正例频率或该类别对于你的问题的相关性。这种方法明显偏向多数类别,在计算中将给予更多的权重:

你可能在 Kaggle 竞赛中遇到的常见多类度量包括:

然后还有二次加权 Kappa,我们稍后会探讨它作为序数预测问题的智能评估指标。在其最简单形式中,Cohen Kappa得分只是衡量你的预测与真实值之间的一致性。该度量实际上是为了测量互标注一致性而创建的,但它非常灵活,并且已经找到了更好的用途。

互标注一致性是什么?让我们想象一下,你有一个标注任务:根据照片中是否包含猫、狗或两者都不是的图像来对照片进行分类。如果你要求一组人帮你完成任务,你可能会因为有人(在这种任务中被称为评委)可能将狗误认为是猫或反之,而得到一些错误的标签。正确完成这项工作的聪明方法是让多个评委对同一照片进行标注,然后根据 Cohen Kappa 得分衡量他们的协议水平。

因此,Cohen Kappa 被设计为一个表示两个标注者在标注(分类)问题上一致程度的得分:

在公式中,p[0]是评分者之间观察到的相对一致性,而p[e]是偶然一致性的假设概率。使用混淆矩阵的命名法,这可以重写为:

这个公式的有趣之处在于,得分考虑了协议仅仅是通过偶然发生的经验概率,因此该度量对所有最可能的分类都有修正。该度量范围从 1,表示完全一致,到-1,表示评委完全对立(完全不一致)。

值在 0 附近的表示评委之间的同意和不同意完全是偶然发生的。这有助于你判断模型是否在大多数情况下真的比随机情况表现更好。

安德烈·卢克扬诺夫

www.kaggle.com/artgor

本章的第二次采访是与安德烈·卢克扬诺夫,笔记本和讨论大师以及比赛大师。在他的日常工作岗位上,他是 MTS 集团的机器学习工程师和技术负责人。他对自己的 Kaggle 经历有很多有趣的事情要说!

你最喜欢的比赛类型是什么?为什么?在技术、解决方法方面,你在 Kaggle 上的专长是什么?

我更喜欢那些解决方案足够通用,可以转移到其他数据集/领域的比赛。我对尝试各种神经网络架构、最先进的方法和后处理技巧感兴趣。我不喜欢那些需要逆向工程或创建某些“黄金特征”的比赛,因为这些方法在其他数据集中不适用。

当你在 Kaggle 上竞争时,你也成为了笔记本(排名第一)和讨论的大师。你在这两个目标上投入了吗?

我在编写笔记本上投入了大量的时间和精力,但讨论大师排名似乎是自然而然发生的。

让我们从笔记本排名开始。

2018 年有一个特别的比赛叫做 DonorsChoose.org 应用筛选。DonorsChoose 是一个基金,它赋予全国各地的公立学校教师请求他们学生所需的大量材料和体验的能力。它组织了一场比赛,获胜的解决方案不是基于排行榜上的分数,而是基于笔记本的点赞数。这看起来很有趣,我为比赛写了一个笔记本。许多参与者都在社交媒体上宣传他们的分析,我也是这样做的。结果,我获得了第二名,赢得了一台 Pixelbook(我还在使用它!)

这次成功让我非常兴奋,我继续写笔记本。起初,我只是想分享我的分析并得到反馈,因为我想要尝试比较我的分析和可视化技能与其他人,看看我能做什么,人们对此有何看法。人们开始喜欢我的内核,我想进一步提高我的技能。另一个动力是提高制作快速 MVP(最小可行产品)的技能。当一个新的比赛开始时,许多人开始写笔记本,如果你想成为其中之一,你必须能够快速完成而不牺牲质量。这很有挑战性,但很有趣,也很值得。

我能在 2019 年 2 月获得笔记本大师排名;过了一段时间,我达到了第一名,并保持了超过一年的记录。现在我不太频繁地写笔记本,但我仍然喜欢这样做。

至于讨论,我认为它似乎自然而然地发生了。我回答了 Notebooks 上的评论,并分享和讨论了我参与的比赛中的一些想法,我的讨论排名稳步上升。

请告诉我们您参加的一个特别具有挑战性的比赛,以及您使用了哪些见解来应对这项任务。

这是一场预测分子性质的比赛。我在这里详细写了一篇博客文章(towardsdatascience.com/a-story-of-my-first-gold-medal-in-one-kaggle-competition-things-done-and-lessons-learned-c269d9c233d1)。这是一场针对预测分子中原子之间相互作用的特定领域比赛。核磁共振(NMR)是一种使用与 MRI 类似原理的技术,用于理解蛋白质和分子的结构和动态。全球的研究人员通过进行 NMR 实验来进一步了解分子的结构和动态,涉及环境科学、药理学和材料科学等领域。在这场比赛中,我们试图预测分子中两个原子之间的磁相互作用(标量耦合常数)。量子力学中最先进的方法可以在仅提供 3D 分子结构作为输入的情况下计算出这些耦合常数。但这些计算非常资源密集,因此不能总是使用。如果机器学习方法能够预测这些值,这将真正帮助药物化学家更快、更便宜地获得结构洞察。

我通常为新的 Kaggle 比赛编写 EDA 内核,这次也不例外。在 Kaggle 比赛中,表格数据的常见方法是进行广泛的特征工程和使用梯度提升模型。我在早期的尝试中也使用了 LGBM,但我知道应该有更好的方法来处理图。我意识到领域专业知识将提供重大优势,所以我寻找了所有这样的信息。当然,我也注意到了几位活跃的专家,他们在论坛上撰写文章并创建了内核,所以我阅读了他们的一切。有一天,我收到了一位该领域专家的电子邮件,他认为我们的技能可以互补。通常,我更喜欢自己独立工作一段时间,但在这个情况下,联合力量似乎是一个好主意。而且这个决定证明是一个非常好的决定!随着时间的推移,我们能够聚集一个惊人的团队。

经过一段时间,我们注意到在比赛中神经网络有潜力:一位知名的 Kaggler,Heng,发布了一个 MPNN(消息传递神经网络)模型的示例。过了一段时间,我甚至能够运行它,但结果比我们的模型要差。尽管如此,我们的团队知道,如果我们想取得好成绩,我们就需要与这些神经网络合作。看到 Christof 能够极快地构建新的神经网络,真是令人惊叹。很快,我们就只专注于开发这些模型。

在那之后,我的角色转变为支持性角色。我对我们的神经网络进行了大量实验:尝试各种超参数、不同的架构、训练计划的各种小调整等等。有时我对我们的预测进行 EDA,以找到有趣或错误的案例,后来我们使用这些信息进一步改进了我们的模型。

我们获得了第 8 名,我在这次比赛中学到了很多。

Kaggle 是否帮助你在职业生涯中取得进展?如果是的话,是如何帮助的?

Kaggle 确实在很大程度上帮助了我,特别是在我的技能和个人品牌方面。撰写和发布 Kaggle 笔记本不仅教会了我 EDA 和 ML 技能,而且迫使我变得适应性强,能够快速理解新的主题和任务,在方法之间更有效地迭代。同时,它也为我提供了一定程度的可见性,因为人们欣赏我的工作。

我的第一个作品集(erlemar.github.io/)有很多不同的笔记本,其中一半是基于旧 Kaggle 比赛的。这无疑有助于我获得第一份工作。我的 Kaggle 成就也帮助我吸引了来自好公司的招聘人员,有时甚至可以跳过面试流程的某些步骤,甚至让我获得了几个咨询项目。

在你的经验中,缺乏经验的 Kaggler 通常忽略了什么?你现在知道什么,而当你刚开始时希望知道的呢?

我认为我们需要将缺乏经验的 Kaggler 分为两组:那些在数据科学方面缺乏经验的人和那些在 Kaggle 上缺乏经验的人。

那些在一般情况下缺乏经验的人会犯许多不同的错误(这是可以理解的,每个人都是从某个地方开始的):

  • 最严重的问题之一:缺乏批判性思维和不知道如何进行自己的研究;

  • 不知道何时以及使用哪些工具/方法;

  • 盲目地使用公开的笔记本,而不知道它们是如何工作的;

  • 专注于某个想法并花费太多时间追求它,即使它不起作用;

  • 当他们的实验失败时感到绝望和失去动力。

至于那些在数据科学方面有经验但没有 Kaggle 经验的人,我认为他们最严重的问题是低估了 Kaggle 的难度。他们没有预料到 Kaggle 竞争激烈,需要尝试许多不同的事情才能成功,有很多只在比赛中有效的技巧,还有专业参加比赛的人。

此外,人们往往高估了领域专业知识。我承认,有一些比赛是拥有领域专家的团队赢得了金牌和奖项,但在大多数情况下,经验丰富的 Kagglers 取得了胜利。

此外,我多次见过以下情况:有些人宣称赢得 Kaggle 很容易,并且他(或他的团队)将在不久的将来获得金牌或许多金牌。在大多数情况下,他们默默失败。

你在过去比赛中犯过哪些错误?

  • 对数据的观察不足。有时我无法生成更好的特征或应用更好的后处理,就是由于这一点。而且逆向工程和“黄金特征”是一个完全额外的主题。

  • 因为希望它能够成功,所以在单一想法上花费太多时间。这被称为沉没成本谬误。

  • 实验不足。努力会得到回报——如果你不花足够的时间和资源在比赛中,你不会在排行榜上获得高分。

  • 参加“错误”的比赛。有些比赛存在泄露、逆向工程等问题。有些比赛的公共和私有测试数据分配不合理,导致动荡。有些比赛对我来说不够有趣,我不应该开始参加。

  • 与错误的人组队。有些情况下,我的队友没有像我预期的那样活跃,这导致了更差的团队得分。

当人们参加比赛时,他们应该记住什么最重要的事情或做什么?

我认为重要的是记住你的目标,了解你愿意为这次比赛投入什么,并考虑可能的结果。人们在参加比赛时有许多可能的目标:

  • 赢得金钱或获得奖牌;

  • 获取新技能或提高现有技能;

  • 处理新的任务/领域;

  • 建立人脉;

  • 公共关系;

  • 等等;

当然,有多种动机是可能的。

至于你准备投入什么,通常是指你愿意投入的时间和精力,以及你拥有的硬件。

当我提到结果时,我的意思是比赛结束时会发生什么。你可能会在这个比赛中投入很多并赢得胜利,但你也可能失败。你准备好接受这个现实了吗?赢得某个特定的比赛对你来说至关重要吗?也许你需要准备投入更多的努力;另一方面,也许你有长期目标,一次失败的比赛不会造成太大的伤害。

目标检测问题的指标

近年来,在 Kaggle 上,深度学习竞赛越来越普遍。大多数这些竞赛,专注于图像识别或自然语言处理任务,并没有要求使用与我们迄今为止探索的不同的评估指标。然而,一些特定的问题需要一些特殊的指标来正确评估:与目标检测分割相关的问题。

图片

图 5.4:计算机视觉任务。(来源:https://cocodataset.org/#explore?id=38282, https://cocodataset.org/#explore?id=68717)

目标检测中,你不需要对图像进行分类,而是需要找到图片中的相关部分并相应地进行标记。例如,在图 5.4 中,一个目标检测分类器被委托在照片中定位到有狗或猫存在的部分,并对每个部分使用适当的标签进行分类。左边的例子展示了使用矩形框(称为边界框)定位猫的位置。右边的例子展示了如何通过边界框检测图片中的多只猫和狗,并正确地进行分类(蓝色边界框用于狗,红色边界框用于猫)。

为了描述物体的空间位置,在目标检测中,我们使用边界框,它定义了一个矩形区域,其中包含该物体。边界框通常使用两个(x, y)坐标来指定:左上角和右下角。从机器学习算法的角度来看,找到边界框的坐标相当于将回归问题应用于多个目标。然而,你可能不会从头开始构建问题,而是依赖于预先构建的、通常是预先训练的模型,例如 Mask R-CNN (arxiv.org/abs/1703.06870)、RetinaNet (arxiv.org/abs/2106.05624v1)、FPN (arxiv.org/abs/1612.03144v2)、YOLO (arxiv.org/abs/1506.02640v1)、Faster R-CNN (arxiv.org/abs/1506.01497v1)或 SDD (arxiv.org/abs/1512.02325)。

分割中,你实际上是在像素级别进行分类,所以如果你有一个 320x200 的图像,你实际上需要进行 64,000 个像素分类。根据任务的不同,你可能需要进行语义分割,即对照片中的每个像素进行分类,或者进行实例分割,你只需要对代表特定类型感兴趣对象的像素进行分类(例如,如图 5.5 所示,我们例子中的猫):

图片

图 5.5:同一图像上的语义分割和实例分割。(来源:https://cocodataset.org/#explore?id=338091)

让我们从这些任务的特定度量指标概述开始,这些指标可以很好地适用于这两个问题,因为在两种情况下,你都在预测整个区域(在目标检测中是矩形区域,在分割中是多边形区域),并且你必须将你的预测与真实情况进行比较,真实情况再次表示为区域。在分割方面,最简单的度量指标是像素精度,正如其名所示,是像素分类的准确性。

它不是一个很好的度量指标,因为,就像在二元和多类问题上的准确性一样,如果你的相关像素在图像中占的面积不大(你只是预测了主要主张,因此你没有分割),你的分数可能看起来很好。

因此,有两种度量指标被使用得更多,尤其是在竞赛中:交并比dice 系数

交并比(IoU)

交并比IoU)也被称为Jaccard 指数。在分割问题中使用 IoU 意味着你有两个图像要比较:一个是你的预测,另一个是揭示真实情况的掩码,这通常是一个二元矩阵,其中值 1 代表真实情况,否则为 0。在多个对象的情况下,你有多个掩码,每个掩码都标记了对象的类别。

当用于目标检测问题时,你有两个矩形区域的边界(预测和真实情况的边界),由它们的顶点坐标表示。对于每个分类类别,你计算你的预测和真实情况掩码之间的重叠面积,然后你将这个面积除以你的预测和真实情况之间的并集面积,这个总和考虑了任何重叠。这样,如果你预测的面积大于应有的面积(分母将更大)或更小(分子将更小),你都会按比例受到惩罚:

图 5.6:IoU 计算的视觉表示

图 5.6中,你可以看到涉及计算的区域视觉表示。通过想象方块重叠得更多,你可以弄清楚当你的预测,即使覆盖了真实情况,也超过了它(并集的面积变得更大)时,度量如何有效地惩罚你的解决方案。

这里有一些使用 IoU 的竞赛示例:

Dice

另一个有用的度量标准是Dice 系数,它是预测和真实值重叠区域的面积加倍,然后除以预测和真实值区域的总和:

图 5.7:Dice 计算的视觉表示

在这种情况下,与 Jaccard 指数相比,你不会在分母中考虑预测与真实值之间的重叠。这里,期望的是,随着你最大化重叠区域,你预测正确的区域大小。再次强调,如果你预测的区域大于你应该预测的区域,你会受到惩罚。事实上,这两个度量标准是正相关,对于单个分类问题,它们产生几乎相同的结果。

实际上,差异出现在你处理多个类别时。实际上,无论是使用 IoU 还是 Dice 系数,当你有多个类别时,你会平均所有类别的结果。然而,在这样做的时候,IoU 度量标准往往会因为单个类别的预测错误而更多地惩罚整体平均值,而 Dice 系数则更为宽容,更倾向于表示平均性能。

使用 Dice 系数的 Kaggle 竞赛示例(它常在具有医疗目的的竞赛中出现,但不仅限于此,因为它也可以用于云层和汽车):

IoU 和 Dice 构成了分割和目标检测中所有更复杂度量的基础。通过为 IoU 或 Dice 选择一个适当的阈值水平(通常为 0.5),你可以决定是否确认一个检测,即分类。在这个时候,你可以使用之前讨论的分类度量,如精确度、召回率和F1,就像在流行的目标检测和分割挑战赛(如 Pascal VOC host.robots.ox.ac.uk/pascal/VOC/voc2012)或 COCO cocodataset.org)中所做的那样。

多标签分类和推荐问题的度量标准

推荐系统是数据分析与机器学习最受欢迎的应用之一,在 Kaggle 上有很多使用推荐方法的竞赛。例如,Quick, Draw! Doodle Recognition Challenge被评估为推荐系统的一个预测。然而,Kaggle 上的一些其他竞赛真正致力于构建有效的推荐系统(例如Expedia Hotel Recommendationswww.kaggle.com/c/expedia-hotel-recommendations)和 RecSYS,推荐系统会议(recsys.acm.org/),甚至在其年度竞赛中在 Kaggle 上举办了一次(RecSYS 2013www.kaggle.com/c/yelp-recsys-2013)。

K 值平均平均精度MAP@{K})通常是评估推荐系统性能的指标,也是你在 Kaggle 上遇到的最常见指标,在所有试图将问题作为推荐系统构建或处理的竞赛中。

此外,还有一些其他指标,例如k 值精度,或P@K,以及k 值平均精度,或AP@K,这些都是损失函数,换句话说,是在每个单个预测的层面上计算的。了解它们是如何工作的可以帮助你更好地理解 MAP@K 以及它在推荐系统和多标签分类中的表现。

事实上,与推荐系统类似,多标签分类意味着你的模型输出一系列类别预测。这些结果可以使用一些二元分类指标的均值(例如在希腊媒体监控多标签分类(WISE 2014)中,使用了平均F1 分数:www.kaggle.com/c/wise-2014)以及更典型的推荐系统指标进行评估,例如 MAP@K。最终,你可以将推荐和多标签预测都视为排序任务,这在推荐系统中转化为一系列排序建议,在多标签分类中转化为一系列标签(没有精确的顺序)。

MAP@

MAP@K 是一个复杂的指标,它源于许多计算。为了完全理解 MAP@K 指标,让我们从其最简单的组成部分开始,即k 值精度P@K)。在这种情况下,由于一个示例的预测是一个从最可能到最不可能的排序预测序列,该函数只考虑了前k个预测,然后计算它与真实情况的匹配数量,并将该数字除以k。简而言之,它与平均精度度量在k个预测上的平均相当。

在计算上稍微复杂一些,但在概念上很简单,平均精度 @ kAP@K)是所有从 1k 的值上计算的 P@K 的平均值。这样,该指标评估了预测的整体效果,使用最顶部的预测,然后是前两个预测,以此类推,直到前 k 个预测。

最后,MAP@K 是整个预测样本的 AP@K 的平均值,因为它包含了评估中的所有预测,所以它是一个指标。以下是你在 Expedia Hotel Recommendations 竞赛(www.kaggle.com/c/expedia-hotel-recommendations)中可以找到的 MAP@5 公式:

图片

在公式中,图片 是用户推荐的数量,P(k) 是截止 k 的精度,n 是预测的酒店集群数量(你可以为每个推荐预测最多 5 家酒店)。

这显然比我们的解释要复杂一些,但公式只是表达了 MAP@K 是所有预测的 AP@K 评估的平均值。

在完成了对不同回归和分类指标的特定指标的概述之后,让我们讨论如何在 Kaggle 竞赛中处理评估指标。

优化评估指标

总结我们到目前为止所讨论的内容,目标函数是学习算法内部的一个函数,用于衡量算法的内部模型如何拟合提供的数据。目标函数还向算法提供反馈,以便它在后续迭代中改进其拟合。显然,由于整个算法的努力都是为了根据目标函数表现良好,如果 Kaggle 评估指标完美匹配你的算法的目标函数,你将获得最佳结果。

不幸的是,这种情况并不常见。通常,提供的评估指标只能通过现有的目标函数来近似。获得一个好的近似,或者努力使你的预测在评估标准上表现得更好,是 Kaggle 竞赛中表现良好的秘诀。当你的目标函数与评估指标不匹配时,你有几种替代方案:

  1. 修改你的学习算法,使其包含一个与你的评估指标相匹配的目标函数,尽管并非所有算法都允许这样做(例如,LightGBM 和 XGBoost 算法允许你设置自定义目标函数,但大多数 Scikit-learn 模型不允许这样做)。

  2. 调整你模型的超参数,选择那些在使用评估指标时使结果最耀眼的选择。

  3. 后处理您的结果,使其更接近评估标准。例如,您可以为预测执行转换的优化器编写代码(概率校准算法是一个例子,我们将在本章末尾讨论它们)。

将竞赛指标纳入您的机器学习算法中,实际上是最有效的提高预测准确性的方法,尽管只有少数算法可以被修改为使用竞赛指标作为目标函数。因此,第二种方法更为常见,许多竞赛最终都陷入了一场为了使模型在评估指标上表现最佳而争夺最佳超参数的斗争。

如果您已经编写了评估函数的代码,那么进行正确的交叉验证或选择合适的测试集就占据了主导地位。如果您手头没有编码函数,您必须首先以合适的方式编写它,遵循 Kaggle 提供的公式。

不容置疑,执行以下操作将产生差异:

让我们更详细地讨论当评估指标与算法的目标函数不匹配时您可以选择的替代方案。我们将从探索自定义指标开始。

自定义指标和自定义目标函数

当目标函数与评估指标不匹配时,作为第一个选项,我们上面已经了解到您可以通过创建自己的自定义目标函数来解决这个问题,但只有少数算法可以轻松修改以包含特定的目标函数。

好消息是,允许这样做的一些算法在 Kaggle 竞赛和数据科学项目中是最有效的。当然,创建自己的自定义目标函数可能听起来有点棘手,但这是提高竞赛分数的一种非常有益的方法。例如,当使用 XGBoost、CatBoost 和 LightGBM 等梯度提升算法,以及所有基于 TensorFlow 或 PyTorch 的深度学习模型时,都有这样的选项。

您可以在这里找到关于 TensorFlow 和 PyTorch 中自定义指标和目标函数的精彩教程:

这些将为您提供基本的功能模板以及一些关于如何编写自定义目标或评估函数的有用建议。

如果您只想直接获取所需的自定义目标函数,可以尝试这个 RNA 的 Notebook(www.kaggle.com/bigironsphere):[www.kaggle.com/bigironsphere/loss-function-library-keras-pytorch/notebook](https://www.kaggle.com/bigironsphere/loss-function-library-keras-pytorch/notebook)。它包含了一系列适用于 TensorFlow 和 PyTorch 的自定义损失函数,这些函数在不同的竞赛中都有出现。

如果您需要在 LightGBM、XGBoost 或 CatBoost 中创建自定义损失,如它们各自的文档中所述,您必须编写一个函数,该函数接受预测和真实值作为输入,并返回梯度和对角线作为输出。

您可以参考 Stack Overflow 上的这篇帖子,以更好地理解梯度和对角线是什么:https://stats.stackexchange.com/questions/231220/how-to-compute-the-gradient-and-hessian-of-logarithmic-loss-question-is-based

从代码实现的角度来看,您只需创建一个函数,如果您需要传递除预测标签和真实标签向量之外的更多参数,可以使用闭包。以下是一个简单的focal loss(一个旨在在损失计算中为少数类赋予较大权重的损失函数,如 Lin, T-Y. 等人在 Focal loss for dense object detection 中所述:arxiv.org/abs/1708.02002)函数示例,您可以用它作为自己自定义函数的模板:

from scipy.misc import derivative
import xgboost as xgb
def focal_loss(alpha, gamma):
    def loss_func(y_pred, y_true):
        a, g = alpha, gamma
        def get_loss(y_pred, y_true):
            p = 1 / (1 + np.exp(-y_pred))
            loss = (-(a * y_true + (1 - a)*(1 - y_true)) * 
                    ((1 - (y_true * p + (1 - y_true) * 
                     (1 - p)))**g) * (y_true * np.log(p) + 
                    (1 - y_true) * np.log(1 - p)))
            return loss
        partial_focal = lambda y_pred: get_loss(y_pred, y_true)
        grad = derivative(partial_focal, y_pred, n=1, dx=1e-6)
        hess = derivative(partial_focal, y_pred, n=2, dx=1e-6)
        return grad, hess
    return loss_func
xgb = xgb.XGBClassifier(objective=focal_loss(alpha=0.25, gamma=1)) 
focal_loss, which is then fed into an XGBoost instance’s object parameters. The example is worth showing because the focal loss requires the specification of some parameters in order to work properly on your problem (alpha and gamma). The more simplistic solution of having their values directly coded into the function is not ideal, since you may have to change them systematically as you are tuning your model. Instead, in the proposed function, when you input the parameters into the focal_loss function, they reside in memory and they are referenced by the loss_func function that is returned to XGBoost. The returned cost function, therefore, will work, referring to the alpha and gamma values that you have initially instantiated.

示例的另一个有趣方面是,它确实使得通过 SciPy 的导数函数计算成本函数的梯度和对偶函数变得容易。如果你的成本函数是可微分的,你不必担心手动进行任何计算。然而,创建一个自定义目标函数需要一些数学知识,并且需要相当多的努力来确保它适用于你的目的。你可以阅读关于Max Halford在实现 LightGBM 算法的焦点损失时遇到的困难以及他是如何克服它们的,这里:maxhalford.github.io/blog/lightgbm-focal-loss/。尽管困难重重,但能够创造出自定义损失函数确实可以决定你在 Kaggle 竞赛中的成功,在那里你必须从你的模型中提取最大可能的结果。

如果你构建自己的目标函数不起作用,你可以简单地降低你的雄心,放弃将你的函数作为优化器使用的目标函数,而是将其编码为自定义评估指标。尽管你的模型不会直接优化以执行此函数,但你仍然可以通过基于它的超参数优化来提高其预测性能。这是我们在上一节中讨论的第二个选项。

只需记住,如果你是从头开始编写一个指标,有时你可能需要遵守某些代码约定以确保你的函数正常工作。例如,如果你使用 Scikit-learn,你必须使用make_scorer函数来转换你的函数。make_scorer函数实际上是一个包装器,它使你的评估函数适合与 Scikit-learn API 一起工作。它将在考虑一些元信息的同时包装你的函数,例如是否使用概率估计或预测,是否需要指定预测的阈值,以及最后但同样重要的是,优化的方向性,即你是否希望最大化或最小化返回的分数:

from sklearn.metrics import make_scorer
from sklearn.metrics import average_precision_score
scorer = make_scorer(average_precision_score, 
average='weighted', greater_is_better=True, needs_proba=False) 

在上述示例中,你基于平均精度指标准备了一个评分器,指定它应该在使用多类分类问题时使用加权计算。

如果你正在优化你的评估指标,你可以应用网格搜索、随机搜索或更复杂的优化,如贝叶斯优化,并找到使你的算法在评估指标上表现最优的参数集,即使它与不同的成本函数一起工作。在讨论了模型验证之后,我们将探讨如何在 Kaggle 竞赛中最佳地安排参数优化并获得最佳结果,特别是在处理表格数据问题的章节中。

处理你的预测结果

后处理调优意味着你的预测通过一个函数转换成其他东西,以便更好地展示评估结果。在构建你自定义的损失函数或优化你的评估指标之后,你也可以通过应用一个特定的函数到你的预测上,利用你评估指标的特征来提高你的结果。以二次加权 Kappa 为例。我们之前提到,这个指标在处理有序值预测时很有用。为了回顾,原始的 Kappa 系数是算法与真实值之间一致性的调整概率指数。它是一种经过概率校正的准确度测量方法,该概率是指预测与真实值之间的匹配是由于幸运的机会。

这里是之前提到的 Kappa 系数的原始版本:

图片

在公式中,p[0] 是评分者之间观察到的相对一致性,而 p[e] 是假设的偶然一致性概率。在这里,你需要两个矩阵,一个是观察到的分数矩阵,另一个是基于偶然一致性预期的分数矩阵。当 Kappa 系数加权时,你还需要考虑一个权重矩阵,公式变为:

图片

矩阵 p[p] 包含了不同错误权重的惩罚,这对于有序预测非常有用,因为当预测偏离真实值更远时,这个矩阵可以施加更多的惩罚。使用二次形式,即对结果 k 进行平方,使得惩罚更加严重。然而,优化这样一个指标确实不容易,因为它很难将其实现为一个成本函数。后处理可以帮助你。

一个例子可以在 PetFinder.my 预测领养比赛 (www.kaggle.com/c/petfinder-adoption-prediction) 中找到。在这个比赛中,由于结果可能有 5 个可能的评级(0、1、2、3 或 4),你可以通过分类或回归来处理它们。如果你使用回归,对回归输出的后处理变换可以提高模型在二次加权 Kappa 度量标准上的性能,从而超越直接输出离散预测的分类方法。

在 PetFinder 竞赛的情况下,后处理包括一个优化过程,该过程首先将回归结果转换为整数,首先使用边界[0.5, 1.5, 2.5, 3.5]作为阈值,并通过迭代微调,找到一组更好的边界,以最大化性能。边界的微调需要计算优化器,如 SciPy 的optimize.minimize,它基于 Nelder-Mead 算法。优化器找到的边界通过交叉验证方案进行了验证。你可以直接从竞赛期间Abhishek Thakur发布的帖子中了解更多关于这种后处理细节:www.kaggle.com/c/petfinder-adoption-prediction/discussion/76107

除了 PetFinder 竞赛之外,许多其他竞赛已经证明,智能后处理可以提高结果和排名。我们在这里将指出几个例子:

不幸的是,后处理通常非常依赖于你使用的度量标准(理解度量标准对于设计任何有效的后处理至关重要)并且通常也针对特定数据,例如在时间序列数据和泄露的情况下。因此,为任何竞赛概括任何确定正确后处理程序的步骤都非常困难。尽管如此,始终要意识到这种可能性,并在竞赛中寻找任何表明后处理结果有利的线索。你可以从之前类似竞赛中获得有关后处理的提示,并通过论坛讨论——最终,有人会提出这个话题。

预测概率及其调整

在上述关于度量标准优化(预测的后处理)的讨论完成后,我们将讨论在预测正确概率至关重要,但你不确定你使用的算法是否做得好的情况下。正如我们之前详细说明的,分类概率涉及二分类和多分类问题,并且通常使用对数损失(也称为 log loss 或逻辑损失或交叉熵损失)的二元或多元版本进行评估(更多细节,请参阅关于分类度量标准(标签预测和概率)多分类度量标准的先前章节)。

然而,评估或优化对数损失可能并不足够。在努力使用你的模型实现正确的概率预测时,需要注意的主要问题包括:

  • 不返回真正概率估计的模型

  • 你在问题中的类别分布不平衡

  • 训练数据和测试数据之间的类别分布不同(在公共和私人排行榜上)

单独的第一点就提供了检查和验证分类预测质量(就建模不确定性而言)的理由。事实上,即使 Scikit-learn 包中提供了许多算法以及predict_proba方法,这也并不能保证它们会返回真正的概率。

以决策树为例,它是许多有效建模表格数据的有效方法的基石。分类决策树输出的概率基于终端叶子;也就是说,它依赖于包含待预测案例的叶子上类别的分布。如果树完全生长,案例很可能位于一个包含非常少其他案例的小叶子上,因此预测的概率会非常高。如果你改变max_depthmax_leaf_nodesmin_samples_leaf等参数,结果概率将随着树的生长从高值急剧变化到低值。

决策树是集成模型(如 bagging 模型和随机森林)以及提升模型(如梯度提升,包括其高性能实现 XGBoost、LightGBM 和 CatBoost)中最常见的基模型。但是,由于同样的原因——概率估计并非真正基于坚实的概率估计——这个问题影响了许多其他常用的模型,例如支持向量机和k最近邻。这些方面在 Kagglers 中大多鲜为人知,直到在Otto Group Product Classification Challenge([www.kaggle.com/c/otto-group-product-classification-challenge/overview/](https://www.kaggle.com/c/otto-group-product-classification-challenge/overview/))中,由Christophe Bourguignat和其他人在比赛中提出(参见https://www.kaggle.com/cbourguignat/why-calibration-works),当时使用最近添加到 Scikit-learn 中的校准函数可以轻松解决。

除了你将使用的模型之外,你的问题中类别之间的不平衡也可能导致模型完全不可靠。因此,在处理不平衡分类问题时,一个好的方法是通过使用欠采样或过采样策略重新平衡类别,或者当算法计算损失时为每个类别应用不同的自定义权重。所有这些策略都可能使你的模型表现更佳;然而,它们肯定会扭曲概率估计,你可能需要调整它们以在排行榜上获得更好的模型分数。

最后,第三个需要关注的问题是关于测试集的分布。这类信息通常被隐藏,但通常有方法可以估计它并找出它(例如,通过基于公开排行榜结果的试错法,正如我们在第一章介绍 Kaggle 和其他数据科学竞赛中提到的)。

例如,这种情况发生在iMaterialist Furniture Challenge(www.kaggle.com/c/imaterialist-challenge-furniture-2018/)和更受欢迎的Quora Question Pairs(www.kaggle.com/c/quora-question-pairs)竞赛中。这两场竞赛都引发了关于如何进行后处理以调整概率以符合测试预期的各种讨论(参见swarbrickjones.wordpress.com/2017/03/28/cross-entropy-and-training-test-class-imbalance/www.kaggle.com/dowakin/probability-calibration-0-005-to-lb以获取关于所用方法的更多详细信息)。从一般的角度来看,即使你不知道要预测的类别的测试分布,根据从训练数据中获得的先验概率来正确预测概率仍然非常有好处(并且直到你得到相反的证据,这就是你的模型应该模仿的概率分布)。实际上,如果你的预测概率分布与训练集中的分布相匹配,那么纠正你的预测概率将会容易得多。

当你的预测概率与目标训练分布不匹配时,解决方案是使用 Scikit-learn 提供的校准函数CalibratedClassifierCV

sklearn.calibration.CalibratedClassifierCV(base_estimator=None, *,
    method='sigmoid', cv=None, n_jobs=None, ensemble=True) 

校准函数的目的是对你的预测概率应用一个后处理函数,以便使其更接近于在真实数据中看到的经验概率。假设你的模型是 Scikit-learn 模型或类似行为,该函数将作为你的模型包装器,并将其预测直接传递到后处理函数。你可以在两种后处理方法之间进行选择。第一种是sigmoid方法(也称为 Plat 的缩放),这实际上就是逻辑回归。第二种是等调回归,这是一种非参数回归;注意,如果示例很少,它往往会过拟合。

你还必须选择如何拟合这个校准器。记住,它是一个应用于你模型结果的模型,因此你必须通过系统地重新工作预测来避免过拟合。你可以使用交叉验证(关于这一点,下一章将详细介绍设计良好的验证),然后生成多个模型,一旦平均,将提供你的预测(ensemble=True)。否则,这是我们通常的选择,求助于折叠外预测(关于这一点,下一章将详细介绍)并使用所有可用数据对其进行校准(ensemble=False)。

即使CalibratedClassifierCV可以处理大多数情况,你还可以找出一些经验方法来调整测试时的概率估计,以获得最佳性能。你可以使用任何转换函数,从手工制作的到由遗传算法推导出的复杂函数,例如。你唯一的限制是应该交叉验证它,并可能从公共排行榜中获得一个好的最终结果(但不一定,因为你应该更信任你的本地交叉验证分数,正如我们将在下一章中讨论的那样)。这样的策略的一个好例子是由 Silogram (www.kaggle.com/psilogram)提供的,他在Microsoft Malware Classification Challenge中找到了一种方法,通过将输出提升到由网格搜索确定的幂来调整随机森林不可靠的概率输出,使其成为概率输出(见www.kaggle.com/c/malware-classification/discussion/13509))。

Sudalai_Rajkumar

Sudalai Rajkumar

www.kaggle.com/sudalairajkumar

在本章的最后一次采访中,我们与 Sudalai Rajkumar,SRK,这位在竞赛、数据集和笔记本方面的围棋大师,以及讨论大师进行了交谈。他在 Analytics Vidhya 数据科学平台上排名#1,并为初创公司担任 AI/ML 顾问。

你最喜欢的比赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

我最喜欢的竞赛类型是那些涉及大量特征工程的竞赛。我认为这也是我的优势。我通常对数据探索感兴趣,以深入理解数据(你可以从我的系列简单探索笔记本中推断出来[https://www.kaggle.com/sudalairajkumar/code]),然后基于它创建特征。

你是如何处理 Kaggle 竞赛的?这种处理方式与你在日常工作中所做的是如何不同的?

竞赛的框架包括数据探索、找到合适的验证方法、特征工程、模型构建和集成/堆叠。所有这些都在我的日常工作中涉及。但除此之外,还有大量的利益相关者讨论、数据收集、数据标记、模型部署、模型监控和数据故事讲述。

告诉我们你参加的一个特别具有挑战性的竞赛,以及你使用了哪些见解来应对这项任务。

Santander 产品推荐是我们参加的一个令人难忘的竞赛。我和 Rohan 进行了大量的特征工程,并构建了多个模型。在最终的集成中,我们为不同的产品使用了不同的权重,其中一些权重加起来并不等于 1。从数据探索和理解中,我们手动挑选了这些权重,这帮了我们。这让我们意识到领域/数据在解决问题中的重要性,以及数据科学既是艺术也是科学。

Kaggle 是否帮助你在职业生涯中取得进步?如果是的话,你是如何做到的?

Kaggle 在我的职业生涯中发挥了非常重要的作用。我能够获得我最后两个工作主要是因为 Kaggle。此外,Kaggle 的成功帮助我轻松地与其他数据科学领域的杰出人物建立联系,并向他们学习。这也极大地帮助我在当前作为初创公司 AI/ML 顾问的角色中,因为它增加了我的可信度。

在你的经验中,不经验的 Kagglers 通常忽视了什么?你现在知道什么,而当你刚开始时希望知道的呢?

深入理解数据。这往往被忽视,人们直接进入模型构建阶段。探索数据在 Kaggle 竞赛的成功中扮演着非常重要的角色。这有助于创建适当的交叉验证,创建更好的特征,并从数据中提取更多价值。

你在过去竞赛中犯过哪些错误?

这是一个非常长的列表,我认为这些都是学习机会。在每一场竞赛中,我尝试了 20-30 个想法,可能只有 1 个会成功。这些错误/失败比实际的成功或有效的事物能带来更多的学习。例如,我在我参加的第一个竞赛中,由于过度拟合,从顶尖的十分之一跌到了最底部的十分之一,这是我对过度拟合的非常艰难的学习方式。但这次学习一直伴随着我。

有没有任何特定的工具或库,你会推荐用于数据分析/机器学习?

我主要在处理表格数据时使用 XGBoost/LightGBM。如今,我也使用开源的 AutoML 库和 Driverless AI 来获取早期的基准测试。对于深度学习模型,我使用 Keras、Transformers 和 PyTorch。

当人们参加竞赛时,他们应该记住或做最重要的事情是什么?

一致性是关键。每个竞赛都会有起有落。可能会有多天没有任何进展,但我们不应该放弃,继续尝试。我认为这适用于任何事,而不仅仅是 Kaggle 竞赛。

你使用其他竞赛平台吗?它们与 Kaggle 相比如何?

我也在其他平台如 Analytics Vidhya DataHack 平台、Driven Data、CrowdAnalytix 等参与过。它们也很好,但 Kaggle 的采用范围更广,具有全球性质,因此与其他平台相比,Kaggle 上的竞争量要大得多。

摘要

在本章中,我们讨论了 Kaggle 竞赛中的评估指标。首先,我们解释了评估指标如何与目标函数不同。我们还提到了回归问题和分类问题之间的区别。对于每种类型的问题,我们分析了在 Kaggle 竞赛中可以找到的最常见的指标。

之后,我们讨论了在竞赛中从未见过且不太可能再次出现的指标。最后,我们探索并研究了不同的常见指标,给出了它们在以前的 Kaggle 竞赛中使用的例子。然后,我们提出了一些优化评估指标的策略。特别是,我们建议尝试编写自己的自定义成本函数,并提供了可能的实用后处理步骤的建议。

你现在应该已经掌握了在 Kaggle 竞赛中评估指标的作用。你也应该有一个策略来处理每一个常见或不常见的指标,通过回顾过去的竞赛并全面理解指标的工作方式。在下一章中,我们将讨论如何通过验证策略使用评估指标,并正确估计你的 Kaggle 解决方案的性能。

加入我们书籍的 Discord 空间

加入我们书籍的 Discord 工作空间,参加每月的“问我任何问题”活动,与作者交流:

Kaggle Discord 链接

二维码

第六章:设计良好的验证

在 Kaggle 竞赛中,在建模和提交结果的紧张时刻,可能会觉得只从排行榜上得到的结果就足够了。最终,你可能会认为在比赛中重要的是你的排名。这是一个在比赛中反复犯下的常见错误。实际上,直到比赛结束后,你才知道实际的排行榜(私有部分)是什么样的,而且信任它的公开部分是不明智的,因为它往往具有误导性。

在本章中,我们将向您介绍在数据竞赛中验证的重要性。您将了解:

  • 过拟合是什么以及公开排行榜可能具有误导性

  • 恐怖的震动

  • 不同的验证策略

  • 对抗性验证

  • 如何发现并利用泄露

  • 当选择最终提交时,你的策略应该是什么

在建模时监控你的表现,以及在何时发生过拟合时进行区分,这不仅是在数据科学竞赛中,而且在所有数据科学项目中的一项关键能力。正确验证你的模型是你可以从 Kaggle 竞赛中学到并能在专业世界中再次出售的最重要技能之一。

排行榜上的窥探

如我们之前所述,在每一场比赛中,Kaggle 将测试集分为一个公开部分,这部分在实时排行榜上可视化,以及一个私有部分,这部分将用于计算最终分数。这些测试部分通常是随机确定的(尽管在时间序列比赛中,它们是基于时间确定的)并且整个测试集发布时不会区分公开和私有。

最近,为了防止某些比赛中对测试数据的审查,Kaggle 甚至推迟了测试数据的发布,只提供一些示例,并在提交时用真实的测试集替换它们。这些被称为代码竞赛,因为你实际上并没有提供预测本身,而是一个包含生成这些预测的代码的笔记本。

因此,从模型中得出的提交将覆盖整个测试集,但只有公开部分会立即评分,而私有部分的评分将留待比赛结束后进行。

基于此,出现了三个考虑因素:

  • 为了使比赛能够正常进行,训练数据和测试数据应来自相同的分布。此外,测试数据的公开和私有部分在分布上应该相似。

  • 即使训练数据和测试数据显然来自相同的分布,但任一集中示例不足可能会使得在训练数据和公开以及私有测试数据之间获得一致的结果变得困难。

  • 公共测试数据应被视为数据科学项目中的一个保留测试:仅用于最终验证。因此,不应过多查询,以避免所谓的适应性过拟合,这意味着模型在特定测试集上表现良好,但在其他测试集上表现不佳。

考虑这三个因素对于理解比赛的动态至关重要。在大多数比赛中,讨论论坛中总是有很多关于训练、公共和私有测试数据之间如何相互关联的问题,而且看到基于公共排行榜有效性评估的数百个解决方案的提交是非常常见的。

人们也常常听到关于动荡的讨论,这些讨论可能会彻底改变排名。实际上,这些动荡是对最终排名的一种重新排列,可能会让许多之前在公共排行榜上排名更好的人感到失望。据传闻,动荡通常归因于训练集和测试集之间或测试数据的私有部分和公共部分之间的差异。它们是根据竞争者如何看到他们的预期局部得分与排行榜反馈的相关性进行事前评估的,并通过一系列基于两个数字的分析进行事后评估:

  • 基于平均(绝对值(私有排名-公共排名)/队伍数量)的总体动荡数字

  • 排名前列的动荡人物,仅考虑公共排名的前 10%

这些事后数字最初是由Steve Donohowww.kaggle.com/breakfastpirate)提出的,他编制了一个最糟糕的 Kaggle 动荡排名(见www.kaggle.com/c/recruit-restaurant-visitor-forecasting/discussion/49106#278831)。如今,这些数字很容易通过基于我们在第五章“比赛任务和度量”中讨论的 Meta Kaggle 数据集的许多 Notebooks 重新创建。例如,通过查阅这些数字,你可能会发现由于动荡,特别是顶尖位置上的动荡,RSNA 颅内出血检测比赛对许多人来说是多么可怕。

然而,除了事后评估之外,我们还可以从之前的动荡中获得很多教训,这些教训可以帮助你在 Kaggle 比赛中取得成功。加州大学伯克利分校的一些研究人员也这样认为。在 2019 年 NIPS 会议上,Roelofs、Fridovich-Keil 等人详细研究了数千个 Kaggle 比赛,以深入了解 Kaggle 比赛中的公共-私有排行榜动态。尽管他们关注的是有限的比赛子集(120 个以上参与者,专注于二元分类),但他们获得了一些有趣的发现:

  • 很少有自适应过拟合;换句话说,公共排名通常在未公开的私有排行榜中是成立的。

  • 大多数动荡都是由于随机波动和过度拥挤的排名造成的,竞争对手彼此过于接近,任何在私有测试集中的性能的微小变化都会导致排名的重大变化。

  • 当训练集非常小或训练数据不是独立同分布i.i.d.)时,就会发生动荡。

Roelofs, R.,Fridovich-Keil, S.等人撰写的关于机器学习过拟合的元分析全文,可在以下链接找到:papers.nips.cc/paper/2019/file/ee39e503b6bedf0c98c388b7e8589aca-Paper.pdf

然而,在我们的 Kaggle 比赛长期经验中,我们从一开始就看到了许多自适应过拟合的问题。例如,你可以阅读Greg Park对我们最初参加的其中一场比赛的分析:gregpark.io/blog/Kaggle-Psychopathy-Postmortem/。由于这是许多 Kagglers 的相当常见且持久的问题,我们建议一种比简单地跟随公共排行榜发生的事情更为复杂的策略:

  • 总是构建可靠的交叉验证系统进行本地评分。

  • 总是尝试使用最佳验证方案控制非-i.i.d.分布,该方案由具体情况决定。除非在比赛描述中明确说明,否则识别非-i.i.d.分布并不容易,但你可以通过讨论或通过使用分层验证方案(例如,根据某个特征进行分层时,结果会显著改善)来获得提示。

  • 将本地评分与公共排行榜相关联,以确定它们是否朝同一方向移动。

  • 使用对抗验证进行测试,以揭示测试分布是否与训练数据相似。

  • 使用集成使你的解决方案更加鲁棒,特别是如果你正在处理小型数据集。

在接下来的章节中,我们将探讨这些想法(除了集成,这是未来章节的主题)并为你提供所有最佳的工具和策略,以获得最佳结果,尤其是在私有数据集上。

在比赛中验证的重要性

如果你仔细思考一场比赛,你可以将其想象为一个巨大的实验系统。能够创造出最系统、最有效运行这些实验方式的人将获胜。

事实上,尽管你拥有所有的理论知识,你仍将与其他数百或数千名数据专业人士竞争,他们的能力与你或多或少相当。

此外,他们将与您使用完全相同的数据,以及大致相同的工具来从数据中学习(TensorFlow、PyTorch、Scikit-learn 等)。有些人肯定能更好地获取计算资源,尽管 Kaggle Notebooks 的可用性和云计算价格的普遍下降意味着差距不再那么大。因此,如果您观察知识、数据、模型和可用计算机之间的差异,您不会在您和其他竞争对手之间找到许多区分因素,这些因素可以解释比赛中巨大的性能差异。然而,一些参与者持续优于其他人,这表明存在一些潜在的成功的因素。

在访谈和聚会中,一些 Kagglers 将这个成功因素描述为“毅力”,其他人则将其描述为“尝试一切”,还有一些人将其描述为“愿意把所有东西都投入到比赛中”。这些可能听起来有点模糊和神奇。相反,我们称之为系统实验。在我们看来,成功参与的关键在于您进行的实验数量以及您运行所有实验的方式。您进行的实验越多,您就越有可能比其他参与者更好地解决该问题。这个数量当然取决于几个因素,例如您可用的空闲时间、您的计算资源(越快越好,但正如我们之前提到的,这本身并不是一个如此强烈的区分因素),您团队的大小以及他们在任务中的参与度。这与通常报道的毅力和参与度作为成功的关键是一致的。

然而,这些并不是影响结果唯一因素。您必须考虑到您进行实验的方式也会产生影响。“快速失败并从中学习”是竞争中一个重要的因素。当然,您在失败和成功时都需要仔细反思,以便从经验中学习,否则您的比赛可能只是变成了一系列随机的尝试,希望找到正确的解决方案。

因此,在所有其他条件相同的情况下,拥有一个合适的验证策略是成功 Kaggle 竞争者和那些只是过度拟合排行榜、比赛后排名低于预期的人之间的重要区分因素。

验证是您用来正确评估模型产生的错误以及根据您的实验来衡量其性能如何提高或降低的方法。

通常情况下,选择合适的验证方法的影响往往被忽视,而更倾向于考虑更多量化因素,例如拥有最新、最强大的 GPU 或一个能提交更多作品的更大团队。

然而,如果你只依赖实验的力量以及它们在排行榜上的结果,那就好比“向墙上扔泥巴,希望有什么东西能粘上去”(参见 gregpark.io/blog/Kaggle-Psychopathy-Postmortem/)。有时这样的策略会奏效,但大多数时候不会,因为你将错过在正确方向上进行实验的重要机会,甚至无法看到你在所有泥巴中找到的闪亮的宝石。例如,如果你过于专注于使用随机、非系统的策略在公共排行榜上碰运气,即使你产生了伟大的解决方案,你也可能最终无法正确选择你的最终提交,并错过私有排行榜上得分最高的一个。

拥有一个合适的验证策略可以帮助你决定哪些模型应该提交到私有测试集进行排名。尽管提交你最高公共排行榜模型的诱惑可能很大,但始终考虑你自己的验证分数。对于你的最终提交,根据具体情况以及你是否信任排行榜,基于排行榜选择你的最佳模型,基于你的本地验证结果选择你的最佳模型。如果你不信任排行榜(尤其是当训练样本很小或示例是非独立同分布时),提交具有两个最佳验证分数的模型,选择两个非常不同的模型或集成。这样,你将降低选择在私有测试集上无法表现良好的解决方案的风险。

指出了拥有实验方法的重要性之后,剩下的所有问题都变成了验证的实用性问题。实际上,当你对一个解决方案进行建模时,你需要做出一系列相互关联的决定:

  1. 如何处理你的数据

  2. 应该应用哪种模型

  3. 如何更改模型的架构(尤其是对于深度学习模型)

  4. 如何设置模型的超参数

  5. 如何后处理预测

即使公共排行榜与私有排行榜完全相关,每天提交数量的限制(所有比赛中都存在的限制)也阻止你触及上述所有可能测试的表面。拥有一个合适的验证系统会提前告诉你你所做的是否能在排行榜上工作。

Dmitry Larko

Dmitry Larko

www.kaggle.com/dmitrylarko

Dmitry Larko 是 Kaggle 竞赛的大师级选手,同时也是 H2O.ai 的首席数据科学家。他在机器学习和数据科学领域拥有超过十年的经验。他在 2012 年 12 月发现了 Kaggle,并在几个月后参加了他的第一个比赛。他是 Kaggle 竞赛中验证的强烈倡导者,正如他在采访中告诉我们的。

你最喜欢的比赛类型是什么?为什么?在 Kaggle 上,你在技术和解决方法方面有什么专长?

我主要参加了表格数据集的比赛,但也喜欢参加计算机视觉的比赛。

你是如何处理 Kaggle 比赛的?这种方法与你在日常工作中所做的方法有何不同?

我总是尝试从简单开始,为较小的/较简单的模型首先构建一个提交流程。这里的一个重要步骤是创建一个适当的验证方案,这样你可以以稳健的方式验证你的想法。此外,花尽可能多的时间查看和分析数据始终是一个好主意。

在我的日常工作中,我正在构建一个 AutoML 平台,所以我在 Kaggle 上尝试的许多事情最终都成为了这个平台的一部分。

告诉我们你参加的一个特别具有挑战性的比赛,以及你使用了哪些见解来应对这项任务。

我脑海中没有想出什么,这无关紧要,因为对我来说技术上具有挑战性的事情可能对其他人来说是小菜一碟。技术挑战并不那么重要;重要的是要记住,比赛在一定程度上类似于马拉松,而不是短跑。或者如果你喜欢,你可以将其视为一系列的短跑。因此,重要的是不要过度疲劳,保证充足的睡眠,进行锻炼,并在公园散步以恢复大脑,激发新想法。要赢得 Kaggle 比赛,你需要所有的创造力和专业知识,有时甚至需要一点运气。

Kaggle 是否帮助你在职业生涯中取得进步?如果是的话,是如何帮助的?

我之所以得到现在的工作,是因为我是 Kaggle 竞赛的大师。对我现在的雇主来说,这一点已经足以证明我在该领域的专业知识。

在你的经验中,没有经验的 Kagglers 通常忽略了什么?你现在知道什么,而当你刚开始时希望知道的呢?

他们通常忽略了正确的验证方案,并跟随公共排行榜的反馈。在大多数情况下,这会导致所谓的 Kaggle 上的“震动”。

此外,他们急于跳过探索性数据分析,直接构建模型,这导致了解决方案简单化以及排行榜分数平庸。

你在过去比赛中犯过哪些错误?

我犯的主要错误实际上与一个没有经验的人会犯的错误是一样的——跟随排行榜分数而不是我的内部验证。每次我决定这样做,都会让我在排行榜上失去几个位置。

你有没有推荐用于数据分析或机器学习的特定工具或库?

那通常是常见的几种方法。对于表格数据:LightGBM、XGBoost、CatBoost;对于深度学习:PyTorch、PyTorch-Lightning、timm;以及 Scikit-learn 适用于所有人。

当人们参加比赛时,他们应该记住或做哪件事是最重要的?

从简单开始,始终进行验证;相信你的验证分数,而不是排行榜分数。

偏差和方差

一个好的验证系统会帮助你获得比从训练集中得到的错误度量更可靠的指标。实际上,从训练集中获得的指标会受到每个模型容量和复杂性的影响。你可以将模型的容量视为其可以用来从数据中学习的内存。

每个模型都有一组内部参数,帮助模型记录从数据中提取的模式。每个模型都有其获取模式的能力,一些模型会注意到某些规则或关联,而其他模型会注意到其他规则。当模型从数据中提取模式时,它会将其记录在“记忆”中。

你也会听到关于模型的容量或表达性,作为偏差和方差的问题。在这种情况下,模型的偏差和方差指的是预测,但基本原理严格与模型的表达性相关。模型可以被简化为将输入(观察到的数据)映射到结果(预测)的数学函数。一些数学函数比其他函数更复杂,这体现在它们内部参数的数量以及它们使用参数的方式:

  • 如果模型的数学函数不够复杂或表达性不足以捕捉你试图解决的问题的复杂性,我们谈论的是偏差,因为你的预测将受到模型自身限制(“偏差”)的影响。

  • 如果模型核心的数学函数对于当前问题来说过于复杂,我们就遇到了方差问题,因为模型会记录比所需更多的训练数据细节和噪声,其预测将深受其影响并变得不可预测。

现在,考虑到机器学习的进步和可用的计算资源,问题总是由于方差,因为深度神经网络和梯度提升,最常用的解决方案,通常具有超过大多数你将面临的问题解决所需数学表达性。

当某个模型能够提取的所有有用模式都被捕捉到后,如果模型尚未耗尽其容量,它将开始记忆与预测无关的数据特征和信号(通常称为噪声)。虽然最初提取的模式将帮助模型泛化到测试数据集并更准确地预测,但并非它从训练集中学习到的所有特定内容都有帮助;相反,它可能会损害其性能。学习没有泛化价值的训练集元素的过程通常被称为过拟合

验证的核心目的是明确定义一个分数或损失值,将这个值中可泛化的部分与由于过度拟合训练集特征而产生的部分分开。

这就是验证损失。你可以在以下学习曲线图中看到这种情况的可视化:

图片

图 6.1:从训练数据中学习更多并不总是意味着学习预测

如果你将损失度量在 y 轴上与模型学习努力的某个度量(这可能是神经网络的 epoch,或梯度提升的 round)在 x 轴上作图,你会注意到学习似乎总是在训练数据集上发生,但这种情况并不总是适用于其他数据。

即使你更改超参数、处理数据或完全决定使用不同的模型,这种情况也会发生。曲线的形状会改变,但你总会找到一个甜蜜点,即过拟合开始的地方。这个点在不同的模型和你在建模过程中所做的各种选择之间可能不同。如果你通过正确的验证策略正确地计算了过拟合开始的时间点,你的模型性能将肯定与排行榜结果(公开和私人)相关联,并且你的验证指标将为你提供一个评估你工作的代理,而无需提交任何内容。

你可以在各个层面上听到过拟合:

  • 在训练数据层面,当你使用一个对于问题过于复杂的模型时

  • 在验证集本身层面,当你过度调整你的模型以适应特定的验证集时

  • 在公开排行榜层面,当你的结果远远低于你从训练中预期的结果时

  • 在私人排行榜层面,尽管在公开排行榜上取得了好成绩,但你的私人分数可能会令人失望。

虽然在意义上略有不同,但它们都同样意味着你的模型不可泛化,正如我们在本节中描述的那样。

尝试不同的分割策略

如前所述,验证损失是基于一个不属于训练集的数据样本。这是一个经验度量,告诉你你的模型在预测方面有多好,比从训练中得到的分数更准确,后者主要告诉你你的模型有多少是记忆了训练数据模式。正确选择用于验证的数据样本构成了你的验证策略。

为了总结验证模型和正确衡量其性能的策略,你有几个选择:

  • 第一种选择是使用保留系统,这可能会带来风险,即未能正确选择数据的代表性样本或过度拟合到你的验证保留数据。

  • 第二种选择是使用概率方法并依赖于一系列样本来得出你对模型的结论。在概率方法中,有交叉验证、留一法(LOO)和自助法。在交叉验证策略中,根据你基于数据特征(简单随机抽样、分层抽样、分组抽样、时间抽样)采取的抽样策略,有不同的细微差别。

所有这些策略的共同之处在于它们都是采样策略。这意味着它们帮助你根据你数据的一小部分(随机选择的)来推断一个总体指标(你模型的性能)。采样是统计学的根本,它不是一个精确的过程,因为基于你的采样方法、可用数据和选择某些案例作为样本部分的随机性,你将经历一定程度的误差。

例如,如果你依赖于有偏见的样本,你的评估指标可能会被错误地估计(高估或低估)。然而,如果设计得当并实施得当,采样策略通常能为你提供对你总体指标的较好估计。

所有这些策略的另一个共同之处在于它们都是分割,它们以排他的方式将案例分为训练部分或验证部分。实际上,正如我们讨论的,由于大多数模型都有一定的记忆能力,在训练和验证中使用相同的案例会导致估计过高,因为这允许模型展示其记忆能力;相反,我们希望它能够被评估其从未见示例中推导出模式和函数的能力。

基本的训练-测试分割

我们将要分析的第一种策略是训练-测试分割。在这种策略中,你从你的训练集中采样一部分(也称为保留集),并使用它作为所有使用剩余数据部分训练的模型的测试集。

这种策略的巨大优势在于它非常简单:你选择你数据的一部分,并在那部分上检查你的工作。你通常将数据分成 80/20,以利于训练部分。在 Scikit-learn 中,这是在train_test_split函数中实现的。我们将关注该方法的一些方面:

  • 当你拥有大量数据时,你可以预期你提取的测试数据与整个数据集上的原始分布相似(具有代表性)。然而,由于提取过程基于随机性,你总是有可能提取一个不具有代表性的样本。特别是,如果起始的训练样本很小,这种可能性会增加。使用对抗验证(在接下来的几节中会详细介绍)比较提取的保留部分可以帮助你确保你以正确的方式评估你的努力。

  • 此外,为确保你的测试样本具有代表性,特别是关于训练数据与目标变量之间的关系,你可以使用分层,这确保了在样本数据中某些特征的占比得到尊重。你可以在train_test_split函数中使用stratify参数,并提供一个包含类别分布的数组以保留。

我们必须指出,即使你有可用的代表性保留集,有时简单的训练-测试分割不足以确保在比赛中正确跟踪你的努力。

事实上,随着你继续检查这个测试集,你可能会将你的选择推向某种适应过拟合(换句话说,错误地将训练集的噪声作为信号),就像你在公共排行榜上频繁评估时发生的那样。因此,尽管概率评估在计算上更昂贵,但它更适合比赛。

概率评估方法

概率评估机器学习模型的性能基于分布样本的统计特性。通过采样,你创建了一个较小的原始数据集,它预期具有相同的特征。此外,采样中未被触及的部分本身也构成一个样本,并且预期具有与原始数据相同的特征。通过在采样的数据上训练和测试你的模型,并重复这一过程多次,你基本上创建了一个统计估计器,用于衡量你模型的性能。每个样本可能都存在一些“误差”,也就是说,它可能并不完全代表原始数据的真实分布。然而,随着你采样的增加,这些多个样本上的估计器的平均值将收敛到你正在估计的度量量的真实平均值(这是一个观察到的结果,在概率上由称为大数定律的定理解释)。

概率估计器自然需要比简单的训练-测试分割更多的计算,但它们提供了更多的信心,确保你正确地估计了正确的度量:你模型的总体性能。

k 折交叉验证

最常用的概率验证方法是k-折交叉验证,这种方法被认为能够正确估计你的模型在未见过的、来自同一分布的测试数据上的性能。

这在 Bates, S., Hastie, T., 和 Tibshirani, R. 的论文中有明确的解释;交叉验证:它估计了什么以及它做得怎么样? arXiv 预印本 arXiv:2104.00673,2021 (arxiv.org/pdf/2104.00673.pdf)。

k-折交叉验证可以成功地用于比较预测模型,以及在选择模型的最佳超参数时。

k-折交叉验证有相当多的不同变体,但最简单的一种,即在 Scikit-learn 中的KFold函数中实现的,是基于将你的可用训练数据分成k个分区。之后,在k次迭代中,其中一个k个分区被用作测试集,而其他分区用于模型的训练。

k 验证分数随后被平均,这个平均分数值就是 k-折验证分数,它将告诉你模型在任意未见数据上的估计平均性能。分数的标准差将告诉你估计的不确定性。图 6.2 展示了 5 折交叉验证的结构:

图 6.2图 6.2:5 折验证方案的结构

k-折交叉验证分数中,你必须记住的一个重要方面是,它估计的是在 k - 1 折相同数据量上训练的模型的平均分数。如果你之后在所有数据上训练你的模型,之前的验证估计就不再适用了。当 k 接近例子数量 n 时,你对在完整训练集上得到的模型的估计将越来越准确,然而,由于从每个折中获得的估计之间的相关性越来越大,你将失去所有验证的概率估计。在这种情况下,你最终会得到一个数字,显示你的模型在训练数据上的性能(这仍然是一个有用的估计,用于比较目的,但它不会帮助你正确估计模型的泛化能力)。

当你达到 k = n 时,你有了 LOO 验证方法,这在你有少量案例可用时很有用。这种方法主要是一个无偏拟合度量,因为它几乎使用了所有可用数据来训练,只用一个例子来测试。然而,它并不是对未见数据预期性能的良好估计。它在整个数据集上的重复测试高度相关,并且产生的 LOO 指标更多地代表了模型在数据集本身的性能,而不是模型在未知数据上的性能。

正确的 k 分区数量选择是基于一些与你的可用数据相关的方面来决定的:

  • k 越小(最小为 2),每个折越小,因此,在 k - 1 折上训练的模型的学习偏差将越大:你的在较小 k 上验证的模型相对于在较大 k 上训练的模型将表现得更差。

  • k 越高,数据越多,但你的验证估计的相关性也越高:你将失去 k-折交叉验证在估计未见数据性能中的有趣特性。

通常,k 被设置为 5、7 或 10,更少的情况下设置为 20 折。我们通常认为 k = 5 或 k = 10 是比赛中的好选择,后者使用更多的数据来训练(90%的可用数据),因此更适合在重新在完整数据集上训练时确定你模型的性能。

当决定在比赛中为特定数据集选择什么 k 时,我们发现从两个角度进行反思是有用的。

首先,折数的数量选择应该反映你的目标:

  • 如果你的目的是性能估计,你需要具有低偏差估计的模型(这意味着没有系统性的估计扭曲)。你可以通过使用更多的折数来实现这一点,通常在 10 到 20 之间。

  • 如果你的目标是参数调整,你需要权衡偏差和方差,因此建议使用中等数量的折数,通常在 5 到 7 之间。

  • 最后,如果你的目的是仅仅应用变量选择和简化你的数据集,你需要具有低方差估计的模型(否则你将会有不一致性)。因此,较低的折数就足够了,通常在 3 到 5 之间。

当可用数据的规模相当大时,你可以安全地保持在建议范围的较低端。

其次,如果你只是想要进行性能估计,请注意,你使用的折数越多,你的验证集中的案例就越少,因此每个折的估计值之间的相关性就越高。超过某个点后,增加k会使你的交叉验证估计对未见过的测试集的预测能力降低,而对模型在训练集上的性能估计更加具有代表性。这也意味着,随着折数的增加,你可以得到用于堆叠的完美出折预测,正如我们将在第九章“使用混合和堆叠解决方案的集成”中详细解释的那样。

在 Kaggle 竞赛中,k-折交叉验证不仅用于验证你的解决方案方法和确定模型的性能,还用于生成预测。当你进行交叉验证时,你是在进行子采样,并且平均多个基于数据子样本构建的模型的预测结果是一种有效的对抗方差的策略,通常比在所有可用数据上训练更有效(我们将在第九章中进一步讨论这一点)。因此,许多 Kaggle 竞争者使用交叉验证期间构建的模型在测试集上提供一系列预测,平均起来将为他们提供解决方案。

k 折变化

由于它基于随机采样,k-折在以下情况下可能会提供不合适的分割:

  • 你必须保持小类别的比例,无论是在目标级别还是在特征级别。这在你目标高度不平衡时很典型。典型的例子是垃圾邮件数据集(因为垃圾邮件只是正常电子邮件量的一个小部分)或任何信用风险数据集,你必须预测不太频繁的违约贷款事件。

  • 你必须保持一个数值变量的分布,无论是在目标级别还是在特征级别。这通常是回归问题,其中分布非常偏斜或尾部很重。一个常见的例子是房价预测,其中有一小部分待售房屋的价格会远高于平均房价。

  • 你的案例是非独立同分布的,特别是在处理时间序列预测时。

在前两种情况下,解决方案是分层k-fold,其中采样是以受控的方式进行,以保留你想要保留的分布。如果你需要保留单个类的分布,你可以使用 Scikit-learn 的StratifiedKFold,使用分层变量,通常是你的目标变量,也可以是任何其他你需要保留其分布的特征。该函数将生成一组索引,帮助你相应地划分你的数据。你还可以使用pandas.cut或 Scikit-learn 的KBinsDiscretizer对数值变量进行离散化后获得相同的结果。

当你必须根据多个变量或重叠标签进行分层时,比如在多标签分类中,这会变得稍微复杂一些。

你可以在Scikit-multilearn包(scikit.ml/)中找到一个解决方案,特别是IterativeStratification命令,它可以帮助你控制你想要保留的顺序(多个变量的组合比例数)。该命令实现了以下论文中解释的算法:scikit.ml/api/skmultilearn.model_selection.iterative_stratification.html

实际上,即使你的问题不是分类问题,而是回归问题,你仍然可以很好地利用分层。在回归问题中使用分层可以帮助你的回归器在交叉验证期间拟合到与整个样本中找到的类似的目标(或预测因子)分布。在这些情况下,为了使StratifiedKFold正常工作,你必须使用你的连续目标的一个离散代理而不是你的连续目标。

实现这一点的第一种,最简单的方法是使用 pandas 的cut函数,并将你的目标划分为足够多的箱,例如 10 或 20:

import pandas as pd
y_proxy = pd.cut(y_train, bins=10, labels=False) 

为了确定要使用的箱数,Abhishek Thakur更喜欢使用基于可用示例数量的Sturges 规则,并将该数字提供给 pandas 的cut函数(见www.kaggle.com/abhishek/step-1-create-folds)):

import numpy as np
bins = int(np.floor(1 + np.log2(len(X_train)))) 

另一种方法是关注训练集中特征的分部,并试图重现它们。这需要在对训练集的特征进行聚类分析(一种无监督方法)时使用,从而排除目标变量和任何标识符,然后使用预测的聚类作为层。您可以在本笔记本中看到一个示例(www.kaggle.com/lucamassaron/are-you-doing-cross-validation-the-best-way),其中首先执行 PCA(主成分分析)以消除相关性,然后执行k-means 聚类分析。您可以通过运行经验测试来决定使用多少个聚类。

在继续讨论k-fold 可能提供不合适的划分的情况时,第三种情况,即当您有非独立同分布(non-i.i.d.)的数据时,例如在某些示例之间发生分组的情况,事情变得复杂。非独立同分布示例的问题在于示例之间的特征和目标是相关的(因此,如果您知道其中的一个示例,那么预测所有示例就更容易)。实际上,如果您碰巧将同一组数据在训练集和测试集中划分,那么您的模型可能会学会区分组而不是目标本身,从而产生良好的验证分数,但在排行榜上的结果却非常糟糕。这里的解决方案是使用GroupKFold:通过提供一个分组变量,您可以确保每个组要么被放置在训练折中,要么被放置在验证折中,但永远不会在两者之间分割。

在数据中发现分组,使得数据非独立同分布(non-i.i.d.),实际上并不是一个容易完成的任务。除非竞赛问题中明确指出,否则您将不得不依靠您调查数据的能力(使用无监督学习技术,如聚类分析)和问题的领域。例如,如果您的数据是关于移动电话使用情况,您可能会通过注意到特征中相似值的序列,意识到一些示例来自同一用户。

时间序列分析也面临同样的问题,由于数据非独立同分布,您不能通过随机抽样进行验证,因为您将混合不同的时间框架,而后期的时间框架可能会留下前期的时间痕迹(在统计学中称为自相关的特征)。在时间序列验证的最基本方法中,您可以使用基于时间的训练集和验证集划分,如图6.3所示:

图 6.3

图 6.3:训练集和验证集的划分基于时间

然而,你的验证能力将会有限,因为你的验证将锚定在特定的时间点上。对于更复杂的方法,你可以使用 Scikit-learn 包提供的TimeSeriesSplitsklearn.model_selection.TimeSeriesSplit)中的时间分割验证。TimeSeriesSplit可以帮助你设置时间序列的训练和测试部分的时间框架。

在训练时间框架的情况下,TimeSeriesSplit函数可以帮助你设置你的训练数据,使其涉及测试时间框架之前的所有过去数据,或者限制它到一个固定的回溯期(例如,始终使用测试时间框架前三个月的数据进行训练)。

图 6.4中,你可以看到涉及增长训练集和移动验证集的时间验证策略的结构:

图 6.4:训练集随时间增长

图 6.5中,你可以看到如果你规定训练集有一个固定的回溯期,策略是如何变化的:

图 6.5:训练和验证分割随时间移动

根据我们的经验,按照固定的回溯期可以帮助提供对时间序列模型更公平的评估,因为你总是在同一个训练集大小上计数。

通过相反地使用随时间增长的训练集大小,你混淆了模型性能随时间切片的影响和模型中减少的偏差(因为更多的例子意味着更少的偏差)。

最后,记住TimeSeriesSplit可以设置为在训练和测试时间之间保持一个预定义的间隔。当你被告知测试集在未来某个时间(例如,训练数据后一个月)时,这非常有用,并且你想测试你的模型是否足够鲁棒,能够预测那么远的未来。

嵌套交叉验证

在这个阶段,介绍嵌套交叉验证非常重要。到目前为止,我们只讨论了根据最终性能测试模型,但通常在调整超参数时,还需要测试它们的中间性能。实际上,你不能测试某些模型参数在你的测试集上的效果,然后使用相同的数据来评估最终性能。因为你已经专门找到了在测试集上工作的最佳参数,你在同一测试集上的评估指标将过于乐观;在另一个测试集上,你可能不会得到完全相同的结果。在这种情况下,你必须区分验证集测试集,验证集用于评估各种模型和超参数的性能,而测试集将帮助你估计模型的最终性能。

如果你使用测试-训练分割,这是通过将测试部分分割成两个新的部分来实现的。通常的分割是 70/20/10,分别用于训练、验证和测试(但你也可以决定不同)。如果你使用交叉验证,你需要嵌套交叉验证;也就是说,你基于另一个交叉验证的分割进行交叉验证。本质上,你运行常规的交叉验证,但在你需要评估不同的模型或不同的参数时,你基于折叠分割进行交叉验证。

图 6.6中的例子展示了这种内部和外部交叉验证结构。在外部部分,你确定用于测试评估指标的数据部分。在内部部分,它由外部部分提供的训练数据组成,你安排训练/验证分割以评估和优化特定的模型选择,例如决定选择哪个模型或超参数值:

图片

图 6.6:嵌套交叉验证在外部和内部循环中的结构

这种方法的优势在于使你的测试和参数搜索完全可靠,但在这样做的同时,你会遇到几个问题:

  • 减少的训练集,因为你在第一次通过交叉验证分割后,又再次分割

  • 更重要的是,它需要大量的模型构建:如果你运行两个嵌套的 10 折交叉验证,你需要运行 100 个模型

尤其是因为最后一个原因,一些 Kagglers 倾向于忽略嵌套交叉验证,并使用相同的交叉验证进行模型/参数搜索和性能评估,或者使用固定的测试样本进行最终评估,从而承担一些适应性拟合的风险。根据我们的经验,这种方法可能同样有效,尽管如果你生成折叠外预测用于后续建模(我们将在下一节讨论),可能会导致过度估计模型性能和过拟合。我们始终建议你尝试最适合测试你模型的方 法。如果你的目标是正确估计你的模型性能并在其他模型中重用其预测,请记住,在可能的情况下使用嵌套交叉验证可以为你提供一个更少过拟合的解决方案,并在某些比赛中产生差异。

生成折叠外预测(OOF)

除了估计你的评估指标性能之外,交叉验证的一个有趣应用是生成测试预测和折叠外预测。实际上,当你训练你的训练数据的一部分并预测剩余的部分时,你可以:

  • 在测试集上预测:所有预测的平均值通常比在所有数据上重新训练相同的模型更有效:这是一种与混合相关的集成技术,将在第九章使用混合和堆叠解决方案进行集成中讨论。

  • 在验证集上进行预测:最后,你将对整个训练集进行预测,并可以按照原始训练数据的顺序重新排序这些预测。这些预测通常被称为出卷预测OOF预测,它们可以非常有用。

OOF 预测的第一个用途是估计你的性能,因为你可以直接在 OOF 预测上计算你的评估指标。获得的表现与交叉验证估计(基于采样)不同;它没有相同的概率特性,因此它不是衡量泛化性能的有效方式,但它可以告诉你你的模型在特定训练集上的表现。

第二个用途是生成一个图表,将预测值与真实值或其他来自不同模型的预测值进行可视化。这将帮助你理解每个模型的工作原理以及它们的预测是否相关。

最后一个用途是创建元特征或元预测器。这将在第九章中完全探讨,但重要的是现在就强调这一点,因为 OOF 预测是交叉验证的副产品,并且它们之所以有效,是因为在交叉验证期间,你的模型始终在预测训练时间未看到的示例。

由于你的 OOF 预测中的每一个预测都是由在不同数据上训练的模型生成的,因此这些预测是无偏的,你可以放心使用,无需担心过拟合(尽管下一章将讨论一些注意事项)。

生成 OOF 预测有两种方式:

  • 通过编写一个将验证预测存储到预测向量中的程序,同时注意将它们按照训练数据中的示例索引位置排列

  • 通过使用 Scikit-learn 函数cross_val_predict,该函数将自动为你生成 OOF 预测

当我们在本章后面查看对抗验证时,我们将看到这种第二种技术的实际应用。

子采样

除了k-折交叉验证之外,还有其他验证策略,但它们不具有相同的泛化特性。我们已经讨论了 LOO,这是k = n(其中n是示例数量)的情况。另一个选择是子采样。子采样类似于k-折,但你没有固定的折数;你使用你认为必要的数量(换句话说,做出一个有根据的猜测)。你重复地子采样你的数据,每次使用你采样的数据作为训练数据,而未采样的数据作为验证数据。通过平均所有子样本的评估指标,你将得到模型性能的验证估计。

由于你正在系统地测试所有示例,就像在k-fold 中一样,你需要进行相当多的试验才能有很好的机会测试它们。同样,如果你没有应用足够的子样本,某些案例可能比其他案例被测试的次数更多。你可以使用 Scikit-learn 的ShuffleSplit运行这种验证。

重抽样

最后,另一个选择是尝试重抽样,这在统计学中被设计用来推断估计值的误差分布;出于同样的原因,它也可以用于性能估计。重抽样要求你抽取一个样本,有放回地,其大小与可用数据相同。

到这一点,你可以以两种不同的方式使用重抽样:

  • 就像在统计学中一样,你可以多次进行重抽样,在样本上训练你的模型,并在训练数据本身上计算你的评估指标。重抽样的平均值将提供你的最终评估。

  • 否则,就像在子抽样中一样,你可以使用重抽样样本进行训练,并将未从数据中抽样的部分作为测试集。

根据我们的经验,在重抽样训练数据上计算评估指标的第一种方法,通常在统计学中用于线性模型的系数值及其误差分布的估计,在机器学习中作用不大。这是因为许多机器学习算法倾向于过度拟合训练数据,因此即使进行重抽样,你也无法在训练数据上得到有效的指标评估。因此,Efron 和 Tibshirani(参见Efron, B. and Tibshirani, R. Improvements on cross-validation: the 632+ bootstrap method. Journal of the American Statistical Association 92.438 (1997): 548-560.)提出了632+估计器作为最终的验证指标。

首先,他们提出了一个简单版本,称为 632 重抽样:

图片

在这个公式中,给定你的评估指标errerr[fit]是在训练数据上计算的指标,err[bootstrap]是在重抽样数据上计算的指标。然而,在训练模型过度拟合的情况下,err[fit]往往会接近零,使得估计器不太有用。因此,他们开发了 632+重抽样的第二个版本:

图片

其中w是:

图片

图片

这里有一个新的参数,图片,这是无信息误差率,通过评估预测模型在所有可能的响应变量和预测变量组合上来估计。正如 Scikit-learn 的开发者所讨论的(github.com/scikit-learn/scikit-learn/issues/9153),计算图片实际上是不可行的。

由于使用自助法作为经典统计学在机器学习应用中的局限性和不可行性,你可以使用第二种方法,从自助法未采样的例子中获得你的评估。

在这种形式下,自助法是交叉验证的替代方案,但与子采样一样,它需要构建更多的模型并进行测试,比交叉验证要多。然而,了解这些替代方案是有意义的,以防你的交叉验证在评估指标上显示出过高的方差,你需要通过测试和重新测试进行更深入的检查。

以前,这种方法已经在 Scikit-learn 中实现(github.com/scikit-learn/scikit-learn/blob/0.16.X/sklearn/cross_validation.py#L613),但后来被移除了。由于你无法在 Scikit-learn 中找到自助法,而且它甚至对测试数据也进行了自助,你可以使用我们自己的实现。以下是我们的示例:

import random
def Bootstrap(n, n_iter=3, random_state=None):
    """
    Random sampling with replacement cross-validation generator.
    For each iter a sample bootstrap of the indexes [0, n) is
    generated and the function returns the obtained sample
    and a list of all the excluded indexes.
    """
    if random_state:
        random.seed(random_state)
    for j in range(n_iter):
        bs = [random.randint(0, n-1) for i in range(n)]
        out_bs = list({i for i in range(n)} - set(bs))
        yield bs, out_bs 

总之,自助法确实是交叉验证的替代方案。它在统计学和金融学中确实被更广泛地使用。在机器学习中,黄金法则是要使用k-折交叉验证方法。然而,我们建议不要忘记在所有那些情况下使用自助法,因为这些情况是由于异常值或一些过于异质化的例子,你在交叉验证中评估指标的标准误差很大。在这些情况下,自助法将证明在验证你的模型方面非常有用。

Ryan Chesler

Ryan Chesler

www.kaggle.com/ryches

本章的第二次采访是与 Ryan Chesler,一个讨论大师和笔记本及竞赛大师。他是 H2O.ai 的数据科学家,也是 Meetup 上圣地亚哥机器学习小组的组织者之一(www.meetup.com/San-Diego-Machine-Learning/)。在他的回答中,验证的重要性被提到了几次。

你最喜欢的竞赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

我倾向于涉猎各种竞赛。与专注于特定领域,如计算机视觉或自然语言处理相比,尝试各种各样的问题更有趣。我最感兴趣的是那些可以从数据和分析预测错误中得出深刻见解的问题。对我来说,错误分析是最有启发性的过程之一;理解模型失败的地方,并试图找到改进模型或输入数据表示的方法来克服弱点。

你是如何处理 Kaggle 竞赛的?这种方法和你在日常工作中所做的工作有何不同?

在这两种情况下,我的方法都是相似的。很多人似乎更喜欢在建模之前进行探索性数据分析,但我发现为建模准备数据的过程通常就足够了。我典型的做法是手动查看数据,并就我认为如何最好地建模数据和探索不同选项做出一些初步决定。然后,我构建模型并评估性能,然后专注于分析错误,并根据我在模型出错的地方推理出下一步的建模步骤。

Kaggle 是否帮助了你在你的职业生涯中?如果是,那又是如何帮助的?

是的,这帮助我得到了我现在的工作。我在 H2O 工作,他们非常重视 Kaggle 的成就。我的上一份工作也喜欢我在比赛中表现良好。

你也是圣地亚哥一个有两千多名参与者的 Meetup 的组织者。这与你在 Kaggle 的经验有关吗?

是的,这绝对有关。我从几乎一无所知开始,尝试了一个 Kaggle 比赛,一开始并没有取得太大的成功。我参加了一个当地的 Meetup,找到了可以一起学习和合作的人。当时,我能够与比我技能水平高得多的人一起工作,我们在比赛中表现得很好,排名第三/4500+个团队。

之后,这个团队不再那么一致,我想让这个社区继续下去,所以我组建了自己的团队,开始组织自己的活动。我差不多已经做了 4 年,我得以站在桌子的另一边教人们,帮助他们开始。最初,我们只专注于 Kaggle 比赛和尝试组建团队,但慢慢地开始扩展到做读书俱乐部和关于各种感兴趣主题的讲座。我把我的很多成功归因于有这个专门的每周时间来学习和思考机器学习。

在你的经验中,不经验的 Kagglers 通常忽略了什么?你现在知道的事情,你希望在你刚开始的时候就知道?

在我的经验中,很多人过分强调偏差-方差权衡和过拟合的重要性。这是我一直看到人们过分担心的事情。重点不应该是使训练和验证性能接近,而应该是使验证性能尽可能好。

你在过去比赛中犯过哪些错误?

我的一个持续错误就是探索得不够。有时候我过早地否定了自己的想法,结果这些想法对于提高表现非常重要。我经常可以在第一次尝试时就接近竞争水平,但当我尝试新事物并持续改进时,需要一种我仍在努力掌握的不同技能。

你会推荐使用哪些特定的工具或库来进行数据分析或机器学习?

我使用了很多标准工具:XGBoost、LightGBM、Pytorch、TensorFlow、Scikit-learn。我没有对任何特定的工具或库有强烈的偏好,只是与问题相关的任何工具。

当人们参加竞赛时,他们应该牢记或做什么是最重要的?

我认为人们必须牢记的最重要的事情是良好的验证。我经常看到人们自欺欺人,以为他们的性能在提高,但提交到排行榜后,才发现实际情况并没有他们预期的那么好。理解如何将假设与你的新未见数据相匹配,并构建一个对新条件具有鲁棒性的模型是一项重要的技能。

调整你的模型验证系统

到这一点,你应该对所有可能的验证策略有一个全面的了解。当你参加竞赛时,制定你的验证策略并实施它。然后,测试你选择的策略是否正确。

作为一条黄金法则,在制定你的验证策略时,要以你必须复制竞赛组织者用于将数据分割为训练、私有和公共测试集的方法为指导思想。问问自己组织者是如何安排这些分割的。他们是随机抽取样本吗?他们试图保留数据中的某些特定分布吗?测试集实际上是从与训练数据相同的分布中抽取的吗?

这些不是你在现实世界项目中会问自己的问题。与必须不惜一切代价进行泛化的现实世界项目相反,竞赛有一个更狭窄的焦点,即拥有在给定测试集(特别是私有测试集)上表现良好的模型。如果你从一开始就关注这个想法,你就有更大的机会找到最佳的验证策略,这将有助于你在竞赛中排名更高。

由于这是一个试错过程,当你试图为竞赛找到最佳的验证策略时,你可以系统地应用以下两个一致性检查,以确定你是否走上了正确的道路:

  1. 首先,你必须检查你的本地测试是否一致,也就是说,单个交叉验证折的误差彼此之间差异不大,或者当你选择简单的训练-测试分割时,使用不同的训练-测试分割可以重现相同的结果。

  2. 然后,你必须检查你的本地验证误差是否与公共排行榜上的结果一致。

如果你第一次检查失败了,你可以根据以下可能的问题来源选择几个选项:

  • 你没有多少训练数据

  • 数据过于多样,每个训练分区都与每个其他分区非常不同(例如,如果你有太多的高基数特征,即具有太多级别的特征——如邮政编码——或者如果你有多元异常值)

在这两种情况下,关键是你缺乏你想要实施的模型所需的数据。即使问题只是数据过于多样,绘制学习曲线也会让你明显看出你的模型需要更多的数据。

在这种情况下,除非你发现转向一个更简单的算法在评估指标上有效(在这种情况下,以方差换取偏差可能会降低你的模型性能,但并不总是如此),否则你的最佳选择是使用广泛的验证方法。这可以通过以下方式实现:

  • 使用更大的 k 值(从而接近 LOO,其中 k = n)。你的验证结果将更多地关于你的模型在未见数据上的表现能力,但通过使用更大的训练部分,你将拥有更稳定评估的优势。

  • 平均多个 k-折验证的结果(基于不同随机种子初始化选择的不同数据分区)。

  • 使用重复的引导启动。

请记住,当你发现不稳定的局部验证结果时,你不会是唯一一个受此问题困扰的人。通常,这是一个由于数据来源和特征而普遍存在的问题。通过关注讨论论坛,你可能会得到一些可能的解决方案的提示。例如,对于高基数特征,目标编码是一个好的解决方案;分层可以帮助处理异常值;等等。

当你通过了第一项检查但失败了第二项时,情况就不同了;你的局部交叉验证是一致的,但你发现它在排行榜上并不成立。为了意识到这个问题存在,你必须仔细记录你所有的实验、验证测试类型、使用的随机种子以及如果你提交了预测结果,排行榜结果。这样,你可以绘制一个简单的散点图并尝试拟合线性回归,或者更简单的是,计算你的局部结果与相关公开排行榜分数之间的相关性。注释和分析所有这些需要一些时间和耐心,但这是你可以跟踪的最重要的比赛表现元分析。

当不匹配是因为你的验证分数系统地低于或高于排行榜分数时,你实际上有一个强烈的信号表明你的验证策略中缺少某些东西,但这并不妨碍你改进你的模型。实际上,你可以继续改进你的模型,并期待排行榜上的改进,尽管不是成比例的。然而,系统性的差异始终是一个红旗,意味着你所做的事情与组织者安排的测试模型之间存在差异。

当你的局部交叉验证分数与排行榜反馈完全不相关时,情况甚至更糟。这确实是一个红旗。当你意识到这种情况时,你应该立即运行一系列测试和调查,以找出原因,因为无论这是一个普遍问题还是不是,这种情况对你的最终排名构成了严重威胁。在这种情况下,有几种可能性:

  • 你发现测试集是从与训练集不同的分布中抽取的。敌对验证测试(我们将在下一节讨论)是在这种情况下可以启发你的方法。

  • 数据是非独立同分布的,但这并不明确。例如,在《自然保护基金会渔业监测》竞赛(www.kaggle.com/c/the-nature-conservancy-fisheries-monitoring)中,训练集中的图像是从类似情况(渔船)中拍摄的。你必须自己想出如何安排它们,以避免模型学会识别目标而不是图像的上下文(例如,参见Anokas的这项工作:www.kaggle.com/anokas/finding-boatids)。

  • 特征的多变量分布是相同的,但在测试集中某些组的分布不同。如果你能找出这些差异,你可以相应地设置你的训练集和验证集,从而获得优势。你需要调查公共排行榜来解决这个问题。

  • 测试数据发生了漂移或趋势,这在时间序列预测中通常是情况。再次强调,你需要调查公共排行榜来获取一些关于可能有助于提高分数的后处理方法的见解,例如,将乘数应用于你的预测,从而模拟测试数据中的下降或上升趋势。

正如我们之前讨论的,调查排行榜是通过专门设计的提交来获取关于公共测试集组成的洞察。如果私有测试集与公共测试集相似,这种方法特别有效。没有通用的调查方法,因此你必须根据竞赛和问题的类型设计调查方法。

例如,在论文《通过利用对数损失算子攀登 Kaggle 排行榜》(export.arxiv.org/pdf/1707.01825)中,Jacob 解释了如何在没有下载训练数据的情况下在竞赛中获得第四名。

关于回归问题,在 Kaggle 最近组织的“30 天机器学习”活动中,Hung Khoi解释了如何通过调查排行榜帮助他理解训练数据集和公共测试数据中目标列的均值和标准差之间的差异(参见:www.kaggle.com/c/30-days-of-ml/discussion/269541)。

他使用了以下方程:

实际上,你只需要提交两次来求解测试目标的均值和方差,因为有两个未知项——方差和均值。

你还可以从克里斯·德奥特www.kaggle.com/cdeotte)的这篇帖子中获得一些关于排行榜探测的其他想法,www.kaggle.com/cdeotte/lb-probing-strategies-0-890-2nd-place,与不要过度拟合 II 竞赛www.kaggle.com/c/dont-overfit-ii)相关。

如果你想要了解从排行榜中获取信息是如何一把双刃剑,你可以阅读一下扎哈·奇基舍夫是如何在LANL 地震预测竞赛中获取信息,最终在公开排行榜上领先,但在私人排行榜上排名 87 位:towardsdatascience.com/how-to-lb-probe-on-kaggle-c0aa21458bfe

使用对抗验证

正如我们之前讨论的,交叉验证允许你测试你的模型将泛化到来自与你的训练数据相同分布的未见数据集的能力。希望你在 Kaggle 竞赛中需要创建一个可以预测公共和私有数据集的模型,你应该期待这样的测试数据与训练数据来自相同的分布。在现实中,这并不总是如此。

即使你没有因为你的决策不仅基于排行榜结果,还考虑了交叉验证而过度拟合测试数据,你仍然可能会对结果感到惊讶。这种情况可能发生在测试集与你的模型基于的训练集略有不同的情况下。实际上,目标概率及其分布,以及预测变量如何与之相关,在训练过程中向你的模型传达了某些期望,如果测试数据与训练数据不同,这些期望是无法满足的。

因此,仅仅避免过度拟合到排行榜,就像我们之前讨论的那样,是不够的,首先,还建议找出你的测试数据是否与训练数据可比。然后,如果它们不同,你必须弄清楚是否有机会减轻训练数据和测试数据之间的不同分布,并构建一个在该测试集上表现良好的模型。

对抗验证就是为了这个目的而开发的。这是一种允许你轻松估计你的训练数据和测试数据之间差异程度的技巧。这项技术长期以来在 Kaggle 参与者中流传,从团队到团队传递,直到齐格蒙特·扎尼亚克www.kaggle.com/zygmunt)在他的 FastML 博客上发布文章后公开。

这个想法很简单:取你的训练数据,移除目标,将你的训练数据和测试数据一起组装,并创建一个新的二元分类目标,其中正标签分配给测试数据。在这个时候,运行机器学习分类器,并评估 ROC-AUC 评估指标(我们在上一章详细说明竞赛任务和指标中讨论了此指标)。

如果你的 ROC-AUC 值在 0.5 左右,这意味着训练数据和测试数据不容易区分,并且显然来自相同的分布。高于 0.5 且接近 1.0 的 ROC-AUC 值表明算法很容易区分来自训练集和测试集的数据:在这种情况下,不要期望能够轻易地将结果推广到测试集,因为它显然来自不同的分布。

你可以在为Sberbank 俄罗斯住房市场竞赛编写的示例 Notebook 中找到示例(www.kaggle.com/c/sberbank-russian-housing-market),它展示了对抗验证的实际示例及其在竞赛中的应用:www.kaggle.com/konradb/adversarial-validation-and-other-scary-terms

由于你的数据可能具有不同的类型(数值或字符串标签)并且你可能存在缺失情况,在成功运行分类器之前,你需要进行一些数据处理。我们的建议是使用随机森林分类器,因为:

  • 它不输出真正的概率,但其结果仅作为序数,这对于 ROC-AUC 分数来说是一个完美的匹配。

  • 随机森林是一种基于决策树的灵活算法,它可以自行进行特征选择,并且可以在不进行任何预处理的情况下操作不同类型的特征,同时将所有数据转换为数值。它对过拟合也相当稳健,你不必过多考虑调整其超参数。

  • 由于其基于树的本质,你不需要太多的数据处理。对于缺失数据,你可以简单地用不可能的负值(如-999)替换这些值,并且你可以通过将字符串转换为数字(例如,使用 Scikit-learn 标签编码器,sklearn.preprocessing.LabelEncoder)来处理字符串变量。作为一个解决方案,它的性能不如独热编码,但它非常快速,并且对于这个问题可以正常工作。

虽然构建分类模型是直接对抗验证测试集的最直接方法,但你也可以使用其他方法。一种方法是将训练数据和测试数据映射到一个低维空间,就像 NanoMathias 在这篇帖子中做的那样 (www.kaggle.com/nanomathias/distribution-of-test-vs-training-data)。虽然需要更多的调整工作,但基于 t-SNE 和 PCA 的这种方法具有将结果以吸引人且易于理解的方式图形化的巨大优势。

不要忘记,我们的大脑在识别视觉表示中的模式方面比识别数值模式更擅长(关于我们视觉能力的详细讨论,请参阅onlinelibrary.wiley.com/doi/full/10.1002/qua.24480)。

PCA 和 t-SNE 不是唯一可以帮助你降低数据维度并使其可视化的工具。UMAP (github.com/lmcinnes/umap)通常可以提供更快的低维度解决方案,具有清晰且明显的数据簇。变分自动编码器(在第七章表格竞赛建模中讨论)可以处理非线性维度缩减,并提供比 PCA 更有用的表示;然而,它们设置和调整起来更复杂。

示例实现

尽管你可以在 Zygmunt 的原始文章和我们所链接的笔记本中找到对抗验证的例子,但我们为你创建了一个全新的例子,这个例子基于 Playground 竞赛的Tabular Playground Series – Jan 2021 (www.kaggle.com/c/tabular-playground-series-jan-2021)。

你首先导入一些 Python 包,并从竞赛中获取训练数据和测试数据:

import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_predict
from sklearn.metrics import roc_auc_score
train = pd.read_csv("../input/tabular-playground-series-jan-2021/train.csv")
test = pd.read_csv("../input/tabular-playground-series-jan-2021/test.csv") 

数据准备简短而直接。由于所有特征都是数值型的,你不需要任何标签编码,但你必须用负数(通常-1 就足够了)填充任何缺失值,并删除目标和任何标识符;当标识符是递增的,对抗验证可能会返回一个高的 ROC-AUC 分数:

train = train.fillna(-1).drop(["id", "target"], axis=1)
test = test.fillna(-1).drop(["id", axis=1])
X = train.append(test)
y = [0] * len(train) + [1] * len(test) 

在这一点上,你只需要使用cross_val_predict函数为你数据生成RandomForestClassifier预测,该函数自动创建交叉验证方案并将预测存储在验证折上:

model = RandomForestClassifier()
cv_preds = cross_val_predict(model, X, y, cv=5, n_jobs=-1, method='predict_proba') 

因此,你获得的预测是无偏的(它们没有过拟合,因为你没有在你训练的数据上进行预测),并且可以用于误差估计。请注意,cross_val_predict不会拟合你的实例化模型,所以你不会从它那里得到任何信息,例如模型使用的重要特征是什么。如果你需要此类信息,你只需先通过调用model.fit(X, y)来拟合它。

最后,你可以查询你预测的 ROC-AUC 分数:

print(roc_auc_score(y_true=y, y_score=cv_preds[:,1])) 

你应该得到大约 0.49-0.50 的值(除非你使用具有固定random_seed的交叉验证,否则cross_val_predict不会是确定性的)。这意味着你无法轻易地区分训练数据和测试数据。因此,它们来自相同的分布。

处理训练数据和测试数据的不同分布

ROC-AUC 分数为 0.8 或更高会提醒你测试集是特殊的,并且与训练数据相当不同。在这些情况下,你可以采取哪些策略?实际上,你手头有几个策略:

  • 抑制

  • 在与测试集最相似的案例上进行训练

  • 通过模拟测试集进行验证

通过抑制,你移除在对抗测试集中对结果影响最大的变量,直到分布再次相同。为此,你需要一个迭代的方法。这次,你将你的模型拟合到所有数据上,然后检查重要性度量(例如,由 Scikit-learn 的RandomForest分类器的feature_importances_方法提供)和 ROC-AUC 拟合分数。在这个时候,你从数据中移除对模型最重要的变量,然后重新运行一切。你重复这个周期,其中你训练、测量 ROC-AUC 拟合,并从数据中移除最重要的变量,直到拟合的 ROC-AUC 分数下降到大约 0.5。

这种方法的唯一问题是,你可能实际上被迫从数据中移除大多数重要的变量,并且任何基于这种变量缺失数据的模型将无法由于缺乏信息特征而足够准确地预测。

当你在与测试集最相似的例子上进行训练时,你采取不同的方法,专注于训练中使用的变量而不是样本。在这种情况下,你只从训练集中选择符合测试分布的样本。任何训练好的模型都适合测试分布(但它不会推广到其他任何东西),这应该允许你在竞赛问题上有最好的测试。这种方法的局限性在于你正在减少数据集的大小,并且根据符合测试分布的样本数量,你可能会由于缺乏训练示例而得到一个非常偏颇的结果模型。在我们的前一个例子中,只选择训练数据中概率超过 0.5 的对抗预测并将它们相加,结果只选择了 1,495 个案例(这个数字如此之小,因为测试集与训练集没有很大不同):

print(np.sum(cv_preds[:len(X), 1] > 0.5)) 

最后,使用通过模拟测试集进行验证的策略,你继续在所有数据上训练,但为了验证目的,你只从训练集的对抗预测中选择概率超过 0.5(或更高的阈值,如 0.9)的例子。

将验证集调整到测试集将允许你选择所有可能超参数和模型选择,这将有利于在排行榜上获得更好的结果。

在我们的例子中,我们可以从以下代码的输出中找出feature_19feature_54在训练/测试分割中出现的差异最大:

model.fit(X, y)
ranks = sorted(list(zip(X.columns, model.feature_importances_)), 
               key=lambda x: x[1], reverse=True)
for feature, score in ranks:
    print(f"{feature:10} : {score:0.4f}") 

总结来说,我们对对抗性验证还有一些额外的评论。首先,使用它通常会帮助你更好地在竞赛中表现,但并不总是如此。Kaggle 的代码竞赛以及其他你无法完全访问测试集的竞赛,不能通过对抗性验证来检查。此外,对抗性验证可以告诉你关于整个测试数据的信息,但它不能就私有和公共测试数据之间的分割提供建议,这是最常见的公开排行榜过拟合和随之而来的波动的原因。

最后,虽然对抗性验证是一种为竞赛特别设计的非常具体的方法,但在现实世界中它有许多实际的应用场景:你有多少次选择了错误的测试集来验证你的模型?我们在这里提出的方法可以让你明白你是否在项目中正确地使用了测试数据和任何验证数据。此外,数据变化和生产中的模型可能会受到这些变化的影响,如果你不重新训练它们,可能会产生不良预测。这被称为概念漂移,通过使用对抗性验证,你可以立即了解是否需要重新训练新的模型以投入生产,或者是否可以继续使用之前的模型。

Giuliano_Janson

吉尔亚诺·贾森

www.kaggle.com/adjgiulio

吉尔亚诺·贾森是一位竞赛大师,同时也是 Zillow 集团机器学习和自然语言处理的高级应用科学家。他向我们讲述了他的竞赛胜利、交叉验证的重要性以及数据泄露,这是下一节的主题。

你最喜欢的竞赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

我理想的竞赛由以下三个要素组成:a) 一个有趣的问题需要解决,b) 一个中等大小的数据集,足够小以适应内存,但又不至于太小而成为过拟合的烦恼,c) 从特征工程的角度来看,有机会发挥创造力。这三个维度的结合是我擅长竞争机器学习的地方,因为我感觉我有能力在不担心工程约束的情况下使用严谨和创造力。

你是如何处理 Kaggle 竞赛的?这种方法与你在日常工作中所做的工作有何不同?

Kaggle 竞赛是一场马拉松。进入竞赛时,我知道我可以通过几天的工作获得我最好最终得分的 90%到 95%。其余的都是缓慢的磨砺。唯一的成功指标是你的分数;其他什么都不重要。

我的日常工作更像是一系列冲刺。模型性能只是我需要考虑的一小部分。上线日期可能同样重要,或者其他方面,如可解释性、可扩展性和可维护性可能会完全改变方向。每次冲刺后,优先级都会重新评估,最终产品可能看起来与最初设想的大相径庭。此外,建模只是我一天中的一部分。我花更多的时间与人交谈,管理优先级,构建用例,清洗数据,以及思考使原型模型成为成功的生产解决方案所需的一切。

告诉我们你参加的一个特别具有挑战性的比赛,以及你使用了哪些见解来应对这项任务。

我赢得的两个比赛中之一,基因泰克癌症比赛,是一个仅限硕士生的比赛。提供的数据是原始交易数据。没有从表格数据集开始。这种类型的工作是我喜欢的,因为特征工程实际上是机器学习我最喜欢的部分之一。由于我在比赛时已经在医疗保健行业工作了十年,我对数据有商业和临床见解,但最重要的是,我对正确处理这种数据复杂性的工程见解,以及当这种交易原始数据处理不当时可能出现的所有问题。这最终证明是获胜的关键,因为关于可能泄漏来源的初步假设之一最终被证明是正确的,并为我们的模型提供了“黄金特征”,从而为我们的模型提供了最后的推动力。从比赛中得到的启示是在进行特征工程或设置验证方法时始终格外小心。泄漏可能很难检测,而通常的模型验证方法在大多数情况下无法帮助识别泄漏,从而将模型置于在生产中表现不佳的风险之中。

Kaggle 是否帮助你在职业生涯中取得进展?如果是的话,是如何帮助的?

Kaggle 以两种方式帮助了我。首先,它提供了一个低门槛的入门点,让我接触到现代机器学习,大量的前沿建模技术,并迫使我真正理解专业级模型验证技术的艺术和科学。其次,Kaggle 让我有机会接触到应用机器学习领域的一些最聪明的大脑。我与一些顶级 Kaggle 参与者合作学到的教训是我珍视的,并且我每天都在努力与我的队友分享这些教训。

你是如何通过 Kaggle 建立起你的作品集的?

我的职业生涯并没有因为我的 Kaggle 简历而有太大的影响。我的意思是,我没有因为我的 Kaggle 排名而得到工作机会或面试。我是在已经担任高级数据科学角色时开始使用 Kaggle 的,尽管当时对机器学习的关注不多。多亏了在 Kaggle 上学到的知识,我能够更好地倡导我的职业转变,转向专注于机器学习的职位。

截至目前,我工作中很多人喜欢讨论竞争性机器学习,并对我的 Kaggle 经验中的技巧和窍门表示好奇,但事实也是,机器学习社区中很大一部分人甚至可能都不知道 Kaggle 是什么。

在你的经验中,不经验的 Kagglers 通常忽略了什么?你现在知道什么,而你在最初开始时希望知道的呢?

对于竞争性机器学习的新手来说,交叉验证的重要性很容易被忽视。一个稳固的交叉验证框架可以让你可靠和客观地衡量改进。在一个可能长达六个月的比赛中,最好的模型通常不是来自那些有最好初始想法的人,而是来自那些愿意根据数据经验反馈进行迭代和调整的人。一个出色的验证框架是这一切的基础。

在过去的比赛中,你犯过哪些错误?

我总是与机器学习新入门者分享的一个教训是“永远不要对过于复杂的思想过分迷恋。”面对一个新复杂问题时,很容易被诱惑去构建复杂的解决方案。复杂的解决方案通常需要时间来开发。但主要问题是,复杂的解决方案通常价值不大,基于稳健的基线。例如,想象你想模拟选举结果,并开始思考一系列特征来捕捉可观察和潜在地理、社会经济和时间特征之间的复杂条件关系。你可能会花费数周时间开发这些特征,假设由于它们考虑得非常周到,它们将产生重大影响。

错误在于,尽管这些复杂特征本身可能非常强大,但基于一系列简单特征和能够构建高度优化、数据驱动的深度交互的模型,我们花费时间和精力构建的复杂特征可能会突然导致边际改进很小甚至没有。我的建议是坚持奥卡姆剃刀原则,在诱惑更复杂的方法之前先尝试简单的事情。

你会推荐使用哪些特定的工具或库来进行数据分析或机器学习?

我是一个 pandas 和 Scikit-learn 的用户。我喜欢 pandas 如何使数据操作和探索变得简单,以及我如何能在几分钟内快速使用 Scikit-learn 原型化模型。我的大部分原型工作都是使用这两个库完成的。尽管如此,我的最终模型通常基于 XGBoost。对于深度学习,我喜欢使用 Keras。

处理泄露

在 Kaggle 竞赛中,一个常见的问题可能会影响挑战的结果,那就是数据泄露。数据泄露,通常简单地被称为泄露或使用其他花哨的名称(如黄金特征),涉及在训练阶段存在但在预测时不可用的信息。这种信息(泄露)的存在会使你的模型在训练和测试中表现过度,让你在比赛中排名很高,但从赞助商的角度来看,任何基于它的解决方案都将变得不可用或至多次优。

我们可以定义泄露为“当与真实情况相关的信息被人为且无意地引入训练特征数据或训练元数据中”时,正如迈克尔·金www.kaggle.com/mikeskim)在 2019 年Kaggle Days San Francisco的演讲中所说。

尽管赞助商和 Kaggle 团队都进行了仔细的检查,但在 Kaggle 竞赛中仍然经常发现泄露。这种情况是由于泄露的微妙和隐蔽性质,它可能会由于 Kagglers 进行的激烈搜索而意外出现,他们总是在寻找任何在比赛中得分更高的方法。

不要将数据泄露与泄露的验证策略混淆。在泄露的验证策略中,问题在于你安排的验证策略有利于更好的验证分数,因为训练数据中存在一些信息泄露。这与比赛本身无关,但它与你处理验证的方式有关。如果你在将训练数据和验证或测试数据分开之前对数据进行任何预处理(归一化、降维、缺失值插补),就会发生这种情况。

为了防止泄露的验证,如果你使用 Scikit-learn 来处理和操作你的数据,你绝对必须将你的验证数据排除在任何拟合操作之外。拟合操作倾向于在应用于任何用于验证的数据时创建泄露。避免这种情况的最佳方法是使用 Scikit-learn 管道(scikit-learn.org/stable/modules/generated/sklearn.pipeline.Pipeline.html),它将你的数据处理和模型一起封装,从而避免无意中将对数据应用任何泄露转换的风险。

因此,数据泄露并不是严格与验证操作相关,尽管它深深地影响着它们。尽管这一章主要致力于验证策略,但在此阶段我们认为讨论数据泄露是必要的,因为这个问题可能会深刻地影响你评估模型及其在比赛测试集之外泛化能力的方式。

一般而言,泄露可能起源于特征或示例级别。特征泄露是最常见的。它可能是由目标代理的存在或由目标本身之后的特征引起的。目标代理可以是来自处理标签本身或来自测试分割过程的任何东西;例如,在定义标识符时,特定的标识符(例如,一个编号弧)可能与某些目标响应相关联,使得模型在正确地提供经过适当处理的信息时更容易猜测。数据处理可能导致泄露的更微妙的方式是,当竞赛组织者在分割之前一起处理训练集和测试集时。历史上,Kaggle 竞赛中的泄露通常发现于:

  1. 组织者处理数据准备不当,尤其是在他们操作训练数据和测试数据的组合时(例如,在 Loan Default Prediction (www.kaggle.com/c/loan-default-prediction) 中,组织者最初使用了包含聚合历史数据的特征,这些特征泄露了未来的信息)。

  2. 当与时间索引或特定数据组相关联时,行的顺序(例如,在 Telstra Network Disruptions (www.kaggle.com/c/telstra-recruiting-network) 中,特征中记录的顺序暗示了代理信息,即位置信息,该信息在数据中不存在,但具有很高的预测性)。

  3. 当与时间索引相关联时,列的顺序(您可以通过将列用作行来获得提示)。

  4. 连续行中的特征重复,因为这可能会暗示具有相关响应的示例,例如在 Bosch Production Line Performance(参见 Belugawww.kaggle.com/c/bosch-production-line-performance/discussion/25434 提出的第一名解决方案)。

  5. 图像元数据(例如在 Two Sigma Connect: Rental Listing Inquiries (www.kaggle.com/c/two-sigma-connect-rental-listing-inquiries))中)。

  6. 编码和标识符的散列或其他容易被破解的匿名化实践。

后验信息的问题源于我们在不考虑时间和跨越时间因果序列的影响时处理信息的方式。由于我们是在回顾过去,我们常常忘记某些在当下有意义的变量在过去并没有价值。例如,如果你必须为新公司计算贷款的信用评分,知道借款的支付通常延迟是债务人较低可靠性和较高风险的强烈指标,但你不能在借钱之前知道这一点。这也是你在项目分析公司数据库时常见的问题:你的查询数据将代表当前情况,而不是过去的情况。如果不能指定你希望检索特定时间点存在的信息,重建过去信息也可能是一项困难的任务。因此,在构建任何模型之前,必须付出巨大的努力来寻找这些泄漏特征,并在构建模型之前排除或调整它们。

类似的问题在基于相同类型数据(例如银行或保险)的 Kaggle 竞赛中也普遍存在,尽管由于对竞赛数据的准备投入了大量的精力,它们以更微妙的方式和形式出现。一般来说,这些泄漏特征很容易被发现,因为它们与目标变量有很强的相关性,并且领域专家可以找出原因(例如,知道数据在数据库中记录的阶段)。因此,在竞赛中,你永远不会找到如此明显的特征,而是它们的衍生特征,通常是已经从赞助商控制中溜走的经过转换或处理过的特征。由于特征通常被匿名化以保护赞助商的业务,它们最终隐藏在其他特征中。这引发了一系列寻找金色/魔法特征的比赛,这是一种结合数据集中现有特征的搜索,以便泄漏出现。

你可以在这里阅读 Corey Levison 的一篇启发性的文章:www.linkedin.com/pulse/winning-13th-place-kaggles-magic-competition-corey-levinson/。这篇文章讲述了 Santander 客户交易预测竞赛如何变成寻找团队魔法特征的过程。

另一个很好的例子是由dune_dweller提供的:www.kaggle.com/c/telstra-recruiting-network/discussion/19239#109766。通过观察数据的排序方式,dune_dweller 发现数据很可能是按时间顺序排列的。将这一信息放入一个新的特征中提高了分数。

泄露可能发生的另一种方式是通过训练示例泄露。这种情况在非独立同分布数据中尤为常见。这意味着某些案例因为它们来自同一时期(或连续的时期)或同一组而相互关联。如果这些案例在训练或测试数据中不是全部在一起,而是被分开,那么机器学习算法有很大可能会学会如何识别这些案例(并推导出预测),而不是使用一般规则。一个经常引用的此类情况的例子涉及安德鲁·吴教授的团队(见twitter.com/nizkroberts/status/931121395748270080)。2017 年,他们使用了一个包含 30,000 名患者 100,000 张 X 光片的数据库。他们使用随机分割来分离训练和测试数据,没有意识到同一患者的 X 光片可能会部分出现在训练集和测试集中。像 Nick Roberts 这样的从业者注意到了这一点,指出可能存在的泄露可能会夸大模型的性能,并导致论文本身的重大修订。

当 Kaggle 竞赛中出现数据泄露时会发生什么?Kaggle 对此有明确的规定,它将:

  • 让竞赛照常进行(特别是如果泄露只有轻微的影响)

  • 从集合中移除泄露并重新启动竞赛

  • 生成一个不包含泄露的新测试集

特别是,Kaggle 建议将发现的任何泄露公之于众,尽管如果没有发生,这并非强制性的或受到制裁。然而,根据我们的经验,如果在竞赛中发生任何泄露,它很快就会变得非常明显,讨论论坛将开始充满关于神奇事物等的讨论。如果你留心论坛中的言论,并能将不同 Kagglers 提供的所有线索综合起来,你很快就会知道。

然而,请注意,一些玩家甚至可能利用关于神奇功能的讨论来分散其他竞争对手对严肃建模的注意力。例如,在Santander Customer Transaction Prediction竞赛中,有一个著名的涉及一些 Kagglers 的情况,他们激发了其他参与者对并非真正神奇的神奇功能的兴趣,将他们的努力引向了错误的方向(见此讨论:www.kaggle.com/c/santander-customer-transaction-prediction/discussion/87057#502362)。

我们的建议是仔细阅读竞赛论坛中关于泄露和神奇功能的讨论,并根据自己的兴趣和参与竞赛的动机来决定是否继续研究并使用发现的任何泄露。

不利用任何泄露可能会真正损害您的最终排名,尽管这肯定会破坏您的学习体验(因为泄露是一种扭曲,您不能对使用它的模型做出任何声明)。如果您参加比赛不是为了获得声誉或以后向赞助商寻求被雇佣的机会,那么使用您遇到的任何泄露都是完全可以接受的。否则,只需忽略它,继续努力工作在您的模型上(谁知道呢;也许 Kaggle 会在比赛结束时重置或修复比赛,使泄露变得无效,让许多使用它的人感到非常失望)。

泄露在不同的比赛中非常不同。如果您想了解一些在 Kaggle 比赛中发生过的真实泄露,您可以看看这三个难忘的例子:

摘要

到达本章的结尾,我们将总结一路上讨论的建议,以便您可以组织您的验证策略,并在比赛中提交几个合适的模型。

在本章中,我们首先分析了公共排行榜的动态,探讨了自适应过拟合和动荡等问题。然后我们讨论了在数据科学竞赛中验证的重要性,构建一个可靠的系统,将其调整到排行榜上,并跟踪您的努力。

讨论了各种验证策略后,我们还看到了通过使用对抗验证来调整超参数和检查测试数据或验证分区最佳的方式。我们通过讨论在 Kaggle 比赛中遇到的各种泄漏情况,并提供了如何处理它们的建议。

这里是我们的总结建议:

  • 在比赛中,始终将比赛的第一部分用于构建一个可靠的验证方案,鉴于其概率性质和泛化到未见数据的能力,更倾向于使用 k-fold 而不是训练-测试分割。

  • 如果您的验证方案不稳定,请使用更多的折数或多次运行它,使用不同的数据分区。始终使用对抗验证检查您的测试集。

  • 根据您的验证方案和排行榜跟踪结果。对于探索可能的优化和突破(如神奇特征或泄漏),更信任您的验证分数。

  • 正如我们在本章开头所解释的,在决定提交给比赛的最终作品时,使用您的验证分数。对于最终提交,根据情况以及您是否信任排行榜,从您最好的本地交叉验证模型和排行榜上得分良好的提交中选择,优先考虑前者。

在我们旅程的这个阶段,我们准备讨论如何使用表格数据来应对比赛,表格数据是按矩阵排列的数值或分类数据(行代表示例,列代表特征)。在下一章中,我们将讨论 Tabular Playground Series,这是 Kaggle 使用表格数据组织的每月比赛(由 Inversion 组织:www.kaggle.com/inversion)。

此外,我们将向您介绍一些具体的技术,帮助您在这些比赛中脱颖而出,例如特征工程、目标编码、降噪自编码器,以及一些用于表格数据的神经网络,作为表格数据问题中公认的顶级学习算法(如 XGBoost、LightGBM 或 CatBoost)的替代方案。

加入我们书籍的 Discord 空间

加入书籍的 Discord 工作空间,参加每月的“问我任何问题”活动,与作者交流:

packt.link/KaggleDiscord

二维码

第七章:为表格竞赛建模

直到 2017 年,没有必要在竞赛类型之间区分太多,由于绝大多数竞赛都是基于表格数据,你甚至找不到 Kaggle 论坛上关于“表格竞赛”的提及。突然,一切都变了。在相对缺乏竞赛(见www.kaggle.com/general/49904)之后,深度学习竞赛占据了上风,表格竞赛变得更为罕见,让许多人感到失望。它们变得如此罕见,以至于 Kaggle 最近不得不基于合成数据推出一系列表格竞赛。发生了什么?

到 2017-2018 年,数据科学已经发展到成熟阶段,许多公司已经开始了他们的数据之旅。数据科学仍然是一个热门话题,但不再是那么罕见。与当时 Kaggle 上充斥的问题类似的问题解决方案已经成为了许多公司的标准实践。在这种情况下,赞助商不太可能启动外部表格竞赛,因为他们已经在内部处理相同的问题。相比之下,深度学习仍然是一个未被充分探索的领域,并且将在很长时间内继续如此,因此开始竞赛来挑战现状并看看是否会出现新的东西是有意义的。

在本章中,我们将讨论表格竞赛。我们将涉及一些著名的历史性竞赛,并专注于更近期的表格游乐场系列,因为表格问题是大多数数据科学家标准实践的一部分,而且从 Kaggle 中确实有很多东西可以学习。我们将从讨论探索性数据分析(EDA)和特征工程开始,这两者是这些竞赛中的常见活动。

在介绍特征工程的关键策略之后,我们将扩展到许多相关主题,例如分类编码、特征选择、目标变换和伪标签。我们将以讨论表格数据的深度学习方法结束,介绍一些专门的深度神经网络,如 TabNet,并展示一个降噪自编码器。我们将解释为什么自编码器在最近的 Kaggle 竞赛中变得如此相关,同时在现实世界的应用中仍然处于边缘地位。

我们将涵盖:

  • 表格游乐场系列

  • 设置随机状态以实现可重复性

  • EDA 的重要性

  • 减小你的数据大小

  • 应用特征工程

  • 伪标签

  • 使用自编码器进行降噪

  • 表格竞赛中的神经网络

本章不会涵盖与表格竞赛相关的所有主题,但你可以在许多其他书籍中轻松找到这些主题,因为它们是数据科学的核心。本章将做的是展示一系列特殊技术和方法,这些技术和方法表征了 Kaggle 上的表格竞赛,而且你不太可能在其他地方轻易找到,除非是在 Kaggle 论坛上。

表格游乐场系列

由于对表格问题的巨大需求,Kaggle 员工在 2021 年开始了一项实验,推出了一项名为“表格游乐场系列”的月度比赛。这些比赛基于复制公共数据或先前比赛数据的合成数据集。这些合成数据是通过名为 CTGAN 的深度学习生成网络创建的。

您可以在github.com/sdv-dev/CTGAN找到 CTGAN 代码。还有一篇相关的论文解释了它是如何通过模拟表格数据中行的概率分布来工作的,然后生成逼真的合成数据(见arxiv.org/pdf/1907.00503v2.pdf)。

合成数据宝库 (sdv.dev/),一个麻省理工学院的倡议,创造了 CTGAN 背后的技术以及围绕它的许多工具。结果是建立了一套开源软件系统,旨在帮助企业生成模仿真实数据的合成数据;它可以帮助数据科学家根据真实数据创建匿名数据集,以及为建模目的增强现有数据集。

Kaggle 在 2021 年推出了 13 个相当成功的比赛,尽管没有提供积分、奖牌或奖品(只有一些商品),但仍然吸引了众多 Kagglers。以下是 2021 年的列表;您可以使用它通过类型或指标定位特定问题,并查找相关的资源,如专题讨论或笔记本:

月份 问题 变量 指标 缺失数据
一月 2021 对未指定问题的回归 数值 RMSE
二月 2021 预测保险索赔价值的回归 数值和分类 RMSE
三月 2021 预测保险索赔的二分类 数值和分类 AUC
四月 2021 与原始泰坦尼克数据集非常相似的复制品的二分类 数值和分类 准确率
五月 2021 预测电子商务产品类别的多分类,基于列表的各种属性 分类 多分类 LogLoss
六月 2021 预测电子商务产品类别的多分类,基于列表的各种属性 数值和分类 多分类 LogLoss
七月 2021 通过各种输入传感器值(例如,时间序列)预测城市空气污染的多元回归 数值,时间 RMSLE
八月 2021 计算与贷款违约相关的损失的回归 数值 RMSE
30 天的机器学习 保险索赔价值的回归 数值和分类 RMSE
九月 2021 预测是否会在保险单上提出索赔的二分类 数值 AUC
2021 年 10 月 二元分类预测给定各种化学性质的分子的生物反应 数值和分类 AUC
2021 年 11 月 通过从电子邮件中提取的各种特征识别垃圾邮件的二进制分类 数值 AUC
2021 年 12 月 基于原始森林覆盖类型预测竞赛的多类分类 数值和分类 多类分类准确率

表 7.1:2021 年 Tabular Playground Series 竞赛

Tabular Playground 竞赛在 2022 年继续进行,面临更加复杂和具有挑战性的问题:

2022 年 1 月 预测 Kaggle 商品的销售,基于两个虚构的独立商店链 日期和分类 对称平均绝对百分比误差(SMAPE)
2022 年 2 月 使用包含一些数据压缩和数据丢失的基因组分析技术数据对 10 种不同的细菌物种进行分类 数值 分类准确率

表 7.2:2022 年 Tabular Playground Series 竞赛

本章的大部分内容是通过观察在这些竞赛中出现的代码和讨论来撰写的,而不是分析过去的更辉煌的竞赛。正如我们提到的,我们认为由于专业环境的改变,表格竞赛确实已经永久消失了,您会发现阅读与现在相关的建议和提示比过去更有用。

正如在其他带有 Kaggle 积分和奖牌的完整竞赛中一样,在表格竞赛中,我们建议您遵循一个简单但非常有效的流程,我们在本书的其他地方讨论过:

  • 探索性数据分析(EDA)

  • 数据准备

  • 建模(使用交叉验证策略进行模型验证)

  • 后处理

  • 提交

通常,您还必须注意保持可重复性和保存所有模型(来自每个折叠),使用的参数列表,所有折叠预测,所有出折叠预测,以及训练在所有数据上的模型的所有预测。

您应该以易于恢复和重建的方式保存所有这些信息,例如使用适当的标签,跟踪 MD5 散列值(您可以参考此 Stack Overflow 答案以获取详细信息:stackoverflow.com/questions/16874598/how-do-i-calculate-the-md5-checksum-of-a-file-in-python),以及跟踪每个实验的 CV 分数和排行榜结果。大多数 Kagglers 使用简单的工具,如.txt文件或 Excel 电子表格来完成这项工作,但存在更复杂的方法,例如使用:

最后,重要的是结果,而不是你使用的工具,所以尽量在实验和模型中保持秩序,即使在竞赛的激烈竞争中也是如此。

在我们继续之前,也要考虑一下 Kaggle 用于生成这些竞赛数据的科技;如果你能正确理解数据是如何生成的,这将给你带来重要的优势。此外,理解合成数据的工作原理确实可以影响你在现实世界中做数据科学的方式,因为它为你提供了一种轻松获取更多样化数据用于训练的方法。

例如,让我们以Google Brain – 呼吸机压力预测竞赛(www.kaggle.com/c/ventilator-pressure-prediction)为例。在这个竞赛中,你必须开发用于机械通气控制的机器学习。虽然你可以通过使用深度学习对提供的数据进行建模来获得良好的结果,但由于数据的合成来源,你也可以逆向工程其生成过程,并获得排行榜上的顶尖结果,正如Jun Koda(www.kaggle.com/junkoda)所做并在他的帖子中解释的那样:www.kaggle.com/c/ventilator-pressure-prediction/discussion/285278

通过自己生成人工数据和理解合成数据从未如此简单,您可以从这个笔记本(www.kaggle.com/lucamassaron/how-to-use-ctgan-to-generate-more-data)中验证,这个笔记本最初是由Dariush Bahrami(www.kaggle.com/dariushbahrami)编写和测试的。

设置随机状态以实现可重现性

在我们开始讨论在表格竞赛中可能使用的步骤和模型之前,回到我们上面提到的可重现性主题将是有用的。

在你看到的 Kaggle 笔记本上的大多数命令中,你都会找到一个参数声明一个数字,一个种子,作为随机状态。这个设置对你的结果的可重现性很重要。由于许多算法不是确定性的,而是基于随机性,通过设置种子,你影响随机生成器的行为,使其随机性变得可预测:相同的随机种子对应相同的随机数序列。换句话说,它允许你在每次运行相同的代码后获得相同的结果。

正因如此,你在 Scikit-learn 中的所有机器学习算法以及所有与 Scikit-learn 兼容的模型(例如,XGBoost、LightGBM 和 CatBoost,仅举一些最受欢迎的)中都会找到一个随机种子设置参数。

在现实世界项目和 Kaggle 比赛中,结果的可重复性都同样重要。在现实世界中,拥有一个可重复的模型可以更好地跟踪模型开发和一致性。在 Kaggle 比赛中,可重复性有助于更好地测试假设,因为你正在控制模型中的任何变化来源。例如,如果你创建了一个新特征,将其放入可重复的管道中,将有助于你了解该特征是否有优势。你可以确信模型中的任何改进或恶化只能归因于该特征,而不是由于自上次运行模型以来某些随机过程的变化。

再次强调,在处理公共笔记本时,可重复性可以为你带来优势。这些笔记本通常会有一个固定的种子值,可能是 0、1 或 42。数字 42 之所以流行,是因为它是对道格拉斯·亚当斯的《银河系漫游指南》的引用,在那里它是“生命、宇宙和一切的终极问题的答案”,由名为“深思想”的超级计算机在 7500 万年内计算得出。现在,如果比赛中的每个人都使用相同的随机种子,可能会产生双重效果:

  • 随机种子可能与公共排行榜配合得过于完美,这意味着过度拟合

  • 许多 Kagglers 会产生类似的结果,这将以相同的方式影响他们在私人排行榜上的排名

通过改变随机种子,你正在避免过度拟合并打破排名;换句话说,你得到了与其他人不同的结果,这最终可能让你处于优势地位。此外,如果你最终赢得了 Kaggle 比赛,你需要展示你的模型是如何产生获奖提交的,因此,如果你想要快速获得奖金,确保一切完全可重复至关重要。

TensorFlow 和 PyTorch 模型没有明确使用随机种子参数,因此确保它们的完全可重复性更具挑战性。以下代码片段在运行时为 TensorFlow 和 PyTorch 模型设置相同的随机种子:

def seed_everything(seed, 
                    tensorflow_init=True, 
                    pytorch_init=True):
    """
    Seeds basic parameters for reproducibility of results
    """
    random.seed(seed)
    os.environ["PYTHONHASHSEED"] = str(seed)
    np.random.seed(seed)
    if tensorflow_init is True:
        tf.random.set_seed(seed)
    if pytorch_init is True:
        torch.manual_seed(seed)
        torch.cuda.manual_seed(seed)
        torch.backends.cudnn.deterministic = True
        torch.backends.cudnn.benchmark = False 

对于 Scikit-learn 来说,相反建议直接设置随机种子——当类或函数允许时——使用random_state参数。

EDA 的重要性

EDA(探索性数据分析)这个术语来源于现代统计方法学的杰出代表约翰·W·图基的工作。在他的 1977 年著作《探索性数据分析》(EDA 的缩写),图基认为 EDA 是一种探索数据、揭示证据并发展可以由统计测试后来证实假设的方法。

他的想法是,我们如何定义统计假设可能更多地基于观察和推理,而不是仅仅基于数学计算的顺序测试。这个想法很好地转化为机器学习领域,因为,正如我们将在下一节讨论的,数据可以被改进和预处理,以便学习算法可以更好地、更有效地工作。

在 Kaggle 竞赛的 EDA 中,你将寻找:

  • 缺失值,更重要的是,与目标相关的缺失值模式。

  • 偏斜的数值变量及其可能的转换。

  • 可以一起分组的分类变量中的罕见类别。

  • 可能的异常值,包括单变量和多变量。

  • 高度相关(甚至重复)的特征。对于分类变量,关注重叠的类别。

  • 对于该问题最有预测性的特征。

你可以通过几种描述性分析、图表和图表来实现这一点,首先检查每个独特的特征(单变量分析,在统计学中),然后匹配几个变量(双变量分析,例如在散点图中),最后同时考虑更多特征(多变量方法)。

如果你感到懒惰或者不确定如何以及从哪里开始,最初依赖自动化策略可能会有所帮助。例如,你可能发现AutoViz (github.com/AutoViML/AutoViz),这是一个流行的快速 EDA 免费软件工具,可以为你节省大量时间。你可以在笔记本上通过运行以下命令来安装它:

pip install git+git://github.com/AutoViML/AutoViz.git 

通过阅读 Dan Roth 在 Medium 上发表的这篇文章,你可以更清楚地了解 AutoViz 能为你做什么,文章链接为towardsdatascience.com/autoviz-a-new-tool-for-automated-visualization-ec9c1744a6ad,或者浏览一些有趣的公共笔记本,例如 Georgii Vyshnia 的www.kaggle.com/gvyshnya/automating-eda-and-feature-importance-detectionwww.kaggle.com/gvyshnya

在后面的链接中,你还可以找到对另一个工具Sweetviz (github.com/fbdesignpro/sweetviz)的引用。Sweetviz 有一篇基于泰坦尼克号数据集的概述文章和教程,可在towardsdatascience.com/powerful-eda-exploratory-data-analysis-in-just-two-lines-of-code-using-sweetviz-6c943d32f34找到。

你可能还会发现另一个有用的流行工具是Pandas Profiling (github.com/pandas-profiling/pandas-profiling),它更依赖于经典的统计描述性统计和可视化,如这篇文章所述:medium.com/analytics-vidhya/pandas-profiling-5ecd0b977ecd

等待其他 Kagglers(数据科学家社区)发布有趣的 EDA 笔记本也可能是一个解决方案,所以请始终关注笔记本部分;有时,宝贵的提示可能会出现。这应该会启动你的建模阶段,并帮助你了解竞赛的基本规则和禁忌。然而,记住,当 EDA 对特定问题高度具体时,它就不再是商品,而成为竞赛的资产;这是你在自动化解决方案中永远找不到的,在公开笔记本中也很少见。你必须自己进行 EDA 并收集关键、获胜的见解。

考虑到所有这些因素,我们的建议是稍微了解一下自动化工具,因为它们真的很容易学习和运行。你可以节省大量的时间,这些时间你可以用来查看图表和推理可能的见解,这无疑会帮助你在竞赛中的表现。然而,在这样做之后,你需要学习 Matplotlib 和 Seaborn,并尝试在不太标准的图表上做一些自己的尝试,这些图表依赖于提供的数据类型和问题。

例如,如果你被给了一系列随时间进行的测量数据,基于时间的连续函数的绘图与绘制单个记录的时间点一样有用,例如显示一个观测值与另一个观测值之间的不同滞后,这可能表明揭示了更好的预测的见解。

使用 t-SNE 和 UMAP 进行降维

在进行 EDA(电子设计自动化)时,你可以创建许多可能的图表,我们并没有意图在这里列出所有,但有一些降维图表值得花点篇幅讨论,因为它们可以提供与非常具体和定制化图表一样多的信息。这些是t-SNE (lvdmaaten.github.io/tsne/)和UMAP (github.com/lmcinnes/umap)。

t-SNE 和 UMAP 是两种数据科学家经常使用的技术,它们允许你将多元数据投影到低维空间。它们通常用于在二维空间中表示复杂的数据集。2-D UMAP 和 t-SNE 图表可以揭示数据问题中异常值和相关的聚类。

实际上,如果你能绘制出结果 2-D 投影的散点图,并按目标值着色,这个图表可能会给你一些关于处理子组的可能策略的提示。

尽管它与图像竞赛有关,但 UMAP 和 t-SNE 如何帮助你更好地理解数据的良好例子是Chris DeotteSIIM-ISIC 黑色素瘤分类竞赛的分析(见www.kaggle.com/c/siim-isic-melanoma-classification/discussion/168028)。在这个例子中,Chris 将训练数据和测试数据关联到了同一低维投影上,突出了只有测试示例存在的部分。

尽管 UMAP 和 t-SNE 在发现难以找到的数据模式方面提供了无价的帮助,但你仍然可以将它们用作建模努力中的特征。在Otto Group 产品分类挑战中,Mike Kim使用 t-SNE 投影作为竞赛的训练特征,展示了这种使用的有趣例子(见www.kaggle.com/c/otto-group-product-classification-challenge/discussion/14295)。

如文章如何有效地使用 t-SNE(distill.pub/2016/misread-tsne/)所述,你必须正确使用这些技术,因为很容易在没有任何聚类和模式的地方发现它们。同样的警告也适用于 UMAP,因为它也可以生成可能被误读的图表。例如pair-code.github.io/understanding-umap/这样的指南为 UMAP 和 t-SNE 在真实世界数据上的性能提供了合理的建议和注意事项。

尽管存在这些危险,但根据我们的经验,这些方法肯定比基于 PCA 或 SVD 等线性组合方差重构的经典方法更有揭示性。与这些方法相比,UMAP 和 t-SNE 能够极大地降低维度,同时保持数据的拓扑结构,允许可视化结果。然而,作为副作用,它们的拟合速度要慢得多。不过,NVIDIA 已经发布了基于 CUDA 的RAPIDS套件(developer.nvidia.com/rapids),使用 GPU 驱动的笔记本或脚本,可以在非常合理的时间内返回 UMAP 和 t-SNE 的结果,从而有效地将其用作 EDA 工具。

你可以在以下链接中找到一个有用的例子,展示了如何使用 RAPIDS 实现和 GPU 进行数据探索,以参加30 Days of ML竞赛:www.kaggle.com/lucamassaron/interesting-eda-tsne-umap/

在下面的图中,这是上述示例笔记本的输出,你可以看到多个聚类如何填充数据集,但没有任何一个可以被认定为与目标有特定的关系:

__results___9_0.png

图 7.1:t-SNE 图中出现的多个聚类

在另一个笔记本(www.kaggle.com/lucamassaron/really-not-missing-at-random)中,相同的技巧被应用于缺失样本的二进制指标,揭示出一些引人入胜的图表,这些图表暗示了由某种特定类型响应主导的特定和独立区域。实际上,在那个例子中,缺失样本并不是随机出现的,它们具有相当强的预测性:

__results___10_0.png

图 7.2:这个 t-SNE 图很容易揭示出正目标占主导地位的区域

减少数据大小

如果你直接在 Kaggle 笔记本上工作,你会发现它们的限制相当令人烦恼,处理它们有时会变成一个耗时的工作。这些限制之一是内存不足错误,这会停止执行并迫使你从脚本开始重新启动。这在许多比赛中相当常见。然而,与基于文本或图像的深度学习比赛不同,在这些比赛中你可以分批从磁盘检索数据并处理它们,而大多数处理表格数据的算法都需要在内存中处理所有数据。

最常见的情况是,当你使用 Pandas 的 read_csv 从 CSV 文件上传数据时,但 DataFrame 太大,无法在 Kaggle 笔记本中进行特征工程和机器学习。解决方案是在不丢失任何信息的情况下压缩你使用的 Pandas DataFrame 的大小(无损压缩)。这可以通过以下脚本轻松实现,该脚本源自 Guillaume Martin 的工作(你可以在以下位置找到原始笔记本:www.kaggle.com/gemartin/load-data-reduce-memory-usage)。

def reduce_mem_usage(df, verbose=True):
    numerics = ['int16', 'int32', 'int64', 
                'float16', 'float32', 'float64']'
    start_mem = df.memory_usage().sum() / 1024**2    
    for col in df.columns:
        col_type = df[col].dtypes
        if col_type in numerics:
            c_min = df[col].min()
            c_max = df[col].max()
            if str(col_type)[:3] == 'int':
                if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
                    df[col] = df[col].astype(np.int8)
                elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
                    df[col] = df[col].astype(np.int16)
                elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
                    df[col] = df[col].astype(np.int32)
                elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
                    df[col] = df[col].astype(np.int64)  
            else:
                if c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
                    df[col] = df[col].astype(np.float32)
                else:
                    df[col] = df[col].astype(np.float64)    
    end_mem = df.memory_usage().sum() / 1024**2
    if verbose: print('Mem. usage decreased to {:5.2f} Mb ({:.1f}% reduction)'.format(end_mem, 100 * (start_mem - end_mem) / start_mem))
    return df 

Guillaume Martin 并不是第一个在 Kaggle 上提出这种想法的人。第一个有这种压缩 Pandas DataFrame 想法的 Kaggler 是 Arjan Groen,他在 Zillow 比赛中编写了一个减少函数(www.kaggle.com/arjanso/reducing-dataframe-memory-size-by-65)。

这个脚本利用了这样一个事实:数据集中的所有数值特征都位于一个特定的值域内。由于 Python 中有不同类型的整数和浮点数值变量,根据它们在内存中占用的字节数,脚本会将每个特征中找到的值域与每种数值类型可以接受的最大值和最小值进行比较。这样做是为了将特征设置为与其值域相匹配且需要最低内存的数值类型。

这种方法在 Kaggle 笔记本上工作得像微风一样,但也有一些注意事项。一旦你通过压缩为每个特征设置了最佳匹配的数值类型,你就不能应用任何可能导致数值超过设定数值类型容量的特征工程,因为这样的操作会产生错误的结果。我们的建议是在特征工程之后或在进行不会重新缩放现有数据的主要转换之前应用它。结合垃圾收集库gcgc.collect()方法将改善你的 Kaggle 笔记本的内存状况。

减小你的数据大小(以及其他事情)的另一种方式是使用特征工程(特别是特征选择和数据压缩)。

应用特征工程

在现实世界的项目中,能够区分成功机器学习模型和一般模型的往往是数据,而不是模型。当我们谈论数据时,区分糟糕、良好和优秀数据的不同之处不仅仅是缺失值的缺乏和值的可靠性(其“质量”),或者是可用示例的数量(其“数量”)。根据我们的经验,真正的区分因素是内容本身的信息价值,这由特征的类型来表示。

特征是数据科学项目中真正可以塑造的粘土,因为它们包含了模型用来分离类别或估计值的所需信息。每个模型都有表达性和将特征转换为预测的能力,但如果你在特征方面有所欠缺,没有任何模型能帮助你启动并给出更好的预测。模型只是使数据中的价值显现出来。它们本身并不具有魔法

在 Kaggle 上,除了那些罕见的比赛中你可以寻找更多数据来添加之外,所有参与者从开始就拥有相同的数据。在那个阶段,你如何处理数据就构成了大部分差异。忽视你可以改进现有数据的这一事实是许多 Kagglers 常犯的一个错误。特征工程,一套将数据转换为对模型更有用信息的技术的集合,是提高比赛表现的关键。即使你应用的模型更强大,也需要你处理数据并将其呈现为更易于理解的形式。

特征工程也是将任何先验知识(通常是关于问题的专业知识)嵌入数据的方式:通过求和、减法或除法现有特征,您可以得到可以更好地解释您正在处理的问题的指标或估计。特征工程还有其他目的,在 Kaggle 竞赛中可能不那么有价值,但在现实世界的项目中可能很重要。第一个目的是减少训练数据的大小(在处理 Notebooks 时,这可能在 Kaggle 竞赛中也有用,因为 Notebooks 有内存限制)。第二个目的是通过使用人类可理解的特征来使结果的模型更容易解释。

每个领域可能都有编码特定的变量变换,这些变换可能不是显而易见的,但对该领域专家来说是众所周知的。想想金融领域,在那里您必须通过应用特定的变换(如卡尔曼滤波或小波变换)来区分不同特征集的信号和噪声,这些特征集代表市场和公司数据。鉴于可能存在的领域数量和许多特征工程过程的复杂性,在本节中,我们不会深入探讨特定领域的专业知识及其处理特征的特殊方式。

相反,我们将向您展示最常见和最通用的技术,您可以在任何表格竞赛中应用这些技术。

容易导出的特征

使用变换来提取特征是最简单的方法,但通常也是最有效的。例如,计算特征比率(将一个特征除以另一个特征)可能非常有效,因为许多算法无法模拟除法(例如,梯度提升)或者很难尝试模拟(例如,深度神经网络)。以下是一些常见的变换尝试:

  • 时间特征处理:将日期分解为其元素(年、月、日);将其转换为年份中的周和星期几;计算日期之间的差异;计算与关键事件之间的差异(例如,假日)。

对于日期,另一种常见的变换是从日期或时间中提取时间元素。基于正弦和余弦变换的循环连续变换也很有用,可以表示时间的连续性并创建周期性特征:

cycle = 7
df['weekday_sin'] = np.sin(2 * np.pi * df['col1'].dt.dayofweek / cycle)
df['weekday_cos'] = np.cos(2 * np.pi * df['col1'].dt.dayofweek / cycle) 
  • 数值特征变换:缩放;归一化;对数或指数变换;分离整数和十进制部分;对两个数值特征求和、减法、乘法或除法。通过标准化(统计学中使用的 z 分数方法)或归一化(也称为最小-最大缩放)获得的缩放对于使用对特征规模敏感的算法(如任何神经网络)是有意义的。

  • 数值特征的分箱:这是通过将值分布到一定数量的箱中来将连续变量转换为离散变量。分箱有助于去除数据中的噪声和错误,并且当与独热编码(例如,查看 Scikit-learn 实现)结合使用时,它允许对分箱特征和目标变量之间的非线性关系进行建模。

  • 分类特征编码:独热编码;将两个或三个分类特征合并在一起的数据处理;或者更复杂的目标编码(更多内容将在以下章节中介绍)。

  • 基于级别的分类特征拆分和聚合:例如,在泰坦尼克号竞赛(www.kaggle.com/c/titanic)中,你可以拆分名字和姓氏,以及它们的缩写,以创建新的特征。

  • 多项式特征是通过将特征提升到指数来创建的。例如,查看这个 Scikit-learn 函数:scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PolynomialFeatures.html.

虽然它们不是正确的特征工程,而是更多数据清洗技术,但缺失数据和异常值处理涉及对数据进行更改,这些更改仍然会转换你的特征,并且它们可以帮助数据中的信号出现:

  • 缺失值处理:创建指示缺失值的二元特征,因为有时缺失并不是随机的,缺失值背后可能有重要的原因。通常,缺失值表明了数据记录的方式,充当其他变量的代理变量。就像人口普查调查一样:如果有人不告诉你他们的收入,这意味着他们非常贫穷或非常富有。如果学习算法需要,可以用平均值、中位数或众数(很少需要使用更复杂的方法)替换缺失值。

你可以参考由Parul Pandey编写的完整指南作为参考:www.kaggle.com/parulpandey www.kaggle.com/parulpandey/a-guide-to-handling-missing-values-in-python.

只需记住,一些模型可以自己处理缺失值,并且做得比许多标准方法都要好,因为缺失值处理是它们优化过程的一部分。可以自己处理缺失值的模型都是梯度提升模型:

否则,你可以简单地以单变量方式定位异常样本,根据它们与平均值的多少个标准差或它们与四分位数范围IQR)边界的距离来做出判断。在这种情况下,你可能简单地排除任何高于1.5 * IQR + Q3(上异常值)或低于Q1 - 1.5 * IQR(下异常值)的点。一旦你找到了异常值,你也可以通过使用二元变量来指出它们。

所有这些数据转换都可以提高你的模型的预测性能,但在比赛中它们很少是决定性的。尽管这是必要的,但你不能仅仅依赖于基本的特征工程。在接下来的章节中,我们将建议更复杂的程序来从你的数据中提取价值。

基于行和列的元特征

为了在竞争中表现出色,你需要更复杂的特征工程。一个好的开始是查看基于每一的特征,单独考虑:

  • 计算数值值(或其子集)的均值、中位数、总和、标准差、最小值或最大值

  • 计算缺失值

  • 计算行中找到的常见值的频率(例如,考虑二元特征并计算正值)

  • 将每一行分配到由聚类分析(如k-均值)得出的簇中

这些元特征(之所以称为这样,是因为它们是代表一组单个特征的特性)通过指出算法中的特定样本组来帮助区分你数据中找到的不同类型的样本。

元特征也可以基于构建。对单个特征的聚合和汇总操作的目标是提供有关数值和分类特征值的信息;这个特征是常见还是罕见? 这是模型无法掌握的信息,因为它无法在特征中计数分类实例。

作为元特征,你可以使用任何类型的列统计量(例如众数、均值、中位数、总和、标准差、最小值、最大值,以及对于数值特征的偏度和峰度)。对于列向的元特征,你可以采取几种不同的方法:

  • 频率编码:简单统计分类特征中值的频率,然后在新的特征中用这些值的频率来替换它们。当数值特征中存在频繁出现的值时,也可以对数值特征应用频率编码。

  • 相对于相关组的频率和列统计信息计算:在这种情况下,您可以从数值和分类特征的值中创建新特征,因为您正在考虑数据中的不同组。一个组可以是您通过聚类分析计算出的聚类,或者您可以使用特征定义的组(例如,年龄可以产生年龄组,地区可以提供区域,等等)。然后,根据每个样本所属的组应用描述每个组的元特征。例如,使用 Pandas 的groupby函数,您可以创建元特征,然后根据分组变量将它们与原始数据合并。这个特征工程技术的难点在于在数据中找到有意义的组来计算特征。

  • 通过组合更多的组,可以进一步推导出列频率和统计信息。

列表当然不是详尽的,但它应该能给您一个在特征级别和行级别使用频率和统计信息寻找新特征的思路。

让我们通过一个基于Amazon Employee Access Challenge数据的简单例子来看看。首先,我们将对ROLE_TITLE特征应用频率编码:

import pandas as pd
train = pd.read_csv("../input/amazon-employee-access-challenge/train.csv")
# Frequency count of a feature
feature_counts = train.groupby('ROLE_TITLE').size()
print(train['ROLE_TITLE'].apply(lambda x: feature_counts[x])) 

结果将显示特征类别已被它们的观察频率所取代。

现在我们继续根据ROLE_DEPTNAME的分组对ROLE_TITLE特征进行编码,因为我们预计不同的头衔在某些部门可能更常见,而在其他部门则更罕见。

结果将是一个由两者组成的新特征,我们用它来计算其值的频率:

feature_counts = train.groupby(['ROLE_DEPTNAME', 'ROLE_TITLE']).size()
print(train[['ROLE_DEPTNAME', 'ROLE_TITLE']].apply(lambda x: feature_counts[x[0]][x[1]], axis=1)) 

您可以在以下 Kaggle 笔记本中找到所有的工作代码和结果:www.kaggle.com/lucamassaron/meta-features-and-target-encoding/.

目标编码

由于 Scikit-learn 提供的简单函数,如:

  • LabelEncoder

  • OneHotEncoder

  • OrdinalEncoder

这些函数可以将类别转换为数值特征,然后再转换为机器学习算法容易处理的二进制特征。然而,当需要处理的类别数量太多时,由单热编码策略产生的数据集会变得稀疏(其中大部分值将是零值),对于计算机或笔记本的内存和处理器来说处理起来很麻烦。在这些情况下,我们谈论的是高基数特征,这需要特殊处理。

自从早期的 Kaggle 竞赛以来,高基数变量实际上已经使用一个根据 Micci-Barreca, D. 的编码函数进行处理,该函数是根据 在分类和预测问题中处理高基数分类属性的前处理方案。ACM SIGKDD Explorations Newsletter 3.1 (2001): 27-32。

这种方法的背后思想是将分类特征的多个类别转换为它们对应的预期目标值。在回归的情况下,这是该类别的平均预期值;对于二分类,它是给定该类别的条件概率;对于多分类,你有每个可能结果的条件概率。

例如,在 泰坦尼克号 入门竞赛 (www.kaggle.com/competitions/titanic) 中,你必须确定每位乘客的生存概率,对分类特征进行目标编码,例如性别特征,意味着用其平均生存概率替换性别值。

以这种方式,分类特征被转换为一个数值型特征,而无需将数据转换为更大和更稀疏的数据集。简而言之,这就是 目标编码,它在许多情况下确实非常有效,因为它类似于基于高基数特征的堆叠预测。然而,与堆叠预测一样,你实际上是在使用另一个模型的预测作为特征,目标编码会带来过拟合的风险。事实上,当某些类别非常罕见时,使用目标编码几乎等同于提供目标标签。有方法可以避免这种情况。

在看到可以直接导入到你的代码中的实现之前,让我们看看一个实际的目标编码代码示例。此代码用于 PetFinder.my 预测竞赛 中的一个高分提交:

import numpy as np
import pandas as pd
from sklearn.base import BaseEstimator, TransformerMixin
class TargetEncode(BaseEstimator, TransformerMixin):

    def __init__(self, categories='auto', k=1, f=1, 
                 noise_level=0, random_state=None):
        if type(categories)==str and categories!='auto':
            self.categories = [categories]
        else:
            self.categories = categories
        self.k = k
        self.f = f
        self.noise_level = noise_level
        self.encodings = dict()
        self.prior = None
        self.random_state = random_state

    def add_noise(self, series, noise_level):
        return series * (1 + noise_level *   
                         np.random.randn(len(series)))

    def fit(self, X, y=None):
        if type(self.categories)=='auto':
            self.categories = np.where(X.dtypes == type(object()))[0]
        temp = X.loc[:, self.categories].copy()
        temp['target'] = y
        self.prior = np.mean(y)
        for variable in self.categories:
            avg = (temp.groupby(by=variable)['target']
                       .agg(['mean', 'count']))
            # Compute smoothing 
            smoothing = (1 / (1 + np.exp(-(avg['count'] - self.k) /                 
                         self.f)))
            # The bigger the count the less full_avg is accounted
            self.encodings[variable] = dict(self.prior * (1 -  
                             smoothing) + avg['mean'] * smoothing)

        return self

    def transform(self, X):
        Xt = X.copy()
        for variable in self.categories:
            Xt[variable].replace(self.encodings[variable], 
                                 inplace=True)
            unknown_value = {value:self.prior for value in 
                             X[variable].unique() 
                             if value not in 
                             self.encodings[variable].keys()}
            if len(unknown_value) > 0:
                Xt[variable].replace(unknown_value, inplace=True)
            Xt[variable] = Xt[variable].astype(float)
            if self.noise_level > 0:
                if self.random_state is not None:
                    np.random.seed(self.random_state)
                Xt[variable] = self.add_noise(Xt[variable], 
                                              self.noise_level)
        return Xt

    def fit_transform(self, X, y=None):
        self.fit(X, y)
        return self.transform(X) 

函数的输入参数为:

  • categories: 你想要进行目标编码的特征的列名。你可以保持 'auto' 选项开启,类将自动选择对象字符串。

  • k (int): 考虑类别平均值的样本的最小数量。

  • f (int): 平滑效果,用于平衡类别平均数与先验概率,或相对于所有训练样本的平均值。

  • noise_level: 你想要添加到目标编码中的噪声量,以避免过拟合。开始时使用非常小的数字。

  • random_state: 当 noise_level > 0 时,用于复制相同目标编码的可重复性种子。

注意 kf 参数的存在。实际上,对于分类特征的 i 级别,我们正在寻找一个近似值,这个值可以帮助我们使用单个编码变量更好地预测目标。用观察到的条件概率替换级别可能是解决方案,但对于观察样本较少的级别则效果不佳。解决方案是将该级别的观察后验概率(给定编码特征的特定值的目标概率)与先验概率(在整个样本中观察到的目标概率)使用一个 lambda 因子进行混合。这被称为 经验贝叶斯方法

在实际应用中,我们使用一个函数来确定,对于分类变量的给定级别,我们将使用条件目标值、平均目标值,还是两者的混合。这由 lambda 因子决定,对于固定的 k 参数(通常具有单位值,意味着最小单元格频率为两个样本),其输出值取决于我们选择的 f 值。

__results___2_0.png

图 7.3:lambda 值(y 轴)随 f 值和分类值的样本大小(x 轴)的变化图

如图表所示,其中 x 轴表示给定分类级别的案例数量,y 轴表示条件目标值的权重,较小的 f 值倾向于突然从使用平均目标值切换到使用条件值。较高的 f 值倾向于将条件值与平均值混合,除非我们处理的是具有大量样本的分类级别。

因此,对于固定的 kf 的更高值意味着对观察到的经验频率的信任度降低,而对所有单元格的经验概率的依赖性增加。f 的正确值通常是一个需要测试的问题(由交叉验证支持),因为你可以将 f 参数视为一个超参数本身。

在所有这些解释之后,这个类实际上非常容易使用。用你想要目标编码的特征名称和想要尝试的参数实例化它,并在一些训练数据上拟合它。然后,你可以转换任何其他数据,仅对拟合的特征进行目标编码:

te = TargetEncode(categories='ROLE_TITLE')
te.fit(train, train['ACTION'])
te.transform(train[['ROLE_TITLE']]) 

该示例使用的是之前我们使用的相同的 Amazon Employee Access Challenge 数据,并且它仅对 ROLE_TITLE 特征进行目标编码。

除了编写自己的代码,你还可以使用来自 github.com/scikit-learn-contrib/category_encoders 的包及其目标编码器 (contrib.scikit-learn.org/category_encoders/targetencoder.html)。这是一个即用型解决方案,其工作方式与这一节中的代码完全相同。

使用特征重要性评估你的工作

过度应用特征工程可能会产生副作用。如果你创建了太多相关特征或者对于问题来说不重要的特征,模型可能需要太长时间来完成训练,你可能会得到更差的结果。这看起来可能像是一个悖论,但这是由以下事实解释的:每个变量都携带一些噪声(由于测量或记录错误而产生的随机成分),模型可能会错误地选择这些噪声而不是信号:你使用的变量越多,你的模型选择噪声而不是信号的几率就越高。因此,你应该尽量只保留你在训练数据集中使用的相关特征;将特征选择视为你特征工程过程(修剪阶段)的一部分。

确定需要保留的特征是一个难题,因为随着可用特征数量的增加,可能的组合数量也增加。有各种方法可以用来选择特征,但首先重要的是要考虑你的数据准备流程中特征选择必须发生的阶段。

根据我们的经验,我们建议你考虑将特征选择放在数据准备流程的末尾。由于特征与其他特征共享部分方差,你不能通过逐个测试它们来评估它们的有效性;你必须一次性考虑所有特征,才能正确地确定你应该使用哪些特征。

此外,你还应该使用交叉验证来测试所选特征的有效性。因此,在你准备好所有特征并有一个一致的工作流程和有效的模型(它不需要是一个完全优化的模型,但它应该能够正常工作并返回可接受的竞赛结果)之后,你就可以测试应该保留哪些特征以及可以丢弃哪些特征。在这个阶段,有各种操作特征选择的方法:

  • 统计学中使用的经典方法依赖于通过测试每个特征进入或离开预测集集来执行正向添加或反向消除。然而,这种方法可能相当耗时,因为它依赖于变量的一些内部重要性度量或它们对模型性能(相对于特定指标)的影响,你必须在每个步骤中为每个特征重新计算这些度量。

  • 对于回归模型,使用 lasso 选择可以通过稳定性选择程序提供关于所有重要且相关的特征(实际上,该程序可能还会保留高度相关的特征)的提示。在稳定性选择中,你多次测试(使用袋装过程)应该保留哪些特征——只考虑在每个测试中系数不为零的特征——然后你应用一个投票系统来保留那些最频繁被分配非零系数的特征。

你可以在这个仓库中了解更多关于该过程的细节:github.com/scikit-learn-contrib/stability-selection

  • 对于基于树的模型,如随机森林或梯度提升,基于分裂的纯度降低或目标指标的增益是常见的特征排序方法。一个阈值可以去除最不重要的特征。

  • 对于基于树的模型始终如此,但很容易推广到其他模型,基于测试的随机化特征(或与随机特征的简单比较)有助于区分那些有助于模型正确预测的特征和那些只是噪音或冗余的特征。

在这个例子中,Chris DeotteVentilator Pressure Prediction 竞赛中提出了一个如何通过随机化特征来选择重要特征的例子:www.kaggle.com/cdeotte/lstm-feature-importance。这个笔记本测试了特征在基于 LSTM 的神经网络中的作用。首先,构建模型并记录基线性能。然后,逐个对特征进行随机排序,并要求模型再次进行预测。如果预测结果变差,则表明你随机排序了一个不应该被更改的重要特征。相反,如果预测性能保持不变甚至提高,则随机排序的特征对模型没有影响,甚至可能是有害的。

在重要性评估中,也没有免费的午餐。随机排序不需要任何重新训练,这在训练新模型需要时间时是一个巨大的优势。然而,在某些情况下可能会失败。随机排序有时会创建不切实际的不合理输入组合,这些组合在评估时没有意义。在其他情况下,它可能会被高度相关的特征的存在所欺骗(错误地确定一个很重要而另一个不重要)。在这种情况下,通过删除特征(而不是随机排序它),重新训练模型,然后将其性能与基线进行比较是最佳解决方案。

在基于随机特征的另一种方法中,Boruta (github.com/scikit-learn-contrib/boruta_py) 以迭代方式使用随机特征来测试模型的有效性。Boruta 选择过程的替代版本,BorutaShap (github.com/Ekeany/Boruta-Shap),利用 SHAP 值来结合特征选择和解释性原因。这种选择通常比简单的特征去除或随机化更可靠,因为特征需要多次与随机特征进行测试,直到它们可以从统计上证明其重要性。Boruta 或 BorutaShap 可能需要多达 100 次迭代,并且只能使用基于树的机器学习算法进行操作。

如果您正在为线性模型选择特征,Boruta 可能会过度选择。这是因为它会考虑特征的主要效应以及与其他特征一起的交互作用(但在线性模型中,您只关心主要效应和所选子集的交互作用)。您仍然可以通过使用梯度提升并设置最大深度为单棵树来有效地使用 Boruta 进行线性模型选择,这样您就只考虑特征的主要效应,而不考虑它们的交互作用。

您可以通过查看在30 天机器学习竞赛期间展示的这篇教程笔记本来了解如何简单快速地设置 BorutaShap 特征选择:www.kaggle.com/lucamassaron/tutorial-feature-selection-with-boruta-shap

Bojan Tunguz

www.kaggle.com/tunguz

Bojan Tunguz 是 Kaggle 上一位确实理解特征工程重要性的 Kaggler(同时也是 XGBoost 的忠实粉丝!表情符号)。我们非常渴望与他交谈,了解他在 NVIDIA 作为机器学习模型师的经历,以及他作为 Kaggle 四冠大师的令人印象深刻的表现。

您最喜欢的竞赛类型是什么?为什么?在技术和解决方法方面,您在 Kaggle 上的专长是什么?

我喜欢任何非代码竞赛。这多年来已经发生了很大的变化。我曾经非常热衷于图像竞赛,但随着这些竞赛中竞争所需的工程堆栈的复杂性逐年增加,我已经不再那么热衷了。有一段时间,我非常热衷于自然语言处理竞赛,但这些在 Kaggle 上一直很少见。然而,多年来有一个不变的是我对表格数据问题的兴趣。这些曾经是 Kaggle 竞赛的典型问题,但不幸的是已经消失了。我仍然非常关注这个 ML 领域,并已经转向在这个领域做一些基本的研究。与其他 ML/DL 领域相比,在表格数据上改进 ML 的进展非常有限,我相信这里有很大的机会。

您是如何应对 Kaggle 竞赛的?这种方法与您日常工作的方法有何不同?

我一直非常认真地对待 Kaggle 的游戏性。对我来说,这意味着我通常以非常轻松的态度开始新的 Kaggle 比赛——提交简单的解决方案、异想天开的解决方案、其他玩家修改的解决方案、混合方案等。这些帮助我了解问题,了解哪些方法有效,我可以用几个简单的技巧走多远,等等。其中一些也适用于我的日常建模,但有一个重要的方面是缺失的——那就是来自社区和排行榜的支持和反馈。当你独自工作或与一个小团队一起工作时,你永远不知道你所构建的是否是能做得最好的,或者是否有更好的解决方案。

告诉我们一个你参加的特别具有挑战性的比赛,以及你使用了哪些见解来应对这个任务。

在我 Kaggling 生涯中最具挑战性和最重要的比赛是Home Credit Default Risk比赛。这是有史以来第二大 Kaggle 比赛,而且发生在我人生中一个特别具有挑战性的时期。

信用承保是一个非常具有挑战性的数据科学问题,需要大量的智能特征工程和一个可靠的验证方案。我个人的见解是使用简单的线性建模进行特征选择,这有助于我们的整体模型。我们的团队赢得了那个比赛,时至今日,我仍然认为这是我的 Kaggle 生涯中最耀眼的时刻。

Kaggle 是否帮助了你在职业生涯中?如果是的话,是如何帮助的?

Kaggle 是我机器学习职业生涯的最大推动力。在我所持有的四个机器学习职位中,有三个是直接由我的 Kaggle 成功引起的。Kaggle 证书在一个人职业生涯中的重要性是无法被过分强调的。

在你的经验中,不经验的 Kaggler 们通常忽略了什么?你现在知道的事情,你希望在你刚开始的时候就知道?

所有机器学习问题,尤其是 Kaggle 比赛,有两个方面我过去很长时间都没有给予足够的重视:特征工程和稳健的验证策略。我喜欢机器学习库和算法,并且倾向于尽可能早地开始构建机器学习算法。但对你模型性能影响最大的将是非常好的特征。不幸的是,特征工程更多的是一种艺术而不是科学,并且通常与模型和数据集高度相关。大多数更有趣的特征工程技巧和实践很少,如果不是从未,在标准的机器学习课程或资源中教授。其中许多不能教授,并且依赖于一些特殊的问题特定见解。但将特征工程视为默认的做法是一种可以培养的心态。通常需要多年的实践才能精通它。

你会推荐使用哪些工具或库来进行 Kaggling?

XGBoost 就是你所需要的全部!

伪标签

在那些训练示例数量可能影响结果的竞赛中,伪标签化可以通过提供从测试集中提取的更多示例来提高您的分数。其思路是将您对预测有信心的测试集示例添加到训练集中。

该方法首次由团队 Wizardry 在 Santander Customer Transaction Prediction 竞赛中提出(阅读此处:www.kaggle.com/c/santander-customer-transaction-prediction/discussion/89003),伪标签化通过提供更多数据帮助模型优化其系数,但这并不总是有效。首先,在某些竞赛中并不必要。也就是说,添加伪标签不会改变结果;如果伪标签数据中存在一些额外的噪声,甚至可能会使结果变得更糟。

不幸的是,您无法事先确定伪标签化是否会在竞赛中有效(您必须通过实验来测试),尽管绘制学习曲线可能为您提供有关更多数据是否有用的线索(请参阅 Scikit-learn 提供的此示例:scikit-learn.org/stable/auto_examples/model_selection/plot_learning_curve.html)。

其次,决定添加测试集预测的哪些部分或如何调整整个流程以获得最佳结果并不容易。通常,流程如下:

  1. 训练您的模型

  2. 在测试集上进行预测

  3. 建立置信度度量

  4. 选择要添加的测试集元素

  5. 使用合并后的数据构建新的模型

  6. 使用此模型进行预测并提交

Chris Deotte 在 Instant Gratification 竞赛中提供了一个获取伪标签的完整流程的示例:www.kaggle.com/cdeotte/pseudo-labeling-qda-0-969。您不需要知道太多技巧就可以应用它。

在尝试应用伪标签化时,您应该考虑以下注意事项:

  • 您应该有一个非常好的模型,能够为这些预测产生良好的结果,这样它们才能在训练中使用。否则,您只会添加更多的噪声。

  • 由于在测试集中完全完美的预测是不可能的,您需要区分好的预测和不应该使用的预测。如果您正在使用交叉验证(CV)折进行预测,检查您预测的标准差(这适用于回归和分类问题),并仅选择标准差最低的测试示例。如果您正在预测概率,请仅使用高端或低端预测概率(即模型实际上更有信心的情况)。

  • 在第二阶段,当你将训练示例与测试示例连接起来时,不要放入超过 50%的测试示例。理想情况下,70%的原始训练示例和 30%的伪标签示例是最好的。如果你放入太多的伪标签示例,你的新模型可能会从原始数据中学到很少,而从更容易的测试示例中学到更多,从而导致一个性能不如原始模型的蒸馏模型。实际上,当你训练时,你的模型也在学习如何处理标签中的噪声,但伪标签示例没有这种噪声。

不要忘记,你无法完全信任你的伪标签,所以请记住,使用测试预测作为训练示例的同时,你也在部分地破坏你的数据。当这样做带来的好处大于负面影响时,这个技巧才有效。

  • 如果你依赖于验证来提前停止、固定超参数或简单地评估你的模型,不要在验证中使用伪标签。它们可能会非常误导。始终使用原始训练案例,原因如上所述。

  • 如果可能的话,在训练时使用不同类型的模型来估计伪标签,并使用原始标签和伪标签来训练你的最终模型。这将确保你不仅强化了先前模型使用的信息,而且还从伪标签中提取了新的信息。

显然,伪标签更像是一门艺术而非科学。它可能在某些比赛中起到决定性作用,但需要非常熟练地执行才能产生结果。将其视为一种资源,并始终尝试基于伪标签提交一次。

使用自编码器进行去噪

自编码器,最初因其非线性数据压缩(一种非线性 PCA)和图像去噪而闻名,在 Michael Jahrer(www.kaggle.com/mjahrer)成功使用它们赢得Porto Seguro 的驾驶员安全预测比赛(www.kaggle.com/c/porto-seguro-safe-driver-prediction)后,开始被认可为表格竞赛中的一个有趣工具。Porto Seguro是一个基于保险的风险分析竞赛(超过 5,000 名参与者),以其特别嘈杂的特征为特点。

Michael Jahrer 描述了他如何通过使用去噪自编码器DAEs)找到更好的数值数据表示,以便进行后续的神经网络监督学习。一个 DAE 可以根据网络中心隐藏层的激活以及编码信息的中间层的激活,生成一个具有大量特征的新数据集。

在他著名的帖子(www.kaggle.com/c/porto-seguro-safe-driver-prediction/discussion/44629)中,Michael Jahrer 描述了 DAE 不仅可以去除噪声,还可以自动创建新特征,因此特征的表示是以与图像竞赛中发生的方式相似的方式学习的。在帖子中,他提到了 DAE 食谱的秘密成分,不仅仅是层,而是为了增强数据而放入数据中的噪声。他还明确指出,这项技术需要将训练数据和测试数据堆叠在一起,这意味着这项技术不会应用于 Kaggle 竞赛之外的应用。事实上,在这项获奖尝试之后,这项技术从论坛和大多数竞赛中消失,直到最近在 Tabular Playground Series 期间再次出现。

从技术上讲,DAE 由一个编码部分和一个解码部分组成。编码部分以训练数据作为输入,后面跟着几个密集层。理想情况下,你有一个隐藏的中间层,其激活仅编码所有训练信息。如果这个中间层的节点数小于原始输入形状,你有一个压缩,并且从统计学的角度来看,你代表了一些潜在维度,这是输入数据的生成过程背后的;否则,你只是消除冗余,将噪声与信号分离(这并不是一个坏的结果)。

在层的第二部分,解码部分,你再次扩大层,直到它们恢复原始输入的形状。输出与输入进行比较,以计算误差损失,并将其反向传播到网络中。

从这些解决方案中,你可以推断出有两种类型的 DAE:

  • 瓶颈 DAE中,模仿图像处理中使用的方案,你将中间层的激活作为新特征,这个中间层将编码部分与解码部分分开。这些架构具有沙漏形状,首先逐层减少神经元数量,直到中间瓶颈层,然后在第二部分再次扩大。隐藏层的数量总是奇数。

图 7.4:在瓶颈 DAE 中,你只取瓶颈层的权重作为特征

  • 深度堆叠 DAE中,你将隐藏层的所有激活作为特征,不区分编码、解码或中间层。在这些架构中,层的尺寸相同。隐藏层的数量可以是偶数或奇数。

图 7.5:在深度堆叠 DAE 中,你将所有堆叠的隐藏层权重作为特征

正如我们提到的,经常讨论的一个重要方面是向你的 DAE 添加一些随机噪声。为了帮助训练任何类型的 DAE,你需要注入有助于增强训练数据并避免过参数化的神经网络仅仅记住输入(换句话说,过拟合)的噪声。在 Porto Seguro 竞赛中,Michael Jahrer 通过使用一种称为交换噪声的技术添加噪声,他描述如下:

在上表中的“inputSwapNoise”概率下,我以一定的概率从特征本身进行采样。0.15 表示 15% 的特征被来自另一行的值替换。

所描述的是一种基本的增强技术,称为mixup(也用于图像增强:arxiv.org/abs/1710.09412)。在表格数据的 mixup 中,你决定一个混合概率。基于这个概率,你改变样本中的一些原始值,用来自相同训练数据中更或更相似的样本的值来替换它们。

在他的演示中(www.kaggle.com/springmanndaniel/1st-place-turn-your-data-into-daeta),Danzel 描述了三种方法:列向、行向和随机:

  • 列向噪声交换中,你交换一定数量列中的值。要交换的值的列的比例是基于你的混合概率决定的。

  • 行向噪声交换中,你始终交换每行中一定数量的值。本质上,每行包含相同比例的交换值,基于混合概率,但交换的特征会从行到行变化。

  • 随机噪声交换中,你根据混合概率固定要交换的值的数量,并从整个数据集中随机选择它们(这在效果上与行向交换相似)。

与伪标签一样,DAE 也更像是艺术而不是科学,这另一种说法是它完全是试错。它并不总是有效,使它在某个问题上有效的一组细节可能对另一个问题没有帮助。为了在比赛中获得一个好的 DAE,你需要关注一系列需要测试和调整的方面:

  • DAE(深度堆叠架构)的架构(深度堆叠往往效果更好,但你需要确定每层的单元数和层数)

  • 学习率和批量大小

  • 损失(区分数值特征和分类特征的损失也有帮助)

  • 停止点(最低损失并不总是最好的;如果可能,请使用验证和早期停止)

根据问题,你应该预计在设置正确的架构和调整其正常工作时会遇到一些困难。然而,你的努力可能会在最终的私有排行榜上获得优异成绩。事实上,在最近的表格竞赛中,DAE 技术作为许多获胜提交的一部分出现:

用于表格竞赛的神经网络

在讨论了与 DAE 的神经网络之后,我们必须通过讨论神经网络如何在表格竞赛中更一般地帮助你来完成这一章。梯度提升解决方案在表格竞赛(以及现实世界项目)中仍然明显占据主导地位;然而,有时神经网络可以捕捉到梯度提升模型无法获取的信号,并且可以作为优秀的单一模型或是在集成中表现突出的模型。

正如许多现在和过去的围棋大师经常引用的那样,将不同的模型(如神经网络和梯度提升模型)混合在一起,在表格数据问题中总是比单独使用单个模型产生更好的结果。前 Kaggle 排名第一的Owen Zhang在以下采访中详细讨论了如何在竞赛中将神经网络和 GBM 混合在一起以获得更好的结果:www.youtube.com/watch?v=LgLcfZjNF44

为表格竞赛快速构建神经网络不再是令人畏惧的挑战。TensorFlow/Keras 和 PyTorch 等库使得事情变得简单,而且有一些预先制作的网络,如 TabNet,已经打包到库中,使得它们更加容易使用。

要快速开始构建自己的网络,你可以使用各种资源。我们强烈建议参考我们出版的书籍《Machine Learning Using TensorFlow Cookbook》(www.packtpub.com/product/machine-learning-using-tensorflow-cookbook/9781800208865),因为其中有一章专门介绍如何使用 TensorFlow 为表格问题构建 DNN(第七章,使用表格数据预测)。在书中,你还可以找到许多其他关于如何使用 TensorFlow 进行 Kaggle 的建议和食谱。

否则,你可以参考一些在线资源,这些资源在30 Days of ML竞赛期间介绍过这个主题:

在构建这些解决方案时,需要考虑的关键因素包括:

  • 使用 GeLU、SeLU 或 Mish 等激活函数代替 ReLU;它们在许多论文中被引用为更适合建模表格数据,而且我们自己的经验证实它们往往表现更好。

  • 尝试不同的批处理大小。

  • 使用与 mixup(在自动编码器部分讨论过)相结合的增强。

  • 对数值特征进行分位数变换,并强制其结果为均匀分布或高斯分布。

  • 利用嵌入层,但也记住嵌入并不涵盖一切。实际上,它们忽略了嵌入特征与其他所有特征之间的交互(因此你必须通过直接特征工程将这些交互强制进入网络)。

特别是,记住嵌入层是可以重用的。实际上,它们只包含一个矩阵乘法,将输入(高基数变量的稀疏独热编码)减少到低维度的密集编码。通过记录和存储训练好的神经网络的嵌入,你可以转换相同的特征,并将结果嵌入用于许多不同的算法,从梯度提升到线性模型。

参考图 7.6 中的图表,以更清楚地了解涉及 24 个级别的分类变量的过程。在图表中,我们展示了如何将分类特征的值从文本值或整数值转换为神经网络可以处理的值向量:

图片

图 7.6:嵌入层的工作原理

一切都从知道特征有多少个不同的值开始。这构成了字典大小,这是一条重要的信息。在这个例子中,我们考虑了一个具有 24 个不同值的特征。这些信息使我们能够创建一个大小为 24 的独热编码向量,代表每个可能的特征值。然后,这个向量被乘以一个矩阵,其行大小对应于独热编码向量的大小,列大小对应于输出维度的大小。通过这种方式,通过向量-矩阵乘法,分类变量的输入将被转换为一个多维数值。乘法的有效性由神经网络的反向传播算法保证,该算法将更新矩阵中的每个值,以从乘法中获得最具预测性的结果。

如果你不想在 TensorFlow 或 PyTorch 中构建自己的深度神经网络,你可以依赖一些现成的架构解决方案。所有这些解决方案都是现成的,因为它们被打包,或者是因为其他 Kagglers 基于原始论文编写了它们。基于它们在表格竞赛中的成功,以下是一些当你自己参与表格竞赛时可以尝试的主要方案:

总之,你可以通过混合用于分类特征的嵌入层和用于数值特征的密集层来为表格数据构建自己的神经网络。然而,如果这样做没有效果,你总是可以依赖由编写良好的包提供的相当广泛的良好解决方案。始终关注新包的出现:它可能有助于你在 Kaggle 竞赛和现实世界项目中表现更好。此外,根据我们的经验,不要期望神经网络在表格竞赛中是最好的模型;这种情况很少发生。相反,混合来自经典表格数据模型(如梯度提升模型和神经网络)的解决方案,因为它们倾向于从数据中提取不同的信号,你可以将这些信号集成在一起进行集成。

Jean-François Puget

www.kaggle.com/cpmpml

我们与 Jean-François Puget,又名 CPMP,讨论了可重复性的重要性、如何处理数据、他最好的竞赛以及更多内容。作为 Kaggle 竞赛和讨论的大师级人物,以及 NVIDIA RAPIDS 的杰出工程师,他与我们分享了众多宝贵的见解。编辑特别喜欢他关于科学方法的看法。

你最喜欢的竞赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

我喜欢具有科学背景或我能与之相关联的背景的竞赛。我不喜欢匿名数据和合成数据,除非数据是通过非常精确的物理模拟生成的。更普遍地说,我喜欢我对不太了解的领域的 Kaggle 竞赛,因为这是我将学到最多东西的地方。这并不是获得排名积分最有效的方法,但这是我最感兴趣的方法。

你是如何处理 Kaggle 竞赛的?这种方法与你在日常工作中所做的方法有何不同?

我先从查看数据并尽可能理解它开始。我试图在其中找到模式,特别是预测性模式。我经常做的事情是使用两个特征或派生特征在 x 轴和 y 轴上绘制样本,并为样本着色编码使用第三个特征。这三个特征中的一个可以是目标。我使用了大量的可视化,因为我相信人类的视觉是最佳的数据分析工具。

我花时间做的第二件事是如何评估模型或管道的性能。确实,能够尽可能准确地评估模型性能非常重要。这并不令人惊讶;评估通常是 k 折交叉验证的一种变体。但是,折的定义可以根据竞赛类型进行调整(基于时间的折用于预测竞赛,当样本由于某种原因相互关联时,例如,具有相同用户 ID 的操作,可以使用分组 k 折)。

我随后创建了一个从数据到提交的端到端基线,并尝试了一下。如果这是一个代码竞赛,那么测试你是否正确设置了你的流水线是关键。

然后我尝试更复杂的模型(如果使用深度学习模型),或者更多的特征(如果使用 XGBoost 或其他来自 RAPIDS 或 sklearn 的模型)。我将这些提交以查看我的本地评估分数和公共测试分数之间是否存在相关性。如果相关性良好,那么我就越来越少地提交。

几周后,我花时间进行超参数调整。但我只做一次,或者最多两次,在竞赛接近尾声时进行最后一次调整。实际上,超参数调整是过度拟合的最佳方式之一,我非常担心过度拟合。

请告诉我们你参加的一个特别具有挑战性的竞赛,以及你使用了哪些见解来应对这项任务。

我最自豪的竞赛之一是 TalkingData AdTracking Fraud Detection Challenge 竞赛,我们拥有大量的点击历史记录,我们必须预测哪些点击导致了某些应用程序的下载。特征非常少,行数非常多(如五亿行)。当时我只有一台 64GB 的机器,我必须实现一种非常高效的方法来创建新特征并评估它们。在这个竞赛中,我有一些见解。首先,导致应用程序下载的点击是用户在应用程序下载页面上的最后一个点击。因此,“同一用户在相同应用程序上的下一次点击时间”是最重要的特征。一个派生的见解是:有相当多的来自同一用户和应用程序的点击具有相同的戳记。我假设如果有下载,那么它将是最后一个。第三个见解是使用矩阵分解方法来近似特征值共现。当时我在 Keras 中实现了一个 libFM 模型,添加潜在向量作为特征有帮助。唯一其他这样做的是排名第一的团队。有了这个,我在 GM 团队中获得了第六名。我那时还不是 Kaggle GM。

Kaggle 是否帮助你在职业生涯中取得进展?如果是的话,是如何帮助的?

Kaggle 帮助了我两次。在 IBM,Kaggle 是 SOTA 机器学习实践的宝贵知识来源。我利用这些知识来指导和开发 IBM 机器学习工具(IBM Watson Studio 和 IBM Watson Machine Learning)。

例如,我设法在 2016 年让 IBM 支持 Python 包,当时 IBM 是 Java/Scala 的强大力量。如果没有我,IBM 可能会押注 Spark 和 Scala 进行机器学习,并完全错过 Python 浪潮。我还非常早期地推动了 XGBoost,当时 IBM 只想支持 Spark ML 或 TensorFlow。

第二次 Kaggle 帮助我找到了我的当前工作。NVIDIA 正在寻找具有良好社交影响力的 Kaggle 竞赛 GM,以帮助推广 NVIDIA 堆栈,包括 RAPIDS GPU 加速机器学习包。

在您的经验中,不经验证的 Kagglers 通常会忽略什么?现在您知道什么,而您希望在最初开始时就知道?

区分 Kagglers 和其他数据科学家的一点是模型性能的评估。Kagglers 需要掌握这一点,因为如果不掌握,他们可能会选择在公共排行榜上看起来很好但在私有排行榜上表现不佳的提交。一旦 Kaggler 知道如何构建在私有排行榜上表现良好的模型,那么他们也知道如何构建在新数据上表现良好的模型,即不会过拟合的模型。

不经验证的 Kagglers 通常会询问方法/模型 X 是否可以在特定的竞赛中工作。我的回答总是,“试一试看看是否可行。”人们常常忽略机器学习是一个实验科学。为了构建好的模型,必须遵循科学方法:

  • 提出一个假设(例如,添加这个特征,或者添加这个神经网络层,将提高流水线性能)

  • 运行一个实验来测试假设(训练修改后的流水线)

  • 分析实验结果(交叉验证分数是否比之前更好?哪里更好?哪里更差?)

每个实验都应该进行,以便可以确认或拒绝一个假设。为此,实验应该一次只改变一件事。通常,不经验的人会改变很多事,然后无法得出什么有效或无效的结论。

您会推荐使用哪些特定的工具或库来进行数据分析与机器学习?

我主要使用 Matplotlib 进行数据探索。如果数据集较小,我在 Pandas 中进行数据处理;如果数据集较大,我在 cuDF(来自 RAPIDS)中进行。对于机器学习,我使用 RAPIDS 的 cuML,带有 GPU 加速的 XGBoost,以及 PyTorch。如果可能,我会使用预训练模型,例如来自 Hugging Face 的自然语言处理模型,或者来自 timm 包的图像分类模型。

当人们参加竞赛时,应该牢记或做什么最重要?

确保你有足够的时间投入其中。

摘要

在本章中,我们讨论了 Kaggle 上的表格竞赛。由于在表格竞赛中适用的多数知识都与标准数据科学知识和实践重叠,因此我们专注于 Kaggle 更具体的技巧。

从最近推出的表格游乐场系列开始,我们触及了与可重复性、EDA、特征工程、特征选择、目标编码、伪标签以及应用于表格数据集的神经网络相关的话题。

EDA(探索性数据分析)是如果您想了解如何赢得比赛的关键阶段。它也非常不结构化,并且高度依赖于您拥有的数据类型。除了给您提供 EDA 的一般建议外,我们还引起了您对 t-SNE 和 UMAP 等技术的注意,这些技术可以一眼总结您的整个数据集。下一个阶段,特征工程,也强烈依赖于您正在处理的数据类型。因此,我们提供了一系列可能的特征工程想法,您可以尝试将其应用于您的特定案例。至于特征选择,在简要概述后,我们引起了您对基于特征重要性和随机化的技术的注意,这些技术几乎可以应用于任何机器学习算法。

在解释了目标编码后,我们指出这不能以自动化的方式处理,然后我们转向了一些你可能不会在实际项目中应用但可以在 Kaggle 竞赛中非常有效的特殊技术:用于表格竞赛的伪标签和去噪自编码器。最后,在讨论了如何使用神经网络中的嵌入层处理分类特征之后,我们为您快速概述了适用于表格数据的预制神经网络架构。

在下一章中,我们将通过讨论如何最佳地进行超参数优化,来完成对您在表格竞赛中需要采用的所有技术的概述。

加入我们的 Discord 空间

加入本书的 Discord 工作空间,参加每月一次的作者“问我任何问题”活动:

packt.link/KaggleDiscord

二维码

第八章:超参数优化

Kaggle 解决方案的表现并不仅仅取决于你选择的机器学习算法类型。除了数据和使用的特征之外,它还强烈取决于算法的超参数,这些参数必须在训练之前固定,并且在训练过程中无法学习。在表格数据竞赛中选择正确的变量/数据/特征是最有效的;然而,超参数优化在所有类型的竞赛中都是有效的。实际上,给定固定的数据和算法,超参数优化是唯一确保提高算法预测性能并攀登排行榜的方法。它还有助于集成,因为经过调优的模型集成总是比未经调优的模型集成表现更好。

你可能会听说,如果你了解并理解你的选择对算法的影响,手动调整超参数是可能的。许多 Kaggle 大师和专家都宣称,他们在比赛中经常直接调整他们的模型。他们以二分法操作风格有选择性地操作最重要的超参数,探索参数值的越来越小的区间,直到他们找到产生最佳结果的价值。然后,他们转向另一个参数。如果每个参数只有一个最小值,并且参数之间相互独立,这种方法将完美无缺。在这种情况下,搜索主要是由经验和学习算法的知识驱动的。然而,根据我们的经验,在 Kaggle 上遇到的大多数任务并非如此。问题的复杂性和使用的算法需要一种只有搜索算法才能提供的系统方法。因此,我们决定编写这一章节。

在本章中,我们将探讨如何扩展你的交叉验证方法来找到最佳的超参数,这些参数可以推广到你的测试集。这个想法是处理你在比赛中经历的压力和资源稀缺。因此,我们将专注于贝叶斯优化方法,这是一种基于你可用资源的复杂模型和数据问题优化的有效方法。我们不会限制自己只搜索预定义超参数的最佳值;我们还将深入研究神经网络架构的问题。

我们将涵盖以下主题:

  • 基本优化技术

  • 关键参数及其使用方法

  • 贝叶斯优化

让我们开始吧!

基本优化技术

Scikit-learn 包中用于超参数优化的核心算法是网格搜索随机搜索。最近,Scikit-learn 的贡献者还添加了减半算法来提高网格搜索和随机搜索策略的性能。

在本节中,我们将讨论所有这些基本技术。通过掌握它们,你不仅将拥有针对某些特定问题的有效优化工具(例如,SVMs 通常通过网格搜索进行优化),而且你还将熟悉超参数优化的工作原理的基础。

首先,弄清楚必要的成分至关重要:

  • 需要优化超参数的模型

  • 包含每个超参数搜索之间值边界的搜索空间

  • 交叉验证方案

  • 一个评估指标及其评分函数

所有这些元素都在搜索方法中汇集起来,以确定你正在寻找的解决方案。让我们看看它是如何工作的。

网格搜索

网格搜索是一种遍历超参数的方法,在高维空间中不可行。对于每个参数,你选择一组你想要测试的值。然后,你测试这个集合中所有可能的组合。这就是为什么它是穷举的:你尝试了所有可能的情况。这是一个非常简单的算法,它受到维度灾难的影响,但,从积极的一面来看,它是令人尴尬地并行的(参见www.cs.iusb.edu/~danav/teach/b424/b424_23_embpar.html以了解这个计算机科学术语的定义)。这意味着如果你有足够的处理器来运行搜索,你可以非常快速地获得最优调整。

以一个分类问题为例,让我们看看支持向量机分类SVC)。对于分类和回归问题,支持向量机(SVMs)可能是你将最频繁使用网格搜索的机器学习算法。使用 Scikit-learn 的make_classification函数,我们可以快速生成一个分类数据集:

from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
X, y = make_classification(n_samples=300, n_features=50,
                           n_informative=10,
                           n_redundant=25, n_repeated=15,
                           n_clusters_per_class=5,
                           flip_y=0.05, class_sep=0.5,
                           random_state=0) 

对于我们的下一步,我们定义了一个基本的 SVC 算法并设置了搜索空间。由于 SVC 的核函数(SVM 中转换输入数据的内部函数)决定了要设置的不同的超参数,我们提供了一个包含两个不同搜索空间字典的列表,用于根据选择的核类型使用参数。我们还设置了评估指标(在这种情况下,我们使用准确率,因为目标是完美平衡的):

from sklearn import svm
svc = svm.SVC()
svc = svm.SVC(probability=True, random_state=1)
from sklearn import model_selection
search_grid = [
               {'C': [1, 10, 100, 1000], 'kernel': ['linear']},
               {'C': [1, 10, 100, 1000], 'gamma': [0.001, 0.0001],
               'kernel': ['rbf']}
               ]

scorer = 'accuracy' 

在我们的例子中,线性核不需要调整gamma参数,尽管它对于径向基函数核非常重要。因此,我们提供了两个字典:第一个包含线性核的参数,第二个包含径向基函数核的参数。每个字典只包含与该核相关的引用以及对该核相关的参数范围。

需要注意的是,评估指标可能与算法优化的成本函数不同。事实上,如第五章“竞赛任务和指标”中所述,你可能会遇到竞赛的评估指标不同,但你无法修改算法的成本函数。在这种情况下,根据你的评估指标调整超参数仍然有助于获得性能良好的模型。虽然这个最优的超参数集是基于算法的成本函数构建的,但找到的将是在这种约束下返回最佳评估指标的参数集。这或许不是理论上你能为该问题获得的最佳结果,但它可能通常不会离最佳结果太远。

所有成分(模型、搜索空间、评估指标、交叉验证方案)都组合到GridSearchCV实例中,然后模型被拟合到数据上:

search_func = model_selection.GridSearchCV(estimator=svc, 
                                           param_grid=search_grid,
                                           scoring=scorer, 
                                           n_jobs=-1,
                                           cv=5)
search_func.fit(X, y)
print (search_func.best_params_)
print (search_func.best_score_) 

过了一段时间,根据你在其上运行优化的机器,你将根据交叉验证的结果获得最佳组合。

总之,网格搜索是一种非常简单的优化算法,可以利用多核计算机的可用性。它可以很好地与不需要很多调整的机器学习算法(如 SVM 和岭回归和 Lasso 回归)一起工作,但在所有其他情况下,其适用性相当有限。首先,它限于通过离散选择来优化超参数(你需要一个有限的值集来循环)。此外,你不能期望它在需要调整多个超参数的算法上有效工作。这是由于搜索空间的爆炸性复杂性,以及由于大多数计算效率低下是由于搜索在盲目地尝试参数值,其中大多数对问题不起作用。

随机搜索

随机搜索,简单地随机采样搜索空间,在高维空间中是可行的,并且在实践中被广泛使用。然而,随机搜索的缺点是它没有使用先前实验的信息来选择下一个设置(我们应注意的是,这是与网格搜索共享的问题)。此外,为了尽可能快地找到最佳解决方案,你除了希望幸运地找到正确的超参数外,别无他法。

随机搜索工作得非常好,而且很容易理解。尽管它依赖于随机性,但它并不仅仅基于盲目的运气,尽管一开始看起来可能如此。实际上,它就像统计学中的随机抽样:这种技术的主要观点是,如果你进行足够的随机测试,你就有很好的机会找到正确的参数,而无需在测试稍微不同的相似性能组合上浪费能量。

当参数设置过多时,许多 AutoML 系统依赖于随机搜索(参见 Golovin, D. 等人 Google Vizier: A Service for Black-Box Optimization,2017)。作为一个经验法则,当你的超参数优化问题的维度足够高时(例如,超过 16),可以考虑使用随机搜索。

下面,我们使用随机搜索来运行之前的示例:

import scipy.stats as stats
from sklearn.utils.fixes import loguniform
search_dict = {'kernel': ['linear', 'rbf'], 
               'C': loguniform(1, 1000),
               'gamma': loguniform(0.0001, 0.1)
               }
scorer = 'accuracy'
search_func = model_selection.RandomizedSearchCV
              (estimator=svc,param_distributions=search_dict, n_iter=6,
              scoring=scorer, n_jobs=-1, cv=5)
search_func.fit(X, y)
print (search_func.best_params_)
print (search_func.best_score_) 

注意,现在我们不再关心在单独的空间中运行搜索以针对不同的核。与网格搜索不同,在网格搜索中,每个参数(即使是无效的参数)都会系统地测试,这需要计算时间,而在这里,搜索的效率不受测试的超参数集的影响。搜索不依赖于无关参数,而是由机会引导;任何试验都是有用的,即使你只测试了所选核中许多有效参数中的一个。

减半搜索

正如我们提到的,网格搜索和随机搜索都以无信息的方式工作:如果某些测试发现某些超参数不会影响结果或某些值区间无效,则该信息不会传播到后续的搜索中。

因此,Scikit-learn 最近引入了HalvingGridSearchCVHalvingRandomSearchCV估计器,可以使用这些估计器通过应用于网格搜索和随机搜索调整策略的连续减半来搜索参数空间。

在减半法中,在初始测试轮次中,会评估大量超参数组合,但使用的计算资源却很少。这是通过在训练数据的一小部分案例上运行测试来实现的。较小的训练集需要更少的计算来测试,因此,在牺牲更精确的性能估计的代价下,使用的资源(即时间)更少。这个初始轮次允许选择一组候选超参数值,这些值在问题上的表现更好,用于第二轮,当训练集大小增加时。

随后的轮次以类似的方式进行,随着测试值的范围受到限制(现在测试需要更多的时间来执行,但返回更精确的性能估计),将更大的训练集子集分配给搜索,而候选者的数量继续减半。

下面是一个应用于之前问题的示例:

from sklearn.experimental import enable_halving_search_cv
from sklearn.model_selection import HalvingRandomSearchCV
search_func = HalvingRandomSearchCV(estimator=svc,
                                    param_distributions=search_dict,
                                    resource='n_samples',
                                    max_resources=100,
                                    aggressive_elimination=True,
                                    scoring=scorer,
                                    n_jobs=-1,
                                    cv=5,
                                    random_state=0)
search_func.fit(X, y)
print (search_func.best_params_)
print (search_func.best_score_) 

通过这种方式,减半通过选择候选者向后续优化步骤提供信息。在下一节中,我们将讨论通过超参数空间实现更精确和高效搜索的更智能的方法。

Kazuki_Onodera

Kazuki Onodera

www.kaggle.com/onodera

让我们暂停一下,进行另一位 Kaggler 的采访。小野田和树是一位拥有约 7 年比赛经验的竞赛大师和讨论大师。他也是 NVIDIA 的高级深度学习数据科学家,并是 NVIDIA KGMON(Kaggle NVIDIA 大师)团队的一员。

你最喜欢的比赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

Instacart 购物篮分析。这项比赛对 Kaggle 社区来说相当具有挑战性,因为它使用了与客户订单相关的匿名数据,以预测用户下一次订单中将会购买哪些之前购买的产品。我喜欢它的原因是我热爱特征工程,我能想出一堆其他人无法想到的好奇特征,这让我在比赛中获得了第二名。

你是如何应对 Kaggle 比赛的?这种方法与你在日常工作中所做的方法有何不同?

我试图想象一个模型是如何工作的,并深入研究假阴性和假阳性。这和我的日常工作一样。

请告诉我们您参加过的特别具有挑战性的比赛,以及您使用了哪些见解来应对这项任务。

人类蛋白质图谱 - 单细胞分类。这项比赛是一种实例分割比赛,但没有提供掩码。因此,它变成了一个弱监督的多标签分类问题。我创建了一个两阶段管道来去除标签噪声。

Kaggle 是否帮助了你的职业生涯?如果是的话,是如何帮助的?

是的。我现在在 NVIDIA KGMON(Kaggle NVIDIA 大师)团队工作。Kaggle 推出了许多不同的机器学习比赛,这些比赛在数据类型、表格、图像、自然语言和信号等方面各不相同,以及在与行业和领域相关方面:工业、金融、天文学、病理学、体育、零售等等。我相信除了 Kagglers 之外,没有人能够访问并拥有所有这些类型的数据经验。

在你的经验中,不经验的 Kaggler 通常忽略了什么?你现在知道的事情,你希望在你最初开始时就知道?

目标分析。此外,种子平均法常常被忽视:总是简单但强大。

在过去的比赛中,你犯过哪些错误?

目标分析。顶尖团队总是比其他人更好地分析目标,所以如果我在比赛中没有获得更好的名次,我会去阅读关于顶尖解决方案的内容,因为他们总是向我描述我在比赛中遗漏的数据知识。

您是否推荐使用特定的工具或库来进行数据分析或机器学习?

仅 Python 和 Jupyter 笔记本。

当人们参加比赛时,他们应该记住或做些什么最重要?

如果你能从失败中学习,那么你实际上并没有真正失败。

你是否使用其他竞赛平台?它们与 Kaggle 相比如何?

KDD Cup 和 RecSys。两者都满足有趣和具有挑战性的最低要求

关键参数及其使用方法

下一个问题是为你使用的每种模型选择正确的超参数集。特别是,为了在优化中提高效率,你需要知道每个算法中实际有意义的每个超参数的值。

在本节中,我们将检查 Kaggle 竞赛中最常用的模型,特别是表格模型,并讨论你需要调整的超参数以获得最佳结果。我们将区分用于通用表格数据问题的经典机器学习模型和梯度提升模型(在参数空间方面要求更高)。

至于神经网络,当我们介绍标准模型时(例如,TabNet 神经网络模型有一些特定的参数需要设置,以便它能够正常工作),我们可以给你一些关于调整特定参数的想法。然而,Kaggle 竞赛中大多数深度神经网络的优化并不是在标准模型上进行的,而是在自定义模型上进行的。因此,除了基本的学习参数(如学习率和批量大小)之外,神经网络的优化基于你模型神经架构的特定特征。你必须以专门的方式处理这个问题。在章节的末尾,我们将讨论使用 KerasTuner(keras.io/keras_tuner/)进行神经架构搜索NAS)的示例。

线性模型

需要调整的线性模型通常是带有正则化的线性回归或逻辑回归:

  • C:你应该搜索的范围是np.logspace(-4, 4, 10);较小的值指定更强的正则化。

  • alpha:你应该在范围np.logspace(-2, 2, 10)中搜索;较小的值指定更强的正则化,较大的值指定更强的正则化。此外,请注意,当使用 lasso 时,较高的值需要更多的时间来处理。

  • l1_ratio:你应该从列表[.1, .5, .7, .9, .95, .99, 1]中选择;它仅适用于弹性网络。

在 Scikit-learn 中,根据算法,你可以找到超参数C(逻辑回归)或alpha(lasso、ridge、弹性网络)。

支持向量机

SVMs是一系列用于分类和回归的强大且先进的监督学习技术,可以自动拟合线性和非线性模型。Scikit-learn 提供了一个基于LIBSVM的实现,这是一个完整的 SVM 分类和回归实现库,以及LIBLINEAR,一个适用于大型数据集(尤其是稀疏文本数据集)的线性分类可扩展库。在它们的优化中,SVMs 通过使用具有最大可能类间边界的决策边界来尝试在分类问题中分离目标类别。

尽管 SVM 使用默认参数可以正常工作,但它们通常不是最优的,你需要通过交叉验证测试各种值组合来找到最佳组合。按照其重要性列出,你必须设置以下参数:

  • C:惩罚值。减小它会使类之间的间隔更大,从而忽略更多的噪声,但也使模型更具泛化能力。最佳值通常可以在范围 np.logspace(-3, 3, 7) 中找到。

  • kernel:此参数将确定在 SVM 中如何实现非线性。它可以设置为 'linear''poly''rbf''sigmoid' 或自定义核。最常用的值无疑是 rbf

  • degree:与 kernel='poly' 一起使用,表示多项式展开的维度。其他核函数会忽略它。通常,将其值设置为 25 之间效果最佳。

  • gamma:是 'rbf''poly''sigmoid' 的系数。高值往往能更好地拟合数据,但可能导致一些过拟合。直观上,我们可以将 gamma 视为单个示例对模型的影响。低值使每个示例的影响范围更广。由于必须考虑许多点,SVM 曲线将倾向于形成一个受局部点影响较小的形状,结果将得到一个更平滑的决定边界曲线。相反,高值的 gamma 意味着曲线更多地考虑局部点的排列方式,因此你得到一个更不规则和波动的决定曲线。此超参数的建议网格搜索范围是 np.logspace(-3, 3, 7)

  • nu:对于使用 nuSVR 和 nuSVC 的回归和分类,此参数设置接近边界的训练点的容差,这些点没有被正确分类。它有助于忽略刚好在边界附近或被错误分类的点,因此可以使分类决策曲线更平滑。它应该在 [0,1] 范围内,因为它与你的训练集的比例相关。最终,它类似于 C,高比例会扩大间隔。

  • epsilon:此参数指定 SVR 将接受多少误差,通过定义一个大的 epsilon 范围,在该范围内,算法训练期间对示例的错误预测不会关联任何惩罚。建议的搜索范围是 np.logspace(-4, 2, 7)

  • penaltylossdual:对于 LinearSVC,这些参数接受 ('l1', 'squared_hinge', False)('l2', 'hinge', True)('l2', 'squared_hinge', True)('l2', 'squared_hinge', False) 组合。('l2', 'hinge', True) 组合类似于 SVC(kernel='linear') 学习器。

可能看起来支持向量机(SVM)有很多超参数需要设置,但实际上许多设置仅针对特定实现或核函数,因此你只需要选择相关的参数。

随机森林和极端随机树

Leo BreimanAdele Cutler最初设计了随机森林算法核心的想法,并且算法的名字至今仍然是他们的商标(尽管算法是开源的)。随机森林在 Scikit-learn 中实现为RandomForestClassifierRandomForestRegressor

随机森林的工作方式与袋装法类似,也是由 Leo Breiman 提出的,但它仅使用二分分割决策树,这些树被允许生长到极端。此外,它使用重抽样来为每个模型中的案例进行采样。当树生长时,在每次分支的分割中,考虑用于分割的变量集合也是随机抽取的。

这是算法核心的秘密:它组合了由于在不同分割点考虑了不同的样本和变量,彼此之间非常不同的树。由于它们不同,它们也是不相关的。这很有益处,因为当结果被组合时,可以排除很多方差,因为分布两端的极端值往往相互平衡。换句话说,袋装算法保证了预测的一定多样性,使得它们可以发展出单个学习器(如决策树)可能不会遇到的规则。所有这种多样性都是有用的,因为它有助于构建一个平均值为更好的预测器,比集成中的任何单个树都要好。

Extra Trees(也称为极端随机树),在 Scikit-learn 中由ExtraTreesClassifier/ExtraTreesRegressor类表示,是一种更随机的随机森林,它以更大的偏差为代价,在估计中产生更低的方差。然而,当涉及到 CPU 效率时,Extra Trees 与随机森林相比可以提供相当的速度提升,因此在处理大型数据集(包括示例和特征)时,它们可能是理想的。导致更高偏差但速度更好的原因是 Extra Tree 中构建分割的方式。随机森林在为树的分支分割抽取一个随机特征集后,会仔细搜索这些特征以找到分配给每个分支的最佳值。相比之下,在 Extra Trees 中,分割的候选特征集和实际的分割值都是完全随机决定的。因此,不需要太多的计算,尽管随机选择的分割可能不是最有效的(因此有偏差)。

对于这两种算法,应该设置的关键超参数如下:

  • max_features: 这是每个分割中存在的采样特征的数量,这可以确定算法的性能。数字越低,速度越快,但偏差越高。

  • min_samples_leaf: 这允许你确定树的深度。大数值会减少方差并增加偏差。

  • bootstrap: 这是一个布尔值,允许进行重抽样。

  • n_estimators:这是树的数量。记住,树越多越好,尽管存在一个阈值,超过这个阈值,根据数据问题,我们会得到递减的回报。此外,这会带来计算成本,你必须根据你拥有的资源来考虑。

额外树是随机森林的良好替代品,尤其是在你拥有的数据特别嘈杂的情况下。由于它们在随机选择分割时牺牲了一些方差减少以换取更多的偏差,它们在重要但嘈杂的特征上不太容易过拟合,这些特征在其他情况下会主导随机森林的分割。

梯度提升树

梯度提升树或梯度提升决策树GBDT)是提升算法的改进版本(提升算法通过在数据的重新加权版本上拟合一系列弱学习器)。像 AdaBoost 一样,GBDT 基于梯度下降函数。该算法已被证明是基于集成模型家族中最有效的一种,尽管它以估计的方差增加、对数据噪声的敏感性增加(这两个问题可以通过使用子采样来缓解)以及由于非并行操作而产生的显著计算成本为特征。

除了深度学习之外,梯度提升是最发达的机器学习算法。自从 AdaBoost 和由杰罗姆·弗里德曼开发的初始梯度提升实现以来,出现了各种算法的其他实现,最新的包括 XGBoost、LightGBM 和 CatBoost。

LightGBM

高性能的 LightGBM 算法(github.com/Microsoft/LightGBM)能够分布到多台计算机上,并快速处理大量数据。它是由微软的一个团队作为 GitHub 上的开源项目开发的(还有一个学术论文:papers.nips.cc/paper/2017/hash/6449f44a102fde848669bdd9eb6b76fa-Abstract.html)。

LightGBM 基于决策树,就像 XGBoost 一样,但它遵循不同的策略。虽然 XGBoost 使用决策树在变量上进行分割并探索该变量的不同树分割(按层树增长策略),LightGBM 专注于一个分割,并从这里继续分割以实现更好的拟合(按叶树增长策略)。这使得 LightGBM 能够快速达到数据的良好拟合,并生成与 XGBoost 相比的替代解决方案(如果你预计将两种解决方案结合起来以减少估计的方差,这是好的)。从算法的角度来看,如果我们把决策树操作的分割结构看作一个图,XGBoost 追求的是广度优先搜索(BFS),而 LightGBM 追求的是深度优先搜索(DFS)。

调整 LightGBM 可能看起来令人畏惧;它有超过一百个可调整的参数,您可以在本页面上探索这些参数:github.com/Microsoft/LightGBM/blob/master/docs/Parameters.rst(也在这里:lightgbm.readthedocs.io/en/latest/Parameters.html)。

作为一项经验法则,你应该关注以下超参数,它们通常对结果影响最大:

  • n_estimators:一个介于 10 和 10,000 之间的整数,用于设置迭代次数。

  • learning_rate:一个介于 0.01 和 1.0 之间的实数,通常从对数均匀分布中采样。它表示梯度下降过程计算权重时的步长,该过程计算算法到这一点为止所有迭代的加权和。

  • max_depth:一个介于 1 和 16 之间的整数,表示特征上的最大分割数。将其设置为小于 0 的数字允许最大可能的分割数,通常冒着对数据进行过拟合的风险。

  • num_leaves:一个介于 2 和max_depth的 2 次幂之间的整数,表示每棵树最多拥有的最终叶子数。

  • min_data_in_leaf:一个介于 0 和 300 之间的整数,用于确定一个叶子中数据点的最小数量。

  • min_gain_to_split:一个介于 0 和 15 之间的浮点数;它设置了算法进行树分割的最小增益。通过设置此参数,你可以避免不必要的树分割,从而减少过拟合(它对应于 XGBoost 中的gamma参数)。

  • max_bin:一个介于 32 和 512 之间的整数,用于设置特征值将被分桶的最大数量。如果此参数大于默认值 255,则意味着产生过拟合结果的风险更高。

  • subsample:一个介于 0.01 和 1.0 之间的实数,表示用于训练的样本部分。

  • subsample_freq:一个介于 0 和 10 之间的整数,指定算法在迭代过程中进行子样本采样的频率。

注意,如果设置为 0,算法将忽略对subsample参数给出的任何值。此外,它默认设置为 0,因此仅设置subsample参数将不起作用。

  • feature_fraction:一个介于 0.1 和 1.0 之间的实数,允许您指定要子样本的特征部分。对特征进行子样本是允许更多随机化在训练中发挥作用,以对抗特征中存在的噪声和多重共线性。

  • subsample_for_bin:一个介于 30 和示例数量之间的整数。这设置了用于构建直方图桶的示例数量。

  • reg_lambda:一个介于 0 和 100.0 之间的实数,用于设置 L2 正则化。由于它对尺度的敏感性大于对参数确切数量的敏感性,它通常从对数均匀分布中采样。

  • reg_alpha:一个介于 0 和 100.0 之间的实数,通常从对数均匀分布中采样,用于设置 L1 正则化。

  • scale_pos_weight:一个介于 1e-6 和 500 之间的实数,最好从对数均匀分布中采样。该参数对正例进行加权(从而有效地上采样或下采样)以对抗负例,负例的值保持为 1。

虽然在使用 LightGBM 时需要调整的超参数数量可能看起来令人畏惧,但事实上只有少数几个非常重要。给定固定的迭代次数和学习率,只有少数几个是最有影响力的(feature_fractionnum_leavessubsamplereg_lambdareg_alphamin_data_in_leaf),正如 Kaggle 大师 Kohei Ozaki 在其博客文章中解释的那样:medium.com/optuna/lightgbm-tuner-new-optuna-integration-for-hyperparameter-optimization-8b7095e99258。Kohei Ozaki 利用这一事实来为 Optuna 创建快速调整程序(你将在本章末尾找到更多关于 Optuna 优化器的信息)。

XGBoost

XGBoost (github.com/dmlc/XGBoost) 代表着极梯度提升。这是一个开源项目,虽然它不是 Scikit-learn 的一部分,但最近通过 Scikit-learn 包装接口进行了扩展,这使得将其集成到 Scikit-learn 风格的数据管道中变得更加容易。

XGBoost 算法在 2015 年的数据科学竞赛中获得了动力和人气,例如 Kaggle 和 KDD Cup 2015 的竞赛。正如算法的创造者(陈天奇、何通和卡洛斯·古埃斯特林)在关于该算法的论文中所报告的那样,在 2015 年 Kaggle 举办的 29 个挑战中,有 17 个获胜方案使用了 XGBoost 作为独立解决方案或作为多个不同模型的集成的一部分。从那时起,尽管它在与 LightGBM 和 CatBoost 等其他 GBM 实现的创新竞争中有些吃力,但该算法在数据科学家社区中始终保持着强大的吸引力。

除了在准确性和计算效率方面都表现出良好的性能外,XGBoost 还是一个可扩展的解决方案,它最好地利用了多核处理器以及分布式机器。

XGBoost 通过对初始树提升 GBM 算法的重要调整,代表着新一代的 GBM 算法:

  • 稀疏感知;它可以利用稀疏矩阵,节省内存(不需要密集矩阵)和计算时间(零值以特殊方式处理)。

  • 近似树学习(加权分位数草图),与经典的可能分支切割的完整探索相比,在更短的时间内产生类似的结果。

  • 在单台机器上进行并行计算(在搜索最佳分割时使用多线程)以及类似地,在多台机器上进行分布式计算。

  • 在单机上执行离核计算,利用一种称为列块的数据存储解决方案。这种方式通过列在磁盘上排列数据,从而通过以优化算法(该算法在列向量上工作)期望的方式从磁盘中拉取数据来节省时间。

XGBoost 也可以有效地处理缺失数据。其他基于标准决策树的树集成方法需要首先使用一个离群值(如负数)来填充缺失数据,以便开发出适当的树分支来处理缺失值。

关于 XGBoost 的参数 (xgboost.readthedocs.io/en/latest/parameter.html),我们决定突出一些你在竞赛和项目中都会遇到的关键参数:

  • n_estimators:通常是一个介于 10 到 5,000 之间的整数。

  • learning_rate:一个介于 0.01 到 1.0 之间的实数,最好从对数均匀分布中采样。

  • min_child_weight:通常是一个介于 1 到 10 之间的整数。

  • max_depth:通常是一个介于 1 到 50 之间的整数。

  • max_delta_step:通常是一个介于 0 到 20 之间的整数,表示我们允许每个叶输出允许的最大 delta 步长。

  • subsample:一个介于 0.1 到 1.0 之间的实数,表示要子采样的样本比例。

  • colsample_bytree:一个介于 0.1 到 1.0 之间的实数,表示按树对列的子采样比例。

  • colsample_bylevel:一个介于 0.1 到 1.0 之间的实数,表示树中按层级的子采样比例。

  • reg_lambda:一个介于 1e-9 和 100.0 之间的实数,最好从对数均匀分布中采样。此参数控制 L2 正则化。

  • reg_alpha:一个介于 1e-9 和 100.0 之间的实数,最好从对数均匀分布中采样。此参数控制 L1 正则化。

  • gamma:指定树分区所需的最小损失减少量,此参数需要一个介于 1e-9 和 0.5 之间的实数,最好从对数均匀分布中采样。

  • scale_pos_weight:一个介于 1e-6 和 500.0 之间的实数,最好从对数均匀分布中采样,它代表正类的一个权重。

与 LightGBM 类似,XGBoost 也有许多类似的超参数需要调整,因此之前为 LightGBM 做出的所有考虑也适用于 XGBoost。

CatBoost

2017 年 7 月,俄罗斯搜索引擎 Yandex 公开了另一个有趣的 GBM 算法 CatBoost (catboost.ai/),其名称来源于将“Category”和“Boosting”两个词组合在一起。实际上,它的优势在于其处理分类变量的能力,这些变量构成了大多数关系数据库中的大部分信息,它通过采用一热编码和目标编码的混合策略来实现。目标编码是一种通过为特定问题分配适当的数值来表示分类级别的方法;更多关于这一点可以在第七章表格竞赛建模中找到。

CatBoost 用于编码分类变量的想法并不新颖,但它是一种之前已经使用过的特征工程方法,主要在数据科学竞赛中使用。目标编码,也称为似然编码、影响编码或均值编码,简单来说,是一种根据与目标变量的关联将标签转换为数字的方法。如果你有一个回归,你可以根据该级别的典型目标均值来转换标签;如果是分类,那么就是给定该标签的目标分类概率(每个类别值的条件目标概率)。这可能看起来是一个简单而聪明的特征工程技巧,但它有副作用,主要是过拟合,因为你在预测器中获取了来自目标的信息。

CatBoost 有相当多的参数(见catboost.ai/en/docs/references/training-parameters/)。我们只讨论了其中最重要的八个:

  • iterations: 通常是一个介于 10 和 1,000 之间的整数,但根据问题可以增加。

  • depth: 一个介于 1 和 8 之间的整数;通常较高的值需要更长的拟合时间,并且不会产生更好的结果。

  • learning_rate: 一个介于 0.01 和 1.0 之间的实数值,最好从对数均匀分布中采样。

  • random_strength: 从 1e-9 到 10.0 的范围内以对数线性方式采样的实数,它指定了评分分割的随机水平。

  • bagging_temperature: 一个介于 0.0 和 1.0 之间的实数值,用于设置贝叶斯自助抽样。

  • border_count: 一个介于 1 和 255 之间的整数,表示数值特征的分割。

  • l2_leaf_reg: 一个介于 2 和 30 之间的整数;L2 正则化的值。

  • scale_pos_weight: 一个介于 0.01 和 10.0 之间的实数,表示正类权重。

即使 CatBoost 可能看起来只是另一种 GBM 实现,但它有一些差异(也通过不同的参数使用突出显示),这些差异在比赛中可能提供极大的帮助,无论是作为单一模型解决方案还是作为集成模型的一部分。

HistGradientBoosting

最近,Scikit-learn 引入了一个新的梯度提升版本,灵感来自 LightGBM 的分箱数据和直方图(见 EuroPython 上的这个演示:www.youtube.com/watch?v=urVUlKbQfQ4)。无论是作为分类器(HistGradientBoostingClassifier)还是回归器(HistGradientBoostingRegressor),它都可以用于用不同模型丰富集成,并且它提供了一个更短、更关键的超参数范围需要调整:

  • learning_rate: 一个介于 0.01 和 1.0 之间的实数,通常从对数均匀分布中采样。

  • max_iter: 一个介于 10 到 10,000 之间的整数。

  • max_leaf_nodes:一个介于 2 到 500 之间的整数。它与max_depth相互作用;建议只设置这两个参数中的一个,并将另一个设置为None

  • max_depth:一个介于 2 到 12 之间的整数。

  • min_samples_leaf:一个介于 2 到 300 之间的整数。

  • l2_regularization:一个介于 0.0 到 100.0 之间的浮点数。

  • max_bins:一个介于 32 到 512 之间的整数。

即使 Scikit-learn 的HistGradientBoosting与 LightGBM 或 XGBoost 没有太大区别,但它确实提供了一种在比赛中实现 GBM 的不同方法,由HistGradientBoosting构建的模型在集成多个预测时(如混合和堆叠)可能会提供一些贡献。

到达本节末尾,你应该对最常见的机器学习算法(尚未讨论深度学习解决方案)及其最重要的超参数有了更熟悉的了解,这将有助于你在 Kaggle 竞赛中构建出色的解决方案。了解基本的优化策略、可用的算法及其关键超参数只是一个起点。在下一节中,我们将开始深入讨论如何使用贝叶斯优化更优地调整它们。

Alberto Danese

www.kaggle.com/albedan

我们本章的第二位访谈对象是 Alberto Danese,他是意大利信用卡和数字支付公司 Nexi 的数据科学负责人。这位在 2015 年加入平台的竞赛大师,作为单独的竞争者获得了大部分金牌。

你最喜欢的比赛类型是什么?为什么?在 Kaggle 上,你在技术和解决方法方面有什么专长?

我一直从事金融服务行业,主要处理结构化数据,我更倾向于这类比赛。我喜欢能够实际掌握数据的本质,并做一些智能的特征工程,以从数据中提取每一丝信息。

从技术角度讲,我在经典机器学习库方面有丰富的经验,尤其是梯度提升决策树:最常用的库(XGBoost、LightGBM、CatBoost)总是我的首选。

你是如何处理 Kaggle 竞赛的?这种方法与你在日常工作中所做的方法有何不同?

我总是花很多时间探索数据,试图弄清楚赞助商实际上想用机器学习解决什么问题。与新手通常对 Kaggle 的看法不同,我不会花太多时间在特定 ML 算法的所有“调整”上——显然这种方法是有效的!

在我的日常工作中,理解数据也非常重要,但在 Kaggle 竞赛中却完全缺失了一些额外的阶段。我必须做到:

  • 定义一个用 ML 解决的问题(与业务部门的同事一起)

  • 找到数据,有时也来自外部数据提供商

  • 当机器学习部分完成时,理解如何将其投入生产并管理其演变

告诉我们你参加的一个特别具有挑战性的比赛,以及你使用了哪些见解来应对任务。

我很喜欢参加TalkingData AdTracking Fraud Detection Challenge,通过这个挑战我成为了大师。除了它是一个非常有趣的话题(打击点击农场中的欺诈)之外,它还真正推动了我进行高效的特征工程,因为数据量巨大(超过 1 亿个标记行),减少计算时间对于测试不同的方法至关重要。它还迫使我以最佳方式理解如何利用滞后/领先特征(以及其他窗口函数),以便在本质上是一个经典机器学习问题中创建一种时间序列。

Kaggle 是否帮助你在职业生涯中取得进步?如果是的话,是如何帮助的?

当然!能够实现伟大的目标和可验证的结果无疑是使简历脱颖而出的因素之一。当我 2016 年被 Cerved(一家市场情报服务公司)雇佣时,招聘经理完全清楚 Kaggle 是什么——在面试中谈论一些真实世界的项目是非常有价值的。当然,Kaggle 在我的职业生涯发展中扮演了重要的角色。

在你的经验中,不经验的 Kagglers 通常忽略了什么?你现在知道的事情,你希望在你最初开始时就知道?

我认为每个人都是从编码开始的,也许是从一个公共内核开始,只是更改几行或参数。这在开始时是完全可以接受的!但你确实需要花相当多的时间不编码,而是研究数据和理解问题。

你在过去比赛中犯过哪些错误?

不确定这算不算一个错误,但我经常更喜欢单独竞争:一方面,这很好,因为它迫使你处理比赛的每一个方面,你可以按照自己的意愿管理时间。但我也非常喜欢与队友在几个比赛中合作:我可能应该更经常地考虑团队合作,因为你可以从合作中学到很多东西。

你会推荐使用哪些特定的工具或库来进行数据分析或机器学习?

除了那些通常的,我一直非常喜欢data.table(从 R 版本开始):我认为它没有得到应有的认可!当你想在本地机器上处理大量数据时,它真的是一个非常棒的包。

当人们参加比赛时,他们应该记住或做最重要的事情是什么?

首先理解问题和数据:不要立即开始编码!

贝叶斯优化

放弃网格搜索(仅在实验空间有限时可行),实践者通常会选择应用随机搜索优化或尝试贝叶斯优化BO)技术,这需要更复杂的设置。

TPEs 最初在 Snoek, J.,Larochelle, H.和 Adams, R. P.的论文《Practical Bayesian optimization of machine learning algorithms》中提出,该论文的网址为export.arxiv.org/pdf/1206.2944。贝叶斯优化的关键思想是,我们优化一个代理函数(也称为替代函数),而不是真正的目标函数(网格搜索和随机搜索都这样做)。我们这样做是因为没有梯度,如果测试真正的目标函数成本高昂(如果不是,那么我们简单地进行随机搜索),以及如果搜索空间是嘈杂且足够复杂的情况下。

贝叶斯搜索在探索利用之间取得平衡。一开始,它随机探索,从而在过程中训练替代函数。基于这个替代函数,搜索利用其对预测器如何工作的初始近似知识,以便采样更有用的示例并最小化成本函数。正如名称中的贝叶斯部分所暗示的,我们在优化过程中使用先验来做出更明智的采样决策。这样,我们通过限制需要进行的评估次数来更快地达到最小化。

贝叶斯优化使用一个获取函数来告诉我们一个观察结果的前景如何。实际上,为了管理探索和利用之间的权衡,算法定义了一个获取函数,它提供了一个单一指标,说明尝试任何给定点将有多有用。

通常,贝叶斯优化由高斯过程提供动力。当搜索空间具有平滑且可预测的响应时,高斯过程表现更好。当搜索空间更复杂时,一个替代方案是使用树算法(例如,随机森林),或者一个完全不同的方法,称为树帕尔森估计器树结构帕尔森估计器TPEs)。

与直接构建一个估计参数集成功度的模型不同,从而像先知一样行动,TPEs(Tree Parzen Estimators)根据实验提供的连续近似,估计一个多变量分布的参数,这些参数定义了参数的最佳性能值。通过这种方式,TPEs 通过从概率分布中采样来推导出最佳参数集,而不是直接从机器学习模型(如高斯过程)中直接推导。

我们将讨论这些方法中的每一个,首先通过检查基于高斯过程的 Scikit-optimize 和 KerasTuner,Scikit-optimize 还可以使用随机森林,而 KerasTuner 可以使用多臂老丨虎丨机,然后是主要基于 TPE 的 Optuna(尽管它也提供不同的策略:optuna.readthedocs.io/en/stable/reference/samplers.html)。

尽管贝叶斯优化被认为是超参数调整的当前最佳实践,但始终记住,对于更复杂的参数空间,使用贝叶斯优化在时间和计算上并不比随机搜索简单找到的解决方案有优势。例如,在 Google Cloud Machine Learning Engine 服务中,贝叶斯优化的使用仅限于涉及最多十六个参数的问题。对于更多参数的情况,它将回退到随机抽样。

使用 Scikit-optimize

Scikit-optimize (skopt) 是使用与 Scikit-learn 相同的 API 开发的,同时广泛使用了 NumPy 和 SciPy 函数。此外,它是由 Scikit-learn 项目的一些贡献者创建的,例如 Gilles Louppe

该包基于高斯过程算法,维护得很好,尽管有时它必须因为 Scikit-learn、NumPy 或 SciPy 方面的改进而赶上来。例如,在撰写本文时,为了在 Kaggle 笔记本上正确运行,你必须回滚到这些包的旧版本,正如 GitHub 上的一个问题所解释的 (github.com/scikit-optimize/scikit-optimize/issues/981)。

该包具有直观的 API,相当容易对其进行修改并使用其函数在自定义优化策略中。Scikit-optimize 还以其有用的图形表示而闻名。实际上,通过可视化优化过程的结果(使用 Scikit-optimize 的 plot_objective 函数),你可以判断是否可以重新定义问题的搜索空间,并制定一个关于问题优化工作原理的解释。

在我们的示例中,我们将参考以下 Kaggle 笔记本中的工作:

我们在这里的目的就是向您展示如何快速处理一场比赛,例如 30 Days of ML 的优化问题,这是一场最近举办的比赛,许多 Kagglers 参与其中,学习新技能并在为期 30 天的比赛中将它们应用。这场比赛的目标是预测保险索赔的价值,因此它是一个回归问题。您可以通过访问 www.kaggle.com/thirty-days-of-ml 了解更多关于这个项目的信息并下载我们将要展示的示例所需的数据(材料总是对公众开放)。

如果您无法访问数据,因为您之前没有参加过比赛,您可以使用这个 Kaggle 数据集:www.kaggle.com/lucamassaron/30-days-of-ml

以下代码将展示如何加载此问题的数据,然后设置一个贝叶斯优化过程,该过程将提高 LightGBM 模型的性能。

我们首先加载所需的包:

# Importing core libraries
import numpy as np
import pandas as pd
from time import time
import pprint
import joblib
from functools import partial
# Suppressing warnings because of skopt verbosity
import warnings
warnings.filterwarnings("ignore")
# Classifiers
import lightgbm as lgb
# Model selection
from sklearn.model_selection import KFold
# Metrics
from sklearn.metrics import mean_squared_error
from sklearn.metrics import make_scorer
# Skopt functions
from skopt import BayesSearchCV
from skopt.callbacks import DeadlineStopper, DeltaYStopper
from skopt.space import Real, Categorical, Integer 

作为下一步,我们加载数据。除了将一些具有字母作为级别的分类特征转换为有序数字之外,数据不需要太多处理:

# Loading data 
X = pd.read_csv("../input/30-days-of-ml/train.csv")
X_test = pd.read_csv("../input/30-days-of-ml/test.csv")
# Preparing data as a tabular matrix
y = X.target
X = X.set_index('id').drop('target', axis='columns')
X_test = X_test.set_index('id')
# Dealing with categorical data
categoricals = [item for item in X.columns if 'cat' in item]
cat_values = np.unique(X[categoricals].values)
cat_dict = dict(zip(cat_values, range(len(cat_values))))
X[categoricals] = X[categoricals].replace(cat_dict).astype('category')
X_test[categoricals] = X_test[categoricals].replace(cat_dict).astype('category') 

在数据可用后,我们定义了一个报告函数,该函数可以被 Scikit-optimize 用于各种优化任务。该函数接受数据和优化器作为输入。它还可以处理 回调函数,这些函数执行诸如报告、基于达到一定搜索时间阈值或性能没有提高(例如,在特定次数的迭代中看不到改进)的早期停止,或者在每个优化迭代后保存处理状态等操作:

# Reporting util for different optimizers
def report_perf(optimizer, X, y, title="model", callbacks=None):
    """
    A wrapper for measuring time and performance of optimizers
    optimizer = a sklearn or a skopt optimizer
    X = the training set 
    y = our target
    title = a string label for the experiment
    """
    start = time()

    if callbacks is not None:
        optimizer.fit(X, y, callback=callbacks)
    else:
        optimizer.fit(X, y)

    d=pd.DataFrame(optimizer.cv_results_)
    best_score = optimizer.best_score_
    best_score_std = d.iloc[optimizer.best_index_].std_test_score
    best_params = optimizer.best_params_

    print((title + " took %.2f seconds, candidates checked: %d, best CV            score: %.3f" + u" \u00B1"+" %.3f") % 
                             (time() - start,
                             len(optimizer.cv_results_['params']),
                             best_score, 
                             best_score_std))
    print('Best parameters:')
    pprint.pprint(best_params)
    print()
    return best_params 

现在我们必须准备评分函数(评估基于此),验证策略(基于交叉验证),模型和搜索空间。对于评分函数,它应该是一个均方根误差指标,我们参考了 Scikit-learn 中的实践,在那里你总是最小化一个函数(如果你必须最大化,你最小化其负值)。

make_scorer 包装器可以轻松地复制这样的实践:

# Setting the scoring function
scoring = make_scorer(partial(mean_squared_error, squared=False),
                      greater_is_better=False)
# Setting the validation strategy
kf = KFold(n_splits=5, shuffle=True, random_state=0)
# Setting the basic regressor
reg = lgb.LGBMRegressor(boosting_type='gbdt',
                        metric='rmse',
                        objective='regression',
                        n_jobs=1, 
                        verbose=-1,
                        random_state=0) 

设置搜索空间需要使用 Scikit-optimize 中的不同函数,例如 RealIntegerChoice,每个函数从您定义的不同类型的分布中进行采样,作为参数(通常是均匀分布,但在您对参数的规模效应比其确切值更感兴趣时,也使用对数均匀分布):

# Setting the search space
search_spaces = {

     # Boosting learning rate
    'learning_rate': Real(0.01, 1.0, 'log-uniform'),

     # Number of boosted trees to fit
    'n_estimators': Integer(30, 5000),

     # Maximum tree leaves for base learners
    'num_leaves': Integer(2, 512),

     # Maximum tree depth for base learners
    'max_depth': Integer(-1, 256),
     # Minimal number of data in one leaf
    'min_child_samples': Integer(1, 256),
     # Max number of bins buckets
    'max_bin': Integer(100, 1000),
     # Subsample ratio of the training instance 
    'subsample': Real(0.01, 1.0, 'uniform'),
     # Frequency of subsample 
    'subsample_freq': Integer(0, 10),

     # Subsample ratio of columns
    'colsample_bytree': Real(0.01, 1.0, 'uniform'), 

     # Minimum sum of instance weight
    'min_child_weight': Real(0.01, 10.0, 'uniform'),

     # L2 regularization
    'reg_lambda': Real(1e-9, 100.0, 'log-uniform'),

     # L1 regularization
    'reg_alpha': Real(1e-9, 100.0, 'log-uniform'),
   } 

一旦您已经定义:

  • 您的交叉验证策略

  • 您的评估指标

  • 您的基础模型

  • 您的超参数搜索空间

剩下的只是将它们输入到您的优化函数BayesSearchCV中。根据提供的 CV 方案,此函数将根据搜索空间内的值寻找评分函数的最小值。您可以设置最大迭代次数、代理函数的类型(高斯过程GP在大多数情况下都适用),以及随机种子以实现可重复性:

# Wrapping everything up into the Bayesian optimizer
opt = BayesSearchCV(estimator=reg,
                    search_spaces=search_spaces,
                    scoring=scoring,
                    cv=kf,
                    n_iter=60,           # max number of trials
                    n_jobs=-1,           # number of jobs
                    iid=False,         
                    # if not iid it optimizes on the cv score
                    return_train_score=False,
                    refit=False,  
                    # Gaussian Processes (GP) 
                    optimizer_kwargs={'base_estimator': 'GP'},
                    # random state for replicability
                    random_state=0) 

在这一点上,您可以使用我们之前定义的报告函数开始搜索。一段时间后,该函数将返回该问题的最佳参数。

# Running the optimizer
overdone_control = DeltaYStopper(delta=0.0001)
# We stop if the gain of the optimization becomes too small
time_limit_control = DeadlineStopper(total_time=60 * 60 * 6)
# We impose a time limit (6 hours)
best_params = report_perf(opt, X, y,'LightGBM_regression', 
                          callbacks=[overdone_control, time_limit_control]) 

在示例中,我们通过指定一个最大允许时间(6 小时)来限制操作,并在停止和报告最佳结果之前。由于贝叶斯优化方法结合了不同超参数组合的探索和利用,因此任何时间停止都将始终返回迄今为止找到的最佳解决方案(但不一定是可能的最佳解决方案)。这是因为获取函数将始终根据代理函数返回的估计性能及其不确定性区间,优先探索搜索空间中最有希望的部分。

定制贝叶斯优化搜索

Scikit-optimize 提供的BayesSearchCV函数确实很方便,因为它自己封装并安排了超参数搜索的所有元素,但它也有局限性。例如,您可能发现在比赛中这样做很有用:

  • 对每次搜索迭代有更多控制,例如混合随机搜索和贝叶斯搜索

  • 能够在算法上应用早期停止

  • 更多地定制您的验证策略

  • 早期停止无效的实验(例如,当可用时立即评估单个交叉验证折的性能,而不是等待所有折的平均值)

  • 创建表现相似的超参数集簇(例如,为了创建多个模型,这些模型仅在使用的超参数上有所不同,用于混合集成)

如果您能够修改BayesSearchCV内部过程,那么这些任务中的每一个都不会太复杂。幸运的是,Scikit-optimize 允许您做到这一点。实际上,在BayesSearchCV以及该包的其他封装器后面,都有特定的最小化函数,您可以将它们用作您自己的搜索函数的独立部分:

  • gp_minimize:使用高斯过程进行贝叶斯优化

  • forest_minimize:使用随机森林或极端随机树进行贝叶斯优化

  • gbrt_minimize:使用梯度提升的贝叶斯优化

  • dummy_minimize:仅仅是随机搜索

在以下示例中,我们将修改之前的搜索,使用我们自己的自定义搜索函数。新的自定义函数将在训练期间接受早期停止,并在其中一个折的验证结果不是最佳表现时剪枝实验。

你可以在 Kaggle 笔记本中找到下一个示例:www.kaggle.com/lucamassaron/hacking-bayesian-optimization

如前例所示,我们首先导入必要的包。

# Importing core libraries
import numpy as np
import pandas as pd
from time import time
import pprint
import joblib
from functools import partial
# Suppressing warnings because of skopt verbosity
import warnings
warnings.filterwarnings("ignore")
# Classifier/Regressor
from xgboost import XGBRegressor
# Model selection
from sklearn.model_selection import KFold, StratifiedKFold
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import train_test_split
# Metrics
from sklearn.metrics import mean_squared_error
from sklearn.metrics import make_scorer
# Skopt functions
from skopt import BayesSearchCV
from skopt.callbacks import DeadlineStopper, DeltaYStopper
from skopt.space import Real, Categorical, Integer
from skopt import gp_minimize, forest_minimize
from skopt import gbrt_minimize, dummy_minimize
# Decorator to convert a list of parameters to named arguments
from skopt.utils import use_named_args 
# Data processing
from sklearn.preprocessing import OrdinalEncoder 

与之前一样,我们从30 Days of ML竞赛上传数据:

# Loading data 
X_train = pd.read_csv("../input/30-days-of-ml/train.csv")
X_test = pd.read_csv("../input/30-days-of-ml/test.csv")
# Preparing data as a tabular matrix
y_train = X_train.target
X_train = X_train.set_index('id').drop('target', axis='columns')
X_test = X_test.set_index('id')
# Pointing out categorical features
categoricals = [item for item in X_train.columns if 'cat' in item]
# Dealing with categorical data using OrdinalEncoder
ordinal_encoder = OrdinalEncoder()
X_train[categoricals] = ordinal_encoder.fit_transform(X_train[categoricals])
X_test[categoricals] = ordinal_encoder.transform(X_test[categoricals]) 

现在我们设置了进行超参数搜索所需的所有必要元素,即评分函数、验证策略、搜索空间以及要优化的机器学习模型。评分函数和验证策略将后来成为构成目标函数的核心元素,目标函数是贝叶斯优化努力最小化的函数。

# Setting the scoring function
scoring = partial(mean_squared_error, squared=False)
# Setting the cv strategy
kf = KFold(n_splits=5, shuffle=True, random_state=0)
# Setting the search space
space = [Real(0.01, 1.0, 'uniform', name='learning_rate'),
         Integer(1, 8, name='max_depth'),
         Real(0.1, 1.0, 'uniform', name='subsample'),
         # Subsample ratio of columns by tree
         Real(0.1, 1.0, 'uniform', name='colsample_bytree'),  
         # L2 regularization
         Real(0, 100., 'uniform', name='reg_lambda'),
         # L1 regularization
         Real(0, 100., 'uniform', name='reg_alpha'),
         # minimum sum of instance weight (hessian)  
         Real(1, 30, 'uniform', name='min_child_weight')
         ]
model = XGBRegressor(n_estimators=10_000, 
                     booster='gbtree', random_state=0) 

注意这次我们没有在搜索空间中包含估计器的数量(即n_estimators参数)。相反,我们在实例化模型时设置它,并输入一个高值,因为我们期望根据验证集提前停止模型。

作为下一步,你现在需要创建目标函数。目标函数应该只接受要优化的参数作为输入,并返回相应的分数。然而,目标函数还需要接受你刚刚准备好的搜索所需的元素。自然地,你可以从函数内部引用它们。然而,将它们带入函数本身,在其内部内存空间中,是一种良好的实践。这有其优点;例如,你会使元素不可变,并且它们将与目标函数(通过序列化或如果你在多处理器级别上分配搜索任务)一起携带。你可以通过创建一个make函数来实现第二个结果,该函数接受元素,并通过make函数返回修改后的目标函数。通过这种简单的结构,你的目标函数将包含所有元素,如数据和模型,而你只需要传递要测试的参数。

让我们开始编写函数。我们将沿途讨论一些相关方面:

# The objective function to be minimized
def make_objective(model, X, y, space, cv, scoring, validation=0.2):
    # This decorator converts your objective function 
    # with named arguments into one that accepts a list as argument,
    # while doing the conversion automatically.
    @use_named_args(space) 
    def objective(**params):
        model.set_params(**params)
        print("\nTesting: ", params)
        validation_scores = list()
        for k, (train_index, test_index) in enumerate(kf.split(X, y)):
            val_index = list()
            train_examples = int(train_examples * (1 - validation))
            train_index, val_index = (train_index[:train_examples], 
                                      train_index[train_examples:])

            start_time = time()
            model.fit(X.iloc[train_index,:], y[train_index],
                      early_stopping_rounds=50,
                      eval_set=[(X.iloc[val_index,:], y[val_index])], 
                      verbose=0
                    )
            end_time = time()

            rounds = model.best_iteration

            test_preds = model.predict(X.iloc[test_index,:])
            test_score = scoring(y[test_index], test_preds)
            print(f"CV Fold {k+1} rmse:{test_score:0.5f}-{rounds} 
                  rounds - it took {end_time-start_time:0.0f} secs")
            validation_scores.append(test_score) 

在函数的第一个部分,你只需创建一个目标函数,进行交叉验证并使用提前停止来拟合数据。我们使用了一种激进的提前停止策略以节省时间,但如果你认为它可能对你的问题更有效,你可以增加耐心轮数。请注意,验证示例是按顺序从训练折叠中的示例中取出的(参见代码中train_indexval_index的定义),而将折叠外的示例(由kf交叉验证分割得到的test_index)保留用于最终验证。如果你不希望对用于提前停止的数据产生自适应过拟合,这一点很重要。

在下一部分,在进入交叉验证循环并继续训练和测试剩余的交叉验证折之前,你分析出在出折集上获得的折的结果:

 if len(history[k]) >= 10:
                threshold = np.percentile(history[k], q=25)
                if test_score > threshold:
                    print(f"Early stopping for under-performing fold: 
                          threshold is {threshold:0.5f}")
                    return np.mean(validation_scores)

            history[k].append(test_score)
        return np.mean(validation_scores)
    return objective 

注意,我们正在维护一个全局字典history,其中包含到目前为止从每个折中获得的成果。我们可以比较多个实验和交叉验证的结果;由于随机种子,交叉验证是可重复的,因此相同折的成果可以完美比较。如果当前折的结果与其他迭代中获得的先前折的结果(以底部四分位数作为参考)相比较差,那么想法是停止并返回迄今为止测试的折的平均值。这样做的原因是,如果一个折没有呈现可接受的结果,那么整个交叉验证可能也不会。因此,你可以直接退出并转向另一组更有希望的参数。这是一种交叉验证的早期停止,应该会加快你的搜索速度,并允许你在更短的时间内覆盖更多实验。

接下来,使用我们的make_objective函数,我们将所有元素(模型、数据、搜索空间、验证策略和评分函数)组合成一个单一函数,即目标函数。结果,我们现在有一个只接受要优化的参数并返回得分的函数,基于这个得分,优化的最小化引擎将决定下一个实验:

objective = make_objective(model,
                           X_train, y_train,
                           space=space,
                           cv=kf,
                           scoring=scoring) 

由于我们想要控制优化的每一步并保存以供以后使用,我们还准备了一个回调函数,该函数将在最小化过程的每个迭代中保存执行实验及其结果列表。只需使用这两条信息,最小化引擎就可以在任何时候停止,然后可以从检查点恢复优化:

def onstep(res):
    global counter
    x0 = res.x_iters   # List of input points
    y0 = res.func_vals # Evaluation of input points
    print('Last eval: ', x0[-1], 
          ' - Score ', y0[-1])
    print('Current iter: ', counter, 
          ' - Best Score ', res.fun, 
          ' - Best Args: ', res.x)
    # Saving a checkpoint to disk
    joblib.dump((x0, y0), 'checkpoint.pkl') 
    counter += 1 

到目前为止,我们已经准备好开始。贝叶斯优化需要一些起始点才能正常工作。我们通过随机搜索(使用dummy_minimize函数)创建了一系列实验,并保存了它们的结果:

counter = 0
history = {i:list() for i in range(5)}
used_time = 0
gp_round = dummy_minimize(func=objective,
                          dimensions=space,
                          n_calls=30,
                          callback=[onstep],
                          random_state=0) 

然后,我们可以检索保存的实验并打印出贝叶斯优化测试的超参数集序列及其结果。实际上,我们可以在x0y0列表中找到参数及其结果:

x0, y0 = joblib.load('checkpoint.pkl')
print(len(x0)) 

到目前为止,我们甚至可以带一些对搜索空间、获取函数、调用次数或回调的更改来恢复贝叶斯优化:

x0, y0 = joblib.load('checkpoint.pkl')
gp_round = gp_minimize(func=objective,
                       x0=x0,    # already examined values for x
                       y0=y0,    # observed values for x0
                       dimensions=space,
                       acq_func='gp_hedge',
                       n_calls=30,
                       n_initial_points=0,
                       callback=[onstep],
                       random_state=0) 

一旦我们满意地认为不需要继续调用优化函数,我们可以打印出最佳得分(基于我们的输入和验证方案)以及最佳超参数集:

x0, y0 = joblib.load('checkpoint.pkl')
print(f"Best score: {gp_round.fun:0.5f}")
print("Best hyperparameters:")
for sp, x in zip(gp_round.space, gp_round.x):
    print(f"{sp.name:25} : {x}") 

基于最佳结果,我们可以重新训练我们的模型,以便在比赛中使用。

现在我们有了参数集及其结果(x0y0列表),我们还可以探索不同的结果,并将那些输出相似但使用的参数集不同的结果聚类在一起。这将帮助我们训练一个具有相似性能但不同优化策略的更多样化的模型集。这是混合的理想情况,即通过平均多个模型来降低估计的方差,并获得更好的公共和私人排行榜分数。

参考第九章,关于混合和堆叠解决方案的集成的讨论。

将贝叶斯优化扩展到神经网络架构搜索

进入深度学习领域,神经网络似乎也有许多超参数需要调整:

  • 批处理大小

  • 学习率

  • 优化器的类型及其内部参数

所有这些参数都会影响网络的学习方式,它们可以产生重大影响;仅仅批处理大小或学习率的一点点差异就可能导致网络能否将其错误降低到某个阈值以下。

话虽如此,这些学习参数并不是你在与深度神经网络(DNNs)一起工作时唯一可以优化的参数。网络在层中的组织方式和其架构的细节可以产生更大的影响。

事实上,从技术上来说,架构意味着深度神经网络的表示能力,这意味着,根据你使用的层,网络要么能够读取和处理数据中所有可用的信息,要么不能。与其他机器学习算法相比,虽然你的选择似乎无限,但唯一明显的限制是你处理神经网络部分以及将它们组合在一起的知识和经验。

优秀的深度学习实践者在组装高性能深度神经网络(DNNs)时常用的常见最佳实践主要依赖于:

  • 依赖于预训练模型(因此你必须非常了解可用的解决方案,例如在 Hugging Face(huggingface.co/models)或 GitHub 上找到的)

  • 阅读前沿论文

  • 复制同一比赛或之前的 Kaggle 笔记本

  • 尝试和错误

  • 灵感和运气

在 Geoffrey Hinton 教授的一次著名课程中,他提到,你可以使用自动化方法,如贝叶斯优化,来实现相似甚至更好的结果。贝叶斯优化还可以避免你陷入困境,因为你无法在众多可能的超参数组合中找到最佳组合。

关于 Geoffrey Hinton 教授课程的录音,请参阅www.youtube.com/watch?v=i0cKa0di_lo

关于幻灯片,请参阅www.cs.toronto.edu/~hinton/coursera/lecture16/lec16.pdf

正如我们之前提到的,即使在大多数复杂的 AutoML 系统中,当你有太多的超参数时,依赖于随机优化可能会产生更好的结果,或者与贝叶斯优化在相同的时间内产生相同的结果。此外,在这种情况下,你还得对抗一个具有尖锐转弯和表面的优化景观;在深度神经网络优化中,许多参数不会是连续的,而是布尔值,仅仅一个变化可能会意外地改善或恶化你网络的性能。

我们的经验告诉我们,随机优化可能不适合 Kaggle 比赛,因为:

  • 你有限的时间和资源

  • 你可以利用你之前的优化结果来找到更好的解决方案

在这种情况下,贝叶斯优化是理想的:你可以根据你拥有的时间和计算资源来设置它,分阶段进行,通过多次会话来细化你的设置。此外,你不太可能轻松地利用并行性来调整深度神经网络,因为它们使用 GPU,除非你手头上有多台非常强大的机器。通过顺序工作,贝叶斯优化只需要一台性能良好的机器来完成这项任务。最后,即使通过搜索难以找到最优架构,由于你利用了先前实验的信息,尤其是在开始时,可以完全避免那些不会工作的参数组合。而在随机优化中,除非你在过程中改变搜索空间,否则所有组合都始终有可能被测试。

然而,也存在一些缺点。贝叶斯优化使用从先前试验构建的代理函数来模拟超参数空间,这并不是一个没有错误的过程。过程最终只关注搜索空间的一部分而忽略其他部分(这些部分可能包含你正在寻找的最小值)的可能性并不是微乎其微的。解决这个问题的方法是运行大量实验以确保安全,或者交替进行随机搜索和贝叶斯优化,用随机试验挑战贝叶斯模型,迫使其以更优的方式重塑搜索模型。

在我们的例子中,我们再次使用 Kaggle 的“30 天机器学习”活动中的数据,这是一个回归任务。我们的例子基于 TensorFlow,但经过一些小的修改,它可以在其他深度学习框架上运行,例如 PyTorch 或 MXNet。

如前所述,你可以在 Kaggle 上找到这个例子:www.kaggle.com/lucamassaron/hacking-bayesian-optimization-for-dnns

让我们开始:

import tensorflow as tf 

在导入 TensorFlow 包后,我们利用其Dataset函数创建一个可迭代的对象,能够为我们的神经网络提供数据批次:

def df_to_dataset(dataframe, shuffle=True, batch_size=32):
    dataframe = dataframe.copy()
    labels = dataframe.pop('target')
    ds = tf.data.Dataset.from_tensor_slices((dict(dataframe),   
                                             labels))
    if shuffle:
        ds = ds.shuffle(buffer_size=len(dataframe))
    ds = ds.batch(batch_size)
    return ds
tf.keras.utils.get_custom_objects().update({'leaky-relu': tf.keras.layers.Activation(tf.keras.layers.LeakyReLU(alpha=0.2))}) 

我们还将漏激活 ReLU 作为我们模型的自定义对象;可以通过字符串调用它,无需直接使用该函数。

我们继续编写一个函数,该函数根据一组超参数创建我们的深度神经网络模型:

def create_model(cat0_dim, cat1_dim, cat2_dim,
                 cat3_dim, cat4_dim, cat5_dim, 
                 cat6_dim, cat7_dim, cat8_dim, cat9_dim,
                 layers, layer_1, layer_2, layer_3, layer_4, layer_5, 
                 activation, dropout, batch_normalization, learning_rate, 
                 **others):

    dims = {'cat0': cat0_dim, 'cat1': cat1_dim, 'cat2': cat2_dim, 
            'cat3': cat3_dim, 'cat4': cat4_dim, 'cat5': cat5_dim,
            'cat6': cat6_dim, 'cat7': cat7_dim, 'cat8': cat8_dim, 
            'cat9': cat9_dim}

    vocab = {h:X_train['cat4'].unique().astype(int) 
             for h in ['cat0', 'cat1', 'cat2', 'cat3', 
                       'cat4', 'cat5', 'cat6', 'cat7', 
                       'cat8', 'cat9']}

    layers = [layer_1, layer_2, layer_3, layer_4, layer_5][:layers]

    feature_columns = list()
    for header in ['cont1', 'cont2', 'cont3', 'cont4', 'cont5', 
                   'cont6','cont7', 'cont8', 'cont9', 'cont10',
                   'cont11', 'cont12', 'cont13']:

        feature_columns.append(tf.feature_column.numeric_column(header))
    for header in ['cat0', 'cat1', 'cat2', 'cat3', 'cat4', 'cat5', 
                   'cat6', 'cat7', 'cat8', 'cat9']:
        feature_columns.append(
            tf.feature_column.embedding_column(
            tf.feature_column.categorical_column_with_vocabulary_list(
            header, vocabulary_list=vocab[header]),  
            dimension=dims[header]))
    feature_layer = tf.keras.layers.DenseFeatures(feature_columns)
    network_struct = [feature_layer]
    for nodes in layers:
        network_struct.append(
                 tf.keras.layers.Dense(nodes, activation=activation))
        if batch_normalization is True:
                   network_struct.append(
                   tf.keras.layers.BatchNormalization())
        if dropout > 0:
            network_struct.append(tf.keras.layers.Dropout(dropout))
    model = tf.keras.Sequential(network_struct + 
                                [tf.keras.layers.Dense(1)])
    model.compile(optimizer=tf.keras.optimizers.Adam(
                          learning_rate=learning_rate),
                  loss= tf.keras.losses.MeanSquaredError(),
                  metrics=['mean_squared_error'])

    return model 

在内部,create_model函数中的代码根据提供的输入自定义神经网络架构。例如,作为函数的参数,您可以提供每个分类变量的嵌入维度,或者定义网络中存在的密集层的结构和数量。所有这些参数都与贝叶斯优化要探索的参数空间相关,因此创建模型的函数的每个输入参数都应该与搜索空间中定义的采样函数相关。您需要做的只是将采样函数放在一个列表中,按照create_model函数期望的顺序:

# Setting the search space

space = [Integer(1, 2, name='cat0_dim'),
         Integer(1, 2, name='cat1_dim'),
         Integer(1, 2, name='cat2_dim'),
         Integer(1, 3, name='cat3_dim'),
         Integer(1, 3, name='cat4_dim'),
         Integer(1, 3, name='cat5_dim'),
         Integer(1, 4, name='cat6_dim'),
         Integer(1, 4, name='cat7_dim'),
         Integer(1, 6, name='cat8_dim'),
         Integer(1, 8, name='cat9_dim'),
         Integer(1, 5, name='layers'),
         Integer(2, 256, name='layer_1'),
         Integer(2, 256, name='layer_2'),
         Integer(2, 256, name='layer_3'),
         Integer(2, 256, name='layer_4'),
         Integer(2, 256, name='layer_5'),
         Categorical(['relu', 'leaky-relu'], name='activation'),
         Real(0.0, 0.5, 'uniform', name='dropout'),
         Categorical([True, False], name='batch_normalization'),
         Categorical([0.01, 0.005, 0.002, 0.001], name='learning_rate'),
         Integer(256, 1024, name='batch_size')
        ] 

如前所述,您现在将所有与搜索相关的元素组合成一个目标函数,由一个函数创建,该函数结合了您的基本搜索元素,如数据和交叉验证策略:

def make_objective(model_fn, X, space, cv, scoring, validation=0.2):
    # This decorator converts your objective function with named arguments
    # into one that accepts a list as argument, while doing the conversion
    # automatically.
    @use_named_args(space) 
    def objective(**params):

        print("\nTesting: ", params)
        validation_scores = list()

        for k, (train_index, test_index) in enumerate(kf.split(X)):
            val_index = list()
            train_examples = len(train_index)
            train_examples = int(train_examples * (1 - validation))
            train_index, val_index = (train_index[:train_examples], 
                                      train_index[train_examples:])

            start_time = time()

            model = model_fn(**params)
            measure_to_monitor = 'val_mean_squared_error'
            modality='min'
            early_stopping = tf.keras.callbacks.EarlyStopping(
                                 monitor=measure_to_monitor,
                                 mode=modality,
                                 patience=5, 
                                 verbose=0)
            model_checkpoint = tf.keras.callbacks.ModelCheckpoint(
                                   'best.model',
                                   monitor=measure_to_monitor, 
                                   mode=modality, 
                                   save_best_only=True, 
                                   verbose=0)
            run = model.fit(df_to_dataset(
                                X_train.iloc[train_index, :], 
                                batch_size=params['batch_size']),
                            validation_data=df_to_dataset(
                                X_train.iloc[val_index, :], 
                                batch_size=1024),
                            epochs=1_000,
                            callbacks=[model_checkpoint, 
                                       early_stopping],
                            verbose=0)

            end_time = time()

            rounds = np.argmin(
                     run.history['val_mean_squared_error']) + 1

            model = tf.keras.models.load_model('best.model')
            shutil.rmtree('best.model')

            test_preds = model.predict(df_to_dataset(
                            X.iloc[test_index, :], shuffle=False, 
                            batch_size=1024)).flatten()
                            test_score = scoring(
                            X.iloc[test_index, :]['target'], 
                            test_preds)
            print(f"CV Fold {k+1} rmse:{test_score:0.5f} - {rounds} 
                  rounds - it took {end_time-start_time:0.0f} secs")
            validation_scores.append(test_score)

            if len(history[k]) >= 10:
                threshold = np.percentile(history[k], q=25)
                if test_score > threshold:
                    print(f"Early stopping for under-performing fold: 
                          threshold is {threshold:0.5f}")
                    return np.mean(validation_scores)

            history[k].append(test_score)
        return np.mean(validation_scores)
    return objective 

下一步是提供一个随机搜索的序列(作为从搜索空间中开始构建一些反馈的一种方式),并将结果作为起点收集。然后,我们可以将它们输入到贝叶斯优化中,并通过使用forest_minimize作为代理函数来继续:

counter = 0
history = {i:list() for i in range(5)}
used_time = 0
gp_round = dummy_minimize(func=objective,
                          dimensions=space,
                          n_calls=10,
                          callback=[onstep],
                          random_state=0)
gc.collect()
x0, y0 = joblib.load('checkpoint.pkl')
gp_round = gp_minimize(func=objective,
                           x0=x0,  # already examined values for x
                           y0=y0,  # observed values for x0
                           dimensions=space,
                           n_calls=30,
                           n_initial_points=0,
                           callback=[onstep],
                           random_state=0)
gc.collect() 

注意,在随机搜索的前十轮之后,我们使用随机森林算法作为代理函数继续我们的搜索。这将确保比使用高斯过程获得更好的和更快的成果。

如前所述,在这个过程中,我们必须努力在现有时间和资源范围内使优化变得可行(例如,通过设置一个较低的n_calls数量)。因此,我们可以通过保存优化状态、检查获得的结果,并在之后决定继续或结束优化过程,不再投入更多时间和精力去寻找更好的解决方案,来分批进行搜索迭代。

使用 KerasTuner 创建更轻量化和更快的模型

如果前一节因为其复杂性而让您感到困惑,KerasTuner 可以为您提供一种快速设置优化的解决方案,无需太多麻烦。尽管它默认使用贝叶斯优化和高斯过程,但 KerasTuner 背后的新想法是超带优化。超带优化使用 bandit 方法来确定最佳参数(参见web.eecs.umich.edu/~mosharaf/Readings/HyperBand.pdf)。这种方法与神经网络配合得相当好,因为神经网络的优化景观非常不规则和不连续,因此并不总是适合高斯过程。

请记住,你无法避免构建一个使用输入超参数构建自定义网络的函数;KerasTuner 只是使这个过程变得更加容易。

让我们从开始讲起。KerasTuner(keras.io/keras_tuner/)被其创造者、Keras 的创始人弗朗索瓦·肖莱特宣布为“为 Keras 模型提供灵活且高效的超参数调整”。

肖莱特提出的运行 KerasTuner 的配方由简单的步骤组成,从你现有的 Keras 模型开始:

  1. 将你的模型封装在一个函数中,其中hp作为第一个参数。

  2. 在函数的开始处定义超参数。

  3. 将 DNN 的静态值替换为超参数。

  4. 编写代码,从给定的超参数构建一个复杂的神经网络模型。

  5. 如果需要,在构建网络时动态定义超参数。

我们现在将通过使用一个示例来探索所有这些步骤如何在 Kaggle 比赛中为你工作。目前,KerasTuner 是任何 Kaggle 笔记本提供的堆栈的一部分,因此你不需要安装它。此外,TensorFlow 附加组件是笔记本预安装的包的一部分。

如果你没有使用 Kaggle 笔记本并且需要尝试 KerasTuner,你可以使用以下命令轻松安装:

!pip install -U keras-tuner
!pip install -U tensorflow-addons 

你可以在 Kaggle 笔记本上找到这个示例已经设置好的链接:www.kaggle.com/lucamassaron/kerastuner-for-imdb/

我们的第一步是导入必要的包(为一些命令创建快捷方式,例如pad_sequences),并直接从 Keras 上传我们将使用的数据:

import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow import keras
import tensorflow_addons as tfa
from sklearn.model_selection import train_test_split
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LeakyReLU
from tensorflow.keras.layers import Activation
from tensorflow.keras.optimizers import SGD, Adam
from tensorflow.keras.wrappers.scikit_learn import KerasClassifier
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
pad_sequences = keras.preprocessing.sequence.pad_sequences
imdb = keras.datasets.imdb(train_data, train_labels),
(test_data, test_labels) = imdb.load_data(num_words=10000)
train_data, val_data, train_labels, val_labels = train_test_split(train_data, train_labels, test_size=0.30,
                 shuffle=True, random_state=0) 

这次,我们使用的是 IMDb 数据集,该数据集包含在 Keras 包中(keras.io/api/datasets/imdb/)。该数据集具有一些有趣的特性:

  • 这是一个包含 25,000 条 IMDb 电影评论的数据集。

  • 评论通过情感(正面/负面)进行标记。

  • 目标类别是平衡的(因此准确率作为评分指标)。

  • 每条评论都被编码为一个单词索引列表(整数)。

  • 为了方便,单词通过整体频率进行索引。

此外,它已经在 Kaggle 上的一项关于词嵌入的流行比赛中成功应用(www.kaggle.com/c/word2vec-nlp-tutorial/overview)。

这个例子涉及自然语言处理。这类问题通常是通过使用基于 LSTM 或 GRU 层的循环神经网络RNNs)来解决的。BERT、RoBERTa 和其他基于转换器的模型通常能取得更好的结果——作为依赖大型语言语料库的预训练模型——但这并不一定在所有问题中都成立,RNNs 可以证明是一个强大的基线,可以击败或是一个神经模型集成的良好补充。在我们的例子中,所有单词都已经进行了数字索引。我们只是将表示填充的数字代码添加到现有的索引中(这样我们就可以轻松地将所有文本归一化到短语长度),句子的开始,一个未知单词和一个未使用的单词:

# A dictionary mapping words to an integer index
word_index = imdb.get_word_index()
# The first indices are reserved
word_index = {k:(v+3) for k,v in word_index.items()} 
word_index["<PAD>"] = 0
word_index["<START>"] = 1
word_index["<UNK>"] = 2  # unknown
word_index["<UNUSED>"] = 3
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
def decode_review(text):
    return ' '.join([reverse_word_index.get(i, '?') for i in text]) 

下一步涉及创建一个用于注意力的自定义层。注意力是转换器模型的基础,也是近年来神经自然语言处理中最具创新性的想法之一。

关于这些类型层如何工作的所有细节,请参阅关于注意力的开创性论文:Vaswani, A. 等人。注意力即一切。神经信息处理系统进展。2017 (proceedings.neurips.cc/paper/2017/file/3f5ee243547dee91fbd053c1c4a845aa-Paper.pdf)。

注意力的概念可以很容易地传达。LSTM 和 GRU 层输出处理过的序列,但并非这些输出序列中的所有元素对于你的预测都一定是重要的。你不必使用池化层在分层序列上平均所有输出序列,实际上你可以对它们进行加权平均(并且在训练阶段学习使用正确的权重)。这种加权过程(注意力)无疑会提高你将要传递的结果。当然,你可以通过使用多个注意力层(我们称之为多头注意力)使这种方法更加复杂,但在我们的例子中,一个单独的层就足够了,因为我们想证明在这个问题上使用注意力比简单地平均或简单地连接所有结果更有效:

from tensorflow.keras.layers import Dense, Dropout
from tensorflow.keras.layers import Flatten, RepeatVector, dot, multiply, Permute, Lambda
K = keras.backend
def attention(layer):
    # --- Attention is all you need --- #
    _,_,units = layer.shape.as_list()
    attention = Dense(1, activation='tanh')(layer)
    attention = Flatten()(attention)
    attention = Activation('softmax')(attention)
    attention = RepeatVector(units)(attention)
    attention = Permute([2, 1])(attention)
    representation = multiply([layer, attention])
    representation = Lambda(lambda x: K.sum(x, axis=-2), 
                            output_shape=(units,))(representation)
    # ---------------------------------- #
    return representation 

作为我们对这个问题的 DNN 架构进行实验的进一步变化,我们还想测试使用不同类型的优化器(如修正的 Adam(自适应学习率的 Adam 优化器;阅读这篇帖子了解更多信息:lessw.medium.com/new-state-of-the-art-ai-optimizer-rectified-adam-radam-5d854730807b)或随机加权平均SWA)的有效性。SWA 是一种基于修改后的学习率计划来平均优化过程中遍历的权重的方法:如果你的模型倾向于过拟合或过度估计,SWA 有助于接近最优解,并且在 NLP 问题中已被证明是有效的。

def get_optimizer(option=0, learning_rate=0.001):
    if option==0:
        return tf.keras.optimizers.Adam(learning_rate)
    elif option==1:
        return tf.keras.optimizers.SGD(learning_rate, 
                                       momentum=0.9, nesterov=True)
    elif option==2:
        return tfa.optimizers.RectifiedAdam(learning_rate)
    elif option==3:
        return tfa.optimizers.Lookahead(
                   tf.optimizers.Adam(learning_rate), sync_period=3)
    elif option==4:
        return tfa.optimizers.SWA(tf.optimizers.Adam(learning_rate))
    elif option==5:
        return tfa.optimizers.SWA(
                   tf.keras.optimizers.SGD(learning_rate, 
                                       momentum=0.9, nesterov=True))
    else:
        return tf.keras.optimizers.Adam(learning_rate) 

定义了两个关键函数后,我们现在面临最重要的编码函数:该函数将根据参数提供不同的神经网络架构。我们不会对所有我们想要连接到不同架构选择的参数进行编码;我们只提供 hp 参数,它应该包含我们想要使用的所有可能的参数,并且将由 KerasTuner 运行。除了函数输入中的 hp 之外,我们还固定了词汇表的大小和要填充的长度(如果实际长度较短,则添加虚拟值;如果长度较长,则截断短语):

layers = keras.layers
models = keras.models

def create_tunable_model(hp, vocab_size=10000, pad_length=256):
    # Instantiate model params
    embedding_size = hp.Int('embedding_size', min_value=8, 
                            max_value=512, step=8)
    spatial_dropout = hp.Float('spatial_dropout', min_value=0, 
                               max_value=0.5, step=0.05)
    conv_layers = hp.Int('conv_layers', min_value=1,
                         max_value=5, step=1)
    rnn_layers = hp.Int('rnn_layers', min_value=1,
                        max_value=5, step=1)
    dense_layers = hp.Int('dense_layers', min_value=1,
                          max_value=3, step=1)
    conv_filters = hp.Int('conv_filters', min_value=32, 
                          max_value=512, step=32)
    conv_kernel = hp.Int('conv_kernel', min_value=1,
                         max_value=8, step=1)
    concat_dropout = hp.Float('concat_dropout', min_value=0, 
                              max_value=0.5, step=0.05)
    dense_dropout = hp.Float('dense_dropout', min_value=0, 
                             max_value=0.5, step=0.05) 

函数的第一部分,我们简单地从 hp 参数中恢复所有设置。我们还明确指出了每个参数的搜索范围。与迄今为止我们看到的所有解决方案相反,这部分工作是在模型函数内部完成的,而不是外部。

函数继续通过使用从 hp 中提取的参数定义不同的层。在某些情况下,一个参数会开启或关闭网络的一部分,执行特定的数据处理。例如,在代码中我们插入了一个图的分支(conv_filtersconv_kernel),它使用卷积层处理单词序列,这些卷积层在它们的 1D 形式中,也可以对 NLP 问题很有用,因为它们可以捕捉到 LSTMs 可能更难把握的局部单词序列和意义。

现在我们可以定义实际的模型:

 inputs = layers.Input(name='inputs',shape=[pad_length])
    layer  = layers.Embedding(vocab_size, embedding_size, 
                              input_length=pad_length)(inputs)
    layer  = layers.SpatialDropout1D(spatial_dropout)(layer)
    for l in range(conv_layers):
        if l==0:
            conv = layers.Conv1D(filters=conv_filters, 
                       kernel_size=conv_kernel, padding='valid',
                       kernel_initializer='he_uniform')(layer)
        else:
            conv = layers.Conv1D(filters=conv_filters,  
                       kernel_size=conv_kernel, padding='valid', 
                       kernel_initializer='he_uniform')(conv) 
    avg_pool_conv = layers.GlobalAveragePooling1D()(conv)
    max_pool_conv = layers.GlobalMaxPooling1D()(conv)
    representations = list()
    for l in range(rnn_layers):

        use_bidirectional = hp.Choice(f'use_bidirectional_{l}',
                                      values=[0, 1])
        use_lstm = hp.Choice(f'use_lstm_{l}', values=[0, 1])
        units = hp.Int(f'units_{l}', min_value=8, max_value=512, step=8)
        if use_lstm == 1:
            rnl = layers.LSTM
        else:
            rnl = layers.GRU
        if use_bidirectional==1:
            layer = layers.Bidirectional(rnl(units, 
                              return_sequences=True))(layer)
        else:
            layer = rnl(units, return_sequences=True)(layer)
        representations.append(attention(layer))
    layer = layers.concatenate(representations + [avg_pool_conv, 
                                                  max_pool_conv])
    layer = layers.Dropout(concat_dropout)(layer)
    for l in range(dense_layers):
        dense_units = hp.Int(f'dense_units_{l}', min_value=8, 
                             max_value=512, step=8)
        layer = layers.Dense(dense_units)(layer)
        layer = layers.LeakyReLU()(layer)
        layer = layers.Dropout(dense_dropout)(layer)
    layer = layers.Dense(1, name='out_layer')(layer)
    outputs = layers.Activation('sigmoid')(layer)
    model = models.Model(inputs=inputs, outputs=outputs) 

我们首先定义输入层,并使用随后的嵌入层对其进行转换,该嵌入层将序列值编码到密集层中。在处理过程中应用了一些 SpatialDropout1D 正则化,这是一个会随机丢弃输出矩阵的整个列的功能(标准 dropout 会在矩阵中随机丢弃单个元素)。在这些初始阶段之后,我们将网络分为基于卷积(Conv1D)的一个管道和基于循环层(GRU 或 LSTM)的另一个管道。在循环层之后,我们应用了注意力层。最后,这两个管道的输出被连接起来,经过几个更多的密集层后,到达最终的输出节点,一个 sigmoid,因为我们必须表示一个范围在 0 到 1 之间的概率。

在模型定义之后,我们设置学习参数并编译模型,然后返回它:

 hp_learning_rate = hp.Choice('learning_rate', 
                                 values=[0.002, 0.001, 0.0005])
    optimizer_type = hp.Choice('optimizer', values=list(range(6)))
    optimizer = get_optimizer(option=optimizer_type,  
                              learning_rate=hp_learning_rate)

    model.compile(optimizer=optimizer,
                  loss='binary_crossentropy',
                  metrics=['acc'])

    return model 

注意,我们使用 Keras 的功能 API 构建了模型,而不是顺序 API。实际上,我们建议您避免使用顺序 API;它更容易设置,但严重限制了您的潜在架构。

到目前为止,大部分工作已经完成。作为一个建议,我们自己使用 KerasTuner 进行了许多优化后,我们更喜欢首先构建一个非参数化模型,使用我们想要测试的所有可能的架构特性,将网络中相互排斥的部分设置为最复杂的解决方案。在我们设置了生成函数并且我们的模型看起来运行正常之后,例如,我们可以表示其图,并成功地将一些示例作为测试拟合。之后,我们开始将参数化变量插入架构,并设置hp参数定义。

根据我们的经验,立即从参数化函数开始将需要更多的时间和调试努力。KerasTuner 背后的想法是让您将 DNN 视为一组模块化电路,并帮助您优化数据在其中的流动方式。

现在,我们导入 KerasTuner。首先,我们设置调优器本身,然后开始搜索:

import keras_tuner as kt
tuner = kt.BayesianOptimization(hypermodel=create_tunable_model,
                                objective='val_acc',
                                max_trials=100,
                                num_initial_points=3,
                                directory='storage',
                                project_name='imdb',
                                seed=42)
tuner.search(train_data, train_labels, 
             epochs=30,
             batch_size=64, 
             validation_data=(val_data, val_labels),
             shuffle=True,
             verbose=2,
             callbacks = [EarlyStopping('val_acc',
                                        patience=3,
                                        restore_best_weights=True)]
             ) 

作为调优器,我们选择了贝叶斯优化,但您也可以尝试 Hyperband 调优器(keras.io/api/keras_tuner/tuners/hyperband/)并检查它是否更适合您的问题。我们将我们的模型函数提供给hypermodel参数。然后,我们使用字符串或函数设置目标,最大尝试次数(如果没有什么更多的事情要做,KerasTuner 将提前停止),以及初始随机尝试次数——越多越好——以告知贝叶斯过程。早期停止是建模 DNN 的标准且表现良好的实践,您绝对不能忽视。最后,但同样重要的是,我们设置了保存搜索结果的目录以及用于优化步骤可重复性的种子数字。

搜索阶段就像运行一个标准的 Keras 模型拟合一样,而且这一点非常重要——它接受回调。因此,您可以轻松地将早期停止添加到您的模型中。在这种情况下,给定的 epoch 数应被视为最大 epoch 数。您可能还希望优化批大小,我们在这个例子中没有这样做。这仍然需要一些额外的工作,但您可以通过阅读这个 GitHub 已关闭的问题来了解如何实现它:github.com/keras-team/keras-tuner/issues/122

优化完成后,您可以提取最佳参数并保存最佳模型,而无需重新训练:

best_hps = tuner.get_best_hyperparameters()[0]
model = tuner.hypermodel.build(best_hps)
print(best_hps.values)
model.summary()
model.save("best_model.h5") 

在这个例子中,KerasTuner 找到了一个解决方案,它使用了:

  • 一个更大的嵌入层

  • 只简单的 GRU 和 LSTM 层(没有双向层)

  • 多个一维卷积层(Conv1D)的堆叠

  • 更多和更大的密集层

有趣的是,这个解决方案不仅更有效,而且比我们基于直觉和问题经验的前期尝试更轻、更快。

Chollet 本人建议使用 KerasTuner 不仅是为了让你的深度神经网络(DNNs)表现更好,而且是为了将它们缩小到更易于管理的尺寸,这在代码竞赛中可能起到决定性作用。这允许你在有限的推理时间内,结合更多协同工作的模型。

如果你想要查看更多使用 KerasTuner 的示例,François Chollet 还创建了一系列用于 Kaggle 竞赛的 Notebooks,以展示他的优化器的运作和功能:

Optuna 中的 TPE 方法

我们通过另一个有趣的工具和贝叶斯优化的方法来完成我们对贝叶斯优化的概述。正如我们讨论的,Scikit-optimize 使用高斯过程(以及树算法),并直接模拟代理函数和获取函数。

作为对这些主题的提醒,代理函数帮助优化过程模拟尝试一组超参数时的潜在性能结果。代理函数是使用之前的实验及其结果构建的;它只是一个应用于预测特定机器学习算法在特定问题上的行为的预测模型。对于提供给代理函数的每个参数输入,你都会得到一个预期的性能输出。这既直观又相当易于操作,正如我们所看到的。

获取函数则指出哪些超参数组合可以被测试,以改善代理函数预测机器学习算法性能的能力。它也有助于真正测试我们是否可以根据代理函数的预测达到顶级性能。这两个目标代表了贝叶斯优化过程中的探索部分(进行实验)和利用部分(测试性能)。

相反,基于TPE的优化器通过估计参数值的成功可能性来解决问题。换句话说,它们通过连续的细化来模拟参数自身的成功分布,为更成功的值组合分配更高的概率。

在这种方法中,通过这些分布将超参数集分为好和坏,这些分布充当贝叶斯优化中的代理和获取函数,因为分布告诉你在哪里采样以获得更好的性能或探索存在不确定性的地方。

要探索 TPE 的技术细节,我们建议阅读 Bergstra,J.等人撰写的超参数优化算法。神经网络信息处理系统 24 卷,2011 年(proceedings.neurips.cc/paper/2011/file/86e8f7ab32cfd12577bc2619bc635690-Paper.pdf)。

因此,TPE 可以通过从调整后的参数概率分布中进行采样,来模拟搜索空间并同时建议算法下一步可以尝试的内容。

很长一段时间里,Hyperopt是那些喜欢使用 TPE 而不是基于高斯过程的贝叶斯优化的用户的选项。然而,2018 年 10 月,Optuna 出现在开源领域,由于其多功能性(它也适用于神经网络甚至集成),速度和效率,以及与先前优化器相比找到更好解决方案的能力,它已成为 Kagglers 的首选选择。

在本节中,我们将展示设置搜索有多容易,在 Optuna 术语中,这被称为研究。你所需要做的就是编写一个目标函数,该函数接受 Optuna 要测试的参数作为输入,然后返回一个评估结果。验证和其他算法方面可以在目标函数内以直接的方式处理,也可以使用对函数本身外部的变量的引用(既可以是全局变量也可以是局部变量)。Optuna 还允许剪枝,即表示某个特定实验进展不佳,Optuna 可以停止并忘记它。Optuna 提供了一系列激活此回调的函数(见optuna.readthedocs.io/en/stable/reference/integration.html);在此之后,算法将为你高效地运行一切,这将显著减少优化所需的时间。

所有这些都在我们的下一个示例中。我们回到优化30 Days of ML竞赛。这次,我们试图找出哪些参数使 XGBoost 适用于这个竞赛。

你可以在www.kaggle.com/lucamassaron/optuna-bayesian-optimization找到这个示例的 Notebook。

作为第一步,我们上传库和数据,就像之前一样:

import pandas as pd
import numpy as np
from sklearn import preprocessing
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OrdinalEncoder
from xgboost import XGBRegressor
import optuna
from optuna.integration import XGBoostPruningCallback
# Loading data 
X_train = pd.read_csv("../input/30-days-of-ml/train.csv").iloc[:100_000, :]
X_test = pd.read_csv("../input/30-days-of-ml/test.csv")
# Preparing data as a tabular matrix
y_train = X_train.target
X_train = X_train.set_index('id').drop('target', axis='columns')
X_test = X_test.set_index('id')
# Pointing out categorical features
categoricals = [item for item in X_train.columns if 'cat' in item]
# Dealing with categorical data using OrdinalEncoder
ordinal_encoder = OrdinalEncoder()
X_train[categoricals] = ordinal_encoder.fit_transform(X_train[categoricals])
X_test[categoricals] = ordinal_encoder.transform(X_test[categoricals]) 

当使用 Optuna 时,你只需定义一个包含模型、交叉验证逻辑、评估指标和搜索空间的目標函数。

自然地,对于数据,你可以参考函数本身之外的对象,这使得函数的构建变得容易得多。例如,在 KerasTuner 中,你需要一个基于 Optuna 类的特殊输入参数:

def objective(trial):

    params = {
            'learning_rate': trial.suggest_float("learning_rate", 
                                                 0.01, 1.0, log=True),
            'reg_lambda': trial.suggest_loguniform("reg_lambda", 
                                                   1e-9, 100.0),
            'reg_alpha': trial.suggest_loguniform("reg_alpha", 
                                                  1e-9, 100.0),
            'subsample': trial.suggest_float("subsample", 0.1, 1.0),
            'colsample_bytree': trial.suggest_float(
                                      "colsample_bytree", 0.1, 1.0),
            'max_depth': trial.suggest_int("max_depth", 1, 7),
            'min_child_weight': trial.suggest_int("min_child_weight", 
                                                  1, 7),
            'gamma': trial.suggest_float("gamma", 0.1, 1.0, step=0.1)
    }
    model = XGBRegressor(
        random_state=0,
        tree_method="gpu_hist",
        predictor="gpu_predictor",
        n_estimators=10_000,
        **params
    )

    model.fit(x, y, early_stopping_rounds=300, 
              eval_set=[(x_val, y_val)], verbose=1000,
              callbacks=[XGBoostPruningCallback(trial, 'validation_0-rmse')])
    preds = model.predict(x_test)
    rmse = mean_squared_error(y_test, preds, squared=False)
    return rmse 

在这个例子中,出于性能考虑,我们不会进行交叉验证,而是使用一个固定的数据集进行训练,一个用于验证(早期停止),一个用于测试目的。在这个例子中,我们使用 GPU,并且我们还对可用的数据进行子集化,以便将 60 次试验的执行时间缩短到合理的长度。如果你不想使用 GPU,只需从XGBRegressor实例化中移除tree_methodpredictor参数。同时请注意,我们如何在fit方法中设置回调,以便提供关于模型性能的 Optuna 反馈,这样优化器就可以在实验表现不佳时提前停止,为其他尝试腾出空间。

x, x_val, y, y_val = train_test_split(X_train, y_train, random_state=0,
                                      test_size=0.2)
x, x_test, y, y_test = train_test_split(x, y, random_state=0, test_size=0.25)
study = optuna.create_study(direction="minimize")
study.optimize(objective, n_trials=100) 

另一个值得注意的方面是,你可以根据你的问题选择优化最小化或最大化,因为 Scikit-optimize 只适用于最小化问题。

print(study.best_value)
print(study.best_params) 

要完成运行,你只需打印或导出最佳测试性能和优化找到的最佳参数即可。

Ruchi Bhatia

Ruchi Bhatia

www.kaggle.com/ruchi798

作为本章的总结,让我们来看最后一个访谈。这次,我们将与 Ruchi Bhatia 进行对话,她是数据集和笔记的大师级人物。Ruchi 目前是卡内基梅隆大学的硕士研究生,OpenMined 的数据科学家,以及 Z by HP 的数据科学全球大使。

你最喜欢的竞赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

我最喜欢的竞赛类型是自然语言处理和数据分析竞赛。多语言能力在我的主要关注点和兴趣——自然语言处理中发挥了重要作用。

至于数据分析竞赛,我喜欢从复杂的数据中找出意义,并用数据支持我的答案!Kaggle 上的每一场竞赛都是新颖的,需要不同的技术。我主要遵循数据驱动的算法选择方法,没有固定的偏好。

你是如何应对 Kaggle 竞赛的?这种方法与你在日常工作中所做的方法有何不同?

当一项新的竞赛被宣布时,我的首要任务是深入理解问题陈述。有时问题陈述可能超出了我们的舒适区或领域,因此确保我们在进行探索性数据分析之前充分理解它们是至关重要的。在进行 EDA 的过程中,我的目标是理解数据分布,并专注于了解手头的数据。在这个过程中,我们可能会遇到模式,我们应该努力理解这些模式,并为异常值和特殊情况形成假设。

在此之后,我花时间理解竞争指标。创建一个无泄漏的交叉验证策略是我的下一步。之后,我选择一个基线模型并提交我的第一个版本。如果本地验证和竞赛排行榜之间的相关性不令人满意,我会根据需要迭代,以理解可能的差异并加以考虑。

然后我继续随着时间的推移改进我的建模方法。除此之外,调整参数和尝试新的实验有助于了解什么最适合手头的数据(确保在整个过程中防止过拟合)。最后,在竞赛的最后几周,我执行模型集成并检查我解决方案的鲁棒性。

至于我在 Kaggle 之外的项目,我大部分时间都花在数据收集、清理和从数据中获得相关价值上。

Kaggle 是否帮助了你的职业生涯?如果是,是如何帮助的?

Kaggle 极大地帮助我加速了我的职业生涯。它不仅帮助我发现我对数据科学的热情,还激励我有效地贡献并保持一致性。这是一个在指尖上有大量数据可以尝试动手实验并展示我们工作的全球平台。此外,我们的工作易于访问,因此我们可以触及更广泛的受众。

我已经将大部分 Kaggle 工作用于我的作品集,以表明我在迄今为止的旅程中完成的工作的多样性。Kaggle 竞赛旨在解决新颖和现实世界的问题,我认为雇主寻找我们解决这类问题的能力和天赋。我还整理了广泛的数据集,这有助于突出我在处理原始数据方面的敏锐度。这些项目帮助我获得了多个工作机会。

在你的经验中,不经验丰富的 Kagglers 通常忽略了什么?你现在知道什么,而当你刚开始时希望知道的呢?

在我的经验中,我注意到许多 Kagglers 在竞赛中的排名不符合他们的预期时,会感到沮丧。经过几周甚至几个月的辛勤工作,我明白他们为什么可能会早早放弃,但赢得 Kaggle 竞赛并非易事。有来自不同教育背景和工作经验的人参与竞争,有勇气尝试就足够了。我们应该专注于个人的成长,看看我们在旅程中走了多远。

对于数据分析或机器学习,有没有任何特定的工具或库你推荐使用?

综合性的探索性数据分析结合相关的可视化有助于我们发现数据趋势和背景,从而改进我们的方法。由于我相信可视化的重要性,我最喜欢的数据科学库将是 Seaborn 和 TensorBoard。Seaborn 用于 EDA,TensorBoard 用于机器学习工作流程中的可视化。我也偶尔使用 Tableau。

当人们参加比赛时,他们应该记住或做最重要的事情是什么?

当人们进入比赛时,我相信他们应该为深入分析问题陈述和研究做好准备。Kaggle 的比赛尤其具有挑战性,并且在许多情况下有助于解决现实生活中的问题。人们应该保持积极的心态,不要灰心丧气。Kaggle 的比赛提供了学习和成长的最佳机会!

摘要

在本章中,我们详细讨论了超参数优化作为提高模型性能和在排行榜上获得更高分数的方法。我们首先解释了 Scikit-learn 的代码功能,例如网格搜索和随机搜索,以及较新的折半算法。

然后,我们进一步讨论了贝叶斯优化,并探讨了 Scikit-optimize、KerasTuner,最后是 Optuna。我们花了更多的时间讨论通过高斯过程直接建模代理函数以及如何对其进行黑客攻击,因为它可以让你有更强的直觉和更灵活的解决方案。我们认识到,目前 Optuna 已经成为 Kagglers 中的黄金标准,无论是表格竞赛还是深度神经网络竞赛,因为它在 Kaggle 笔记本允许的时间内更快地收敛到最优参数。

然而,如果你想从竞争中脱颖而出,你应该努力测试其他优化器的解决方案。

在下一章中,我们将继续讨论另一种提高你在 Kaggle 比赛中表现的方法:集成模型。通过了解平均、混合和堆叠的工作原理,我们将展示如何通过仅调整超参数之外的方式提升你的结果。

加入我们书的 Discord 空间

加入本书的 Discord 工作空间,参加每月的“问我任何问题”活动,与作者交流:

packt.link/KaggleDiscord

二维码

第九章:使用混合和堆叠解决方案进行集成

当你开始在 Kaggle 上竞争时,很快就会意识到你不能仅凭一个精心设计的模型获胜;你需要集成多个模型。接下来,你将立即想知道如何设置一个有效的集成。周围很少有指南,而且 Kaggle 的经验比科学论文还要多。

这里要说明的是,如果集成是赢得 Kaggle 竞赛的关键,那么在现实世界中,它与复杂性、维护性差、难以重现以及微小的技术成本相关,而这些成本往往掩盖了优势。通常,那种能让你从排名较低跃升至排行榜顶部的微小提升,对于现实世界的应用来说并不重要,因为成本掩盖了优势。然而,这并不意味着集成在现实世界中完全没有使用。在有限的形式下,如平均和混合几个不同的模型,集成使我们能够以更有效和更高效的方式解决许多数据科学问题。

在 Kaggle 中,集成不仅仅是提高预测性能的一种方法,它也是一种团队合作策略。当你与其他队友一起工作时,将每个人的贡献整合在一起,往往会产生比个人努力更好的结果,并且还可以通过将每个人的努力结构化,朝着明确的目标组织团队工作。实际上,当工作在不同时区进行,并且每个参与者都有不同的约束条件时,像结对编程这样的协作技术显然是不可行的。一个团队成员可能因为工作时间受到限制,另一个可能因为学习和考试,等等。

在竞赛中,团队往往没有机会,也不一定需要,将所有参与者同步和协调到同一任务上。此外,团队内的技能也可能有所不同。

在团队中共享的良好的集成策略意味着个人可以继续根据自己的常规和风格工作,同时仍然为团队的成功做出贡献。因此,即使不同的技能在使用基于预测多样性的集成技术时也可能成为优势。

在本章中,我们将从您已经了解的集成技术开始,因为它们嵌入在随机森林和梯度提升等算法中,然后进一步介绍针对多个模型的集成技术,如平均、混合和堆叠。我们将为您提供一些理论、一些实践,以及一些代码示例,您可以在 Kaggle 上构建自己的解决方案时将其用作模板。

我们将涵盖以下主题:

  • 集成算法简介

  • 将模型平均集成到集成中

  • 使用元模型混合模型

  • 堆叠模型

  • 创建复杂的堆叠和混合解决方案

在让您阅读本章并尝试所有技术之前,我们必须提到一个关于集成对我们和所有 Kaggle 竞赛者的伟大参考:由Triskelion (Hendrik Jacob van Veen) 和几位合作者 (Le Nguyen The Dat, Armando Segnini) 在 2015 年撰写的博客文章。Kaggle 集成指南最初可以在mlwave博客上找到 (mlwave.com/kaggle-ensembling-guide),但现在已不再更新,但您可以从usermanual.wiki/Document/Kaggle20ensembling20guide.685545114.pdf检索指南的内容。该文章整理了当时 Kaggle 论坛上关于集成的多数隐性和显性知识。

集成算法简介

模型集成可以优于单个模型的想法并非最近才出现。我们可以追溯到维多利亚时代的英国爵士 弗朗西斯·高尔顿,他发现,为了猜测在县博览会上一头牛的重量,从一群或多或少受过教育的人那里收集的大量估计的平均值比从专家那里得到的单个精心设计的估计更有用。

在 1996 年,Leo Breiman 通过说明袋装技术(也称为“自助聚合”过程)来形式化使用多个模型组合成一个更具预测性的模型的想法,这后来导致了更有效的随机森林算法的发展。在此之后,其他集成技术如梯度提升堆叠也被提出,从而完成了我们今天使用的集成方法范围。

您可以参考几篇文章来了解这些集成算法最初是如何设计的:

  • 对于随机森林,请阅读 Breiman, L. 的文章 Bagging predictors。机器学习 24.2 – 1996: 123-140。

  • 如果您想更详细地了解提升最初是如何工作的,请阅读 Freund, Y. 和 Schapire, R.E. 的文章 Experiments with a new boosting algorithm. icml. Vol. 96 – 1996,以及 Friedman, J. H. 的文章 Greedy function approximation: a gradient boosting machine. Annals of Statistics (2001): 1189-1232。

  • 至于堆叠,请参考 Ting, K. M. 和 Witten, I. H. 的文章 Stacking bagged and dagged models,1997 年,这是该技术的第一个正式草案。

Kaggle 竞赛中集成预测者的第一个基本策略直接来源于分类和回归的 bagging 和随机森林策略。它们涉及对各种预测进行平均,因此被称为平均技术。这些方法在 11 年前举办的第一个 Kaggle 竞赛中迅速出现,也得益于 Kaggle 之前的 Netflix 竞赛,在那里基于不同模型结果平均的策略主导了场景。鉴于它们的成功,基于平均的基本集成技术为许多即将到来的竞赛设定了标准,并且它们至今仍然非常有用和有效,有助于在排行榜上获得更高的分数。

Stacking,这是一种更复杂且计算量更大的方法,在竞赛问题变得更加复杂和参与者之间的竞争更加激烈时出现。正如随机森林方法启发了对不同预测的平均一样,提升法极大地启发了堆叠方法。在提升法中,通过顺序重新处理信息,你的学习算法可以以更好和更完整的方式建模问题。实际上,在梯度提升中,为了建模先前迭代无法掌握的数据部分,会构建顺序决策树。这种想法在堆叠集成中得到了重申,在那里你堆叠先前模型的输出并重新处理它们,以获得预测性能的提升。

罗布·穆拉

www.kaggle.com/robikscube

罗布与我们分享了他在集成和从 Kaggle 中学到的经验。作为竞赛、笔记本和讨论的大师,以及 Biocore LLC 的高级数据科学家,我们可以从他丰富的经验中学到很多东西。

你最喜欢的竞赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

我最喜欢的竞赛类型是那些涉及独特数据集,需要结合不同类型的建模方法来提出新颖解决方案的竞赛。我喜欢当竞赛不仅仅是训练大型模型,而是真正需要深入理解数据并实施利用特定任务架构的想法时。我不试图专精于任何特定的方法。当我第一次开始参加 Kaggle 时,我主要坚持使用梯度提升模型,但为了在近年来保持竞争力,我加深了对深度学习、计算机视觉、NLP 和优化的理解。我最喜欢的竞赛需要使用不止一种技术。

你是如何参加 Kaggle 竞赛的?这种方法和你在日常工作中所做的方法有何不同?

我在某些方面将 Kaggle 比赛与工作项目非常相似。首先是对数据理解。在现实世界项目中,你可能需要定义问题和开发一个好的指标。在 Kaggle 中,这些已经为你准备好了。接下来是理解数据和指标之间的关系——以及开发和测试你认为将最好解决问题的建模技术。与现实生活中数据科学相比,Kaggle 最大的不同之处在于最后一点,即集成和调整模型以获得微小的优势——在许多现实世界的应用中,这些类型的大型集成不是必要的,因为计算成本与性能提升之间的差距可能很小。

告诉我们你参加的一个特别具有挑战性的比赛,以及你使用了哪些见解来应对任务。

我参加的一个极具挑战性的比赛是 NFL 头盔冲击检测 比赛。它涉及视频数据,我对这个领域没有先前的经验。它还要求研究常见的方法和阅读该主题的现有论文。我必须工作在两阶段的方法上,这增加了解决方案的复杂性。另一个我认为具有挑战性的比赛是室内定位导航 比赛。它涉及建模、优化,以及真正理解数据。我在比赛中并没有做得很好,但我学到了很多。

Kaggle 是否帮助你在职业生涯中?如果是,那么是如何帮助的?

是的。Kaggle 在帮助我在数据科学领域获得知名度方面发挥了重要作用。我也在知识和理解新技术方面有所增长,并且遇到了许多才华横溢的人,他们帮助我在技能和机器学习理解方面成长。

我的团队在 NFL 头盔冲击检测 比赛中获得了第二名。在那场比赛之前,我还参加了许多 NFL 比赛。比赛的主持人联系了我,最终这帮助我获得了现在的职位。

在你的经验中,不经验丰富的 Kagglers 通常忽略了什么?你现在知道什么,而当你刚开始时希望知道的呢?

我认为不经验丰富的 Kagglers 有时过于担心模型的集成和超参数调整。这些在比赛的后期很重要,但除非你已经建立了一个良好的基础模型,否则它们并不重要。我也认为完全理解比赛指标非常重要。许多 Kagglers 忽略了理解如何优化解决方案以适应评估指标的重要性。

你在过去比赛中犯过哪些错误?

很多。我曾经过度拟合模型,花费时间在最终没有带来益处的事情上。然而,我认为这对我在未来比赛中更好地应对是必要的。这些错误可能在特定的比赛中伤害了我,但帮助我在后来的比赛中变得更好。

对于数据分析或机器学习,您会推荐使用哪些特定的工具或库?

对于数据探索性分析(EDA),了解如何使用 NumPy、Pandas 和 Matplotlib 或另一个绘图库来操作数据。对于建模,了解如何使用 Scikit-learn 设置适当的交叉验证方案。标准模型如 XGBoost/LightGBM 了解如何设置基线是有用的。深度学习库主要是 TensorFlow/Keras 或 PyTorch。了解这两个主要深度学习库中的任何一个都很重要。

将模型平均到一个集成中

为了更好地介绍平均集成技术,让我们快速回顾 Leo Breiman 为集成设计的所有策略。他的工作代表了集成策略的一个里程碑,他在当时发现的方法在广泛的问题中仍然相当有效。

Breiman 探索了所有这些可能性,以确定是否有一种方法可以减少那些倾向于过度拟合训练数据的强大模型的误差方差,例如决策树。

从概念上讲,他发现集成效果基于三个要素:我们如何处理训练案例的抽样,我们如何构建模型,以及最后,我们如何组合获得的不同模型

关于抽样,测试并发现的方法有:

  • 粘贴,即使用示例(数据行)的子样本(不放回抽样)构建多个模型

  • 袋装,即使用随机选择的 bootstrap 示例(放回抽样)构建多个模型

  • 随机子空间,即使用特征(数据列)的子样本(不放回抽样)构建多个模型

  • 随机补丁,一种类似于袋装的方法,除了在为每个模型选择时也抽样特征,就像在随机子空间中一样

我们抽样而不是使用相同信息的原因是,通过子抽样案例和特征,我们创建了所有与同一问题相关的模型,同时每个模型又与其他模型不同。这种差异也适用于每个模型如何过度拟合样本;我们期望所有模型以相同的方式从数据中提取有用的、可推广的信息,并以不同的方式处理对预测无用的噪声。因此,建模中的变化减少了预测中的变化,因为错误往往相互抵消。

如果这种变化如此有用,那么下一步不应该只是修改模型学习的数据,还应该修改模型本身。我们有两种主要的模型方法:

  • 同类型模型的集成

  • 不同模型的集成

有趣的是,如果我们将要组合的模型在预测能力上差异太大,那么以某种方式集成并不会带来太多帮助。这里的要点是,如果你能组合能够正确猜测相同类型预测的模型,那么它们可以在平均错误预测时平滑它们的错误。如果你正在集成性能差异太大的模型,你很快就会意识到这没有意义,因为总体效果将是负面的:因为你没有平滑你的错误预测,你也在降低正确的预测。

这是有关于平均的一个重要限制:它只能使用一组不同的模型(例如,因为它们使用不同的样本和特征进行训练)如果它们在预测能力上相似。以一个例子来说,线性回归和k最近邻算法在建模问题和从数据中捕捉信号方面有不同的方式;得益于它们核心的(独特的)特征函数形式,这些算法可以从数据中捕捉到不同的预测细微差别,并在预测任务的特定部分上表现更好,但当你使用平均时,你实际上无法利用这一点。相比之下,算法必须捕获信号的不同方式是堆叠实际上可以利用的,因为它可以从每个算法中获取最佳结果。

基于此,我们可以总结出,为了使基于平均的集成(平均多个模型的输出)有效,它应该:

  • 建立在训练在不同样本上的模型之上

  • 建立在从可用特征的不同子样本中使用的模型之上

  • 由具有相似预测能力的模型组成

从技术上讲,这意味着模型的预测应该在预测任务上保持尽可能不相关,同时达到相同的准确度水平。

现在我们已经讨论了平均多个机器学习模型的机遇和限制,我们最终将深入探讨其技术细节。平均多个分类或回归模型有三种方法:

  • 多数投票,使用多个模型中最频繁的分类(仅适用于分类模型)

  • 平均值或概率

  • 使用值或概率的加权平均值

在接下来的几节中,我们将详细讨论每种方法在 Kaggle 比赛中的具体应用。

多数投票

通过改变我们在集成中使用的示例、特征和模型(如果它们在预测能力上相似,如我们之前讨论的)来产生不同的模型,这需要一定的计算工作量,但不需要你构建一个与使用单个模型时设置完全不同的数据处理管道。

在这个流程中,你只需要收集不同的测试预测,记录所使用的模型,训练时如何采样示例或特征,你使用的超参数,以及最终的交叉验证性能。

如果比赛要求你预测一个类别,你可以使用多数投票;也就是说,对于每个预测,你选择你的模型中最频繁预测的类别。这适用于二元预测和多类别预测,因为它假设你的模型有时会有错误,但它们大多数时候可以正确猜测。多数投票被用作“错误纠正程序”,丢弃噪声并保留有意义的信号。

在我们的第一个简单示例中,我们演示了多数投票是如何工作的。我们首先创建我们的示例数据集。使用 Scikit-learn 中的make_classification函数,我们生成一个类似 Madelon 的数据集。

原始的 Madelon 是一个包含数据点的合成数据集,这些数据点被分组在某个维超立方体的顶点上,并随机标记。它包含一些信息性特征,混合了无关的和重复的特征(以在特征之间创建多重共线性),并且它包含一定量的注入随机噪声。由Isabelle Guyon(SVM 算法的创造者之一)为 2003 年 NIPS 特征选择挑战赛所构思,Madelon 数据集是具有挑战性的合成数据集的模型示例。甚至一些 Kaggle 比赛也受到了它的启发:www.kaggle.com/c/overfitting和更近期的www.kaggle.com/c/dont-overfit-ii

我们将在本章中用这个 Madelon 数据集的重建作为测试集成技术的基础:

from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
X, y = make_classification(n_samples=5000, n_features=50, 
                           n_informative=10,
                           n_redundant=25, n_repeated=15, 
                           n_clusters_per_class=5,
                           flip_y=0.05, class_sep=0.5, 
                           random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y,   
                           test_size=0.33, random_state=0) 

在将其分为训练集和测试集之后,我们继续实例化我们的学习算法。我们将仅使用三个基础算法:SVMs、随机森林和k最近邻分类器,以默认超参数进行演示。你可以尝试更改它们或增加它们的数量:

from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import log_loss, roc_auc_score, accuracy_score
model_1 = SVC(probability=True, random_state=0)
model_2 = RandomForestClassifier(random_state=0)
model_3 = KNeighborsClassifier() 

下一步仅仅是训练每个模型在训练集上:

model_1.fit(X_train, y_train)
model_2.fit(X_train, y_train)
model_3.fit(X_train, y_train) 

在这一点上,我们需要对每个模型和集成进行测试集预测,并使用多数投票集成这些预测。为此,我们将使用 SciPy 中的mode函数:

import numpy as np
from scipy.stats import mode
preds = np.stack([model_1.predict(X_test),
                  model_2.predict(X_test),
                  model_3.predict(X_test)]).T
max_voting = np.apply_along_axis(mode, 1, preds)[:,0] 

首先,我们检查每个单独模型的准确性:

for i, model in enumerate(['SVC', 'RF ', 'KNN']):
    acc = accuracy_score(y_true=y_test, y_pred=preds[:, i])
    print(f"Accuracy for model {model} is: {acc:0.3f}") 

我们看到三个模型的表现相似,大约在0.8左右。现在是我们检查多数投票集成的时候了:

max_voting_accuray = accuracy_score(y_true=y_test, y_pred=max_voting)
print(f"Accuracy for majority voting is: {max_voting_accuray:0.3f}") 

投票集成实际上更准确:0.817,因为它成功地整合了大多数正确的信号。

对于多标签问题(当你可以预测多个类别时),你可以简单地选择那些被预测超过一定次数的类别,假设一个相关性阈值表示对类别的预测是信号,而不是噪声。例如,如果你有五个模型,你可以将这个阈值设置为 3,这意味着如果一个类别被至少三个模型预测,那么这个预测应该被认为是正确的。

在回归问题中,以及当你预测概率时,实际上你不能使用多数投票。多数投票仅与类别所有权相关。相反,当你需要预测数字时,你需要数值地组合结果。在这种情况下,求助于平均数加权平均数将为你提供组合预测的正确方法。

模型预测的平均值

在比赛中平均不同模型的预测时,你可以认为所有预测都具有潜在的相同预测能力,并使用算术平均数来得出平均值。

除了算术平均数之外,我们还发现使用以下方法也非常有效:

  • 几何平均数:这是将n个提交相乘,然后取结果的1/n次幂。

  • 对数平均数:类似于几何平均数,你对提交取对数,将它们平均在一起,然后取结果的指数。

  • 调和平均数:这是取你提交的倒数算术平均数,然后取结果的倒数。

  • 幂平均数:这是取提交的n次幂的平均值,然后取结果的1/n次幂。

简单的算术平均总是非常有效,基本上是一个无需思考就能奏效的方法,其效果往往比预期的更好。有时,像几何平均数或调和平均数这样的变体可能会更有效。

继续上一个例子,我们现在将尝试找出当我们切换到ROC-AUC作为评估指标时,哪种平均数效果最好。首先,我们将评估每个单独模型的性能:

proba = np.stack([model_1.predict_proba(X_test)[:, 1],
                  model_2.predict_proba(X_test)[:, 1],
                  model_3.predict_proba(X_test)[:, 1]]).T
for i, model in enumerate(['SVC', 'RF ', 'KNN']):
    ras = roc_auc_score(y_true=y_test, y_score=proba[:, i])
    print(f"ROC-AUC for model {model} is: {ras:0.5f}") 

结果显示范围从0.8750.881

我们第一次测试使用的是算术平均数:

arithmetic = proba.mean(axis=1)
ras = roc_auc_score(y_true=y_test, y_score=arithmetic)
print(f"Mean averaging ROC-AUC is: {ras:0.5f}") 

结果的 ROC-AUC 分数明显优于单个性能:0.90192。我们还测试了几何平均数、调和平均数、对数平均数或幂平均数是否能优于普通平均数:

geometric = proba.prod(axis=1)**(1/3)
ras = roc_auc_score(y_true=y_test, y_score=geometric)
print(f"Geometric averaging ROC-AUC is: {ras:0.5f}")
harmonic = 1 / np.mean(1\. / (proba + 0.00001), axis=1)
ras = roc_auc_score(y_true=y_test, y_score=harmonic)
print(f"Geometric averaging ROC-AUC is: {ras:0.5f}")
n = 3
mean_of_powers = np.mean(proba**n, axis=1)**(1/n)
ras = roc_auc_score(y_true=y_test, y_score=mean_of_powers)
print(f"Mean of powers averaging ROC-AUC is: {ras:0.5f}")
logarithmic = np.expm1(np.mean(np.log1p(proba), axis=1))
ras = roc_auc_score(y_true=y_test, y_score=logarithmic)
print(f"Logarithmic averaging ROC-AUC is: {ras:0.5f}") 

运行代码将告诉我们,它们都不行。在这种情况下,算术平均数是集成时的最佳选择。实际上,在几乎所有情况下,比简单平均数更有效的是将一些先验知识融入到组合数字的方式中。这发生在你在平均计算中对模型进行加权时。

加权平均

当对模型进行加权时,你需要找到一种经验方法来确定正确的权重。一种常见的方法是,尽管非常容易导致自适应过拟合,但可以通过在公共排行榜上测试不同的组合,直到找到得分最高的组合。当然,这并不能保证你在私人排行榜上得分相同。在这里,原则是加权效果更好的部分。然而,正如我们详细讨论过的,由于与私人测试数据的重要差异,公共排行榜的反馈往往不可信。然而,你可以使用你的交叉验证分数或出卷分数(后者将在稍后的堆叠部分中讨论)。事实上,另一种可行的策略是使用与模型交叉验证性能成比例的权重

虽然这有点反直觉,但另一种非常有效的方法是将提交内容与它们的协方差成反比进行加权。实际上,因为我们正通过平均来努力消除误差,所以基于每个提交的独特方差进行平均,使我们能够更重地加权那些相关性较低且多样性较高的预测,从而更有效地减少估计的方差。

在下一个示例中,我们首先将我们的预测概率创建为一个相关矩阵,然后我们继续进行以下操作:

  1. 移除对角线上的一个值并用零替换

  2. 通过行平均相关矩阵以获得一个向量

  3. 取每行总和的倒数

  4. 将它们的总和归一化到 1.0

  5. 使用得到的加权向量进行预测概率的矩阵乘法

下面是这个代码的示例:

cormat = np.corrcoef(proba.T)
np.fill_diagonal(cormat, 0.0)
W = 1 / np.mean(cormat, axis=1)
W = W / sum(W) # normalizing to sum==1.0
weighted = proba.dot(W)
ras = roc_auc_score(y_true=y_test, y_score=weighted)
print(f"Weighted averaging ROC-AUC is: {ras:0.5f}") 

得到的 ROC-AUC 值为0.90206,略好于简单的平均。给予更多不相关预测更高的重视是一种经常成功的集成策略。即使它只提供了轻微的改进,这也可能足以将比赛转为你的优势。

在你的交叉验证策略中进行平均

正如我们所讨论的,平均不需要你构建任何特殊的复杂管道,只需要一定数量的典型数据管道来创建你将要平均的模型,无论是使用所有预测相同的权重,还是使用一些经验发现的权重。唯一测试它的方法是在公共排行榜上运行提交,从而冒着自适应拟合的风险,因为你的平均评估将仅基于 Kaggle 的响应。

在直接在排行榜上测试之前,你也可以在训练时间通过在验证折(你未用于训练模型的折)上运行平均操作来测试。这将为你提供比排行榜上更少的偏见反馈。在下面的代码中,你可以找到一个交叉验证预测是如何安排的示例:

from sklearn.model_selection import KFold
kf = KFold(n_splits=5, shuffle=True, random_state=0)
scores = list()
for k, (train_index, test_index) in enumerate(kf.split(X_train)):
    model_1.fit(X_train[train_index, :], y_train[train_index])
    model_2.fit(X_train[train_index, :], y_train[train_index])
    model_3.fit(X_train[train_index, :], y_train[train_index])

    proba = np.stack(
          [model_1.predict_proba(X_train[test_index, :])[:, 1],
           model_2.predict_proba(X_train[test_index, :])[:, 1],
           model_3.predict_proba(X_train[test_index, :])[:, 1]]).T

    arithmetic = proba.mean(axis=1)
    ras = roc_auc_score(y_true=y_train[test_index], 
                        y_score=arithmetic)
    scores.append(ras)
    print(f"FOLD {k} Mean averaging ROC-AUC is: {ras:0.5f}")
print(f"CV Mean averaging ROC-AUC is: {np.mean(scores):0.5f}") 

依赖于上述代码中的交叉验证结果可以帮助你评估哪种平均策略更有前途,而无需直接在公共排行榜上进行测试。

对 ROC-AUC 评估进行平均校正

如果你的任务将根据 ROC-AUC 分数进行评估,简单地平均你的结果可能不够。这是因为不同的模型可能采用了不同的优化策略,它们的输出可能差异很大。一种解决方案是对模型进行校准,这是我们之前在第五章中讨论的一种后处理类型,但显然这需要更多的时间和计算努力。

在这些情况下,直接的解决方案是将输出概率转换为排名,然后简单地平均这些排名(或者对它们进行加权平均)。使用 min-max 缩放器方法,你只需将每个模型的估计值转换为 0-1 的范围,然后继续进行预测的平均。这将有效地将你的模型的概率输出转换为可以比较的排名:

from sklearn.preprocessing import MinMaxScaler
proba = np.stack(
          [model_1.predict_proba(X_train)[:, 1],
           model_2.predict_proba(X_train)[:, 1],
           model_3.predict_proba(X_train)[:, 1]]).T
arithmetic = MinMaxScaler().fit_transform(proba).mean(axis=1)
ras = roc_auc_score(y_true=y_test, y_score=arithmetic)
print(f"Mean averaging ROC-AUC is: {ras:0.5f}") 

当你直接处理测试预测时,这种方法工作得非常完美。如果你在交叉验证期间尝试平均结果,你可能会遇到问题,因为你的训练数据的预测范围可能与你的测试预测范围不同。在这种情况下,你可以通过训练一个校准模型来解决该问题(参见 Scikit-learn 上的概率校准scikit-learn.org/stable/modules/calibration.html第五章),将预测转换为每个模型的真正、可比较的概率。

使用元模型进行混合模型

我们在第一章中详细讨论的 Netflix 竞赛不仅证明了平均对于数据科学竞赛中的难题是有利的;它还提出了你可以使用一个模型更有效地平均模型结果的想法。获胜团队 BigChaos 在其论文(Töscher, A., Jahrer, M., 和 Bell, R.M. The BigChaos Solution to the Netflix Grand Prize. Netflix prize documentation – 2009)中多次提到了混合,并提供了关于其有效性和工作方式的许多提示。

简而言之,混合是一种加权平均过程,其中用于组合预测的权重是通过保留集和在此之上训练的元模型来估计的。元模型简单来说就是从其他机器学习模型输出中学习的机器学习算法。通常,元学习器是一个线性模型(但有时也可以是非线性的;关于这一点,我们将在下一节中详细讨论),但实际上你可以使用任何你想要的,但会有一些风险,我们将在后面讨论。

获得混合的方法非常直接:

  1. 在开始构建所有模型之前,你应从训练数据中随机提取一个保留样本(在团队中,你们所有人都应使用相同的保留样本)。通常,保留数据大约是可用数据的 10%;然而,根据情况(例如,训练数据中的示例数量,分层),它也可能更少或更多。像往常一样,在采样时,你可以强制分层以确保采样具有代表性,并且你可以使用对抗性验证来测试样本是否真的与训练集其余部分的分布相匹配。

  2. 在剩余的训练数据上训练所有模型。

  3. 在保留数据和测试数据上进行预测。

  4. 将保留预测作为元学习器的训练数据,并重新使用元学习器模型,使用你的模型的测试预测来计算最终的测试预测。或者,你可以使用元学习器来确定在加权平均中应使用的预测因子及其权重。

这种方法有很多优点和缺点。让我们先从优点开始。首先,它很容易实现;你只需要弄清楚保留样本是什么。此外,使用元学习算法可以确保你将找到最佳权重,而无需在公共排行榜上进行测试。

在弱点方面,有时,根据样本大小和所使用的模型类型,减少训练示例的数量可能会增加你的估计器的预测方差。此外,即使你在采样保留数据时非常小心,你仍然可能陷入自适应过拟合,即找到适合保留数据的权重,但这些权重不具有可推广性,特别是如果你使用了一个过于复杂的元学习器。最后,使用保留数据作为测试目的具有与我们在模型验证章节中讨论的训练和测试分割相同的限制:如果保留样本的样本量太小,或者由于某种原因,你的采样不具有代表性,那么你将无法获得可靠的估计。

混合的最佳实践

在混合中,你使用的元学习器的类型可以产生很大的差异。最常见的选择是使用线性模型或非线性模型。在线性模型中,线性或逻辑回归是首选。使用正则化模型也有助于排除无用的模型(L1 正则化)或减少不那么有用的模型的影响(L2 正则化)。使用这类元学习器的一个限制是,它们可能会给某些模型分配负贡献,正如你将从模型系数的值中看到的那样。当你遇到这种情况时,模型通常过拟合,因为所有模型都应该对集成(或最坏的情况是,完全不贡献)的建设做出积极贡献。Scikit-learn 的最新版本允许你只强制执行正权重,并移除截距。这些约束作为正则化器,防止过拟合。

作为元学习器的非线性模型较为少见,因为它们在回归和二分类问题中容易过拟合,但在多分类和多标签分类问题中它们通常表现出色,因为它们可以模拟现有类别之间的复杂关系。此外,如果除了模型的预测之外,你还向它们提供原始特征,它们通常表现更好,因为它们可以发现任何有助于它们正确选择更值得信赖的模型的交互作用。

在我们的下一个例子中,我们首先尝试使用线性模型(逻辑回归)进行混合,然后使用非线性方法(随机森林)。我们首先将训练集分为用于混合元素的训练部分和用于元学习器的保留样本。之后,我们在可训练的部分上拟合模型,并在保留样本上进行预测。

from sklearn.preprocessing import StandardScaler
X_blend, X_holdout, y_blend, y_holdout = train_test_split(X_train, y_train, test_size=0.25, random_state=0)
model_1.fit(X_blend, y_blend)
model_2.fit(X_blend, y_blend)
model_3.fit(X_blend, y_blend)
proba = np.stack([model_1.predict_proba(X_holdout)[:, 1],
                  model_2.predict_proba(X_holdout)[:, 1],
                  model_3.predict_proba(X_holdout)[:, 1]]).T
scaler = StandardScaler()
proba = scaler.fit_transform(proba) 

现在我们可以使用保留样本上的预测概率来训练我们的线性元学习器:

from sklearn.linear_model import LogisticRegression
blender = LogisticRegression(solver='liblinear')
blender.fit(proba, y_holdout)
print(blender.coef_) 

得到的系数如下:

[[0.78911314 0.47202077 0.75115854]] 

通过观察系数,我们可以判断哪个模型对元集成模型的贡献更大。然而,记住系数在未良好校准时也会重新调整概率,因此一个模型的系数较大并不一定意味着它是最重要的。如果你想要通过观察系数来了解每个模型在混合中的作用,你首先必须通过标准化(在我们的代码示例中,这是使用 Scikit-learn 的StandardScaler完成的)来重新调整它们。

我们的结果显示,SVC 和k最近邻模型在混合中的权重比随机森林模型更高;它们的系数几乎相同,并且都大于随机森林的系数。

一旦元模型训练完成,我们只需在测试数据上预测并检查其性能:

test_proba = np.stack([model_1.predict_proba(X_test)[:, 1],
                       model_2.predict_proba(X_test)[:, 1],
                       model_3.predict_proba(X_test)[:, 1]]).T
blending = blender.predict_proba(test_proba)[:, 1]
ras = roc_auc_score(y_true=y_test, y_score=blending)
print(f"ROC-AUC for linear blending {model} is: {ras:0.5f}") 

我们可以使用非线性元学习器,例如随机森林,尝试相同的事情:

blender = RandomForestClassifier()
blender.fit(proba, y_holdout)
test_proba = np.stack([model_1.predict_proba(X_test)[:, 1],
                       model_2.predict_proba(X_test)[:, 1],
                       model_3.predict_proba(X_test)[:, 1]]).T
blending = blender.predict_proba(test_proba)[:, 1]
ras = roc_auc_score(y_true=y_test, y_score=blending)
print(f"ROC-AUC for non-linear blending {model} is: {ras:0.5f}") 

集合选择技术由CaruanaNiculescu-MizilCrewKsikes正式化,为使用线性或非线性模型作为元学习器提供了一个替代方案。

如果你对更多细节感兴趣,请阅读他们著名的论文:Caruana, R.,Niculescu-Mizil, A.,Crew, G.,和 Ksikes, A. 从模型库中进行集合选择(第二十一届国际机器学习会议论文集,2004 年)。

集合选择实际上是一个加权平均,因此它可以简单地被认为是线性组合的类似物。然而,它是一个受约束的线性组合(因为它属于爬山优化的一部分),它还将选择模型并只对预测应用正权重。所有这些都有助于最小化过拟合的风险,并确保一个更紧凑的解决方案,因为解决方案将涉及模型选择。从这个角度来看,在所有过拟合风险较高的问题上(例如,因为训练案例数量很少或模型过于复杂)以及在现实世界应用中,由于它简单而有效的解决方案,推荐使用集合选择。

当使用元学习器时,你依赖于其自身成本函数的优化,这可能与竞赛采用的指标不同。集合选择的另一个巨大优势是它可以优化到任何评估函数,因此当竞赛的指标与机器学习模型通常优化的规范不同时,通常建议使用它。

实施集合选择需要以下步骤,如前述论文所述:

  1. 从你的训练模型和保留样本开始。

  2. 在保留样本上测试所有你的模型,并根据评估指标,保留最有效的模型进行选择(即集合选择)。

  3. 然后,继续测试其他可能添加到集合选择中的模型,以便所提出的选择的平均值优于之前的一个。你可以有放回或无放回地进行。无放回时,你只将一个模型放入选择集合一次;在这种情况下,程序就像在正向选择之后的一个简单平均。(在正向选择中,你迭代地向解决方案添加性能提升最大的模型,直到添加更多模型不再提升性能。)有放回时,你可以将一个模型放入选择多次,从而类似于加权平均。

  4. 当你无法获得任何进一步的改进时,停止并使用集合选择。

这里是一个集合选择的简单代码示例。我们首先从原始训练数据中推导出一个保留样本和一个训练选择。我们拟合模型并在我们的保留样本上获得预测,就像之前与元学习器混合时所见:

X_blend, X_holdout, y_blend, y_holdout = train_test_split
    (X_train, y_train, test_size=0.5, random_state=0)
model_1.fit(X_blend, y_blend)
model_2.fit(X_blend, y_blend)
model_3.fit(X_blend, y_blend)
proba = np.stack([model_1.predict_proba(X_holdout)[:, 1],
                  model_2.predict_proba(X_holdout)[:, 1],
                  model_3.predict_proba(X_holdout)[:, 1]]).T 

在下一个代码片段中,通过一系列迭代创建集成。在每个迭代中,我们尝试依次将所有模型添加到当前的集成中,并检查它们是否提高了模型。如果这些添加中的任何一个在保留样本上优于之前的集成,则集成将被更新,并且性能水平将提高到当前水平。

如果没有额外的改进可以提高集成,循环就会停止,并报告集成的组成:

iterations = 100
proba = np.stack([model_1.predict_proba(X_holdout)[:, 1],
                  model_2.predict_proba(X_holdout)[:, 1],
                  model_3.predict_proba(X_holdout)[:, 1]]).T
baseline = 0.5
print(f"starting baseline is {baseline:0.5f}")
models = []
for i in range(iterations):
    challengers = list()
    for j in range(proba.shape[1]):
        new_proba = np.stack(proba[:, models + [j]])
        score = roc_auc_score(y_true=y_holdout, 
                              y_score=np.mean(new_proba, axis=1))
        challengers.append([score, j])

    challengers = sorted(challengers, key=lambda x: x[0],
                         reverse=True)
    best_score, best_model = challengers[0]
    if best_score > baseline:
        print(f"Adding model_{best_model+1} to the ensemble",  
              end=': ') 
        print(f"ROC-AUC increases score to {best_score:0.5f}")
        models.append(best_model)
        baseline = best_score
    else:
        print("Cannot improve further - Stopping") 

最后,我们计算每个模型被插入平均值的次数,并计算我们在测试集上的平均权重:

from collections import Counter
freqs = Counter(models)
weights = {key: freq/len(models) for key, freq in freqs.items()}
print(weights) 

你可以通过各种方式使该过程更加复杂。由于这种方法可能会在初始阶段过度拟合,你可以从一个随机初始化的集成集开始,或者,正如作者所建议的,你可能已经从集合并行中开始使用n个表现最好的模型(你决定n的值,作为一个超参数)。另一种变化涉及在每个迭代中对可以进入选择的模型集应用采样;换句话说,你随机排除一些模型不被选中。这不仅会将随机性注入到过程中,而且还会防止特定模型在选择中占主导地位。

组合堆叠模型

堆叠首次在David Wolpert的论文(Wolpert, D. H. Stacked generalization. Neural networks 5.2 – 1992)中提到,但这个想法在多年后才被广泛接受和普及(例如,只有到 2019 年 12 月发布的 0.22 版本,Scikit-learn 才实现了堆叠包装器)。这主要是因为 Netflix 竞赛,其次是 Kaggle 竞赛。

在堆叠中,你始终有一个元学习器。然而,这次它不是在保留集上训练,而是在整个训练集上训练,这得益于折叠外OOF)预测策略。我们已经在第六章设计良好的验证中讨论了这种策略。在 OOF 预测中,你从一个可复制的k-折交叉验证分割开始。可复制的意味着,通过记录每一轮每个训练和测试集中的案例,或者通过随机种子保证的可重复性,你可以为需要成为堆叠集成一部分的每个模型复制相同的验证方案。

在 Netflix 竞赛中,堆叠和混合经常被互换使用,尽管 Wolpert 最初设计的方法实际上意味着利用基于k-折交叉验证的方案,而不是保留集。事实上,堆叠的核心思想不是像平均那样减少方差;它主要是为了减少偏差,因为预计每个参与堆叠的模型都将掌握数据中存在的一部分信息,以便在最终的元学习器中重新组合。

让我们回顾一下在训练数据上进行的 OOF 预测是如何工作的。在测试模型时,在每次验证中,你都在训练数据的一部分上训练一个模型,并在从训练中保留的另一部分上进行验证。

通过记录验证预测并重新排序以重建原始训练案例的顺序,你将获得对你所使用的训练集的模型预测。然而,由于你使用了多个模型,并且每个模型都预测了它没有用于训练的案例,因此你的训练集预测不应该有任何过拟合效应。

获得所有模型的 OOF 预测后,你可以继续构建一个元学习器,该学习器根据 OOF 预测(第一级预测)预测你的目标,或者你可以在之前的 OOF 预测之上继续产生进一步的 OOF 预测(第二级或更高级预测),从而创建多个堆叠层。这与 Wolpert 本人提出的一个想法相兼容:通过使用多个元学习器,你实际上是在模仿一个没有反向传播的完全连接的前馈神经网络的结构,其中权重被优化计算,以单独在每个层级别最大化预测性能。从实际的角度来看,堆叠多层已被证明非常有效,对于单算法无法获得最佳结果的复杂问题,它工作得非常好。

此外,堆叠的一个有趣方面是,你不需要具有可比预测能力的模型,就像在平均和通常在混合中那样。事实上,甚至表现更差的模型也可能是堆叠集成的一部分。一个k最近邻模型可能无法与梯度提升解决方案相提并论,但当你使用其 OOF 预测进行堆叠时,它可能产生积极的影响,并提高集成的预测性能。

当你训练了所有堆叠层后,就到了预测的时候了。至于产生在各个堆叠阶段使用的预测,重要的是要注意你有两种方法来做这件事。Wolpert 的原始论文建议在所有训练数据上重新训练你的模型,然后使用这些重新训练的模型在测试集上进行预测。在实践中,许多 Kagglers 没有重新训练,而是直接使用为每个折叠创建的模型,并在测试集上进行多次预测,最后进行平均。

在我们的经验中,当使用少量k折时,堆叠通常在预测测试集之前在所有可用数据上完全重新训练时更有效。在这些情况下,样本一致性可能真的会在预测质量上产生差异,因为训练在较少数据上意味着估计的方差更大。正如我们在第六章中讨论的,在创建 OOF 预测时,始终最好使用高数量的折,在 10 到 20 之间。这限制了保留的示例数量,并且,在没有对所有数据进行重新训练的情况下,您可以直接使用从交叉验证训练模型获得的预测的平均值来获得您的测试集预测。

在我们的下一个例子中,为了说明目的,我们只有五个 CV 折,结果被堆叠了两次。在下图中,您可以跟踪数据和模型如何在堆叠过程的各个阶段之间移动:

图 9.1:两层堆叠过程的示意图,最终对预测进行平均

注意:

  • 训练数据被输入到堆叠的每一层(堆叠的第二层中的 OOF 预测与训练数据相结合)

  • 在从 CV 循环中获得 OOF 预测后,模型在完整的训练数据集上重新训练:

  • 最终预测是所有堆叠预测器获得的预测的简单平均值

现在我们来看看代码,了解这个图如何转换为 Python 命令,从第一层训练开始:

from sklearn.model_selection import KFold
kf = KFold(n_splits=5, shuffle=True, random_state=0)
scores = list()
first_lvl_oof = np.zeros((len(X_train), 3))
fist_lvl_preds = np.zeros((len(X_test), 3))
for k, (train_index, val_index) in enumerate(kf.split(X_train)):
    model_1.fit(X_train[train_index, :], y_train[train_index])
    first_lvl_oof[val_index, 0] = model_1.predict_proba(
                                     X_train[val_index, :])[:, 1]

    model_2.fit(X_train[train_index, :], y_train[train_index])
    first_lvl_oof[val_index, 1] = model_2.predict_proba(
                                     X_train[val_index, :])[:, 1]

    model_3.fit(X_train[train_index, :], y_train[train_index])
    first_lvl_oof[val_index, 2] = model_3.predict_proba(
                                     X_train[val_index, :])[:, 1] 

在第一层之后,我们在完整的数据集上重新训练:

model_1.fit(X_train, y_train)
fist_lvl_preds[:, 0] = model_1.predict_proba(X_test)[:, 1]
model_2.fit(X_train, y_train)
fist_lvl_preds[:, 1] = model_2.predict_proba(X_test)[:, 1]
model_3.fit(X_train, y_train)
fist_lvl_preds[:, 2] = model_3.predict_proba(X_test)[:, 1] 

在第二次堆叠中,我们将重用第一层中的相同模型,并将堆叠的 OOF 预测添加到现有变量中:

second_lvl_oof = np.zeros((len(X_train), 3))
second_lvl_preds = np.zeros((len(X_test), 3))
for k, (train_index, val_index) in enumerate(kf.split(X_train)):
    skip_X_train = np.hstack([X_train, first_lvl_oof])
    model_1.fit(skip_X_train[train_index, :],
                y_train[train_index])
    second_lvl_oof[val_index, 0] = model_1.predict_proba(
                          skip_X_train[val_index, :])[:, 1]

    model_2.fit(skip_X_train[train_index, :],
                y_train[train_index])
    second_lvl_oof[val_index, 1] = model_2.predict_proba(
                          skip_X_train[val_index, :])[:, 1]

    model_3.fit(skip_X_train[train_index, :],
                y_train[train_index])
    second_lvl_oof[val_index, 2] = model_3.predict_proba(
                          skip_X_train[val_index, :])[:, 1] 

再次,我们在第二层对完整数据进行重新训练:

skip_X_test = np.hstack([X_test, fist_lvl_preds])
model_1.fit(skip_X_train, y_train)
second_lvl_preds[:, 0] = model_1.predict_proba(skip_X_test)[:, 1]
model_2.fit(skip_X_train, y_train)
second_lvl_preds[:, 1] = model_2.predict_proba(skip_X_test)[:, 1]
model_3.fit(skip_X_train, y_train)
second_lvl_preds[:, 2] = model_3.predict_proba(skip_X_test)[:, 1] 

堆叠通过平均第二层中所有堆叠的 OOF 结果来完成:

arithmetic = second_lvl_preds.mean(axis=1)
ras = roc_auc_score(y_true=y_test, y_score=arithmetic)
scores.append(ras)
print(f"Stacking ROC-AUC is: {ras:0.5f}") 

结果的 ROC-AUC 分数约为0.90424,这比在相同数据和模型上之前的混合和平均尝试要好。

堆叠变体

堆叠的主要变体涉及改变测试数据在层之间的处理方式,是否只使用堆叠的 OOF 预测,还是在所有堆叠层中也使用原始特征,以及使用什么模型作为最后一个模型,以及各种防止过拟合的技巧。

我们讨论了一些我们亲自实验过的最有效的方法:

  • 优化可能使用也可能不使用。有些解决方案不太关心优化单个模型;有些只优化最后一层;有些则优化第一层。根据我们的经验,优化单个模型很重要,我们更喜欢尽早在我们的堆叠集成中完成它。

  • 模型可以在不同的堆叠层中有所不同,或者相同的模型序列可以在每个堆叠层中重复。 这里我们没有一个普遍的规则,因为这真的取决于问题。更有效的模型类型可能因问题而异。作为一个一般建议,将梯度提升解决方案和神经网络结合起来从未让我们失望。

  • 在堆叠过程的第一个层次,尽可能多地创建模型。 例如,如果你的问题是分类问题,可以尝试回归模型,反之亦然。你也可以使用具有不同超参数设置的模型,从而避免过度优化,因为堆叠会为你做出决定。如果你使用神经网络,只需改变随机初始化种子就足以创建一个多样化的模型集合。你也可以尝试使用不同的特征工程,甚至使用无监督学习(例如,Mike Kim 在使用 t-SNE 维度解决他的问题时所做的那样:www.kaggle.com/c/otto-group-product-classification-challenge/discussion/14295)。这种想法是,所有这些贡献的选择都是在堆叠的第二层完成的。这意味着在那个点上,你不需要进一步实验,只需专注于一组表现更好的模型。通过应用堆叠,你可以重用所有实验,并让堆叠为你决定在建模过程中使用到什么程度。

  • 一些堆叠实现会采用所有功能或其中一部分功能进入后续阶段,这让人联想到神经网络中的跳层。我们注意到,在堆叠的后期引入特征可以提高你的结果,但请注意:这也引入了更多的噪声和过拟合的风险。

  • 理想情况下,你的 OOF 预测应该来自具有高折叠数的交叉验证方案,换句话说,在 10 到 20 之间,但我们也看到过使用较低折叠数(如 5 折)的解决方案。

  • 对于每个折叠,对同一模型进行多次数据袋装(重复抽样的重采样)然后平均所有模型的结果(OOF 预测和测试预测)有助于避免过拟合,并最终产生更好的结果。

  • 注意堆叠中的早期停止。 直接在验证折上使用它可能会导致一定程度的过拟合,这最终可能或可能不会通过堆叠过程得到缓解。我们建议你谨慎行事,并始终基于训练折的验证样本应用早期停止,而不是验证折本身。

可能性是无限的。一旦你掌握了这种集成技术的基本概念,你所需要做的就是将你的创造力应用于手头的问题。我们将在本章的最后部分讨论这个关键概念,我们将研究一个 Kaggle 比赛的堆叠解决方案。

创建复杂的堆叠和混合解决方案

在本章的这一部分,你可能想知道应该将我们讨论过的技术应用到什么程度。从理论上讲,你可以在 Kaggle 上的任何比赛中使用我们提出的所有集成技术,而不仅仅是表格比赛,但你必须考虑一些限制因素:

  • 有时,数据集很大,训练单个模型需要很长时间。

  • 在图像识别比赛中,你只能使用深度学习方法。

  • 即使你能在深度学习比赛中堆叠模型,可供堆叠的不同模型的选择也很有限。由于你被限制在深度学习解决方案中,你只能改变网络的小设计方面和一些超参数(有时只是初始化种子),而不会降低性能。最终,鉴于相同的模型类型和架构中相似性多于差异,预测将过于相似,相关性过高,从而限制了集成技术的有效性。

在这些条件下,复杂的堆叠制度通常不可行。相比之下,当您拥有大量数据集时,平均和混合通常是可能的。

在早期的比赛中,以及所有最近的表格比赛中,复杂的堆叠和混合解决方案主导了比赛。为了给您一个关于在比赛中堆叠所需复杂性和创造性的概念,在本节的最后,我们将讨论由 Gilberto Titericz (www.kaggle.com/titericz) 和 Stanislav Semenov (www.kaggle.com/stasg7) 为 Otto Group 产品分类挑战赛 (www.kaggle.com/c/otto-group-product-classification-challenge) 提供的解决方案。该比赛于 2015 年举行,其任务要求根据 93 个特征将超过 200,000 个产品分类到 9 个不同的类别。

Gilberto 和 Stanislav 提出的解决方案包含三个级别:

  1. 在第一级,有 33 个模型。除了一个 k 近邻簇,其中只有 k 参数不同之外,所有模型都使用了相当不同的算法。他们还使用了无监督的 t-SNE。此外,他们基于维度操作(在最近邻和簇的距离上执行的计算)和行统计(每行中非零元素的数量)构建了八个特征。所有 OOF 预测和特征都传递到了第二级。

  2. 在第二层,他们开始优化超参数,进行模型选择和袋装(通过重采样创建了多个相同模型的版本,并对每个模型的结果进行了平均)。最终,他们只对三种模型在所有数据上进行了重新训练:XGBoost、AdaBoost 和神经网络。

  3. 在第三层,他们首先对 XGBoost 和神经网络进行几何平均,然后将其与 AdaBoost 的结果平均,从而准备了一个加权平均的结果。

我们可以从这个解决方案中学到很多,而不仅仅局限于这个比赛。除了复杂性(在第二层,每个模型重采样的次数在数百次左右)之外,值得注意的是,关于本章中讨论的方案存在多种变体。创造性和试错显然主导了解决方案。这在许多 Kaggle 比赛中相当典型,因为问题很少从一场比赛到另一场比赛相同,每个解决方案都是独特的且难以重复。

许多 AutoML 引擎,例如AutoGluon,或多或少地试图从这些程序中汲取灵感,以提供一系列预定义的自动化步骤,通过堆叠和混合确保您获得最佳结果。

查看arxiv.org/abs/2003.06505以获取 AutoGluon 构建其堆叠模型所使用的算法列表。列表相当长,您将找到许多为自己的堆叠解决方案提供灵感的想法。

然而,尽管他们实施了围绕最佳实践的一些最佳做法,但与一支优秀的 Kagglers 团队所能实现的结果相比,他们的结果总是略逊一筹,因为你在实验和组合集成的方式中的创造力是成功的关键。这一点也适用于我们本章的内容。我们向您展示了集成最佳实践;将它们作为起点,通过混合想法并根据您在 Kaggle 比赛或您正在处理的现实世界问题中进行创新来创建自己的解决方案。

Xavier_Conort

Xavier Conort

www.kaggle.com/xavierconort

为了总结本章,我们采访了 Xavier Conort,他是 2012-2013 年的比赛大师,排名第一。他是 Kaggle 历史初期许多 Kagglers 的灵感来源,现在是他自己公司的创始人兼首席执行官,Data Mapping and Engineering。他向我们讲述了他在 Kaggle 的经历、他的职业生涯以及更多内容。

你最喜欢的比赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

我真的很喜欢那些需要从多个表格中进行特征工程才能获得好成绩的比赛。我喜欢挖掘好的特征,尤其是对于我来说全新的商业问题。这让我对自己的能力解决新问题充满了信心。除了好的特征工程,堆叠也帮助我获得了好成绩。我使用它来混合多个模型或转换文本或高分类变量为数值特征。我最喜欢的算法是 GBM,但我测试了许多其他算法来增加我的混合多样性。

您是如何处理 Kaggle 比赛的?这种方法和您日常工作的方法有何不同?

我的主要目标是尽可能从每次比赛中学习到知识。在参加比赛之前,我试图评估我将发展哪些技能。我不害怕超越我的舒适区。多亏了排行榜的反馈,我知道我可以快速从我的错误中学习。日常工作中很少有这样的机会。很难评估我们正在努力解决的问题的实际质量。因此,我们只是保持谨慎,倾向于重复过去的食谱。我认为没有 Kaggle,我无法学到这么多。

请告诉我们您参加的一个特别具有挑战性的比赛,以及您使用了哪些见解来应对这项任务。

我最喜欢的比赛是 GE Flight Quest,这是由 GE 组织的一项比赛,参赛者需要预测美国国内航班的到达时间。我特别喜欢比赛私人排行榜的设计方式。它通过对我们预测在比赛截止日期之后发生的航班的准确性进行评分,来测试我们预测未来事件的能力。

因为我们只有几个月的历史(如果我的记忆正确,是 3 或 4 个月),我知道有很强的过拟合风险。为了减轻这种风险,我决定只构建与航班延误有明显的因果关系的特征,例如测量天气条件和交通状况的特征。我非常小心地排除了机场名称从我主要特征列表中。事实上,在短短几个月的历史中,一些机场没有经历过恶劣的天气条件。因此,我非常担心我最喜欢的机器学习算法 GBM 会使用机场名称作为良好天气的代理,然后无法很好地预测那些在私人排行榜上的机场。为了捕捉一些机场比其他机场管理得更好的事实,并略微提高我的排行榜分数,我最终确实使用了机场名称,但仅作为残余效应。这是我的第二层模型的一个特征,它使用第一层模型的预测作为偏移量。这种方法可以被认为是一种两步提升,其中你在第一步中抑制了一些信息。我从应用此方法以捕捉地理空间残余效应的精算师那里学到了这一点。

Kaggle 是否帮助了您的职业生涯?如果是的话,是如何帮助的?

这无疑帮助了我作为数据科学家的职业生涯。在转向数据科学之前,我是一名保险行业的精算师,对机器学习一无所知,也不认识任何数据科学家。多亏了 Kaggle 竞赛的多样性,我加速了我的学习曲线。多亏了我的好成绩,我能够展示我的成绩记录,并说服雇主一个 39 岁的精算师可以独立成功开发新技能。而且多亏了 Kaggle 的社区,我与世界各地的许多充满激情的数据科学家建立了联系。我最初与他们竞争或对抗时非常开心。最后,我有机会与他们中的一些人一起工作。Jeremy Achin 和 Tom De Godoy,DataRobot 的创始人,在我被邀请加入 DataRobot 之前是我的竞争对手。如果没有 Kaggle 的帮助,我认为我可能还在保险行业作为精算师工作。

你是否曾经使用过你在 Kaggle 比赛中做过的某些事情来构建你的投资组合,以展示给潜在雇主?

我必须承认,我曾参加过一些比赛,目的是为了给我的雇主或潜在客户留下深刻印象。这很有效,但乐趣更少,压力更大。

在你的经验中,没有经验的新手 Kagglers 经常忽略什么?你现在知道什么,而你在最初开始时希望知道的呢?

我建议没有经验的 Kagglers 不要在比赛期间查看发布的解决方案,而应尝试自己找到好的解决方案。我很高兴在 Kaggle 早期,竞争者没有分享代码。这迫使我通过艰难的方式学习。

你在过去比赛中犯过哪些错误?

一个错误是继续参加设计不良且存在泄露的比赛。这纯粹是浪费时间。你从那些比赛中学不到很多东西。

有没有你推荐的特定工具或库用于数据分析或机器学习?

梯度提升机是我最喜欢的算法。我最初使用了 R 的 gbm,然后是 Scikit-learn GBM,然后是 XGBoost,最后是 LightGBM。大多数时候,它一直是我的获胜解决方案的主要成分。为了了解 GBM 学习的内容,我推荐使用 SHAP 包。

当人们参加比赛时,他们应该记住或做些什么最重要的事情?

竞争是为了学习。竞争是为了与其他充满激情的数据科学家建立联系。不要只是为了赢得比赛而竞争。

摘要

在本章中,我们讨论了如何将多个解决方案进行集成,并提出了你可以用来开始构建自己解决方案的一些基本代码示例。我们从随机森林和梯度提升等模型集成背后的思想开始。然后,我们继续探讨不同的集成方法,从简单的测试提交平均到跨多层堆叠模型的元建模。

正如我们讨论的结尾,集成更多是基于一些共享的共同实践的艺术形式。当我们探索到一个成功的复杂堆叠机制,并在 Kaggle 竞赛中获胜时,我们对其如何针对数据和问题本身进行定制组合感到惊讶。你不能只是拿一个堆叠,复制到另一个问题上,并希望它是最佳解决方案。你只能遵循指南,通过大量的实验和计算工作,自己找到由平均/堆叠/混合的多种模型组成的最佳解决方案。

在下一章中,我们将开始深入研究深度学习竞赛,从计算机视觉竞赛开始,用于分类和分割任务。

加入我们书籍的 Discord 空间

加入书籍的 Discord 工作空间,每月与作者进行一次 问我任何问题 的活动:

packt.link/KaggleDiscord

二维码

第十章:计算机视觉建模

计算机视觉任务是机器学习实际应用中最受欢迎的问题之一;它们是许多 Kagglers(包括我本人,即 Konrad)进入深度学习的门户。在过去的几年里,该领域取得了巨大的进步,新的 SOTA 库仍在不断发布。在本章中,我们将为您概述计算机视觉中最受欢迎的竞赛类型:

  • 图像分类

  • 目标检测

  • 图像分割

我们将从图像增强的简短部分开始,这是一组任务无关的技术,可以应用于不同的问题以提高我们模型的一般化能力。

增强策略

虽然深度学习技术在图像识别、分割或目标检测等计算机视觉任务中取得了极大的成功,但底层算法通常非常数据密集:它们需要大量数据以避免过拟合。然而,并非所有感兴趣的领域都满足这一要求,这就是数据增强发挥作用的地方。这是指一组图像处理技术,它们创建图像的修改版本,从而增强训练数据集的大小和质量,导致深度学习模型性能的改善。增强数据通常代表一组更全面的可能数据点,从而最小化训练集和验证集之间的距离,以及任何未来的测试集。

在本节中,我们将回顾一些更常见的增强技术,以及它们软件实现的选项。最常用的变换包括:

  • 翻转:沿水平或垂直轴翻转图像

  • 旋转:按给定角度(顺时针或逆时针)旋转图像

  • 裁剪:随机选择图像的一个子区域

  • 亮度:修改图像的亮度

  • 缩放:将图像增加到更大的(向外)或更小的(向内)尺寸

下面,我们将通过使用美国演员和喜剧传奇人物贝蒂·怀特的图像来展示这些变换在实际中的应用:

包含人物、蓝色  自动生成的描述

图 10.1:贝蒂·怀特图像

我们可以沿垂直或水平轴翻转图像:

包含人物、女性、蓝色  自动生成的描述

图 10.2:贝蒂·怀特图像 – 垂直翻转(左侧)和水平翻转(右侧)

旋转相当直观;注意背景中图像的自动填充:

包含人物  自动生成的描述

图 10.3:贝蒂·怀特图像 – 顺时针旋转

我们还可以将图像裁剪到感兴趣的区域:

包含人物、闭眼、注视  自动生成的描述

图 10.4:Betty White 图像 – 裁剪

在高层次上,我们可以这样说,增强可以以两种方式之一应用:

  • 离线:这些通常用于较小的数据集(较少的图像或较小的尺寸,尽管“小”的定义取决于可用的硬件)。想法是在数据集预处理步骤中生成原始图像的修改版本,然后与“原始”图像一起使用。

  • 在线:这些用于较大的数据集。增强后的图像不会保存到磁盘上;增强操作是在小批量中应用,并馈送到模型中。

在接下来的几节中,我们将为您概述两种最常用的图像数据集增强方法:内置的 Keras 功能和albumentations包。还有其他一些选项可供选择(skimage、OpenCV、imgaug、Augmentor、SOLT),但我们将重点关注最受欢迎的几种。

本章讨论的方法侧重于由 GPU 驱动的图像分析。张量处理单元TPUs)的使用是一个新兴但仍然相对小众的应用。对图像增强与 TPU 驱动分析感兴趣的读者被鼓励查看 Chris Deotte(@cdeotte)的出色工作:

www.kaggle.com/cdeotte/triple-stratified-kfold-with-tfrecords

Chris 是一位四重 Kaggle 大师,也是一位出色的教育者,通过他创建的笔记本和参与的讨论;总的来说,任何 Kaggler 都值得关注的一个人,无论你的经验水平如何。

我们将使用来自甘薯叶病分类竞赛(www.kaggle.com/c/cassava-leaf-disease-classification)的数据。像往常一样,我们首先进行基础工作:首先,加载必要的包:

import os
import glob
import numpy as np
import scipy as sp
import pandas as pd
import cv2
from skimage.io import imshow, imread, imsave
# imgaug
import imageio
import imgaug as ia
import imgaug.augmenters as iaa
# Albumentations
import albumentations as A
# Keras
# from keras.preprocessing.image import ImageDataGenerator, array_to_img, img_to_array, load_img
# Visualization
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
%matplotlib inline
import seaborn as sns
from IPython.display import HTML, Image
# Warnings
import warnings
warnings.filterwarnings("ignore") 

接下来,我们定义一些辅助函数,以便稍后简化演示。我们需要一种将图像加载到数组中的方法:

def load_image(image_id):
    file_path = image_id 
    image = imread(Image_Data_Path + file_path)
    return image 

我们希望以画廊风格显示多张图像,因此我们创建了一个函数,该函数接受一个包含图像以及所需列数的数组作为输入,并将该数组重塑为具有给定列数的网格:

def gallery(array, ncols=3):
    nindex, height, width, intensity = array.shape
    nrows = nindex//ncols
    assert nindex == nrows*ncols
    result = (array.reshape(nrows, ncols, height, width, intensity)
              .swapaxes(1,2)
              .reshape(height*nrows, width*ncols, intensity))
    return result 

在处理完模板后,我们可以加载用于增强的图像:

data_dir = '../input/cassava-leaf-disease-classification/'
Image_Data_Path = data_dir + '/train_images/'
train_data = pd.read_csv(data_dir + '/train.csv')
# We load and store the first 10 images in memory for faster access
train_images = train_data["image_id"][:10].apply(load_image) 

让我们加载一张单独的图像,以便我们知道我们的参考是什么:

curr_img = train_images[7]
plt.figure(figsize = (15,15))
plt.imshow(curr_img)
plt.axis('off') 

下面就是:

包含背景、植物、外部、绿色的图像 - 自动生成的描述

图 10.5:参考图像

在接下来的几节中,我们将演示如何使用内置的 Keras 功能和albumentations库从参考图像生成增强图像。

Keras 内置增强

Keras 库具有内置的增强功能。虽然不如专用包那么全面,但它具有易于与代码集成的优势。我们不需要单独的代码块来定义增强变换,而可以将它们包含在 ImageDataGenerator 中,这是我们可能无论如何都会使用的一个功能。

我们首先检查的 Keras 方法是基于 ImageDataGenerator 类。正如其名所示,它可以用来生成带有实时数据增强的图像数据批次。

ImageDataGenerator 方法

我们首先以以下方式实例化 ImageDataGenerator 类的对象:

import tensorflow as tf
from tensorflow.keras.preprocessing.image import ImageDataGenerator,
array_to_img, img_to_array, load_img 
datagen = ImageDataGenerator( 
        rotation_range = 40, 
        shear_range = 0.2, 
        zoom_range = 0.2, 
        horizontal_flip = True, 
        brightness_range = (0.5, 1.5)) 
curr_img_array = img_to_array(curr_img)
curr_img_array = curr_img_array.reshape((1,) + curr_img_array.shape) 

我们将所需的增强作为 ImageDataGenerator 的参数。官方文档似乎没有涉及这个话题,但实际结果表明,增强是按照它们作为参数定义的顺序应用的。

在上面的例子中,我们只使用了可能选项的一个有限子集;对于完整的列表,建议读者查阅官方文档:keras.io/api/preprocessing/image/.

接下来,我们使用 ImageDataGenerator 对象的 .flow 方法遍历图像。该类提供了三种不同的函数来将图像数据集加载到内存中并生成增强数据批次:

  • flow

  • flow_from_directory

  • flow_from_dataframe

它们都达到相同的目标,但在指定文件位置的方式上有所不同。在我们的例子中,图像已经存储在内存中,因此我们可以使用最简单的方法进行迭代:

i = 0
for batch in datagen.flow(
    curr_img_array,
    batch_size=1,
    save_to_dir='.',
    save_prefix='Augmented_image',
    save_format='jpeg'):
    i += 1
    # Hard-coded stop - without it, the generator enters an infinite loop
    if i > 9: 
        break 

我们可以使用之前定义的辅助函数来检查增强图像:

aug_images = []
for img_path in glob.glob("*.jpeg"):
    aug_images.append(mpimg.imread(img_path))
plt.figure(figsize=(20,20))
plt.axis('off')
plt.imshow(gallery(np.array(aug_images[0:9]), ncols = 3))
plt.title('Augmentation examples') 

这里是结果:

包含植物的图像 自动生成的描述

图 10.6:增强图像的集合

增强是一个非常有用的工具,但有效地使用它们需要做出判断。首先,显然是一个好主意可视化它们,以了解对数据的影响。一方面,我们希望引入一些数据变化以增加我们模型的一般化;另一方面,如果我们过于激进地改变图像,输入数据将变得不那么有信息量,模型的性能可能会受到影响。此外,选择使用哪些增强也可能具有问题特异性,正如我们通过比较不同的竞赛所看到的那样。

如果您查看上面的 图 10.6(来自 Cassava Leaf Disease Classification 竞赛的参考图像),我们应识别疾病的叶子可能具有不同的尺寸,指向不同的角度等,这既是因为植物的形状,也是因为图像拍摄方式的不同。这意味着垂直或水平翻转、裁剪和旋转等变换在这个上下文中都是有意义的。

相比之下,我们可以查看来自Severstal:钢铁缺陷检测竞赛的样本图像(www.kaggle.com/c/severstal-steel-defect-detection)。在这个竞赛中,参与者必须在钢板上定位和分类缺陷。所有图像都具有相同的大小和方向,这意味着旋转或裁剪会产生不真实的图像,增加了噪声,并对算法的泛化能力产生不利影响。

包含文本、货架、屏幕截图的图片,自动生成的描述

图 10.7:Severstal 竞赛的样本图像

预处理层

作为原生 Keras 中的预处理步骤的数据增强的另一种方法是使用preprocessing层 API。该功能非常灵活:这些管道可以与 Keras 模型结合使用或独立使用,类似于ImageDataGenerator

下面我们简要说明如何设置预处理层。首先,导入:

from tensorflow.keras.layers.experimental import preprocessing
from tensorflow.keras import layers 

我们以标准的 Keras 方式加载预训练模型:

pretrained_base = tf.keras.models.load_model(
    '../input/cv-course-models/cv-course-models/vgg16-pretrained-base',
)
pretrained_base.trainable = False 

预处理层可以使用与其他层在Sequential构造函数内部使用相同的方式;唯一的要求是它们需要在我们的模型定义的开始处指定,在所有其他层之前:

model = tf.keras.Sequential([
    # Preprocessing layers
    preprocessing.RandomFlip('horizontal'), # Flip left-to-right
    preprocessing.RandomContrast(0.5), # Contrast change by up to 50%
    # Base model
    pretrained_base,
    # model head definition 
    layers.Flatten(),
    layers.Dense(6, activation='relu'),
    layers.Dense(1, activation='sigmoid'),
]) 

albumentations

albumentations软件包是一个快速图像增强库,它作为其他库的某种包装构建。

该软件包是经过在多个 Kaggle 竞赛中密集编码的结果(参见medium.com/@iglovikov/the-birth-of-albumentations-fe38c1411cb3),其核心开发者和贡献者中包括一些知名的 Kagglers,包括尤金·赫维琴亚 (www.kaggle.com/bloodaxe)、弗拉基米尔·伊格洛维科夫 (www.kaggle.com/iglovikov)、亚历克斯·帕里诺夫 (www.kaggle.com/creafz)和ZFTurbo (www.kaggle.com/zfturbo)。

完整的文档可以在albumentations.readthedocs.io/en/latest/找到。

下面我们列出重要特性:

  • 针对不同数据类型的统一 API

  • 支持所有常见的计算机视觉任务

  • 与 TensorFlow 和 PyTorch 的集成

使用albumentations功能转换图像非常简单。我们首先初始化所需的转换:

import albumentations as A
horizontal_flip = A.HorizontalFlip(p=1)
rotate = A.ShiftScaleRotate(p=1)
gaus_noise = A.GaussNoise() 
bright_contrast = A.RandomBrightnessContrast(p=1) 
gamma = A.RandomGamma(p=1) 
blur = A.Blur() 

接下来,我们将转换应用于我们的参考图像:

img_flip = horizontal_flip(image = curr_img)
img_gaus = gaus_noise(image = curr_img)
img_rotate = rotate(image = curr_img)
img_bc = bright_contrast(image = curr_img)
img_gamma = gamma(image = curr_img)
img_blur = blur(image = curr_img) 

我们可以通过'image'键访问增强图像并可视化结果:

img_list = [img_flip['image'],img_gaus['image'], img_rotate['image'],
            img_bc['image'], img_gamma['image'], img_blur['image']]
plt.figure(figsize=(20,20))
plt.axis('off')
plt.imshow(gallery(np.array(img_list), ncols = 3))
plt.title('Augmentation examples') 

这里是我们的结果:

包含植物、外部、花园、草地的图片,自动生成的描述

图 10.8:使用 albumentations 库增强的图像

在讨论了增强作为处理计算机视觉问题的一个关键预处理步骤之后,我们现在可以应用这一知识到以下章节中,从一个非常常见的任务开始:图像分类。

Chris Deotte

www.kaggle.com/cdeotte

在我们继续之前,让我们回顾一下我们与 Chris Deotte 的简短对话,我们在本书中多次提到他(包括在本章的早期),这有很好的理由。他是四届 Kaggle 大师级选手,也是 NVIDIA 的高级数据科学家和研究员,于 2019 年加入 Kaggle。

您最喜欢的竞赛类型是什么?为什么?在技术和解决方法方面,您在 Kaggle 上的专长是什么?

我喜欢与有趣数据相关的竞赛,以及需要构建创新新颖模型的竞赛。我的专长是分析训练好的模型,以确定它们的优点和缺点。之后,我喜欢改进模型和/或开发后处理来提升 CV LB。

您是如何处理 Kaggle 竞赛的?这种处理方式与您日常工作的处理方式有何不同?

我每次开始竞赛都会进行 EDA(探索性数据分析),创建本地验证,构建一些简单的模型,并将它们提交到 Kaggle 以获取排行榜分数。这有助于培养一种直觉,了解为了构建一个准确且具有竞争力的模型需要做什么。

请告诉我们您参加的一个特别具有挑战性的竞赛,以及您用来应对任务的见解。

Kaggle 的 Shopee – 价格匹配保证*是一个具有挑战性的竞赛,需要图像模型和自然语言模型。一个关键的见解是从两种模型中提取嵌入,然后确定如何结合使用图像和语言信息来找到产品匹配。

Kaggle 是否帮助了您的职业生涯?如果是的话,是如何帮助的?

是的。Kaggle 帮助我通过提高我的技能和增强我的简历的市场价值,成为 NVIDIA 的高级数据科学家。

许多雇主浏览 Kaggle 上的作品,以寻找具有特定技能的员工来帮助他们解决特定的项目。这样,我收到了许多工作机会的邀请。

在您的经验中,经验不足的 Kagglers 通常忽略了什么?您现在知道什么,而您希望在最初开始时就了解的呢?

在我看来,经验不足的 Kagglers 往往忽略了本地验证的重要性。看到自己的名字在排行榜上是很兴奋的。而且很容易专注于提高我们的排行榜分数,而不是交叉验证分数。

在过去的竞赛中,您犯过哪些错误?

很多时候,我犯了一个错误,就是过分相信我的排行榜分数,而不是交叉验证分数,从而选择了错误的最终提交。

对于数据分析或机器学习,您会推荐使用哪些特定的工具或库?

当然。在优化表格数据模型时,特征工程和快速实验非常重要。为了加速实验和验证的周期,使用 NVIDIA RAPIDS cuDF 和 cuML 在 GPU 上至关重要。

当人们参加比赛时,他们应该记住或做最重要的事情是什么?

最重要的是要享受乐趣并学习。不要担心你的最终排名。如果你专注于学习和享受乐趣,那么随着时间的推移,你的最终排名会越来越好。

您是否使用其他比赛平台?它们与 Kaggle 相比如何?

是的,我在 Kaggle 之外也参加过比赛。像 Booking.com 或 Twitter.com 这样的个别公司偶尔会举办比赛。这些比赛很有趣,涉及高质量的真实数据。

分类

在本节中,我们将演示一个端到端流程,该流程可以用作处理图像分类问题的模板。我们将逐步介绍必要的步骤,从数据准备到模型设置和估计,再到结果可视化。除了提供信息(并且很酷)之外,这一最后步骤如果需要深入检查代码以更好地理解性能,也可以非常有用。

我们将继续使用来自Cassava Leaf Disease Classification比赛的数据(www.kaggle.com/c/cassava-leaf-disease-classification)。

如往常一样,我们首先加载必要的库:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import datetime
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import tensorflow as tf
from tensorflow.keras import models, layers
from tensorflow.keras.preprocessing import image
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau
from tensorflow.keras.applications import EfficientNetB0
from tensorflow.keras.optimizers import Adam
import os, cv2, json
from PIL import Image 

通常,定义几个辅助函数是个好主意;它使得代码更容易阅读和调试。如果您正在处理一个通用的图像分类问题,2019 年由谷歌研究大脑团队在论文中引入的EfficientNet系列模型可以作为一个良好的起点(arxiv.org/abs/1905.11946)。基本思想是平衡网络深度、宽度和分辨率,以实现所有维度的更有效扩展,从而获得更好的性能。对于我们的解决方案,我们将使用该系列中最简单的成员,EfficientNet B0,这是一个具有 1100 万个可训练参数的移动尺寸网络。

对于 EfficientNet 网络的详细解释,您可以从ai.googleblog.com/2019/05/efficientnet-improving-accuracy-and.html作为起点进行探索。

我们以 B0 为基础构建模型,随后是一个用于提高平移不变性的池化层和一个适合我们多类分类问题的激活函数的密集层:

class CFG:    
    # config
    WORK_DIR = '../input/cassava-leaf-disease-classification'
    BATCH_SIZE = 8
    EPOCHS = 5
    TARGET_SIZE = 512
def create_model():
    conv_base = EfficientNetB0(include_top = False, weights = None,
                               input_shape = (CFG.TARGET_SIZE,
                               CFG.TARGET_SIZE, 3))
    model = conv_base.output
    model = layers.GlobalAveragePooling2D()(model)
    model = layers.Dense(5, activation = "softmax")(model)
    model = models.Model(conv_base.input, model)
    model.compile(optimizer = Adam(lr = 0.001),
                  loss = "sparse_categorical_crossentropy",
                  metrics = ["acc"])
    return model 

关于我们传递给EfficientNetB0函数的参数的一些简要说明:

  • include_top 参数允许你决定是否包含最终的密集层。由于我们想将预训练模型用作特征提取器,默认策略将是跳过它们,然后自己定义头部。

  • 如果我们想从头开始训练模型,可以将 weights 设置为 None,或者如果我们要利用在大图像集合上预训练的权重,可以设置为 'imagenet''noisy-student'

下面的辅助函数允许我们可视化激活层,这样我们可以从视觉角度检查网络性能。这在开发一个在透明度方面臭名昭著的领域的直觉时非常有帮助:

def activation_layer_vis(img, activation_layer = 0, layers = 10):
    layer_outputs = [layer.output for layer in model.layers[:layers]]
    activation_model = models.Model(inputs = model.input,
                                    outputs = layer_outputs)
    activations = activation_model.predict(img)

    rows = int(activations[activation_layer].shape[3] / 3)
    cols = int(activations[activation_layer].shape[3] / rows)
    fig, axes = plt.subplots(rows, cols, figsize = (15, 15 * cols))
    axes = axes.flatten()

    for i, ax in zip(range(activations[activation_layer].shape[3]), axes):
        ax.matshow(activations[activation_layer][0, :, :, i],
                   cmap = 'viridis')
        ax.axis('off')
    plt.tight_layout()
    plt.show() 

我们通过为给定的模型创建基于“受限”模型的预测来生成激活,换句话说,使用整个架构直到倒数第二层;这是到 activations 变量的代码。函数的其余部分确保我们展示正确的激活布局,对应于适当卷积层中过滤器的形状。

接下来,我们处理标签并设置验证方案;数据中没有特殊结构(例如,时间维度或类别间的重叠),因此我们可以使用简单的随机分割:

train_labels = pd.read_csv(os.path.join(CFG.WORK_DIR, "train.csv"))
STEPS_PER_EPOCH = len(train_labels)*0.8 / CFG.BATCH_SIZE
VALIDATION_STEPS = len(train_labels)*0.2 / CFG.BATCH_SIZE 

若想了解更多详细的验证方案,请参阅 第六章设计良好的验证

我们现在可以设置数据生成器,这对于我们的基于 TF 的算法循环图像数据是必要的。

首先,我们实例化两个 ImageDataGenerator 对象;这是当我们引入图像增强的时候。为了演示目的,我们将使用 Keras 内置的增强。之后,我们使用 flow_from_dataframe() 方法创建生成器,该方法用于生成具有实时数据增强的批处理张量图像数据:

train_labels.label = train_labels.label.astype('str')
train_datagen = ImageDataGenerator(
    validation_split = 0.2, preprocessing_function = None,
        rotation_range = 45, zoom_range = 0.2,
        horizontal_flip = True, vertical_flip = True,
        fill_mode = 'nearest', shear_range = 0.1,
        height_shift_range = 0.1, width_shift_range = 0.1)
train_generator = train_datagen.flow_from_dataframe(
    train_labels, 
    directory = os.path.join(CFG.WORK_DIR, "train_images"),
    subset = "training", 
    x_col = "image_id",y_col = "label", 
    target_size = (CFG.TARGET_SIZE, CFG.TARGET_SIZE),
    batch_size = CFG.BATCH_SIZE, 
    class_mode = "sparse")
validation_datagen = ImageDataGenerator(validation_split = 0.2)
validation_generator = validation_datagen.flow_from_dataframe(
        train_labels,
        directory = os.path.join(CFG.WORK_DIR, "train_images"),
        subset = "validation", 
        x_col = "image_id",y_col = "label", 
        target_size = (CFG.TARGET_SIZE, CFG.TARGET_SIZE),
        batch_size = CFG.BATCH_SIZE, class_mode = "sparse") 

在指定了数据结构之后,我们可以创建模型:

model = create_model()
model.summary() 

当我们的模型创建完成后,我们可以快速查看一个摘要。这主要用于检查,因为除非你有 photographic memory,否则你不太可能记住像 EffNetB0 这样复杂模型的层组成批次。在实践中,你可以使用摘要来检查输出过滤器的维度是否正确,或者参数计数(可训练的/不可训练的)是否符合预期。为了简洁起见,我们只展示了输出下面的前几行;检查 B0 的架构图将给你一个完整输出将有多长的概念。

Model: "functional_1"
__________________________________________________________________________
Layer (type)                  Output Shape         Param # Connected to
==========================================================================
input_1 (InputLayer)          [(None, 512, 512, 3) 0
__________________________________________________________________________
rescaling (Rescaling)         (None, 512, 512, 3)  0       input_1[0][0]
__________________________________________________________________________
normalization (Normalization) (None, 512, 512, 3)  7       rescaling[0][0]
___________________________________________________________________________
stem_conv_pad (ZeroPadding2D) (None, 513, 513, 3)  0       normalization[0][0]
___________________________________________________________________________
stem_conv (Conv2D)              (None, 256, 256, 32) 864    stem_conv_pad[0][0]
___________________________________________________________________________
stem_bn (BatchNormalization)    (None, 256, 256, 32) 128    stem_conv[0][0]
___________________________________________________________________________
stem_activation (Activation)    (None, 256, 256, 32) 0      stem_bn[0][0]
___________________________________________________________________________
block1a_dwconv (DepthwiseConv2D (None, 256, 256, 32) 288    stem_activation[0][0]
___________________________________________________________________________
block1a_bn (BatchNormalization) (None, 256, 256, 32) 128    block1a_dwconv[0][0]
___________________________________________________________________________ 

在完成上述步骤后,我们可以继续拟合模型。在这一步中,我们还可以非常方便地定义回调。第一个是 ModelCheckpoint

model_save = ModelCheckpoint('./EffNetB0_512_8_best_weights.h5', 
                             save_best_only = True, 
                             save_weights_only = True,
                             monitor = 'val_loss', 
                             mode = 'min', verbose = 1) 

检查点使用了一些值得详细说明的参数:

  • 通过设置 save_best_only = True,我们可以保留最佳模型权重集。

  • 我们通过只保留权重而不是完整的优化器状态来减小模型的大小。

  • 我们通过找到验证损失的最低点来决定哪个模型是最优的。

接下来,我们使用防止过拟合的流行方法之一,早期停止。我们监控模型在保留集上的性能,如果给定数量的 epoch 内指标不再提升,则停止算法,在这个例子中是5

early_stop = EarlyStopping(monitor = 'val_loss', min_delta = 0.001,
                           patience = 5, mode = 'min',
                           verbose = 1, restore_best_weights = True) 

ReduceLROnPlateau回调监控保留集上的损失,如果在patience数量的 epoch 内没有看到改进,则降低学习率,在这个例子中是通过 0.3 的因子降低。虽然这不是万能的解决方案,但它可以经常帮助收敛:

reduce_lr = ReduceLROnPlateau(monitor = 'val_loss', factor = 0.3, 
                              patience = 2, min_delta = 0.001, 
                              mode = 'min', verbose = 1) 

我们现在准备好拟合模型:

history = model.fit(
    train_generator,
    steps_per_epoch = STEPS_PER_EPOCH,
    epochs = CFG.EPOCHS,
    validation_data = validation_generator,
    validation_steps = VALIDATION_STEPS,
    callbacks = [model_save, early_stop, reduce_lr]
) 

我们将简要解释我们之前没有遇到的两个参数:

  • 训练生成器在每个训练 epoch 中产生steps_per_epoch批次的批次。

  • 当 epoch 结束时,验证生成器产生validation_steps批次的批次。

在调用model.fit()之后的一个示例输出如下:

Epoch 00001: val_loss improved from inf to 0.57514, saving model to ./EffNetB0_512_8_best_weights.h5 

模型拟合后,我们可以使用我们在开头编写的辅助函数检查样本图像上的激活。虽然这对于模型的成功执行不是必需的,但它可以帮助确定我们的模型在应用顶部的分类层之前提取了哪些类型的特征:

activation_layer_vis(img_tensor, 0) 

这是我们可能会看到的情况:

包含文本、蔬菜、彩色  自动生成的描述

图 10.9:拟合模型的样本激活

我们可以使用model.predict()生成预测:

ss = pd.read_csv(os.path.join(CFG.WORK_DIR, "sample_submission.csv"))
preds = []
for image_id in ss.image_id:
    image = Image.open(os.path.join(CFG.WORK_DIR,  "test_images",
                                    image_id))
    image = image.resize((CFG.TARGET_SIZE, CFG.TARGET_SIZE))
    image = np.expand_dims(image, axis = 0)
    preds.append(np.argmax(model.predict(image)))
ss['label'] = preds 

我们通过遍历图像列表来构建预测。对于每一张图像,我们将图像重塑到所需的维度,并选择具有最强信号的通道(模型预测类别概率,我们通过argmax选择最大的一个)。最终的预测是类别编号,与竞赛中使用的指标一致。

我们现在已经演示了一个用于图像分类的最小化端到端流程。当然,许多改进都是可能的——例如,更多的增强、更大的架构、回调定制——但基本模板应该为你提供一个良好的起点。

我们现在继续讨论计算机视觉中的第二个流行问题:目标检测。

目标检测

目标检测是计算机视觉/图像处理任务,我们需要在图像或视频中识别特定类别的语义对象实例。在前面章节讨论的分类问题中,我们只需要为每个图像分配一个类别,而在目标检测任务中,我们希望在感兴趣的对象周围绘制一个边界框来定位它。

在本节中,我们将使用来自 全球小麦检测 竞赛的数据(www.kaggle.com/c/global-wheat-detection)。在这个竞赛中,参与者必须检测小麦穗,即植物顶部含有谷物的穗状物。在植物图像中检测这些穗状物用于估计不同作物品种中小麦穗的大小和密度。我们将展示如何使用 Yolov5,一个在目标检测中建立已久的模型,并在 2021 年底之前是最先进的模型,直到它(基于初步结果)被 YoloX 架构超越,来训练一个解决此问题的模型。Yolov5 在竞赛中产生了极具竞争力的结果,尽管由于许可问题最终被组织者禁止使用,但它非常适合本演示的目的。

图 10.10:检测到的小麦穗的样本图像可视化

在我们开始之前,有一个重要的问题需要提及,那就是边界框注释的不同格式;有不同(但数学上等价)的方式来描述矩形的坐标。

最常见的类型是 coco、voc-pascal 和 yolo。它们之间的区别可以从下面的图中清楚地看出:

图 10.11:边界框的注释格式

我们还需要定义的一个部分是网格结构:Yolo 通过在图像上放置一个网格并检查是否有感兴趣的对象(在我们的例子中是小麦穗)存在于任何单元格中来检测对象。边界框被重新塑形以在图像的相关单元格内偏移,并且 (x, y, w, h) 参数被缩放到单位区间:

图 10.12:Yolo 注释定位

我们首先加载训练数据的注释:

df = pd.read_csv('../input/global-wheat-detection/train.csv')
df.head(3) 

让我们检查几个:

包含桌子的图像 自动生成的描述

图 10.13:带有注释的训练数据

我们从 bbox 列中提取边界框的实际坐标:

bboxs = np.stack(df['bbox'].apply(lambda x: np.fromstring(x[1:-1],
                                  sep=',')))
bboxs 

让我们看看这个数组:

array([[834., 222.,  56.,  36.],
       [226., 548., 130.,  58.],
       [377., 504.,  74., 160.],
       ...,
       [134., 228., 141.,  71.],
       [430.,  13., 184.,  79.],
       [875., 740.,  94.,  61.]]) 

下一步是从 Yolo 格式中提取坐标到单独的列:

for i, column in enumerate(['x', 'y', 'w', 'h']):
    df[column] = bboxs[:,i]
df.drop(columns=['bbox'], inplace=True)
df['x_center'] = df['x'] + df['w']/2
df['y_center'] = df['y'] + df['h']/2
df['classes'] = 0
df = df[['image_id','x', 'y', 'w', 'h','x_center','y_center','classes']]
df.head(3) 

Ultralytics 的实现对数据集的结构有一些要求,特别是注释存储的位置以及训练/验证数据的文件夹。

以下代码中文件夹的创建相当简单,但鼓励更好奇的读者查阅官方文档(github.com/ultralytics/yolov5/wiki/Train-Custom-Data):

# stratify on source
source = 'train'
# Pick a single fold for demonstration's sake
fold = 0 
val_index = set(df[df['fold'] == fold]['image_id'])
# Loop through the bounding boxes per image
for name,mini in tqdm(df.groupby('image_id')):
    # Where to save the files
    if name in val_index:
        path2save = 'valid/'
    else:
        path2save = 'train/'   
    # Storage path for labels
    if not os.path.exists('convertor/fold{}/labels/'.
                          format(fold)+path2save):
        os.makedirs('convertor/fold{}/labels/'.format(fold)+path2save)
    with open('convertor/fold{}/labels/'.format(fold)+path2save+name+".
              txt", 'w+') as f:
   # Normalize the coordinates in accordance with the Yolo format requirements
        row = mini[['classes','x_center','y_center','w','h']].
        astype(float).values
        row = row/1024
        row = row.astype(str)
        for j in range(len(row)):
            text = ' '.join(row[j])
            f.write(text)
            f.write("\n")
    if not os.path.exists('convertor/fold{}/images/{}'.
                          format(fold,path2save)):
        os.makedirs('convertor/fold{}/images/{}'.format(fold,path2save))
    # No preprocessing needed for images => copy them as a batch
    sh.copy("../input/global-wheat-detection/{}/{}.jpg".
            format(source,name),
            'convertor/fold{}/images/{}/{}.jpg'.
            format(fold,path2save,name)) 

我们接下来要做的是安装 Yolo 包本身。如果你在 Kaggle Notebook 或 Colab 中运行此代码,请确保已启用 GPU;实际上,即使没有 GPU,Yolo 的安装也可以工作,但你可能会因为 CPU 与 GPU 性能差异而遇到各种超时和内存问题。

!git clone https://github.com/ultralytics/yolov5  && cd yolov5 &&
pip install -r requirements.txt 

我们省略了输出,因为它相当广泛。最后需要准备的是 YAML 配置文件,其中我们指定训练和验证数据的位置以及类别数。我们只对检测麦穗感兴趣,而不是区分不同类型,所以我们有一个类别(其名称仅用于符号一致性,在这种情况下可以是任意字符串):

yaml_text = """train: /kaggle/working/convertor/fold0/images/train/
            val: /kaggle/working/convertor/fold0/images/valid/
            nc: 1
            names: ['wheat']"""
with open("wheat.yaml", 'w') as f:
    f.write(yaml_text)
%cat wheat.yaml 

有了这些,我们就可以开始训练我们的模型:

!python ./yolov5/train.py --img 512 --batch 2 --epochs 3 --workers 2 --data wheat.yaml --cfg "./yolov5/models/yolov5s.yaml" --name yolov5x_fold0 --cache 

除非您习惯于从命令行启动事物,否则上述咒语肯定是晦涩难懂的,因此让我们详细讨论其组成:

  • train.py 是用于从预训练权重开始训练 YoloV5 模型的主脚本。

  • --img 512 表示我们希望原始图像(如您所见,我们没有以任何方式预处理)被缩放到 512x512。为了获得有竞争力的结果,您应该使用更高的分辨率,但此代码是在 Kaggle 笔记本中执行的,它对资源有一定的限制。

  • --batch 指的是训练过程中的批量大小。

  • --epochs 3 表示我们希望训练模型三个周期。

  • --workers 2 指定数据加载器中的工作线程数。增加此数字可能会帮助性能,但在版本 6.0(截至本文写作时在 Kaggle Docker 图像中可用的最新版本)中存在已知错误,当工作线程数过高时,即使在有更多可用资源的机器上也是如此。

  • --data wheat.yaml 是指向我们上面定义的数据规范 YAML 文件的文件。

  • --cfg "./yolov5/models/yolov5s.yaml" 指定模型架构和用于初始化的相应权重集。您可以使用与安装提供的版本(请参阅官方文档以获取详细信息),或者您可以自定义自己的并保持它们以相同的 .yaml 格式。

  • --name 指定结果模型要存储的位置。

我们将以下训练命令的输出分解。首先,基础工作:

Downloading the pretrained weights, setting up Weights&Biases https://wandb.ai/site integration, GitHub sanity check.
Downloading https://ultralytics.com/assets/Arial.ttf to /root/.config/Ultralytics/Arial.ttf...
wandb: (1) Create a W&B account
wandb: (2) Use an existing W&B account
wandb: (3) Don't visualize my results
wandb: Enter your choice: (30 second timeout) 
wandb: W&B disabled due to login timeout.
train: weights=yolov5/yolov5s.pt, cfg=./yolov5/models/yolov5s.yaml, data=wheat.yaml, hyp=yolov5/data/hyps/hyp.scratch-low.yaml, epochs=3, batch_size=2, imgsz=512, rect=False, resume=False, nosave=False, noval=False, noautoanchor=False, evolve=None, bucket=, cache=ram, image_weights=False, device=, multi_scale=False, single_cls=False, optimizer=SGD, sync_bn=False, workers=2, project=yolov5/runs/train, name=yolov5x_fold0, exist_ok=False, quad=False, cos_lr=False, label_smoothing=0.0, patience=100, freeze=[0], save_period=-1, local_rank=-1, entity=None, upload_dataset=False, bbox_interval=-1, artifact_alias=latest
github: up to date with https://github.com/ultralytics/yolov5 ![](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/kgl-bk/img/B17574_10_002.png)
YOLOv5 ![](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/kgl-bk/img/B17574_10_003.png) v6.1-76-gc94736a torch 1.9.1 CUDA:0 (Tesla P100-PCIE-16GB, 16281MiB)
hyperparameters: lr0=0.01, lrf=0.01, momentum=0.937, weight_decay=0.0005, warmup_epochs=3.0, warmup_momentum=0.8, warmup_bias_lr=0.1, box=0.05, cls=0.5, cls_pw=1.0, obj=1.0, obj_pw=1.0, iou_t=0.2, anchor_t=4.0, fl_gamma=0.0, hsv_h=0.015, hsv_s=0.7, hsv_v=0.4, degrees=0.0, translate=0.1, scale=0.5, shear=0.0, perspective=0.0, flipud=0.0, fliplr=0.5, mosaic=1.0, mixup=0.0, copy_paste=0.0
Weights & Biases: run 'pip install wandb' to automatically track and visualize YOLOv5 ![](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/kgl-bk/img/B17574_10_003.png) runs (RECOMMENDED)
TensorBoard: Start with 'tensorboard --logdir yolov5/runs/train', view at http://localhost:6006/
Downloading https://github.com/ultralytics/yolov5/releases/download/v6.1/yolov5s.pt to yolov5/yolov5s.pt...
100%|██████████████████████████████████████| 14.1M/14.1M [00:00<00:00, 40.7MB/s] 

然后是模型。我们看到架构的摘要、优化器设置和使用的增强:

Overriding model.yaml nc=80 with nc=1
                 from  n    params  module                                  arguments
  0                -1  1    3520  models.common.Conv                      [3, 32, 6, 2, 2]
  1                -1  1    18560  models.common.Conv                      [32, 64, 3, 2]
  2                -1  1    18816  models.common.C3                        [64, 64, 1]
  3                -1  1    73984  models.common.Conv                      [64, 128, 3, 2]
  4                -1  2    115712  models.common.C3                        [128, 128, 2]
  5                -1  1    295424  models.common.Conv                      [128, 256, 3, 2]
  6                -1  3    625152  models.common.C3                        [256, 256, 3]
  7                -1  1   1180672  models.common.Conv                      [256, 512, 3, 2]
  8                -1  1   1182720  models.common.C3                        [512, 512, 1]
  9                -1  1    656896  models.common.SPPF                      [512, 512, 5]
 10                -1  1    131584  models.common.Conv                      [512, 256, 1, 1]
 11                -1  1         0  torch.nn.modules.upsampling.Upsample    [None, 2, 'nearest']
 12           [-1, 6]  1         0  models.common.Concat                    [1]
 13                -1  1    361984  models.common.C3                        [512, 256, 1, False]
 14                -1  1     33024  models.common.Conv                      [256, 128, 1, 1]
 15                -1  1         0  torch.nn.modules.upsampling.Upsample    [None, 2, 'nearest']
 16           [-1, 4]  1         0  models.common.Concat                    [1]
 17                -1  1     90880  models.common.C3                        [256, 128, 1, False]
 18                -1  1    147712  models.common.Conv                      [128, 128, 3, 2]
 19          [-1, 14]  1         0  models.common.Concat                    [1]
 20                -1  1    296448  models.common.C3                        [256, 256, 1, False]
 21                -1  1    590336  models.common.Conv                      [256, 256, 3, 2]
 22          [-1, 10]  1         0  models.common.Concat                    [1]
 23                -1  1   1182720  models.common.C3                        [512, 512, 1, False]
 24      [17, 20, 23]  1     16182  models.yolo.Detect                      [1, [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326]], [128, 256, 512]]
YOLOv5s summary: 270 layers, 7022326 parameters, 7022326 gradients, 15.8 GFLOPs
Transferred 342/349 items from yolov5/yolov5s.pt
Scaled weight_decay = 0.0005
optimizer: SGD with parameter groups 57 weight (no decay), 60 weight, 60 bias
albumentations: Blur(always_apply=False, p=0.01, blur_limit=(3, 7)), MedianBlur(always_apply=False, p=0.01, blur_limit=(3, 7)), ToGray(always_apply=False, p=0.01), CLAHE(always_apply=False, p=0.01, clip_limit=(1, 4.0), tile_grid_size=(8, 8))
train: Scanning '/kaggle/working/convertor/fold0/labels/train' images and labels
train: New cache created: /kaggle/working/convertor/fold0/labels/train.cache
train: Caching images (0.0GB ram): 100%|██████████| 51/51 00:00<00:00, 76.00it/
val: Scanning '/kaggle/working/convertor/fold0/labels/valid' images and labels..
val: New cache created: /kaggle/working/convertor/fold0/labels/valid.cache
val: Caching images (2.6GB ram): 100%|██████████| 3322/3322 [00:47<00:00, 70.51i
Plotting labels to yolov5/runs/train/yolov5x_fold0/labels.jpg... 
AutoAnchor: 6.00 anchors/target, 0.997 Best Possible Recall (BPR). Current anchors are a good fit to dataset ![Image sizes 512 train, 512 valUsing 2 dataloader workers ```这后面是实际的训练日志:```pyStarting training for 3 epochs...     Epoch   gpu_mem       box       obj       cls    labels  img_size       0/2    0.371G    0.1196   0.05478         0        14       512: 100%|███               Class     Images     Labels          P          R     mAP@.5 mAP@WARNING: NMS time limit 0.120s exceeded               Class     Images     Labels          P          R     mAP@.5 mAP@                 all       3322     147409    0.00774     0.0523    0.00437   0.000952     Epoch   gpu_mem       box       obj       cls    labels  img_size       1/2    0.474G    0.1176   0.05625         0         5       512: 100%|███               Class     Images     Labels          P          R     mAP@.5 mAP@WARNING: NMS time limit 0.120s exceeded               Class     Images     Labels          P          R     mAP@.5 mAP@WARNING: NMS time limit 0.120s exceeded               Class     Images     Labels          P          R     mAP@.5 mAP@                 all       3322     147409    0.00914     0.0618    0.00493    0.00108     Epoch   gpu_mem       box       obj       cls    labels  img_size       2/2    0.474G    0.1146   0.06308         0        12       512: 100%|███               Class     Images     Labels          P          R     mAP@.5 mAP@                 all       3322     147409    0.00997     0.0674    0.00558    0.001233 epochs completed in 0.073 hours.Optimizer stripped from yolov5/runs/train/yolov5x_fold0/weights/last.pt, 14.4MBOptimizer stripped from yolov5/runs/train/yolov5x_fold0/weights/best.pt, 14.4MBValidating yolov5/runs/train/yolov5x_fold0/weights/best.pt...Fusing layers... YOLOv5s summary: 213 layers, 7012822 parameters, 0 gradients, 15.8 GFLOPs               Class     Images     Labels          P          R     mAP@.5 mAP@WARNING: NMS time limit 0.120s exceeded               Class     Images     Labels          P          R     mAP@.5 mAP@WARNING: NMS time limit 0.120s exceeded               Class     Images     Labels          P          R     mAP@.5 mAP@WARNING: NMS time limit 0.120s exceeded               Class     Images     Labels          P          R     mAP@.5 mAP@WARNING: NMS time limit 0.120s exceeded               Class     Images     Labels          P          R     mAP@.5 mAP@WARNING: NMS time limit 0.120s exceeded               Class     Images     Labels          P          R     mAP@.5 mAP@WARNING: NMS time limit 0.120s exceeded               Class     Images     Labels          P          R     mAP@.5 mAP@WARNING: NMS time limit 0.120s exceeded               Class     Images     Labels          P          R     mAP@.5 mAP@WARNING: NMS time limit 0.120s exceeded               Class     Images     Labels          P          R     mAP@.5 mAP@WARNING: NMS time limit 0.120s exceeded               Class     Images     Labels          P          R     mAP@.5 mAP@WARNING: NMS time limit 0.120s exceeded               Class     Images     Labels          P          R     mAP@.5 mAP@                 all       3322     147409    0.00997     0.0673    0.00556    0.00122Results saved to yolov5/runs/train/yolov5x_fold0 ```训练和验证阶段的结果都可以进行检查;它们存储在 `yolov5` 文件夹下的 `./yolov5/runs/train/yolov5x_fold0`:![](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/kgl-bk/img/B17574_10_14.png)

图 10.14:带有注释的验证数据

一旦我们训练了模型,我们可以使用表现最佳模型的权重(Yolov5 有一个很酷的功能,可以自动保留最佳和最后一个周期的模型,将它们存储为 `best.pt` 和 `last.pt`)来生成测试数据上的预测:

```py
!python ./yolov5/detect.py --weights ./yolov5/runs/train/yolov5x_fold0/weights/best.pt --img 512 --conf 0.1 --source /kaggle/input/global-wheat-detection/test --save-txt --save-conf --exist-ok 

我们将讨论特定于推理阶段的参数:

  • --weights 指向我们上面训练的模型中最佳权重的位置。

  • --conf 0.1指定模型生成的候选边界框中哪些应该被保留。通常,这是一个在精确度和召回率之间的折衷(太低的阈值会导致大量误报,而将阈值调得太高则可能根本找不到任何麦穗头)。

  • --source是测试数据的位置。

为我们的测试图像创建的标签可以在本地进行检查:

!ls ./yolov5/runs/detect/exp/labels/ 

这是我们可能会看到的内容:

2fd875eaa.txt  53f253011.txt  aac893a91.txt  f5a1f0358.txt
348a992bb.txt  796707dd7.txt  cc3532ff6.txt 

让我们看看一个单独的预测:

!cat 2fd875eaa.txt 

它具有以下格式:

0 0.527832 0.580566 0.202148 0.838867 0.101574
0 0.894531 0.587891 0.210938 0.316406 0.113519 

这意味着在图像2fd875eaa中,我们的训练模型检测到了两个边界框(它们的坐标是行中的 2-5 项),并在行末给出了大于 0.1 的置信度分数。

我们如何将预测组合成所需格式的提交?我们首先定义一个辅助函数,帮助我们将坐标从 yolo 格式转换为 coco(如本竞赛所需):这是一个简单的顺序重排和通过乘以图像大小来归一化到原始值范围的问题:

def convert(s):
    x = int(1024 * (s[1] - s[3]/2))
    y = int(1024 * (s[2] - s[4]/2))
    w = int(1024 * s[3])
    h = int(1024 * s[4])

    return(str(s[5]) + ' ' + str(x) + ' ' + str(y) + ' ' + str(w)
           + ' ' + str(h)) 

然后,我们继续生成一个提交文件:

  1. 我们遍历上述列出的文件。

  2. 对于每个文件,所有行都被转换为所需格式的字符串(一行代表一个检测到的边界框)。

  3. 这些行随后被连接成一个字符串,对应于这个文件。

代码如下:

with open('submission.csv', 'w') as myfile:
    # Prepare submission
    wfolder = './yolov5/runs/detect/exp/labels/'
    for f in os.listdir(wfolder):
        fname = wfolder + f
        xdat = pd.read_csv(fname, sep = ' ', header = None)
        outline = f[:-4] + ' ' + ' '.join(list(xdat.apply(lambda s:
                                     convert(s), axis = 1)))
        myfile.write(outline + '\n')

myfile.close() 

让我们看看它的样子:

!cat submission.csv 
53f253011 0.100472 61 669 961 57 0.106223 0 125 234 183 0.1082 96 696 928 126 0.108863 515 393 86 161 0.11459 31 0 167 209 0.120246 517 466 89 147
aac893a91 0.108037 376 435 325 188
796707dd7 0.235373 684 128 234 113
cc3532ff6 0.100443 406 752 144 108 0.102479 405 87 4 89 0.107173 576 537 138 94 0.113459 256 498 179 211 0.114847 836 618 186 65 0.121121 154 544 248 115 0.125105 40 567 483 199
2fd875eaa 0.101398 439 163 204 860 0.112546 807 440 216 323
348a992bb 0.100572 0 10 440 298 0.101236 344 445 401 211
f5a1f0358 0.102549 398 424 295 96 

生成的submission.csv文件完成了我们的流程。

在本节中,我们展示了如何使用 YoloV5 解决目标检测问题:如何处理不同格式的注释,如何为特定任务定制模型,训练它,并评估结果。

基于这些知识,你应该能够开始处理目标检测问题。

现在我们转向计算机视觉任务中的第三大流行类别:语义分割。

语义分割

考虑到分割,最简单的方式是它将图像中的每个像素分类,将其分配给相应的类别;这些像素组合在一起形成感兴趣的区域,例如医学图像中器官上的病变区域。相比之下,目标检测(在上一节中讨论)将图像的片段分类到不同的对象类别,并在它们周围创建边界框。

我们将使用来自Sartorius – Cell Instance Segmentation竞赛(www.kaggle.com/c/sartorius-cell-instance-segmentation)的数据展示建模方法。在这个竞赛中,参与者被要求使用一组显微镜图像训练用于神经细胞实例分割的模型。

我们将围绕Detectron2构建解决方案,这是一个由 Facebook AI Research 创建的库,支持多种检测和分割算法。

Detectron2 是原始 Detectron 库 (github.com/facebookresearch/Detectron/) 和 Mask R-CNN 项目 (github.com/facebookresearch/maskrcnn-benchmark/) 的继任者。

我们首先安装额外的包:

!pip install pycocotools
!pip install 'git+https://github.com/facebookresearch/detectron2.git' 

我们安装 pycocotools (github.com/cocodataset/cocoapi/tree/master/PythonAPI/pycocotools),这是我们格式化注释和 Detectron2(我们在这个任务中的工作马)所需的,以及 Detectron2 (github.com/facebookresearch/detectron2)。

在我们能够训练我们的模型之前,我们需要做一些准备工作:注释需要从组织者提供的 run-length encoding (RLE) 格式转换为 Detectron2 所需的 COCO 格式。RLE 的基本思想是节省空间:创建一个分割意味着以某种方式标记一组像素。由于图像可以被视为一个数组,这个区域可以用一系列直线(行或列方向)表示。

您可以通过列出索引或指定起始位置和后续连续块长度来编码每一行。下面给出了一个视觉示例:

包含桌子的图像 自动生成的描述

图 10.15:RLE 的视觉表示

微软的 Common Objects in Context (COCO) 格式是一种特定的 JSON 结构,它规定了图像数据集中标签和元数据的保存方式。下面,我们演示如何将 RLE 转换为 COCO 并与 k-fold 验证分割结合,以便为每个分割获得所需的训练/验证 JSON 文件对。

让我们从这里开始:

# from pycocotools.coco import COCO
import skimage.io as io
import matplotlib.pyplot as plt
from pathlib import Path
from PIL import Image
import pandas as pd
import numpy as np
from tqdm.notebook import tqdm
import json,itertools
from sklearn.model_selection import GroupKFold
# Config
class CFG:
    data_path = '../input/sartorius-cell-instance-segmentation/'
    nfolds = 5 

我们需要三个函数将 RLE 转换为 COCO。首先,我们需要将 RLE 转换为二值掩码:

# From https://www.kaggle.com/stainsby/fast-tested-rle
def rle_decode(mask_rle, shape):
    '''
    mask_rle: run-length as string formatted (start length)
    shape: (height,width) of array to return 
    Returns numpy array, 1 - mask, 0 - background
    '''
    s = mask_rle.split()
    starts, lengths = [np.asarray(x, dtype=int)
                       for x in (s[0:][::2], s[1:][::2])]
    starts -= 1
    ends = starts + lengths
    img = np.zeros(shape[0]*shape[1], dtype=np.uint8)
    for lo, hi in zip(starts, ends):
        img[lo:hi] = 1
    return img.reshape(shape)  # Needed to align to RLE direction 

第二个将二值掩码转换为 RLE:

# From https://newbedev.com/encode-numpy-array-using-uncompressed-rle-for-
# coco-dataset
def binary_mask_to_rle(binary_mask):
    rle = {'counts': [], 'size': list(binary_mask.shape)}
    counts = rle.get('counts')
    for i, (value, elements) in enumerate(
            itertools.groupby(binary_mask.ravel(order='F'))):
        if i == 0 and value == 1:
            counts.append(0)
        counts.append(len(list(elements)))
    return rle 

最后,我们将两者结合起来以生成 COCO 输出:

def coco_structure(train_df):
    cat_ids = {name: id+1 for id, name in enumerate(
        train_df.cell_type.unique())}
    cats = [{'name': name, 'id': id} for name, id in cat_ids.items()]
    images = [{'id': id, 'width': row.width, 'height': row.height,
               'file_name':f'train/{id}.png'} for id,
               row in train_df.groupby('id').agg('first').iterrows()]
    annotations = []
    for idx, row in tqdm(train_df.iterrows()):
        mk = rle_decode(row.annotation, (row.height, row.width))
        ys, xs = np.where(mk)
        x1, x2 = min(xs), max(xs)
        y1, y2 = min(ys), max(ys)
        enc =binary_mask_to_rle(mk)
        seg = {
            'segmentation':enc, 
            'bbox': [int(x1), int(y1), int(x2-x1+1), int(y2-y1+1)],
            'area': int(np.sum(mk)),
            'image_id':row.id, 
            'category_id':cat_ids[row.cell_type], 
            'iscrowd':0, 
            'id':idx
        }
        annotations.append(seg)
    return {'categories':cats, 'images':images,'annotations':annotations} 

我们将我们的数据分割成非重叠的分割:

train_df = pd.read_csv(CFG.data_path + 'train.csv')
gkf = GroupKFold(n_splits = CFG.nfolds)
train_df["fold"] = -1
y = train_df.width.values
for f, (t_, v_) in enumerate(gkf.split(X=train_df, y=y,
                             groups=train_df.id.values)):
    train_df.loc[v_, "fold"] = f

fold_id = train_df.fold.copy() 

我们现在可以遍历分割:

all_ids = train_df.id.unique()
# For fold in range(CFG.nfolds):
for fold in range(4,5):    
    train_sample = train_df.loc[fold_id != fold]
    root = coco_structure(train_sample)
    with open('annotations_train_f' + str(fold) + 
              '.json', 'w', encoding='utf-8') as f:
        json.dump(root, f, ensure_ascii=True, indent=4)

    valid_sample = train_df.loc[fold_id == fold]
    print('fold ' + str(fold) + ': produced')
for fold in range(4,5):    
    train_sample = train_df.loc[fold_id == fold]
    root = coco_structure(train_sample)
    with open('annotations_valid_f' + str(fold) + 
              '.json', 'w', encoding='utf-8') as f:
        json.dump(root, f, ensure_ascii=True, indent=4)

    valid_sample = train_df.loc[fold_id == fold]
    print('fold ' + str(fold) + ': produced') 

循环必须分块执行的原因是 Kaggle 环境的大小限制:笔记本输出的最大大小限制为 20 GB,5 个分割,每个分割有 2 个文件(训练/验证),总共 10 个 JSON 文件,超过了这个限制。

当在 Kaggle 笔记本中运行代码时,这些实际考虑因素值得记住,尽管对于这种“预备”工作,您当然可以在其他地方产生结果,并在之后将其作为 Kaggle 数据集上传。

在生成分割后,我们可以开始为我们的数据集训练 Detectron2 模型。像往常一样,我们首先加载必要的包:

from datetime import datetime
import os
import pandas as pd
import numpy as np
import pycocotools.mask as mask_util
import detectron2
from pathlib import Path
import random, cv2, os
import matplotlib.pyplot as plt
# Import some common detectron2 utilities
from detectron2 import model_zoo
from detectron2.engine import DefaultPredictor, DefaultTrainer
from detectron2.config import get_cfg
from detectron2.utils.visualizer import Visualizer, ColorMode
from detectron2.data import MetadataCatalog, DatasetCatalog
from detectron2.data.datasets import register_coco_instances
from detectron2.utils.logger import setup_logger
from detectron2.evaluation.evaluator import DatasetEvaluator
from detectron2.engine import BestCheckpointer
from detectron2.checkpoint import DetectionCheckpointer
setup_logger()
import torch 

虽然一开始从 Detectron2 导入的数量可能看起来令人畏惧,但随着我们对任务定义的深入,它们的功能将变得清晰;我们首先指定输入数据文件夹、注释文件夹和定义我们首选模型架构的 YAML 文件路径:

class CFG:
    wfold = 4
    data_folder = '../input/sartorius-cell-instance-segmentation/'
    anno_folder = '../input/sartoriusannotations/'
    model_arch = 'mask_rcnn_R_50_FPN_3x.yaml'
    nof_iters = 10000 
    seed = 45 

这里值得提一下的是迭代参数(nof_iters 上方)。通常,模型训练是以完整遍历训练数据的次数(即周期数)来参数化的。Detectron2 的设计不同:一次迭代指的是一个 mini-batch,模型的不同部分使用不同的 mini-batch 大小。

为了确保结果可重复,我们固定了模型不同组件使用的随机种子:

def seed_everything(seed):
    random.seed(seed)
    os.environ['PYTHONHASHSEED'] = str(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.backends.cudnn.deterministic = True
seed_everything(CFG.seed) 

竞赛指标是不同交并比IoU)阈值下的平均平均精度。作为对 第五章竞赛任务和指标 的回顾,建议的一组对象像素与一组真实对象像素的 IoU 计算如下:

图片 B17574_10_001

该指标遍历一系列 IoU 阈值,在每个点上计算平均精度值。阈值值从 0.5 到 0.95,增量为 0.05。

在每个阈值值处,根据预测对象与所有地面真实对象比较后产生的真正例TP)、假阴性FN)和假阳性FP)的数量计算一个精度值。最后,竞赛指标返回的分数是测试数据集中每个图像的个体平均精度的平均值。

下面,我们定义计算该指标所需的函数,并将其直接用于模型内部作为目标函数:

# Taken from https://www.kaggle.com/theoviel/competition-metric-map-iou
def precision_at(threshold, iou):
    matches = iou > threshold
    true_positives = np.sum(matches, axis=1) == 1  # Correct objects
    false_positives = np.sum(matches, axis=0) == 0  # Missed objects
    false_negatives = np.sum(matches, axis=1) == 0  # Extra objects
    return np.sum(true_positives), np.sum(false_positives),
    np.sum(false_negatives)
def score(pred, targ):
    pred_masks = pred['instances'].pred_masks.cpu().numpy()
    enc_preds = [mask_util.encode(np.asarray(p, order='F'))
                 for p in pred_masks]
    enc_targs = list(map(lambda x:x['segmentation'], targ))
    ious = mask_util.iou(enc_preds, enc_targs, [0]*len(enc_targs))
    prec = []
    for t in np.arange(0.5, 1.0, 0.05):
        tp, fp, fn = precision_at(t, ious)
        p = tp / (tp + fp + fn)
        prec.append(p)
    return np.mean(prec) 

指标定义后,我们可以在模型中使用它:

class MAPIOUEvaluator(DatasetEvaluator):
    def __init__(self, dataset_name):
        dataset_dicts = DatasetCatalog.get(dataset_name)
        self.annotations_cache = {item['image_id']:item['annotations']
                                  for item in dataset_dicts}

    def reset(self):
        self.scores = []
    def process(self, inputs, outputs):
        for inp, out in zip(inputs, outputs):
            if len(out['instances']) == 0:
                self.scores.append(0)    
            else:
                targ = self.annotations_cache[inp['image_id']]
                self.scores.append(score(out, targ))
    def evaluate(self):
        return {"MaP IoU": np.mean(self.scores)} 

这为我们创建 Trainer 对象提供了基础,它是围绕 Detectron2 构建的解决方案的核心:

class Trainer(DefaultTrainer):
    @classmethod
    def build_evaluator(cls, cfg, dataset_name, output_folder=None):
        return MAPIOUEvaluator(dataset_name)
    def build_hooks(self):
        # copy of cfg
        cfg = self.cfg.clone()
        # build the original model hooks
        hooks = super().build_hooks()
        # add the best checkpointer hook
        hooks.insert(-1, BestCheckpointer(cfg.TEST.EVAL_PERIOD, 
                                         DetectionCheckpointer(self.model,
                                         cfg.OUTPUT_DIR),
                                         "MaP IoU",
                                         "max",
                                         ))
        return hooks 

我们现在继续以 Detectron2 风格加载数据集的训练/验证数据:

dataDir=Path(CFG.data_folder)
register_coco_instances('sartorius_train',{}, CFG.anno_folder + 
                        'annotations_train_f' + str(CFG.wfold) + 
                        '.json', dataDir)
register_coco_instances('sartorius_val',{}, CFG.anno_folder + 
                        'annotations_valid_f' + str(CFG.wfold) + 
                        '.json', dataDir)
metadata = MetadataCatalog.get('sartorius_train')
train_ds = DatasetCatalog.get('sartorius_train') 

在我们实例化 Detectron2 模型之前,我们需要注意配置它。大多数值都可以保留为默认值(至少在第一次尝试时是这样);如果你决定进一步调整,从 BATCH_SIZE_PER_IMAGE(为了提高泛化性能)和 SCORE_THRESH_TEST(为了限制假阴性)开始:

cfg = get_cfg()
cfg.INPUT.MASK_FORMAT='bitmask'
cfg.merge_from_file(model_zoo.get_config_file('COCO-InstanceSegmentation/' +
                    CFG.model_arch))
cfg.DATASETS.TRAIN = ("sartorius_train",)
cfg.DATASETS.TEST = ("sartorius_val",)
cfg.DATALOADER.NUM_WORKERS = 2
cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url('COCO-InstanceSegmentation/'
                    + CFG.model_arch)
cfg.SOLVER.IMS_PER_BATCH = 2
cfg.SOLVER.BASE_LR = 0.001
cfg.SOLVER.MAX_ITER = CFG.nof_iters
cfg.SOLVER.STEPS = []
cfg.MODEL.ROI_HEADS.BATCH_SIZE_PER_IMAGE = 512
cfg.MODEL.ROI_HEADS.NUM_CLASSES = 3  
cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = .4
cfg.TEST.EVAL_PERIOD = len(DatasetCatalog.get('sartorius_train')) 
                           // cfg.SOLVER.IMS_PER_BATCH 

训练模型很简单:

os.makedirs(cfg.OUTPUT_DIR, exist_ok=True)
trainer = Trainer(cfg) 
trainer.resume_or_load(resume=False)
trainer.train() 

你会注意到训练过程中的输出含有丰富的关于该过程进度的信息:

包含文本的图片 自动生成的描述

图 10.16:Detectron2 的训练输出

模型训练完成后,我们可以保存权重并用于推理(可能在一个单独的笔记本中 – 参见本章前面的讨论)和提交准备。我们首先添加新的参数,允许我们正则化预测,设置置信度阈值和最小掩码大小:

THRESHOLDS = [.18, .35, .58]
MIN_PIXELS = [75, 150, 75] 

我们需要一个辅助函数来将单个掩码编码成 RLE 格式:

def rle_encode(img):
    '''
    img: numpy array, 1 - mask, 0 - background
    Returns run length as string formatted
    '''
    pixels = img.flatten()
    pixels = np.concatenate([[0], pixels, [0]])
    runs = np.where(pixels[1:] != pixels[:-1])[0] + 1
    runs[1::2] -= runs[::2]
    return ' '.join(str(x) for x in runs) 

下面是生成每张图像所有掩码的主要函数,过滤掉可疑的(置信度分数低于THRESHOLDS)和面积小的(包含的像素少于MIN_PIXELS):

def get_masks(fn, predictor):
    im = cv2.imread(str(fn))
    pred = predictor(im)
    pred_class = torch.mode(pred['instances'].pred_classes)[0]
    take = pred['instances'].scores >= THRESHOLDS[pred_class]
    pred_masks = pred['instances'].pred_masks[take]
    pred_masks = pred_masks.cpu().numpy()
    res = []
    used = np.zeros(im.shape[:2], dtype=int) 
    for mask in pred_masks:
        mask = mask * (1-used)
        # Skip predictions with small area
        if mask.sum() >= MIN_PIXELS[pred_class]:
            used += mask
            res.append(rle_encode(mask))
    return res 

我们然后准备存储图像 ID 和掩码的列表:

dataDir=Path(CFG.data_folder)
ids, masks=[],[]
test_names = (dataDir/'test').ls() 

大型图像集的竞赛——如本节中讨论的——通常需要训练模型超过 9 小时,这是代码竞赛中规定的时限(见www.kaggle.com/docs/competitions)。这意味着在同一笔记本中训练模型和运行推理变得不可能。一个典型的解决方案是首先作为一个独立的笔记本在 Kaggle、Google Colab、GCP 或本地运行训练笔记本/脚本。第一个笔记本的输出(训练好的权重)被用作第二个笔记本的输入,换句话说,用于定义用于预测的模型。

我们通过加载我们训练好的模型的权重以这种方式继续进行:

cfg = get_cfg()
cfg.merge_from_file(model_zoo.get_config_file("COCO-InstanceSegmentation/"+
                    CFG.arch+".yaml"))
cfg.INPUT.MASK_FORMAT = 'bitmask'
cfg.MODEL.ROI_HEADS.NUM_CLASSES = 3 
cfg.MODEL.WEIGHTS = CFG.model_folder + 'model_best_f' + 
                    str(CFG.wfold)+'.pth' 
cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.5
cfg.TEST.DETECTIONS_PER_IMAGE = 1000
predictor = DefaultPredictor(cfg) 

我们可以通过加载我们训练好的模型的权重来可视化一些预测:

encoded_masks = get_masks(test_names[0], predictor)
_, axs = plt.subplots(1,2, figsize = (40, 15))
axs[1].imshow(cv2.imread(str(test_names[0])))
for enc in encoded_masks:
    dec = rle_decode(enc)
axs[0].imshow(np.ma.masked_where(dec == 0, dec)) 

这里有一个例子:

图片 B17574_10_17.png

图 10.17:可视化 Detectron2 的样本预测与源图像

使用上面定义的辅助函数,以 RLE 格式生成提交的掩码非常简单:

for fn in test_names:
    encoded_masks = get_masks(fn, predictor)
    for enc in encoded_masks:
        ids.append(fn.stem)
        masks.append(enc)
pd.DataFrame({'id':ids, 'predicted':masks}).to_csv('submission.csv', 
                                                   index=False)
pd.read_csv('submission.csv').head() 

这里是最终提交的前几行:

包含文本的图像 自动生成的描述

图 10.18:训练好的 Detectron2 模型的格式化提交

我们已经到达了本节的结尾。上面的流程演示了如何设置语义分割模型并进行训练。我们使用了少量迭代,但要达到有竞争力的结果,需要更长时间的训练。

Laura Fink 的图片

Laura Fink

www.kaggle.com/allunia

为了总结本章,让我们看看 Kaggler Laura Fink 对她在平台上的时间的看法。她不仅是 Notebooks 大师,制作了许多精湛的 Notebooks,还是 MicroMata 的数据科学负责人。

你最喜欢的竞赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

我最喜欢的竞赛是那些能为人类带来好处的竞赛。我特别喜欢所有与健康相关的挑战。然而,对我来说,每个竞赛都像一场冒险,有自己的谜题需要解决。我真的很享受学习新技能和探索新的数据集或问题。因此,我并不专注于特定的技术,而是专注于学习新事物。我认为我因在探索性数据分析(EDA)方面的优势而闻名。

你是如何参加 Kaggle 竞赛的?这种方法和你在日常工作中所做的工作有什么不同?

当参加比赛时,我首先阅读问题陈述和数据描述。浏览论坛和公开的笔记本以收集想法后,我通常开始开发自己的解决方案。在初始阶段,我花了一些时间进行 EDA(探索性数据分析)以寻找隐藏的组并获取一些直觉。这有助于设置适当的验证策略,我认为这是所有后续步骤的基础。然后,我开始迭代机器学习管道的不同部分,如特征工程或预处理,改进模型架构,询问数据收集的问题,寻找泄漏,进行更多的 EDA,或构建集成。我试图以贪婪的方式改进我的解决方案。Kaggle 比赛非常动态,需要尝试不同的想法和不同的解决方案才能最终生存下来。

这肯定与我的日常工作不同,我的日常工作更侧重于从数据中获得见解,并找到简单但有效的解决方案来改进业务流程。在这里,任务通常比使用的模型更复杂。要解决的问题必须非常明确地定义,这意味着必须与不同背景的专家讨论应达到的目标,涉及哪些流程,以及数据需要如何收集或融合。与 Kaggle 比赛相比,我的日常工作需要更多的沟通而不是机器学习技能。

告诉我们您参加的一个特别具有挑战性的比赛,以及您使用了哪些见解来应对这项任务。

G2Net 引力波探测比赛是我最喜欢的之一。目标是检测隐藏在来自探测器组件和地球力量的噪声中的模拟引力波信号。在这场比赛中的一个重要见解是,你应该批判性地看待分析数据的标准方法,并尝试自己的想法。在我阅读的论文中,数据主要是通过使用傅里叶变换或常 Q 变换,在数据白化并应用带通滤波器后准备的。

很快就很明显,白化没有帮助,因为它使用了功率谱密度的样条插值,这本身就很嘈杂。将多项式拟合到噪声数据的小子集会增加另一个错误来源,因为过度拟合。

在去除白化之后,我尝试了不同超参数的 Constant-Q 变换,这长期以来一直是论坛和公共 Notebooks 中的领先方法。由于有两种引力波源可以覆盖不同的 Q 值范围,我尝试了一个在这些超参数上有所不同的模型集合。这证明在提高我的分数方面很有帮助,但后来我达到了一个极限。Constant-Q 变换对时间序列应用一系列滤波器,并将它们转换到频域。我开始问自己,是否有一种方法可以以更好、更灵活的方式执行这些滤波任务。就在这时,社区中提出了使用 1 维卷积神经网络的想法,我非常喜欢它。我们都知道,2 维卷积神经网络的滤波器能够根据图像数据检测边缘、线条和纹理。同样,可以使用“经典”滤波器,如拉普拉斯或索贝尔滤波器。因此,我自问:我们能否使用 1 维 CNN 来学习最重要的滤波器,而不是应用某种方式已经固定的变换?

我无法让我的 1 维卷积神经网络解决方案工作,但结果证明许多顶级团队都做得很好。G2Net 比赛是我最喜欢的之一,尽管我错过了赢得奖牌的目标。然而,我在过程中获得的知识和关于所谓标准方法的教训都是非常宝贵的。

Kaggle 是否帮助了你的职业生涯?如果是的话,是如何帮助的?

我在大学毕业后开始了我的第一份工作,成为一名 Java 软件开发者,尽管我在硕士论文期间已经接触过机器学习。我对进行更多数据分析很感兴趣,但在那时,几乎没有什么数据科学的工作,或者它们没有被这样命名。当我第一次听说 Kaggle 时,我立刻被它吸引。从那时起,我经常在晚上去 Kaggle 上玩。当时我并没有打算改变我的职位,但后来有一个研究项目需要机器学习技能。我能够通过在 Kaggle 上参与获得的知识证明我是这个项目的合适人选。这最终成为了我数据科学生涯的起点。

Kaggle 一直是我尝试新想法、学习新方法和工具、获取实践经验的好地方。通过这种方式获得的能力对我在工作中的数据科学项目非常有帮助。这就像是一次知识上的提升,因为 Kaggle 为你提供了一个沙盒,让你可以尝试不同的想法,发挥创造力而无需承担风险。在比赛中失败意味着至少有一个教训可以学习,但项目中的失败可能会对自己和其他人产生巨大的负面影响。

除了参加比赛外,另一种建立你个人作品集的绝佳方式是编写 Notebooks。这样做,你可以向世界展示你如何解决问题,以及如何传达洞察力和结论。当你必须与管理人员、客户和来自不同背景的专家合作时,后者非常重要。

在你的经验中,不经验的 Kagglers 通常忽略了什么?你现在知道什么,而当你最初开始时希望知道的呢?

我认为许多参加比赛的初学者被公开排行榜所吸引,没有良好的验证策略就构建模型。当他们在排行榜上衡量自己的成功时,他们很可能会对公开测试数据进行过度拟合。比赛结束后,他们的模型无法泛化到未见的私有测试数据,他们通常会下降数百名。我仍然记得在梅赛德斯-奔驰绿色制造比赛中,由于我无法爬升公开排行榜,我有多么沮丧。但当最终排名公布时,人们在上榜和下榜之间的波动让我感到惊讶。从那时起,我总是牢记,一个适当的验证方案对于管理欠拟合和过拟合的挑战非常重要。

你在比赛中犯过哪些错误?

我迄今为止犯的最大错误是在比赛开始时花费太多时间和精力在解决方案的细节上。实际上,在建立适当的验证策略之后,快速迭代不同的想法会更好。这样,找到改进的潜在方向更容易、更快,陷入某个地方的危险也小得多。

您会推荐使用哪些特定的工具或库来进行数据分析或机器学习?

在 Kaggle 社区活跃时,你可以学习和实践许多常见的工具和库,我唯一能做的就是推荐它们。保持灵活并了解它们的优缺点很重要。这样,你的解决方案不依赖于你的工具,而更多地依赖于你的想法和创造力。

当人们参加比赛时,他们应该记住或做哪件事是最重要的?

数据科学不是关于构建模型,而是关于理解数据和收集数据的方式。我迄今为止参加的许多比赛都显示了数据泄露或测试数据中隐藏的组,这些组可以通过探索性数据分析找到。

摘要

在本章中,我们从 Kaggle 比赛的角度为您概述了与计算机视觉相关的重要主题。我们介绍了增强,这是一种用于扩展算法泛化能力的技巧的重要类别,随后展示了三个最常见问题的端到端管道:图像分类、目标检测和语义分割。

在下一章中,我们将注意力转向自然语言处理,这是另一个极其广泛且受欢迎的问题类别。

加入我们书籍的 Discord 空间

加入书籍的 Discord 工作空间,每月与作者进行一次 问我任何问题 的活动:

packt.link/KaggleDiscord

第十一章:NLP 建模

自然语言处理NLP)是一个在语言学、计算机科学和人工智能交叉领域的学科。其主要关注点是算法,用于处理和分析大量自然语言数据。在过去的几年里,它已经成为 Kaggle 竞赛中越来越受欢迎的话题。虽然该领域本身非常广泛,包括非常受欢迎的话题,如聊天机器人和机器翻译,但在本章中,我们将专注于 Kaggle 竞赛经常涉及的具体子集。

将情感分析视为一个简单的分类问题非常受欢迎,并且被广泛讨论,因此我们将从对问题的某种更有趣的变体开始:在推文中识别情感支持短语。我们将继续描述一个开放域问答问题的示例解决方案,并以一个关于 NLP 问题增强的章节结束,这是一个比其计算机视觉对应物受到更多关注的主题。

总结来说,我们将涵盖:

  • 情感分析

  • 开放域问答

  • 文本增强策略

情感分析

Twitter 是最受欢迎的社会媒体平台之一,也是许多个人和公司的重要沟通工具。

在后一种情况下,在语言中捕捉情感尤为重要:一条积极的推文可以迅速传播,而一条特别消极的推文可能会造成伤害。由于人类语言很复杂,因此不仅需要决定情感,而且还需要能够调查“如何”:哪些单词实际上导致了情感描述?

我们将通过使用来自推文情感提取竞赛(www.kaggle.com/c/tweet-sentiment-extraction)的数据来展示解决这个问题的一种方法。为了简洁起见,我们省略了以下代码中的导入,但您可以在 GitHub 上本章相应笔记本中找到它们。

为了更好地了解这个问题,让我们先看看数据:

df = pd.read_csv('/kaggle/input/tweet-sentiment-extraction/train.csv')
df.head() 

这里是前几行:

包含桌子的图像 自动生成的描述

图 11.1:训练数据中的样本行

实际的推文存储在text列中。每个推文都有一个相关的sentiment,以及存储在selected_text列中的支持短语(基于情感分配决策的推文部分)。

我们首先定义基本的清理函数。首先,我们想要去除网站 URL 和非字符,并将人们用来代替脏话的星号替换为单个标记,"swear"。我们使用一些正则表达式来帮助我们完成这项工作:

def basic_cleaning(text):
    text=re.sub(r'https?://www\.\S+\.com','',text)
    text=re.sub(r'[^A-Za-z|\s]','',text)
    text=re.sub(r'\*+','swear',text) # Capture swear words that are **** out
    return text 

接下来,我们将从推文的正文中移除 HTML 以及表情符号:

def remove_html(text):
    html=re.compile(r'<.*?>')
    return html.sub(r'',text)
def remove_emoji(text):
    emoji_pattern = re.compile("["
                           u"\U0001F600-\U0001F64F" #emoticons
                           u"\U0001F300-\U0001F5FF" #symbols & pictographs
                           u"\U0001F680-\U0001F6FF" #transport & map symbols
                           u"\U0001F1E0-\U0001F1FF" #flags (iOS)
                           u"\U00002702-\U000027B0"
                           u"\U000024C2-\U0001F251"
                           "]+", flags=re.UNICODE)
    return emoji_pattern.sub(r'', text) 

最后,我们希望能够移除重复的字符(例如,这样我们就有“way”而不是“waaaayyyyy”):

def remove_multiplechars(text):
    text = re.sub(r'(.)\1{3,}',r'\1', text)
    return text 

为了方便,我们将四个函数组合成一个单一的清理函数:

def clean(df):
    for col in ['text']:#,'selected_text']:
        df[col]=df[col].astype(str).apply(lambda x:basic_cleaning(x))
        df[col]=df[col].astype(str).apply(lambda x:remove_emoji(x))
        df[col]=df[col].astype(str).apply(lambda x:remove_html(x))
        df[col]=df[col].astype(str).apply(lambda x:remove_multiplechars(x))
    return df 

最后的准备涉及编写基于预训练模型(tokenizer参数)创建嵌入的函数:

def fast_encode(texts, tokenizer, chunk_size=256, maxlen=128):
    tokenizer.enable_truncation(max_length=maxlen)
    tokenizer.enable_padding(max_length=maxlen)
    all_ids = []

    for i in range(0, len(texts), chunk_size):
        text_chunk = texts[i:i+chunk_size].tolist()
        encs = tokenizer.encode_batch(text_chunk)
        all_ids.extend([enc.ids for enc in encs])

    return np.array(all_ids) 

接下来,我们创建一个预处理函数,使我们能够处理整个语料库:

def preprocess_news(df,stop=stop,n=1,col='text'):
    '''Function to preprocess and create corpus'''
    new_corpus=[]
    stem=PorterStemmer()
    lem=WordNetLemmatizer()
    for text in df[col]:
        words=[w for w in word_tokenize(text) if (w not in stop)]

        words=[lem.lemmatize(w) for w in words if(len(w)>n)]

        new_corpus.append(words)

    new_corpus=[word for l in new_corpus for word in l]
    return new_corpus 

使用我们之前准备好的函数,我们可以清理和准备训练数据。sentiment列是我们的目标,我们将其转换为虚拟变量(独热编码)以提高性能:

df.dropna(inplace=True)
df_clean = clean(df)
df_clean_selection = df_clean.sample(frac=1)
X = df_clean_selection.text.values
y = pd.get_dummies(df_clean_selection.sentiment) 

下一个必要的步骤是对输入文本进行分词,以及将它们转换为序列(包括填充,以确保数据集跨数据集的长度相等):

tokenizer = text.Tokenizer(num_words=20000)
tokenizer.fit_on_texts(list(X))
list_tokenized_train = tokenizer.texts_to_sequences(X)
X_t = sequence.pad_sequences(list_tokenized_train, maxlen=128) 

我们将使用DistilBERT为我们的模型创建嵌入,并直接使用它们。DistilBERT 是 BERT 的一个轻量级版本:权衡是参数减少 40%时的性能损失 3%。我们可以训练嵌入层并提高性能——但代价是大幅增加的训练时间。

tokenizer = transformers.AutoTokenizer.from_pretrained("distilbert-base-uncased")  
# Save the loaded tokenizer locally
save_path = '/kaggle/working/distilbert_base_uncased/'
if not os.path.exists(save_path):
    os.makedirs(save_path)
tokenizer.save_pretrained(save_path)
# Reload it with the huggingface tokenizers library
fast_tokenizer = BertWordPieceTokenizer(
                 'distilbert_base_uncased/vocab.txt', lowercase=True)
fast_tokenizer 

我们可以使用之前定义的fast_encode函数,以及上面定义的fast_tokenizer,来编码推文:

X = fast_encode(df_clean_selection.text.astype(str),
                fast_tokenizer,
                maxlen=128) 

数据准备就绪后,我们可以构建模型。为了这次演示,我们将采用这些应用中相当标准的架构:结合 LSTM 层、通过全局池化和 dropout 归一化,以及顶部的密集层。为了实现真正有竞争力的解决方案,需要对架构进行一些调整:一个“更重”的模型,更大的嵌入,LSTM 层中的更多单元,等等。

transformer_layer = transformers.TFDistilBertModel.from_pretrained('distilbert-base-uncased')
embedding_size = 128
input_ = Input(shape=(100,))
inp = Input(shape=(128, ))
embedding_matrix=transformer_layer.weights[0].numpy()
x = Embedding(embedding_matrix.shape[0],
              embedding_matrix.shape[1],
              embeddings_initializer=Constant(embedding_matrix),
              trainable=False)(inp)
x = Bidirectional(LSTM(50, return_sequences=True))(x)
x = Bidirectional(LSTM(25, return_sequences=True))(x)
x = GlobalMaxPool1D()(x)
x = Dropout(0.5)(x)
x = Dense(50, activation='relu', kernel_regularizer='L1L2')(x)
x = Dropout(0.5)(x)
x = Dense(3, activation='softmax')(x)
model_DistilBert = Model(inputs=[inp], outputs=x)
model_DistilBert.compile(loss='categorical_crossentropy',
                              optimizer='adam',
                              metrics=['accuracy']) 

对于数据的时序维度没有特殊需要关注,所以我们满足于将数据随机分为训练集和验证集,这可以在调用fit方法内部实现:

model_DistilBert.fit(X,y,batch_size=32,epochs=10,validation_split=0.1) 

以下是一些示例输出:

Epoch 1/10
27480/27480 [==============================] - 480s 17ms/step - loss: 0.5100 - accuracy: 0.7994
Epoch 2/10
27480/27480 [==============================] - 479s 17ms/step - loss: 0.4956 - accuracy: 0.8100
Epoch 3/10
27480/27480 [==============================] - 475s 17ms/step - loss: 0.4740 - accuracy: 0.8158
Epoch 4/10
27480/27480 [==============================] - 475s 17ms/step - loss: 0.4528 - accuracy: 0.8275
Epoch 5/10
27480/27480 [==============================] - 475s 17ms/step - loss: 0.4318 - accuracy: 0.8364
Epoch 6/10
27480/27480 [==============================] - 475s 17ms/step - loss: 0.4069 - accuracy: 0.8441
Epoch 7/10
27480/27480 [==============================] - 477s 17ms/step - loss: 0.3839 - accuracy: 0.8572 

从拟合的模型生成预测的过程是直接的。为了利用所有可用数据,我们首先在所有可用数据上重新训练我们的模型(因此没有验证):

df_clean_final = df_clean.sample(frac=1)
X_train = fast_encode(df_clean_selection.text.astype(str),
                      fast_tokenizer,
                      maxlen=128)
y_train = y 

在生成预测之前,我们在整个数据集上重新拟合了模型:

Adam_name = adam(lr=0.001)
model_DistilBert.compile(loss='categorical_crossentropy',optimizer=Adam_name,metrics=['accuracy'])
history = model_DistilBert.fit(X_train,y_train,batch_size=32,epochs=10) 

我们下一步是将测试数据处理成与用于模型训练数据相同的格式:

df_test = pd.read_csv('/kaggle/input/tweet-sentiment-extraction/test.csv')
df_test.dropna(inplace=True)
df_clean_test = clean(df_test)
X_test = fast_encode(df_clean_test.text.values.astype(str),
                     fast_tokenizer,
                     maxlen=128)
y_test = df_clean_test.sentiment 

最后,我们生成预测:

y_preds = model_DistilBert.predict(X_test)
y_predictions = pd.DataFrame(y_preds,
                             columns=['negative','neutral','positive'])
y_predictions_final = y_predictions.idxmax(axis=1)
accuracy = accuracy_score(y_test,y_predictions_final)
print(f"The final model shows {accuracy:.2f} accuracy on the test set.") 

最终模型在测试集上的准确率为0.74。以下我们展示了一些输出样例;正如您从这些几行中已经可以看到的,有些情况下情感对人类读者来说很明显,但模型未能捕捉到:

包含桌子的图像 自动生成的描述

图 11.2:预测结果中的示例行

我们现在已经演示了一个用于解决情感归属问题的样本管道(识别导致标注者在情感分类决策中做出决定的文本部分)。如果您想实现有竞争力的性能,以下是一些可以进行的改进,按可能的影响顺序排列:

  • 更大的嵌入:这使我们能够在(处理过的)输入数据级别上捕获更多信息

  • 更大的模型:LSTM 层中的单元更多

  • 更长的训练:换句话说,更多的 epoch

虽然上述改进无疑会提高模型的性能,但我们管道的核心元素是可重用的:

  • 数据清洗和预处理

  • 创建文本嵌入

  • 在目标模型架构中结合循环层和正则化

我们现在将讨论开放域问答,这是在 NLP 竞赛中经常遇到的问题。

Abhishek Thakur

www.kaggle.com/abhishek

我们采访了 Abhishek Thakur,他是世界上第一位四重 Kaggle 大师。他目前在 Hugging Face 工作,在那里他正在构建 AutoNLP;他还写了几乎唯一一本关于 Kaggle 的英文书籍(除了这本书之外!)接近(几乎)任何机器学习问题

你在 Kaggle 上的专长是什么?

没有。每个竞赛都不同,从每个竞赛中都可以学到很多东西。如果我要有一个专长,我会在那个领域的所有竞赛中获胜。

你是如何处理 Kaggle 竞赛的?这种处理方式与你在日常工作中所做的是否不同?

我首先会查看数据,并试图理解它。如果我在竞赛中落后了,我会寻求公共 EDA 核的帮助。

当我接近 Kaggle(或非 Kaggle)上的问题时,我首先会建立一个基准。建立基准非常重要,因为它为你提供了一个可以比较未来模型的基准。如果我在建立基准方面落后了,我会尽量避免使用公共 Notebooks。如果我们那样做,我们只会朝一个方向思考。至少,我感觉是这样的。

当我完成一个基准测试后,我会尽量在不做任何复杂操作,比如堆叠或混合的情况下,尽可能多地提取信息。然后我会再次检查数据和模型,并尝试逐步改进基准。

日常工作中有时有很多相似之处。大多数时候都有一个基准,然后你必须提出技术、特征、模型来击败基准。

你参加的最有趣的竞赛是什么?你有什么特别的见解吗?

每个竞赛都很吸引人。

Kaggle 是否帮助你在职业生涯中取得进展?

当然,它有帮助。在过去的几年里,Kaggle 在招聘数据科学家和机器学习工程师方面赢得了非常好的声誉。Kaggle 排名和与许多数据集的经验无疑在行业中以某种方式有所帮助。你越熟悉处理不同类型的问题,你迭代的速度就越快。这在行业中是非常有用的。没有人愿意花几个月的时间做对业务没有任何价值的事情。

在您的经验中,不经验丰富的 Kagglers 通常忽略什么?您现在知道什么,而您希望自己在最初开始时就知道?

大多数初学者很容易放弃。加入 Kaggle 竞赛并受到顶尖选手的威胁是非常容易的。如果初学者想在 Kaggle 上成功,他们必须要有毅力。在我看来,毅力是关键。许多初学者也未能独立开始,而是一直坚持使用公共内核。这使得他们像公共内核的作者一样思考。我的建议是先从自己的竞赛开始,查看数据,构建特征,构建模型,然后深入内核和讨论,看看其他人可能有什么不同的做法。然后,将您所学到的知识融入到自己的解决方案中。

开放域问答

在本节中,我们将探讨Google QUEST Q&A Labeling竞赛(www.kaggle.com/c/google-quest-challenge/overview/description)。在这个竞赛中,问题-答案对由人类评分员根据一系列标准进行评估,例如“问题对话性”、“问题求证事实”或“答案有帮助”。任务是预测每个目标列(对应于标准)的数值;由于标签是跨多个评分员汇总的,因此目标列实际上是一个多元回归输出,目标列被归一化到单位范围。

在使用高级技术(如基于 transformer 的 NLP 模型)进行建模之前,通常使用更简单的方法建立一个基线是一个好主意。与上一节一样,为了简洁,我们将省略导入部分,但您可以在 GitHub 仓库中的笔记本中找到它们。

我们首先定义几个辅助函数,这些函数可以帮助我们提取文本的不同方面。首先,一个函数将输出给定字符串的词数:

def word_count(xstring):
    return xstring.split().str.len() 

竞赛中使用的指标是斯皮尔曼相关系数(在排名上计算的线性相关系数:en.wikipedia.org/wiki/Spearman%27s_rank_correlation_coefficient)。

由于我们打算构建一个 Scikit-learn 管道,定义一个指标作为评分器是有用的(make_scorer方法是一个 Scikit-learn 的包装器,它接受一个评分函数——如准确度或 MSE——并返回一个可调用的对象,该对象对估计器的输出进行评分):

def spearman_corr(y_true, y_pred):
        if np.ndim(y_pred) == 2:
            corr = np.mean([stats.spearmanr(y_true[:, i],
                                            y_pred[:, i])[0]
for i in range(y_true.shape[1])])
        else:
            corr = stats.spearmanr(y_true, y_pred)[0]
        return corr

custom_scorer = make_scorer(spearman_corr, greater_is_better=True) 

接下来,一个小型的辅助函数,用于从l中提取大小为n的连续块。这将帮助我们稍后在不遇到内存问题时为我们的文本生成嵌入:

def chunks(l, n):
    for i in range(0, len(l), n):
        yield l[i:i + n] 

我们将使用的特征集的一部分是来自预训练模型的嵌入。回想一下,本节的想法是构建一个不训练复杂模型的基线,但这并不妨碍我们使用现有的模型。

我们首先导入分词器和模型,然后以块的形式处理语料库,将每个问题/答案编码为固定大小的嵌入:

def fetch_vectors(string_list, batch_size=64):
    # Inspired by https://jalammar.github.io/a-visual-guide-to-using-bert-    for-the-first-time/
    DEVICE = torch.device("cuda")
    tokenizer = transformers.DistilBertTokenizer.from_pretrained
                    ("../input/distilbertbaseuncased/")
    model = transformers.DistilBertModel.from_pretrained
                ("../input/distilbertbaseuncased/")
    model.to(DEVICE)
    fin_features = []
    for data in chunks(string_list, batch_size):
        tokenized = []
        for x in data:
            x = " ".join(x.strip().split()[:300])
            tok = tokenizer.encode(x, add_special_tokens=True)
            tokenized.append(tok[:512])
        max_len = 512
        padded = np.array([i + [0] * (max_len - len(i)) for i in tokenized])
        attention_mask = np.where(padded != 0, 1, 0)
        input_ids = torch.tensor(padded).to(DEVICE)
        attention_mask = torch.tensor(attention_mask).to(DEVICE)
        with torch.no_grad():
            last_hidden_states = model(input_ids,
                                       attention_mask=attention_mask)
        features = last_hidden_states[0][:, 0, :].cpu().numpy()
        fin_features.append(features)
    fin_features = np.vstack(fin_features)
    return fin_features 

我们现在可以继续加载数据:

xtrain = pd.read_csv(data_dir + 'train.csv')
xtest = pd.read_csv(data_dir + 'test.csv')
xtrain.head(4) 

这里是前几行:

包含桌子的图片 自动生成的描述

图 11.3:训练数据中的样本行

我们指定了我们感兴趣的 30 个目标列:

target_cols = ['question_asker_intent_understanding',
               'question_body_critical', 
               'question_conversational', 'question_expect_short_answer', 
               'question_fact_seeking',
               'question_has_commonly_accepted_answer', 
               'question_interestingness_others',
               'question_interestingness_self', 
               'question_multi_intent', 'question_not_really_a_question', 
               'question_opinion_seeking', 'question_type_choice', 
               'question_type_compare', 'question_type_consequence', 
               'question_type_definition', 'question_type_entity', 
               'question_type_instructions', 'question_type_procedure', 
               'question_type_reason_explanation',
               'question_type_spelling', 
               'question_well_written', 'answer_helpful', 
               'answer_level_of_information', 'answer_plausible', 
               'answer_relevance', 'answer_satisfaction', 
               'answer_type_instructions', 'answer_type_procedure', 
               'answer_type_reason_explanation', 'answer_well_written'] 

关于它们的意义和解释的讨论,读者可以参考竞赛的数据页面,在www.kaggle.com/c/google-quest-challenge/data

接下来,我们进行特征工程。我们首先计算问题标题和正文以及答案中的单词数量。这是一个简单但出人意料的有用特征,在许多应用中都很受欢迎:

for colname in ['question_title', 'question_body', 'answer']:
    newname = colname + '_word_len'

    xtrain[newname] = xtrain[colname].str.split().str.len()
    xtest[newname] = xtest[colname].str.split().str.len() 

我们创建的下一个特征是词汇多样性,计算文本块中独特单词的比例:

colname = 'answer'
xtrain[colname+'_div'] = xtrain[colname].apply
                         (lambda s: len(set(s.split())) / len(s.split()) )
xtest[colname+'_div'] = xtest[colname].apply
                        (lambda s: len(set(s.split())) / len(s.split()) ) 

当处理来自在线的信息时,我们可以通过检查网站地址的组成部分(我们将组成部分定义为由点分隔的地址元素)来提取可能的信息性特征;我们计算组成部分的数量,并将单个组成部分作为特征存储:

for df in [xtrain, xtest]:
    df['domcom'] = df['question_user_page'].apply
                   (lambda s: s.split('://')[1].split('/')[0].split('.'))
    # Count components
    df['dom_cnt'] = df['domcom'].apply(lambda s: len(s))
    # Pad the length in case some domains have fewer components in the name
    df['domcom'] = df['domcom'].apply(lambda s: s + ['none', 'none'])
    # Components
    for ii in range(0,4):
        df['dom_'+str(ii)] = df['domcom'].apply(lambda s: s[ii]) 

许多目标列处理的是答案对于给定问题的相关性。一种量化这种关系的方法是评估字符串对中的共享单词

# Shared elements
for df in [xtrain, xtest]:
    df['q_words'] = df['question_body'].apply(lambda s: [f for f in s.split() if f not in eng_stopwords] )
    df['a_words'] = df['answer'].apply(lambda s: [f for f in s.split() if f not in eng_stopwords] )
    df['qa_word_overlap'] = df.apply(lambda s: len(np.intersect1d(s['q_words'], s['a_words'])), axis = 1)
    df['qa_word_overlap_norm1'] = df.apply(lambda s: s['qa_word_overlap']/(1 + len(s['a_words'])), axis = 1)
    df['qa_word_overlap_norm2'] = df.apply(lambda s: s['qa_word_overlap']/(1 + len(s['q_words'])), axis = 1)
    df.drop(['q_words', 'a_words'], axis = 1, inplace = True) 

停用词和标点符号的出现模式可以告诉我们一些关于风格和意图的信息:

for df in [xtrain, xtest]:

    ## Number of characters in the text ##
    df["question_title_num_chars"] = df["question_title"].apply(lambda x: len(str(x)))
    df["question_body_num_chars"] = df["question_body"].apply(lambda x: len(str(x)))
    df["answer_num_chars"] = df["answer"].apply(lambda x: len(str(x)))
    ## Number of stopwords in the text ##
    df["question_title_num_stopwords"] = df["question_title"].apply(lambda x: len([w for w in str(x).lower().split() if w in eng_stopwords]))
    df["question_body_num_stopwords"] = df["question_body"].apply(lambda x: len([w for w in str(x).lower().split() if w in eng_stopwords]))
    df["answer_num_stopwords"] = df["answer"].apply(lambda x: len([w for w in str(x).lower().split() if w in eng_stopwords]))
    ## Number of punctuations in the text ##
    df["question_title_num_punctuations"] =df['question_title'].apply(lambda x: len([c for c in str(x) if c in string.punctuation]) )
    df["question_body_num_punctuations"] =df['question_body'].apply(lambda x: len([c for c in str(x) if c in string.punctuation]) )
    df["answer_num_punctuations"] =df['answer'].apply(lambda x: len([c for c in str(x) if c in string.punctuation]) )
    ## Number of title case words in the text ##
    df["question_title_num_words_upper"] = df["question_title"].apply(lambda x: len([w for w in str(x).split() if w.isupper()]))
    df["question_body_num_words_upper"] = df["question_body"].apply(lambda x: len([w for w in str(x).split() if w.isupper()]))
    df["answer_num_words_upper"] = df["answer"].apply(lambda x: len([w for w in str(x).split() if w.isupper()])) 

在准备“复古”特征时——我们的重点是文本的简单汇总统计,而不关注语义结构——我们可以继续为问题和答案创建嵌入。理论上,我们可以在我们的数据上训练一个单独的 word2vec 类型的模型(或者微调现有的一个),但为了这次演示,我们将直接使用预训练的模型。一个有用的选择是来自 Google 的通用句子编码器(tfhub.dev/google/universal-sentence-encoder/4)。这个模型在多种数据源上进行了训练。它接受一段英文文本作为输入,并输出一个 512 维度的向量。

module_url = "../input/universalsentenceencoderlarge4/"
embed = hub.load(module_url) 

将文本字段转换为嵌入的代码如下:我们按批处理遍历训练/测试集中的条目,为每个批次嵌入(出于内存效率的考虑),然后将它们附加到原始列表中。

最终的数据框是通过垂直堆叠每个批次级别的嵌入列表构建的:

embeddings_train = {}
embeddings_test = {}
for text in ['question_title', 'question_body', 'answer']:
    train_text = xtrain[text].str.replace('?', '.').str.replace('!', '.').tolist()
    test_text = xtest[text].str.replace('?', '.').str.replace('!', '.').tolist()

    curr_train_emb = []
    curr_test_emb = []
    batch_size = 4
    ind = 0
    while ind*batch_size < len(train_text):
        curr_train_emb.append(embed(train_text[ind*batch_size: (ind + 1)*batch_size])["outputs"].numpy())
        ind += 1

    ind = 0
    while ind*batch_size < len(test_text):
        curr_test_emb.append(embed(test_text[ind*batch_size: (ind + 1)*batch_size])["outputs"].numpy())
        ind += 1    

    embeddings_train[text + '_embedding'] = np.vstack(curr_train_emb)
    embeddings_test[text + '_embedding'] = np.vstack(curr_test_emb)
    print(text) 

给定问题和答案的向量表示,我们可以通过在向量对上使用不同的距离度量来计算字段之间的语义相似度。尝试不同度量的背后的想法是希望捕捉到各种类型的特征;在分类的背景下,这可以类比为使用准确性和熵来全面了解情况:

l2_dist = lambda x, y: np.power(x - y, 2).sum(axis=1)
cos_dist = lambda x, y: (x*y).sum(axis=1)
dist_features_train = np.array([
    l2_dist(embeddings_train['question_title_embedding'], embeddings_train['answer_embedding']),
    l2_dist(embeddings_train['question_body_embedding'], embeddings_train['answer_embedding']),
    l2_dist(embeddings_train['question_body_embedding'], embeddings_train['question_title_embedding']),
    cos_dist(embeddings_train['question_title_embedding'], embeddings_train['answer_embedding']),
    cos_dist(embeddings_train['question_body_embedding'], embeddings_train['answer_embedding']),
    cos_dist(embeddings_train['question_body_embedding'], embeddings_train['question_title_embedding'])
]).T
dist_features_test = np.array([
    l2_dist(embeddings_test['question_title_embedding'], embeddings_test['answer_embedding']),
    l2_dist(embeddings_test['question_body_embedding'], embeddings_test['answer_embedding']),
    l2_dist(embeddings_test['question_body_embedding'], embeddings_test['question_title_embedding']),
    cos_dist(embeddings_test['question_title_embedding'], embeddings_test['answer_embedding']),
    cos_dist(embeddings_test['question_body_embedding'], embeddings_test['answer_embedding']),
    cos_dist(embeddings_test['question_body_embedding'], embeddings_test['question_title_embedding'])
]).T 

让我们分别收集距离特征到不同的列中:

for ii in range(0,6):
    xtrain['dist'+str(ii)] = dist_features_train[:,ii]
    xtest['dist'+str(ii)] = dist_features_test[:,ii] 

最后,我们还可以创建文本字段的TF-IDF表示;一般想法是创建基于输入文本多种变换的多个特征,然后将它们输入到一个相对简单的模型中。

这样,我们可以在不拟合复杂深度学习模型的情况下捕获数据的特征。

我们可以通过分析文本的词和字符级别来实现这一点。为了限制内存消耗,我们对这两种类型的特征的最大数量设置上限(你的里程可能会有所不同;如果有更多的内存,这些限制可以增加):

limit_char = 5000
limit_word = 25000 

我们实例化了字符级和词级向量器。我们问题的设置使得 Scikit-learn 的Pipeline功能的使用变得方便,允许在模型拟合过程中组合多个步骤。我们首先为标题列创建两个单独的转换器(词级和字符级):

title_col = 'question_title'
title_transformer = Pipeline([
    ('tfidf', TfidfVectorizer(lowercase = False, max_df = 0.3, min_df = 1,
                             binary = False, use_idf = True, smooth_idf = False,
                             ngram_range = (1,2), stop_words = 'english', 
                             token_pattern = '(?u)\\b\\w+\\b' , max_features = limit_word ))
])
title_transformer2 = Pipeline([
 ('tfidf2',  TfidfVectorizer(sublinear_tf=True,
    strip_accents='unicode', analyzer='char',
    stop_words='english', ngram_range=(1, 4), max_features= limit_char))   
]) 

我们对正文使用相同的逻辑(两个不同的管道转换器):

body_col = 'question_body'
body_transformer = Pipeline([
    ('tfidf',TfidfVectorizer(lowercase = False, max_df = 0.3, min_df = 1,
                             binary = False, use_idf = True, smooth_idf = False,
                             ngram_range = (1,2), stop_words = 'english', 
                             token_pattern = '(?u)\\b\\w+\\b' , max_features = limit_word ))
])
body_transformer2 = Pipeline([
 ('tfidf2',  TfidfVectorizer( sublinear_tf=True,
    strip_accents='unicode', analyzer='char',
    stop_words='english', ngram_range=(1, 4), max_features= limit_char))   
]) 

最后,对于答案列:

answer_col = 'answer'
answer_transformer = Pipeline([
    ('tfidf', TfidfVectorizer(lowercase = False, max_df = 0.3, min_df = 1,
                             binary = False, use_idf = True, smooth_idf = False,
                             ngram_range = (1,2), stop_words = 'english', 
                             token_pattern = '(?u)\\b\\w+\\b' , max_features = limit_word ))
])
answer_transformer2 = Pipeline([
 ('tfidf2',  TfidfVectorizer( sublinear_tf=True,
    strip_accents='unicode', analyzer='char',
    stop_words='english', ngram_range=(1, 4), max_features= limit_char))   
]) 

我们通过处理数值特征来结束特征工程部分。我们只使用简单的方法:用缺失值插补处理 N/A 值,并使用幂转换器来稳定分布并使其更接近高斯分布(如果你在神经网络中使用数值特征,这通常很有帮助):

num_cols = [
    'question_title_word_len', 'question_body_word_len',
    'answer_word_len', 'answer_div',
    'question_title_num_chars','question_body_num_chars',
    'answer_num_chars',
    'question_title_num_stopwords','question_body_num_stopwords',
    'answer_num_stopwords',
    'question_title_num_punctuations',
    'question_body_num_punctuations','answer_num_punctuations',
    'question_title_num_words_upper',
    'question_body_num_words_upper','answer_num_words_upper',
    'dist0', 'dist1', 'dist2', 'dist3', 'dist4',       'dist5'
]
num_transformer = Pipeline([
    ('impute', SimpleImputer(strategy='constant', fill_value=0)),
    ('scale', PowerTransformer(method='yeo-johnson'))
]) 

Pipelines 的一个有用特性是它们可以被组合和嵌套。接下来,我们添加处理分类变量的功能,然后将所有这些整合到一个ColumnTransformer对象中,以简化数据预处理和特征工程逻辑。输入的每个部分都可以以适当的方式处理:

cat_cols = [ 'dom_0',  'dom_1', 'dom_2', 
    'dom_3', 'category','is_question_no_name_user',
    'is_answer_no_name_user','dom_cnt'
]
cat_transformer = Pipeline([
    ('impute', SimpleImputer(strategy='constant', fill_value='')),
    ('encode', OneHotEncoder(handle_unknown='ignore'))
])
preprocessor = ColumnTransformer(
    transformers = [
        ('title', title_transformer, title_col),
        ('title2', title_transformer2, title_col),
        ('body', body_transformer, body_col),
        ('body2', body_transformer2, body_col),
        ('answer', answer_transformer, answer_col),
        ('answer2', answer_transformer2, answer_col),
        ('num', num_transformer, num_cols),
        ('cat', cat_transformer, cat_cols)
    ]
) 

最后,我们准备好使用一个结合预处理和模型拟合的Pipeline对象:

pipeline = Pipeline([
    ('preprocessor', preprocessor),
    ('estimator',Ridge(random_state=RANDOM_STATE))
]) 

总是评估你的模型在样本外的性能是一个好主意:一个方便的做法是创建折叠外预测,这在第六章中已经讨论过。该过程涉及以下步骤:

  1. 将数据分成折叠。在我们的案例中,我们使用GroupKFold,因为一个问题可以有多个答案(在数据框的单独行中)。为了防止信息泄露,我们想要确保每个问题只出现在一个折叠中。

  2. 对于每个折叠,使用其他折叠中的数据训练模型,并为选择的折叠以及测试集生成预测。

  3. 对测试集上的预测进行平均。

我们开始准备“存储”矩阵,我们将在这里存储预测。mvalid将包含折叠外的预测,而mfull是整个测试集预测的占位符,这些预测在折叠间平均。由于几个问题包含多个候选答案,我们在question_body上对KFold分割进行分层:

nfolds = 5
mvalid = np.zeros((xtrain.shape[0], len(target_cols)))
mfull = np.zeros((xtest.shape[0], len(target_cols)))
kf = GroupKFold(n_splits= nfolds).split(X=xtrain.question_body, groups=xtrain.question_body) 

我们遍历折叠并构建单独的模型:

for ind, (train_index, test_index) in enumerate(kf):

    # Split the data into training and validation
    x0, x1 = xtrain.loc[train_index], xtrain.loc[test_index]
    y0, y1 = ytrain.loc[train_index], ytrain.loc[test_index]
    for ii in range(0, ytrain.shape[1]):
        # Fit model
        be = clone(pipeline)
        be.fit(x0, np.array(y0)[:,ii])
        filename = 'ridge_f' + str(ind) + '_c' + str(ii) + '.pkl'
        pickle.dump(be, open(filename, 'wb'))

        # Storage matrices for the OOF and test predictions, respectively
        mvalid[test_index, ii] = be.predict(x1)
        mfull[:,ii] += be.predict(xtest)/nfolds

    print('---') 

一旦拟合部分完成,我们就可以根据竞赛中指定的指标来评估性能:

corvec = np.zeros((ytrain.shape[1],1))
for ii in range(0, ytrain.shape[1]):
    mvalid[:,ii] = rankdata(mvalid[:,ii])/mvalid.shape[0]
    mfull[:,ii] = rankdata(mfull[:,ii])/mfull.shape[0]

    corvec[ii] = stats.spearmanr(ytrain[ytrain.columns[ii]], mvalid[:,ii])[0]

print(corvec.mean()) 

最终得分是 0.34,作为一个起点来说相当可以接受。

在本节中,我们展示了如何在文本体上构建描述性特征。虽然这并不是 NLP 竞赛的获胜公式(得分尚可,但并不能保证进入奖牌区),但它是一个值得保留在工具箱中的有用工具。我们以一个概述文本增强技术的章节来结束这一章。

Shotaro Ishihara

www.kaggle.com/sishihara

我们这一章节的第二位访谈对象是 Shotaro Ishihara,别名 u++,他是PetFinder.my Adoption Prediction竞赛获胜团队的成员,同时也是一位竞赛和笔记大师。他目前是一家日本新闻媒体公司的数据科学家和研究员,并在日本出版了关于 Kaggle 的书籍,包括 Abhishek Thakur 书籍的翻译。他还维护着一个关于 Kaggle 活动的每周日文通讯 (www.getrevue.co/profile/upura)).

我们在哪里可以找到你写的/翻译的 Kaggle 书籍?

www.kspub.co.jp/book/detail/5190067.html 是基于Titanic GettingStarted竞赛的 Kaggle 入门指南。

book.mynavi.jp/ec/products/detail/id=123641 是 Abhishek Thakur 的《Approaching (Almost) Any Machine Learning Problem》的日文翻译。

你最喜欢的竞赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

在 Kaggle,我喜欢参加那些包含表格或文本数据集的竞赛。这类数据集对我来说很熟悉,因为它们在新闻媒体公司中广泛使用。我对处理这些数据集的方法有很好的了解。

你是如何参加 Kaggle 竞赛的?这种方法和你在日常工作中所做的方法有何不同?

第一个过程是相同的:通过探索性数据分析来思考如何解决这个问题。Kaggle 假设会使用高级机器学习,但在商业中并非如此。在实践中,我试图找到避免使用机器学习的方法。即使我确实使用了它,我也更愿意使用 TF-IDF 和线性回归等经典方法,而不是 BERT 等高级方法。

我们对了解如何避免在现实世界问题中使用机器学习感兴趣。你能给我们举一些例子吗?

在工作上处理自动文章摘要时,我们采用了一种更直接的提取方法(www.jstage.jst.go.jp/article/pjsai/JSAI2021/0/JSAI2021_1D2OS3a03/_article/-char/en),而不是基于神经网络的方法(www.jstage.jst.go.jp/article/pjsai/JSAI2021/0/JSAI2021_1D4OS3c02/_article/-char/en)。

使用机器学习很难保证 100%的性能,有时人们更倾向于选择简单的方法,这些方法易于人类理解和参与。

请告诉我们你参加的一个特别具有挑战性的竞赛,以及你用来解决这个任务的见解。

PetFinder.my Adoption Prediction竞赛中,提供了一个多模态数据集。许多参赛者试图探索和使用所有类型的数据,主要方法是从图像和文本中提取特征,将它们连接起来,并训练 LightGBM。我也采用了同样的方法。令人惊讶的是,我的一个队友 takuoko(www.kaggle.com/takuok)开发了一个处理所有数据集端到端的大神经网络。设计良好的神经网络有可能在多模态竞赛中优于 LightGBM。这是我在 2019 年学到的一课。

这个教训今天仍然有效吗?

我认为答案是肯定的。与 2019 年相比,神经网络在处理多模态数据方面越来越好。

Kaggle 是否对你的职业生涯有所帮助?如果是的话,是如何帮助的?

是的。Kaggle 给了我很多数据分析的经验。我从 Kaggle 获得的人工智能知识极大地帮助我更成功地工作。我在 Kaggle 和商业工作中的成就是我获得 2020 年国际新闻媒体协会颁发的 30 Under 30 奖项和大奖的主要原因之一。Kaggle 还让我结识了许多人。这些关系无疑对我的职业发展做出了贡献。

你是如何通过 Kaggle 建立起你的作品集的?

学习到的技能、取得的竞赛结果以及发布的 Notebooks、书籍、通讯等。

你是如何推广你的发布的?

我拥有多种沟通渠道,并使用适当的工具进行推广。例如,Twitter、个人博客和 YouTube。

在你的经验中,没有经验的 Kagglers 通常忽略了什么?你现在知道什么,而你在最初开始时希望知道的?

探索性数据分析的重要性。在机器学习领域,有一个无免费午餐定理的概念。我们不仅应该学习算法,还应该学习如何应对挑战。无免费午餐定理是一个声明,即没有一种通用的模型能够在所有问题上都表现良好。

在机器学习竞赛中,找到适合数据集和任务特性的模型对于提高你的分数至关重要。

你在过去比赛中犯过哪些错误?

过度拟合到公共排行榜。在 LANL 地震预测 竞赛中,我在公共排行榜上得分很高,最终以第五名的成绩完成了比赛。然而,我的最终排名是 211^(th),这意味着我过于相信了一个有限的数据集。过度拟合是机器学习中一个非常流行的概念,我在 Kaggle 的痛苦经历中意识到了这一点的重要性。

你建议采取什么特定的方法来避免过度拟合?

仔细观察训练集和评估集是如何划分的非常重要。我试图构建一个验证集,使其能够重现这种划分。

你会推荐使用哪些特定的工具或库来进行数据分析或机器学习?

我喜欢 Pandas,这是一个处理表格数据集的必备库。我通过提取、聚合和可视化来使用它进行数据探索性分析。

你建议读者如何掌握 Pandas?

你可以查看一些社区教程。Kaggle 还提供了一些关于 Pandas 和特征工程的教程课程。

你使用其他竞赛平台吗?它们与 Kaggle 相比如何?

我有时会使用像 Signate、Nishika 等日本平台(upura.github.io/projects/data_science_competitions/)。这些平台在功能和 UX/UX 方面显然不如 Kaggle,但看到熟悉的主题,如日语,还是很有趣的。

文本增强策略

我们在前一章中广泛讨论了计算机视觉问题的增强策略。相比之下,针对文本数据的类似方法是一个不太被探索的领域(正如没有像albumentations这样的单一包所证明的那样)。在本节中,我们展示了处理该问题的可能方法之一。

基本技术

通常,首先检查基本方法是有益的,重点关注随机变化和同义词处理。Wei 和 Zou(2019)在arxiv.org/abs/1901.11196提供了对基本方法的有系统研究。

我们从同义词替换开始。用同义词替换某些单词会产生与原文意思相近但略有扰动的文本(如果你对同义词的来源等更多细节感兴趣,可以查看wordnet.princeton.edu/上的项目页面)。

def get_synonyms(word):

    synonyms = set()

    for syn in wordnet.synsets(word):
        for l in syn.lemmas():
            synonym = l.name().replace("_", " ").replace("-", " ").lower()
            synonym = "".join([char for char in synonym if char in ' qwertyuiopasdfghjklzxcvbnm'])
            synonyms.add(synonym) 
    if word in synonyms:
        synonyms.remove(word)

    return list(synonyms) 

我们在上述定义的工作函数周围创建了一个简单的包装器,指定一段文本(包含多个单词的字符串),并最多替换其中的n个单词:

def synonym_replacement(words, n):    
    words = words.split()    
    new_words = words.copy()
    random_word_list = list(set([word for word in words if word not in stop_words]))
    random.shuffle(random_word_list)
    num_replaced = 0

    for random_word in random_word_list:
        synonyms = get_synonyms(random_word)

        if len(synonyms) >= 1:
            synonym = random.choice(list(synonyms))
            new_words = [synonym if word == random_word else word for word in new_words]
            num_replaced += 1

        if num_replaced >= n: # Only replace up to n words
            break
    sentence = ' '.join(new_words)
    return sentence 

让我们看看这个函数在实际中的应用:

print(f" Example of Synonym Replacement: {synonym_replacement('The quick brown fox jumps over the lazy dog',4)}") 
Example of Synonym Replacement: The spry brown university fox jumpstart over the lazy detent 

这并不完全符合莎士比亚的风格,但它确实传达了相同的信息,同时显著改变了风格。我们可以通过为每条推文创建多个新句子来扩展这种方法:

trial_sent = data['text'][25]
print(trial_sent)
the free fillin' app on my ipod is fun, im addicted
for n in range(3):
    print(f" Example of Synonym Replacement: {synonym_replacement(trial_sent,n)}") 
Example of Synonym Replacement: the free fillin' app on my ipod is fun, im addict
Example of Synonym Replacement: the innocent fillin' app on my ipod is fun, im addicted
Example of Synonym Replacement: the relinquish fillin' app on my ipod is fun, im addict 

如您所见,使用同义词生成文本片段的变体相当直接。

接下来,交换是一种简单而有效的方法;我们通过随机交换文本中单词的顺序来创建一个修改后的句子。

仔细应用,这可以被视为一种可能有用的正则化形式,因为它干扰了像 LSTM 这样的模型所依赖的数据的顺序性。第一步是定义一个交换单词的函数:

def swap_word(new_words):    
    random_idx_1 = random.randint(0, len(new_words)-1)
    random_idx_2 = random_idx_1
    counter = 0    
    while random_idx_2 == random_idx_1:
        random_idx_2 = random.randint(0, len(new_words)-1)
        counter += 1        
        if counter > 3:
            return new_words

    new_words[random_idx_1], new_words[random_idx_2] = new_words[random_idx_2], new_words[random_idx_1] 
    return new_words 

然后,我们围绕这个函数编写一个包装器:

# n is the number of words to be swapped
def random_swap(words, n):    
    words = words.split()
    new_words = words.copy()

    for _ in range(n):
        new_words = swap_word(new_words)

    sentence = ' '.join(new_words)    
    return sentence 

同义词和交换不会影响我们修改的句子的长度。如果在一个特定的应用中修改该属性是有用的,我们可以在句子中删除或添加单词。

实现前者的最常见方法是随机删除单词:

def random_deletion(words, p):
    words = words.split()

    # Obviously, if there's only one word, don't delete it
    if len(words) == 1:
        return words
    # Randomly delete words with probability p
    new_words = []
    for word in words:
        r = random.uniform(0, 1)
        if r > p:
            new_words.append(word)
    # If you end up deleting all words, just return a random word
    if len(new_words) == 0:
        rand_int = random.randint(0, len(words)-1)
        return [words[rand_int]]
    sentence = ' '.join(new_words)

    return sentence 

让我们看看一些例子:

print(random_deletion(trial_sent,0.2))
print(random_deletion(trial_sent,0.3))
print(random_deletion(trial_sent,0.4)) 
the free fillin' app on my is fun, addicted
free fillin' app on my ipod is im addicted
the free on my ipod is fun, im 

如果我们可以删除,当然也可以添加。在句子中随机插入单词可以被视为 NLP 中向图像添加噪声或模糊的等效操作:

def random_insertion(words, n):    
    words = words.split()
    new_words = words.copy()    
    for _ in range(n):
        add_word(new_words)        
    sentence = ' '.join(new_words)
    return sentence
def add_word(new_words):    
    synonyms = []
    counter = 0

    while len(synonyms) < 1:
        random_word = new_words[random.randint(0, len(new_words)-1)]
        synonyms = get_synonyms(random_word)
        counter += 1
        if counter >= 10:
            return        
    random_synonym = synonyms[0]
    random_idx = random.randint(0, len(new_words)-1)
    new_words.insert(random_idx, random_synonym) 

这是函数在行动中的样子:

print(random_insertion(trial_sent,1))
print(random_insertion(trial_sent,2))
print(random_insertion(trial_sent,3)) 
the free fillin' app on my addict ipod is fun, im addicted
the complimentary free fillin' app on my ipod along is fun, im addicted
the free along fillin' app addict on my ipod along is fun, im addicted 

我们可以将上述讨论的所有变换组合成一个单一的功能,生成相同句子的四个变体:

def aug(sent,n,p):
    print(f" Original Sentence : {sent}")
    print(f" SR Augmented Sentence : {synonym_replacement(sent,n)}")
    print(f" RD Augmented Sentence : {random_deletion(sent,p)}")
    print(f" RS Augmented Sentence : {random_swap(sent,n)}")
    print(f" RI Augmented Sentence : {random_insertion(sent,n)}")
aug(trial_sent,4,0.3) 
Original Sentence : the free fillin' app on my ipod is fun, im addicted
SR Augmented Sentence : the disembarrass fillin' app on my ipod is fun, im hook
RD Augmented Sentence : the free app on my ipod fun, im addicted
RS Augmented Sentence : on free fillin' ipod is my the app fun, im addicted
RI Augmented Sentence : the free fillin' app on gratis addict my ipod is complimentary make up fun, im addicted 

上文讨论的增强方法没有利用文本数据的结构——以一个例子来说,分析像“词性”这样的简单特征可以帮助我们构建更有用的原始文本变换。这是我们接下来要关注的方法。

nlpaug

我们通过展示nlpaug包(github.com/makcedward/nlpaug)提供的功能来结束本节。它聚合了文本增强的不同方法,并设计得轻量级且易于集成到工作流程中。以下是一些包含其中的功能示例。

! pip install nlpaug 

我们导入字符级和词级增强器,我们将使用它们来插入特定方法:

import nlpaug.augmenter.char as nac
import nlpaug.augmenter.word as naw
test_sentence = "I genuinely have no idea what the output of this sequence of words will be - it will be interesting to find out what nlpaug can do with this!" 

当我们将模拟的打字错误应用于测试句子时会发生什么?这种转换可以用多种方式参数化;有关参数及其解释的完整列表,鼓励读者查阅官方文档:nlpaug.readthedocs.io/en/latest/augmenter/char/keyboard.html

aug = nac.KeyboardAug(name='Keyboard_Aug', aug_char_min=1,
                      aug_char_max=10, aug_char_p=0.3, aug_word_p=0.3,
                      aug_word_min=1, aug_word_max=10, stopwords=None,
                      tokenizer=None, reverse_tokenizer=None,
                      include_special_char=True, include_numeric=True,
                      include_upper_case=True, lang='en', verbose=0,
                      stopwords_regex=None, model_path=None, min_char=4)
test_sentence_aug = aug.augment(test_sentence)
print(test_sentence)
print(test_sentence_aug) 

这是输出结果:

I genuinely have no idea what the output of this sequence of words will be - it will be interesting to find out what nlpaug can do with this!
I geb&ine:y have no kdeZ qhQt the 8uYput of tTid sequsnDr of aorVs will be - it wi,k be jnterewtlHg to find out what nlpaug can do with this! 

我们可以模拟一个OCR 错误逐渐渗透到我们的输入中:

aug = nac.OcrAug(name='OCR_Aug', aug_char_min=1, aug_char_max=10,
                 aug_char_p=0.3, aug_word_p=0.3, aug_word_min=1,
                 aug_word_max=10, stopwords=None, tokenizer=None,
                 reverse_tokenizer=None, verbose=0,
                 stopwords_regex=None, min_char=1)
test_sentence_aug = aug.augment(test_sentence)
print(test_sentence)
print(test_sentence_aug) 

我们得到:

I genuinely have no idea what the output of this sequence of words will be - it will be interesting to find out what nlpaug can do with this!
I 9enoine1y have no idea what the ootpot of this sequence of wokd8 will be - it will be inteke8tin9 to find out what nlpaug can du with this! 

虽然有用,但在对数据进行创造性更改时,字符级变换的适用范围有限。让我们看看nlpaug在词级修改方面提供了哪些可能性。我们的第一个例子是用反义词替换固定百分比的单词:

aug = naw.AntonymAug(name='Antonym_Aug', aug_min=1, aug_max=10, aug_p=0.3,
                     lang='eng', stopwords=None, tokenizer=None,
                     reverse_tokenizer=None, stopwords_regex=None,
                     verbose=0)
test_sentence_aug = aug.augment(test_sentence)
print(test_sentence)
print(test_sentence_aug) 

我们得到:

I genuinely have no idea what the output of this sequence of words will be - it will be interesting to find out what nlpaug can do with this!
I genuinely lack no idea what the output of this sequence of words will differ - it will differ uninteresting to lose out what nlpaug can unmake with this! 

nlpaug 还为我们提供了例如替换同义词的可能性;这些转换也可以使用上面讨论的更基本的技术来实现。为了完整性,我们下面展示了一个小样本,它使用了一个底层的 BERT 架构:

aug = naw.ContextualWordEmbsAug(model_path='bert-base-uncased',
                                model_type='', action='substitute',
                                # temperature=1.0,
                                top_k=100,
                                # top_p=None,
                                name='ContextualWordEmbs_Aug', aug_min=1,
                                aug_max=10, aug_p=0.3, 
                                stopwords=None, device='cpu',
                                force_reload=False,
                                # optimize=None,
                                stopwords_regex=None,
                                verbose=0, silence=True)
test_sentence_aug = aug.augment(test_sentence)
print(test_sentence)
print(test_sentence_aug) 

这里是结果:

I genuinely have no idea what the output of this sequence of words will be - it will be interesting to find out what nlpaug can do with this!
i genuinely have no clue what his rest of this series of words will say - its will seemed impossible to find just what we can do with this! 

如您所见,nlpaug 为修改您的文本输入以生成增强提供了广泛的选择。实际上应该选择哪一些很大程度上取决于上下文,并且这个决定需要一点领域知识,适合特定的应用。

一些可供进一步探索的地方包括入门级比赛,例如 使用灾难推文的自然语言处理 (www.kaggle.com/c/nlp-getting-started),以及更中级或高级的比赛,如 Jigsaw 毒性评论严重程度评级 (www.kaggle.com/c/jigsaw-toxic-severity-rating) 或 Google QUEST Q&A 标注 (www.kaggle.com/c/google-quest-challenge)。在这些所有情况下,nlpaug 都已被广泛使用——包括在获奖方案中。

摘要

在本章中,我们讨论了 NLP 竞赛的建模。我们展示了适用于 Kaggle 竞赛中出现的各种问题的传统和最先进的方法。此外,我们还触及了经常被忽视的主题——文本增强。

在下一章中,我们将讨论模拟比赛,这是一种近年来逐渐流行起来的新型竞赛类别。

加入我们本书的 Discord 空间

加入本书的 Discord 工作空间,参加每月一次的作者 问我任何问题 活动:

packt.link/KaggleDiscord

第十二章:模拟和优化竞赛

强化学习RL)在机器学习的不同分支中是一个有趣的案例。一方面,它在技术层面上要求很高:来自监督学习的各种直觉并不适用,相关的数学工具也更为复杂;另一方面,它对于外行或非专业人士来说最容易解释。一个简单的类比是教你的宠物(我非常有意地避免狗与猫的争论)表演特技:你为做得好的特技提供奖励,否则拒绝。

强化学习在 Kaggle 的竞赛派对中是一个后来者,但近年来,随着模拟竞赛的引入,情况发生了变化。在本章中,我们将描述 Kaggle 宇宙中这个新而令人兴奋的部分。到目前为止——在撰写本文时——已经举办了四个特色竞赛和两个游乐场竞赛;虽然这个列表并不广泛,但它允许我们给出一个广泛的概述。

在本章中,我们将展示在几个模拟竞赛中提出的问题的解决方案:

  • 我们从Connect X开始。

  • 我们接着展示剪刀石头布,其中展示了构建竞争性代理的双重方法。

  • 接下来,我们展示了一个基于多臂老丨虎丨机的解决方案,用于Santa竞赛。

  • 我们以对剩余竞赛的概述结束,这些竞赛略超出了本章的范围。

如果强化学习对你来说是一个全新的概念,那么首先获得一些基本理解可能是个好主意。开始 RL 冒险的一个非常好的方式是 Kaggle 学习课程,该课程专门针对游戏 AI 背景下的这个主题(www.kaggle.com/learn/intro-to-game-ai-and-reinforcement-learning)。该课程介绍了诸如代理和策略等基本概念,同时也提供了一个(快速)介绍深度强化学习。课程中的所有示例都使用了来自游乐场竞赛Connect X的数据,其目标是训练一个能够在线连接跳棋的代理(www.kaggle.com/c/connectx/overview)。

在更广泛的意义上,值得指出的是,模拟和优化竞赛的一个重要方面是环境:由于问题的本质,你的解决方案需要表现出比仅仅提交一组数字(如“常规”监督学习竞赛的情况)更动态的特性。关于模拟竞赛中使用的环境的非常详尽和有信息量的描述可以在github.com/Kaggle/kaggle-environments/blob/master/README.md找到。

Connect X

在本节中,我们展示如何使用启发式方法解决玩跳棋的简单问题。虽然这不是深度学习解决方案,但我们认为这种概念的基本展示对那些没有显著先前接触 RL 的人来说更有用。

如果你刚开始接触使用 AI 玩棋类游戏的概念,汤姆·范·德·维勒www.kaggle.com/tvdwiele)的演示是一个值得探索的资源:tinyurl.com/36rdv5sa

Connect X的目标是在游戏棋盘上先于对手将你的棋子排成一行(水平、垂直或对角线)的数量X)。玩家轮流将棋子放入棋盘顶部的某一列。这意味着每一步的目的可能是为了赢得比赛,或者是为了阻止对手赢得比赛。

图片

图 12.1:Connect X 棋盘

Connect X是第一个引入代理的比赛:参与者必须提交能够与其他人玩游戏的游戏代理,而不是静态的提交(或与未见数据集评估的笔记本)。评估按步骤进行:

  1. 上传后,提交将自行对弈以确保其正常工作。

  2. 如果这个验证片段成功,就会分配一个技能评分,并且提交将加入所有竞争者的行列。

  3. 每个提交都会播放几个片段,随后进行排名调整。

考虑到这个设置,让我们继续展示如何为Connect X比赛构建一个提交。我们提供的代码适用于X=4,但可以轻松地适应其他值或变量X

首先,我们安装 Kaggle 环境包:

!pip install kaggle-environments --upgrade 

我们定义了一个环境,我们的代理将在其中被评估:

from kaggle_environments import evaluate, make
env = make("connectx", debug=True)
env.render() 

虽然你可能有一个尝试复杂方法的冲动,但开始简单是有用的——正如我们在这里所做的那样,通过使用简单的启发式方法。这些方法在伴随的代码中组合成一个单独的函数,但为了展示,我们一次描述一个。

第一条规则是检查是否有任何玩家有机会垂直连接四个棋子,如果有,就返回可能的位置。我们可以通过使用一个简单的变量作为我们的输入参数来实现这一点,它可以取两个可能的值,表示正在分析哪个玩家的机会:

def my_agent(observation, configuration):
    from random import choice
    # me:me_or_enemy=1, enemy:me_or_enemy=2
    def check_vertical_chance(me_or_enemy):
        for i in range(0, 7):
            if observation.board[i+7*5] == me_or_enemy \
            and observation.board[i+7*4] == me_or_enemy \
            and observation.board[i+7*3] == me_or_enemy \
            and observation.board[i+7*2] == 0:
                return i
            elif observation.board[i+7*4] == me_or_enemy \
            and observation.board[i+7*3] == me_or_enemy \
            and observation.board[i+7*2] == me_or_enemy \
            and observation.board[i+7*1] == 0:
                return i
            elif observation.board[i+7*3] == me_or_enemy \
            and observation.board[i+7*2] == me_or_enemy \
            and observation.board[i+7*1] == me_or_enemy \
            and observation.board[i+7*0] == 0:
                return i
        # no chance
        return -99 

我们可以定义一个类似的方法来处理横向机会:

 def check_horizontal_chance(me_or_enemy):
        chance_cell_num = -99
        for i in [0,7,14,21,28,35]:
            for j in range(0, 4):
                val_1 = i+j+0
                val_2 = i+j+1
                val_3 = i+j+2
                val_4 = i+j+3
                if sum([observation.board[val_1] == me_or_enemy, \
                        observation.board[val_2] == me_or_enemy, \
                        observation.board[val_3] == me_or_enemy, \
                        observation.board[val_4] == me_or_enemy]) == 3:
                    for k in [val_1,val_2,val_3,val_4]:
                        if observation.board[k] == 0:
                            chance_cell_num = k
                            # bottom line
                            for l in range(35, 42):
                                if chance_cell_num == l:
                                    return l - 35
                            # others
                            if observation.board[chance_cell_num+7] != 0:
                                return chance_cell_num % 7
        # no chance
        return -99 

我们对对角线组合重复相同的做法:

# me:me_or_enemy=1, enemy:me_or_enemy=2
def check_slanting_chance(me_or_enemy, lag, cell_list):
        chance_cell_num = -99
        for i in cell_list:
            val_1 = i+lag*0
            val_2 = i+lag*1
            val_3 = i+lag*2
            val_4 = i+lag*3
            if sum([observation.board[val_1] == me_or_enemy, \
                    observation.board[val_2] == me_or_enemy, \
                    observation.board[val_3] == me_or_enemy, \
                    observation.board[val_4] == me_or_enemy]) == 3:
                for j in [val_1,val_2,val_3,val_4]:
                    if observation.board[j] == 0:
                        chance_cell_num = j
                        # bottom line
                        for k in range(35, 42):
                            if chance_cell_num == k:
                                return k - 35
                        # others
                        if chance_cell_num != -99 \
                        and observation.board[chance_cell_num+7] != 0:
                            return chance_cell_num % 7
        # no chance
        return -99 

我们可以将逻辑组合成一个单独的函数,检查机会(与对手玩游戏):

 def check_my_chances():
        # check my vertical chance
        result = check_vertical_chance(my_num)
        if result != -99:
            return result
        # check my horizontal chance
        result = check_horizontal_chance(my_num)
        if result != -99:
            return result
        # check my slanting chance 1 (up-right to down-left)
        result = check_slanting_chance(my_num, 6, [3,4,5,6,10,11,12,13,17,18,19,20])
        if result != -99:
            return result
        # check my slanting chance 2 (up-left to down-right)
        result = check_slanting_chance(my_num, 8, [0,1,2,3,7,8,9,10,14,15,16,17])
        if result != -99:
            return result
        # no chance
        return -99 

这些块构成了逻辑的基础。虽然制定起来有点繁琐,但它们是将直觉转化为可用于在游戏中竞争的代理的启发式策略的有用练习。

请参阅存储库中的相关代码,以获取本例中代理的完整定义。

我们新定义的代理的性能可以与预定义的代理(例如,随机代理)进行比较:

env.reset()
env.run([my_agent, "random"])
env.render(mode="ipython", width=500, height=450) 

上述代码展示了如何从头开始设置一个相对简单问题的解决方案(这就是为什么连接 X是一个游乐场而不是一个特色竞赛的原因)。有趣的是,这个简单的问题可以用(几乎)最先进的方法(如 AlphaZero)来处理:www.kaggle.com/connect4alphazero/alphazero-baseline-connectx

在介绍性例子之后,你应该准备好深入更复杂(或者至少不是基于玩具示例)的竞赛。

剪刀石头布

模拟竞赛中提到的几个问题涉及玩游戏并非巧合:在各个复杂程度下,游戏提供了一个规则明确的环境,自然适合代理-行动-奖励框架。除了井字棋之外,连接棋盘是竞争性游戏中最简单的例子之一。随着游戏难度等级的提升(即游戏难度),让我们来看看剪刀石头布以及围绕这个游戏展开的 Kaggle 竞赛应该如何进行。

剪刀石头布竞赛的构想(www.kaggle.com/c/rock-paper-scissors/code)是对基本的剪刀石头布游戏(在一些地区被称为roshambo)的扩展:而不是通常的“三局两胜”的得分,我们使用“一千局一胜”。

我们将描述两种可能的解决问题的方法:一种基于博弈论方法,另一种更侧重于算法方面。

我们从纳什均衡开始。维基百科将这个定义为一个涉及两个或更多玩家的非合作博弈的解,假设每个玩家都知道其他玩家的均衡策略,并且没有玩家可以通过仅改变自己的策略来获得优势。

在博弈论框架下对剪刀石头布的出色介绍可以在www.youtube.com/watch?v=-1GDMXoMdaY找到。

用红色和蓝色表示我们的玩家,矩阵中的每个单元格都显示了给定移动组合的结果:

包含文本的图像,结果表格  自动生成的描述

图 12.2:剪刀石头布的收益矩阵

以一个例子来说明,如果双方都出石头(左上角的单元格),双方都得到 0 分;如果蓝色方出石头而红色方出布(第一行的第二列的单元格),红色方获胜——因此红色方得到+1 分,而蓝色方得到-1 分。

如果我们以 1/3 的等概率玩每个动作,那么对手也必须这样做;否则,如果他们总是出石头,他们将和石头平局,输给布,赢剪刀——每种情况发生的概率都是 1/3(或三分之一的概率)。在这种情况下,期望奖励是 0,这时我们可以改变策略为布,并一直获胜。同样的推理也可以用于布对剪刀和剪刀对石头的策略,由于冗余,我们不会展示结果的矩阵。

为了达到均衡,剩余的选项是两个玩家都需要采取随机策略——这就是纳什均衡。我们可以围绕这个想法构建一个简单的智能体:

%%writefile submission.py
import random
def nash_equilibrium_agent(observation, configuration):
    return random.randint(0, 2) 

在开始时(从笔记本直接写入文件)的魔法是满足这个特定竞赛提交约束的必要条件。

我们的纳什智能体与其他智能体相比表现如何?我们可以通过评估性能来了解:

!pip install -q -U kaggle_environments
from kaggle_environments import make 

在撰写本文时,导入后出现了一个错误(无法加载名为‘gfootball’的模块);Kaggle 的官方建议是忽略它。实际上,它似乎对执行代码没有影响。

我们首先创建石头剪刀布环境,并将模拟的每个模拟的 episodes 限制为 1,000 个:

env = make(
    "rps", 
    configuration={"episodeSteps": 1000}
) 

我们将利用在这个竞赛中创建的笔记本,该笔记本实现了基于确定性启发式算法的多个智能体(www.kaggle.com/ilialar/multi-armed-bandit-vs-deterministic-agents),并从那里导入我们竞争的智能体的代码:

%%writefile submission_copy_opponent.py
def copy_opponent_agent(observation, configuration):
    if observation.step > 0:
        return observation.lastOpponentAction
    else:
        return 0
# nash_equilibrium_agent vs copy_opponent_agent
env.run(
    ["submission.py", "submission_copy_opponent.py"]
)
env.render(mode="ipython", width=500, height=400) 

当我们执行前面的代码块并运行环境时,我们可以观察 1,000 个 epoch 的动画板。一个快照看起来像这样:

图 12.3:评估智能体性能的渲染环境快照

在监督学习——无论是分类还是回归——中,通常有用简单基准来开始处理任何问题,通常是线性模型。尽管它不是最先进的,但它可以提供一个有用的期望值和性能度量。在强化学习中,一个类似的想法也成立;在这个能力中值得尝试的方法是多臂老丨虎丨机,这是我们能够诚实地称之为 RL 的最简单算法。在下一节中,我们将展示这种方法如何在模拟竞赛中使用。

圣诞竞赛 2020

在过去几年里,Kaggle 上形成了一种传统:在每年的 12 月初,会有一个以圣诞为主题的竞赛。实际的算法方面每年都有所不同,但就我们的目的而言,2020 年的竞赛是一个有趣的案例:www.kaggle.com/c/santa-2020

设置是一个经典的多臂老丨虎丨机MAB)算法,通过在自动售货机上重复操作来最大化奖励,但有两个额外特点:

  • 奖励衰减:在每一步,从机器获得奖励的概率会降低 3%。

  • 竞赛:您不仅受时间的限制(有限次数的尝试),还受另一个试图实现相同目标的玩家的限制。我们主要提到这个限制是为了完整性,因为它不是将我们的解决方案明确包含在内的关键因素。

对于如何解决通用多臂老丨虎丨机问题的方法的好解释,读者可以参考lilianweng.github.io/lil-log/2018/01/23/the-multi-armed-bandit-problem-and-its-solutions.html

我们展示的解决方案是从www.kaggle.com/ilialar/simple-multi-armed-bandit改编的,代码来自Ilia Larchenko (www.kaggle.com/ilialar)。我们的方法基于对奖励分布的连续更新:在每一步,我们从具有参数(a+1, b+1)的 Beta 分布中生成一个随机数,其中:

  • a 是从这个臂获得的总奖励(胜利次数)

  • b 是历史损失的数量

当我们需要决定拉哪个臂时,我们选择产生最高数字的臂并使用它来生成下一步;我们的后验分布成为下一步的先验。

下面的图表显示了不同(a, b)值对的 Beta 分布形状:

img/B17574_12_04.png

图 12.4:不同(a,b)参数组合的 Beta 分布密度形状

如您所见,最初,分布是平坦的(Beta(0,0)是均匀的),但随着我们收集更多信息,概率质量会集中在峰值周围,这意味着不确定性更少,我们对判断更有信心。我们可以通过每次使用一个臂来减少a参数来整合特定于比赛的奖励衰减。

我们通过编写一个提交文件来开始创建我们的代理。首先,必要的导入和变量初始化:

%%writefile submission.py
import json
import numpy as np
import pandas as pd
bandit_state = None
total_reward = 0
last_step = None 

我们定义了一个指定 MAB 代理的类。为了阅读的连贯性,我们重新生成了整个代码,并在其中包含注释说明:

def multi_armed_bandit_agent (observation, configuration):
    global history, history_bandit
    step = 1.0         # balance exploration / exploitation
    decay_rate = 0.97  # how much do we decay the win count after each call

    global bandit_state,total_reward,last_step

    if observation.step == 0:
        # initial bandit state
        bandit_state = [[1,1] for i in range(configuration["banditCount"])]
    else:       
        # updating bandit_state using the result of the previous step
        last_reward = observation["reward"] - total_reward
        total_reward = observation["reward"]

        # we need to understand who we are Player 1 or 2
        player = int(last_step == observation.lastActions[1])

        if last_reward > 0:
            bandit_state[observation.lastActions[player]][0] += last_reward * step
        else:
            bandit_state[observation.lastActions[player]][1] += step

        bandit_state[observation.lastActions[0]][0] = (bandit_state[observation.lastActions[0]][0] - 1) * decay_rate + 1
        bandit_state[observation.lastActions[1]][0] = (bandit_state[observation.lastActions[1]][0] - 1) * decay_rate + 1
    # generate random number from Beta distribution for each agent and select the most lucky one
    best_proba = -1
    best_agent = None
    for k in range(configuration["banditCount"]):
        proba = np.random.beta(bandit_state[k][0],bandit_state[k][1])
        if proba > best_proba:
            best_proba = proba
            best_agent = k

    last_step = best_agent
    return best_agent 

如您所见,函数的核心逻辑是对 MAB 算法的直接实现。在bandit_state变量中,我们应用衰减乘数,这是针对我们竞赛的特定调整。

与前一个案例类似,我们现在已经准备好评估我们的代理在竞赛环境中的性能。下面的代码片段展示了如何实现这一点:

%%writefile random_agent.py
import random
def random_agent(observation, configuration):
    return random.randrange(configuration.banditCount)
from kaggle_environments import make
env = make("mab", debug=True)
env.reset()
env.run(["random_agent.py", "submission.py"])
env.render(mode="ipython", width=800, height=700) 

我们看到类似这样的情况:

包含文本、电子设备、屏幕截图、显示的自动生成的描述

图 12.5:评估代理性能的渲染环境快照

在本节中,我们展示了如何在 Kaggle 的模拟竞赛中利用经典的多臂老丨虎丨机算法。虽然作为一个起点很有用,但这并不足以获得奖牌区,在那里深度强化学习方法更为流行。

我们将接着讨论基于其他方法的策略,在多样化的竞赛中。

游戏的名称

除了上述相对简单的游戏之外,模拟竞赛涉及更复杂的设置。在本节中,我们将简要讨论这些。第一个例子是 Halite,在竞赛页面上(www.kaggle.com/c/halite)定义为以下方式:

Halite [...] 是一款资源管理游戏,你在这里建造并控制一支小型舰队。你的算法决定它们的移动以收集卤素,这是一种发光的能量来源。比赛结束时收集到的卤素最多的一方获胜,但如何制定有效且高效的策略取决于你。你控制你的舰队,建造新的船只,创建造船厂,并在游戏板上开采再生的卤素。

游戏的界面如下:

图 12.6:Halite 游戏板

Kaggle 围绕这款游戏组织了两场比赛:一个游乐场版(www.kaggle.com/c/halite-iv-playground-edition)以及一个常规的特色版(www.kaggle.com/c/halite)。在这种情况下,经典的强化学习方法不太有用,因为,由于有任意数量的单位(船只/基地)和动态的对手池,信用分配问题对于拥有“正常”计算资源的人来说变得难以解决。

在完全普遍的意义上解释信用分配问题超出了本书的范围,但感兴趣的读者可以从维基百科条目(en.wikipedia.org/wiki/Assignment_problem)开始,并阅读 Mesnard 等人撰写的这篇出色的介绍性文章:proceedings.mlr.press/v139/mesnard21a.html

Tom van de Wielewww.kaggle.com/c/halite/discussion/183543)对获胜解决方案的描述提供了对在此情况下证明成功的修改方法的优秀概述(深度强化学习,每个单位独立分配信用)。

另一个涉及相对复杂游戏的竞赛是Lux AI (www.kaggle.com/c/lux-ai-2021)。在这个竞赛中,参与者被要求设计智能体来解决一个结合资源收集和分配的多变量优化问题,与其他玩家竞争。此外,成功的智能体还必须分析对手的移动并做出相应的反应。这个竞赛的一个有趣特点是“元”方法的流行:模仿学习 (paperswithcode.com/task/imitation-learning)。这是一种在强化学习中相当新颖的方法,专注于从演示中学习行为策略——而不需要特定的模型来描述状态-动作对的生成。这种想法的一个具有竞争力的实现(在撰写本文时)是由 Kaggle 用户 Ironbar 提供的 (www.kaggle.com/c/lux-ai-2021/discussion/293911)。

最后,没有对 Kaggle 模拟竞赛的讨论是不完整的,没有提及Google Research Football with Manchester City F.C.竞赛 (www.kaggle.com/c/google-football/overview)。这个竞赛背后的动机是让研究人员探索 AI 智能体在复杂环境如足球中的能力。竞赛概述部分将问题表述如下:

体育运动需要短期控制、学习概念如传球和高级策略之间的平衡,这很难教会智能体。目前存在一个环境和测试智能体的当前环境,但其他解决方案可能提供更好的结果。

与上述一些例子不同,这个竞赛主要由强化学习方法主导:

研究上述解决方案是学习如何利用强化学习解决这类问题的绝佳起点。

Firat Gonen

www.kaggle.com/frtgnn

对于本章的访谈,我们采访了 Firat Gonen,他是数据集、笔记本和讨论的三位大师,同时也是 HP 数据科学全球大使。他向我们分享了他对 Kaggle 方法的看法,以及他的态度是如何随着时间的推移而演变的。

你最喜欢的比赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

我最喜欢的比赛类型随着时间的推移而演变。我曾经更喜欢非常通用的表格比赛,只要有一台好笔记本电脑和一些耐心,就能掌握趋势。我觉得我以前能够很好地看到训练集和测试集之间的异常趋势。随着时间的推移,由于 Z by HP 授予我大使称号以及我的工作站设备,我逐渐转向了更多的计算机视觉比赛,尽管我还有很多东西要学。

你是如何应对 Kaggle 比赛的?这种方法与你在日常工作中所做的方法有何不同?

我通常喜欢尽可能推迟建模部分。我喜欢利用这段时间进行探索性数据分析(EDA)、处理异常值、阅读论坛等,尽量保持耐心。在我感觉特征工程完成之后,我尝试只构建基准模型,以掌握不同架构的结果。我的技术在与专业工作相关时也非常相似。我认为在大量时间内试图做到最好是没有用的;时间和成功之间必须有一个平衡。

告诉我们你参加的一个特别具有挑战性的比赛,以及你用来解决任务的见解。

弗朗索瓦·肖莱特举办的比赛极具挑战性;这是第一个迫使我们进入通用人工智能(AGI)的比赛。我记得在那个比赛中,我感到非常无力,我在那里学到了几种新技术。我认为每个人在记住数据科学不仅仅是机器学习的同时,都这样做过。在 Kaggle 上,其他一些技术,如混合整数规划,也重新浮出水面。

Kaggle 是否帮助你在你的职业生涯中?如果是的话,是如何帮助的?

当然:多亏了 Kaggle,我学到了很多新技术,并保持了知识的更新。在我的职业生涯中,我的主要责任大多在于管理。这就是为什么 Kaggle 对我保持对多件事情的更新非常重要。

你是如何利用 Kaggle 建立起你的作品集的?

我认为优势在于一种更间接的方式,人们看到了我的更传统教育资格中的实际技能(多亏了 Kaggle)和更多的理论技能。

在你的经验中,没有经验的 Kagglers 通常忽略了什么?你现在知道的事情,你希望在你最初开始时就知道?

我认为新来者犯的两个错误。第一个是进入新比赛时的恐惧,认为他们会得到很差的分数,并且会被记录下来。这是胡说八道。每个人都有不好的分数;这完全取决于你投入新比赛的程度。第二个是他们想尽快进入模型构建阶段,这是非常错误的;他们想看到他们的基准分数,然后他们会感到沮丧。我建议他们在特征生成和选择,以及在 EDA 阶段花时间。

在过去的比赛中,你犯过哪些错误?

我的错误,不幸的是,与新手非常相似。在几场竞赛中,我没有足够关注早期阶段,变得不耐烦,过了一段时间后,你会觉得自己没有足够的时间回头。

对于数据分析或机器学习,有没有你特别推荐使用的工具或库?

我推荐使用 PyCaret 进行基准测试以获得速度,以及 PyTorch 作为模型构建框架。

当人们参加竞赛时,他们应该记住或做最重要的事情是什么?

探索性数据分析以及之前的类似竞赛讨论。

你是否使用其他竞赛平台?它们与 Kaggle 相比如何?

说实话,我还没有在 Kaggle 之外尝试过,但从游客的角度来看,我已经体验过一些了。适应其他平台需要时间。

摘要

在本章中,我们讨论了模拟竞赛,这是一种越来越受欢迎的新竞赛类型。与以视觉或 NLP 为中心的竞赛相比,模拟竞赛涉及更广泛的方法(带有一定程度的数学内容),这反映了监督学习和强化学习之间的差异。

本章总结了本书的技术部分。在接下来的内容中,我们将讨论如何将你的 Kaggle 笔记本转化为项目组合,并利用它寻找新的职业机会。

加入我们书的 Discord 空间

加入我们书的 Discord 工作空间,每月与作者进行一次“问我任何问题”的会议:

packt.link/KaggleDiscord

第三部分

利用比赛提升你的职业生涯

第十三章:创建你的项目和想法作品集

在 Kaggle 上的参与有其好处:在四个领域表现良好,并在其他 Kagglers 中的声望中排名靠前,无疑会带来满足感和成就感。然而,你在 Kaggle 上的经验也会超出 Kaggle 本身,并有助于推进你的职业生涯。这不仅仅是你在参加比赛、对从未处理过的数据进行实验或用新技术重复实验中获得的经验;还包括你与其他数据科学家建立的联系以及你可能会从公司那里获得的关注。

尽管 Kaggle 在很多公司中并未被完全认可为一种资格证明,但你参加比赛所做的工作可以充分展示你的能力,并帮助你从人群中脱颖而出。在本章中,我们将探讨如何通过在 Kaggle 本身以及其他网站上以适当的方式展示你的工作来脱颖而出。我们将涵盖以下主题:

  • 用 Kaggle 建立你的作品集

  • 在 Kaggle 之外安排你的在线存在

  • 监控竞争更新和新闻通讯

在下一章中,我们将通过探讨 Kaggle 如何通过增强你的专业网络和为你提供职业机会来直接影响你的职业生涯来结束本书。

用 Kaggle 建立你的作品集

Kaggle 声称自己是“数据科学的家园”,这一点必须放在正确的角度来考虑。正如我们详细讨论过的,Kaggle 对所有愿意竞争、根据给定的评估指标找出预测任务中最佳模型的人都是开放的。

在世界各地、教育背景或预测建模熟练程度方面没有限制。有时也有一些非预测性质的竞赛,例如强化学习竞赛、算法挑战和适合比数据科学家更广泛受众的分析竞赛。然而,根据指标从数据中做出最佳预测是 Kaggle 竞赛的核心目的。

实际数据科学有许多方面。首先,你的首要任务是解决问题,衡量你的模型的标准只是对解决问题好坏的或多或少精确的测量。你可能不仅要处理一个指标,还必须考虑多个指标。此外,问题可以以不同的方式解决,很大程度上取决于你如何表述它们。

关于数据,你很少能得到关于你必须使用的数据的规格说明,你可以修改任何现有的数据集以适应你的需求。有时,如果你需要,你甚至可以从头创建自己的数据集。没有关于如何组合数据或处理数据的指示。在解决问题时,你还必须考虑:

  • 技术债务

  • 随时间推移的解决方案的可维护性

  • 运行解决方案的时间和计算成本

  • 解释模型工作原理的可解释性

  • 对运营收入的影响(如果现实世界中的项目是商业项目,增加利润和/或降低成本是主导思想)

  • 在不同复杂性和抽象层次上沟通结果

通常,所有这些方面比原始性能对评估指标的影响更为重要。

技术债务这个术语在软件开发中比在数据科学中更为常见,尽管它也是一个相关的概念。对于技术债务,你应该考虑为了更快地交付你的项目而必须做的事情,但你将在以后以更高的成本重新做。由 David Sculley 和其他谷歌研究人员撰写的经典论文《机器学习系统中的隐藏技术债务》应该能让你了解该问题对于数据科学的相关性:proceedings.neurips.cc/paper/2015/file/86df7dcfd896fcaf2674f757a2463eba-Paper.pdf

并非所有这些专业知识都可以通过 Kaggle 竞赛来补充。其中大部分应该通过在企业环境中直接实践和经验积累来获得。然而,与 Kaggle 竞赛相关的知识和技能并不完全独立于我们上面讨论的许多考虑因素,并且它们是许多企业级数据科学流程的良好补充。通过在 Kaggle 上竞赛,你将接触到不同类型的数据和问题;你需要执行广泛的特征工程和模型假设的快速迭代;你还必须设计使用通用开源包组合最先进解决方案的方法。这是一套宝贵的技能,应该在你的方面得到推广。做到这一点最好的方式是建立一个作品集,它收集了你基于 Kaggle 竞赛和其他 Kaggle 资源的解决方案和工作。

为了从 Kaggle 竞赛中构建作品集,你可以采取多种方法。最简单的方法是利用 Kaggle 提供的设施,特别是数据集、笔记本和讨论区。

Gilberto Titericz

www.kaggle.com/titericz

在我们继续之前,我们在与 Gilberto Titericz 的访谈中提出了一份关于从 Kaggle 衍生出的职业机会的讨论。他是竞赛和讨论的大师,曾是排名第一,现在是 Kaggle 竞赛总金牌数排名第一。他还是 NVIDIA 的高级数据科学家,不久前在一篇关于 Wired 的文章中被提及(www.wired.com/story/solve-these-tough-data-problems-and-watch-job-offers-roll-in/)。

你最喜欢的竞争类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

自从我于 2011 年开始在 Kaggle 上比赛以来,我最喜欢的比赛类型是那些具有结构化表格数据的比赛。我在 Kaggle 中使用最多的技术是分类特征的编码(错误的方法有无限种)和堆叠集成。

你是如何应对 Kaggle 比赛的?这种方法与你在日常工作中所做的方法有何不同?

Kaggle 是一个机器学习的绝佳游乐场。与真实项目相比,Kaggle 中的问题已经非常明确和格式化,数据集已经创建,目标变量已经建立,指标也已经选定。因此,我总是带着 EDA(探索性数据分析)的心态开始参加 Kaggle 比赛。理解问题和了解数据集是超越其他玩家的关键之一。之后,我会花一些时间来定义一个合适的验证策略。这对于正确验证你的模型以及与 Kaggle 私有测试集评分方式保持一致非常重要。除了使用分层 K 折交叉验证对于大多数二分类问题都有效之外,我们还必须评估是否需要使用分组 K 折交叉验证或基于时间的分割来正确验证,避免过拟合,并在尽可能的情况下模拟私有测试集。之后,花一些时间进行特征工程和超参数优化实验也非常重要。此外,我总是在比赛中至少使用一个梯度提升树模型和一个基于深度学习的方法。这种不同方法的结合对于增加预测的多样性并提高比赛指标非常重要。

Kaggle 是否帮助你在职业生涯中取得进步?如果是的话,是如何帮助的?

是的,Kaggle 是我改变职业方向的主要原因。截至 2016 年,我是一名电子工程师,由于自 2011 年以来在比赛中学到的所有知识,我能够转向数据科学领域。Kaggle 帮助我理解机器学习的概念,并将从理论中学到的所有知识应用到实践中。此外,Kaggle 是一个出色的实验场所,你可以下载数据集并与之互动,以从数据中提取尽可能多的信息。结合比赛环境,这使得它成为学习编码和机器学习的完美场所,同时,它具有上瘾性,让你想要学习更多。赢得一些比赛可以使你的名字登上排行榜的顶端,这对任何人的职业生涯来说都是无价的。全球的猎头都在关注 Kaggle,以找到适合他们职位的合适人选,而通过比赛获得的知识和经验可以提升任何人的职业生涯。

你是如何利用 Kaggle 建立你的作品集的?

一旦我加入 Kaggle,我就花了几年的时间学习所有从数据中提取更多信息以及尽可能提高指标的技术、算法和技巧。高精度是大多数比赛的主要目标,但仅靠运气几乎是不可能的;知识和经验在目标是赢得比赛或至少进入金牌区时起着重要作用。我在 Kaggle 比赛中的奖牌数量是我的作品集;截至 2021 年 11 月,有 58 枚金牌和 47 枚银牌,这很好地总结了我在 Kaggle 获得的机器学习经验。考虑到每个比赛至少持续 1 个月,这超过了 105 个月的连续竞争性机器学习经验。

在您的经验中,不经验丰富的 Kagglers 通常忽视什么?您现在知道什么,而您希望自己在最初开始时就知道?

新手们经常 忽视合适的验证策略。这不仅仅发生在 Kaggle 上;我见过世界各地的数据科学家在构建模型时忽略了实验理论中最重要的事情之一。设置合适的验证策略没有普遍的规则,但数据科学家必须考虑模型未来将如何被使用,并使验证尽可能接近这一点。

在过去的比赛中,您犯过哪些错误?

犯过几个错误;不可能一一列举。我可能犯过所有可能的错误组合。错误的好处是你可以从中学习。一旦你犯了一个错误并且发现了它,你再次犯同样的错误的可能性就非常小。人们在 Kaggle 上犯的主要错误是相信排行榜的分数而不是他们本地的验证分数。过度拟合排行榜在 Kaggle 上是常态,这是与真实世界的最大区别。在真实项目中,我们必须构建一个我们信任的强大验证策略,因为在真实世界中,模型将在真实数据上被测试,而你只有一次机会命中目标,而不是每天多次提交。

您是否推荐使用特定的工具或库来进行数据分析与机器学习?

几年前我会推荐 R 语言,但考虑到 Python 在机器学习领域的快速发展以及它在生产中的通用性和易用性,我建议任何开始机器学习的人学习它。在表格数据库方面,我推荐使用 pandas 进行操作,如果您需要速度,则选择 cuDF(pandas 的 RAPIDS.ai GPU 版本)。对于数据探索分析(EDA),我推荐使用 Seaborn 或 Matplotlib 库中的 DataFrame,对于机器学习,推荐使用 Scikit-learn、SciPy、cuML(GPU)、XGBoost、LightGBM、CatBoost 和 PyTorch。请记住,使用原始特征构建简单的 XGBoost 模型既快又能够提供一个良好的基准,以便与后续模型进行比较。

当人们参加竞赛时,他们应该记住或做哪件事是最重要的?

参加 Kaggle 竞赛并提交公开的 Notebooks 很容易,但要在金牌区完成竞赛可能极具挑战性。所以对我来说,最重要的是记住,无论最终排名如何,我们都应该利用 Kaggle 来娱乐和学习,从讨论论坛、公开的 Notebooks,甚至从赛后获胜者的帖子中学习他们的想法和成功之处。

还要记住,使竞赛获胜者脱颖而出的不仅仅是复制别人的做法,而是跳出思维定式,提出新颖的想法、策略、架构和方法。

你使用其他竞赛平台吗?它们与 Kaggle 相比如何?

我在其他竞赛平台上赢得过几场比赛,但与 Kaggle 相比,主要区别在于用户数量。截至 2021 年 11 月,Kaggle 有 17.1 万活跃用户,这使得论坛、Notebooks 和数据集交互在内容上更加丰富。此外,Kaggle 还提供了一些独特的东西:你可以使用 Google 服务器免费编写和运行代码的 Notebooks,如果你没有好的硬件,这可能价值连城。

利用 Notebooks 和讨论

除了排名本身,Notebooks 是你在 Kaggle 上引起注意的方式,因为它们同时展示了你解决问题的方法、你展示想法的方式以及你编写代码的方式。作为在参与者之间轻松和公开分享解决方案和想法的一种方式,Notebooks 是展示雇主欣赏的能力的最重要工具(仅次于排名)。

事实上,近年来数据科学领域最重要的变化之一是其从杰出人才(独角兽数据科学家)的游戏转变为团队游戏,数据科学家必须与其他数据科学家和部门合作,以确保项目的成功。因此,在他们的招聘过程中,公司更关心你能否有效地沟通想法和结果,以及以干净和有效的方式编写代码。

在上一节中,我们讨论了现实世界项目需要更广泛的技术技能,从处理技术债务到设计成本效益解决方案。即使这些技能不会让你在竞赛中获胜,你仍然可以在 Kaggle 上展示这些技能。Notebooks 是完成这项工作的最佳工具。

参考第三章,使用 Kaggle Notebooks 工作和学习,了解 Kaggle Notebooks 的介绍。

你会在 Kaggle 上找到不同类型的 Notebooks。作为一个好的近似,我们可以将它们分为四类:

  • 竞赛排名的解决方案和想法

  • 探索性数据分析EDA)对数据进行分析

  • 解释机器学习模型或数据科学原理的教程

  • 从论文或其他原创解决方案中派生出的模型的新实现

这些中的每一个都可以通过一组有趣的技能为您提供优势。如果解决方案和竞赛的想法是展示您知道如何解决复杂数据科学问题的经典方式,那么其他三种可以展示给世界您能够:

  • 从数据中操作、表示和提取视觉和非视觉洞察力(EDA),这是一种在每个环境中都被认为非常重要的技能,从科学研究到商业

  • 在数据科学上进行教育,打开教育、导师和开发者倡导角色的门

  • 将研究转化为实践,在数据科学(尤其是在深度学习)创新每日出现并需要迅速转化为工作解决方案的时代,这是一项关键技能

即使您在 Kaggle 竞赛中排名不高或没有惊人的解决方案可以展示,如果您能以最佳方式推广这些其他三种类型的笔记本(EDA、教程和论文实现),这些笔记本也可以在现实世界中为您提供机会。为此,您需要了解如何编写可读且有趣的笔记本,这是从实践和经验中学习的东西。由于这是一门艺术,我们的建议是向他人学习,尤其是从笔记本大师那里学习,他们在笔记本用户排名中位居前列 (www.kaggle.com/rankings?group=notebooks&page=1&pageSize=20)。

我们建议您看看他们开发了什么样的笔记本,他们是如何使用图表来安排他们的工作的,他们是如何结构化他们的代码的,然后,最后,根据您的技能和兴趣,尝试模仿他们的其中一个笔记本。我们还建议您不要只把成功的机会赌在代码和图表上,还要赌在您所呈现的叙述上。无论您是在展示解决方案、教学还是在 TensorFlow 中实现神经网络架构,用文字解释笔记本单元格的方式对于留下持久积极的印象非常重要。

除了浏览高排名者的笔记本之外,还有一种方式可以通知您关于最近出现在 Kaggle 上的一些不太主流——但仍然精心制作的——笔记本。天体物理学家和热情的 Kaggle 用户 Heads or Tails,马丁·亨泽 (www.kaggle.com/headsortails),在讨论论坛上发布了一个每周的帖子“本周笔记本:隐藏的宝石”,这是一系列最有趣的笔记本的集合。目前,已经有超过 100 卷,作者继续在 Kaggle 上寻找任何可能有趣的东西。如果您想了解有关酷笔记本的最新信息,只需关注马丁·亨泽在 Kaggle 上的个人资料,或者不时检查他是否在他的讨论中发布了新内容。

如果你喜欢挖掘笔记本寻找灵感并从中学习,我们永远不会厌倦强调你不应该盲目复制他人的工作。Kaggle 上有很多笔记本,经常有人复制一个,做一些小的修改,然后将其作为自己的原创想法重新展示给其他 Kagglers。通常,人们还会从笔记本中挑选一个函数或代码的一部分,并将其插入到自己的工作中。在这两种情况下,请记住始终引用来源和作者。如果你无法追溯到原始作者,甚至只引用你找到所使用代码的最后一个笔记本也足够了。虽然展示的主要目的是展示你自己的努力和技能,但认识到你的代码或某些想法是从其他地方借鉴的非常重要。除了对其他 Kagglers 表示尊重的标志外,来源归属还表明你足够了解他人的努力和发明,并且知道如何在你的工作中应用它们。

在一定程度上,Kaggle 论坛上的讨论可以帮助你在数据科学和软件开发中引起对特定角色的关注。最初,Kaggle 上的讨论只是为了与组织者沟通或询问有关比赛本身的紧迫问题。然而,随着讨论获得了自己的用户排名和掌握等级,你可以在论坛上找到更多关于这些讨论的信息。

参考第 4 章,利用讨论论坛,了解 Kaggle 上的讨论介绍。

根据我们的经验,Kaggle 上的讨论可以分为四个类别:

  • 竞赛解决方案,详细解释(有时辅以相关笔记本)团队如何达到私人排行榜上的某个位置

  • 在比赛中帮助并解释需求

  • 感谢,赞美和闲聊

  • 帮助和指导其他竞争对手,向他们解释事情

我们观察到,在最后一种帖子中表现出色并因此广受关注可以帮助你获得开发者倡导者的角色,特别是如果你还有其他活跃的渠道与你的数据科学家同行互动(例如,Twitch 或 YouTube 频道、Twitter 账户或 Medium 博客)。

随着大型公司和初创公司中开发者倡导者角色的增长,对能够帮助其他数据科学家和开发者在项目中取得成功的专家的需求日益重要。如果你想了解更多关于这个角色的信息,以下在 draft.dev 上的文章解释得相当详细和全面:draft.dev/learn/what-is-a-developer-advocate

利用数据集

Kaggle 竞赛经常受到批评,因为它们展示的数据已经过清洗、井然有序,并且与现实世界中的数据相去甚远。我们的观点略有不同;我们发现 Kaggle 在竞赛中展示的数据也可能相当杂乱或噪声。有时展示的数据在质量和数量上可能不足以获得高分,你将需要在网上寻找额外的数据。

在数据科学项目中,Kaggle 缺失的是在组织的数据仓库和文件中收集和整理数据的过程,这个过程在现实世界中是不可能标准化的,因为它因公司而异,因问题而异。在现实世界中的数据处理主要应在现场学习。

将数据集引入 Kaggle 的目的是为了缓解人们认为 Kaggle 仅仅关注建模问题的想法。在这一点上,Kaggle 数据集非常有帮助,因为它们允许你创建和上传自己的数据,并记录特征及其值;同时,它们还要求你通过规划更新或完全替换数据的频率来管理你的数据。

请参阅第二章使用数据集组织数据,以了解 Kaggle 数据集的介绍。

更有趣的是,在 Kaggle 数据集中,你还被赋予了将使用 Kaggle Notebooks 构建的不同分析和模型附加到数据集的机会,这些模型可以是你在竞赛中想出的,或者是因为你仔细研究了上传的数据,发现了一些可以用它解决的问题。

此外,Kaggle 数据集还为你提供了一个模板,用于检查伴随你的数据的元信息的完整性。描述、标签、许可、来源和更新频率:这些只是所需信息(用于计算可用性评分)的一小部分,这些信息将帮助任何使用你数据的人了解如何使用它。你甚至可以在描述或讨论中指出与你想用它完成的工作相关的数据集任务。这是一种很好的方式来传达你对已上传数据的潜在价值的全面理解。

以前,任务曾是 Kaggle 数据集功能的一部分,但最近已经被移除:www.kaggle.com/product-feedback/292674。尽管如此,你可以使用数据描述和讨论来指出你期望你的数据可以用于什么。

所有这些特性使 Kaggle 数据集成为展示你在 Kaggle 上解决问题的经验以及一般的数据和机器学习算法能力的一个非常好的方式,因为它们允许你:

  • 发布和维护数据集

  • 用任务路线图展示你已理解数据的价值

  • 展示编码和完全工作的解决方案(因为 Kaggle Notebooks 可以立即在相同的数据上工作,无需任何准备),从数据准备到解释性数据分析再到预测建模

我们强烈推荐使用 Kaggle Datasets 来展示你在 Kaggle 竞赛或任何其他项目中完成的工作,因为它们将你的工作与其他人的工作区分开来,并整合了数据和 Notebooks。简而言之,Kaggle Datasets 可以向任何人展示你已实施的解决方案。然而,也存在一个缺点:你大部分时间都绑定在 Notebook 环境中(即使你使用脚本),这在包和版本要求方面并不完全透明,其他人需要知道才能在其他环境中运行代码。

事实上,Kaggle Notebooks 依赖于由配置文件、Dockerfile设置的 Docker 环境,该文件决定了已安装的版本。在浏览 Notebook 时,除非检查此配置文件,否则无法立即看出正在使用哪些版本的包。为此目的,以及为了复制设置,可以在 GitHub 上的 Kaggle 仓库中找到 Dockerfile(github.com/Kaggle/docker-python/blob/main/Dockerfile.tmpl),尽管它随时间变化,你可能需要跟踪你工作中使用的版本。

最后,除了这个方面,别忘了,即使只是浏览一下 Dataset 及其相关的 Notebooks,也需要访问 Kaggle 社区。

加布里埃尔·普雷达

www.kaggle.com/gpreda

我们与数据集、Notebooks 和讨论方面的 Kaggle 大师、Endava 的首席数据科学家加布里埃尔·普雷达进行了一次鼓舞人心的职业导向谈话。加布里埃尔拥有计算电磁学的博士学位,在完全投身数据科学之前,他在软件开发领域有着漫长的职业生涯。当他发现 Kaggle 时,他感觉在这个平台上如鱼得水,并投入了大量时间和精力,这在职业上为他带来了回报。

Kaggle 是否帮助你在职业生涯中取得进展?如何?

Kaggle 帮助我加速了在数据科学领域的学习曲线。在 Kaggle 之前,我四处寻找信息来源或要解决的问题,但方法并不系统,效果也不明显。在 Kaggle 上,我找到了一个与我兴趣相同的人组成的社区。我能够看到领域顶级专家的工作,从他们发布的带有分析或模型的 Notebooks 中学习,从他们那里获得洞察,向他们提问,甚至与他们竞争。在我加入 Kaggle 的时候,我主要在数据分析领域,但很快我就开始竞争;这意味着学习如何构建、验证和迭代改进模型。在 Kaggle 上大约两年后,我改变了我的职业道路;我从管理软件项目转变为全职数据科学工作。Kaggle 也给了我一些知名度,在我目前公司的面试中,候选人提到他们想加入我们,因为他们看到我在那里工作。

你是否曾经将你在 Kaggle 上完成的项目作为你的作品集的一部分,向潜在雇主展示?

我将我的 Kaggle 作品集作为潜在雇主的主要信息来源;我的 LinkedIn 个人资料指向我的 Kaggle 个人资料。此外,近年来,雇主对 Kaggle 的了解越来越多,其中一些雇主会具体询问你的 Kaggle 个人资料。还有一些潜在雇主明确表示他们不认为 Kaggle 相关。我不同意这种观点;就我个人而言,在面试候选人之前,我通常会检查他们的 GitHub 和 Kaggle 个人资料。我发现它们极其相关。一个好的 Kaggle 个人资料不仅能展示技术技能和某些语言、工具、技术或解决问题的经验,还能展示一个人通过讨论和 Notebooks 进行沟通的能力。这对于数据科学家来说是一个非常重要的品质。

你首先在 Notebooks(Kernels)中达到了大师级别,然后在讨论中,最后在数据集中。你能告诉我们你的经历吗?

我成为了第七位 Kernels 大师,并达到了第三名的排名。我想可能有两年的时间我在 Kernels 等级结构中排名前十。我开始写 Kernels 主要是为了在分析我发现更有趣的数据集的同时提高我对 R 语言的知识。我还尝试了各种技术,包括多边形裁剪、构建 Voronoi 多边形的双网格和 2D Delaunay 三角剖分。我逐渐开始专注于探索性数据分析,然后是构建数据集和比赛的模型。此外,一旦我开始更多地参加比赛,我就开始用 Python 编写用于比赛的 Kernels。大约在同一时间,我开始注意到我的某些 Kernels 引起了 Kagglers 的注意,主要是点赞和分支,但也有积极的评论。我写的某些用于在活跃比赛中探索数据的 Kernels 达到了非常广泛的受众,并为我赢得了许多金牌;因此,我达到了大师级,然后是大师级。目前,我很少发布与比赛相关的 Kernels;大部分我创建的是与发布的数据集相关的起始 Kernels。

接下来,我还获得了讨论大师级别。我从未预料到我会达到这个讨论层级。首先,我开始评论他人的 Kernels。然后,随着我越来越多地参与比赛,我的大部分评论都是在活跃比赛的讨论部分,要么是询问这些比赛中感兴趣的话题,要么是开始新的话题,例如,为比赛中的一个问题提出解决方案或收集资源来解决与比赛相关的各种开放性问题。我想提到我添加的一组特殊评论。作为一名 Kaggle Kernels 大师(第一批之一),当我发现非常好的内容时,我经常点赞新 Kagglers 的 Notebooks。

在这种情况下,我会尽量找一些时间来赞扬(尤其是如果内容质量很高)作者的成就。特别是对于初学者来说,除了通过点赞来表达你的赞赏之外,还能提供一些关于他们贡献的积极反馈,可能会给他们带来信心上的提升,使他们更愿意投入学习和在 Kaggle 上做出更多贡献。我喜欢这样做,并希望这能有所帮助。我曾经还编制了一份关于如何在 Kaggle 上评论的推荐清单。这是清单:简短(但不要太简短);具体;提供信息,而不是意见;有机会时赞扬他人的工作;保持冷静并尽量提供帮助;除非有意义(例如,如果是一个讨论,你需要将评论直接指向在那一线程中回应你的人),否则不要在评论中提及他人。

我达到的最后大师级别是在数据集领域。这也是我达到最高排名,第二名的级别。我在排名上的进步很慢。我从我喜欢的事情开始。在数据集中获得高知名度需要投资于数据的整理、清洗和文档化。如果你不喜欢这样的事情,你很可能不会继续下去。我追求的是对我个人和更广泛的社区都重要的事情:对我的国家、我的大陆,甚至是整个世界。我发布了关于我国家选举的数据集,以及关于欧洲各种社会、人口和经济主题的数据集。我专注于时事主题,这些主题对社区既有相关性又非常重要。例如,在疫情期间,我发布了关于 COVID-19 病例、疫苗接种、检测和病毒变种的全球数据集。我收集了超越简单数值、表格值的数据。文本数据,尤其是来自人们的直接贡献,为许多人提供了重要的见解。我获得最高点赞的数据集之一包括关于疫苗神话、板球、大流行、体育赛事和政治人物等多样化主题的 Reddit 帖子和评论或 Twitter 帖子(推文)。我在自动化数据收集、数据清洗和数据处理脚本上投入了大量精力。这为我节省了宝贵的时间(特别是对于频繁更新的数据集——其中一些是每小时通过脚本收集的)但同时也使我对整个过程有了更好的控制。每次我发布一个新的数据集时,我也会写一个或多个起始核。这些核并不是为了吸引大量观众。我创建它们作为潜在用户的数据集辅助核,以便他们更容易使用数据。在许多情况下,我更喜欢保留原始数据(正如我收集的,或从替代来源下载的)并包括一个用于数据清洗、转换和初步分析的核,以及这个过程的结果,即以更易访问的格式呈现的数据。通过这种方式,我试图在数据集中捕捉到比数据本身更多的内容;我还提供了关于数据转换技术的信息。

在 Kaggle 之外安排你的在线存在

由于 Kaggle 数据集和笔记本需要 Kaggle 账户,你必须考虑到并非每个人都已经有一个,或者只是为了查看你的作品而想要创建一个。你还必须考虑更易于访问的替代方案。更常见的是,Kagglers 选择在 GitHub (github.com/) 上创建一个项目,在 Medium (medium.com/) 上写文章,以及其他发布平台,或者在自己的博客上发布。然而,还有其他机会来推广你的作品和技能,例如:

如你所推断,确实有很多机会和媒体可以通过它们来传播你在 Kaggle 上的工作和技能,具体取决于你想要达到的目标。在本章中,我们的重点是仅仅博客和 GitHub 的存在(这是最常见的选项,而且相当有效),但你完全可以根据自己的需求选择任何不同的方法。

博客和出版物

写作可以是一种既能够精炼你的知识——因为你需要阅读某个主题以便撰写关于它的文章——又可以让他人了解你和你技能的方式。因你的写作而出名可以帮助你在多个方面,从被招聘人员和公司注意到到为 Kaggle 竞赛以及你更广泛的专业生活建立联系。

社交媒体(LinkedIn、Twitter 和 Facebook)允许你发布想法和短篇文字,这是我们建议你利用的资源。鉴于数据科学和 Kaggle 竞赛主题需要长时间的讨论和推理,然而,最佳的方法是撰写长篇文章并通过博客或发布写作的网站来发布。理想情况下,我们建议你协调社交媒体和你的文章之间的沟通,以推广它们,通过专门的帖子宣布它们或讨论你写作中的关键点。

让我们先讨论一下你可以在哪里发表你的文章。

Medium 上的文章,尤其是像Towards Data Science (towardsdatascience.com/)这样的Medium 出版物,可以获得很多关注。Medium 出版物是围绕一个共同的主题或话题撰写的故事的共享空间,通常由多位作者共同完成。作为一个网站,Medium 可以触及广泛的读者群,并且一些出版物在数据科学社区中以文章质量而享有很好的声誉。一个出版物可以有一位或多位编辑来挑选作品并确保其内容与出版物的政策和质量水平保持一致。你可以发布文章的 Medium 出版物包括:

每一份这些出版物都拥有一个巨大的优势,那就是它们已经拥有了一个庞大的读者群,可能比你社交媒体上的关注者还要多。你将获得的读者数量可能会超出你的预期,能够触及到公司以及其他你可以建立联系的专业人士。

除了 Medium 之外,这些其他网站也可能接受你的出版物:

  • Hacker Noon (www.publish.hackernoon.com/): 在技术博客作者中非常受欢迎,包含任何与技术相关的内容(相当广泛)。每月有四百万的读者,如果你想要触及众多对技术感兴趣的人,这是一个合适的地方。出现在首页非常困难,是一把双刃剑:你将获得很多关注,同时也会面临许多批评。

  • Dev.to (dev.to/): 主要面向开发者(近八十万人)的读者群,提供关于编码的文章和教程。你的帖子应该更注重你代码的质量和效果(建模处于幕后)。

  • FreeCodeCamp (www.freecodecamp.org/news/developer-news-style-guide/): 更专注于教程;人们去那里学习如何编码。对于推广机器学习课程和推广新软件包来说,这是一个理想的地方。

  • Analytics Vidhya (www.analyticsvidhya.com/about/write/):在印度非常受欢迎;它更多地围绕解释机器学习和深度学习基础的文章。

  • KDnuggets (www.kdnuggets.com/news/submissions.html):数据挖掘领域中最古老的出版物之一。它仍然在数据科学家和学者中拥有相当多的追随者(2021 年 3 月有 100 万独立访客)。

每个出版物都有其优点和缺点,并且在其触及的受众方面有所不同,因此你必须决定哪一个更适合你的内容。首先浏览他们提供的出版物,以便了解你的写作如何融入其中。

当然,如果你愿意,你也可以使用你自己的博客。拥有自己的博客有其优点,比如没有广告或编辑对你所写内容的审查。另一方面,你不能利用现有的受众,你必须通过在社交媒体上推广你的文章来努力创建一个受众群体。你可以在你选择的网络域名上从头开始建立自己的网站,或者你也可以在 GitHub 上创建自己的博客。

如果你决定使用 GitHub(因为它免费,你可能已经用它作为代码的存储库),这里有一个创建 GitHub 博客文章的简单快速指南:jmcglone.com/guides/github-pages/

如果你需要更加自动化的服务,可以使用像杰里米·霍华德fastpagesgithub.com/fastai/fastpages)这样的平台,因为它可以简化你处理带有代码示例的内容的方式,因为它会自动将笔记本和 Word 文档转换为博客页面,并为你发布。

如果你希望完全独立并建立自己的网站,这将需要更多的努力和一些费用;域名和网站空间并不是免费的。在这种情况下,自我推广你的内容变得至关重要。

写作关于你解决方案的主要优势是叙事元素,因为你必须伴随你的代码片段进行描述和解释,并且你需要以一种比在笔记本中更详细的方式写作。从某种意义上说,你描述你的工作的重要性与你所写的代码一样。通过调整你写作的语气,你可以接触到不同类型的受众。以易于理解的方式写作概念意味着你会扩大你的受众群体并与更多专业人士建立联系。相反,以高度技术化的方式写作可能会给那些可能考虑雇佣你的潜在公司留下深刻印象,尽管这限制了你的读者数量。

由于写作是一个非常个人化的行为,我们的提示和建议可能不适用于每个场景,因此我们的一般建议是在事先决定你写作的目的以及你希望通过它接触到的人。

GitHub

除了撰写文章和提供可以直接引导读者访问的代码仓库外,将您的代码放在 GitHub 上还可以帮助您在参加的每个比赛中不必重新发明轮子。您可以将希望重用的代码存储在项目或Gistsdocs.github.com/en/github/writing-on-github/editing-and-sharing-content-with-gists)中,这些是单独可访问的小段代码。

即使您可能希望将所有代码都留在 Kaggle 上,但随着时间的推移,您会发现很难访问,甚至可能完全找不到。这是因为您无法将 Kaggle 笔记本组织成单独的项目;它们将仅作为一个可以按一些属性(如投票数或您上次运行笔记本的时间)排序的长列表呈现。GitHub 使您更容易找到所需内容并重用它们。例如,您可以创建包含所有代码的脚本,然后将其下载并导入 Kaggle 笔记本,而无需复制任何内容。

在以下示例中,我们下载并重用了用于tabular神经网络的辅助函数:

!wget https://raw.githubusercontent.com/lmassaron/deep_learning_for_tabular_data/master/tabular.py
# Importing from Tabular
from tabular import gelu, Mish, mish
from tabular import TabularTransformer, DataGenerator 

使用wget命令可以直接访问 GitHub 上的代码并将其下载到笔记本的磁盘上;之后,您只需从其中导入所需的函数和类即可。要获取提供直接访问您代码的链接,您只需在 GitHub 仓库中找到包含该代码的文件,然后点击页面顶部的原始按钮:

图片

图 13.1:GitHub 上可视化文件的标题。注意标题栏右上角的原始按钮。

点击原始按钮后,您将被带到 GitHub 上存储文件的网页地址。您可以使用该网址从 GitHub 外部引用该文件。

GitHub 对于存储您可以在 Kaggle 讨论中使用的图像也非常有用(因为您不能再上传 Kaggle 论坛上的图像)。在图像的情况下,您不会有一个可以点击的原始按钮,但您可以右键单击图像并在另一个标签页中打开文件;这将产生相同的效果。

GitHub 是展示您工作的另一种绝佳方式,但鉴于网站的性质(针对开发者)以及您可以上传的内容(包含代码的文件),您应该预期会有一个非常技术性的观众。在公司中,人力资源部门可能不会深入查看您的 GitHub 账户,而是停留在README.md上,因此它应该写得很好,视觉效果吸引人。另一方面,招聘经理会更关注您项目中的代码。您应该在文件、程序和类中投入精力,使其结构良好,同时包括安装和复制您结果的必要说明。

你将不得不使用conda(docs.conda.io/en/latest/)或poetry(python-poetry.org/)等工具来确保为你的代码安装了正确的包。为了给你的项目提供最佳的结构,你可能需要像CookieCutter(drivendata.github.io/cookiecutter-data-science/)这样的东西。使用 CookieCutter 提供的模板为你的项目,可以轻松地将你的代码组织到特定的目录中,并提供允许其使用和理解的文件。一个 CookieCutter 模板将使你的项目更容易阅读、理解和维护。

最后,为了管理你的实验和数据源,你还需要为所使用的数据提供一个版本控制系统,而不仅仅是代码的版本控制系统,例如使用数据版本控制DVCdvc.org/)。所有这些资源和你需要正确运行它们的技能(创建你的环境、构建你的项目、版本化数据和模型)更接近于软件工程而不是数据科学能力。它们在 Kaggle 上不是那么相关——或者可以以简单的方式进行——并且将需要努力和学习。然而,它们将成为你将在 GitHub 项目上展示的能力的一部分,提高你在面试官面前留下好印象的机会。

如果你想要展示你模型的实时演示,你有几种不同的选择。最简单的方法是将代码运行在原始 Notebooks 上(只需在 GitHub 项目的README.md文件中放入你的 Kaggle Notebook 的链接)或Google Colab上。要让存储在 GitHub 上的 Notebook 在 Google Colab 中自动运行,只需将链接的域名从github.com更改为githubtocolab.com:链接将在 Colab 中打开你的 Notebook。

然而,你可以准备的最具印象的展示方式是使用HuggingFace Spaces(huggingface.co/spaces)来展示你的 Kaggle 模型如何在在线应用中使用。Spaces 是一种简单的方式来托管机器学习演示和创建你的在线作品集,如文档(huggingface.co/docs/hub/spaces)中所述。它们限制在 16GB 的 RAM 和 8 个 CPU 核心,但它们是免费的,足以展示你的模型如何在专用应用程序中运行。你可以在 HuggingFace 远程机器上安装你的依赖项,与 GitHub 同步代码和模型,或者使用Streamlit(streamlit.io/)或Gradio(gradio.app/)来构建应用程序。

例如,Kaggle 专家和哈佛大学教学助理Rashmi Banthiawww.kaggle.com/rashmibanthia)发布了她从Sartorious Cell Instance Segmentation比赛中的模型演示:huggingface.co/spaces/rashmi/Cell-Instance-Segmentation-MMDetection。通过将您的模型与一些示例一起在实时演示中展示,您甚至可以立即向非机器学习受众传达其有效性。

监控比赛更新和通讯

到现在为止,您可以看到在 Kaggle 上展示您的工作非常重要,这样您就可以向世界传达您对某些类型模型和数据问题的兴趣。从这个角度来看,您始终意识到竞赛提供的机会非常重要。

完成这一目标的主要方式是经常访问 Kaggle 网站,并同意接收他们的电子邮件。您可以从个人资料中的通知和电子邮件设置页面设置此选项,在那里您可以同意接收网站和电子邮件通知。您还可以选择接收包含 Kaggle 新功能和倡议提示的电子邮件,以及关于最近启动的比赛的新闻:

图片

图 13.2:Kaggle 发出的宣布一系列视频的电子邮件

如果您是 Twitter 用户,您会发现关注几个个人资料来了解 Kaggle 上的新内容很有用。Kagooletwitter.com/kagoole)是一个网络应用程序,可以通知您有关新比赛的信息,同时在其 Heroku 应用程序形式(kagoole.herokuapp.com/)中,为您提供过去比赛的解决方案。它是由Doarakkogithub.com/Doarakko/)创建的。您还可以关注的另一个 Twitter 个人资料是Is he Kerneler?twitter.com/HKerneler),由Regonngithub.com/regonn/)创建,它会告诉您每个活跃的 Kaggle 比赛关闭前还有多少时间。

如我们从第一章所知,Kaggle 并非唯一举办数据科学竞赛的组织。为了更好地跟踪 Kaggle 和其他数据科学竞赛网站上的实际情况,我们建议使用mlcontests.com/ods.ai/competitions等网站,这些网站监控 Kaggle 以及其他平台(如 AICrowd 和 DrivenData)上所有正在进行的比赛。例如,mlcontests.com为每个比赛提供有关奖项、截止日期和有用链接的信息。

它还从性能、机器和价格等方面提供了云 GPU 的比较。您可以通过注册邮箱直接接收大量此类信息到您的收件箱。

摘要

在本章中,我们讨论了如何展示您的工作,以及这如何有助于您的事业发展。它帮助您展示能力,虽然(当然)没有涵盖您整个数据科学知识和经验的全部范围,但仍然是一笔巨大的财富。

为了展示您的工作,您可以使用 Kaggle 资源或外部资源。Kaggle 资源为您提供了一个集成环境,如果您已经准备好了所有东西,那么它们相当容易访问且快速设置。外部资源(Medium 发布物、GitHub、HuggingFace Spaces 等)对于大多数招聘人员、人力资源官员和招聘经理来说更为人所知且易于访问,因为他们经常使用它们。

在下一章中,我们将通过讨论网络构建以及如何利用您的 Kaggle 努力获得面试机会,来完成我们对 Kaggle 竞赛为您提供的机遇的讨论。

加入我们书籍的 Discord 空间

加入书籍的 Discord 工作空间,每月与作者进行一次 问我任何问题 的活动:

packt.link/KaggleDiscord

二维码

第十四章:寻找新的职业机会

在上一章介绍了如何在竞赛中更好地突出你的工作和成就之后,我们现在将总结 Kaggle 如何积极影响你的职业生涯。最后一章讨论了利用所有努力寻找新的职业机会的最佳方式。我们期望你现在已经拥有了之前描述的所有工具(你的 Kaggle 讨论、笔记本、数据集,以及一个展示了许多来自 Kaggle 项目的 GitHub 账户),因此这一章将转向更软性的方面:如何建立人脉以及如何向招聘人员和公司展示你的 Kaggle 经验。

众所周知,建立人脉可以开启许多可能性,从被联系关于那些不在公开板上出现的新工作机会,到有可以依赖的人来解决你并非专家的数据科学问题。在 Kaggle 上建立人脉主要与竞赛期间的团队协作以及 Kagglers 组织的聚会和其他活动中的联系有关。

当谈到工作机会时,正如我们之前经常重复的,Kaggle 并不是人力资源和招聘经理广泛认可的选择候选人的来源。一些公司确实会认真考虑你的 Kaggle 排名和成就,但这只是一个特殊情况,而不是普遍规则。通常,你应该预期你的 Kaggle 经验会被忽视,有时甚至会被批评。然而,我们的经验告诉我们,你在 Kaggle 上学到的和实践的东西非常有价值,你可以通过展示你的编码和建模努力来推广它,并且能够谈论你独自或团队合作工作的经验。

在这里,我们将涵盖:

  • 与其他竞赛数据科学家建立联系

  • 参加 Kaggle Days 和其他 Kaggle 聚会

  • 被发现和其他工作机会

与其他竞赛数据科学家建立联系

人脉对于找工作至关重要,因为它们帮助你接触到可能知道机会在公开之前和开始寻找潜在候选人之前的人。近年来,Kaggle 越来越成为一个你可以与其他数据科学家建立联系、合作和交朋友的地方。在过去,竞赛并没有在论坛上引起很多交流,团队因为竞赛积分在团队成员之间平均分配而受到严重惩罚。排名的改进(见www.kaggle.com/general/14196)帮助许多 Kagglers 对组队有了更积极的看法。

如果你已经知道其他团队成员并且已经建立了分配任务和远程协作的方法,那么在 Kaggle 竞赛中组队是可行的。在这些情况下,每个团队成员都已经知道如何协作,通过:

  • 承担团队成员同意的实验部分

  • 与另一位团队成员合作构建解决方案

  • 基于他们的技能和经验探索新的解决方案

  • 准备模型和提交,以便它们可以轻松堆叠或混合

然而,如果你是第一次组队,你会发现要么进入一个团队,要么自己组织一个团队都很困难。除非你有联系,否则很难接触到排行榜上的其他人。首先,并不是所有人都会想组队,因为他们更喜欢独自竞争。此外,一些其他竞争者可能对组队感兴趣,但会过于谨慎而拒绝你的提议。在与你不太熟悉的 Kagglers 组建团队时,有一些担忧:

  • 加入团队的人不会为团队带来任何价值

  • 加入团队的人实际上不会进行合作,而只是成为免费搭车者

  • 加入团队的人违反(或将要违反)Kaggle 规则,这将导致整个团队的取消资格

  • 加入团队的人实际上对间谍活动和向其他团队泄露信息感兴趣

这些情况在比赛中通常是病态的,你应该意识到这些是许多人在评估是否第一次与另一位 Kaggler 组队时常见的考虑因素。只有通过展示你在 Kaggle 上拥有强大的背景,即一个人曾经独立参加过一些比赛,特别是发布了 Notebooks 并参与了讨论,你才能消除这些感知到的潜在问题。这将极大地增加你的提议的可信度,并更有可能使你被团队接受。

当你最终加入一个团队时,建立团队成员之间高效且专注的沟通方式非常重要(例如,在 Slack 或 Discord 上创建一个频道)。同时,达成关于日常操作的共识也是必不可少的,这些操作涉及以下两方面:

  • 决定如何分配你的实验努力。

  • 决定如何使用有限的每日提交(这往往是团队冲突的原因)。最终,只有团队领导选择最后的两个提交,但到达那里的过程自然涉及讨论和分歧。准备好向你的队友展示你为什么决定某些提交为最终版本,通过向他们展示你的本地交叉验证策略和结果。

在以积极的方式与团队一起工作后,你无疑会赢得其他团队成员的尊重和信任。在未来比赛中,你可能会发现与相同的人再次组队或加入他们帮助的另一个团队更容易。

你将在 Kaggle 上遇到并与之合作的人包括数据科学家、数据爱好者、学生、领域专家等等。下面,我们将与一群多样化的 Kagglers 进行交谈,他们描述了自己的日常工作以及 Kaggle 如何融入他们的生活。

Yirun Zhang

www.kaggle.com/gogo827jz

张一润是伦敦国王学院的最后一年博士生。他是笔记本和讨论的大师,曾是Jane Street Market Prediction竞赛(www.kaggle.com/c/jane-street-market-prediction)获胜团队的一员。

你能告诉我们一些关于你自己的情况吗?

我的研究领域在于将机器学习算法应用于解决现代无线通信网络中的挑战性问题,如时间序列预测、资源分配和优化。我还参与了研究 AI 隐私、联邦学习和数据压缩与传输的项目。

除了日常的博士生研究,我在 Kaggle 上活跃了将近两年,自从我的博士生第二年。我第一次参加的 Kaggle 竞赛是Instant Gratification,在这个竞赛中,我使用了来自sklearn库的多种机器学习和统计方法。这次竞赛帮助我形成了对 Kaggle 竞赛中机器学习建模流程的一般认识。

我在 Kaggle 社区中积极分享我的知识,包括笔记本和讨论帖子,现在已成为 Kaggle 笔记本和讨论的大师。通过在论坛上与其他人分享和讨论,我获得了宝贵的反馈和新知识,这也帮助我最近最终赢得了 Kaggle 竞赛。

请告诉我们你赢得的竞赛的一些情况。

Jane Street Market Prediction 确实非常艰难。原因是很难构建一个稳健的交叉验证(CV)策略,很多人只是将公共排行榜作为 验证集。他们训练神经网络数百个 epoch,而没有使用验证策略来过度拟合公共排行榜。我们的团队努力维持自己的 CV 策略,并在动荡中幸存下来。

你在 Kaggle 上的方法与你在日常工作中所做的方法有何不同?

Kaggle 竞赛与我的日常博士生研究非常不同。前者非常紧张,包含即时反馈,而后者是一个长期的过程。然而,我发现我从 Kaggle 竞赛中学到的新知识和方法论对我的博士生研究也非常有用。

大槻大辅

www.kaggle.com/osciiart

大槻大辅,又名 OsciiArt,是一位 Kaggler,他的日常工作并不涉及数据科学。他是大阪大学医院的医生,也是竞赛大师。

你能告诉我们一些关于你自己的情况吗?

我是一名在京都大学医院工作的二年级住院医生。我在京都大学获得了生命科学硕士学位。在我为一家制药公司从事研发工作后,我转到了大阪大学医学院,并获得了日本行医执照。

我开始自学数据科学和 AI,因为我被 AlphaGo 震惊了。我开始在 Kaggle 上参与比赛,以便学习和测试我在数据科学和 AI 方面的技能。我的第一个比赛是 2017 年的 NOAA 渔业 Steller 海狮种群计数。我一直在 Kaggle 竞赛中参与,并且获得了三枚金牌。*

Kaggle 是否帮助你在职业生涯中取得进展?

因为我没有 受过信息科学的教育,所以我使用我在 Kaggle 上的结果在申请 AI 公司实习和申请成为 AI 实验室的短期学生时展示我的技能。作为一个医生,我从未在我的主要工作中使用过我的数据科学技能。然而,多亏了我在 Kaggle 上的结果,我有时有机会参与医学数据研究。

你最喜欢的比赛类型是什么,为什么?

我最喜欢的比赛类型是 医学数据竞赛。我喜欢尝试利用我的医学知识从医学数据中找到一些洞察。

你如何处理 Kaggle 竞赛?

我喜欢寻找 竞赛数据中大多数其他竞争者没有意识到的一个秘密特征,或者尝试针对竞赛数据特征定制的独特方法。实际上,这种方法在大多数情况下并不成功,但仍然很有趣去尝试。

告诉我们你参加的一个特别具有挑战性的比赛,以及你用来解决任务的洞察。

我想 提及 Freesound Audio Tagging 2019,这是一个针对声音数据的多标签分类任务。训练数据由少量可靠标记的数据(干净数据)和大量不可靠标签的数据(噪声数据)组成。此外,精心整理的数据和噪声数据的数据分布存在差异。为了应对这一困难,我们采用了两种策略。第一种是多任务学习,其中在噪声数据上的训练被视为与干净数据不同的任务。第二种是伪标签(一种半监督学习),其中噪声数据被用从使用干净数据训练的模型中预测的标签重新标记。

你是否使用其他竞赛平台?它们与 Kaggle 相比如何?

我使用 Signate (signate.jp/) 和 guruguru (*www.guruguru.science/)。这些是日本的数据科学竞赛平台。它们不像 Kaggle 那样规模大;这些平台上的比赛通常使用的数据集比 Kaggle 小,因此更容易参与。此外,有时还有一些与 Kaggle 上不同的有趣比赛。*

图片

米克尔·博伯-伊拉扎尔

www.kaggle.com/anokas

米克尔·博伯-伊拉扎尔,又名 Anokas,是一位比赛大师、笔记本和讨论大师,同时也是 ForecomAI 的机器学习科学家。他还是剑桥大学计算机科学的学生,也是 Kaggle 上最年轻的比赛大师。

你能告诉我们一些关于你的信息吗?

我是在 2016 年加入 Kaggle 的,当时我 14 岁,对我在做什么一无所知 – 我只是在网上读到了机器学习,感觉很有趣。我开始参加我的第一场比赛,通过复制论坛上其他人的公开代码并进行一些小的调整。在几场比赛中,我逐渐了解了事物是如何运作的,这激励我试图攀登排行榜 – 直到我开始取得良好的进展,最终在当年的 Avito Duplicate Ads 竞赛中获得了第二名。

从那时起,我参加了 75 场比赛,2018 年成为最年轻的比赛大师,也是第一位三冠大师。我后来在萨里大学担任访问研究员,现在我在剑桥大学学习计算机科学,在那里我也在机器学习和安全领域进行研究。

你最喜欢哪种类型的比赛?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

我真的很喜欢那些有很多特征工程机会的比赛,以及那些有很多不同类型数据的比赛,这让你在解决问题的方法上可以非常具有创造性 – 这比那种每个人都要采取相同方法,你只是在争夺最后一位小数点的比赛有趣得多。

我并不认为我在方法上有特别的专长,但我喜欢尝试不同的事情。

请告诉我们你参加的一个特别具有挑战性的比赛,以及你使用了哪些见解来应对这项任务。

几年前,谷歌举办了一场竞赛,用于检测图像中的对象及其之间的关系(例如,“桌子上的椅子”)。其他团队花费了大量时间采用传统方法,训练大型神经网络来处理这些任务,而我没有这方面的知识或计算能力来竞争。我选择从不同的角度来解决这个问题,使用一些巧妙的启发式方法和树模型,最终在仅用几个小时的工作后获得了第七名。

Kaggle 是否帮助你在职业生涯中取得进步?

Kaggle 为我带来了很多机会,并且是一个真正优秀的社区,让我有机会了解它。我在参加的所有比赛中遇到了很多人,学到了很多东西。但 Kaggle 也是我最初进入机器学习领域的原因 – 我相信如果不是因为 Kaggle,我就不会在这个领域。所以,是的,它帮助了我很多。

在过去的比赛中,你犯过哪些错误?

很容易最终得到一个复杂的解决方案,你无法从头开始复制,因为你很可能会在最终解决方案中使用各种版本的代码和中间数据集。那么,如果你足够幸运赢得了比赛,向主办方交付可工作的代码可能会非常紧张!如果你表现良好,确定你的解决方案并清理你的代码是个好主意。

也很容易陷入这种情况:为不同的模型使用不同的验证集,或者不保留验证预测,这可能会使得比较它们或在进行比赛后期进行元学习变得困难。

你会推荐使用哪些特定的工具或库来进行数据分析或机器学习?

我真的很喜欢 XGBoost,它仍然倾向于在表格数据上击败神经网络(以及它的新近表亲 LightGBM)。SHAP 对于解释模型(即使是复杂的模型)非常好,这可以让你对下一步尝试什么有更多的洞察。

当人们参加比赛时,他们应该记住或做哪件事是最重要的?

我认为重要的是尽量避免陷入实现超复杂解决方案的困境,而是尝试逐步实现解决方案。

现在的比赛比我开始的时候难多了,所以看看别人的代码(很多人在比赛期间都会公开)并从中学习是个好主意。你可能还想考虑加入一个由其他 Kagglers 组成的团队:对我来说,团队比赛是最有趣的比赛,而且一直是一种极好的学习经历。

最后一点:大多数想法都不会成功——如果你想赢得比赛,你需要坚持不懈并不断实验!

Kaggle 无疑对前三位受访者丰富的生活和职业生涯产生了影响,他们才刚刚开始。以下我们将与两位现在在各自公司担任高级职位的 Kagglers 交谈,他们的长期而富有成效的旅程也得益于 Kaggle。

丹·贝克

www.kaggle.com/dansbecker

首先,我们有丹·贝克,他是 DataRobot 的笔记本大师和产品、决策智能副总裁。Kaggle 在丹的职业生涯中扮演了重要的角色。

你能告诉我们关于你自己的情况吗?

我第一次尝试使用机器学习是在 2000 年一个 3 人初创公司,我们试图使用神经网络来帮助零售商优化他们在 eBay 上设置的商品保留价格。我们根本不知道我们在做什么,我们彻底失败了。

到 2002 年,我确信机器学习永远不可能成功。我获得了经济学博士学位,并在美国政府担任经济学家。我想搬到科罗拉多州,但那里几乎没有寻找经济学博士的工作。所以我正在寻找一个不那么学术的资历。

在 2010 年,我看到了一篇关于Heritage Health Prize的报纸文章。这是一场早期的 Kaggle 竞赛,奖金高达 300 万美元。我仍然相信,像我作为经济学家所使用的简单模型,会比花哨的机器学习模型给出更好的预测。所以我开始参加比赛,认为在这个比赛中取得好成绩将是我在科罗拉多州找到一份有趣工作的必要凭证。我在那次比赛中的第一次提交并没有排在最后,但也很接近。当我看到我的模型被评分,然后看到其他人比我领先那么多时,我的心情沉重。我短暂地放弃了在比赛中取得好成绩的希望,但我不甘心连平均水平都达不到。

我花了所有的夜晚和周末时间来参加比赛,以爬升排行榜。我重新学习了机器学习,自从我第一次尝试以来,它已经进步了很多。我每天都会学习更多,上传一个新的模型。这花费了很多时间,但每天在排行榜上前进都是一种回报。当我的分数位于排行榜中间时,我认为继续工作可能会让我进入前 10%。所以我继续努力。很快我就进入了前 10%,心想我可能进入前 10 名竞争者。

当我处于前 10 名时,一家分析咨询公司联系我,问我是否愿意被雇佣并在他们的公司名下竞争,他们会用这个名字进行市场营销。我告诉他们,如果我能从科罗拉多州工作,我就愿意这么做。所以 Kaggle 竞赛帮助我实现了我的原始目标。

我们获得了第二名。第二名没有奖金,但自从那次 Kaggle 竞赛以来,我在职业生涯中所做的一切都得益于这次竞赛。这比我想象的还要成功。

Kaggle 还以其他方式帮助你在职业生涯中取得了哪些成就?

Kaggle 几乎完全塑造了我的职业生涯。我的第一份数据科学家工作是在有人从排行榜上招募我时开始的。在那之后,我在 Kaggle 工作。然后我在 DataRobot 工作,当时他们的招聘策略是雇佣在 Kaggle 竞赛中表现良好的人。然后我回到了 Kaggle,开始创建 Kaggle Learn,这是 Kaggle 的数据科学教育平台。这样的例子还有很多。我过去十年中每份工作都明显归因于我在 Kaggle 的最初成功。

当我从经济学转向数据科学时,我的 Kaggle 成就是我被雇佣的核心原因。现在我的职业生涯更深入了,我不太考虑投资组合...而且我很幸运,被招募的次数比我寻找工作的次数多。

你最喜欢的比赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

我在这个社区里已经待了很长时间,但我在过去 7 或 8 年里并没有全身心地投入到一场比赛中。我喜欢新的比赛类型。例如,我第一次接触到深度学习是在 2013 年,当时是 Kaggle 第一次举办深度学习具有竞争力的比赛。那时还没有 Keras、TensorFlow、PyTorch 或今天存在的任何深度学习框架。社区中没有人真正知道如何进行深度学习,所以每个人都在第一次学习新事物。

Kaggle 还举办了一场对抗性建模比赛,其中一些人构建了试图稍微操纵图像以欺骗其他模型的模型。这非常实验性,我不知道他们是否还会再次举办类似的活动。但我真的很喜欢实验性的东西,当社区中的每个人都在一起在论坛上解决问题时。

你是如何应对 Kaggle 比赛的?这种方法与你在日常工作中所做的方法有何不同?

在最近几次比赛中,我专注于“我能为这次比赛构建哪些工具来自动化我在各个项目中的工作”?这并不特别成功,但这是一个有趣的挑战。这与我处理其他所有专业事务的方法非常不同。

在比赛之外,我非常喜欢分析和查看有趣主题的数据。我有时会说,作为数据科学家,我的优势就是我只是查看数据(而不是通过机器学习模型过滤的数据)。

我还花了很多时间思考我们如何从机器学习模型的预测到我们做出的决策。例如,如果一个机器学习模型预测在下一批货物到来之前,一家杂货店将卖出 1,000 个芒果,那么这家杂货店应该持有多少库存?有些人认为应该是 1,000 个...正好是你预测可以卖出的数量。这是错误的。

你需要考虑购买过多芒果导致浪费的成本与耗尽库存的成本之间的权衡。它们的保质期是多久?你能在下一批货物到来之前携带额外的库存吗?那里有很多优化工作要做,这是我日常工作中的一部分,而这些内容在 Kaggle 比赛中并没有体现出来。

告诉我们你参加的一个特别具有挑战性的比赛,以及你使用了哪些见解来应对这项任务。

我尝试构建一个 自动化的系统,用于对 Practice Fusion 糖尿病分类挑战进行连接和特征工程。我从中学到的最主要的事情是,如果你有超过几个文件,你仍然需要一个人员来查看数据并理解哪些特征工程是有意义的。

在你的经验中,不经验丰富的 Kagglers 通常忽略了什么?你现在知道的事情,你希望在你刚开始的时候就知道?

新参与者没有意识到 在 Kaggle 比赛中取得好成绩的门槛有多高。他们认为他们可以跳进来,以相当通用的方法进入前 50%...但这通常不是真的。我最惊讶的是,在集成之前提交时,使用不同模型的排行榜分数来分配权重的重要性。

在过去的比赛中,你犯过哪些错误?

我在多阶段比赛中多次搞砸了 最后时刻的提交细节 (结果最终排名最后或接近最后)。

你会推荐使用哪些特定的工具或库来进行数据分析或机器学习?

大部分都是标准的东西。

在 Kaggle 比赛之外,我个人喜欢使用 Altair 进行可视化...并且我写了很多 SQL。SQL 是为查看简单的聚合或趋势而设计的,而不是构建复杂模型,但我认为这更像是一个特性而不是缺陷。

Jeong-Yoon Lee

www.kaggle.com/jeongyoonlee

最后,我们有 Jeong-Yoon Lee,他是 Netflix 研究团队中 Rankers 和搜索算法工程团队的多次获奖比赛大师和高级研究科学家。

你能告诉我们一些关于你的信息吗?

我叫 Jeong,是 Netflix 的高级研究科学家。我在 2011 年完成我的博士学业后,加入了一家分析咨询初创公司 Opera Solutions,并从那时起开始了 Kaggle。在那里,我遇到了热情的 Kaggle 竞争者,包括 Michael Jahrer,我们一起参加了 KDD 杯和 Kaggle 比赛。从那时起,即使离开公司后,我也继续作为竞争者和组织者参与比赛。最近,我在 Kaggle 上花的时间没有以前那么多,但仍然时不时地查看,以了解机器学习领域的最新工具和方法。

Kaggle 是否帮助你在职业生涯中取得进步?

极大地帮助了。首先,它提供了 机器学习的证书。许多招聘经理(当我还是面试者时)以及候选人(当我担任面试官时)都提到我的 Kaggle 记录引起了他们的注意。其次,它提供了机器学习最先进方法的培训。通过在不同领域超过 100 场比赛中的工作,我对几乎所有机器学习问题都比我同行更熟悉。第三,它提供了一个全球顶级数据科学家的网络。我在 Kaggle 遇到了许多有才华的数据科学家,并享受与他们一起工作。我翻译了 Abhishek Thakur 的书,与 Mario、Giba 和 Abhishek 一起在 KDD 上组织了一个讨论小组,现在正在面试 Luca 的书。;)

2012 年,我使用了由 Steffen Rendle 在 KDD Cup 2012 中引入的因子机,在我加入一家新公司一个月后,将预测性能提高了 30%,超过了现有的 SVM 模型。在我共同创立的一家初创公司中,我们的主要卖点是通过集成算法来 击败市场标准的线性回归。在 Uber,我引入了对抗性验证来解决机器学习管道中特征中的协变量偏移。

你最喜欢的竞赛类型是什么?为什么?在技术和解决方法方面,你在 Kaggle 上的专长是什么?

我喜欢小型到 中型数据集的竞赛,这些竞赛大多是表格数据竞赛,因为我可以在任何时间任何地点快速迭代不同的方法。在我 2015 年在 Kaggle 的巅峰时期,我经常在飞机上或在照看孩子的间隙构建我的解决方案。我的三胞胎在 2014 年底出生,我当时在一家我共同创立的新公司工作。

我认为我没有任何特殊的建模技术,但我的专长更多在于竞赛管理,这包括招募团队成员、建立协作框架(例如,Git、S3、Messenger、Wiki、内部排行榜、交叉验证分割)、在整个竞赛中帮助团队有效工作等。所以,我并不是竞赛大师,但能够进入前十,是因为其他大师喜欢与我合作。

你是如何应对 Kaggle 竞赛的?这种应对方式与你在日常工作中所做的是否有所不同?

我试图建立一个管道,使其能够快速迭代和逐步改进。你尝试的方案越多,你在竞赛中取得好成绩的机会就越大。这个原则也适用于我的日常工作。虽然范围不同。在工作时,我们首先定义问题并确定数据,而在 Kaggle,这两者都是既定的,我们从 EDA 开始。

在你的经验中,没有经验的 Kagglers 通常忽略了什么?你现在知道的事情,你希望在你最初开始时就知道?

最近,我发现许多 用户只是简单地复制其他用户分享的 Notebook,并对其进行微调以获得更好的分数。最终重要的是学习,而非 Kaggle 排名或积分。我建议新的 Kagglers 花更多时间构建自己的解决方案。

当人们参加竞赛时,他们应该记住或做什么是最重要的?

这关乎学习,而非胜利。

你是否使用其他竞赛平台?它们与 Kaggle 相比如何?

我正在担任 Dacon AI 的顾问,这是一家 韩国机器学习竞赛平台公司。它始于 2018 年,至今已举办了 96 场竞赛。与 Kaggle 相比,它还处于早期阶段,但为韩国用户提供了类似的经验。

参加 Kaggle Days 和其他 Kaggle 聚会

与其他 Kagglers 建立联系(以及更容易被团队接受)的一个好方法就是简单地见面。聚会和会议一直是这样做的好方法,即使它们并不专门涉及 Kaggle 竞赛,因为演讲者会谈论他们在 Kaggle 上的经验,或者因为话题已经在 Kaggle 竞赛中处理过。例如,许多研究竞赛要求成功竞争者撰写关于他们经验的论文,这些论文可以在会议演讲中被展示或引用。

直到 2018 年之前,没有与 Kaggle 直接相关的特殊活动,当时由Maria ParyszPaweł Jankiewicz创立的公司 LogicAI 与 Kaggle 合作,在波兰华沙举办了首届 Kaggle Days 活动。他们聚集了超过 100 名参与者以及 8 位 Kaggle 大师级演讲者。

随后还举办了更多的 Kaggle Days 活动。以下是安排的活动,以及相关材料和演讲的链接:

从巴黎的第二场活动开始,各种城市举办了形式为聚会的较小规模活动(在 30 个不同地点举办了超过 50 场聚会)。参加大型活动或聚会是结识其他 Kagglers 和交朋友的好机会,这对职业发展或未来 Kaggle 竞赛的团队合作都有帮助。

实际上,其中一位作者就是通过这种方式找到了他们的下一份工作。

被发现和其他工作机会

在一段时间内,Kaggle 是一个雇主可以找到数据分析与机器学习建模罕见技能的热点。Kaggle 本身就在讨论论坛中提供了一个职位板,许多招聘人员四处游走,在排行榜上寻找可以联系的个人资料。公司本身也举办比赛以明确寻找候选人(例如,Facebook、英特尔和 Yelp 都为此目的安排了招聘竞赛)或者在他们看到在某些类型的问题上表现出色后方便地挑选出最佳竞争者(例如,在电信竞赛之后,保险公司 AXA 就是这样做的)。这一切的顶峰由《Wired》杂志对 Gilberto Titericz 的采访所标记,其中提到“排名靠前的解算者收到了大量的工作邀请” (www.wired.com/story/solve-these-tough-data-problems-and-watch-job-offers-roll-in/)。

最近,事情有所变化,许多 Kagglers 报告说,当你赢得比赛或取得好成绩时,你能期望的最好的结果是招聘人员几个月内的联系。让我们看看事情是如何变化的,以及为什么会这样。

现在,你很少会找到要求 Kaggle 经验的工作机会,因为公司通常要求在该领域(甚至更好的,在同一行业或知识领域)有先前经验,在数学学科方面有学术背景,或者来自谷歌、亚马逊或微软的认证。你的 Kaggle 存在仍然会有些影响,因为它将允许你:

  • 被监控 Kaggle 排名和比赛的招聘人员发现。

  • 被公司本身发现,因为许多经理和人力资源部门都会关注 Kaggle 个人资料。

  • 有一些证明你编码和机器学习能力的证据,这可能有助于公司选择你,可能不需要你进行任何进一步的测试。

  • 有一些与特定公司高度相关的特定经验,这些经验你无法通过其他方式获得,因为数据并不容易对每个人开放(例如,电信、欺诈检测或深度伪造,这些都是 Kaggle 竞赛的主题)。

尽管如此,你的结果和排名很少会被简单地考虑,因为很难区分那些实际上是由于你的技能而产生的部分,以及那些对公司招聘你不太感兴趣的、影响结果的其他因素(例如,你可以用于比赛的时间、硬件可用性或一些运气)。

你的 Kaggle 排名和结果在以下情况下更有可能被注意到:

  • 你在一场对公司特别重要的比赛中取得了好成绩。

  • 你在多个与公司感兴趣的主题相关的比赛中系统性地取得了好成绩,这是一个真正的能力的标志,意味着你并不是没有坚实基础就简单地给自己贴上“数据科学家”或“机器学习工程师”的标签。

  • 通过你在 Kaggle 的参与,你展示了你对数据分析的真正热情,以至于你愿意在空闲时间免费投入。这是一个积极的方面,但也可能成为双刃剑,除非你表明你认识到自己的价值,否则可能会带来较低的货币报价。

虽然它们可能单独不起作用,但你的 Kaggle 排名和结果可以作为区分因素。招聘人员和公司可能会使用 Kaggle 排名来制作潜在候选人的名单。最引人注目的是竞赛和笔记本排名(因此,它们在四个排名领域中也有更激烈的竞争和更多的特级大师),但有时他们也会关注特定竞赛的排名。当寻找某些罕见的专业技能(例如,在自然语言处理或计算机视觉中)时,更容易在需要你巧妙使用它们才能成功的竞赛中找到它们。

另一个在面试时的显著区别是。你可以引用你的竞争对手来展示你如何解决问题,如何编码解决方案,以及你如何与队友互动和协作。在这些场合,除了你在 Kaggle 获得的名次或奖牌,更重要的是谈论 Kaggle 竞赛的具体情况,例如它所指的行业,你必须处理的数据类型以及为什么它对你有吸引力,还要展示你在竞赛中的行动,通常使用在面试中常用的STAR 方法

STAR 方法

在 STAR 方法中,你应该根据情境任务行动结果的框架来组织你在竞赛中所做的事情。这种方法旨在让你更多地谈论行为而不是技术,从而更注重你的能力而不是你选择的算法的能力;任何人都可以使用相同的算法,但只有你能够成功地使用它。

这种方法主要适用于处理成功故事,但你也可以将其应用于不成功的故事,特别是对于你从失败中获得了重要见解的情况,这些见解阻止你以同样的方式再次失败。

要应用这种方法,你需要将你的故事分解为四个部分:

  • 情境:描述情境的背景和细节,以便面试官可以一目了然地了解问题和机会

  • 任务:描述你在该情境中的具体角色和责任,帮助界定你在技能和行为方面的个人贡献

  • 行动:解释你为了处理任务采取了哪些行动

  • 结果:展示你行动的结果以及整体结果

一些公司明确要求使用 STAR 方法(或其相关方法目标-影响-挑战-发现,其中更注重结果);而另一些公司虽然没有明确要求,但期望类似的方法。

最好的回答是那些符合你面试的公司价值观和目标。

由于仅仅报告你在比赛中获得的排名和奖牌可能不足以给面试官留下深刻印象,因此重新表述你在 Kaggle 比赛中的成功经验至关重要。这种方法无论是单独竞争还是团队竞争都适用;在后一种情况下,一个重要的描述方面是如何与其他队友互动并积极影响他们。让我们讨论一些你可以这样做的方法。

首先,你描述在比赛中出现的情况。这可能是初始阶段、实验阶段或最终总结阶段。为了使听众能够评估你的行为是否适合该情况,提供清晰的背景信息非常重要。请非常详细地解释情况以及为什么它需要你的注意和行动。

然后,你应该解释你承担的任务。例如,这可能包括清理数据、进行探索性分析、创建基准模型或持续改进你的解决方案。

接下来,你描述你是如何执行任务的。在这里,如果你能展示一篇 Medium 文章或一个 GitHub 项目来支持你的描述(正如我们在上一章中讨论的那样),将会非常有帮助。通过精心编写的文档和良好的编码系统地展示你的经验和能力,将加强你在面试官面前的价值主张。

最后,你必须解释获得的结果,这可能是有形的(例如,你如何协调在 Kaggle 上竞争的团队的工作)或量化的(例如,你的贡献对最终结果的影响有多大)。

摘要(以及一些告别的话)

在本章中,我们讨论了如何在 Kaggle 上竞争如何有助于提高你的职业前景。我们谈到了通过在比赛中组队以及参与与过去比赛相关的事件来建立联系,以及如何利用你在 Kaggle 上的经验来寻找一份新工作。我们讨论了基于我们的经验和其他 Kagglers 的经验,仅凭 Kaggle 上的成绩并不能保证你获得一个职位。然而,它们可以帮助你吸引招聘人员和人力资源部门的注意,并加强你在数据科学领域展示能力的方式(如果它们得到了我们在上一章中描述的精心构建的简历的支持)。

本章也标志着本书的结束。通过十四章,我们讨论了 Kaggle 比赛、数据集、笔记本和讨论。我们涵盖了机器学习和深度学习的技术主题(从评估指标到模拟比赛),目的是帮助你不仅在 Kaggle 上,而且在 Kaggle 之后都能取得更大的成就。

在 Kaggle 竞赛中参与十年,我们非常清楚,你可以在 Kaggle 上找到你可能需要知道的一切——但所有这些内容都分散在数百个竞赛和数千个笔记本、讨论和数据集中。在你需要的时候找到你需要的东西,对于任何刚开始在 Kaggle 上的人来说可能会是一项艰巨的任务。我们汇编了我们认为至关重要的、不可或缺的知识,以指导你通过你可能想要参加的所有竞赛。这就是为什么这本书不仅仅是一本严格意义上的数据科学书籍,而是一本专门关于 Kaggle 数据科学的书籍。

除了技术和实用提示之外,我们还希望传达,在过去的十年里,我们始终找到了将我们在 Kaggle 上的经验转化为积极经验的方法。你可以将这份作品当作一本描述我们穿越数据科学竞赛世界的无尽旅程的书来重新阅读。在获得所有大师头衔并排名世界第一时,你的 Kaggle 之旅并没有结束。实际上,它永远不会结束,因为你可以用无数种方式重新发明你参与竞赛和利用竞赛经验的方式。随着这本书的结束,你在 Kaggle 上的旅程才刚刚开始,我们祝愿你有一个漫长、丰富且硕果累累的经历——就像我们一样。祝您旅途愉快!

加入我们书籍的 Discord 空间

加入书籍的 Discord 工作空间,每月与作者进行一次“问我任何问题”的会议:

packt.link/KaggleDiscord

posted @ 2025-09-03 10:09  绝不原创的飞龙  阅读(17)  评论(0)    收藏  举报