TowardsDataScience 博客中文翻译 2019(四百六十一)

原文:TowardsDataScience Blog

协议:CC BY-NC-SA 4.0

构建成功的数据科学项目

原文:https://towardsdatascience.com/structuring-a-data-science-project-for-success-ff3ecbdc8187?source=collection_archive---------22-----------------------

我在微软实习的经验教训

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

I wonder if all this was written for the stock photo. Looks real.

数据科学项目是棘手的野兽。目标是获取大量原始数据并产生有价值的可操作的见解。问题是,数据科学项目本质上是探索性的——你不知道什么方法会奏效,也不知道你会找到什么见解。所有这些不确定性是一些常见项目疾病的温床:

  1. 项目蠕变:随着项目的进行,项目的范围逐渐扩大。如果不加以处理,项目范围将扩大到无法完成的程度。这种疾病通常是由好奇心和注意力之间的不平衡引起的。
  2. 项目停滞:你遇到了一个 bug 或数据问题,它会让你停滞几天甚至几周,让你没有动力去做项目。治疗的第一步是接受你停滞不前的事实,然后坦率地和你的团队谈论你面临的问题。

通过构建您的项目,您可以开发管理不确定性的方法,避免一些项目疾病,并按时完成项目。

在微软实习期间,我在教育客户洞察研究团队工作。该小组的目的是使用数据来帮助定义微软在教育领域的战略。我今年夏天的项目是分析教育机构如何使用微软的应用程序和服务。这是一个广泛的项目想法,而且是有目的的——我的经理想知道我会用它做什么。以下是我收到的建议和学到的经验。

1.设定截止日期

为什么在学校学一门新语言比在家里容易?事实是,许多人在截止日期、明确的目标和想象不到的后果方面做得很好。我是他们中的一员,你可能也是。

我打印出我实习的三个月的日历,然后垂直地粘在一起。我用蓝色标出了我将要出差、参加会议或不参与项目的日子,用红色标出了最后期限。在你的办公桌旁放一个可视化的时间表(俗称“日历”),可以很容易地检查你的进度。贴在我办公桌附近也增加了一点隐性的社交压力。我怀疑是否有人花时间看我的日历,但是清楚地定义并公开发布我的截止日期让我更倾向于遵守它们。

个人期限是脆弱的;他们需要所有能得到的外部压力。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Your calendar need not be Instagram ready.

以下是一些你可能想包括的截止日期:

  1. 提案初稿:你第一次发给你的经理/团队的文件(开始后 1 周)
  2. 提案终稿:在你发出这份提案后,你可以对项目做一些调整,但你必须开始全职处理数据(开始后 1.5-2 周)
  3. 签到:记下一些常规的,让自己负责任
  4. 开始停止:选择一个日期,在这一天你将停止全职编码,并开始拼凑一个故事(在最终演示前 2 周)
  5. **首次演示:**向熟悉项目的小组演示,收集初步反馈(最终演示前 0.5-1 周)
  6. 展示日
  7. 报告/演示最终草案

2.尽早与利益相关者交谈

最糟糕的事情莫过于有人在谈话中途插嘴,提出不相关但却很坚定的观点。数据科学就是这样。如果你是一个组织或项目领域的新手,你可能不知道足够的信息来产生好的见解。抱歉。

然而,您的组织中可能有许多人已经在该领域工作了一段时间,对业务如何运作有直觉。你应该尽快和这些人谈谈。如果你正在研究顾客行为,和销售或设计部门谈谈。如果你打算提供策略建议,和项目经理谈谈。

了解他们的工作。描述你的项目,让他们思考你的目标。这可能需要时间,但最终你可能会梳理出有价值的方向。对我来说,我发现与项目经理、用户研究人员和工程师交谈很有帮助。我和他们谈了很多次,但是最重要的是早期的谈话,那时我正在制定一个提案并确定项目的范围。

3.确定项目范围

如果你像我一样,你会有一个宏伟的想法,你的项目将使用最先进的算法,并将解决现在和未来永远的问题。这个宏伟的想法实际上可能需要 6 个月和 4 名工程师来实现。

你可能不会实现这个伟大的想法。

你应该确定项目的范围。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Don’t lose sight of the trees for the forest.

你的第一个目标应该是完成最小可行分析。想一想利益相关者看重什么,调整你的项目,这样你就可以用简单的方法生产出更多的东西。将一些放弃的目标作为达成目标。当你做最后陈述时,有人问“你在 Y 的情况下考虑过 X 吗?”,你可以冷静地回答,“是的,我可能应用了方法 Z,但是那超出了项目的范围。”

一个更简单的完成的项目比一个花哨的不完整的项目要好。

4.开始工作

当你开始处理数据时,事情会变得非常糟糕,而你对此无能为力。实际上是有的,你应该尽快开始处理数据。不要忽视计划阶段,但是不要忘记一些数据流会让你抓狂。最好能快点发现。在我耗时两周的项目中,浏览多层客户许可是一件非常痛苦的事情。我没有办法预测它,它阻止了我实现一些目标,但这没什么大不了的。

5.开始停止

如果你在期末报告前三天尝试一些新奇的方法,你可能做错了。记住,你需要传递的是洞察力,而不是方法。项目经理们更看重一个奇特的算法,而不是你将见解翻译成英语的想法。一位实习同事创造了这种“社交数据”。我觉得行业行话就是“讲故事”。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Imagine a cogent, data-driven Scheherazade. Now become her.

是的,你的分析是开创性的,全世界都因为没有花时间去理解它而变得愚蠢。但事实就是如此。你必须做 80%的艰苦工作,将你的结果翻译成通用语言,并开发令人难忘的可视化或仪表板。计划至少花整整一周的时间来做这件事。

在一次与工程师的会议上,我问了工程师一个关于业务的问题,他拿出了我团队中的一位数据科学家几年前制作的仪表板。我的经理指出,这应该是每个数据科学家的梦想。您希望开发易于理解、团队以外的人在日常工作中采纳和引用的见解。思考永恒的研究。

6.不要觉得有义务报告你所学到的一切

还记得不久前你去的那个有很多数字的无聊演示吗?很可能是因为缺乏焦点。关注与您的提案目标最一致的可行见解。回想一下你在高中英语课上学到的说服性写作。其中一个关键因素是了解你的受众。试着考虑只发布能打动观众的内容:利益相关者。因为你早就和他们谈过了,这应该不会太难。

重要的是,你的项目应该讲述一个有清晰开头、中间和结尾的故事。“结尾”应该包括你对利益相关者的明确建议。即使我有一点不确定,我也经常喜欢把一个想法写下来,这样我就有东西可以辩论,而不是错过一个发展思路的机会。但是你应该确定你画了这条线。

7.编写可重用的代码

使用函数和类来保持代码的模块化和整洁。编写测试——它们不仅仅是为软件工程师准备的!众所周知,机器学习算法很难调试。在我实习期间,我最初根据客户对应用程序和服务的使用情况生成了一个客户亲和矩阵。然而,几天后我才意识到,我用距离而不是相似性得分填充了这个相似性矩阵,我的结果是谎言。(我修好了它,一切都很好。)防止谎言传播;编写测试。

代码 50%是计算机指令,50%是为其他人编写的计算机指令文档。文档记录良好的代码加上写得很好的演示或报告应该会给你的分析带来最大的生存机会。

我希望我学到的经验能对你的项目有所帮助。祝你好运!

特别感谢 Sooraj Kuttykrishnan、Jodie Draper 和微软 EDU 团队的其他成员,感谢他们给了我一个美好的夏天和永恒的指导。

如果你对这篇文章有想法,或者想了解更多关于我所做的工作,请通过媒体关注我,并通过 LinkedIn 与我联系。

结构化数据预测未来,又名项目通量电容器

原文:https://towardsdatascience.com/structuring-data-to-predict-the-future-aka-project-flux-capacitor-7133715d7e57?source=collection_archive---------26-----------------------

数据准备是一个好的机器学习(ML)模型的核心,但获得正确的数据需要时间。很多时间!不要害怕。

为了帮助传递一些知识并使您的过程更容易,我和我的团队发布了代码来支持、加速甚至改进您构建 ML 模型的方式,如倾向模型、客户生命周期价值模型、推荐系统等。

大卫、凯西鲁克山为开发这一解决方案并使之成为可能而付出的努力让他们感到无比自豪。

我们走吧!

在幕后,代码利用谷歌云数据流来运行 Apache Beam 管道,并利用谷歌云 BigQuery 来读取和保存。将大量数据转换成 ML ready 格式的强大而快速的方法。

我们已经优化了代码,可以插入到一个 Google Analytics BigQuery 表中,但是如果有用的话,也可以插入到其他 BigQuery 数据源中。输入您的 BigQuery 表,运行管道,然后——瞧——输出带有结构化特征和标签的新 BigQuery 表。准备好使用你最喜欢的 ML 工具来训练一个 ML 模型,比如 AutoML-Tables,BigQuery ML,Tensorflow,Python sklearn…等等。

渴望一头扎进去?去吧…

或者,如果你能坚持听我说几分钟,了解通量电容管道如何构建数据对于理解其价值非常重要。我试图向我的非技术伙伴(yikes)解释这种技术,所以决定在这里发表我的想法,作为一个更非官方的——希望更有趣的——解释。

为什么要通量电容器?

我们的正式项目名称是 ML 数据窗口管道。尽管如此,一切都需要一个有趣的名字,当我们根据过去发生的事情构建数据来预测未来时,Flux Capacitor 似乎很合适。正是这些数据让“时间旅行”成为可能,🧀。好了,先把这些劣质的东西放在一边,让我来解释一下它是做什么的。

“医生”布朗想买辆车

让我们考虑一个随机的人,称他为医生。在这个例子中,Doc 从我们的网站上购买了一辆新车。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image Christopher Lloyd as Dr. Emmett Brown — Back to the Future

我们的汽车网站上有谷歌分析设置,让我们记录和查看文件的浏览活动。让我们记住,实际上我们只查看匿名指标(我们只是为了好玩才使用文档参考)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image made by author — User timeline of events

这是代表活动的时间线,蓝色方框表示导致购买事件的交互。每次互动都有一些我们可以从谷歌分析中获得的指标,例如浏览器、一天中的时间、访问的页面、点击量等。

让我们从一个时间快照来看这个时间线中的细节,并将该快照称为日期 d 。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image made by author — User timeline of events from Date d

现在,关于日期 d ,我们可以开始回顾过去一段时间,并将文档的会话信息汇总在一起。这个定义的时间段被称为回顾窗口

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image made by author — User timeline of events with Features

在这个例子中,Google Analytics 交互被聚合以创建新的指标,例如最频繁的页面、浏览器、活跃天数、总点击数等。它们代表了我们将用来训练 ML 模型的特征。

接下来,从我们的日期 d 开始,我们通过指定的时间量来预测未来,并将此称为预测窗口

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image made by author — User timeline of events with Features and Label

因此,就数据而言,如果购买发生在相对于日期 d 和日期 d预测窗口内,那么它发生在日期 d 和日期 d 。换句话说,该标签基于在预测窗口期间是否发生了购买事件。

这是这里的关键,所以我们的模型是根据未来会发生什么以及如何预测来训练的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image made by author — User timeline of events with Features and Label

这代表了我们将用来训练 ML 模型的标签。

一旦我们为文档——我们的匿名用户——提取了与 d ate d 相关的特征和标签,我们就会在表中看到一行,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image made by author — User Features and Label for Date d

然后,我们为网站上的所有用户在日期拍摄这些快照。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image made by author — Users timeline of events with Features and Label

生成的表格看起来像这样:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image made by author — Users Features and Label for Date d

然后,我们以间隔 w (例如,一天、一周…等)将日期 d 移动一个设定的滑动窗口,以针对 d + w、d + 2w…等为所有用户及时创建更多快照。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image made by author — Users snapshots over time

随着时间的推移,通过多个快照,我们的数据集可以了解数据如何变化以适应季节性(最近和频率信息)。

“伟大的斯科特!”

结果。一个强大(大)的数据集,准备好训练你的 ML 模型。

最后要记住的是成本。简单运行 ML 数据窗口管道代码,比如使用 Google Analytics 样本数据集,应该是最少的。然而,生产成本将取决于您的数据大小。管道被设计为尽可能高效,但请注意谷歌云数据流定价指南谷歌云平台定价计算器,以确保没有意外。

我希望这是有用的,我们很乐意在评论区看到你是如何应用它的。

PS。如果你从未看过电影《回到未来》,我向你道歉!因为许多这些类比/图片可能看起来有点随机。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image Meme — Made at imgflip

在 Raspberry Pi 上运行对象检测的困难

原文:https://towardsdatascience.com/struggles-of-running-object-detection-on-a-raspberry-pi-fa61b50a3b9f?source=collection_archive---------19-----------------------

斗争是真实的!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Frustrated man (Image by Gerd Altmann from Pixabay)

你是否曾经在阅读了操作指南或 github 自述文件后兴奋不已,却发现事情并不像他们说的那么简单?

我遇到过无数这样的例子,我发现我下载的一些解决方案或者我遵循的一个教程简单地工作了,这是一个罕见的时刻。不过那一刻!如果你对此有共鸣,也有自己的故事可以分享,我很高兴听到这些*(欢迎在下面留下评论)*。

现在,我将分享我在 Raspberry Pi 上运行对象检测的奋斗故事。

我喜欢尝试新技术,并看到它的工作情况。就在一年前,我接触到了 Raspberry Pi,这是一款小型、经济的设备,你可以在上面安装摄像头和其他传感器,部署你的 python 代码,并立即看到它的工作情况。多酷啊。我立刻从 Raspberry pi Australia 的网站上为自己订购了一台,配有 Pi 相机、Raspberry Pi Sense 帽子和一个小外壳,迫不及待地想玩它。

当我在接下来的一周拿到 Raspberry Pi 3 Ultimate Kit 时,我已经准备好了对象检测 python 脚本,可以在 Pi 中复制并运行它。我打开包装,取出圆周率和圆周率相机,组装好,放入黑色外壳,并连接到电源。我没有鼠标,也没有键盘,我有一台电视和一根 HDMI 电缆,这些都在树莓 Pi 套件中,所以我的第一个挑战是将其连接到互联网。我的第二个想法是将我的 python 脚本复制到 Pi 中。几个小时的研究和尝试不同的东西,人们不得不说在线,让我的 Pi 连接到互联网和 VNC 浏览器,我现在能够从我的笔记本电脑远程连接到它,并复制我的 python 脚本。

如果你正在努力使用 wifi 将你的 Pi 连接到互联网并远程访问它,使用这个教程“ 直接连接到你的树莓派 ”,它非常有用,简单易懂,更重要的是,它很有效!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Raspberry Pis (Photo by Jeff Loucks on Unsplash)

如果我的程序能马上工作,我会跳上跳下,可能会在我的阳台上大喊,告诉每个人它工作了。可悲的是,没有成功。它抱怨缺少库,甚至处理一个又一个库,似乎从未停止。不知何故,我甚至在 Pi 中破坏了 pip 安装。我认为这也是许多其他开发者面临的问题,缺少依赖性,版本不受支持,平台不兼容,这个列表永远不会结束。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Error (Image by bartekhdd from Pixabay)

如果有一个我可以运行的脚本或程序,它会为我安装所有需要的库和依赖项,这不是很好吗?

一定要这么难吗?

第二天,我终于在一个 Raspberry Pi 上实现了对象检测代码,它可以检测不同的对象,并在其周围绘制一个边界框。我用的是当时最先进的物体检测模型 YOLOv3 ,非常快速准确。我很开心。Yeyy!终于!

我注意到带有边界框的输出视频滞后,就像当你的互联网连接不好,而你正试图在 YouTube 上观看视频时一样。很糟糕!我很兴奋它的工作,但同时也有点失望,最快的物体检测在 Pi 上不够快。我的意思是,给我的父母或朋友看,它甚至不能做一个好的演示。

所以我的下一个目标是让它在圆周率上跑得更快。

经过大量的研究,我发现我并不是唯一一个在这个问题上挣扎的人,这个问题也不容易解决。事实上,这是大多数希望在边缘设备上运行对象检测的开发人员讨论的挑战之一。

“……我们能够达到每秒 0.9 帧,这还不足以构成实时检测。尽管如此,考虑到 Pi 的处理能力有限,0.9 帧/秒对于某些应用来说仍然是合理的。”—计算机视觉专家和博客作者 Adrian Rosebrock 在他的一篇博客中提到。

有一些像 SqueezeNet 和 MobileNetSSD 这样的模型,开发人员正在讨论,它们针对 Raspberry Pi 进行了优化,可以获得更好的结果,但同样,这需要一个学习曲线。我很沮丧,我不得不经历这么多麻烦来做一个我想要的小实验。如果速度是问题所在,我怎么能指望用它来实现实时解决方案呢?可能有些情况下它不需要实时处理帧,每 10-20 秒处理一帧是可以接受的,但是需要更频繁地检测物体的解决方案呢?

最终,我将 YOLOv3 的 darknet 实现用于一个预先训练好的小型 YOLOv3 模型。推理速度比我所拥有的要好,但是我对解决方案的整体性能并不满意。我有很多问题和顾虑。

为什么它占了这么多空间?我不能并行运行任何其他代码。

为什么圆周率这么热?感觉随时都会炸。

我的 Pi 经常死机,我不得不重启它。我不知道为什么会这样,也不知道如何解决。

这个 Pi 到底为什么一次次重启?

我一直在寻找一种比 YOLOv3 在树莓 Pi 上表现更好的更快的物体检测方法,直到三个月前,我发现悉尼的初创公司 Xailient 的表现超过了 YOLOv3 和 Tiny YOLOv3,并树立了一个新的基准。这对我来说是一个好消息,我知道,无论如何我必须亲自接触他们的对象检测模型并进行测试。三个月后,我来到了 Xailient (这是另一个时间的故事),在 Pi 上试验当前最先进的实时物体检测。

在 Raspberry Pi 上运行对象检测时,你遇到过哪些挑战?在下面的评论中分享你的想法。

原载于**

医学中的学生 t 检验:阐明其直观概念

原文:https://towardsdatascience.com/students-t-test-in-medicine-shedding-light-on-its-intuitive-concepts-fa5d7734aae?source=collection_archive---------8-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

统计学术语被技术术语和复杂的数学符号所困扰。纵观我在生物医学领域的经历,我遇到过两种类型的人:一种是对这种抽象语言感到绝对着迷的人,另一种是每次看到有几个希腊字母的公式就惊恐地逃离的人(尽管他们的研究是基于这些公式的结果)。

我主要为医生和生物医学研究人员工作,他们对应用统计学有着早已被遗忘的知识,所以我最终花更多的时间回答解释性的问题,而不是做分析。这是神秘的统计数据变得如此简单的直接结果。

最常见的问题之一也来自医学中最流行和最常用的统计测试之一:学生的 t 检验和 p 值。

但是学生的 t-test 是什么?

为什么使用学生的 t 检验

给定一个连续变量,Student 的 t 检验允许**比较两个样本的平均值。**简单明了,不涉及重大复杂问题。

对于喜欢数学记数法的人来说,Student 的 t 等于均值之和,除以组合标准差,再乘以 2 的平方根除以观察次数。这比听起来简单:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为了说明学生的 t 检验,我们将设计一个小实验。假设我们想进行一项适度的临床试验,在试验中,我们打算评估一种新药在使用一个月后是否能降低患者的血液胆固醇水平。为此,我们选择了 100 名高胆固醇血症(> 240 mg/dL)患者,并将他们随机分配到两个治疗组(对照组或治疗组)中的一个,比例相同。这样,我们将拥有:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Figure 1. Simple study design where 100 patients, whose cholesterol blood concentration is above 240 mg/dL, are randomly assigned to one of the two treatment groups: control or treated.

一旦研究完成,我们如何知道我们测试的药物是否足够有效?这就是学生 t 检验变得有用的地方。但是首先,我们必须定义我们相信会发生什么。

告诉我可能的假设

这是我经常说的一句话:要在医学中正确使用统计学,良好的前期实验设计是最重要的。必须在开始任何实验之前确定假设。为什么会这样?每个统计测试对不同事物的评价是不同的。最合适的测试将在很大程度上取决于我们如何制定我们最初的假设。这是一个常见的错误来源,可能导致不确定的结果,甚至更糟,错误的结论!

在开始一个像我们上面设计的简单实验之前,我们必须定义两个假设:零假设(T0)和替代假设(T4),零假设(T1)和替代假设分别是 h₀(T2)和 h₁(T7)。

本实验的适当的零假设可以定义为 H₀: (x̅₁ - x̅₂) = 0 ,在我们的例子中,这意味着当比较对照组( x̅₁ 和治疗组( x̅₂ )的平均值时,胆固醇血症没有差异。这很直观:如果血液胆固醇浓度没有变化,对照组和治疗组的手段是一样的,对吗?****

相反,替代假设可以由 H₁: (x̅₁ - x̅₂) ≠ 0 来定义,它设置了与前一种情况相反的情况。如果我们测试的药物引起胆固醇水平的变化,两组平均值的差异必须是非零的。

无效假设(H₀ ) : 治疗对胆固醇水平没有影响。

替代假说(H₁ ) : 治疗改变胆固醇水平。

这些假设设置了理想的场景,使用学生的 t 检验来评估它们,t 检验专门评估均值之间的差异。一旦假设成立,这些方法应该有多大的不同,这样我们才能确定新的治疗方法是有效的?在统计学中,这就是 p 值的用武之地,但首先,我们将从视觉和数字上探索我们的数据。

看看你的数据!

将我们的测量结果存储在电子表格、笔记本或数据库中是非常常见的。这是数据存在的地方,但我们人类一旦达到了容量和密度的临界点,就很容易失去洞察力。尽管在本例中完全可以用肉眼得出结论,强烈建议通过视觉探索数据来了解每个治疗组的表现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Figure 2. Density graph and boxplot representing the distribution of populations for the control group (in blue) and the treated group (in orange). The vertical dashed line (in red) shows the initial cholesterol and inclusion criteria for the study.

**乍一看,我们看到两组分布的中心沿着横坐标轴彼此分离,这意味着,先验地,它们的均值不同(稍后我们将看到是否充分)。此外,我们可以看到治疗组中的大多数个体低于 240 mg/dL 的阈值,考虑到在开始临床试验之前患者的胆固醇浓度高于该值,这是明显改善的指标。

如果我们用数字的形式来研究集中趋势和分散程度,我们会得出同样的结论。显然,这种新药有效!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Table 1. Measures of central tendency and dispersion of each treatment group.

但是在科学上,特别是在研究人们的健康时,我们不能简单地根据两条曲线被一条垂直线移动和分开的事实来说一种治疗是有效的。我们不得不求助于数字的客观性。

什么是 p 值

当医生理解 p 值的真正含义时,他们解释研究结果的方式通常会发生范式转变。需要澄清的是,Student 的 t 检验(以及更多的统计检验)假设零假设为真,这意味着他们从药物没有效果的基础出发。

用技术术语来说, p 值是假设零假设为真,获得与我们的数据中观察到的结果一样极端的结果的概率(从 0 到 1)。例如,如果我们获得的 p 值为 0.6,就有可能发现均值之间的差异,就像我们在 60%的情况下观察到的那样。科学界一致认为,当测试的 p 值低于 0.05 时,该测试被认为具有统计学意义。

这是这里的关键概念**:p 值并没有提供任何关于替代假设的确定性的证据(这正是我们感兴趣的),它只是说机会无法解释我们在数据中观察到的可变性。这是统计学结束和解释开始的地方:你如何解释为什么另一个假设可能是真的?**

解释结果

在进行学生 t 检验之前,根据所使用的统计软件,会产生几个疑问。我的样本有关系吗?我应该假设方差相等吗?单尾还是双尾?

对于第一个问题,在我们的特殊情况下,我们定义的两个治疗独立的**,因为每个组由不同的人组成。如果我们在临床试验的第一天,即给药前测量胆固醇浓度,并在治疗一个月后再次测量胆固醇,以随后分析同一患者随时间推移发生的差异,情况就会相反。在这种情况下,我们应该认为我们的措施是相关的。这就是实验的设计阶段如此重要的原因!**

为了检查两组之间的方差是否相等,有必要执行一个 F 检验,我们不会在本文中讨论。一般来说,我们可以假设当方差之间的比值接近 1 (127/134 = 0.94)时,方差实际上是相等的。这影响了学生的 t 检验,因为如果方差足够不同,我们必须使用韦尔奇校正(这也是另一篇文章)。

当 Student t-test 问我们是想进行单尾还是双尾检验时,基本上是问我们是否知道我们期望的结果会改变我们的均值。在医学上,这转化为我们是否期望药物增加降低胆固醇浓度,或者是否两者可能同时发生。在生命科学中,在对人体进行试验之前,通常会有一些关于药物预期效果的先验证据:体外,动物实验等。为此,执行单尾测试是相当常见的。但是,如果我们想对学生的 t 检验进行更严格的限制,并且假设在我们最初的替代假设中我们确定存在差异(但不是在哪个方向),我们将使用双尾检验。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Table 2. Output of a Student two-tailed t-test for 2 independent samples, assuming equal variances.

我们首先再次发现的是每个治疗组的手段。乍一看,有 50 毫克/分升的绝对差异。但这有关系吗?获得的下一个参数是 t 值,它表明观察到的均值差异几乎是我们数据中可变性大小的 22 倍。太多了!这转化为低于 0.01 的 p 值,如果我们回忆之前的解释,这表明我们在患者中发现像我们观察到的那样极端的变异性的概率不到 1%,这种变异性可以用纯粹的概率来解释。综上所述,我们拒绝零假设,接受另一个假设。

**最后两个参数是 95% **置信区间:其下限和上限。这告诉我们,在大多数情况下,两个值之间的真正差异。对于这种特殊情况,我们发现与对照组相比,胆固醇的差异在 44.70 和 53.88 mg/dL 之间。物理学家应该从这里开始进行临床解释!

那么,新药有效吗?

好吧,统计学永远无法回答这个问题,它只是给我们一些线索,告诉我们某些事情正在发生,而这不是偶然的。这里开始解释部分。在医学上,找到有统计学意义但与临床无关的证据并不罕见。对于这种特殊情况,我们治疗的患者的平均浓度为 220 毫克/分升,仍然高于临床推荐的限值(~200 毫克/分升)。是的,他们有所改善,但也许其他药物的效果更好,副作用更少,等等。甚至可能发生的是,尽管已经发现了显著的差异,但我们所看到的是由于其他因素,如临床试验期间饮食的变化,身体活动的增加,甚至是基因变异。作为研究人员,我们必须考虑所有可能的选择,并在得出结论之前进行测试。

从这个样本实验中得到的信息应该是:对于初始浓度超过 240 mg/dL 的患者,这种治疗可以将胆固醇降低到 45 到 54 mg/dL 之间。

这足以证明它的用途吗?看你问谁了!同样,这是统计学不能(也不应该)回答的解释性部分。

最后

这个例子非常简单(它来自于 R 中的模拟分布),但是我认为它确实说明了学生的 t 检验是有用的。在实际的临床实践中,要考虑更多的变量,甚至是它们的部分贡献,对此学生的 t 检验是不够的。这就是回归和混合模型发挥作用的地方。

记住:要报告一个学生的 t 检验,你必须包括所有的参数: p 值是不够的!有无数的论文只提供了最后一个参数,正如我们已经解释过的,这个参数很少甚至没有提到实际的差异,也不能让我们了解我们观察到的效应的大小。请给出置信区间!

像大师一样设计熊猫数据框

原文:https://towardsdatascience.com/style-pandas-dataframe-like-a-master-6b02bf6468b0?source=collection_archive---------0-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by billow926 on Unsplash

什么是造型,为什么要在意?

造型背后的基本思想是利用视觉辅助手段,如颜色和格式,以便更有效地传达洞察力。

可视化数据集的最常见方式之一是使用表。表格允许你的数据消费者通过读取底层数据来收集洞察力。例如,您可能会发现自己处于这样的场景中,您希望使用表为您的消费者提供对底层数据的访问。

在本文中,您将学习如何通过使用 pandas 样式选项/设置为 pandas 数据框架添加可视化。熊猫文档本身是相当全面的,但是如果你正在寻找稍微友好的介绍,我想你来对地方了。我将使用 kaggle’ " 旧金山工资数据集"作为一个例子,像往常一样,我们首先使用 pandas 加载数据集。

熊猫代码加载数据集和一些基本的数据管理:

df = pd.read_csv('Salaries.csv')\
       .replace('Not Provided', np.nan)\
       .astype({"BasePay":float, "OtherPay":float})

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

This is the rendered dataframe of “San Fransisco Salaries

熊猫选项/设置 API

熊猫有一个选项系统,可以让你定制它行为的某些方面,这里我们将着重于显示相关的选项。在使用渲染数据框时,您可能会遇到以下问题:

  • 数据框中的列/行太多,中间的一些列/行在显示时被忽略。
    例如,如果想要显示最多 7 行和最多 7 列,可以:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 包含长文本的列被截断,包含浮点的列仅在显示器上显示过多/过少的数字。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

熊猫造型 API

正如我们提到的,pandas 也有一个样式系统,可以让你使用 CSS 定制渲染数据帧的某些方面。您编写一个“样式函数”,它采用标量、DataFrameSeries,并返回 like-indexed 数据帧或带有 CSS "attribute: value"对的序列作为值。

最简单的样式示例是在处理货币值时使用货币符号。例如,在我们的数据中,一些列(BasePay、OtherPay、TotalPay 和 TotalPayBenefit)是货币值,所以我们希望添加美元符号和逗号。这可以使用style.format功能来完成:

Pandas 代码通过格式化货币列来呈现数据帧

df.head(10).style.format({"BasePay": "$**{:20,.0f}**", 
                          "OtherPay": "$**{:20,.0f}**", 
                          "TotalPay": "$**{:20,.0f}**",
                          "TotalPayBenefits":"$**{:20,.0f}**"})

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这些样式函数可以递增地传递给在呈现前收集样式的Styler,因此,如果我们想添加一个函数来格式化 EmployeeName 和 companyTitle,可以使用另一个style.format函数来完成:

Pandas 代码呈现数据帧,也将一些列格式化为小写

df.head(10).style.format({"BasePay": "$**{:20,.0f}**", 
                          "OtherPay": "$**{:20,.0f}**", 
                          "TotalPay": "$**{:20,.0f}**",
                          "TotalPayBenefits":"$**{:20,.0f}**"})\
                 .format({"JobTitle": **lambda** x:x.lower(),
                          "EmployeeName": **lambda** x:x.lower()})

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在看起来好多了,但是让我们再往前走一步这里的索引没有添加任何真实的信息,我们可以使用下面的代码片段使用hide_index函数来取消索引的显示:

Pandas 编写代码来呈现不带索引的格式化数据帧

df.head(10).style.format({"BasePay": "$**{:20,.0f}**", 
                          "OtherPay": "$**{:20,.0f}**", 
                          "TotalPay": "$**{:20,.0f}**",
                          "TotalPayBenefits":"$**{:20,.0f}**"})\
                 .format({"JobTitle": **lambda** x:x.lower(),
                          "EmployeeName": **lambda** x:x.lower()})\
                 .hide_index()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在呈现我们的数据集时,拥有这种类型的灵活性是非常强大和有用的,但这还不够。您可以应用条件格式,这是一个数据帧的可视样式,取决于其中的实际数据。最简单的例子是样式 API 中的内置函数,例如,可以用绿色突出显示最高的数字,用彩色突出显示最低的数字:

熊猫代码也强调最小/最大值

df.head(10).style.format({"BasePay": "$**{:20,.0f}**", 
                          "OtherPay": "$**{:20,.0f}**", 
                          "TotalPay": "$**{:20,.0f}**",
                          "TotalPayBenefits":"$**{:20,.0f}**"})\
                 .format({"JobTitle": **lambda** x:x.lower(),
                          "EmployeeName": **lambda** x:x.lower()})\
                 .hide_index()\
                .highlight_max(color='lightgreen')\                            
                .highlight_min(color='#cd4f39')

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

另一个有用的函数是background_gradient,它可以突出显示一列中的值的范围。此外,cmap参数允许我们为渐变选择调色板。matplotlib 文档列出了所有可用的选项(seaborn 也有一些选项)。

熊猫代码,也增加了背景渐变

df.head(10).style.format({"BasePay": "$**{:20,.0f}**", 
                          "OtherPay": "$**{:20,.0f}**", 
                          "TotalPay": "$**{:20,.0f}**",
                          "TotalPayBenefits":"$**{:20,.0f}**"})\
                 .format({"JobTitle": **lambda** x:x.lower(),
                          "EmployeeName": **lambda** x:x.lower()})\
                 .hide_index()\
                 .background_gradient(cmap='Blues')

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当风格实际上不依赖于值时,甚至可以使用styler.set_properties 。在这个例子中,我们将使用黑色背景和绿色文本来呈现数据集。

Pandas 编码为每个单元格以相同的方式呈现格式化的数据帧。

df.head(10).style.set_properties(**{'background-color': 'black',                                                   
                                    'color': 'lawngreen',                       
                                    'border-color': 'white'})

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

但是,如果我们诚实的话,大多数时候我们希望根据值和我们想要强调的内容来更改可视化属性,我们可以使用以下方法之一来帮助实现我们的目标:

  • Styler.applymap(func)针对元素风格。
  • Styler.apply(func, axis=0)用于列样式。
  • Styler.apply(func, axis=1)针对行式风格。
  • Styler.apply(func, axis=None)用于表格风格。

第一个例子是突出显示数据帧中的所有负值。

如果值是一个字符串,则用改变的字体颜色呈现格式化的数据帧

df.head(10).style.format({"BasePay": "$**{:20,.0f}**", 
                          "OtherPay": "$**{:20,.0f}**", 
                          "TotalPay": "$**{:20,.0f}**",
                          "TotalPayBenefits":"$**{:20,.0f}**"})\
                 .format({"JobTitle": **lambda** x:x.lower(),
                          "EmployeeName": **lambda** x:x.lower()})\
                 .hide_index()\
                 .applymap(lambda x: f”color: {‘red’ if isinstance(x,str) else ‘black’}”)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后,pandas 样式 API 还支持更高级的样式,如在列中绘制条形图,我们将在这里介绍bar函数和一些参数来配置它在表中的显示方式:

df.head(10).style.format({"BasePay": "$**{:20,.0f}**", 
                          "OtherPay": "$**{:20,.0f}**", 
                          "TotalPay": "$**{:20,.0f}**",
                          "TotalPayBenefits":"$**{:20,.0f}**"})\
                 .format({"JobTitle": **lambda** x:x.lower(),
                          "EmployeeName": **lambda** x:x.lower()})\
                 .hide_index()\
                 .bar(subset=["OtherPay",], color='lightgreen')\
                 .bar(subset=["BasePay"], color='#ee1f5f')\
                 .bar(subset=["TotalPay"], color='#FFA07A')

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

结论

pandas 风格的 API 和 options API 在您的数据分析接近尾声并需要向他人展示结果时非常有用。字符串格式化有几个棘手的部分,所以希望这里突出显示的内容对您有用。

高清图像上的 GANs 风格转换

原文:https://towardsdatascience.com/style-transfer-with-gans-on-hd-images-88e8efcf3716?source=collection_archive---------0-----------------------

如何使用 GANs 生成高清图像,而无需昂贵的硬件

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

简介

最近的一些研究探索了使用 GANs(生成对抗网络)生成高清晰度图像(1024×1024 像素)的一些方法和技术。看到超逼真的,由算法生成的人脸、动物和其他事物的高清图像,尤其是记得几年前的第一张 GAN 图像,令人难以置信地惊讶。我们很快就从低质量的像素化图像发展到了接近现实的图像:这是该领域研究进展有多快的一个非常清晰的证明。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

StyleGAN HD face samples

但是阅读这些最近的研究(最相关的是 Nvidia 的 StyleGAN 和 Google 的 BigGAN 论文),我总是发现一个方面能够降低我的惊讶和兴奋感:计算能力。发现巨大的计算能力被用来完成那些图像让我意识到在我和那些结果之间有一个不可逾越的障碍。光是这个想法就让我觉得研究中探索的整个新技术对我来说都很遥远,因此不那么令人惊讶。

这就是为什么在这篇文章中,我想探讨如何使 GANs 和 HD 图像一起工作而不需要昂贵的硬件,为不一定有机会使用高级 GPU 的人打开新的机会。这里解释的一切都可以使用免费的谷歌合作实验室平台来实现,该平台为你所有的机器/深度学习项目提供免费的 GPU。

我们的目标

我们将尝试在高清图像的两个域之间执行风格的传输,使用特殊但简单的 GAN 架构来执行我们的任务。更具体地说,我们将把梵高的绘画风格应用到风景的高分辨率图像上。公平地说,在过去的几年里,风格转换一直是计算机视觉中的一个热门话题;开创潮流的原始论文是**《一种艺术风格的神经算法》** (Gatys 等人),在一个预训练的卷积网络上使用了一个内容和风格损失来执行任务。虽然这种方法可以在高清图像上工作,但它只能使用一个单幅图像(假设是“星夜”)作为画家风格的表示,这不是我们想要的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Style Transfer examples from the original paper

另一方面,GAN 通常需要一个图像域来进行训练,因此在我们的情况下能够完全捕捉画家的风格(CycleGAN 的论文显示了风格转移的有趣结果)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

CycleGAN Style Transfer examples

然而,训练 GANs 在计算上极其昂贵**:高分辨率图像的生成只有在非常高端的硬件和长训练时间的情况下才有可能。我希望本文中解释的技巧和技术能够帮助你进行高清图像生成的冒险。**

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Van Gogh paintings

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传****外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

HD images translated to Van Gogh Style

我们开始吧!

架构

我们要努力实现的叫做图像到图像的翻译(从域 A 到域 B)。有不同的方法和网络架构来实现它:最著名的可能是 CycleGAN,但也存在许多关于同一主题的其他论文。

在我的实验中,我使用了一个定制的架构,该架构包含一个作为鉴别器的暹罗网络和一个特殊的(但超级简单的)损失函数。我选择这种方法是因为它不依赖于任何损失中的每像素差异:这仅仅意味着网络不遵循生成图像的任何几何约束,因此能够创建更令人信服的图像转换(这在我们的情况下是有效的)。

在我写的另一篇文章 这里 中可以找到对这种架构及其工作原理的深入透彻的解释。

下面简单介绍一下暹罗甘建筑。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Siamese GAN Architecture

它由单个生成器(G)和鉴别器(D)组成:G 以一幅图像为输入,输出翻译后的图像;d 以一幅图像作为输入,输出一个潜在向量。

暹罗鉴别器有两个目标:告诉 G 如何生成更真实的图像,并在这些假图像中保持与原始图像的相关性(相同的“内容”)。

调用 *A1、*A2、 B1、B2 分别来自 A 域和 B 域的随机图像、X 域的随机图像、 G(X) 生成器生成的图像,鉴别器必须将图像编码成矢量***【D(X)如:*

1. D(B1) 一定离一个固定点(例如原点)很近(欧几里德距离),而 D(G(A1)) 一定离同一点很远。因此,更接近固定点的向量代表更真实的图像。另一方面,生成器试图以经典的对抗方式最小化从 D(G(A1)) 到固定点的距离。

2. (D(A1)-D(A2)) 必须与 (D(G(A1))-D(G(A2))) 相似(余弦相似),才能保留 AG(A) 之间的‘内容’。发生器和鉴别器都参与这个目标。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Siamese Discriminator

有了这两个约束(损失),第一个依赖于向量的大小,而第二个依赖于向量之间的角度,我们的全部目标得到满足,我们可以实现从域 A 到域 b 的图像到图像转换的最终目标。我真的建议您阅读 这篇文章 ,其中我给出了该架构的更全面和深入的解释,同时展示了插图和示例。

现在我们已经锁定了架构,让我们来探索如何以及向网络提供什么,以实现高清图像生成。

图像提取

我们需要 2 个高清图像数据集:在我们的例子中,我们将使用一个风景数据集(域 A)和一个梵高画作数据集(域 B)。请记住,您选择处理的图像越大,对这些图像进行预处理(切割、调整大小)所需的时间就越长(尽管它不会增加专门用于训练网络的时间!).

现在,我们需要选择将被馈送到生成器的图像的大小:显然,我们不能使用来自数据集的整个 HD 图像的大小,否则训练时间和网络大小将是巨大的,并且不会解决任何问题。因此,我们选择了一个足够小的 SxS(例如 64x64 像素),这样训练时间可以得到控制,一切都保持计算上可行,即使对于中端 GPU 也是如此(就像在谷歌合作实验室上免费提供的那些)。

因此,正如您可能已经想到的,图像在被馈送到发生器之前,必须被剪切(或裁剪)成更小的 SxS 图像。因此,在读取图像并将其转换为张量后,我们对图像执行随机 SxS 裁剪**,将其添加到一个批处理中,并将该批处理馈送到网络。这听起来非常简单,事实也确实如此!**

现在,假设我们使用这种方法训练一个 GAN,直到每一个小的 SxS 裁剪都被生成器以一种令我们满意的方式转换为梵高风格:我们现在如何将整个高清图像从域 A 转换到域 B?

同样,非常简单:图像被分成小的 SxS 块(如果 HD 图像的大小是 BxB,那么我们将有(B//S)x(B//S)个小的 SxS 图像),每个 SxS 图像被生成器翻译,最后所有的东西被重新组合在一起**。**

然而,如果我们尝试使用这种从较大图像中提取较小图像的简单思想来训练 GAN,在测试期间,我们很快就会注意到一个相当恼人的问题:由我们想要翻译的大图像提取的小图像,当由生成器转换到域 B 时,不会 有机地融合在一起。每个 SxS 图像的边缘在最终构图中清晰可见,破坏了原本成功的风格转移的“魔力”。这是一个相对较小的问题,但可能相当令人讨厌:即使使用基于像素的方法,如 cycle gan相同的障碍仍然出现**。**

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

The edges are visible

怎么才能解决呢?

我使用的解决方案很容易理解,在我看来也很优雅,它代表了我希望你能从这篇文章中记住(也许会用到)的核心思想**。**

首先,我们需要重新审视我们的数据管道:在我们直接从 BxB HD 图像中剪切 SxS 裁剪之前,我们现在必须得到 2Sx2S 裁剪(如果 S=64,那么我们需要 128x128 裁剪)。然后,在定义了我们的生成器之后,我们创建了一个名为 Combo 的新模型**,它执行以下操作:**

1.取一批 2Sx2S 图像(来自 A 域)作为输入(INP);

2.将 INP 中的每幅图像切割成 4 幅 SxS 图像(INP cut);

3.将input的 4 幅 SxS 图像中的每一幅输入到发生器,并获得 OUTCUT (与input的形状完全相同,但每幅 SxS 图像都有翻译版本);

4.将每组 4 张 SxS 图像加入 OUTCUT 中,取出 OUT (与 INP 形状完全相同,但每张 2Sx2S 图像有翻译版本);

5.输出输出。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Combo model: cutting, translating, joining

Combo 的输出然后作为输入传递给鉴别器,鉴别器现在接受比以前大一倍的输入(2Sx2S)。这个小小的调整不需要太多的计算时间,并且可以有效地解决我们之前的问题**。怎么会?**

现在,生成器被迫生成关于边缘和颜色一致的图像,因为鉴别器不会将不一致的合并图像归类为真实图像,因此会通知生成器可以改进的地方。深入一点,生成器被迫学习如何在 SxS 图像的 4 个边缘中的每一个上生成逼真的边缘:在最终的 2x2 合并图像中,4 个边缘中的每一个都与另一个接触,甚至一个生成不好的边缘都会破坏 2x2 图像的真实感。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Samples during training: (from left to right) images from domain A, translated images (AB), images from domain B

所有的事情加在一起

为了确保到此为止的一切都清楚明白,让我们总结一下整个网络是如何工作的。

目标是将 B 的样式应用于 A 中的图像。大小为 2Sx2S 的图像是从域 A 和 B 中的高分辨率图像中剪切的。来自 A 的图像是 Combo 的输入;该模型将图像切割成 4 个较小的图像(SxS),然后使用生成器 G 转换它们,最后将它们连接在一起。我们称这些假图像为 AB。

现在让我们来关注一下连体鉴别器 D** :其输入的大小是生成器输入(2Sx2S)大小的两倍,而输出是大小为 LENVEC 的向量。**

D 将图像编码成向量 D(X ),例如:

1.D(B)必须靠近原点(大小为 VECLEN 的零点向量):

LossD1 是 D(B)到原点的欧氏距离的平方,所以Eucl(D(A))

2.D(AB)必须远离原点:

LossD2 是( max(0,cost — Eucl(D(AB))))

3.变换向量(D(A1)-D(A2))和(D(AB1)-D(AB2))必须是相似的向量,以保留图像的“内容”:

LossD3 是余弦 _ 相似度(D(A1)-D(A2),D(AB1)-D(AB2))****

另一方面,生成器必须生成(结合的)图像 AB,例如:

1.D(AB)必须靠近原点:

LossG1 是 Eucl(D(AB))

2.变换向量(D(A1)-D(A2))和(D(AB1)-D(AB2))必须是相似的向量(与鉴别器的目的相同):

LossG2 是余弦 _ 相似度(D(A1)-D(A2),D(AB1)-D(AB2))****

在我的文章 here 中可以找到关于这些损失如何工作的更深入的解释,在那里我详细解释了暹罗鉴别器是如何工作的(我认为这值得一读!).

就是这样!

按照这种方法,生成器能够学习如何生成小的风格化图像,这些图像可以被连接在一起而没有任何边缘差异**。因此,当翻译整个高清图像时,在将它切割成单独的较小 SxS 图像并将它们馈送到生成器之后,我们能够将它们连接在一起成为最终的、视觉上令人愉快的和连贯的高清图像。**

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Translation examples on HD images: although not perfect, realistic brush strokes are generated and the images look quite coherent. Solutions might be networks fine tuning and bigger capacity

结论

本文中解释的技术仍然存在一些我们需要解决的问题。

如果选择极高清晰度的图像,用于训练网络的较小作物可能不包含任何相关信息**(它们可能只是纯色,类似于单个像素),因此训练可能不会成功:生成器和鉴别器都需要某种信息来处理(鉴别器必须根据图像的“内容”对图像进行编码),如果该信息不可用,可能会面临一些问题。**

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Failure case: the generator “hallucinates” incoherent colors and shapes in some areas

即使训练成功结束,当连接具有非常高分辨率的图像的所有不同裁剪时,每个小的翻译图像的风格贡献对于整个高清图像来说是不够的,整个高清图像通常看起来与原始图像相似,只是颜色发生了变化。

在我的实验中,我发现,对于训练阶段,使用调整大小(较低分辨率)的 HD 数据集版本,同时在转换时切换到整个 HD 图像,对于第一个问题肯定有帮助。

这项技术给留下了很多有待探索的空间**:不同于传统风格转换的其他类型的图像翻译也是可能的。重要的是要记住,本例中的生成器不知道整个高清图像的上下文,只“看到”较低分辨率的裁剪。因此,给生成器一些上下文(可能以编码的‘上下文向量’的形式)?)可以扩展该技术的应用范围,为更复杂的“上下文感知”类型的高清图像翻译(对象到其他对象、人脸、动物)提供了可能性。**

因此,正如你可能已经理解的,可能性是无穷无尽的,尚未被发现!

我很乐意真诚地感谢您对本文的关注,非常感谢,我希望您带着新的东西离开。

玩得开心!

用一点深度学习的魔力来装饰你的照片

原文:https://towardsdatascience.com/style-up-your-photos-with-a-touch-of-deep-learning-magic-60a003c676f9?source=collection_archive---------22-----------------------

看到深度学习的艺术一面

风格转移在成像的语境中,指的是将一幅图像的“风格”转移到另一幅图像,同时保持第二幅图像的“内容”的过程。

例如,最左边的图像是“内容”图像。我们将中间图像的“样式”(“样式”图像)应用于我们的内容图像。我们预计,由于中间的图像有一种大城市夜晚的氛围,这将反映在最终的图像中——这正是最右边的结果中发生的事情!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Source from the original research paper.

这一领域最具突破性的研究之一来自 Adobe Research。他们称之为 深照风格转移【DPST】

如何转移照片风格

为了正确地执行从一张照片到另一张照片的样式转换,Adobe 团队设计了他们的 DPST 的目标:“将引用的样式转换到输入,同时保持结果的照片真实感

这里的关键部分是保持输出的“照片真实感”属性。如果我们有一个像上面这样的内容照片,我们不希望任何建筑改变。我们只是想让它看起来像是同一张照片是在晚上拍的。

在这项研究发表之前出现的许多风格转换算法扭曲了原始图像的许多内容。在当时神经风格转移技术的结果中,像使直线呈波浪状和改变物体形状这样的事情很常见。

这完全可以接受。许多算法是为艺术风格的转换而设计的,所以一点点的变形也是受欢迎的!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Example of a distorted style-transferred image. Source

但在这种情况下,目的是创造仍然逼真的图像——就像是由真实世界的相机拍摄的一样。

作者做了 2 件主要的事情来实现这一点:(1)损失函数中的照片真实感正则化项(2)用作指导的内容图像的语义分割。

摄影现实规则化

想想我们如何直观地保持图像的真实感。我们希望原始图像的线条和形状保持不变。颜色和灯光可能会改变,但一个人看起来仍然像一个人,一棵树像一棵树,一只狗像一只狗,等等。

基于这种直观的想法,作者实现的正则化项迫使像素从输入到输出的变换在色彩空间中为局部仿射。根据定义,仿射变换在将输入映射到输出时必须保持点、直线和平面。

有了这个约束,直线永远不会变成波浪,在我们的输出中也不会有任何奇怪的形状变化!

分段指导

除了保持点、直线和平面之外,我们还希望确保样式图像中各种“事物”的样式被真实地转换。

想象一下,如果你有一个风格图像,显示一个美丽的橙色日落,如下图所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Source

图像的大部分是略带红色的橙色。如果我们把它转换成一个城市形象,所有的建筑都会变成红色!然而这并不是我们真正想要的——一个更真实的转换会使大部分建筑非常暗(接近黑色),只有天空会有日落和水的颜色。

深度照片风格转移算法使用应用于内容图像的语义分割的结果,以便 T2 引导风格转移。当算法确切地知道哪些像素属于前景和背景时,它可以更真实地传递风格。天空像素将始终被转换为天空像素,背景像素被转换为背景像素,依此类推。

转移样式的代码

您可以从 GitHub 下载照片真实感风格传输库:

git clone [https://github.com/GeorgeSeif/DeepPhotoStyle_pytorch.git](https://github.com/GeorgeSeif/DeepPhotoStyle_pytorch.git)

运行它所需要的只是一个最新版本的 Pytorch 。完成后,进入文件夹,使用下载脚本下载语义分段的模型:

cd DeepPhotoStyle_pytorch
sh download_seg_model.sh

现在我们准备好运行我们的代码了!

下载一个风格图片和一个内容图片——任何你选择的图片!以我的经验来看,城市和风景图片效果最好。最后,像这样运行代码:

python main.py --style_image path_style_image --content_image path_content_image

该算法将迭代地改进样式传递结果,因此您等待的时间越长,效果就越好!默认情况下,它设置为运行 3000 步,但如果您觉得更多的步骤可以改善结果,您可以增加。

自己试试代码吧,很好玩的!查看风格转换后照片的外观。欢迎在下面发布链接,与社区分享您的照片。

喜欢学习?

在推特上关注我,我会在这里发布所有最新最棒的人工智能、技术和科学!也请在 LinkedIn上与我联系!

推荐阅读

想了解更多关于深度学习的知识?用 Python 进行 深度学习 本书将教你如何用有史以来最简单的 Python 库:Keras 进行真正的深度学习!

提醒一句,我支持这个博客,因为分享好书对每个人都有帮助。作为一名亚马逊员工,我从合格购买中获得收入。

StyleGAN2

原文:https://towardsdatascience.com/stylegan2-ace6d3da405d?source=collection_archive---------2-----------------------

本文探讨了 StyleGAN2 中的变化,如权重解调、路径长度正则化和移除渐进增长!

StyleGAN 架构的第一个版本在被称为 Flicker-Faces-HQ (FFHQ) 的面部图像数据集上产生了令人难以置信的结果。与早期迭代的 GANs条件 GANsDCGANs 相比,这些结果最令人印象深刻的特征是生成图像的高分辨率(1024)。除了分辨率之外,还从生成图像的多样性(避免模式崩溃)和一套比较真实图像和生成图像的定量指标(如 FID、初始分数、精确度和召回率)等方面对 gan 进行了比较。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Facial images generated from StyleGAN2

Frechet 初始距离(FID)是最常用的自动度量之一,用于评估从生成模型中采样的图像。该度量基于对真实图像和生成图像上的预训练分类网络的激活进行比较。下表显示了过去 2 年中在该数据集和指标上从 StyleGAN 到 StyleGAN2 的 GAN 进度。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

FID results reported in the first edition of StyleGAN, “A Style-Based Generator Architecture for Generative Adversarial Networks” authored by Tero Karras, Samuli Laine, and Timo Aila. Note the FID scores on the right on the FFHQ dataset to compare with the StyleGAN2 resutls below.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

FID results reported in the second edition of StyleGAN, “Analyzing and Improving the Image Quality of StyleGAN” authored by Tero Karras, Samuli Laine, Miika Aittala, Janne Hellsten, Jaakko Lehtinen, and Timo Aila. Note the FID scores on the far left for the sake of comparison with StyleGAN1.

本文将讨论将 FID 指标提高了约 3 倍的架构变化,以及像消除生成图像中的伪像和平滑潜在空间插值这样的定性改进。潜在空间的平滑导致生成图像的源的微小变化,从而导致结果图像的微小感知变化,实现这样的惊人动画。如果你感兴趣,我还制作了一个视频来解释 StyleGAN2 的变化:

如何辨别图像是否是 StyleGAN 创作的

StyleGAN 的第一版产生了令人惊讶的逼真的面孔,从下面 whichfaceisreal.com 提供的测试中,你能看出哪个面孔是真实的吗?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

你认为你能训练自己在这个游戏中获得满分吗?你认为你能训练一个神经网络来做这件事吗?这是 Kaggle 上价值 100 万美元的 Deepfake 检测挑战背后的想法。whichfaceisreal.com 的作者详细列出了一系列泄露秘密的“人工制品”,可用于区分 StyleGAN 生成的图像。一个这样的伪像是图像中出现的“水滴”效果。意识到这一点会让这个游戏变得更容易(如下所示)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

StyleGAN2 的作者试图从生成的图像中消除这些伪像。他们将水滴的来源归因于自适应实例规范化层对生成器施加的限制。

权重解调

NVIDIA 的研究人员是将标准化层用于图像合成应用的大师,如 StyleGAN 和 GauGAN 。StyleGAN 使用自适应实例规范化来控制源向量 w 对最终生成的图像的影响。 GauGAN 使用一个空间自适应反规格化层从涂鸦草图合成真实感图像。(如下图)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image taken from “Semantic Image Synthesis with Spatially-Adaptive Normalization” by Taesung Park, Ming-Yu Liu, Ting-Chun Wang, and Jun-Yan Zhu. An example showing the power of normalization layers for image synthesis applications.

在 StyleGAN 的第二个版本中,作者重新调整了自适应实例规范化的使用,以避免这些水滴伪像。自适应实例规范化是一个规范化层,源于对实现更快的神经风格转移的研究。神经风格转移展示了卷积神经网络中明显的低级“风格”特征和高级“内容”特征之间的显著分离(如下所示):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image above taken from “Arbitrary Style Transfer in real-time with Adaptive Instance Normalization” authored by Xun Huang and Serge Belongie

但是,样式转换(在自适应实例规范化之前)需要一个漫长的优化过程或仅限于单一样式的预训练网络。阿丹表明,风格和内容可以结合起来,通过标准化统计的唯一使用。(这方面的概述如下所示):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image above taken from “Arbitrary Style Transfer in real-time with Adaptive Instance Normalization” authored by Xun Huang and Serge Belongie

StyleGAN2 的作者解释说,这种标准化丢弃了以相对激活幅度编码的特征图中的信息。发生器克服了这种限制,通过这些层偷偷传递信息,导致这些水滴假象。作者和读者一样困惑,为什么鉴别器不能从这种液滴效应中区分图像。

在 StyleGAN2 中,自适应实例归一化被重构为权重解调。(这一进展如下所示)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

自适应实例规范化(类似于其他规范化层,如 Batch Norm)缩放和移动中间激活的激活。Batch Norm 使用从批次统计数据中计算出的学习均值和方差参数来实现这一点,而 Instance Norm 使用单个图像与批次进行比较。自适应实例范数使用不同的比例和偏移参数来将源 w 的不同区域与特征图的不同区域对准(在每个特征图内或者通过按空间位置按通道分组特征)。

权重解调将缩放和移位参数从顺序计算路径中取出,而不是将缩放烘焙到卷积层的参数中。在我看来,值的移动(在 AdaIN 中用(y)完成)是噪声图 b 的任务。

将缩放参数移动到卷积核权重中使得该计算路径更容易并行化。这导致 40%的训练速度从每秒 37 幅图像提高到每秒 61 幅图像。

移除渐进增长

第二版中 StyleGAN 介绍的下一个工件也可能帮助你在 whichfaceisreal.com 图灵测试中获得好的结果。在他们的配套视频的 1:40 中描述,StyleGAN 图像对鼻子和眼睛等面部图像特征有强烈的位置偏好。作者将此归因于渐进增长。渐进生长描述了首先用低分辨率图像(例如 4)分配 GAN 框架的任务,并且当在较低比例下达到期望的收敛属性时将其放大的过程。

虽然渐进增长可能难以实现,在较高分辨率层中引入关于衰落的超参数,并且需要更复杂的训练循环,但是它是高分辨率图像合成问题的非常直观的分解。众所周知,GANs 的训练具有挑战性,尤其是在生成像 1024 图像这样的图像之后,传统观点认为鉴别器很容易区分真实和虚假的图像,导致生成器在训练期间无法学到任何东西。

Animesh Karnewar 和 Oliver Wang 最近发表的另一篇关于 GANs 的论文“生成对抗网络的多尺度梯度”展示了一种有趣的方式,即利用单个端到端架构来利用多尺度生成。(如下所示):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Technique to avoid progressive growing from “Multi-Scale Gradients for Generative Adversarial Networks” by Animesh Karnewar and Oliver Wang

受 MSG-GAN 的启发,StyleGAN2 的作者设计了一种新的架构,以利用多尺度的图像生成,而无需明确要求模型这样做。他们通过低分辨率特征映射到最终生成的图像之间的 resnet 风格的跳过连接来实现这一点。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

StyleGAN2 architecture without progressive growing

作者表明,与渐进式增长类似,早期的训练迭代更依赖于低频率/分辨率尺度来产生最终输出。下图显示了每个要素地图在最终输出中所占的比重,通过检查跳过的连接添加来计算。对这一点的考察启发作者按比例增加网络容量,以便 1024×1024 的比例对最终输出有更大的贡献。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

路径长度正则化

StyleGAN2 为损失引入了一个新的归一化项,以加强更平滑的潜在空间插值。潜在空间插值描述了源向量 z 的变化如何导致所生成图像的变化。这是通过在发电机上增加以下损耗项来实现的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这是如何实现的具体细节超出了我的理解范围,但是高层次的想法似乎是雅可比矩阵将 w 中的小变化映射到结果图像中的变化(从 w 空间中的点到 1024 个图像)。该矩阵乘以随机图像以避免陷入局部最优,并且该矩阵的 l2 范数乘以其指数移动平均值。因此,l2 范数越大,损耗增加得越多,导致发电机打球并保持潜在空间平滑。

这种实现的另一个有趣的特征被称为惰性正则化。由于计算该雅可比矩阵的计算量很大,因此与每一步相比,该归一化仅每 16 步添加到损失中。

这种平滑的潜在空间极大地方便了将图像投射回潜在空间。这是通过获取一个给定的图像并针对能够产生该图像的源向量 w 进行优化来实现的。这已经在许多有趣的 twitter 帖子中得到证实,研究人员将自己的图像投射到 FFHQ 上训练的 StyleGAN2 的潜在空间中:

来自吉恩·科岗的投影的一些推特演示:( 12 )

投射到一个完全平滑的潜在空间有很多有趣的应用。例如,动画工作流通常包括勾画出高级关键帧,然后手动填充更细粒度的中间帧。像 StyleGAN2 这样的模型将允许您将高级关键帧投影到潜在空间,然后在由先前 w~ p(w)的结构定义的两个 w 向量之间的路径中搜索,直到您找到满意的过渡。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

比方说生成图像的源,w 是从这个球形分布(表示 3D 均匀分布)采样的。然后,将两个关键帧投影到这个球体中,以找到分别生成每个图像的向量。那么在源代码分布中的这些点之间将会有许多路径可供您选择。一个平滑的潜在空间将确保这些过渡是平滑的,就像由人类插图画家和动画师提炼的细粒度中间点一样。

通过投影进行深度伪造检测

投影的一个应用是用它来找到给定生成图像的来源,以判断它是真是假。这个想法是,如果它是假的,你可以找到产生它的潜在载体,如果它是真的,你不能。我认为这种将图像投射回潜在空间的想法非常有趣,但我并不认为这是一种深度伪造检测技术。似乎不可能记录下所有可能的数据集,人们可能不得不训练这些模型,这些模型将是图像投射回潜在空间的真正贡献者。此外,我不明白为什么 StyleGAN 不能完美地重建原始训练图像,因为这确实是训练目标的核心。

我认为投影可以以一种有趣的方式用于异常检测和终身学习。当一个新的例子被错误分类时,它可以被重新投射回用于训练分类器的相同数据集,并且可以像 l2 距离或预先训练的分类器特征那样计算某种图像相似性距离。

感谢您阅读 StyleGAN2 的概述!

人工智能发展中的主观和客观

原文:https://towardsdatascience.com/subjective-and-objective-in-the-development-of-artificial-intelligence-c5627a522f47?source=collection_archive---------25-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by — @pawel_czerwinski

探索人工智能中偏见的定义

当在人工智能中谈到“数据”这个词时,“偏见”经常与给定的讨论一起被提及。我将首先对与主观和客观相关的偏见进行一般性讨论。之后,我将回到与人工智能的发展有关的要考虑的事情的总结。

偏见 不成比例 重量 赞成或反对一个想法或事物,通常是以一种思想封闭、有偏见或不公平的方式。偏见可以是 先天或后天 。人们可能会对某个人、某个团体或某个信仰产生偏见。 在科学与工程中,一个偏差就是一个系统误差 。统计偏差来自对总体的 不公平抽样 ,或者来自不能给出准确平均结果的估计过程 。”【加粗补充】

有很多我们可以关注的,但是让我们把这些不同的词拿出来,仔细研究一下。

  • 重量(数量/选择)
  • 先天的还是后天的
  • 系统误差
  • 不公平抽样
  • 评估过程

什么是加权?

一个加权平均值是一种平均值。不是每个数据点对最终平均值的贡献相等,而是一些数据点比其他数据点贡献更多的“权重”。如果所有的权重相等,那么加权的平均值等于算术平均值(你习惯的常规“平均值”)。这意味着有人决定什么应该给予更多的重视。Vox 在一个视频中展示了一个例子,它考察了美国的一些优秀学校,这些学校更重视(衡量)学生的能力,而不是考试成绩的增长。

先天还是后天?

先天还是后天是一个古老的争论,然而我们已经开始了解我们人类的某些事情。这些知识不仅仅来自一个领域,而是来自自然科学、社会科学、人文科学以及其他领域。

可以提及的几个例子是:

  • 一种 归因偏差 可能发生在个体评估或试图发现自己和他人行为背后的解释时。人们对自己和他人行为的原因进行归因;但是这些属性并不一定准确地反映现实。个人不是作为客观的感知者,而是倾向于感知失误,导致对他们的社会世界有偏见的理解。
  • 确认偏差是一种以确认个人信念或假设的方式搜索、解释、偏好和回忆信息的倾向,而对与之矛盾的信息给予的关注却不成比例地少。对于情绪化的问题和根深蒂固的信念,这种影响更大。人们也倾向于将模糊的证据解释为支持他们现有的立场。

除此之外,“经济人”,或者说“经济人”——理性人的神话,在某种程度上已经被揭穿。李·罗斯说,我们至少可以对自己的不理性保持理性。

系统误差

人们通常认为,如果真的出了问题,系统一定有缺陷。

(也称系统偏差)是一致的、可重复的误差,与有缺陷的设备或有缺陷的实验设计有关。

这些误差通常是由未正确校准或未正确使用的测量仪器造成的。

那么,如果我们同意人类判断相似情况的非理性(或不同)方式的数量,这可能会导致一些人得出结论,我们不是合适的测量工具。

在讨论中经常听到这样的说法:“我们需要减少……中的偏见。”其次是产品、系统、语句、算法等。

人类学家玛丽莲·斯特拉瑟恩(Marilyn Strathern)这样描述经济学家古德哈特定律:“当一个衡量标准成为目标时,它就不再是一个好的衡量标准。”

我不一定完全同意这种说法,但重要的是要认识到,一旦我们确定了一项措施,我们就会改变系统。系统改变后,我们可能需要重新测量。

这有时被认为是人工智能系统的设计,测量可以被“重新校准”。那就是:对仪器进行微小的改动,使其测量准确。但是如果一开始就不准确呢?

不公平采样

编码凝视是乔伊·波伦维尼创造的。她指出,许多解决方案都存在缺陷,因为缺乏对有色人种(除了白色以外的所有颜色)的面部识别。它探测不到她的脸。两年前,同样的面部识别软件在世界各地的几个地方使用,现在仍然如此。因此,偏差可以“传播”,因为一个样本大小用于在一个地方训练算法,所以任何偏离正常的情况。她说我们需要“全谱训练集”。她指出,开发机器学习算法的团队缺乏多样性。

除此之外,她发现面部识别更可能识别男性而不是女性,而女性黑人是最差的。这是一个与人工智能合作的每个人都必须看到的视频:

《广泛、神秘和破坏性的算法》( WMDA)是由《数学毁灭武器》一书的作者凯茜·奥尼尔创造的。凯西是一名“定量分析师”(金融领域的定量统计学家)。这个简短的插图有助于更清楚地表达她的一些主要观点:

人是不同的,每次测量算法的测试数据时,都会定义什么是成功。

“算法让事物为算法的建造者工作”——凯西·奥尼尔

估算过程

这就给我们留下了由某人定义的估计算法。因此,了解以下内容很重要:

  1. 那个人是谁
  2. 决策是如何做出的
  3. 后果

这是一个三指法则试探法,然而它可能非常复杂。尤其是如果我们考虑到一套算法可以应用于数十亿人,例如脸书和谷歌。

下次你说:客观决策,考虑一下这在实践中意味着什么。

这里是#500daysofAI,您正在阅读的是第 186 条。500 天来,我每天都写一篇关于人工智能或与之相关的新文章。

Matplotlib 中的支线剧情:规划剧情的指南和工具

原文:https://towardsdatascience.com/subplots-in-matplotlib-a-guide-and-tool-for-planning-your-plots-7d63fa632857?source=collection_archive---------5-----------------------

我最近做了一个项目,需要在 matplotlib 中进行一些微调的子绘图和叠加。虽然我对制作基本的可视化感到很舒服,但我很快发现我对支线剧情系统的理解没有达到标准。我不得不回到基础,花了一些时间通读文档,在 Stack Overflow 中寻找例子和清晰的解释。

当我开始理解 mateplotlib 的 subplot 系统是如何错综复杂地工作时,我意识到如果有一个简单的 UI 工具可以测试你的代码并确切地看到你的图形中发生了什么,那么学习起来会容易得多。我找不到我要找的东西,所以我继续做了我自己的小网络应用,我称之为情节策划者!这是一个非常简单的工具。只要调整几个参数,看看它们如何改变你正在处理的支线剧情。

在本文中,我将使用我的 plot planner 工具来解释 matplotlib 的 subplot 系统的一些特性是如何工作的。具体来说,我将讨论两种方法。add_subplot()和. subplot2grid()。

但是说够了。我们开始吧!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

subplot emojis!

。add_subplot()

figure.add_subplot()方法是将现有图形对象划分为不同大小的不同区域的最简单方法之一。它返回一个轴对象,并接受三个整数。如果这些整数中的每一个都是一位数,那么它们可以简化为一个三位数的整数。例如,。add_subplot(1,2,3)可以简化为。add_subplot(123)。但是这些数字实际上意味着什么呢?如果这几个栈溢出帖子上的话题,这里似乎有一些混乱。

关键是要理解,前两个整数定义了图形的划分,最后一个数字实际上表示了子情节应该在该划分中的位置。因此,如果您将子情节定义为(2,3,1),这意味着将子情节分成一个 2 x 3 的网格,并将新的子情节放在该网格的第一个单元格中。

你可以阅读更多关于。 matplotlib 文档中的 add_subplot()。

记住所有这些,让我们试着去做吧。我们将制作下面的例子,有 5 个不同大小的支线剧情。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们将从标有 ax1 (红色)的那个开始。只需查看图像,ax1 似乎占据了图形区域的左半部分。首先,我们将定义我们的图形,并使它成为一个 8x8 的正方形(图形大小是任意的,但在这个例子中很好)。然后,忽略所有其他的次要情节,让我们把我们的图形分成两部分,左边和右边。

fig = plt.figure(figsize=(8,8))fig.add_subplot(1, 2, 1)

在这种情况下,这些数字的意思是— 取我的数字,以这样一种方式划分,即有 1 行和 2 列。最后一个数字表示要使用的单元格。奇怪的是支线剧情的索引是从 1 开始的,而不是你所期望的 0。所以当我们说使用第一个支线剧情时,我们是在告诉我们的图形进入第一个支线剧情的空间。

这是一个非常简单的次要情节,但是更复杂的会变得难以理解。这是因为每个支线剧情都是独立的,我们永远不会看到没有被选中的支线剧情。但这里有一个来自 plot planner 应用程序的图像,它可能会使整个事情变得更加清晰。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1 row by two columns. Select 1st subplot

我发现这种形象化比我看到的任何解释都要清晰得多。我们可以看到 1 行 2 列。然后,用绿色突出显示,我们可以看到编号为 1 的单元格是我们选择的子情节。太好了!我们的第一个次要情节完成了。

现在事情变得更有趣了。你可能会想,既然你已经把你的形象分成了左右两部分,那么你现在唯一的选择就是把右半部分留白,或者在那个支线剧情中设计一些东西。事实并非如此。

你定义的每个新支线剧情都不关心你已经制作的任何其他支线剧情。本质上,每一个新支线剧情都会愉快地按照你告诉它的方向发展,而不管其他支线剧情已经存在。

考虑到这一点,让我们创建 ax2 支线剧情(蓝色)。再来看这张图片,ax2 占据了图的右上象限。所以再一次,我们会忘记所有其他的支线剧情(甚至是我们已经做好的那个),我们只专注于在右上角做一个新的支线剧情。

为了做到这一点,我们希望将图形空间分成 4 个象限,并选择右上象限。让我们再看一下情节设计器,看看这是如何工作的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由此看来,我们想要一个 2x2 的网格,我们想要第二个支线剧情。地块索引首先按行编号,然后按列编号。所以我们的代码应该是:

fig.add_subplot(2,2,2)

太好了!让我们来看看我们迄今为止的工作:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

看来一切都上轨道了!现在我已经解释了一些基本原则,我将快速浏览剩下的次要情节。

对于 ax3 (黄色),它看起来大约是我们 ax2 插槽垂直尺寸的一半,出现在它的正下方。基本上,我们在寻找这个:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

那将是 4 行 2 列,第 6 个支线剧情。或者:

fig.add_subplot(4,2,6)

最后两个支线剧情看起来一样大。在这里很难看到确切的比例,所以我会告诉你,我们正在寻找一个 8 行 2 列的图形分割。它看起来应该是这样的,我们想要为我们的两个新轴抓取第 14 和第 16 个支线剧情。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

fig.add_subplot(8,2,14)fig.add_subplot(8,2,16)

我们现在应该有我们的数字完全充满了支线剧情。下面是完整的代码,添加了一些其他视觉元素(颜色、标签等)。).

import matplotlib.pyplot as pltfig = plt.figure(figsize=(8,8))ax1 = fig.add_subplot(1, 2, 1, xticklabels=[], yticklabels=[], xticks=[], yticks=[], fc="red",)ax2 = fig.add_subplot(2, 2, 2, xticklabels=[], yticklabels=[], xticks=[], yticks=[], fc="blue")ax3 = fig.add_subplot(4, 2, 6, xticklabels=[], yticklabels=[], xticks=[], yticks=[], fc="yellow")ax4 = fig.add_subplot(8, 2, 14, xticklabels=[], yticklabels=[], xticks=[], yticks=[], fc="green")ax5 = fig.add_subplot(8, 2, 16, xticklabels=[], yticklabels=[], xticks=[], yticks=[], fc="orange")ax1.text(0.5, 0.5, "ax1", horizontalalignment='center', verticalalignment='center')ax2.text(0.5, 0.5, "ax2", horizontalalignment='center', verticalalignment='center')ax3.text(0.5, 0.5, "ax3", horizontalalignment='center', verticalalignment='center')ax4.text(0.5, 0.5, "ax4", horizontalalignment='center', verticalalignment='center')ax5.text(0.5, 0.5, "ax5", horizontalalignment='center', verticalalignment='center')plt.show()

就是这样!现在你知道了支线剧情系统是如何与?add_subplot()方法。

的。add_subplot()方法当然可以是一个强大的工具,但它有其局限性。例如,你创建的每个支线剧情只能占用一个单元格。这意味着下面的事情是不可能的。add_subplot()(即使看起来更简单)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里的问题是红色支线图占据了图表左侧的 2/3。不幸的是。add_subplot()不能处理选择图形区域的 2/3。

为此,我们可以使用. subplot2grid()。

.subplot2grid()

比如。add_subplot(),. subplot2grid()返回一个 axis 对象,其中包含有关新的子情节应放置在何处的信息。它接受两个必需的位置参数,shape 和 loc。

shape 参数作为两个数的列表或元组传入,其功能类似于。add_subplot()方法。它们指定网格布局,第一个数字是行数,第二个数字是列数。

第二个参数 loc 代表 location,也是一个由两个数字组成的列表或元组。不像。add_subplot(),你不需要通过在你的网格上指定一个索引来指定把你的 subplot 放在哪里。相反,您可以通过指定想要放置子情节的行号和列号来选择网格索引。同样不同的是,. subplot2grid()从 0 开始索引。因此(0,0)将是网格中第一行第一列的单元格。

除了这两个参数,还有两个可选的关键字参数, rowspancolspan 。这就是我们真正体会到. subplot2grid()威力的地方。有了网格布局(shape)和起始索引(loc)之后,就可以用这两个参数来扩展选择,以占据更多的行或列。默认情况下,rowspan 和 colspan 都设置为 1,这意味着占用相当于 1 行 1 列的单元格。当您增加这些数字时,您可以告诉您的轴对象在当前的网格布局中占据尽可能多的相邻行和列。

让我们仔细看看上面那个只有 3 个支线剧情的例子。虽然这些支线剧情中有一些可以(或许应该)用。add_subplot(),我们将在这里对它们都使用. subplot2grid()进行练习。

正如我已经说过的红色支线剧情,我们需要它占据总高度的 2/3。那么我们如何用. subplot2grid()做到这一点呢?除了占了我们三分之二的行之外,它还被绘制在两列的左列。有了这些信息,让我们将网格分成 3 行 2 列,并将起始索引设置为左上角的单元格。最后,我们需要告诉我们的支线剧情占用我们的三行中的两行。为此,我们将 rowspan 参数设置为 2。因此,我们的网格和支线剧情应该是这样的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

a 3x2 grid, start at index (0,0), use 2 rows and 1 column

plt.subplot2grid((3, 2), (0, 0), rowspan=2, colspan=1)

正如您所看到的,对. subplot2grid()还有一点要求。但是它允许你非常精确地安排你的可视化空间!

让我们快速完成这个例子。我们接下来将处理蓝色网格框。就像我说的,你可以用。add_subplot()(图 add_subplot(325))。但是我们也可以用. subplot2grid()来完成这个任务。在我们的 3x2 网格中,我们希望这个子情节占据左下角的单元格。这在下面的绘图设计器的图像中进行了描述。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们的网格形状是相同的(3,2)。因为我们只选择了一个单元格,所以我们将 rowspan 和 colspan 都设置为 1。我们只需要将 loc 参数指向正确的单元格。方便的是,plot planner 应用程序中的单元格标有该单元格在网格中的位置(尽管它们并不难理解)。从上面的图像中,我们想要 cell (2,0),所以我们将它插入到我们的 loc 参数中。代码将会是:

plt.subplot2grid((3, 2), (2, 0), rowspan=1, colspan=1)

对于最后一个支线剧情,我们只需要整个右栏。同样,这很容易通过。add_subplot(122)。我们也可以用 plt.subplot2grid((3,2),(0,1),rowspan=3,colspan=1)来实现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

变得更加冒险(接近荒谬),我们也可以用下面的代码完成右边的专栏。这只是为了说明可以做些什么,而不是如何解决这个问题的实际建议。

plt.subplot2grid((6, 6), (0, 3), rowspan=6, colspan=3)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

将所有这些放在一起,我们得到了以下内容(同样,我添加了一些额外的代码来处理颜色和文本):

fig = plt.figure(figsize=(8,8))ax1 = plt.subplot2grid((3, 2), (0, 0), rowspan=2, colspan=1, xticklabels=[], yticklabels=[], xticks=[], yticks=[], fc="red",)ax2 = plt.subplot2grid((3, 2), (2, 0), rowspan=1, colspan=1, xticklabels=[], yticklabels=[], xticks=[], yticks=[], fc="blue",)ax3 = plt.subplot2grid((3, 2), (0, 1), rowspan=3, colspan=1, xticklabels=[], yticklabels=[], xticks=[], yticks=[], fc="orange",)ax1.text(0.5, 0.5, "ax1 \n(rows = 2/3)", horizontalalignment='center', verticalalignment='center')ax2.text(0.5, 0.5, "ax2", horizontalalignment='center', verticalalignment='center')ax3.text(0.5, 0.5, "ax3", horizontalalignment='center', verticalalignment='center')plt.show()

以及由此产生的视觉效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在你知道了!的基础演练。add_subplot()和. subplot2grid()。如果你想自己尝试一下,或者只是想得到一点帮助来为一个复杂的布局建立索引,可以看看我的 plot planner 工具

感谢你的阅读!

子空间聚类

原文:https://towardsdatascience.com/subspace-clustering-7b884e8fff73?source=collection_archive---------6-----------------------

高维空间中的挑战

这篇文章回答了以下问题:

  1. 处理高维数据的挑战是什么?
  2. 什么是子空间聚类?
  3. 如何用 python 实现子空间聚类算法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

高维数据存在于具有几十到几千个特征(或维度)的输入中。这是在生物信息学(各种测序数据)或 NLP 中经常遇到的情况,其中词汇表的大小非常大。高维数据具有挑战性,因为:

  • 这使得可视化和理解输入变得困难,通常需要预先应用降维技术。它导致了“维数灾难”,这意味着随着维数的增加,所有子空间的完全枚举变得难以处理
  • 大多数基本的聚类技术依赖于结果和降维技术的选择
  • 许多维度可能是不相关的,并且可能掩盖噪声数据中的现有聚类
  • 一种常见的技术是执行特征选择(删除不相关的尺寸),但有时识别冗余尺寸并不容易

什么是子空间聚类?

子空间聚类是一种在不同子空间(一个或多个维度的选择)中寻找聚类的技术。潜在的假设是,我们可以找到仅由维度子集定义的有效聚类(不需要所有 N 个特征都一致)。例如,如果我们考虑将观察基因表达水平的患者数据作为输入(我们可以有超过 20000 个特征),仅通过查看 100 个基因的子集的表达数据就可以发现患有阿尔茨海默病的患者群,或者换句话说,该子集存在于 100 个基因中。换句话说,子空间聚类是传统 N 维聚类分析的扩展,它允许通过创建行和列聚类来同时对特征和观察值进行分组

得到的聚类可能在特征空间和观测空间都重叠。另一个例子如下图所示,摘自的论文。我们可以注意到,来自两个聚类的点可能非常接近,这可能会混淆分析整个特征空间的许多传统聚类算法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此外,我们可以看到子空间聚类设法找到一个子空间(维度 ac ),在这个子空间中,期望的聚类是容易识别的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

子空间聚类的类型

根据搜索策略,我们可以区分两种类型的子空间聚类,如下图所示:自底向上方法从在低维(1 D)空间中找到聚类开始,并迭代地合并它们以处理更高维空间(高达 nd)。自顶向下的方法在全维集合中寻找聚类,并评估每个聚类的子空间。下图摘自同一篇论文,概述了最常见的子空间聚类算法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

团算法

为了更好地理解子空间聚类,我在 python 这里实现了算法。

简而言之,该算法的功能如下:对于每个维度(特征),我们在 nBins( 输入参数)中分割空间,并且对于每个 bin,我们计算直方图(计数的数量)。我们只考虑密集单元,即计数高于作为第二输入参数给出的阈值的仓。密集单元具有以下特征:

  • 它所属的尺寸(例如特征 1)
  • 容器的索引(或位置)(从 0 到 nBins)
  • 躺在箱子里的观察报告

在我的实现中,我已经在 2D 空间中生成了 4 个随机聚类,并且我已经选择了 8 个箱和 2 个点作为最小密度阈值。下图显示了应用于输入空间的结果格网。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Input space split in 8 bins per dimension

clique 算法背后的直觉是存在于 k 维空间中的聚类也可以在 k-1 中找到。我们从 1D 开始,对于每个维度,我们试图找到密集的箱。如果两个或更多的密集仓是相邻的,我们将它们合并成一个更大的仓。该操作可以通过将所有现有的密集面元转换成图来容易地实现,其中如果 2 个密集单元属于相同的维度并且它们的面元索引之间的差不超过 1(例如,对应于特征 3 和面元 4 的密集单元是相同特征的密集单元以及面元 3 和 5 的邻居),则绘制边。要合并的密集单元可以通过计算上述图上的连通分量来识别。

该合并操作的结果为第一维度检索 1 D 中的以下聚类(每个聚类一个图):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于第二维度:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接下来,我们要计算从 2 到输入维数的每个子空间中的所有有效聚类*。该操作归结为计算 k 维中密集单元的组合,并且仅保留具有大小大于初始最小密度阈值的密集连续箱的重叠的结果。一旦我们计算了 k-1 维的密集单元,我们可以通过计算最后 k-1 个候选的所有组合来扩展到 k 维。*

因此,在 2 D 中,我们能够检索下图所示的集群。注意,4 个聚类之外的一些点(紫色)是因为它们属于密度低于任意输入 2 的箱。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

团聚类因其对输入参数(箱的数量和最小密度)的高度敏感性而受到批评,这会导致非常不同的结果。然而,它是自底向上子空间聚类家族中的一个基本算法。有多种方法可以优化 clique 算法,例如通过使用 MAFIA 算法中提出的密度自适应网格。

参考

小团体论文

黑手党算法

子空间聚类方法的比较研究

子空间聚类方法的介绍

在小公司/初创公司中成为成功的数据科学家

原文:https://towardsdatascience.com/succeeding-as-a-data-scientist-in-small-companies-startups-92f59e22bd8c?source=collection_archive---------4-----------------------

这与成熟的大公司完全不同。

这可能会是一个无限的帖子系列,从这个问题衍生而来,这个问题来自一个令人敬畏的社区,即数据呆子 twitter 集群:

一些背景

我已经在规模在 15-150 之间的公司工作了将近 12 年,身兼数据分析师、工程师以及偶尔的科学家等各种职务。作为一名 UX 的研究人员,走进大型企业谷歌云有点不符合他的性格,但随着新产品不断涌现,就问题和混乱而言,这感觉就像一家初创公司,尽管涉及数十亿美元的总收入。

我曾在室内设计(办公室设计)、广告技术、社交网络、链接缩短、电子商务以及现在的企业云等多个领域工作过。我有社会科学背景,有一点 NLP,应用数学和工商管理。我主要生活在后端系统、日志和 SQL 中。

一句话,我是一个多面手,就是人们似乎会推荐给初创公司的那种,而这正是我整个职业生涯蓬勃发展的地方。

这些是我的偏见,也是我从中汲取的经验。如果你在一家初创公司,人工智能/人工智能实际上是业务的核心基础,我可能与你的需求无关。YMMV。

成为第一个“数据人”

我就直说了, 小公司不需要一个数据科学家,但是需要一个“数据人”。 他们可能会称这份工作为“数据科学家/工程师/分析师/忍者”,随便什么。

我的经验是,在 20 至 60 名员工之间,有足够的客户、积累的数据和角色专业化,需要引入能够利用数据提供有用商业见解的人,这开始证明雇佣某人的成本是合理的。在那之前,他们用现有的技能凑合着过日子。

职位几乎可以是任何东西,但是职位描述往往是以下各项的混合:

  1. 理解我们拥有的数据
  2. 帮助建立我们的数据系统
  3. 帮助我们实现数据驱动/运行实验
  4. 发展业务
  5. 教育/认证可能与任何事情相关,也可能不相关

通常很有可能他们并不真正完全了解他们需要什么。这只是一种广义的感觉,“我们有数据,它似乎很有用,但我们没有任何人有技能让它变得有用。”

实际上,担任这个职位的人需要同时做两件大事:

  • 帮助公司今天取得成功
  • 让公司明天成为数据驱动型企业

帮助公司取得今天的成功

创业公司被不确定性所包围。他们不确定谁是他们的客户,生产系统可能不可靠,他们不知道他们的客户在用产品做什么,他们不知道如何利用他们拥有的数据做出决策,他们不知道他们拥有的数据是否有用。

对这些问题的聪明回答会导致更聪明的决定,并有希望实现每个人梦寐以求的神话般的曲棍球棒增长。问题是,这些问题中的大部分并不适合花哨的方法。有用的方法通常是一个世纪前的和/或基于定性方法而不是定量方法。

大多数 DS 方法在优化现有流程时是最有效的,它们会在客户获取、转换、保留和支出等方面为您带来 5%、10%甚至 25%的增长。A/B 测试,推荐系统,ML 分类器,这些都有助于优化。收益是真实的,可量化的,并且可能是显著的,但是早期可能有更大的鱼要煎。

早期最大的影响通常涉及洞察力。洞察力从根本上改变了公司的行为。它们来自非常平凡的事情,比如对用户偏好/行为的研究,为销售人员揭示了一个新的营销概念,或者帮助产品团队认识到 Twitter 上最令人讨厌的功能实际上被 90%的付费客户使用,他们不应该无缘无故地放弃它。

我对这个“现在就帮助公司”角色的看法是,数据人是一个力量倍增器。企业内部的人有问题,工作就是帮助他们解决问题。

成为第一个“数据人”=成为“拥有数据的科学家”

对我来说,成为一名科学家意味着你有一个问题,一个研究问题,并且你使用任何你能使用的方法来得到这个问题的可靠答案。

作为数据科学家,我们倾向于使用定量方法和从系统中收集的数据来回答问题,但这不是获得洞察力的唯一途径。有时你会全力观察或询问用户(定性方法),或者你出去收集数据(实验和调查),或者你观察其他人(竞争分析)。一个好的科学家不会用他们的方法来定义自己,第一个数据人(或任何后来的数据人)也不应该。

目标是回答紧迫的业务需求:“为什么没有人使用我们的产品?”“我们的回报怎么会这么高?”我们该不该进行这次昂贵的拍卖?”“是什么推动了客户流失?”“客户的终身价值是什么,是什么推动了这种价值?"

成为数据驱动的未来

我看到的一个常见陷阱是,数据科学专业毕业的人加入这些职位时,期望使用 Spark 等性感的东西,并将 RNNs 应用到他们的工作中。但可悲的是,他们想住在需要首先完成的基础工作的山顶上,从工程角度来看是,从文化角度来看是*。这种不匹配是残酷的。*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Fancy “Data Science” methods rely on a ton of things, DO NOT expect each layer to be “done” before moving to the next. Think of the colors as “time spent”.

作为第一个被专门雇佣来处理数据的人,金字塔的任何部分都不太可能是坚固的。这是一项历时多年、跨职能、全公司共同努力的工作,旨在将所有部分都落实到位。并行培养所有这些部分是这项工作的一大部分。

请注意,在一个典型的业务中,您将试图同时在金字塔的上下游做事情,而不管底层的稳定性如何。我已经针对脆弱的新系统构建了大量的仪表板和分类器,您也一样。

关于所有这些东西的各个方面,有足够的空间来发表大量的帖子,但这里是我的想法的粗略概述。

固体生产系统和工程实践

这是一件在工程领域非常常见的事情,但是如果一个系统有问题并且损坏了,你就不能真正地测量这个系统的行为,所以期望在需要的时候能帮上忙。

你最终戴的“数据工程师”的帽子越多,你在帮助构建可靠的系统中扮演的角色就越大。人们自然会问你一些问题,比如 PostgresSQL 对 MySQL,AWS 对 GCP,Spark 对 Redshift 等等。帮助这些决定增加了持久的价值。如果没有足够的工程资源,你必须自己建立系统并运行它们。

我还发现,出于商业目的将工程推向仪器系统有一个很好的副作用,那就是发现有趣的 bug。有一次,我在一个不应该有 dupes 的关键表中发现了重复的 id,那是一组 bug hunt,它修复了一个创收系统中的一些东西。经常查看生产表可以发现非常奇怪的错误。

可靠的仪器

垃圾进,垃圾出。

获得可靠的仪器是第一数据人员做的最重要的事情**。这是一个无止境的旅程,从挑选框架(多个)到收集系统和用户数据,确保工程师学会如何在不计算错误(这很容易发生)的情况下实现事情,确保数据库和日志做正确的事情™,并确保你正在计算你认为你正在计算的东西。**

同时,将增加专门收集和报告数据的系统。这些需要由某个人,也许是你来整理和管理。

在文化方面,从新人到首席执行官,每个人都会不断询问数据的可靠性以及如何处理这些信息。他们会要求澄清,解释,深入了解他们自己建立的系统。

这种文化培训本身就是一个漫长的旅程,因为你要制作报告和仪表板,人们会发现与其他系统不一致的地方,并得到与他们对现实的看法不一致的结果。有时他们的现实是错误的,但通常他们是正确的。仅仅是这些对话就能让所有参与其中的人变得更聪明。

老实说,我不认为这个阶段是“完全”的,它只是达到了一个点,当一个新的特性或大的变化出现时,你只需要担心它。

报告和处理数据

仪表板和报告并不是一个性感的工作,但不幸的是,这通常是整个公司的人每天观察公司健康状况的第一/唯一方式,因此投资是必要的。目标是将(潜在的)可操作信息放在能够主动采取行动的人手中。

开始时,大多数仪表板和报告将是手动的。这需要大量的迭代来发现人们足够关心多次看到的指标。自动化是一个不错的选择,但是获得洞察力更重要。

技术方面并不超级复杂。有许多服务和平台可以生成报告和仪表板,您甚至可以使用自定义代码来完成。诀窍是让所有的数据系统一起发挥作用(Ha),有良好的性能,并尽量减少(重大!)随着业务增长维护仪表板和报告的开销。

文化方面是有趣的地方。你在训练人们变得更加数据驱动。这也需要多年的工作和练习。

这里涉及到大量的教育。你将教人们如何阅读 A/B 测试的结果,显著差异意味着什么,解释什么是置信区间/预测区间,解释为什么该图表“只是一个估计,因为我们依赖于第三方报告,它们是不可靠的”。你将回答关于样本大小的问题(永远不够),并不断需要教人们好的方法。

如果人们担心仪表板上的数字,他们应该怎么做?就我个人而言,我告诉他们来和我谈谈。他们关心的是潜在的研究问题(或缺陷),这是字面上的研究黄金。这些人是这部分业务的领域专家,而我只是一个使用 SQL 的书呆子。

自动化和实验

随着时间的推移,您将为新功能的推出建立指标和仪表板。不可避免地,人们会对某个功能的表现感到失望,并想知道为什么。如果你还没有学会前/后分析方法,还没有记住你的主要市场的所有节假日,现在是时候这样做了。

一旦人们习惯了拥有信息,并可能运行一些 A/B 测试得到令人失望的结果,当测试数据与人们的假设相反时,预计将花费大量时间来验证数字是否正确。

尽管有那些痛苦的时刻,我们现在终于在做真正的科学了。提出假设并收集数据进行测试。

到目前为止,有用的仪表板应该是相当自动化的,人们习惯于使用数据来做出决策。当一个新功能被提出时,你知道你已经到了,“我们将如何判断它的成功?”不用你去争取就会出现。

我注意到的另一件有趣的事情是,在这个阶段,一家公司可能会过于舒适地进行实验。他们学习设计他们知道会成功的测试(低风险),或者他们会“测试”一个他们 100%知道不管结果如何都会启动的东西(“我们稍后会优化它”)。

作为科学家,现在你的工作是呼吁人们关注这种行为。他们可以完全不顾测试结果而推出,激进的改变往往测试效果不佳,但应该明确说明意图。

最后,数据科学

在经历了漫长、漫长、漫长的旅程和许多弯路之后,公司本身已经过渡到数据驱动。他们有假设,能够可靠地收集数据,并根据结果做出深思熟虑的决定。他们也更加自给自足,能够阅读(也许创建)带有一些指导的仪表板,并且已经学会何时担心以及如何提出问题。

在金字塔的底部总会有事情要做,但至少现在事情不会一直着火。企业有望更好地了解其在市场和世界中的位置,并且兴趣转移到优化现有流程以获得增量(但显著)收益。

现在,你可以考虑打破花哨的算法…

或者,可能有一个数据仓库需要构建,因为您现在有太多的系统,分析查询无法进一步加速,哎呀。

欢迎评论/反馈

我一口气写完了整篇文章,因为内容需要一个出口。由于篇幅原因,我已经掩盖了一大堆东西。因此,我们非常欢迎对未来帖子的任何反馈和主题请求。在推特上给我打电话。

成功的政府绩效管理

原文:https://towardsdatascience.com/successful-performance-management-in-the-government-6cfc967e0622?source=collection_archive---------19-----------------------

为什么系统的失败对管理者来说是一个潜在的胜利

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by Stephen Dawson on Unsplash

在过去的四十年里,美国联邦政府越来越多地使用绩效管理系统。虽然早期支持者的关注点是绩效管理的潜在好处和使用绩效信息进行政策衡量,但最近的工作对基于政策的系统的功效提出了质疑。然而,通过子机构的采用和实施,可以实现显著的好处。

绩效信息的公开是为了监控政策和计划的有效性。例如,我们能否在全系统的基础上衡量监狱改革立法、福利转工作计划或医疗改革的成功?

不幸的是,的研究显示,在美国,绩效管理还没有在系统层面上运作良好。这主要是因为虽然国会和州立法机构经常需要这些指标和数据,但它们通常不会用于基于政治现实的项目审查或预算过程。

然而,研究表明,在联邦机构的决策中成功地使用了绩效信息,最常见的是在办公室或局一级。在这一级,产生的信息与办公室的目标联系更紧密,反馈回路更紧密,可以说会导致更有效的采纳。

什么是公共部门绩效管理?

绩效管理可以从衡量特定流程的输入和输出的角度来考虑。在公共部门,这承担了与预期政策的产出和结果相关联的绩效信息的额外定义;衡量它们是否符合最终的政策目标,以评估大多数情况下的有效性以及效率。

在公共部门,关注成就和结果也很重要,而不仅仅是财务方面。因此,虽然私营部门可能会关注商业情报和绩效信息,而这些信息关注的是利润和市场份额,但公共部门项目通常会关注与目标和效率一致的结果。

业绩信息的另一个重要特征是,它是定量的,通过报告、数据库变得透明,并包含与公开宣布的目标一致的客观标准。这是数据科学团队花费时间编译输入/输出数据,并为内部和外部利益相关者创建有用的报告和仪表板的地方。

我们定义为绩效信息的收集是有意的,遵循一个设计好的信息创建、收集、分析和报告系统。这一点很重要,因为它不同于简单的数据收集和分析,后者是特定的,而不是基于逻辑模型或预期的因果关系有目的地设计来测量程序。

绩效管理运动简史

绩效管理起源于私营部门,并从早期的科学管理运动中被采纳。科学管理相信你可以衡量一切;从输入、过程、程序和输出。

通过衡量一切并应用分析来发现流程中低效的部分并确定新的流程,公司可以通过更好地利用资源和消除浪费来实现利润最大化。

公共部门对绩效管理的采用通常来自管理顾问、拥有私营部门经验和偏好的任命领导层,以及希望将私营部门的“效率”带到公共部门的政治家。

从根本上来说,这场运动承诺了效率、效力和透明的监督。这些长期以来一直是政治家和公民对联邦政府的要求。

**从政治角度来看,绩效管理运动是一种证明从公民那里收取的税款费用是合理的方式。**在其他情况下,它是一种机制,用于识别和确保对他们在政治上或政策议程上重要的项目或政策的有效实施。

根据 1993 年的政府绩效结果法案(GPRA) ,美国国会要求联邦机构实施绩效管理技术。GPRA 要求各机构通过五年战略计划公开设定目标,根据这些目标衡量其年度进展,并提交年度进展公开报告,以确定其实现目标的成败。

在 GPRA 之后,为了回应一些领域的关注和批评,联邦机构也被要求遵守项目评估评级工具(PART)。第一部分要求机构回答有关其项目的评估问题,以证明预算的合理性.

最近,国会于 2010 年通过了 GPRA 现代化法案(GPRMA ),旨在纠正 GPRA 的失误和失败,并重新建立国会对行政部门绩效管理指导的控制,因为这被视为部分党派之争。具体而言,GPRAMA 要求各机构以机器可读的格式在线发布其报告和战略计划。它还强调制定较少量的较高层次的目标,以及确定具体的机构间优先目标。这背后的意图是将焦点、精力和注意力限制在更少的目标上,以使机构领导层能够保持关注,并更成功地进行项目改进

现在政府正在创建绩效信息,它在政策和项目决策中的使用频率和效果如何?

认识到政治局限性

在高度政治化的政策领域,价值观、谈判和说服通常会排挤使用专业知识和数据来指导决策或问题讨论。当政治推动决策时,必然会产生冲突、权衡和妥协的结果,这些结果本质上很可能不符合客观数据和基于证据的建议。

值得注意的是,这可以而且应该从美国政府这样的代议制民主结构中得到。事实上,几位已经指出,绩效管理运动的客观和简单的数据驱动目标与要求民选官员代表的政治体系有些不相容。

成功采用绩效管理的关键因素

在决策中成功使用绩效管理的一个最重要的发现是经理是最重要的因素之一。然而,并不总是以你期望的方式。

研究人员已经确定了经理的某些特征,这些特征可以鼓励在组织决策中使用绩效信息。这些因素是经理的自我效能感对反馈的感受性、s 对外部环境的敏感性、以及他们对员工投入的重视程度。这些发现背后的想法是,经理的个性可以通过塑造好奇的天性来“摆好桌子”,从而积极影响组织中绩效信息的使用。

其他研究发现,绩效信息使用与组织绩效之间的关系很重要。这些发现支持这样一种观点,即要成功运用绩效管理,它必须与新的愿景、利益相关者更好的结果或组织的重大变化联系起来

组织战略也很重要。当绩效信息与延伸目标或创新相关联时,您更有可能体验到绩效信息的成功实施和使用。如果用于监控现有的工作和目标,这些系统的效率将会降低。因此,经理们不应该仅仅把它放在现有的流程和产品上,而是应该把它的采用与新的计划联系起来,并把组织团结在他们的后面。

组织文化、灵活性和专业性也被发现与绩效信息的使用呈正相关。此外,个人经理层和组织文化层的公共服务动机的存在是绩效信息使用的良好预测因素

培训员工

毫不奇怪,培训会影响联邦政府成功采用和实施绩效评估。

这一点很重要,因为每一个连续的绩效管理原则都将培训确定为一个必要的组成部分。事实上,GPRAMA 特别要求政府确定并实施对机构工作人员的必要培训。

虽然大部分培训旨在对员工进行绩效管理要求和系统的教育,但能力培训将通过向他们提供所需的知识和技能,而不仅仅是告知他们要求,来帮助机构员工成功实施和使用绩效信息。能力培训应包括涵盖如何衡量绩效、如何运用判断力、如何从绩效数据中学习以及如何将绩效数据用于问责目的的课程。

结论

绩效管理很可能以某种形式留在公共部门。除非美国代议制民主结构发生重大变化,否则我们不太可能看到绩效信息被用来客观地决定采取哪些政策和计划。相反,这些很可能仍然是基于价值的决策,由选民和利益相关者提供信息。

我不认为这是基于表现的运动的失败,而是对我们依赖选民意见的制度的承认。有希望的是在次级机构一级采用和实施业绩管理的程度。

此外,使用绩效信息进行战略规划、制定计划和程序变更,以及向领导层提供见解和反馈的成功案例越来越多,令人印象深刻。

此外,有一项很有前途的工作是研究领导力如何将绩效信息与基于证据的政策和计划评估的理念相结合,不仅可以监控领导力并为其提供信息,还可以探索关于产出和结果之间因果联系的假设。这可以更好地确定性能信息应该测量什么,以确保报告的指标是正确的指标。

即使这些信息没有直接插入到政治决策中,它仍然会确保我们的政策项目有更大的效力和效率,而这些正是我们的目标。

此外,它确实为利益集团、研究人员和监督组织提供了更好的信息和透明度,从而可以在政治领导层和选民需要时向他们报告。

后续不确定性

原文:https://towardsdatascience.com/successor-uncertainties-b498097827fb?source=collection_archive---------19-----------------------

无模型强化学习的高效探索

我在这里描述一下我们最近的 NeurIPS 论文【1】【code】,介绍了无模型强化学习中高效探索的最新方法后继不确定性** (SU)。**

主要作者是来自剑桥 机器学习小组 的两名博士生 David JanzJiri Hron ,这项工作源于 David Janz 在 微软剑桥 研究院实习期间。

SU 背后的主要见解是使用概率模型描述 Q 函数,该模型直接考虑 Q 函数值之间的相关性,如贝尔曼方程所示。这些相关性在以前的工作中被忽略了。

结果是一种方法,即在表格基准和 Atari 游戏上胜过竞争对手,同时仍然是快速高度可扩展

问题描述

我们考虑一般的强化学习 (RL)设置,其中代理通过采取动作 a_t,与具有状态 s_t 的环境进行交互,然后环境通过转换到状态 s_t+1 并给出奖励值 r_t+1 进行响应,如下图所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

环境通常是由未知的跃迁分布 P(s_t+1,r_t+1|s_t,a_t)完全确定的随机马尔可夫,。

代理使用策略函数 π(a|s)选择其下一个动作作为当前状态的函数,π(a|s)是给定当前状态下动作的概率分布,即π(a|s)=P(a_t=a|s_t=s)。

RL 的目标是从与环境的相互作用中找到一个政策函数,该函数使贴现回报的期望总和最大化

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中 0≤γ<1 保证和是有限的。

RL 中的主要挑战之一是,行为对奖励的影响通常会延迟,直到很久以后才会被察觉。这使得很难确定在任何给定状态下采取的最佳行动。

q-函数和广义策略迭代

当根据政策π行动时,一种立即说明行动对未来回报的影响的方法是使用状态-行动值函数Q 函数。

Q 函数返回当在状态“s”采取行动“a”然后根据π行动时将获得的折扣奖励的预期总和。特别是,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中期望是π选择的未来行动和环境的转移概率。

Q 函数可以被示为满足贝尔曼方程,因为在特定状态的动作的值取决于在后续状态的其他动作的值。特别是,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

该等式清楚地表明 Q 函数不是任意函数,并且必须满足 特定约束。这将是 SU 设计中的关键,这是在构建 Q 函数的概率模型时考虑这些约束的第一种方法。

政策改进

Q-函数非常有用,因为我们可以通过贪婪地对π的 Q-函数采取行动来获得比π更好或等于π的新策略π_new。这个过程叫做政策完善。当行动空间离散时,我们得到以下贪婪策略

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

政策评价

为了实现策略改进,我们需要对当前策略的 Q 函数进行估计。这一估计最初可能不可用。获得这种估计的一种方法是对 Q 函数的初始随机猜测重复应用贝尔曼算子:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

它根据其他状态-动作对的 Q 值来更新一个状态-动作对的 Q 值。这个过程叫做政策评估

广义策略迭代和有效探索

大多数无模型 RL 方法通过从初始随机策略开始,然后以不同程度的粒度交替策略评估和策略改进步骤来找到最优策略。这被称为广义策略迭代 (GPI)。

在许多 GPI 方法中,贝尔曼算子在政策评估步骤中所要求的期望值通常通过蒙特卡罗近似,通过对从与环境的交互中收集的数据求平均**。**

好的实证结果的一个关键因素是如何收集数据以便快速收敛到最优政策。高效探索指的是与环境的智能互动,旨在使这种快速融合在实践中发生。

然而,大多数情况下,无模型 RL 方法不执行有效的探索,并且只是通过遵循具有概率 1-ɛ的贪婪策略,然后选择具有概率ɛ.的随机动作来收集数据

汤普森采样和概率 Q 函数

Thompson sampling (TS)是一种有效收集数据的策略,该策略在许多不同领域都非常成功。特别地,我们的方法 SU 使用 TS 的近似版本来进行有效的探索。

在一般设置中,TS 通过迭代执行以下两个步骤来工作:

  1. 在给定可用数据的情况下,后验分布中关于数据生成机制的样本假设“H”。
  2. 根据采样的“H ”,通过最佳动作收集新数据。

下图显示了当我们想要最大化一个未知的目标函数时,TS 迭代的一个例子。

图 1 显示了通过在三种不同输入下评估目标获得的数据。图 2 显示了给定目前收集的数据的目标后验分布的样本。曲线 3 显示选定的样本,曲线 4 显示使采样函数最大化的输入。这个输入是我们下一步根据 TS 收集数据的最佳动作。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在 RL 设置中,TS 通常被称为强化学习的后验采样(PSRL)【5,6】。当 TS 应用于 RL 问题时,“H”是一个特定的转移分布,并且“H”上的后验概率是在给定观察元组(r_t+1,s_t+1,s_t,a_t)形式的数据的情况下计算的。相对于特定“H”的最佳动作包括为采样的 P(s_t+1,r_t+1|s_t,a_t)找到最佳策略

这意味着上面的步骤 2 在实践中是高成本的,因为一旦我们采样了特定的跃迁分布,最优行动需要解决由采样的 P(s_t+1,r_t+1|s_t,a_t)指定的新 RL 问题。

结果是汤普森采样在大多数 RL 问题中是不可行的

概率 Q 函数

RL 中 TS 的巨大计算成本促使研究人员提出近似方法。

一种常见的方法是在 Q 函数上使用后验分布,而不是在转移分布上使用后验分布。这种方法通常被称为随机化价值函数**(RVF)【7】。在这种情况下,对从后验分布采样的假设采取最优行动与为采样的 Q 函数计算贪婪策略一样简单。**

RVF 在实践中被证明是成功的,导致 RL 方法执行更有效的探索。然而,当 Q 函数由神经网络近似时,这种方法仍然具有局限性

首先,获得 Q 函数的后验分布并不简单,因为当我们执行非策略 RL 时,我们无法直接访问 Q 函数的输入和相应输出值形式的数据。许多使用神经网络近似的 RVF 方法错误地假设情况就是这样**。此外,这些方法还存在以下两个问题之一:**

  1. Q 函数上的分布忽略了函数值之间的相关性,例如由贝尔曼方程给出的相关性。
  2. Q 函数上的分布捕捉到了这些依赖性,但是计算成本很大

SU 没有这些问题,因为它捕获了由贝尔曼方程给出的依赖性,并且具有低计算成本。

后续不确定性

给定策略π的 Q 函数上的后验分布可以通过以下方式获得:首先,从观察到的元组(r_t+1,s_t+1,s_t,a_t)计算转移分布上的后验分布,然后将该后验分布映射到 Q 函数上的对应分布。

令人惊讶的是,当转移分布满足以下假设时,前面的操作可以直接完成:

  1. P(r_t+1|s_t,a_t)由线性高斯模型r _ t+1 =(s_t,a_t)+ϵ_t,其中 ϕ (s_t,a_t)是状态和动作的特征表示,ϵ_t 是高斯噪声。
  2. 忽略关于 P(s_t+1|s_t,a_t)的不确定性。

奖励的先前线性模型导致 Q 函数的对应线性模型。特别是,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中 ψ (s_t,a_t)为后继特征【10】,即π下每个 ϕ (s_t,a_t)的折现期望未来发生。

在对于 w 的高斯先验下,我们获得对于 w 的高斯后验 N( w|m,S ),在前面的等式下,导致对于 Q 函数的完全相关的高斯后验,均值和协方差函数由下式给出

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由于 Q 函数仅针对给定策略π定义,我们假设该策略是通过从后验分布中采样 Q 函数而获得的平均贪婪策略

这个协方差函数保证采样的 Q 函数将满足贝尔曼方程

实际实施

初始特征 ϕ (s_t,a_t)和后续特征 ψ (s_t,a_t)使用多头神经网络计算,每个可能的动作值和特征类型( ϕψ) 一个头。每个头的输出乘以 m 以获得对下一个奖励和 Q 函数值的预测。

下图显示了 Atari 游戏设置的整个过程,头部的输入由一个卷积神经网络给出,后面是一个完全连接的层。在表格环境中,网络的这一部分可以由状态的一次热编码来代替。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

苏通过优化学习神经网络的参数和 m

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

期望超过通过与环境交互收集的元组(r_t+1,s_t+1,s_t,a_t)。

上述目标包括三种不同的损失条款。第一个是后续特征时间差误差。此项强制 ψ 收敛于 ϕ 的贴现预期未来发生。第二个损失项调整 mϕ ,使得回报预测中的误差较低。最后,第三项是 Q 函数和的估计中的时间差异误差,它强制 ψ 也适用于预测 Q 函数值。

最后,根据高斯线性模型中的贝叶斯规则在线更新协方差矩阵 S :

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中β是奖励的线性模型中的噪声方差,0≤ζ≤1 是允许模型遗忘的衰减因子,这有助于在学习过程中对抗 ϕ 的非平稳性。在下面的实验中我们用了β = 0.001(二叉树)和β = 0.01(雅达利)和ζ = 1(二叉树)和ζ = 0.99999(雅达利)。

二叉树基准的实验

我们评估了 SU 在一个需要高效探索的挑战性问题上的表现。在这个问题中,见下图,有 2L+1 状态和两个可能的动作,这两个动作在环境生成时被随机映射到每个状态中的向上向下的运动。奖励总是 0,除非在到达产生奖励 1 的 s_2L 州之后。奇数指数和 s_2L 的状态是终端。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个问题具有挑战性,因为代理将总是获得奖励 0,除非问题被解决,如果没有智能探索,这将以指数小概率发生,作为 L 的函数。

下图显示了学习最佳策略所需的发作次数的中位数。将 SU 与其他 RVF 方法进行比较,如贝叶斯 DQN ( BDQN ) [3]、不确定性贝尔曼方程( UBE ) [2]和自举 DQN [4]。后一种方法用一组 Q 函数估计量来逼近 Q 函数后验。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

BDQN 和 UBE 执行与相同的策略,随机地对行为进行统一采样****,并且当 L 很大时,他们都努力寻找最优策略。自举的也比苏差,当自举的使用比苏多 25 倍的计算时,差距变得更小。原因是我们的贝叶斯线性模型使用的分析更新允许不确定性得到真正快速的解释,而 bootstrap DQN 更依赖于基于梯度的更新。

雅达利游戏的实验

为了表明它可以扩展到复杂的领域,我们在 49 个 Atari 游戏的标准集上评估了 SU[8]。实施、网络架构和培训程序的具体细节可以在[1]中找到。在[9]中描述的“无操作启动 30 分钟模拟器时间”测试协议下,在 200 米训练帧后,SU 获得了 2.09 的中值人类标准化得分(3 粒种子的平均值)。下表显示 SU 明显优于竞争方法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

关于更多细节,SU 和各个游戏的竞争算法之间的人类标准化分数的差异绘制在下图中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Bars show the difference in human normalized score between SU and Bootstrap DQN (top),
UBE (middle) and DQN (bottom) for each of the 49 Atari 2600 games. Blue indicates SU performed
better, red worse. SU outperforms the baselines on 36/49, 43/49 and 42/49 games respectively.
Y-axis values have been clipped to [−2.5, 2.5].

摘要

后继不确定性是无模型强化学习中高效探索的最新方法。尤其是苏

  1. 是 Q 函数的第一个概率方法,它结合了贝尔曼方程给出的依赖性。****
  2. 是高度可扩展的,和以前的方法一样快或者更快。
  3. 在表格基准测试和雅达利游戏上胜过竞争对手

参考

[1] Janz D .,Hron J .,马祖尔 p .,霍夫曼 k .,埃尔南德斯-洛巴托 J. M .和 Tschiatschek S. 后继者的不确定性:时间差异学习中的探索和不确定性,载于 NeurIPS,2019 年。同等贡献者。*

[2]b .奥多诺格、I .奥斯本、r .穆诺斯和 v .姆尼赫《不确定性贝尔曼方程与探索**。在 2018 年的 ICML。**

[3] Azizzadenesheli,k .、Brunskill,e .和 Anandkumar,A. 通过贝叶斯深度 Q 网络进行有效探索。在 2018 年的 ICLR。https://openreview.net/forum?id=Bk6qQGWRb

[4] Osband,I .、Blundell,c .、Pritzel,a .、Van Roy,B. 通过自举 DQN 进行深度探索。在 NeurIPS,2016。

[5] Strens,M. 强化学习的贝叶斯框架。2000 年在 ICML。

[6] Osband,I .、Russo,d .和 Van Roy,B. (更)有效的通过后验抽样的强化学习。在 NeurIPS,2013 年。

[7] Osband,I .、Van Roy,b .、Wen,Z. 通过随机值函数进行概括和探索。2016 年在 ICML。

[8] Mnih,v .,Kavukcuoglu,k .,Silver,d .,鲁苏,A. A .,Veness,j .,Bellemare,M. G .,Graves,a .,Riedmiller,m .,Fidjeland,A. K .,Ostrovski,g .,等人通过深度强化学习实现人的水平控制**。自然,518(7540):529,2015。**

[9] Hessel,m .,Modayil,j .,van Hasselt,h .,Schaul,t .,Ostrovski,g .,Dabney,w .,Horgan,d .,Piot,b .,Azar,M. G .,和 Silver,D. 彩虹:结合深度强化学习的改进。2018 年 AAAI 人工智能大会。

[10] Dayan,P. 改进时间差异学习的概括:后继表征。神经计算,5(4):613–624,1993。

SudachiPy:一个用 Python 编写的日语词法分析器

原文:https://towardsdatascience.com/sudachipy-a-japanese-morphological-analyzer-in-python-5f1f8fc0c807?source=collection_archive---------20-----------------------

Sudachi 的 Python 版本,专注于日语文本的自然语言处理(NLP)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Photo by Damon Lam on Unsplash

本文介绍了 SudachiPy 的基本功能,它可以用来执行简单的自然语言处理任务,比如日语的标记化。基于官方 Github 页面:

Sudachi 和 SudachiPy 是由人工智能的 WAP 德岛实验室和 NLP 开发的,NLP 是 Works Applications 下的一个研究所,专注于自然语言处理(NLP)。

最初的版本是 Java,而 SudachiPy 是它的 python 实现。说了这么多,有些功能还是和 Java Sudachi 不兼容。本文由 4 个主要部分组成:

  1. 设置
  2. 命令行
  3. 基本用法
  4. 结论

1.设置

SudachiPy

让我们从创建一个虚拟环境并激活它开始。完成后,在终端中运行以下命令:

pip install SudachiPy

这将安装 SudachiPy 的最新版本,在本文撰写时是 0.3.11。SudachiPy 高于 0.3.0 的版本默认引用 SudachiDict_core 包的 system.dic。这个软件包不包含在 SudachiPy 中,必须手动安装。

SudachiDict_core

您可以通过运行以下命令来安装该软件包:

pip install https://object-storage.tyo2.conoha.io/v1/nc_2520839e1f9641b08211a5c85243124a/sudachi/SudachiDict_core-20190718.tar.gz

它会自动安装默认词典。一旦你准备好了,让我们跳到下一部分。以下段落旨在设置您自己的自定义词典。

自定义词典(可选)

Sudachi 为我们提供了 3 种不同的字典

您应该能够下载 system.dic 文件。您可以将字典修改为任何内置或自定义的字典。

  • 路径修改:创建一个 sudachi.json,放在你喜欢的任何地方。默认情况下,包在以下目录中包含此文件。
<virtual_env>/Lib/site-packages/sudachipy/resources/

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image by Author

你可以选择编辑这个文件或者创建一个新的并粘贴下面的代码:

将 systemDict 修改为 systemDict 的相对路径,以指向 system.dic 文件。

  • 直接替换:转到以下目录:
<virtual_env>/Lib/site-packages/sudachidict_core/resources

您应该只能看到一个名为 system.dic 的文件

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image by Author

用您的自定义词典或从官方网站下载的词典替换该文件。

如果您使用的是 Windows 操作系统,并且遇到了OSError: symbolic link privilege not held问题。请以管理员身份运行您的终端。然后,键入并运行以下命令:

sudachipy link -t core

2.命令行

SudachiPy 还为我们提供了一些命令行。一旦安装了 SudachiPySudachiDict_core 模块,您就可以直接使用它了。我将用它来测试我们是否正确安装了模块。有 4 个主要的可用命令:

  • 标记化
  • 建设
  • ubuild(包含使用 0.3 版本时的 bug。*)

打开终端并运行以下命令:

sudachipy tokenize -h

您应该能够看到以下结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image by Author

如果您遇到任何问题,请确保您已经正确安装了 SudachiPySudachiDict_core 模块。

3.基本用法

导入

首先,让我们导入分词器字典模块。

from sudachipy import tokenizer
from sudachipy import dictionary

记号赋予器对象

创建一个标记器对象需要这两个模块。继续添加以下代码:

tokenizer_obj = dictionary.Dictionary().create()

方式

接下来,我们需要为记号赋予器定义模式。模式用于确定标记器应该如何拆分文本。

  • A :文本被分割成与 UniDic 短单元相当的最短单元
  • B :将文本分割成 A 和 C 之间的中间单元
  • C :提取命名实体

让我们通过将其设置为模式 A 来测试一下:

mode = tokenizer.Tokenizer.SplitMode.A

创建一个新变量,并指定任何日语文本。然后,调用列表理解中的 tokenize 函数,如下所示:

txt = "医薬品安全管理責任者"
print([m.surface() for m in tokenizer_obj.tokenize(txt, mode)])

您应该能够获得以下结果:

['医薬', '品', '安全', '管理', '責任', '者']

您可以对其进行测试,以查看所有三个输出的结果,如下所示:

from sudachipy import tokenizer
from sudachipy import dictionarytokenizer_obj = dictionary.Dictionary().create()
txt = "医薬品安全管理責任者"
mode = tokenizer.Tokenizer.SplitMode.A
print([m.surface() for m in tokenizer_obj.tokenize(txt, mode)])
mode = tokenizer.Tokenizer.SplitMode.B
print([m.surface() for m in tokenizer_obj.tokenize(txt, mode)])
mode = tokenizer.Tokenizer.SplitMode.C
print([m.surface() for m in tokenizer_obj.tokenize(txt, mode)])

字典和规范化形式

日语以复杂的动词变化著称。SudachiPy 还为我们提供了一种通过内置函数自由转换它们的方法。我们将只探索三个主要的函数,我们可以在通过 tokenizer obj 分割文本后访问它们。

第一个是原始文本,可以使用表面()函数调用

print([m.surface() for m in tokenizer_obj.tokenize(txt, mode)])

此外,我们还有 dictionary_form ()函数

print([m.dictionary_form() for m in tokenizer_obj.tokenize(txt, mode)])

最后一个是 normalized_form ()函数

print([m.normalized_form() for m in tokenizer_obj.tokenize(txt, mode)])

让我们将它们结合在一起,看看有什么不同:

from sudachipy import tokenizer
from sudachipy import dictionarytokenizer_obj = dictionary.Dictionary().create()
txt = "ゲームデータを消失してしまいました。"
mode = tokenizer.Tokenizer.SplitMode.A
print([m.surface() for m in tokenizer_obj.tokenize(txt, mode)])

print([m.normalized_form() for m in tokenizer_obj.tokenize(txt, mode)])

print([m.dictionary_form() for m in tokenizer_obj.tokenize(txt, mode)])

您应该能够获得以下结果:

['ゲーム', 'データ', 'を', '消失', 'し', 'て', 'しまい', 'まし', 'た', '。']
['ゲーム', 'データ', 'を', '消失', '為る', 'て', '仕舞う', 'ます', 'た', '。']
['ゲーム', 'データ', 'を', '消失', 'する', 'て', 'しまう', 'ます', 'た', '。']

词性

任何自然语言处理中最重要的特征之一是词性标记。我们可以通过词性()函数轻松获得:

print([m.part_of_speech() for m in tokenizer_obj.tokenize(txt, mode)])

输出结果如下所示:

[['名詞', '普通名詞', '一般', '*', '*', '*'], ['名詞', '普通名詞', '一般', '*', '*', '*'], ['助詞', '格助詞', '*', '*', '*', '*'], ['名詞', '普通名詞', 'サ変可能', '*', '*', '*'], ['動詞', '非自立可能', '*', '*', 'サ行変格', '連用形-一般'], ['助詞', '接続助詞', '*', '*', '*', '*'], ['動詞', '非自立可能', '*', '*', '五段-ワア行', '連用形-一般'], ['助動詞', '*', '*', '*', '助動詞-マス', '連用形-一般'], ['助動詞', '*', '*', '*', '助動詞-タ', '終止形-一般'], ['補助記号', '句点', '*', '*', '*', '*']]

4.结论

让我们回顾一下今天所学的内容。我们从安装必要的 python 模块开始, SudachiPySudachiDict_core 。还有一个选项可以配置它来加载您自己的自定义词典。

然后,我们研究了一些可以在终端中调用来测试安装的基本命令。

接下来,我们深入研究基本的 python 代码,学习如何标记日语文本。此外,我们可以得到字典或规范化形式的输出标记。更不用说词性()函数,它可以帮助识别每个令牌的 POS 标签。

感谢您的阅读,祝您愉快。下一篇文章再见。

参考

  1. https://github.com/WorksApplications/SudachiPy
  2. https://github.com/WorksApplications/Sudachi

数独和时间表

原文:https://towardsdatascience.com/sudokus-and-schedules-60f3de5dfe0d?source=collection_archive---------10-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Pan Am’s Reservation Center in the 1950’s

用树搜索解决调度问题

如今,机器学习非常流行,以至于人们很容易忽视“人工智能”领域还有其他算法的事实。事实上,这些算法是如此的重要,以至于忽视它们是很容易的。以搜索算法为例,或者在这种情况下更具体地说是约束满足

Video version of this article

假设你需要安排课程和教室。有 36 节课、36 个房间和 800 个讲座作为你的时间表。想猜猜有多少种可能的配置吗?答案如下:10 种⁴⁹⁰可能的配置。客观地说,宇宙中有 10⁸⁰可观测的原子。即使像教室安排这样平凡的任务也要处理天文数字和排列,并冒险进入 NP 难领域。但是我可以告诉你如何及时地用算法解决这些问题。

运筹学领域并不新鲜,但它的技术和算法对日常实际问题至关重要。在工厂产能如此有限的情况下,如何在多条产品线中实现利润最大化?你如何安排一家医院的 200 名护士,他们有不同的休假要求、职位、工会限制和工作时间规定?如果你需要安排体育比赛并最小化团队旅行距离该怎么办?如何优化列车网络的准点性能?还是简单的解一个数独?

有许多算法可以解决优化性质的问题。这些包括线性规划元启发式整数规划等等。我个人觉得这些优化和搜索算法非常迷人,并且有大量的实际问题需要用它们来解决。有趣的是,这些算法中的一些如何直接应用于机器学习,因为机器学习本身在其核心是一个优化问题。

但今天,我想谈谈如何安排大学课堂,以及解决数独。您可以使用这种整数规划方法在基于规则的约束下安排员工、工厂生产线、云服务器作业、运输车辆和其他资源。我们可以使用数学建模实现神奇的一键生成时间表,而不是依赖迭代的强力策略来将事件放入时间表中(这可能是无药可救的低效)。你甚至可以采用这些方法来构建象棋人工智能算法或者进行任何基于离散的回归。

在我开始之前,我强烈推荐这个具有挑战性但很有用的 Coursera 离散优化课程。这门课相当雄心勃勃,但是很有价值,很有用,也很有趣。这是值得的时间和精力,即使你不得不放慢自己的步伐。

做好准备,对于你们这些技术人员来说,将会有相当多的代码!重要的是要着眼于大局,从概念上理解模型。所以我鼓励你在第一次阅读这篇文章的时候忽略掉这段代码(或者干脆跳过它)。如果您决定深入研究,请确保您熟悉面向对象和函数式编程。

定义问题

在本文中,我们将针对一个教室生成每周大学课程表。我们将在两个维度上绘制占领状态网格:班级与 15 分钟离散间隔的时间线。如果我们想要安排多个房间,那将是三个维度:班级对时间线对房间。我们现在将坚持前者,做一个房间,稍后我将解释如何做多个房间。

这些课程时间长短不一,可能会在一周内“重复出现”。每个重复会话必须在一天中的同一时间开始。

以下是这些类:

  • 心理学 101 (1 小时,2 节课/周)
  • 英语 101 (1.5 小时,2 节课/周)
  • 数学 300 (1.5 小时,每周 2 节课)
  • 心理学 300(每周 3 小时 1 节课)
  • 微积分 I (2 小时,2 节课/周)
  • 线性代数 I (2 小时,每周 3 节课)
  • 社会学 101 (1 小时,2 节课/周)
  • 生物 101 (1 小时,2 节课/周)
  • 供应链 300 (2.5 小时,每周 2 次会议)
  • 定位 101 (1 小时,1 节课/周)

一天应该以 15 分钟为间隔,并且只能以这些间隔来安排课程。换句话说,一节课只能在整点的 00、15、30 或 45 开始。

运营周是周一至周五。营业时间为上午 11:30 至下午 1:00,休息时间如下:

  • 上午 8 时至 11 时 30 分
  • 下午 1 时至 5 时

**您的目标:**创建一个模型,该模型安排这些课程,没有重叠,并符合这些要求。

**剧透提示:**这是我们最终将使用我们从头构建的“人工智能”算法来计算的解决方案。如果你想知道这是如何做到的,请继续阅读。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

SPOIILER ALERT: Here is the schedule our algorithm will build by the end of this article

奠定基础

好吧,不知所措了吗?有很多规则,要探索的排列数量是天文数字。但是一旦我向您展示了这种技术,它将有望非常直接地实现。

关于这个问题,你应该注意的第一件事是每件事是如何被分成“15 分钟”块的。这不是一个连续的/线性的问题,而是一个离散的问题,这就是现实世界中大多数计划是如何建立的。假设我们已经为整个周创建了一个时间表,分成 15 分钟的时间段,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

请注意,“…”只是一个折叠的占位符,因为我们没有足够的空间来显示一周的 672 个数据块(672 = 7 天* 24 小时*一小时 4 个数据块)。

现在让我们扩展这个概念,把类作为时间轴的轴。每个交叉点/单元是一个可以是 1 或 0 的Slot。这个二进制变量将被求解,以指示Slot是否是该类第一次循环的开始时间。我们现在将它们都设置为 0,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

A grid of our decision variables

这个网格对于从逻辑上思考这个问题至关重要。这将是一个有效的视觉辅助,因为我们的约束将集中在网格内的区域。

我将使用 Kotlin 作为编程语言,它与 Java 库配合得非常好,但比 Java 更易读、更简洁。我们将利用 Java 8 强大的LocalDate / LocalTime API 来简化我们的日历工作。

如果你不熟悉 Kotlin,它基本上是一个类似 Swift 的“dumbed down Scala”,大量用于 Android 开发。它本质上利用了 Java、C#、Scala、Groovy 和 Python 的实用特性来创建一种实用的工业语言。它还可以编译成 Java 字节码,并与 Java 库无缝互操作。

让我们像这样设置我们的基本规则参数:

**import** java.time.LocalDate
**import** java.time.LocalTime

*// Any Monday through Friday date range will work* **val** *operatingDates* = 
    LocalDate.of(2017,10,16)..LocalDate.of(2017,10,20)**val** *operatingDay* = LocalTime.of(8,0)..LocalTime.of(17,0)

**val** *breaks* = *listOf*<ClosedRange<LocalTime>>(
        LocalTime.of(11,30)..LocalTime.of(12,59)
)

接下来让我们声明ScheduledClass,它保存了我们想要调度的给定类的属性。

**data class** ScheduledClass(**val id**: Int,
                          **val name**: String,
                          **val hoursLength**: Double,
                          **val recurrences**: Int,
                          **val recurrenceGapDays**: Int = 2)

recurrenceGapDays是每次重复的开始时间之间所需的最少天数。例如,以Psych 100为例,它需要 2 次重复,默认间隔为 2 天。如果第一堂课是在星期一上午 8 点,那么第二堂课必须安排在两天(48 小时)后,也就是星期三上午 8 点。我们将把这个值默认为2,这将使每个会话的开始间隔 48 小时。

接下来我们可以在一个List中声明所有的ScheduledClass实例:

**val** *scheduledClasses* = *listOf*(
        ScheduledClass(
            id=1, 
            name=**"Psych 101"**,
            hoursLength=1.0,
            recurrences=2
        ),
        ScheduledClass(
            id=2, 
            name=**"English 101"**,  
            hoursLength=1.5, 
            recurrences=3
        ),
        ScheduledClass(
            id=3, 
            name=**"Math 300"**, 
            hoursLength=1.5, 
            recurrences=2
        ),
        ScheduledClass(
            id=4, 
            name=**"Psych 300"**,  
            hoursLength=3.0, 
            recurrences=1
        ),
        ScheduledClass(
            id=5, 
            name=**"Calculus I"**, 
            hoursLength=2.0, 
            recurrences=2
        ),
        ScheduledClass(
            id=6, 
            name=**"Linear Algebra I"**, 
            hoursLength=2.0, 
            recurrences=3
        ),
        ScheduledClass(
            id=7, 
            name=**"Sociology 101"**, 
            hoursLength=1.0, 
            recurrences=2
        ),
        ScheduledClass(
            id=8, 
            name=**"Biology 101"**, 
            hoursLength=1.0, 
            recurrences=2
        ),
        ScheduledClass(
            id=9, 
            name=**"Supply Chain 300"**, 
            hoursLength=2.5, 
            recurrences=2
        ),
        ScheduledClass(
            id=10, 
            name=**"Orientation 101"**,
            hoursLength=1.0, 
            recurrences=1
        )
    )

Block类将代表每个离散的 15 分钟时间段。我们将使用一个 Kotlin Sequence结合 Java 8 的LocalDate/LocalTime API 来为整个规划窗口生成所有块。我们还将创建几个助手属性来提取timeRange以及它是否是withinOperatingDaywithinOperatingDay属性将确定此Block是否在可安排的时间窗口内(例如,未安排在午夜或休息时段内)。

*/** A discrete, 15-minute chunk of time a class can be scheduled on */* **data class** Block(**val range**: ClosedRange<LocalDateTime>) {

    **val timeRange** =     
       **range**.**start**.toLocalTime()..**range**.**endInclusive**.toLocalTime()*/** indicates if this block is in operating day/break
    constraints */* **val withinOperatingDay get**() =  
        *breaks*.*all* **{ timeRange**.**start !in it }** &&
            **timeRange**.**start in** *operatingDay* &&
            **timeRange**.**endInclusive in** *operatingDay

    // manage instances* **companion object** {

        */* 
        All operating blocks for the entire week, broken up in 15
        minute increments.
        Lazily initialize to prevent circular construction issues 
       */* **val all by** *lazy* **{***generateSequence*(*operatingDates*.**start**.atStartOfDay()){
                dt **->** dt.plusMinutes(15)
                  .*takeIf* **{ it**.plusMinutes(15) <= 
                       *operatingDates*.**endInclusive**.atTime(23,59) 
                   **}
            }**.*map* **{** Block(**it**..**it**.plusMinutes(15)) **}** .*toList*()
        **}** */* only returns blocks within the operating times */* **val allInOperatingDay by** *lazy* **{
            all**.*filter* **{ it**.**withinOperatingDay }
        }** }
}

注意我将使用一个[lazy { }](https://kotlinlang.org/docs/reference/delegated-properties.html#lazy) 委托为每个域对象初始化项目。这是为了防止循环构造问题,方法是在第一次调用这些项之前不构造它们。

最后,Slot类将代表一个ScheduledClass和一个Block之间的交集/单元。我们将通过将每个ScheduledClass与每个Block配对来生成它们。我们还将创建一个未赋值的selected二进制变量,它将是null,直到我们给它赋值为10

**data class** Slot(**val block**: Block, 
                **val scheduledClass**: ScheduledClass) {

    **var selected**: Int? = **null

    companion object** {

        **val all by** *lazy* **{** Block.**all**.*asSequence*().*flatMap* **{** b **->** ScheduledClass.**all**.*asSequence*().*map* **{** Slot(b,**it**)**}
            }**.*toList*()
        **}** }
}

建模约束

在我进入模型的实现之前,我应该强调你可以使用混合整数解算器库来建模带有线性函数的约束,并让它求解selected变量。在 Python 中你可以使用纸浆或者 PyOmo 。在 Java 平台上,您可以使用 ojAlgoOptaPlanner 。对于许多这样的库,我还可以加入一个$10K IBM CPLEX 许可证,它可以更快地解决更大、更复杂的问题。

但是我将展示如何从头开始构建一个解决方案。在没有任何库的情况下进行优化的好处是,您可以对启发式算法(搜索策略)进行大量控制,并根据您的领域来表达模型。

在我们开始求解每个Slot中的selected变量之前,让我们做一些思维实验来理解我们的约束。我可能浪费了 50 张纸来做这个模型,但我发现了一些有用的东西。它有点抽象,但是对于这个特殊的问题来说是强大而有效的。

同样,我们将为每个Slot分配一个10 来指示第一节课重复的开始。这是我们的求解器可能会想到的一个可能的迭代,其中第一节心理学 101 课在周一上午 9:00 开始,社会学 101 课在周一上午 9:15 开始。这是我们的地图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

仔细研究这个场景。你明白为什么这是一个无效的案例了吗?在周一上午 9:45,心理学 101(需要四个街区)和社会学 101(也需要四个街区)相互冲突。视觉上,你也许能看到冲突。但是怎么形容呢?

“影响”上午 9:45 时间段的计划班时段的总和必须小于或等于1。总和1实际上意味着只有一个类占用那个块,而0意味着根本没有类占用那个块(也有效)。以下情况也是失败,因为“影响”槽的总和是2

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果我们将社会学 101 移到上午 10:00,那么总和将是1,一切都好(如下所示):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们需要将这个逻辑应用于整个时间线上的每个块的*,查询占据这个块的每个类的更早的槽,并规定它们的总和必须不大于 1。这个抽象但强大的想法实现了我们在约束方面需要的一切。下面是实际情况,所有影响上午 9:45 的时间段都用蓝色突出显示。所有这些蓝色时间段的总和必须不超过 1,这样上午 9:45 的时间段才不会被重复预订。*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这甚至可以解释复发的原因。毕竟,我们在一个槽中放了一个1来表示第一个类的候选开始时间*。如果我们正在查看星期五上午 9:45 的时间段,我们将查询一周中会导致星期五上午 9:45 的时间段被占用的时间段(一直到星期一)。下面是一个大视野。这些蓝色槽的总和必须不大于 1。*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

好的,你开始头晕了吗?这个模型的强大之处不在于数学,而在于每个块能够查询影响它的槽,并要求它们的总和不超过 1。这是艰苦工作发生的地方。另一个好处是,我们不需要创建任何新的变量来模拟约束,并且可以用一系列简单的求和约束来约束现有的槽二元变量。

提取重复和受影响的插槽

为了执行影响给定块的槽的想法,我们首先需要做的是为给定块上的每个类识别这些槽,并且说它们的总和必须是“1”。这个代码库的明星是针对给定的List项的 Kotlin 函数:

**enum class** RecurrenceMode { **PARTIAL_ONLY**, **FULL_ONLY**, **ALL** }

**fun** List<T>.affectedWindows(slotsNeeded: Int, 
       gap: Int, 
       recurrences: Int,
       mode: RecurrenceMode = RecurrenceMode.**FULL_ONLY**) = (0..**size**).*asSequence*().*map* **{** i **->** (1..recurrences).*asSequence*().*map* **{** (**it** - 1) * gap **}** .*filter* **{ it** + i < **size }** .*map* **{** r **->** subList(i + r, 
                           (i + r + slotsNeeded)
                             .*let* **{ if** (**it** > **size**) **size else it }**)
                    **}** .*toList*()
        **}**.*filter* **{
            when** (mode) {
                RecurrenceMode.**ALL** -> **true**RecurrenceMode.**FULL_ONLY** -> 
                     **it**.**size** == recurrences && 
                         **it**.*all* **{ it**.**size** == slotsNeeded **}**RecurrenceMode.**PARTIAL_ONLY** -> 
                     **it**.**size** < recurrences || 
                         **it**.*any* **{ it**.**size** < slotsNeeded **}** }
        **}**

我将让您自己深入研究这个函数的实现。现在,更有效的方法是覆盖它所完成的任务,即获取任何List<Slot>并执行一个专门的窗口操作,在每次循环之间注入一个gap。这将返回一个列表列表,List<List<T>>,其中每个列表都是一个循环,列表中的元素是受影响的元素。注意gap是窗口每次开始之间的元素数量。

为了了解这种模式,我们可以将整数 1 到 20 分成 4 个一组,每个循环开始之间的间隔为 6,并且有 3 个循环。我们将只考虑完整的而不是部分的组,所以mode将被设置为RecurrenceMode.FULL_ONLY

**fun** main(args: Array<String>) { (1..20).*toList*()
            .*affectedWindows*(slotsNeeded = 4,
                    gap = 6,
                    recurrences = 3,
                    mode = RecurrenceMode.**FULL_ONLY** )
            .*forEach* **{** *println*(**it**) **}** }

输出:

[[1, 2, 3, 4], [7, 8, 9, 10], [13, 14, 15, 16]]
[[2, 3, 4, 5], [8, 9, 10, 11], [14, 15, 16, 17]]
[[3, 4, 5, 6], [9, 10, 11, 12], [15, 16, 17, 18]]
[[4, 5, 6, 7], [10, 11, 12, 13], [16, 17, 18, 19]]
[[5, 6, 7, 8], [11, 12, 13, 14], [17, 18, 19, 20]]

如果我们将mode设置为RecurrenceMode.PARTIAL_ONLY,它将给出无法产生长度为43循环的“破碎”组。这将有助于稍后识别必须为 0 的槽,因为我们无法获得给定块所需的所有槽(例如,它们溢出超过 5pm 限制)。

**fun** main(args: Array<String>) {
    (1..20).*toList*()
            .*affectedWindows*(slotsNeeded = 4,
                    gap = 6,
                    recurrences = 3,
                    mode = RecurrenceMode.**PARTIAL_ONLY** )
            .*forEach* **{** *println*(**it**) **}** }

输出:

[[6, 7, 8, 9], [12, 13, 14, 15], [18, 19, 20]]
[[7, 8, 9, 10], [13, 14, 15, 16], [19, 20]]
[[8, 9, 10, 11], [14, 15, 16, 17], [20]]
[[9, 10, 11, 12], [15, 16, 17, 18]]
[[10, 11, 12, 13], [16, 17, 18, 19]]
[[11, 12, 13, 14], [17, 18, 19, 20]]
[[12, 13, 14, 15], [18, 19, 20]]
[[13, 14, 15, 16], [19, 20]]
[[14, 15, 16, 17], [20]]
[[15, 16, 17, 18]]
[[16, 17, 18, 19]]
[[17, 18, 19, 20]]
[[18, 19, 20]]
[[19, 20]]
[[20]]

我们可以使用这个affectedWindows()函数来处理课程重复,并在周一到周五的时间规划窗口内生成所有可能的排列。然后,我们可以使用它来查找影响特定块的特定类的槽。我们还将“清除”属于损坏组的插槽。例如,在下午 4:15 开始生物 101 将导致它溢出超过下午 5:00。因此,这个槽应该被固定为“0 ”,甚至在我们稍后进行的搜索中不被考虑。

*/** A discrete, 15-minute chunk of time a class can be scheduled on */* **data class** Block(**val range**: ClosedRange<LocalDateTime>) {

    **val timeRange** =     
       **range**.**start**.toLocalTime()..**range**.**endInclusive**.toLocalTime() */** indicates if this block is zeroed due to operating 
         day/break constraints */* **val withinOperatingDay get**() =  
        *breaks*.*all* **{ timeRange**.**start !in it }** &&
            **timeRange**.**start in** *operatingDay* &&
            **timeRange**.**endInclusive in** *operatingDay* **val affectingSlots by** *lazy* **{** ScheduledClass.**all**.*asSequence*()
                .*flatMap* **{
                    it**.affectingSlotsFor(**this**).*asSequence*()
                **}**.*toSet*()
    **}

    companion object** {

        */* 
        All operating blocks for the entire week, broken up in 15
        minute increments.
        Lazily initialize to prevent circular construction issues 
       */* **val all by** *lazy* **{** *generateSequence*(*operatingDates*.**start**.atStartOfDay()){
                dt **->** dt.plusMinutes(15)
                  .*takeIf* **{ it**.plusMinutes(15) <= 
                       *operatingDates*.**endInclusive**.atTime(23,59) 
                   **}
             }**.*map* **{** Block(**it**..**it**.plusMinutes(15)) **}** .*toList*()
        **}** */* only returns blocks within the operating times */* **val allInOperatingDay by** *lazy* **{
            all**.*filter* **{ it**.**withinOperatingDay }
        }** }
}

**data class** ScheduledClass(**val id**: Int,
                          **val name**: String,
                          **val hoursLength**: Double,
                          **val recurrences**: Int,
                          **val recurrenceGapDays**: Int = 2) {

    */** the # of slots between each recurrence */* **val gap** = **recurrenceGapDays** * 24 * 4

    */** the # of slots needed for a given occurrence */* **val slotsNeededPerSession** = (**hoursLength** * 4).toInt()

    */** yields slots for this given scheduled class */* **val slots by** *lazy* **{** Slot.**all**.*asSequence*()
             .*filter* **{ it**.**scheduledClass** == **this }** .*toList*()
    **}** */** yields slot groups for this scheduled class */* **val recurrenceSlots by** *lazy* **{
        slots**.*affectedWindows*(slotsNeeded = **slotsNeededPerSession**,
                gap = **gap**,
                recurrences = **recurrences**,
                mode = RecurrenceMode.**FULL_ONLY** ).*toList*()
    **}** */** yields slots that affect the given block for this  
         scheduled class */* **fun** affectingSlotsFor(block: Block) = 
          **recurrenceSlots**.*asSequence*()
            .*filter* **{** blk **->** blk.*flatMap* **{ it }** .*any* **{ it**.**block** == block **} 
            }** .*map* **{ it**.*first*().*first*() **}** */** These slots should be fixed to zero **/* **val slotsFixedToZero by** *lazy* **{** *// broken recurrences* **slots**.*affectedWindows*(slotsNeeded = **slotsNeededPerSession**,
                gap = **gap**,
                recurrences = **recurrences**,
                mode = RecurrenceMode.**PARTIAL_ONLY** ) .*flatMap* **{ it**.*asSequence*() **} //flatten the groups!** .*flatMap* **{ it**.*asSequence*() **}**.*plus*(
                    **recurrenceSlots**.*asSequence*()
                            .*flatMap* **{ it**.*asSequence*() **}** .*filter* **{** slot **->** slot.*any* **{** !**it**.**block**.**withinOperatingDay 
                                 }
                            }** .*map* **{ it**.*first*() **}** )
            .*distinct*()
            .*onEach* **{
                it**.**selected** = 0
            **}** .*toList*()
    **}** */**translates and returns the optimized start time of the class */* **val start get**() = 
        **slots**.*asSequence*()
             .*filter* **{ it**.**selected** == 1 **}** .*map* **{ it**.**block**.**dateTimeRange**.**start }** .*min*()!!

   */** translates and returns the optimized end time of the class */* **val end get**() = **start**.plusMinutes(
         (**hoursLength** * 60.0).toLong()
    )

    */** returns the DayOfWeeks where recurrences take place */* **val daysOfWeek get**() = (0..(**recurrences**-1)).*asSequence*()
         .*map* **{ 
           start**.*dayOfWeek*.plus(**it**.toLong() * **recurrenceGapDays**) 
         **}**.*sorted*()

    **companion object** {
        **val all by** *lazy* **{** *scheduledClasses* **}** }
}

求解变量

现在我们已经建立了一个有效的基础设施来查询影响给定块的槽,我们现在准备求解符合我们的约束的变量。希望你对编写递归算法相当熟悉。如果没有,这里有一个练习的机会!

但是首先我要用一个更简单明了的例子来证明这个想法:数独。在我回到调度问题之前,它将演示如何使用分支算法求解变量。

想想数独游戏

但愿数独是一个熟悉的益智游戏。您将看到一个 9x9 的单元格网格,其中一些单元格已经有了可能的数字 1-9。您需要找到其余空白单元格的值,以便每一行、每一列和 3×3 的正方形都有数字 1-9。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

A typical Sudoku puzzle

那么如何求解空白值呢?强行搜索效率会非常低,所以我们需要一个更聪明的搜索策略。

请允许我向您介绍树搜索。感觉类似于决策树,但是我们处理的是离散变量(整数)而不是连续变量(小数)。

首先获取单元格,并在列表中根据它们拥有的候选值的数量对它们进行排序。例如,单元格[4,4]只能是一个值 5,所以它先出现,而单元格[2,6]应该是最后一个,因为它有 6 个可能的值。然后创建一个函数,作为分支算法递归地探索每个可能的值。以下是这种递归算法如何探索可能值的视觉效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

With this sorted list, create a recursive tree that explores each Sudoku cell and its possible values.

请注意我们是如何首先从最受约束的单元格开始的,这大大缩小了我们的搜索空间。搜索策略的这种用法被称为“启发式”。当一个给定的分支被认为不可行时(违反了数独规则),该分支立即终止并转移到下一个替代分支。上图中,单元格[4,2]不能被赋值为“3”,因为单元格[3,2]已经被赋值为“3”。这意味着“3”已经存在于列 2 中,所以没有理由继续搜索那个分支,我们修剪它。

当我们找到一个探索了所有 91 个值的分支,并且没有违反任何约束时,我们就解决了我们的数独游戏!然后,我们可以将分支值折叠回游戏板,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

We have solved our Sudoku!

如果你想看数独解算器的源代码,你可以在这里找到。这部分代码是递归树发生的地方。

[## 托马斯尼尔德/科特林数独求解器

一个用 Kotlin 写的 suduko 游戏解算器。有助于托马斯尼尔德/科特林数独解决方案的开发,通过创建一个…

github.com](https://github.com/thomasnield/kotlin-sudoku-solver)

回到调度问题

那么数独和日程安排问题有什么关系呢?嗯,实际上很多!我们可以应用这种技术,通过使用搜索树来解决 1 或 0 值的槽。在每一个被探索的分支上,你可以像数独游戏一样检查你的约束是否仍然被满足,确保课程没有被安排在已经被占用的时间段。

从概念上讲,这是我们的递归搜索树的样子:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下面是我如何从零开始实现这个野蛮的算法(请原谅我的代码格式或只看源代码)。请注意我是如何使用BranchNode来表示正在搜索的分支的顶端的,我可以在分支上向后遍历,以评估到目前为止所做的决策是否与我当前的决策不冲突。然后我可以创建一个traverse()函数来递归地探索整个树,直到找到解决方案。

**import** java.time.DayOfWeek

**class** BranchNode(**val selectedValue**: Int,
                 restOfTree: List<Slot>,
                 **val previous**: BranchNode? = **null**) {

    **val slot** = restOfTree.*first*()

    **val traverseBackwards** =  *generateSequence*(**this**) **{ 
        it**.**previous 
    }**.*toList*()

    *// calculate remaining slots and prune where constraint propagates* **val remainingSlots by** *lazy* **{
        if** (**selectedValue** == 0)
            restOfTree.*minus*(**slot**)
        **else** {

       *// if this slot is occupied, affected slots can be pruned* **val** affectedSlotsPropogated = Block.**allInOperatingDay** .*asSequence*().*filter* **{
                    slot in it**.**affectingSlots
                }**.*flatMap* **{ it**.**affectingSlots**.*asSequence*() **}** .*filter* **{ it**.**selected** == **null }** .*toSet*()

            restOfTree.*asSequence*()
                    .*filter* **{
                        it**.**scheduledClass** != **slot**.**scheduledClass** && **it !in** affectedSlotsPropogated
                    **}**.*toList*()
        }
    **}

    val scheduleMet get**() = **traverseBackwards** .*asSequence*()
            .*filter* **{ it**.**selectedValue** == 1 **}** .*map* **{ it**.**slot**.**scheduledClass }** .*distinct*()
            .*count*() == ScheduledClass.**all**.*count*()

    **val isContinuable get**() = !**scheduleMet** && 
         **remainingSlots**.*count*() > 0 **val isSolution get**() = **scheduleMet

    fun** applySolution() {
        **slot**.**selected** = **selectedValue** }
}

**fun** executeBranchingSearch() {

    *// pre-constraints* ScheduledClass.**all**.*flatMap* **{ it**.**slotsFixedToZero }** .*forEach* **{ it**.**selected** = 0 **}** *// Encourage most "constrained" slots to be searched first* **val** sortedSlots = Slot.**all**.*asSequence*()
        .*filter* **{ it**.**selected** == **null }**.*sortedWith*(
            *compareBy*(
                    **{** *// prioritize slots dealing with recurrences* **val** dow = **it**.**block**.**range**.**start**.*dayOfWeek* **when** {
                            dow == DayOfWeek.**MONDAY** && 
                              **it**.**scheduledClass**.**recurrences** == 3 
                                 -> -1000 dow != DayOfWeek.**MONDAY** && 
                               **it**.**scheduledClass**.**recurrences** == 3 
                                 -> 1000 dow **in** DayOfWeek.**MONDAY**..DayOfWeek.**WEDNESDAY** && **it**.**scheduledClass**.**recurrences** == 2 
                                -> -500 dow **!in** DayOfWeek.**MONDAY**..DayOfWeek.**WEDNESDAY** && **it**.**scheduledClass**.**recurrences** == 2 
                                -> 500 dow **in** DayOfWeek.**THURSDAY**..DayOfWeek.**FRIDAY** && 
                              **it**.**scheduledClass**.**recurrences** == 1 
                                -> -300 dow **!in** DayOfWeek.**THURSDAY**..DayOfWeek.**FRIDAY** && 
                              **it**.**scheduledClass**.**recurrences** == 1 -
                                 > 300 **else** -> 0
                        }
                    **}**, *// make search start at beginning of week*                    **{ it**.**block**.**range**.**start }**, *// followed by class length* **{**-**it**.**scheduledClass**.**slotsNeededPerSession } **)
    ).*toList*()

    *// this is a recursive function for exploring nodes 
    // in a branch-and-bound tree* **fun** traverse(currentBranch: BranchNode? = **null**): BranchNode? {

        **if** (currentBranch != **null** && 
            currentBranch.**remainingSlots**.isEmpty()) {
            **return** currentBranch
        }

 **for** (candidateValue **in** intArrayOf(1,0)) {
            **val** nextBranch = BranchNode(candidateValue, 
                 currentBranch?.remainingSlots?: sortedSlots,
                currentBranch
            )

            **if** (nextBranch.isSolution)
                **return** nextBranch

            **if** (nextBranch.isContinuable) {
                **val** terminalBranch = traverse(nextBranch)
                **if** (terminalBranch?.isSolution == **true**) {
                    **return** terminalBranch
                }
            }
        }
        **return null** }

    *// start with the first Slot and set it as the seed
    // recursively traverse from the seed and get a solution* **val** solution = traverse()

    solution?.traverseBackwards?.forEach **{** it.applySolution() **}**?: **throw** Exception(**"Infeasible"**)
}**import** java.time.LocalDate
**import** java.time.LocalTime

*// Any Monday through Friday date range will work* **val** *operatingDates* = 
    LocalDate.of(2017,10,16)..LocalDate.of(2017,10,20)**val** *operatingDay* = LocalTime.of(8,0)..LocalTime.of(17,0)

**val** *breaks* = *listOf*<ClosedRange<LocalTime>>(
        LocalTime.of(11,30)..LocalTime.of(12,59)
)

我还使用启发式搜索“最受约束”的位置,这意味着它们更有可能被分配“1”。例如,3-recurrence 类必须安排在星期一,所以我们应该首先评估它在星期一的时间段。启发式算法还会优先搜索高重现类(比如 3 和 2 ),这样我们就可以完成搜索。毕竟高递归类没有太大的灵活性,应该先评估。

请注意,我们还积极地“提前”删除我们不再有理由搜索的未探索的值。例如,如果我的分支刚刚为生物 101 的 9:30AM 时间段分配了“1 ”,我应该为该 9:30AM 时间段和生物 101 提前修剪所有时间段,因为这两个时间段都已被该分支占用。

现在我剩下要做的就是调用这个递归函数并打印结果!

**fun** main(args: Array<String>) {

    *executeBranchingSearch*() ScheduledClass.**all**.*sortedBy* **{ it**.**start }** .*forEach* **{** *println*(**"${it**.**name}-   
              ${it**.**daysOfWeek**.*joinToString*(**"/"**)**} 
              ${it**.**start**.toLocalTime()**}-${it**.**end**.toLocalTime()**}"**)
         **}**
}

如果你像我一样设置你的试探法和参数,下面是它生成的时间表:

Linear Algebra I- MONDAY/WEDNESDAY/FRIDAY 08:00-10:00
English 101- MONDAY/WEDNESDAY/FRIDAY 10:00-11:30
Supply Chain 300- MONDAY/WEDNESDAY 13:00-15:30
Math 300- MONDAY/WEDNESDAY 15:30-17:00
Calculus I- TUESDAY/THURSDAY 08:00-10:00
Psych 101- TUESDAY/THURSDAY 10:00-11:00
Sociology 101- TUESDAY/THURSDAY 13:00-14:00
Biology 101- TUESDAY/THURSDAY 14:00-15:00
Orientation 101- THURSDAY 15:00-16:00
Psych 300- FRIDAY 13:00-16:00

验证输出。不错吧?如果我们要直观地绘制出来,时间表应该是这样的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

很酷,不是吗?除了调度员工和其他资源,我们还可以使用完全相同的方法。它还可以用于任何需要找到可行或最优值以找到更好的解决方案的离散模型。这在最意想不到的情况下非常方便。我不能分享细节,但我有一个项目,似乎有预测/回归的性质。当我放弃了传统的连续回归模型,并谨慎地使用树搜索技术时,它非常有效,比任何回归库更好地解决了我的具体问题。

在我结束之前,如果您想要针对多个教室进行调度,只需针对时间段、班级和房间设置每个二进制Slot变量,使其成为三维的,如下所示。然后,您可以创建约束,以确保任何房间都不会被占用两次。否则,建模工作是一样的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

A 3-dimensional model that defines variables against multiple rooms

希望你们觉得这很有趣并且有用。我也希望这能让你看到其他已经存在了几十年却很少曝光的“人工智能”模型。如果你想了解更多,一定要看看 Coursera 上的离散优化课程,从运筹学领域了解更多的秘密。

源代码:

[## 托马斯尼尔德/优化调度演示

创建教室时间表的分支定界求解器

github.com](https://github.com/thomasnield/optimized-scheduling-demo/tree/master/kotlin_from_scratch_solution)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值