直观地学习数据结构
发展视觉直觉以理解复杂数据结构的艺术。
作为一名经历了大学经历考验和磨难的计算机科学本科生,我遇到过的最被滥用的术语之一是直觉。从第一次打开终端,我的教授向我保证,有一种内在的理解会冲刷我。这种直觉是慢慢产生的,但通常不是来自讲课或残酷的考试。它来自于缓慢地完成家庭作业和项目,试图理解我面前的这个金属大块头是如何解释我在键盘上疯狂键入的希腊语的。然而,随着我成为一名程序员,我开始看到现实世界中与数字世界直接相关的模式和寓言,这极大地帮助了我的理解。
本质上,计算机科学只不过是试图让一块石头以你的方式思考,让一个无生命的物体以与程序员看待世界的方式相似的方式感知世界。因此,在一种悖论中,计算机让用户重新分析他们自己看待世界的方式,将他们的感觉分解为处理器可以理解的基本构件。所以,我的教授们所说的这种直觉不是一种编程:学习计算机如何思考,而是理解程序员如何思考,将复杂的感知分解成计算机可以概念化的比特大小的积木。我清楚地记得,当我意识到这一点的时候——我正在学习一种叫做链表的数据结构。
我能找到的最简洁的链表定义是:
“链表是一种线性数据结构,其中的元素不是存储在连续的内存位置上”(GeeksforGeeks.com:链表数据结构)
这个定义虽然在技术意义上是描述性的,但它很少告诉我们这个结构的物理表示,所以当我们继续阅读时,我们看到了一幅图:
链表图片||来源:https://www.geeksforgeeks.org/data-structures/linked-list/
现在,有了这张图片,我们对这个数据结构有了更多的了解。似乎有一个开始(头)和一个结束(空),中间有一些节点。存储数据的节点都指向另一个节点,它们之间存在单向关系,每个节点都知道哪个节点跟在后面。现在,根据现代学术界的观点,我们应该准备好实现和使用这种数据结构来达到我们想要的任何目的,对吗?嗯,它比这个稍微复杂一点,在我们进入这个数据结构的内部之前,有一个直觉需要开发。我们如何从这个结构中获取一条信息?我们怎么把一个放进去?回顾一下之前的观点,我们不应该考虑计算机如何完成这项任务,而应该考虑在人类世界中我们会如何看待这一点。因此,为了开发这个模型,我们现在将开始一段小学时期最美好的回忆之旅。
当我还是个孩子的时候,这个简单的玩具到处都是,这可能是为什么很多年后我重温它来帮助我理解复杂的数据结构。
童年回忆||作者图片
现在,想象一下,我们让一个孩子从堆栈中取出深蓝色的戒指。只有一种方法可以做到这一点,所以我们观察孩子首先从顶部拿起紫色的环,然后是每个连续的环,直到最后深蓝色的环被抽出。太好了!!!这和计算机科学又有什么关系呢?好吧,我们可以把这个游戏重新设计成一个链表的实现,我们会看到这两者是怎么一回事。
问题的重铸版||作者图片
正如我们所看到的,这是以新的视角展示的上图,顶部的紫色环作为头部,每个连续的环作为一个节点,而结构的蓝绿色底部作为空。这个模型保持了链表的单向性吗?除非孩子在其中一个环上咬出一个洞,那么是的。结构有头有尾吗?没错。有什么环看起来是重叠的吗?不,满足连续内存位。因此,有了这个模型,我们可以很容易地看到如何从系统中可视化地提取数据。假设我们想要在红色环中存储一条信息。首先,我们将取下紫色的顶部(头)并继续下去,直到堆栈的顶部是红色的环。当我们把它拿掉,我们现在有了我们正在寻找的数据。
去掉头像后||作者图片
为了用伪代码实现这个动作序列,我们需要假设孩子一次至少可以看两个环。他们可以查看他们当前持有的戒指,即头,以及堆叠中的下一枚戒指,即尾。因此,当孩子将头拉出堆栈时,当前的尾现在位于新的顶部,因此它成为头,它下面的环成为新的尾。因此,要在代码中实现这一点,我们可以说:
直到深蓝色的环是头部**,继续移除环:因此将顶部的环重新分配给头部。**
在伪代码中:
*while head != dark blue:
head = tail
return head*
正如我们所看到的,我们已经开发了一个现实世界的寓言来描述这个抽象的概念,一个视觉直觉来描述可以应用于计算机科学中许多概念的系统。
为 Hadoop 访问控制设计一个联合代理
理解联邦方法中的数据授权以获得更好的安全性。
作者图片
你是数码守望者的粉丝吗?想想一个人工智能系统,可以处理海量数据,阻止城市犯罪?是的,你猜对了, POI 或相关人员是一个强有力的例子,说明数据可能比诺克斯堡的所有黄金都值钱(如果真的有黄金存在,而美国政府一直否认这一点)!
Hadoop 是最强大的数据处理开源框架之一。但是,它存在安全问题,我们将通过联合访问代理来减少这一问题。在我们开始探索 Hadoop 访问控制的新框架之前,让我们先了解一下 Hadoop 框架。
Hadoop 是什么?
Hadoop 是由 Apache 开发的开源框架。它基于数据聚类和并行处理。不可否认的事实是,一个大型开源社区一直在支持 Hadoop 在大数据应用程序中的存在。他们为创建更强大的企业级数据应用程序堆栈做出了贡献。
存储和处理大量数据的能力对于更大规模地接受 Hadoop 至关重要。Hadoop 擅长结构化和非结构化数据的计算资源管理。但是,这种现代数据架构存在一些挑战,尤其是在数据安全性方面。
为了了解安全挑战,让我们来了解一下 Hadoop 在 BigData 中的应用。
Hadoop 是如何运作的?
我们知道 Hadoop 是基于并行处理和分布式计算机制而设计的。该平台提供了 Hadoop 分布式文件系统或 HDFS。HDFS 支持硬件商品和不同处理框架的容纳。
IAM 或 HDFS 身份认证管理系统用于定义一组协议,帮助最终用户通过应用程序访问数据。这些协议集被称为 API 或应用程序编程接口,这些接口可以由企业开发用于数据访问。
Hadoop 联盟
随着数据集群的增加,Hadoop 框架面临着很大的压力。为了解决这个问题,它引入了两个联盟
- HDFS 联邦
- 纱线联盟
HDFS 联盟使用多个命名节点。这些 NameNodes 是独立的,管理整个名称空间的子集。这里,NameNodes 维护名为名称空间的文件系统。他们通过名称分组来维护文件的层次结构。树状文件结构在一个名为名称空间的容器中进行管理。
任何 HDFS 系统都由两层组成。
图片: HDFS 联邦系统(下一代大数据联邦访问控制:参考模型
**第 1 层:**NameNodes 中的名称空间控制着与目录、文件和块相关的数据的存储。
**第 2 层:**它是支持低级操作的块存储,有两个部分——数据的块管理和物理存储。
通过添加水平 DataNodes,可以轻松扩展物理存储,这与 NameNodes 不同。扩展 NameNodes 有一个限制,因为它只能垂直进行,由于单节点架构,这很难做到。但是,有了 HDFS 联邦,NameNodes 的名称空间层可以水平扩展。
HDFS 建筑
图片: HDFS 架构(下一代大数据联邦访问控制:参考模型)
它有一个架构来提高大数据存储和访问管理的效率。HDFS 结构有两个主要组成部分。
- 将单个节点 NameNode 作为主节点
- 在集群从属服务器上运行的几个 DataNodes。
命名节点运行命名空间层来管理与文件系统相关的数据。它管理对文件的访问,并使用分级系统。它还跟踪块的位置,并维护日志文件,以便在必要时进行审计。这些文件在内部被分成多个块。块被放置在存储在本地存储磁盘中的数据节点中。
HDFS 的出入控制
如果一个客户想要访问存储在 HDFS 系统中的任何数据,就会发出一个调用。在收到调用后,HDFS 使用 NameNodes 来提供对存储在本地磁盘的 DataNodes 中的数据的访问。
数据一旦写入,没有高级 IAM 协议就无法编辑。没有适当的协议,HDFS 不允许对客户端的任何其他访问。因此,所提供的访问是一次写入多次读取。
在这里,HDFS 建筑提出了几个挑战。
- 客户端需要获得多级权限才能访问数据。
- 重新认证任务需要取消原始会话,从而造成更多的时间浪费。
- 由于成千上万的节点到节点的交互,通信的复杂性增加了。
- 节点之间缺乏端到端的数据加密可能会威胁到数据安全。
- 为了有效管理,节点编排必须在令牌时间内完成。
- 安全审计需要广泛且难以执行。
- 由于几个 NNs 集群,日志信息审计变得很困难。
HDFS 联邦接入代理
联合经纪人并不新鲜。许多企业使用这种解决方案进行数据访问控制。这种体系结构引入了一个中间层,它将客户端的流量重定向到代理服务器或网关进行身份验证。访问代理是基于云的系统或内部数据中心的授权执行点。
**图片:**HDFS 联邦代理系统(下一代大数据联邦访问控制:参考模型
这里,我们发现了一个联邦访问代理,它可以抽象远程访问调用的管理。这些调用通常是客户端为了数据访问而对 Hadoop 联合服务进行的。
该架构执行两个基本功能。
- Apache Knox 网关用于客户端识别和集群访问控制。
- 执行管理员授权分配。
在这里,客户端需要在 Knox 网关得到认证。接下来,这个用于身份验证的 ClientID 被传递给 Ranger,以验证对 NNs 的访问。验证客户端调用后,将建立一个安全会话。
如果 Ranger 授权成功,它将提供访问 NNs 的证书。它将为客户端分配与每个 NameNode 交互的权限。NN 将通过分配给特定 NameNode 的 DataNodes 执行数据访问。
每次尝试访问 DataNodes 进行数据访问时,都会创建审计日志。这些日志存储在一个集中的 HDFS 存储中,以降低跨集群分布审计日志的复杂性。
结论:
我们发现使用联合代理可以降低 Hadoop 框架中安全性和客户端认证的复杂性。由于世界各地的企业都在大力投资数据聚合和存储,因此需要像 Hadoop 这样高效的框架。但是,由于它使用数据集群和并行处理,人们已经感受到了联合的需要。
开源框架已经见证了联邦框架的兴起,但是还有一些问题需要解决。联合代理增加了额外的身份验证层,创建了更加严格的数据安全性。它还简化了对数据的访问,而不需要太多的验证时间。因为抽象层有两个不同的授权和验证网关来快速推进流程。
这种数据访问是通过协议实现的。企业可以使用应用程序编程接口在联邦层之前创建数据访问协议。那么,你愿意冒数据被盗的风险吗?或者创建安全的数据访问、验证和存储协议。
联系我们 立即获取 API 开发解决方案并创建安全协议!
面向数据科学的开发运维:让您的 Python 项目具有可重复性
从依赖地狱中拯救你的朋友
在亚马逊担任软件工程师的第一年年末,我的经理给我上了宝贵的一课。提高团队的效率和我个人的贡献一样重要。
我把这一课带到了位于impossible的建模团队,在那里,当我们处理大规模模拟项目时,我专注于最大化科学家的生产力。这些经历现在让我倡导我们一起努力解决一个我经历了近十年的问题。
问题是
现在是 2013 年,我是 Python 开发的新手,正在玩一些自然语言处理代码。根据电子邮件的内容推断人们的态度(感谢安然)。当时对我来说这就像科幻小说。
不幸的是,这在一段时间内仍然是科幻的,因为我花了 4 天时间让作者的代码运行起来。缺少系统库,相互冲突的依赖关系,未知的 Python 版本。在接下来的 7 年里,软件工程取得了很大的进步,但是这个问题仍然存在。为什么运行对方的代码这么难?
一些历史背景
Python 包管理有着悠久而丰富多彩的历史,这是我从未想过会写的一句话。 开源应用的架构 描述了社区从一个打包系统到下一个打包系统的几次迁移。经过多年的迭代,我们已经有了几种安装 Python 代码的主流方式和两种不同的主要包索引。每个 Python 开发人员都需要投入一些时间来学习它们,以确保他们的工作可供他人使用。
强制 XKCD(https://xkcd.com/1987/)
本指南
实现一般意义上的可再现性真的很难——数据版本控制、确定性保证等等。本指南只解决依赖管理问题。每个 Python 开发人员都有可能确保他们的代码可以安装在他们同事的机器上。我的目标是帮助传授必要的“devops”知识,这样你就可以改变你的工作的成熟度。
默认情况下,您的项目代码与安装在您笔记本电脑上的所有东西交织在一起。尽管在依赖配置方面做了一些努力,但它是一个自包含的产品,将在开源生态系统中蓬勃发展。
在项目的早期进行组织是关键。它留给您维护您的环境的更简单的任务,以便您的工作是一种乐趣,构建,部署和发布!
可复制的项目对合作者更有吸引力,迭代更快,并且可移植。这对于自动化测试、分析和在像活页夹这样的服务上分享你的工作是非常棒的。
关于 Devops 的一个注记
本指南不是关于编写代码的,而是关于一旦编写好代码,有效地使用它所必需的活动。在传统的软件开发中,“操作”不是开发者关心的问题。然而,在过去的 15 年中,角色已经合并(因此是开发人员操作),因为改进的工具允许个人编写和运行他们的代码。学习操作技能让你产生影响!
库与应用
把你的作品交到人们手中是不同的,这取决于你是在制作一个库还是应用程序。如果您正在构建库,那么您可能已经了解了许多依赖项管理的机制,以及如何通过 Pypi 或 conda 通道使您的工作可用。
另一方面,可能在 Jupyter 笔记本中分析数据、创建 CLI 工具或构建仪表板的应用程序构建者是从重新思考依赖性管理中获益最多的人。
你的代码只是冰山一角
我想挑战简单地分享你的代码就意味着你的分析是可重复的这一观点。您的代码实际上是冰山的一角,为了让它在新的环境中运行,需要一系列工具来重建冰山。
你的项目不仅仅是你的代码。它几乎肯定需要外部包,其中一些可能具有特定的本机依赖性(专门为某个操作系统构建的二进制文件),并且可能需要特殊的硬件。目标是以编程方式尽可能多地指定这些内容。(图片由作者提供)
假设您已经将您的 repo 上传到 GitHub,我们如何确保其他人可以复制您的项目环境的下一个级别 Python 包?这就需要选择一个包管理器并创建一个依赖文件。
软件包管理架构入门
你无疑对包管理器客户端比较熟悉:pip install
、conda activate
等。但这只是这个系统的一部分。
包管理器从存放数百万不同社区提交的项目的存储库中定位并获取必要的依赖项。(图片由作者提供)
每个包管理器客户端在您的本地设备上都有配置,告诉它当前项目需要什么(例如 matplotlib v3.2.1,python 3.8+),应该检查哪些包索引,等等。
使用配置,客户端调用一个或多个包索引来解析哪些文件需要下载到您的系统上,然后从可能不同的包存储中下载这些文件。
现在我们已经描述了用于重现项目依赖关系的不同组件,我们可以评估主要的包管理器客户端选项。
选择您的软件包管理器
我将只讨论 pip、pipenv 和 conda,因为它们将涵盖您的几乎所有用例。我发现自己出于不同的原因经常使用这三种方法,如果你喜欢冒险,还有更多的方法。
1.Pip (+ Virtualenv)
✅ 安装速度快
✅简单配置文件
❌不锁定可传递依赖关系
❌ 必须组合两个独立的 CLI 工具
❌不能锁定 Python 版本
ℹ️使用纯文本配置requirements.txt
Pip 是默认的 Python 包管理客户端,它是随 Python 自动安装的。Virtualenv 允许您用自己的 Python 二进制文件和依赖项为项目创建一个独立的目录。
➜ which python
/usr/local/anaconda3/bin/python # my system python~/Desktop/cfg
➜ virtualenv venv # create the virtualenv
...
~/Desktop/cfg
➜ . venv/bin/activate # activate it.➜ which python # $PATH has changed!
/Users/alexremedios/Desktop/cfg/venv/bin/python~/Desktop/cfg
➜ ls -la venv # take a look inside
total 8
drwxr-xr-x 21 alexremedios staff 672B 26 May 12:01 bin
drwxr-xr-x 3 alexremedios staff 96B 26 May 12:01 lib
-rw-r--r-- 1 alexremedios staff 265B 26 May 12:01 pyvenv.cfg
venv/bin
包含您的 Python 解释器副本和用于激活虚拟环境的脚本。venv/lib
包含site-packages
,在那里pip install
放置您的 Python 依赖项。
Pip + Virtualenv 让您可以使用不同的 Python 版本和依赖项在一台机器上运行多个 repos。
⚠️没有 virtualenv,Pip 写入您的全局站点包目录,这可能会导致混乱和冲突。
ℹ️您可以使用which
找到当前 python 环境的站点包目录
~/Desktop/cfg
➜ which python
/usr/local/anaconda3/bin/python~/Desktop/cfg
➜ ls -la /usr/local/anaconda3/lib/python3.7/site-packages
现在 Pip + Virtualenv 感觉有点低级,但是对于在你的笔记本电脑上有多个项目污染你的全球包环境的问题,这是最简单的可能的解决方案。
如何导出 pip 环境…正确的方法
我推荐手动编写你的 requirements.txt,你大概没有超过 10 个直接需求,文件格式超级简单。手动编写文件还会鼓励您留意项目所依赖的内容。
# requirements.txt, doesn't have to be complex
matplotlib>=3.2
numpy
pandas
创建 requirements.txt 的另一种方法是冻结您当前的环境:pip freeze > requirements.txt
但是您应该在创建文件之后减少以下缺点。
⚠️ ❄️ 1.传递依赖将变成直接依赖
您可能想知道为什么您只安装了两个包,而现在您的 requirements.txt 中有 50 个包,这是因为 pip freeze 不知道您直接需要的包和子依赖包之间的区别。
# pip freeze > requirements.txt results in the whole transitive closure being treated as direct dependencies
…
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.4
spinners==0.0.23
SQLAlchemy==1.3.15
streamlit==0.56.0
tenacity==6.0.0
termcolor==1.1.0
terminado==0.8.3
testpath==0.4.4
textwrap3==0.9.2
timeago==1.0.13
toml==0.10.0
toolz==0.10.0
toposort==1.5
tornado==6.0.3
tqdm==4.42.1
traitlets==4.3.3
…
这是不好的,因为当你想删除一个依赖项时,你必须手动识别并删除所有的子依赖项。或者,你的 Python 环境将会有很多不需要的依赖,这会导致依赖地狱。
⚠️ ❄️ 2.版本将被过度固定
版本锁定是指您确切地指定您的项目应该使用哪个版本的依赖项,如上所示(例如tornado==6.0.3
)。虽然能够保证其他人将与您的项目安装相同的包版本是有用的,但是将每个依赖项都固定在 requirements.txt 中并不是最好的解决方案,原因有三。
第一个原因是您必须通过更改固定版本来手动更新每个包。您的依赖项的次要版本更新包含错误修复和其他改进。定期更新依赖关系对于防止代码腐烂很有用。
第二个原因是,当你添加一个新的需求时,它更有可能导致冲突。例如,当您只需要jupyter-client==6.*
时依赖jupyter-client==6.0.0
会导致不必要的冲突,如果您随后安装依赖jupyter-client>=6.1.0
的nbconvert
。
ERROR: nbclient 0.3.1 has requirement jupyter-client>=6.1.0, but you'll have jupyter-client 6.0.0 which is incompatible.
不固定所有需求的最后一个原因是,如果其他人遇到上述问题,他们会对使用您的代码感到困惑。他们如何知道您的代码是否真的需要需求的特定版本?
也就是说,您应该只在必要的时候将版本固定在特定的依赖项上。在这种情况下,最好留下评论。
如果您想获得锁定所有依赖项版本的好处而没有这些问题,那么 Pipenv 是您的最佳选择。
2)管道
👍推荐,除非你需要大量的科学库
✅锁定传递依赖
✅锁定 Python 版本
✅ CLI 工具自动创建依赖配置
❌锁定传递依赖可能很慢
ℹ️使用 TOML 格式的Pipfile
和 JSON 格式的Pipfile.lock
进行配置
Pipenv 就像包装在命令行工具中的 pip + virtualenv,它也锁定了可传递的依赖关系。
什么是锁定?
锁定将您的一般需求锁定在整个传递闭包的精确版本上。例如,如果你说你需要matplotlib==3.2
,那么加锁会让你说你最后一次运行代码使用了matplotlib==3.2.1
+所使用的子依赖的确切版本
前面我说过版本锁定是不好的,但是 pipenv 的锁定足够智能,可以给你版本锁定的好处,而没有脆弱性。
Pipfile 和 Pipfile.lock
Pipfile 类似于 requirements.txt,它是人类可读的,并且指定了您的直接 Python 包依赖项+您的 Python 版本(不同于 requirements.txt)。
您通常不需要直接编辑它们,因为 pipenv CLI 工具会在您pipenv install something
时为您管理它们。
# Pipfile is a TOML formatted description of your project runtime and direct dependencies.[[source]]
name = "pypi"
url = "[https://pypi.org/simple](https://pypi.org/simple)"
verify_ssl = true
[dev-packages]
pylint = "*"
autopep8 = "*"
pipenv = "*"
pytest = "*"
python-magic = "*"
[packages]
treebeard = {editable = true,path = "."}
[requires]
python_version = "3.7"
[pipenv]
allow_prereleases = true
每次 Pipfile 发生变化时,都会重新生成锁定文件。它描述了你的项目的传递闭包,即当前安装的所有依赖项(不仅仅是直接依赖项)。Pipfile.lock 非常适合部署您的工作:它准确地描述了要安装哪些包,考虑到有些包只在特定的操作系统上需要。
// Pipfile.lock is a JSON formatted description of your project's transitive closure, programmatically derived from your Pipfile
...
"appnope": {
"hashes": [
"sha256:5b26757dc6f79...",
"sha256:8b995ffe925347a2138d7..."
],
"markers": "platform_system == 'Darwin'",
"version": "==0.1.0"
},
...
注意:Pipenv 的一个快速发展的替代品是诗歌。
3)康达
👍推荐用于大型数据科学或机器学习项目
✅更好地支持原生依赖关系
✅锁定 Python 版本
✅默认安装包含有用的数据科学包
❌由于solving environment
❌不锁定可传递依赖关系
ℹ️使用 YAML 格式配置environment.yml
Conda 是三个包管理器中最强大的,因为它支持用任何语言编写的包,但是比 pip + virtualenv 慢一个数量级。
处理本机依赖关系
Conda 被设计成在比 pip 更低的层次上工作——使用二进制文件而不是 Python 包。这有几个很好的特性:
首先,这意味着 Python 和 Pip 只是 conda 包。您可以将它们安装在 conda 环境中,并像使用 Pip + virtualenv 一样使用 Pip + conda。也就是说,如果您已经处于 conda 环境中,则不需要 virtualenv。
第二,conda 的通用性意味着依赖于非 Python 包的 Python 包往往会工作得更好。
例子:安装 cartopy(一个通用的映射库) Cartopy 可能很难运行。它是一个 Python 库,可以通过 Pypi 安装,但是 Pypi 包通常不包含它们运行所需的所有本机依赖项。
因此,您可能需要安装带有 pip 的 Python 代码
➜ pip install cartopy
然后用您平台特定的包管理器来填充缺失的库,例如brew
、yum
或apt-get
。
brew install proj
Conda 不在乎 cartopy 是用 python 写的,但 proj 是用 C++写的,它照样安装它们。
会照顾好一切。
conda 的这个特性对于经常依赖于这些原生依赖项的科学用例来说非常有用。
康达频道和康达锻造
因为 conda 能够管理本机依赖项,所以它有一个不同于 Pypi 的包结构。因此,您可以从 conda 通道安装 conda 软件包。
包的默认来源渠道是https://anaconda.org/anaconda,类似于https://pypi.org/是 pip 的默认渠道。
然而,与 pip 不同的是,有一些非默认的频道包含许多您需要的软件包,例如康达锻造频道(https://anaconda.org/conda-forge)。
Conda 包名称
Pypi、anaconda、conda forge 和其他通道都有自己的名称空间。这意味着这是可能的,但是不能保证一个公共包在每个源中都有相同的名字。
⚠️🐍不要假设 requirements.txt 可以粘贴到 environment.yml 而不改变一些包名(除非您将它们粘贴到pip:
部分)。
为您的 conda 环境创建 environment.yml
与 pip 类似,我建议手动维护您的环境。yml,但是如果事情已经远离您:
➜ **conda env export --from-history --no-builds** # ✅ good
name: base
channels:
- conda-forge
- defaults
dependencies:
- conda
- python
- conda-env
# Note! Conda does not know about anything you have pip installed, you will have to manually add them:
- pip:
- tensorflow
**—-no-builds**
标志防止包含特定于平台的构建 id。如果没有这个标志,你将会遇到像ResolvePackageNotFound
这样的问题,即使是存在于每个平台上的依赖关系。
举例:OpenSSL openssl=1.1.1d
存在于每个平台,openssl=1.1.1d=h0b31af3_0
只存在于 OS X 上自己看。
**--from-history**
是在 Conda 4.7.12 中引入的,因此您可能需要更新 Conda。如果你花了一段时间让某个东西工作起来,却忘记记下你安装了什么,那么这个标志非常有用。Conda 将只导出您使用conda install
明确安装的包和版本。
⚠️ ❄️如果你不使用这些标志,你会遇到与pip freeze
相同的问题
➜ **conda env export** # ❌ bad, has transitive dependencies with pinned versionsname: blah
channels:
- conda-forge
- defaults
dependencies:
- ca-certificates=2019.11.28=hecc5488_0
- certifi=2019.11.28=py37_0
- libcxx=9.0.1=1
- libffi=3.2.1=h6de7cb9_1006
- ncurses=6.1=h0a44026_1002
- openssl=1.1.1d=h0b31af3_0
验证项目再现性
唷,这可能是你主动学习配置 conda 环境之类的最长时间了。
现在,我们已经选择了一个包管理器,并对它进行了配置,以成功安装我们的依赖项。这是一个巨大的进步,让合作者可以快速开始,但是我们如何确保随着项目的增长,可再现性不会随着时间的推移而被侵蚀呢?
手动方式是定期在一台新机器上安装你的项目,只是为了检查它是否还在运行。这听起来效率很低,所以在结束之前,我打算先研究一下自动方式。
监控你工作的整体质量的过程被称为持续集成(CI) ,它围绕着将自动化服务连接到你的源代码库。持续集成超出了本文档的范围,但我将向您介绍两个产品: Github Actions ,它默认集成到 Github 中,以及 Treebeard (完全公开:我正在开发),它也可以安装在 Github repos 上以跟踪可再现性。
一旦你经历了使你的项目可复制的麻烦,不附加 CI 服务将是一种浪费。这就像写了一篇博客文章却没有进行拼写检查一样。说到这里,我们结束吧。
外卖食品
- 在您的项目中包含一个维护良好的依赖文件,可以让您通过避免依赖地狱,以更快的速度进行协作和交付
- 如果您需要
pip freeze
或conda export
您的环境,记得删除可传递的依赖项和不必要的固定版本 - 持续集成让您可以自动检查依赖性
进一步阅读
这篇文章讨论了一些 devops 实践,它们帮助你在团队中更有效地工作,并让你的代码在外面的世界中运行。有关相关实践,请阅读:
与 GCP 合作开发数据科学
来源:https://pix abay . com/photos/dock-ship-container-port-boat-1277744/
为模型服务部署生产级容器
数据科学团队的职能之一是建立机器学习(ML)模型,为产品和个性化提供预测信号。虽然 DevOps 并不总是被视为数据科学团队的核心职责,但随着这些团队开始更多地负责运行和维护数据产品,它变得越来越重要。数据科学家不再将代码或模型交给工程团队进行部署,而是在生产中拥有系统,这种情况越来越普遍。虽然我之前写过一本关于用 Python 构建可伸缩 ML 管道的书,但是我并没有太关注构建数据产品的维护方面。这篇文章的目标是通过一个示例 ML 系统,它涵盖了数据科学 DevOps 的一些方面。
【https://www.amazon.com/dp/165206463X
模型服务是指托管其他服务可以调用的端点,以便从机器学习模型中获得预测。这些系统通常是实时预测,其中关于用户或 web 会话的状态被传递到端点。设置端点的一些方法包括使用 web 服务,如 Flask 和 Gunicorn,使用无服务器功能,如 GCP 云功能,以及使用消息协议,如 GCP PubSub。如果您希望建立一个需要以最小的延迟为大量请求提供服务的系统,那么您可能希望使用容器化的方法,而不是无服务器的功能,因为您将对系统的运行时环境有更多的控制,并且成本可以显著降低。在这篇文章中,我们将展示如何在谷歌云平台(GCP)上使用 Flask 和 Docker 结合谷歌 Kubernetes 引擎(GKE)构建一个可扩展的 ML 系统。Kubernetes 是一种越来越受 DevOps 欢迎的技术,也是一种越来越需要数据科学家的技能。
除了使用这些工具来构建一个能够以低延迟扩展到大量请求的数据产品之外,我们还将探讨模型服务的以下开发运维关注点:
- 如何部署具有高可用性的 ML 模型?
- 如何扩展您的部署以满足需求?
- 您如何设置警报和跟踪事件?
- 您如何推出系统的新部署?
在这篇文章中,我们将重点关注能够部署和监控 ML 模型的工具,而不是触及其他数据科学问题,如模型训练。在本帖中,我们将探讨以下主题:
- 为 GCP 建立发展环境
- 为本地模型服务开发 Flask 应用程序
- 用 Docker 将应用程序容器化
- 将容器发布到 GCP 容器注册中心
- 使用 GKE 在一个机器集群上托管服务
- 使用负载平衡器分发请求
- 使用 Stackdriver 进行分布式日志记录和度量跟踪
- 使用带有滚动更新的 GKE 部署新版本的服务
- 使用 Stackdriver 为警报设置警报
在本文中,我们将从一个烧瓶应用程序开始,并使用 GCP 将其扩展到生产级应用程序。在此过程中,我们将使用 Gunicorn、Docker、Kubernetes 和 Stackdriver 来解决大规模部署此端点时的 DevOps 问题。这篇文章的完整代码清单可以在 GitHub 上找到。
1.GCP 发展环境
在开始之前,你需要建立一个 GCP 账户,该账户提供 300 美元的免费积分。这些信用提供了足够的资金来启动和运行各种 GCP 工具。您还需要创建一个新的 GCP 项目,如下图所示。我们将创建一个项目,其名称为serving
,完整的项目 id 为serving-268422
。
在 GCP 创建一个新项目。
我推荐使用 Linux 环境来开发 Python 应用程序,因为 Docker 和 Gunicorn 最适合这些环境。如果您安装了以下应用程序并设置了命令行访问,您应该能够理解:
我不会深究安装这些应用程序的细节,因为安装会因您的计算环境而有很大的不同。需要注意的一点是,gcloud 工具会在幕后安装 Python 2.7,因此请确保您的命令行 Python 版本不会受到此安装的影响。设置好glcoud
之后,您可以使用以下命令创建一个凭证文件,用于对 GCP 的编程访问:
gcloud config set project serving-268422
gcloud auth login
gcloud init
gcloud iam service-accounts create serving
gcloud projects add-iam-policy-binding serving-268422 --member
"serviceAccount:[serving@serving-268422.iam.gserviceaccount.com](mailto:serving@serving-268420.iam.gserviceaccount.com)"
--role "roles/owner"
gcloud iam service-accounts keys create serving.json
--iam-account [serving@serving-268422.iam.gserviceaccount.com](mailto:serving@serving-268420.iam.gserviceaccount.com)
该脚本将创建一个名为serving
的新服务帐户,可以访问您的 GCP 资源。最后一步创建了一个名为serving.json
的凭证文件,我们将用它来连接 Stackdriver 之类的服务。接下来,我们将使用 pip 在 Google Cloud 中安装用于日志记录和监控的 Python 库,以及使用 Python 作为端点托管 ML 模型的标准库。
pip install google-cloud-logging
pip install google-cloud-monitoringpip install pandas
pip install scikit-learn
pip install flask
pip install gunicorn
我们现在已经设置好了构建和监控服务于 ML 模型的 web 服务所需的工具。
2.模特服务用烧瓶
为了演示如何将预测模型作为端点,我们将使用 scikit-learn 训练一个逻辑回归模型,然后使用 Flask 公开该模型。接下来,我们将使用 Gunicorn 使我们的服务成为多线程的,并扩展到更大的请求量。
服务于模型请求的示例 web 应用程序的完整代码如下面的代码片段所示。代码首先直接从 GitHub 获取一个训练数据集,然后用 scikit-learn 构建一个逻辑回归模型。接下来,predict
函数用于定义服务模型请求的端点。传入的参数( G1,G2,G3,…,G10 )从 JSON 表示转换成一个只有一行的 Pandas dataframe。这些变量中的每一个都跟踪客户过去是否购买过特定的产品,并且输出是客户购买新产品的倾向。dataframe 被传递给 fitted 模型对象以生成倾向得分,该倾向得分作为 JSON 有效载荷被返回给发出模型服务请求的客户机。关于这段代码的更多细节,请参考我之前关于将模型作为 web 端点的文章。
假设这个文件被命名为serving.py
,我们可以通过运行下面的命令来测试这个应用程序:python serving.py
。为了测试服务是否正常工作,我们可以使用 Python 中的请求模块:
运行该代码块的输出如下所示。结果显示 web 请求是成功的,并且模型为样本客户提供了 0.0673 的倾向得分。
**>>> print(result)** <Response [200]>**>>> print(result.json())** {'response': '0.06730006696024807', 'success': True}
Flask 使用单个进程来服务 web 请求,这限制了端点可以服务的流量。为了扩展到更多的请求,我们可以将 Flask 与一个 WSGI 服务器(比如 Gunicorn)结合使用。要使用 Gunicorn 运行模型服务,我们只需将文件名传递给 Gunicorn,如下所示。
gunicorn --bind 0.0.0.0 serving:app
运行此命令后,您将在端口 8000 上运行一个服务。为了测试服务是否正常工作,您可以更改上面的请求片段,使用端口 8000 而不是端口 5000。
3.集装箱模型
我们现在有了一个在本地机器上运行的模型服务应用程序,但是我们需要一种分发服务的方法,以便它可以扩展到大量的请求。虽然可以使用云提供商提供硬件来手动分发模型服务,但 Kubernetes 等工具提供了完全托管的方法来设置机器以服务请求。我们将使用 Docker 来封装应用程序,然后使用 GKE 托管服务。
容器化应用程序的 docker 文件如下所示。我们从 Ubuntu 环境开始,首先安装 Python 和 pip。接下来,我们安装所需的库,并复制应用程序代码和凭证文件。最后一个命令运行 Gunicorn,在端口 8000 上公开模型服务应用程序。
下面的命令说明了如何构建容器,在您的机器上查看生成的 Docker 图像,并以交互模式在本地运行容器。在本地测试容器以确保您在应用程序中使用的服务按预期工作是很有用的。要测试应用程序是否工作,请将上面的请求片段更新为使用端口 80。
sudo docker image build -t "model_service" .
sudo docker images
sudo docker run -it -p 80:8000 model_service
4.发布到容器注册表
要在 Kubernetes 中使用我们的容器,我们需要将图像推送到 Docker 注册中心,Kubernetes pods 可以从那里提取图像。GCP 提供了一种叫做容器注册的服务来提供这种功能。要将我们的容器推到注册表中,运行下面要点中显示的命令。
上面代码片段中的第一个命令将凭证文件传递给docker login
命令,以便登录到容器注册中心。下一个命令用您的 GCP 帐户 ID 标记图像,最后一个命令将图像推送到注册表。运行这些命令后,您可以浏览到 GCP 控制台的容器注册表部分,验证映像是否已成功推送。
集装箱注册中的模型服务图像。
5.与 GKE 一起部署
我们现在可以启动 Kubernetes 集群,在分布式环境中托管模型服务应用程序。第一步是通过浏览 GCP 控制台中的“Kubernetes Engine”选项卡并单击“create cluster”来设置集群。将model-serving
指定为集群名称,选择 2 个节点的池大小,并将 g1-small 实例类型用于节点池。这将创建一个小集群,我们可以使用它进行测试。
与 GKE 一起创造了一个库伯内特集群。
一旦集群启动,我们就可以使用我们的容器创建一个工作负载。要将我们的映像作为应用程序在 GKE 上运行,请执行以下步骤:
- 从“Kubernetes 引擎”中点击“工作负载”
- 单击“部署”
- 点击“图像路径”下的“选择”
- 选择带有最新标签的型号 _ 服务图像
- 点击“继续”
- 分配一个应用程序名称“serving”
- 在“集群”下,选择您新创建的集群
- 单击“部署”
这个过程可能需要几分钟才能完成。完成后,您可以单击工作负载来浏览管理工作负载的 pod 的状态。该映像应该部署到三个单元,如下所示。
模型在 GKE 应用的三个实例。
6.公开服务
我们现在有了在分布式环境中运行的服务,但是还没有一种与服务交互的方法。我们需要设置一个负载平衡器来将模型服务公开给开放的 web。执行以下步骤为服务设置负载平衡器:
- 选择“服务”工作负载
- 点击“操作”,然后点击“暴露”
- 为“端口”选择 80,为“目标端口”选择 8000
- 在“服务类型”下,选择“负载平衡器”
- 点击“暴露”
要使用负载平衡器,请单击您的工作负载并浏览到“公开服务”一节。您现在应该看到一个负载平衡器设置了一个公开服务的公共 IP 地址。您可以通过修改请求片段以使用负载平衡器的 IP 来测试服务。
服务工作负载的负载平衡器。
我们现在已经具备了可扩展模型托管服务的许多要素。如果需要,系统现在可以扩展到更多的单元,我们可以增加节点池大小和副本数量,以使用 GKE 处理大流量。此外,如果在系统中检测到故障,新的 pod 将会启动,因此 GKE 为我们的服务提供高可用性。我们在该系统的开发操作中缺少的关键部分是警报和监控。我们需要知道系统何时出现故障或异常,并快速做出响应。
7.使用 Stackdriver 进行监控
GCP 现在提供了 Stackdriver 的托管版本,这是一种提供日志记录、监控、警报和事件跟踪的服务。我们将使用日志功能来记录服务器状态更新,我们将使用监控功能来跟踪系统的请求量。为了实现这个功能,我们将使用 Stackdriver 的 Python 接口。
下面的代码片段展示了如何为 Stackdriver 设置一个日志客户端,并向服务记录一条文本消息。要在本地工作时设置日志客户端,您需要提供一个凭证文件,但是在 GCP 的计算实例或 GKE 上工作时,这一步可能不是必需的。
要浏览应用程序的日志,请在 GCP 控制台中搜索“日志”,然后选择“日志查看器”选项卡。选择“Global”作为资源,您应该会看到如下所示的日志消息。在下一节中,我们将使用日志记录来跟踪新的服务器实例何时启动,并记录导致异常的模型请求。
在 Stackdriver 中查看服务日志。
Stackdriver 提供了记录定制指标的能力,比如记录每分钟模型服务请求的数量。要使用定制指标,您需要首先创建一个定制指标,如下面的代码片段所示。该代码首先设置一个监控客户机,为记录定义一个新的度量,然后提供一个样本数据点。一旦设置了新的指标,您就可以在每台服务器上每分钟记录一条记录。
要查看自定义指标,请在 GCP 控制台中搜索“Stakdriver Monitoring API ”,然后选择“Metrics explorer”。通过搜索“custom”并选择“Global”作为资源来选择指标。您应该会看到一个图表,它绘制了一段时间内自定义指标的值。
使用 Stackdriver 监控功能跟踪每分钟的请求数。
在下一节中,我们将使用定制的指标来记录每分钟有多少模型请求被处理。在最后一节中,我们将展示如何使用自定义指标来设置警报。
8.执行滚动更新
现在,我们已经拥有了为模型服务应用程序设置警报和监控所需的所有组件。Flask 应用程序的完整代码清单如下所示。
以下是最初应用程序的主要变化:
- 我们为日志记录和监控引入了额外的库
- 我们根据 IP 和一个随机值为服务创建一个唯一的 ID
- 我们设置了日志记录和监控客户端,并记录服务器启动消息
- 我们创建一个计数器变量来跟踪请求计数
- 我们定义了一个函数来编写自定义指标
- 我们定义一个每秒运行一次的函数,将请求计数作为自定义指标传递给 Stackdriver 并重置计数
- 我们用导致失败的 try/except 块和日志请求来包装 Flask 请求
- 我们为每个 web 请求更新计数器值
一旦我们在本地测试完脚本,我们将需要重新构建容器,并向集群推送一个新版本。第一步是重新构建容器,并将更新后的映像推送到容器注册表,如下所示:
sudo docker image build -t "model_service" .
sudo docker tag model_service us.gcr.io/serving-268422/model_service
sudo docker push us.gcr.io/serving-268422/model_service**# Output** latest: digest: sha256:**286fe62e19368d37afd792b5bc6196dd5b223eada00e992d32ed6f66a4de939d** size: 3048
更新映像后,push 命令将输出一个摘要,我们可以用它在 GKE 上推出容器的新版本。第一步是选择上面显示的摘要的粗体部分。接下来,选择您的工作负载,单击“ACTIONS”,然后单击“Rolling update”。用新的容器摘要替换“图像”下的摘要,然后按更新。
用滚动更新来更新容器。
使用滚动更新的目的是防止在执行系统更新时服务出现任何停机。下图显示了 GKE 如何在运行旧版本的同时为新的服务版本构建新的 pod。一旦单元被加速旋转以匹配当前的工作负载,旧的单元版本将被删除。
GKE 为执行滚动更新提供了强大的功能,降低了 GCP 上基于容器的数据产品开发运维的复杂性。
9.使用 Stackdriver 进行事件跟踪
我们将在本文中讨论的最后一个 DevOps 问题是设置警报和响应事件。使用 Stackdriver 记录定制指标的一个主要好处是,我们可以为不同的问题设置警报,例如缺少检测到的数据或太多的传入请求。我们将通过执行以下步骤,设置一个当系统每分钟收到 10 个以上请求时触发的警报:
- 在 GCP 控制台中搜索“Stakdriver Monitoring API”
- 单击“警报”选项卡
- 单击“创建策略”
- 分配一个名称,“太多请求”
- 点击“添加条件”
- 在“目标”下,选择自定义指标和“全局”作为资源
- 在“配置”下,将阈值设置为 10
- 选择持续时间的“最近值”
- 点击“添加”
- 在“通知”下,单击“添加通知频道”
- 选择电子邮件并输入您的电子邮件地址
- 点击“添加”,然后点击“保存”。
我们现在设置了一个警报,如果自定义指标值超过每秒 10 个请求,就会触发一个事件。我们现在可以通过向负载平衡器 URL 发送 10 个以上的请求来生成警报。向端点发送过多流量的示例如下所示。
超过警报策略的请求计数。
几分钟后,Stackdriver 将检测到违反了警报策略,并触发一个事件。您将收到为警报设置的每个频道的通知。通过电子邮件发送的通知示例如下所示。
警报策略违规的电子邮件通知
如果触发了警报,您可以浏览 Stackdriver 中的“alerting”选项卡,查看状态更新并提供事件的附加注释。对于这个事件,我们可能会决定更改工作负载的自动缩放设置。对于其他类型的实例,比如停机,我们可能会部署一个新版本,或者为当前版本设置新的副本。
在堆栈驱动程序警报选项卡中查看事件。
Stackdriver 中的警报功能提供了许多有助于数据科学家拥有更多数据管道的功能。除了电子邮件提醒之外,您还可以使用其他渠道(如 Slack、PagerDuty 或 SMS)向团队通知提醒。
结论
在这篇文章中,我们介绍了数据科学团队承担更多将 ML 模型投入生产的 DevOps 责任所必需的许多构建模块。我们在这篇文章中提到的关键特性是滚动更新和提醒。当使用 GKE 时,GCP 提供了开箱即用的功能,这使得数据科学团队能够拥有更多部署预测模型的流程。
借助这些工具,数据科学团队可以构建在出现问题时触发警报的数据产品,调查日志以确定问题的根源,并部署新的模型版本,同时最大限度地减少停机时间。
本·韦伯是 Zynga 的一名杰出的数据科学家。我们正在招聘!
ML 和其他半真半假的 DevOps 生命周期的过程和工具
活动讲座
肯尼·丹尼尔| TMLS2019
关于演讲者
肯尼·丹尼尔是 Algorithmia 的创始人兼首席技术官。他在攻读博士学位期间,看到了大量从未公开的算法,于是萌生了开发 Algorithmia 的想法。
作为回应,他建立了 Algorithmia 云人工智能层,该层已帮助超过 80,000 名开发人员共享、管道化和消费超过 7000 个模型。
通过与数百家实施 ML 的公司合作,他创建了企业 AI 层,帮助世界上最大的组织部署、连接、管理和保护大规模的机器学习操作。Kenny 拥有卡内基梅隆大学和南加州大学的学位,在那里他学习了人工智能和机制设计。
DevOps —使用亚马逊 EKS、ECS 和 Docker 的无服务器 OCR-NLP 管道
我们如何使用由 Docker 和 Kubernetes 驱动的事件驱动微服务架构,自动扩展光学字符识别管道,每天将数千份 PDF 文档转换为文本
在最近的一个项目中,我们被要求创建一个能够将 PDF 文档转换为文本的管道。收到的 PDF 文档通常有 100 页,可以包含打字和手写文本。这些 PDF 文档由用户上传到 SFTP。正常情况下,平均每小时会有 30-40 个文档,但在高峰时期会高达 100 个。由于他们的业务不断增长,客户表示每天需要 OCR 多达一千份文档。这些文件然后被输入到一个 NLP 管道中做进一步的分析。
让我们做一个概念验证——我们的发现
转换 100 页文档的时间— 10 分钟
执行 OCR 的 Python 进程消耗了大约 6GB RAM 和 4 个 CPU。
我们需要找到一种渠道,不仅能满足我们的常规需求,还能在高峰期自动扩展。
最终实施
我们决定使用事件驱动的微服务来构建一个无服务器管道。整个过程细分如下:
- 以 PDF 格式上传的文档—使用 SFTP 的 AWS Transfer 处理
- 当上传新的 PDF 文档时触发 S3 事件通知—触发 Lambda 函数
- Lambda 函数在 Kinesis 流中添加 OCR 事件
- OCR 微服务被触发-使用 Tesseract 库(每页一个)将 PDF 转换为文本。在 MongoDB 中将文本输出保存为 JSON 文档
- 在 Kinesis 流中添加 NLP 事件
- NLP 微服务从 MongoDB 读取 JSON。NLP 的最终结果保存回 MongoDB
作者图片
技术栈
数据接收— AWS SFTP 服务
微服务— 存储在亚马逊弹性容器注册表(ECR)中的 Docker 图像
容器编排—EC2 节点上的亚马逊弹性库本内特服务(亚马逊 EKS)
用于容器的无服务器计算引擎— AWS Fargate
基础设施供应— 地形
消息传递— 亚马逊 Kinesis 流
网络体系结构
作者图片
聚类自动缩放
使用水平和垂直缩放的组合来实现集群自动缩放,如下所示:
垂直缩放—容器
根据我们的计算,我们能够在给定的 EC2 节点上支持 25 个正在运行的容器。我们用 3 个副本容器(minReplicas=3)启动 OCR 微服务,并将最大值设置为 25 (maxReplicas=25)。我们还设置 targetAverageUtilization=15,这意味着如果容器 CPU 利用率超过 15%,即容器正在处理一个文档,那么在给定的 EKS 节点上将一个新容器加速到最大值 25。
作者图片
水平缩放— EKS 节点
如果当前 ELS 节点已满**,即 25 个并发运行的容器,则 EKS 会自动提供一个新的 EKS 节点。此后,垂直扩展接管新节点并旋转新容器。**
通过这种方式,基础架构能够支持数百个 OCR 和 NLP 流程。高峰需求满足后,等待期开始。在等待期到期后,新部署的 EKS 节点和容器会缩减,以便达到最佳资源分配。
我希望这篇文章有助于启动您的 DevOps 知识。这些主题是由 Datafence Cloud Academy 提供的 DevOps 课程的一部分。
DevOps:下一个级别
DevOps 的新领域和 2020 年的发展趋势
自从 Patrick Debois 在 2009 年创造了 DevOps 这个术语以来,十多年过去了。在 IT 界,没有什么是确定的。所有的技术和技巧都在沿着我们无法阻挡的创新趋势继续发展。
我们不能仅仅说:“我厌倦了改变;请让我休息一下。”唯一的选择是为变化做好准备。
在 DevOps 中,由于云和市场的兴起,有一个显著的变化,要求总是更高。
DevOps 理念被大多数公司采用,并为质量和成本节约带来了重要的改进。无论如何,DevOps 场景正在不断发展,并适应新的市场需求。
在这篇文章中,我们将分析 DevOps 的新前沿以及与时俱进的知识。让我们看看这篇文章中有哪些最新的趋势可以遵循。
DevOps 增长
简而言之,DevOps 是将开发(Dev)和运营(Ops)集成在一起的实践,创建一个合作过程。
根据 IDC 的报告,到 2022 年,DevOps 市场将达到 80 亿美元,而 Grand View Research 的另一份报告称,到 2025 年将达到 130 亿美元。这意味着公司对这一主题的兴趣越来越大。
促成这种变化的另一个驱动因素是云的上升。根据甲骨文预测,到 2025 年,80%的 IT 解决方案将迁移到云。
自从 DevOps 概念被提出以来,DevOps 实践从未停止过发展,并得到了越来越强大的工具的支持。
这应该是阅读下一章的一个推动,并成为这一演变的一部分。
将安全性集成到开发运维流程中
安全性一直很重要,但随着数字服务和敏感数据(生物特征、照片等)存储的使用越来越多。),是至关重要的。
用户开始询问他们的数据在哪里,如何管理这些数据。我们的客户希望我们保护他们的信息安全。我们不能失败。顺便说一下,用户总是要求更高,我们需要支持敏捷流程来响应变化。
我们不能允许这样的情况,我们完成一个项目或一个重要的功能,然后我们需要提交给 SecOps。我们的项目可能会被 SecOps 停止一周,或者更糟,被拒绝。我们不能仅仅重做所有的工作,因为一些“小”的安全细节被开发人员忽略了。
这不仅仅是一个成本问题。更多的是时机问题。营销不会等待你的内部问题。客户将从其他供应商那里购买该功能。这就是为什么,几乎对于关键项目,我们需要将 DevOps 与 SecOps 集成,从一开始就让安全人员参与进来。
从一开始就牢记安全需求,并让安全团队参与进来,有助于防止发布过程中出现糟糕的情况和摩擦。
如果你有一个 NoOps 过程,这个问题就更重要了。NoOps 带来了更多的自动化,您可能会陷入这样一种境地:不受信任的代码在没有正确监督的情况下进入生产环境。
这种情况可以通过在流水线(或装配线,我们将在下面的段落中读到)中包含和自动化安全测试来解决。
我们不想因为安全漏洞而在报纸上结束,对吧?嗯,我们需要一种方法来整合敏捷性和安全性;这得到了 DevSecOps 哲学的支持。
NoOPS
NoOps 表示无操作。无服务器解决方案的普及和新的自动化前沿使 NoOps 梦想成为可能。它的理念是去除所有的平台管理部分,减少开发者和基础设施之间的摩擦。
正如我在《更好的编程》上发表的文章《 DevOps 已死,NoOps 万岁》中所解释的,对 DevOps 的尊重是:
DevOps […]意味着你总是需要一些手工劳动。你仍然有一个人在这个过程的大部分背后。这意味着用老方法工作。
NoOps 的目的是定义一种新的过程,在这种过程中,不需要将开发和运营结合起来,因为大多数任务都是自动化的。好处是上市时间更短,质量更好,维护工作更少。
这很有意思。我们可以同意背后的原则,但我们必须意识到故事的另一面:监控和安全。在一个完全自动化的系统中,一切都由代码驱动,谁来负责这些步骤呢?
这个问题的解决方案还是自动化。您需要对每个部分实现自动化,而不再是由一个人来完成,包括监控和安全。这收敛到 DevSecOps 方向。
从管道到装配线
当 DevOps 在 2009 年诞生时,典型的场景可能是一个单一的 web 应用程序。这是 MVC 模式发生革命性变化的时候,在我看来这是很久以前的事了。
今天的情况有点不同。在最简单的情况下,我们有两个应用程序(前端 SPA 和后端 API)。通常他们会有不同的发展,并由不同的团队管理。很可能,我们会为下一个项目选择微服务架构。这意味着要部署大量的应用程序和依赖关系。
我们的管道方法似乎相当有限。管道模型非常适合遵循线性模型(提交>编译>测试>部署),其中您的项目很容易用简单的流程来表示。让我们看一个例子。
传统的流水线方法
在图中,我们看到了代码,由到达 CI\CD 工具的 Pull 请求批准,构建并测试。如果在“构建到测试”阶段出现错误,问题将在部署之前提交给正确的解决方案。
这种方法可能仍然适用于大多数项目,但是在许多情况下,它可能是有限制的。
这种新方法类似于工业,它被称为“装配流水线”
就像在物理装配流水线中,每个部门都做出自己的贡献,为产品添加“一块”。
使用这种方法,所有参与这一过程的人从一开始就参与其中,并且从一开始就有一个清晰的流程。通过这种方式,许多团队可以合作并管理具有多种依赖关系的复杂场景(来自应用程序、安全性等)…).
装配线合作的一个例子
装配线是对复杂场景中 DevOps 碎片化的响应,需要文化协作作为粘合剂将所有部分结合在一起。此外,我们还需要一个“超级工具”来管理所有这些交互。
基于工业的类比,我们称之为装配岛。坏消息是,大多数常见的 CI\CD 工具本身并不支持这种方法,您将需要与一组工具结合在一起。
一切都是一个代码
没有比源代码更好的了。它是可版本化的,可以复制和粘贴,可以通过电子邮件发送,如果你执行两次,你会得到相同的结果。或者说,差不多,这就是我们对好代码的期望。
从历史上看,sysadmin 的事情并不相同。你不能剪切和粘贴一个数据中心,并通过电子邮件发送,对不对?
好消息是你的基础设施可以像源代码一样。
我们已经推出了像 Ansible 、 Terraform 或其他自动化框架这样的解决方案。我们可以对我们的脚本进行版本控制,并自动化基础架构管理。
此外,像在 Kubernetes 中那样的基础设施的声明性描述对于理解事物如何工作非常有帮助,而不需要阅读大量的文档或者通过对配置文件进行逆向工程来理解服务器配置。
流程开始时的自动化有助于以正确的方式开始,并为我们提供一个可复制的基础架构,这无疑是一个巨大的帮助(只是想一想灾难恢复或简单地创建一个新环境来测试单个功能)。
外卖
DevOps 是一个上升的话题。甚至很多东西都已经写好了;我们需要发展我们的能力以适应趋势。
这一变化中最重要的关键词是:
- 云
- 自动化
- 装配流水线
- NoOps
它们中的大多数都汇聚到同一个目标:使用技术来改进质量过程。
这种改变可以通过自动化人工任务、在流程中集成安全性和监控来实现,并使用云来加速这一过程。
这怎么可能呢?
我们需要一个工具或一组工具来支持所有的操作,并协调人们的合作,但像往常一样,改变心态是第一步。
感谢您的阅读。
觉得这篇文章有用?在 Medium 上关注我( Daniele Fontani )并查看我在下面 DevOps 上最受欢迎的文章!不要忘记👏这篇文章分享一下吧!
DevSecOps vs DataOps vs MLOps
为您的项目选择正确的工作流程
马文·迈耶在 Unsplash 上的照片
自从敏捷宣言在 2001 开始,许多软件开发方法出现了,每一种都试图改进过程。虽然基本的敏捷宣言仍然指导着工作流,但是今天主要的努力已经扩展到打破广泛领域的孤岛。这是通过将孤立的部门统一到协作团队中来实现的。
起初,开发和运营团队被统一到 DevOps 团队中。今天,越来越清楚的是,DevOps 是不够的。安全性也是一个关键的方面,需要在整个过程中解决,而不是在最后解决。因此,DevSecOps 应运而生,它为开发周期增加了安全性。为了确保数据库操作和机器学习操作顺利运行,还创建了 DataOps 和 MLOps。
本文研究了这四种主要方法——devo PS、DevSecOps、DataOps 和 MLOps——为何时以及如何使用每个工作流提供了指导原则。
什么是 DevSecOps?
DevSecOps 是 DevOps 与安全团队的结合。它旨在确保在开发和操作任务中分担安全责任,并实现“安全即代码”的管理。实现 DevSecOps 通常由已经习惯使用 DevOps 策略的团队承担。
DevSecOps 团队使安全成员能够在开发和部署操作与安全问题之间架起一座桥梁。通过打破团队之间的孤岛,他们可以帮助将安全实践集成到现有的工作流中,减少摩擦,并从一开始就确保产品更加安全。
DevSecOps 与 DevOps 的不同之处在于工具和思维方式。这些实现将测试转移到开发过程的左侧,并专注于教授安全性最佳实践。这个想法是从一开始就防止漏洞进入项目。
例如,DevSecOps 团队经常在集成开发环境(ide)中集成静态应用程序安全测试(SAST)工具。这意味着安全审计和测试甚至在提交代码进行传统测试之前就开始了。同样,团队的关注点可能包括云安全状态管理(CSPM)或环境部署步骤中的合规性审计工具。这有助于在环境上线之前发现错误配置。
数据运营:利用 DevSecOps 原则进行安全数据分析
随着 DevSecOps 从 DevOps 发展而来,其他业务单位也开始结合 DevOps 原则,从战略中分支出来并不断发展。一个例子是 DataOps,它有一个数据分析的基础。
DataOps 采用 DevOps 的实践和价值观,并将其扩展到数据分析工作流和目标。它将重点放在协作和分担责任上,并将其转移到收集、存储、分析、保护和交付数据的工程师和管理员身上。
DataOps 旨在简化现有的大数据流程,同时提高工作负载价值和安全性。它通过将安全性集成到数据的编码、保留和交付中来实现这一点,同时牢记分析和存储工作流之间的依赖关系。这有助于确保更可靠的访问,并且可以提高价值实现时间。
数据运营基础设施
实施数据运营需要的不仅仅是心态或工作流程的改变;它还需要基础设施的改造。例如,关注敏捷反馈循环和自动化的新架构模式。
DataOps 还经常要求团队实施专为分析和存储设计的下一代技术。例如,团队通常需要采用冗余的、基于集群的存储来确保数据处理管道的高可用性和可伸缩性。可能还需要配置和部署环境,以确保隔离和遵守数据隐私法规。对于生产和测试或开发环境都是如此。
DataOps 团队可能需要解决的另一个变化是所支持的工作负载的多样性。为了让管道提供敏捷性,解决方案需要集成到单个基础设施中,而不是由任务或团队来分发。这意味着整合大数据分析工具,如 Spark 和 Hadoop ,日志聚合器,如 Sumo Logic 和 Splunk,以及监督工具,如 Prometheus 和吉拉。
DevOps 与 MLOps
MLOps 是 DevOps 的另一个分支。在其中,DevOps 原理和工作流被应用于机器学习操作,例如模型训练和部署。它实现了流水线和自动化,使训练操作和完成的模型集成到软件产品中的流程更加顺畅。
在许多方面,MLOps 也与 DataOps 重叠,因为它也需要数据集的处理、维护和安全性。然而,机器学习工作负载的某些方面需要不同的关注点或实现。这些差异包括:
- 团队技能 —在 MLOps 中,团队需要吸收 ML 研究人员和数据科学家,他们通常不是经验丰富的软件工程师。这些成员专注于实验、模型开发和数据分析,可能不具备执行应用程序开发、操作或安全任务所需的技能。
- 开发——与传统的更加线性的开发不同,ML 通常是高度实验性的。团队需要能够操作参数和特性,并经常重新训练模型。这需要更复杂的反馈回路。此外,团队需要能够在不妨碍工作流可重用性的情况下跟踪操作的可重复性。
- 测试 —在 MLOps 中测试需要在 DevOps 或 DevSecOps 中通常完成的方法之上的额外方法。例如,MLOps 需要测试数据验证、模型验证和模型质量测试。
- 部署 —根据您正在部署的 ML 模型的类型,您可能需要为正在进行的数据处理和培训建立管道。这需要多步流水线,可以处理再训练步骤以及验证和重新部署过程。没有 MLOps,这是手动完成的,但有了它,这些步骤应该是自动化的。
- 生产 —生产中的模型可能会面临标准应用程序部署不会面临的挑战,例如与不断发展的数据配置文件相关的问题。这可能导致模型衰退,可靠性降低。MLOps 实现需要包含持续的监控和审计,以确认模型是可用的和准确的。如果精度下降,模型需要被召回并修正。
MLOps 与 DevOps 不同的另一个重要领域是如何构建持续集成/持续开发(CI/CD)管道。在 MLOps 中,CI 组件需要扩展到测试和验证数据模式、数据和模型。CD 组件需要支持培训管道以及最终模型预测服务或应用程序的部署。此外,还有另一个组件,持续测试(CT ),它需要被考虑以实现自动的模型再训练和改进。
结论
DevSecOps 将开发、安全和操作角色统一到一个统一的团队中。工作流程通常是自动化的,反馈循环应该是连续的。这确保团队成员将时间花在关键任务上,并不断改进和保护代码。
DataOps 工作流将协作和自动化等 DevOps 原则用于数据管理工作流。此工作流有助于消除源自数据级别的孤岛。MLOps 工作流也利用了 DevOps 原则,但这里的应用是在机器学习操作中。
选择工作流程是一个关键的组成部分,它需要所有相关方的合作。在实现工作流之前,您应该确保所有团队成员都具备必要的技能,工作流适合您的项目,并且您拥有用于测试、部署和生产的所有必要工具。
使用 Streamlit 的糖尿病预测应用
使用 PIMA 印度糖尿病数据集创建机器学习应用程序
马库斯·温克勒在 Unsplash 上的照片
Streamlit 是一个开源的 Python 库,其速度快得惊人,可以轻松地为机器学习和数据科学构建漂亮的定制网络应用。这是一个非常棒的工具,只需要一些 python 知识就可以创建高度交互式的仪表板。
使用 streamlit 创建应用程序会对最终用户产生影响,因为它有一个良好的用户界面,并支持许多用户友好的小部件。在 streamlit 中创建应用程序也很容易。我们将使用 streamlit 创建一个应用程序,它将预测用户是否患有糖尿病。我们将使用的数据集是 PIMA Indian 糖尿病数据集,它包含 8 个预测变量和 1 个目标变量。
让我们看看数据集中有哪些不同的属性。预测变量被命名为结果,其被编码为 0 和 1,其中 0 代表非糖尿病,1 代表糖尿病。其他属性信息如下所示。
探索数据集
让我们从探索我们将使用的数据集开始。为了探索数据集,我们将使用 jupyter 笔记本通过 pandas 加载数据集并执行探索性数据分析。
import pandas as pd
df = pd.read_csv('Diabetes.csv')
df.head()
糖尿病数据集
可视化不同的属性:
- 热图
sns.heatmap(df.corr(),annot=True)
热图显示不同属性之间的关联。
2.配对图
sns.pairplot(df,hue='Outcome')
配对图用于显示糖尿病患者和非糖尿病患者之间的相似性和差异。
类似地,我们可以为 EDA 过程创建更多的图,并探索所有属性的不同特性。
我们将在 jupyter 笔记本中创建逻辑回归模型并保存它,以便在我们的 streamlit 应用程序中调用它进行预测。
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
import pickle# Split dataset into training set and test set
X_train, X_test, y_train, y_test = train_test_split(df[['Pregnancies', 'Glucose','BloodPressure','SkinThickness','Insulin','BMI','DiabetesPedigreeFunction','Age']], df['Outcome'], test_size=0.3, random_state=109)#Creating the model
logisticRegr = LogisticRegression(C=1)
logisticRegr.fit(X_train, y_train)
y_pred = logisticRegr.predict(X_test)#Saving the Model
pickle_out = open("logisticRegr.pkl", "wb")
pickle.dump(logisticRegr, pickle_out)
pickle_out.close()
现在让我们开始创建应用程序。为了创建应用程序,我们需要用 python 创建一个脚本,为此,我们需要在系统中安装一个代码编辑器。你可以使用任何代码编辑器,但是我个人使用 Atom 是因为它的特性。我们将使用逻辑回归来创建预测模型。让我们从导入所需的库开始。
加载所需的库
import streamlit as st
import pandas as pd
import numpy as np
import plotly.express as px
from plotly.subplots import make_subplots
import plotly.graph_objects as go
import matplotlib.pyplot as plt
import seaborn as sns
import pickle
调用我们在上面保存的模型:
pickle_in = open('logisticRegr.pkl', 'rb')
classifier = pickle.load(pickle_in)
为应用程序创建用户界面:
Streamlit 为其界面预定义了 UI。我们将使用不同的小部件来显示信息,并接受用户输入进行预测。
st.sidebar.header('Diabetes Prediction')
select = st.sidebar.selectbox('Select Form', ['Form 1'], key='1')
if not st.sidebar.checkbox("Hide", True, key='1'):
st.title('Diabetes Prediction(Only for females above 21years of Age)')
name = st.text_input("Name:")
pregnancy = st.number_input("No. of times pregnant:")
glucose = st.number_input("Plasma Glucose Concentration :")
bp = st.number_input("Diastolic blood pressure (mm Hg):")
skin = st.number_input("Triceps skin fold thickness (mm):")
insulin = st.number_input("2-Hour serum insulin (mu U/ml):")
bmi = st.number_input("Body mass index (weight in kg/(height in m)^2):")
dpf = st.number_input("Diabetes Pedigree Function:")
age = st.number_input("Age:")submit = st.button('Predict')if submit:
prediction = classifier.predict([[pregnancy, glucose, bp, skin, insulin, bmi, dpf, age]])
if prediction == 0:
st.write('Congratulation',name,'You are not diabetic')
else:
st.write(name," we are really sorry to say but it seems like you are Diabetic.")
这将创建应用程序,现在我们需要使用。py”扩展名。例如,如果您需要将应用程序保存为“predict ”,那么您需要将其保存为“predict.py”。为了启动该应用程序,我们将打开 Anaconda 命令提示符并键入下面给出的命令。
streamlit run predict.py
这将启动我们的应用程序,现在我们可以使用不同的用户输入来创建一个人是否患有糖尿病。
应用程序的主页
通过输入不同用户的信息,我们可以产生不同的结果,并看到我们的模型有多好和准确。
这只是 streamlit 所能做的一个例子。您可以探索更多信息,了解 streamlit 为创建网络应用和仪表盘提供的无限功能。创建您的应用程序,并在本文的回复中分享您的经验。
自动化探索性数据分析和数据建模
towardsdatascience.com](/exploratory-data-analysis-using-dora-ac596e5a32a6) [## 使用 TA-Lib 对股票进行技术分析
技术分析 python 库
towardsdatascience.com](/technical-analysis-of-stocks-using-ta-lib-305614165051)
在你走之前
感谢 的阅读!如果你想与我取得联系,请随时通过 hmix13@gmail.com 联系我或我的 LinkedIn 个人资料 。也可以在我的Github中查看我在这里使用过的代码和数据集。 还有,随意探索 我的简介 阅读我写过的与数据科学相关的不同文章。
诊断广义线性模型
广义线性模型
检查 r 中 GLM 的模型假设和异常值。
G 一般化线性模型( GLM )之所以受欢迎,是因为它可以处理多种不同响应变量类型的数据(如 二项式 、 、泊松 ,或者 多项式 )。
与非线性模型相比,例如神经网络或基于树的模型,线性模型在预测方面可能没有那么强大。但是解释的简单性使得它仍然具有吸引力,尤其是当我们需要理解每个预测因素是如何影响结果的时候。
GLM 的缺点和优点一样明显。线性关系可能并不总是成立的,它对异常值非常敏感。因此,不进行诊断就安装 GLM 是不明智的。
在这篇文章中,我将简要地谈谈如何诊断一个广义线性模型。实现将用 R 代码展示。
主要有两种类型的诊断方法。一个是异常值检测,另一个是模型假设检验。
残差
在深入诊断之前,我们需要熟悉几种类型的残差,因为我们将在整个帖子中使用它们。在高斯线性模型中,残差的概念非常直接,它基本上描述了预测值(通过拟合的模型)和数据之间的差异。
响应残差
在 GLM 中,它被称为“响应”残差,这只是一种有别于其他类型残差的符号。
响应的方差在 GLM 不再是常数,这导致我们对残差进行一些修改。
如果我们根据估计的标准误差重新调整响应残差,它就变成了皮尔逊残差。
皮尔逊残差
你可能会看到一些人使用估计值的平方根而不是标准误差作为上式的分母。这是因为他们使用的是泊松模型,其中估计方差等于估计均值。所以,不要混淆,它们是一回事。
皮尔逊残差的特征之一是皮尔逊残差平方和近似遵循卡方分布。
皮尔逊残差的平方和近似遵循卡方分布
这一特性启发研究人员使用另一种类型的残差,称为偏差残差,其平方和也遵循卡方分布。
*注意:*模型的偏差仅描述拟合优度,它专门计算完全(饱和)模型的对数似然性与考虑中的模型(您为拟合而构建的模型)的对数似然性之间的差异。高偏差表明模型拟合不好。
每个数据点都有自己对模型偏差的贡献。如果我们根据拟合值和数据之间的差异给这些个体偏差中的每一个分配一个方向,我们就得到偏差残差。
偏差残差
通常,在 GLMs 的诊断中,偏差残差优于其他类型的残差。
在 R 中,使用“残差”函数实现这些不同类型的残差是很简单的。
# R code
# Suppose your fitted model is modresiduals(mod,type = "response") ## response residuals
residuals(mod,type = "pearson") ## pearson residuals
residuals(mod,type = "deviance") ## deviance residuals
函数‘residuals’的默认性能是偏差残差,所以如果您忘记将‘type’参数传递给函数,也不用担心。
模型诊断
残差与拟合值的关系图是诊断中最重要的图形。该图旨在检查残差和拟合值之间是否存在非线性。
GLMs 和高斯线性模型之间的一个区别在于,GLM 中的拟合值应该是在通过连接函数转换之前的值,然而在高斯模型中,拟合值是预测的响应。
让我们以下面的泊松模型为例。记住泊松回归模型是这样的:
泊松模型
让我们首先绘制残差与估计值(mu_i)的关系图。
# R code
mod_pois = glm(Species ~ ., family=poisson,gala) # gala is the example dataset in package "faraway" plot(residuals(mod_pois) ~ predict(mod_pois,type="response"),xlab=expression(hat(mu)),ylab="Deviance residuals",pch=20,col="red")
相对于估计值的偏差残差
我们可以看到大部分点都挤在了图的左侧,很难解读。因此,最好不要检查链接函数的转换。
# R code
plot(residuals(mod_pois) ~ predict(mod_pois,type="link"),xlab=expression(hat(eta)),ylab="Deviance residuals",pch=20,col="blue")
相对于\eta 的偏差残差
如果满足模型的假设,我们确实期望图中有恒定的变化,因为偏差残差不应该有已经重新调整的非恒定变化。如果图中有明显的非线性,就会发出警告信号。然而,该图看起来没有任何非线性。
假设剧情出现了明显的非线性,怎么办?
通常,从改变链接函数或转换响应变量开始是不明智的。链接函数的选择通常受限于响应数据的自然特征,例如,泊松模型要求响应变量为正。同样,响应变量的转换将违反响应的假定分布。
有两件事值得一试:
- 更改模型类型。例如,利用负二项式模型,而不是泊松模型。
- 对预测值做一些转换,比如 log()、sqrt()等等…
离群点检测
GLMs 诊断的另一个重要部分是检测异常值。它可以是定量的或图形的。**
在检测异常值的定量方法中,基本思想是找到那些对模型具有异常大的影响的点或者拟合模型最敏感的点。
在拟合模型上可以使用两个指标:杠杆和库克距离。这是 r 中的代码。**
**# R code
i_n = influence(mod_pois)$hat # calculate the influence of data points
which.max(i_n)**
这产生了,
**## SantaCruz
## 25**
这意味着数据点“SantaCruz”对拟合泊松模型的影响最大。
如果我们应用库克的距离度量,它将产生相同的结果。
**# R code
c_d = cooks.distance(mod_pois)
which.max(c_d)**
结果是,
**## SantaCruz
## 25**
我们还可以使用 QQ 图来图形化地检测异常值。与高斯线性模型诊断的一个区别是,在 GLM 诊断中,我们不在 QQ 图中寻找直线,因为残差预计不会呈正态分布。GLM 的 QQ 图的唯一目的是找出数据中的异常值。
**# R code
halfnorm((i_n))**
杠杆作用的 QQ 图
**# R code
halfnorm((c_d))**
库克距离的 QQ 图
这些方法建议进一步调查数据点“25 SantaCruz
”。
外卖
- 在 GLMs 中运行诊断非常重要。
- 两个方面需要研究:模型假设和异常值。
- 必要时,对预测变量进行转换,但不对响应变量进行转换。
- 移除定量和图形化检测到的异常值。
希望这篇帖子对你有帮助。
参考资料:
远方,Julian J. 用 R 扩展线性模型:广义线性、混合效应和非参数回归模型。CRC 出版社,2016。
GLMs 允许在响应变量具有非正态误差分布的情况下使用线性模型…
www.datascienceblog.net](https://www.datascienceblog.net/post/machine-learning/interpreting_generalized_linear_models/) [## RPubs
编辑描述
rpubs.com](https://rpubs.com/benhorvath/glm_diagnostics) [## 5.7 模型诊断|预测建模说明
正如第 5.2 节所暗示的,广义线性模型是建立在一些概率假设上的,这些假设是…
bookdown.org](https://bookdown.org/egarpor/PM-UC3M/glm-diagnostics.html)
扎克·杜兰特在 Unsplash 上的照片**
用机器学习诊断心脏病
我们能用核磁共振诊断心脏病吗?你打赌!
目录表
- 摘要
- 背景
- 材料&方法
- 结果&结论
- 参考文献
1。摘要
根据疾病控制和预防中心(CDC)的数据,心脏病是美国男性、女性和大多数种族和民族人群的头号死因。在美国,每分钟都有一个以上的人死于这种疾病,每年有近 50 万人死于这种疾病,每年造成数十亿美元的损失。⁴因此,这个故事的目的是研究不同的潜在监督机器学习(ML)算法,以创建诊断心脏病模型。
2。背景
为了进行这种分析,使用公开可用的克利夫兰心脏病数据集用 Python 构建了一个 Jupyter notebook ,该数据集有 300 多个独特的实例,共有 76 个属性。⁵ ⁶ ⁷ ⁸ ⁹从这 76 个总属性中,只有 14 个常用于研究至今。此外,该分析中使用的超参数来自 Olson 博士的建议,“将机器学习应用于生物信息学问题的数据驱动建议。”⁰在这个分析中使用的库和编码包是:SciPy,Python,NumPy,IPython ⁴,Matplotlib ⁵,熊猫⁶,Scikit-Learn ⁷,和 Scikit-Image。⁸
克里夫兰 dataset⁵ ⁶ ⁷ ⁸ ⁹的背景
克利夫兰数据库的实验集中在简单地试图区分心脏病的存在和不存在。使用的 14 个属性:
表 1。克利夫兰数据集 14 特征和描述。
olson 博士超参数建议的背景
Randal S. Olson 等人在 2017 年发表的一篇文章为解决机器学习的生物信息学问题提供了有见地的最佳实践建议,“将机器学习应用于生物信息学问题的数据驱动建议”。
简要地看一下摘要,他们分析了“13 种最先进的、常用的机器学习算法,这些算法针对一组 165 个公开可用的分类问题,以便为当前的研究人员提供数据驱动的算法建议。”有趣的是,这个数据集并没有包含在他们论文的表 2 的分析中。这使得它成为实验的一个很好的候选,因为它是使用 ML 的生物信息学中的一个二元分类问题。
通过他们的研究,他们能够提供“五种具有超参数的算法的建议,这些算法可以最大限度地提高测试问题的分类器性能,以及将机器学习应用于监督分类问题的一般指南。”这些建议概述如下:
表二。奥尔森博士的超参数建议摘要。⁰
3.材料和方法
导入库
导入和查看数据
表 3。导入后克利夫兰数据集的前 10 行原始数据。
将二进制分类的诊断列值更改为 0 或 1 之间。
*# Change num values > 0 to 1 for a Diagnosis*
data['diagnosis'] = np.where((data['diagnosis']>0),1,0)
检查有关数据的信息以了解其类型。
data.info() *# view*
有两个包含object
值的特性:
- #通过荧光镜检查着色的主要血管
- 地中海贫血
在这两个特征中,有一个唯一的值?
,表示缺少一个值。这是从以下内容中发现的:
data['# Major Vessels colored by Flouroscopy'].unique()data['Thalassemia (norm.,fix. defect, revers. defect)'].unique()
两者分别只有 4 次和 2 次计数。假设实例数量不多,因此这些实例按如下方式删除:
sum(data['# Major Vessels colored by Flouroscopy'].values=='?')droplist=data.loc[data['# Major Vessels colored by Flouroscopy']=='?'].index.tolist()data.drop(droplist,axis=0,inplace=**True**)data['# Major Vessels colored by Flouroscopy']=data['# Major Vessels colored by Flouroscopy'].astype(str).astype(float).astype(int)sum(data['Thalassemia (norm.,fix. defect, revers. defect)'].values=='?')droplist=data.loc[data['Thalassemia (norm.,fix. defect, revers. defect)']=='?'].index.tolist()data.drop(droplist,axis=0,inplace=**True**)data['Thalassemia (norm.,fix. defect, revers. defect)']=data['Thalassemia (norm.,fix. defect, revers. defect)'].astype(str).astype(float).astype(int)
此外,还使用以下方法检查缺失值:
data.isnull().sum()
此时,没有丢失的值。
特征工程
如上所述的一些分类值只有几个唯一值。对这些值使用分类编码是一个很好的实践,这样 ML 算法就不会过度适应唯一的值。将这些转换成二进制允许 ML 算法以较少偏差的方式处理数据,而不会丢失任何信息。
表 4。特征工程后克利夫兰数据集的前 5 行原始数据。
接下来,清理和特征工程后的克利夫兰数据集直方图如图 0 所示。这个数字和心脏病的结果有一些明显的关系。
例如,看左上方的年龄支线图,在 60 岁左右,心脏病患者的数量相对于该年龄没有疾病的人增加了近一倍。另外,看性爱的支线剧情,很明显男性(值= 1)比女性(值=0)患心脏病的次数多。还有更多的关系,但关键是这些关系是存在的,而且是牢固的。因此,最大似然算法应该利用这些来进行诊断。
图 0。清理和特征工程后克利夫兰数据集的直方图。
缩放数据
缩放数据非常重要,这样 ML 算法就不会过度拟合错误的要素。使用MinMaxScaler()
,根据 0 和 1 之间的最小值和最大值,对每个特征的值进行缩放。这防止了信息丢失,但是允许 ML 算法用数据正确地训练。
X = data.drop(['diagnosis'], axis= 1)
y= pd.DataFrame(data['diagnosis'])
*#Scale Data*
scaler = MinMaxScaler()
X=MinMaxScaler().fit_transform(X.values)
X = pd.DataFrame(X)
X.columns=(data.drop(['diagnosis'], axis= 1)).columns
表 5。克利夫兰的前 5 行缩放数据。
具有皮尔逊相关系数的特征热图
皮尔逊相关系数值接近 1 表示强相关。因此,当查看热图(图 1)时,与第一列“诊断”最相关的值对于训练 ML 算法将非常重要。其他特征之间接近 1 的值是不理想的,因为该信息已经存在用于训练 ML 算法。这在统计学上叫做多重共线性。看来Major Vessels F. Colored: 0
和Thalassemia:normal
的特征与diagnosis
具有最强的相关性,皮尔逊相关系数为 0.24。这不是很大的相关性,因此 ML 模型不太可能具有心脏病诊断所需的准确性,但这并不妨碍诊断的有效模型,因为它是基于受试者工作特征(ROC)曲线的曲线下面积(AUC)。
图一。清理、要素工程和缩放后的克利夫兰数据集的热图。
为培训拆分数据
在删除了 6 个缺失值的实例后,数据被分成 80%的培训(237 人)和 20%的测试(60 人)。这是拆分数据以训练 ML 算法的一般经验法则。
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size= 0.2, random_state= 42) *#withold 20% of data for testing*
机器学习
为了找到一个好的模型,在训练数据集上测试了几种算法。使用算法的不同超参数的敏感性研究用 GridSearchCV 迭代,以便优化每个模型。通过查看训练数据和验证数据结果,最佳模型是具有最高准确性而不会过度拟合的模型。对于这些模型来说,计算机时间似乎不是问题,所以它对模型之间的决策没有什么影响。代码和细节请看 Jupyter 笔记本。
使用 GridSearchCV 通过 k-fold 交叉验证评估模型,k-fold = 10,GridSearchCV 迭代不同算法的超参数。测试了几个新的超参数,以及奥尔森博士的分析中没有包括的一个附加算法。新算法是一个神经网络,多层感知器(MLPClassifier)。
表 6。GridSearchCV 使用的超参数和算法摘要。
评估模型
查看混淆矩阵图
图二。答 3。答 4。答,5。答,6。答,7。答,8。答,9。答,10。答,11。答,12。答和 13。答
当涉及到诊断心脏病时,我们希望确保我们没有太多的假阳性(你没有心脏病,但被告知你做了并得到了治疗)或假阴性(你有心脏病,但被告知你没有得到治疗)。因此,选择总体精度最高的模型(精度是混淆矩阵上对角线的总和除以总和)。您可以看到,所有模型的假阳性和假阴性都不到几个,这意味着它们都相当准确,因为测试集有 60 个观察值。
查看变量重要性图
图二。b,3。b,4。b,5。b,8。b 和 9。B
大多数ensemble
模型都有一个名为predict_proba
的参数,该参数允许通过gini
指数或entropy
的多数投票,根据模型的概率输出最重要的特征。这 5 个最重要的变量出现在所有这些变量重要性图中:
Exercise Max Heart Rate Achieved
Major Vessels F. Colored: 0
ST depression induced by Exercise relative to Rest
Thalassemia: reversable defect
Thalassemia: normal
毫不奇怪,在图 1 的热图中,这些相同的变量也显示出与诊断的高度相关性。因此,这些变量变得如此重要是有道理的。
查看接收机工作特性(ROC)曲线
图 14
所有模型的受试者操作特征(ROC)曲线都有Excellent
曲线下面积(AUC ),因为它们的值都大于 90%(所有值都接近 95%),这意味着它们都可以作为优秀的诊断。这也表现在它们的高特异性和灵敏度值上。
4.结果和结论
所有模型在微调其超参数后都表现良好,但最佳模型是具有最高总体准确性的模型。
在该分析中,Support Vector Machine (SVM)
和Multilayer Perceptron Classifier (MLP)
并列获得最高的整体精度,91.7%
(见下表 7)。然而,SVM
的 AUC96.8%
比MLP
的 AUC93.6%
高。这意味着SVM
在这种情况下是更好的诊断模型,但并不意味着SVM
在所有情况下都是最好的模型。所有这一切意味着,对于给定的测试集,这个模型表现最好。在这个测试中 20%的数据(60 个随机个体)中,只有少数几个在所有模型中被误诊。没有一个模型是完美的,但是这些结果是有希望的。如果平均来说,测试集中少于几个人被如此准确和精确地误诊,这是一个好的开始。显然,未来的目标是找到更好的度量标准来训练和测试 ML 算法或更多的数据。
这里的关键是,由于受试者工作特征(ROC)曲线的曲线下面积(AUC)如此之高,心脏病的诊断是可能的。所有值都大于 90%,诊断结果非常好。
表 7。统计结果汇总。
5.参考
- Heron,M. 死亡:2017 年的主要原因。国家人口动态统计报告;68(6).2019 年 11 月 19 日接入。
- 本杰明·EJ、蒙特纳·P、阿朗索·A、比滕考特·MS、卡拉威·CW、卡森·AP 等,《心脏病和中风统计——2019 年更新:美国心脏协会的一份报告》。循环。2019;139(10):e56–528。
- Fryar CD,Chen T-C,Li X. 心血管疾病未控制危险因素的患病率:美国,1999–2010。NCHS 数据简报,第 103 号。马里兰州 Hyattsville:国家卫生统计中心;2012.2019 年 5 月 9 日接入。
- Dua d .和 Graff c .(2019 年)。http://archive.ics.uci.edu/ml 的 UCI 机器学习库。加州欧文:加州大学信息与计算机科学学院。
- 匈牙利心脏病研究所。布达佩斯:安朵斯·雅诺西,医学博士
- 瑞士苏黎世大学医院:医学博士威廉·斯坦布伦
- 瑞士巴赛尔大学医院:医学博士马蒂亚斯·普菲斯特勒
- V.A .医疗中心,长滩和克里夫兰诊所基金会: Robert Detrano,医学博士,哲学博士
- Olson,Randal S .等人“将机器学习应用于生物信息学问题的数据驱动建议”。“太平洋生物计算研讨会。太平洋生物计算研讨会 23(2017):192–203。
- SciPy。(2019)SciPy 1.0-Python 中科学计算的基本算法。预印本 arXiv:1907.10121
- 巨蟒。 a)特拉维斯·芬特。科学计算的 Python,科学计算&工程,9,10–20(2007)b)k . Jarrod mill man 和 Michael Aivazis。面向科学家和工程师的 Python,科学计算&工程,13,9–12(2011)
- NumPy。 a)特拉维斯·芬特。美国 NumPy 指南:特雷戈尔出版公司(2006 年)。(b)斯蒂芬·范德沃特、克里斯·科尔伯特和盖尔·瓦洛夸。NumPy 数组:高效数值计算的结构,科学计算&工程,13,22–30(2011)
- IPython。 a)费尔南多·佩雷斯和布莱恩·e·格兰杰。IPython:交互式科学计算系统,科学计算工程,9,21–29(2007)
- Matplotlib。 J. D. Hunter,“Matplotlib:一个 2D 图形环境”,科学计算&工程,第 9 卷,第 3 期,第 90–95 页,2007 年。
- 熊猫。 韦斯·麦金尼。Python 中统计计算的数据结构,第 9 届科学中的 Python 会议录,51–56(2010)
- Scikit-Learn。 法比安·佩德雷戈萨、加尔·瓦洛夸、亚历山大·格拉姆福特、文森特·米歇尔、贝特朗·蒂里翁、奥利维耶·格里塞尔、马蒂厄·布隆德尔、彼得·普雷顿霍弗、罗恩·韦斯、文森特·杜伯格、杰克·范德普拉斯、亚历山大·帕索斯、大卫·库尔纳波、马蒂厄·布鲁彻、马蒂厄·佩罗特、爱德华·杜谢斯奈。sci kit-learn:Python 中的机器学习,机器学习研究杂志,12,2825–2830(2011)
- Scikit-Image。 斯蒂芬·范德沃特、约翰内斯·l·舍恩伯格、胡安·努涅斯-伊格莱西亚斯、弗朗索瓦·布洛涅、约书亚·d·华纳、尼尔·雅戈、伊曼纽尔·古雅特、托尼·于和 scikit-image 供稿人。sci kit-Image:Python 中的图像处理,PeerJ 2:e453 (2014)
与数据库的对话
活动讲座
凯瑟琳·休姆| TMLS2019
关于演讲者
Kathryn Hume 领导加拿大皇家银行机器学习研究实验室 Borealis AI 的产品和业务开发。在加入 Borealis AI 之前,Kathryn 曾在 integrate.ai 和 Fast Forward Labs(被 Cloudera 收购)担任领导职务。她已经帮助 50 多家财富 500 强公司开发了机器学习应用程序,是人工智能道德和负责任部署方面的公认专家。Kathryn 经常在人工智能上发表演讲和写作,其作品在 TED、《环球邮报》和《哈佛商业评论》上发表。她拥有斯坦福大学比较文学博士学位,会说七种语言,并在哈佛商学院、麻省理工学院、斯坦福大学和卡尔加里大学法学院发表过关于人工智能和职业道德的演讲。
与数据库对话 |凯瑟琳·休姆
使用 PyTorch 根据切割、颜色、净度和价格预测钻石价格
PyTorch 中构建简单线性回归模型的指南
资料来源:Opensourceforu.com
你有没有问过自己,钻石是怎么定价的?这篇文章讨论了基于切割、颜色、净度和其他属性的钻石价格预测,还介绍了使用 PyTorch 建立一个简单的线性回归模型。
我的 PyTorch 之旅从“使用 PyTorch 进行深度学习:零到 Gans”课程开始,现在我们已经进入了该课程的第二周,到目前为止,我们已经在 PyTorch 中讲述了线性&逻辑回归的基础知识和实现。PyTorch 的基本功能请参考这篇文章。
线性回归简介:
线性回归是机器学习中最简单的算法之一。根据统计,术语回归被定义为输出变量和输入变量之间关系的度量,因此,线性回归假设自变量(输入)和因变量(输出)之间存在线性关系。
更一般地,线性模型通过简单地计算输入要素的加权和以及偏差(也称为截距)项来进行预测。(来源)如下式所示。
资料来源:中央统计局。Brown.edu
我考虑了经典的钻石数据集,它包含了近 54,000 颗钻石的价格和其他属性,该数据集托管在 Kaggle 上。数据集包含 53940 行和 10 个变量。在开始构建模型之前,让我们来看看变量&的定义。
- 价格以美元为单位
- 克拉钻石的重量
- 切割切割质量(一般、良好、非常好、优质、理想)
- 颜色钻石颜色,从 J(最差)到 D(最好)
- 净度衡量钻石净度的指标(I1(最差)、SI2、SI1、VS2、VS1、VVS2、VVS1、IF(最佳))
- x 长度单位为毫米
- y 宽度单位为毫米
- z 深度以米为单位
- 深度:钻石的高度
- 表:钻石台面的宽度,以其平均直径的百分比表示
让我们根据以下步骤开始构建线性回归模型
- 导入所需的包
- 加载数据集
- 执行探索性数据分析(EDA)
- 为训练准备数据集
- 创建线性回归模型
- 训练模型以适应数据
- 使用训练好的模型进行预测
步骤 1:导入所需的包
步骤 2:加载数据集
为了加载数据集,我们将使用pd.read_csv()
函数,它会将数据集转换为数据帧,并使用pd.head()
函数查看数据集的前 5 行
删除不必要的列“未命名:0”
数据集有 53940 行和 10 个变量
步骤 3:探索性数据分析(EDA)
看看数据集的简明摘要
数据集中没有空数据,也可以使用data.isnull().any
进行检查。给定的数据集有 6 个数值列和 3 个非数值(分类)列
使用pd.describe()
获取数据集的描述性统计数据
观察到 x(长度)、y(宽度)和 z(深度)的最小值为零,并且使钻石的长度\宽度\深度为零没有任何意义。
让我们放弃这几行
观察到 x、y 和 z 的最小值是非零值
用hist()
法绘制数值属性的分布
让我们看看数据集的配对图。配对图允许我们看到变量的分布以及两个变量之间的关系
观察到 x、y 和 z 变量相对于因变量(目标)价格具有良好的相关性,让我们通过使用.corr()
&来量化这种相关性,并使用sns.heatmap()
方法将其可视化
我们可以得出结论,克拉,x,y & z 特征与 w.r.t 价格变量有很强的相关性,而深度与 w.r.t 价格变量的相关性很弱。因此,我们可以从模型的最终输入特征列表中删除深度特征。
让我们通过使用箱线图来了解目标(价格)变量的分类特征
我使用箱线图来比较给定分类变量类别之间的数据分布、集中趋势(中值)和可变性,这些图也有助于识别异常值。
到目前为止,我已经做了非常初步的数据探索,以了解这些特征及其与目标变量的关系。本文的主要目标是在 PyTorch 中实现线性回归,所以让我们停止数据探索,直接进入本文的核心部分。
在继续构建模型之前,最好将分类数据转换为数值数据,有两种方法可以转换为数值形式 1。标签编码器或整数编码 2。一键编码
一般来说,一键编码做得很好,更多细节请参考此链接
步骤 4:为训练准备数据集
我们需要将熊猫数据框架中的数据转换成 PyTorch 张量用于训练。为此,第一步是将它转换成 NumPy 数组
现在,我们需要为训练和验证创建 PyTorch 数据集和数据加载器。第一步是通过使用torch.tensor
函数将输入的&目标数组转换成张量来创建TensorDataset
现在,我们将把数据集分为训练和验证数据集。训练集将用于训练模型,而验证集用于评估模型&它还用于调整超参数(学习率、时期、批量等…)以便更好地概括训练模型。
我们将使用random_split
函数将数据集分割成所需百分比的训练&验证
让我们来看一组数据,以验证到目前为止一切正常。
xb 和 yb 的形状表明到目前为止一切都运行良好
步骤 5:创建线性回归模型
我们将通过扩展 PyTorch 的nn.Module
类来创建线性回归模型
在__init__
构造函数方法中,我们使用nn.Linear
函数实例化权重和偏差。在 forward 方法中,当我们将一批输入传递给模型时会调用这个方法,它会将输入传递给self.linear
。定义的神经网络将如下所示
我们已经定义了模型并实例化了权重和偏差。现在,让我们定义成本函数。
线性回归的常用成本函数(J)是均方误差(MSE)。该函数计算预测值和目标值之间的差值,并对其求平方。成本函数衡量评估模型性能的好坏。
training_step
& validation_step
该方法分别计算每个训练&验证批次的预测& MSE 损失。该方法返回每个历元的平均验证损失。请参考此链接以了解历元&迭代之间的差异。
让我们通过调用DiamondPriceModel()
类创建一个模型对象
步骤 6:训练模型以适应数据
- 线性回归的主要目标是最小化成本函数。
- 梯度下降是一种帮助最小化成本函数的优化算法,其主要目的是找出全局最小值。这里,梯度表示函数的变化率,它指向函数最大增长的方向。为了最小化成本函数,我们需要执行成本函数(J)相对于其参数(权重(w) &偏差(b))的梯度。
注意:学习率α是一种控制权重更新量的方法。如果我们选择一个小的学习率,我们的模型训练可能需要很长时间。但是如果选择大的学习率,可能会超调,我们的训练永远不会收敛。具体的学习率取决于我们的数据和我们使用的模型类型,但通常最好在[1e 8,1e 1]范围内探索。
evaluate
函数,调用validation_step
& validation_epoch_end
方法,返回每个历元后的平均验证损失。
fit
函数通过在随机梯度下降(梯度下降的一种变体)优化的帮助下更新每一步的参数来执行模型训练。它记录每个时期的确认损失,并返回训练过程的历史。
最初的验证损失非常高,这是人们从随机初始化的模型中可能期望的。目标是使用足够的历元和合适的学习速率来最小化这种损失。这是一个反复试验的过程,以产生最适合我们的模型。
运行该模型 800 个时期,验证损失已经减少到 1276336.0000。现在,让我们用验证数据来测试模型。
步骤 7:根据验证数据测试模型
predict_single
函数返回给定输入张量的预测钻石价格值。
测试# 1
测试# 2
通过在当前模型中引入隐藏层可以提高模型性能,并且可以进一步将优化器改进为 Adam 优化器。
**代码库:**你可以在我的 GitHub repo 找到笔记本。
嗯!!我们刚刚使用 PyTorch 完成了第一个机器学习算法。感谢阅读!!并尝试使用自己的数据集。
快乐学习!编码快乐!
参考资料:
- https://www.youtube.com/watch?v=4ZZrP68yXCI&feature = youtu . be
- https://github . com/madewithml/basics/blob/master/notebooks/07 _ Linear _ Regression/07 _ PT _ Linear _ Regression . ipynb
- https://cs . brown . edu/courses/csci 1951-a/assignments/stats _ 讲义. html
- https://jovian.ml/forum/c/pytorch-zero-to-gans/18
- https://www.kaggle.com/shivam2503/diamonds
DiCE:酒店取消的多种反事实解释
DiCE 是更广泛的 InterpretML 库的一部分,非常擅长为特定数据集生成“多样化的反事实解释”。
“多样化的反事实解释”,我们指的是由一个模型做出的决定,这个模型的影响力足以改变结果变量。
我们举个例子。假设一位酒店经理正在分析客户预订,并希望分析哪类客户更有可能取消预订。
来源:Jupyter 笔记本
这是一个客户没有取消酒店预订的例子。
根据这个特例,客户属于细分市场线下 TA/TO ,客户类型为合同,分销渠道为 TA/TO ,所需停车位为 0 。
然而,一个希望最大限度减少取消预订的酒店经理最好能确定哪些类型的顾客可能会取消预订。这就是骰子的用武之地。
当查看上面的反事实解释时——客户取消了的预订,我们观察到:
- 提前期在四个示例中明显更长。
- 一个客户仍然属于线下 TA/TO 细分市场。
- 四个取消的客户之一仍然是合同客户。
- 分配渠道和所需停车位数量保持不变。
不取消和取消(至少在这种情况下)的最大区别似乎是提前期。从客户预订到实际入住酒店的时间越长,客户取消预订的可能性就越大。
方法学
为了生成上面的骰子解释:
- 使用 Tensorflow 的 Keras API 训练神经网络,以便建立分类算法。
- 然后从神经网络生成骰子模型。
- 使用原始数据集的参数创建“骰子数据集”,即包含连续变量的范围,同时包括分类变量的所有类别。
- 然后生成骰子解释实例,以解释结果变量的结果。
- 然后产生反事实的例子(按照上面的例子)来解释可能改变结果变量的条件。
- 接近度和差异度被修改,以检验接近原始输入的数据与显著变化的数据如何影响反事实解释。
关于第 3 点,DiCE 的一个便利特性是能够丢弃旧数据,同时保留模型训练参数。这样的做法符合公平数据原则,规定数据应该 F 可用, A 可访问, I 不可操作, R 可用。
但是,其他法律可以规定客户有权删除其个人数据,或者确实要求在一定期限后删除客户数据。
在这种情况下,DiCE 的优势在于它允许保留对相关数据的模型训练,而不必保留数据本身。
请注意,TensorFlow 1.15 在此实例中与 Python 3.6.9 一起用于运行 DiCE 和相关模型。
神经网络训练及骰子说明实例
以下是数据集的一个示例:
来源:Jupyter 笔记本
数据集中的连续要素定义如下:
# Dataset for training an ML model
d = dice_ml.Data(dataframe=dataset,
continuous_features=['LeadTime','RequiredCarParkingSpaces'],
outcome_name='IsCanceled')
使用二元交叉熵损失和 adam 优化器在 30 个时期内训练神经网络模型。
from tensorflow.keras.models import Sequential
from tensorflow.python.keras.layers import Dense
from tensorflow.python.keras.wrappers.scikit_learn import KerasRegressorsess = tf.InteractiveSession()
# Generating train and test data
train, _ = d.split_data(d.normalize_data(d.one_hot_encoded_data))
X_train = train.loc[:, train.columns != 'IsCanceled']
y_train = train.loc[:, train.columns == 'IsCanceled']# Fitting a dense neural network model
ann_model = Sequential()
ann_model.add(Dense(6, input_shape=(X_train.shape[1],), activation=tf.nn.relu))
ann_model.add(Dense(1, activation=tf.nn.sigmoid))
ann_model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history=ann_model.fit(X_train, y_train, validation_split=0.20, epochs=30, verbose=0, class_weight={0:1,1:2})
history
这是模型损耗的曲线图:
来源:Jupyter 笔记本
这是训练和验证准确性的图表:
来源:Jupyter 笔记本
出于解释目的,该模型现在存储为骰子模型:
>>> m = dice_ml.Model(model=ann_model)
>>> m
<dice_ml.model_interfaces.keras_tensorflow_model.KerasTensorFlowModel at 0x7f22fb2e0da0>
如前所述,这些特征现在以与 DiCE 兼容的特殊格式存储,以便生成 DiCE 解释实例:
new_d = dice_ml.Data(features={
'LeadTime':[0, 737],
'MarketSegment': ['Complementary', 'Corporate', 'Direct', 'Groups', 'Offline TA/TO', 'Online TA'],
'CustomerType': ['Contract', 'Group', 'Transient', 'Transient-Party'],
'DistributionChannel':['Corporate', 'Direct', 'TA/TO', 'Undefined'],
'RequiredCarParkingSpaces': [0, 8]},
outcome_name='IsCanceled')
形成骰子解释实例:
>>> exp = dice_ml.Dice(new_d,m)
>>> exp<dice_ml.dice_interfaces.dice_tensorflow1.DiceTensorFlow1 at 0x7f22fb2da630>
不同的反事实解释
既然解释实例已经形成,这可以用来生成反事实的解释。
让我们以具有以下属性的客户为例,这些属性包含在所谓的查询实例中:
query_instance = {'LeadTime': 68,
'MarketSegment': 'Online TA',
'CustomerType': 'Transient',
'DistributionChannel': 'TA/TO',
'RequiredCarParkingSpaces': 0}
反事实的例子产生如下:
# Generate counterfactual examples
dice_exp = exp.generate_counterfactuals(query_instance, total_CFs=4, desired_class="opposite")
# Visualize counterfactual explanation
dice_exp.visualize_as_dataframe()
来源:Jupyter 笔记本
这里,生成了一个原始结果为 1 的案例,即取消。还观察到顾客不取消他们的酒店预订的反例也产生了。
相反,这里有一个查询实例,客户取消了的酒店预订。
query_instance_2 = {'LeadTime': 93,
'MarketSegment': 'Offline TA/TO',
'CustomerType': 'Contract',
'DistributionChannel': 'TA/TO',
'RequiredCarParkingSpaces': 0}
再次,产生了反事实的例子:
# Generate counterfactual examples
dice_exp_2 = exp.generate_counterfactuals(query_instance_2, total_CFs=4, desired_class="opposite")
# Visualize counterfactual explanation
dice_exp_2.visualize_as_dataframe()
来源:Jupyter 笔记本
这些只是一些如何产生反事实解释的例子。
在这些例子中,产生了四个反事实的例子(total_CFs=4),其中“相反的”类是感兴趣的。
邻近性与多样性
关于反事实的解释,使用骰子可以产生更深刻的见解。
例如,可以考虑对原始输入的解释的接近度以及那些解释的多样性,即对所讨论的解释的建议改变的范围。 DiCE GitHub 自述文件提供了关于这些的更多解释。
以下是 proximity_weight 增加到 1.5 时的反事实解释。
查询实例 1
来源:Jupyter 笔记本
查询实例 2
来源:Jupyter 笔记本
现在,让我们将 diversity_weight 设置为 2.5 ,其中 proximity_weight 在 0 处。
查询实例 1
来源:Jupyter 笔记本
查询实例 2
来源:Jupyter 笔记本
结论
在本文中,您已经看到:
- 骰子如何被用来产生反事实的解释
- DiCE 如何在不保留数据本身的情况下保留模型训练
- 修改神经网络输出以便与骰子兼容
- 利用邻近性和多样性来产生有根据的反事实解释
非常感谢您的参与,这个例子的相关 GitHub 库可以在这里找到。
免责声明:本文是在“原样”的基础上编写的,没有任何担保。本文旨在提供数据科学概念的概述,不应以任何方式解释为专业建议。
参考
卡普兰疯狂:二分法连续变量
封面照片:托拜厄斯·范·施奈德
M 任何时候,我都会看到从数据集(如 TGCA 癌症基因组图谱)中选择基因的海报或演示,作者表明该基因的高(或低)表达与更好或更差的存活率相关。通常,会显示 p 值,作者没有提到他们是否调整了其他(已知的)预后因素。然后他们利用这种存活率的差异进行进一步的功能验证。
这没有太大的意义。
因为基因表达是一个连续变量(这里基因表达可以用任何其他连续变量代替),所以需要设定一个阈值来定义“高”与“低”表达,以便绘制 Kaplan Meier 生存曲线。连续变量的二分法是一个的坏习惯,并且伴随着成本。
如果没有预先指定临界值,可以改变阈值,直到找到显著的 p 值:
使用这种所谓的“最佳”分界点(通常是给出最小 P 值的分界点)存在产生虚假显著结果的高风险;两组之间的结果变量的差异将被高估,可能相当大;并且置信区间会太窄。永远不要使用这种策略— 奥特曼,BMJ
通过模拟数据,这个概念变得更加清晰。在该动画中,截止值从~27 开始变得“统计显著”,并在~ 65 左右停止显著,而曲线本身的变化很小:
在这个模拟中,除了定义高和低基因表达的阈值之外,没有任何变化。
如果您仍然想了解连续变量和生存率之间的关系,您可能想了解 Cox 比例风险模型,其中包括影响预后的其他协变量。
如果我们只包括基因表达,Cox 比例风险(请注意,我没有检查比例风险假设,这应该在您对真实数据集进行此操作之前进行)给出的 p 值为 0.0276,HR 为 3.114,95%置信区间为[1.133,8.559]。由于在将其用作 Cox 拟合的输入之前,我已经转换了 log10 尺度的基因表达,3.14 的 HR 将意味着基因表达每增加 log10 单位的风险增加 3.14 倍(正如@jvdesomp 在 Twitter 上指出的)。
此外,使用 Cox 回归模型,如果基因表达增加,我们可以评估相对死亡率(而不是隐藏大部分数据的二分法):
模拟内部
生成虚构数据
如果你对模拟是如何制作的感兴趣,请继续阅读。
开始了一项虚构的研究,随访时间为 96 个月,有 300 名参与者。没有被审查的个体(除了在 96 个月,模拟结束时)。基因表达从对数正态分布中随机取样,其中 rlnorm R 函数的均值为 4,标准差为 1:
模拟了其他几个协变量,这在本文中没有提到。我鼓励你自己看一看它们,看看对 KM 估计和 Cox 模型有什么影响。
考克斯回归
为了研究基因表达对风险比的影响,使用了惩罚样条项(注意,基因表达是从对数正态分布中取样的,因此这里应该转换回 log10 标度):
相对死亡率如生存曲线插图所述绘制(但使用 ggplot 代替 matplot 函数)。
这个模拟的完整代码可以在 GitHub 上获得。
关于作者:
我是 Ruben Van Paemel 博士,我于 2017 年从医学院毕业后开始在根特大学(医学遗传学中心)担任博士研究员,由研究基金会 Flanders 资助。我也是根特大学医院的儿科住院医师。可以在 Twitter 关注我:@ RubenVanPaemel
我从事神经母细胞瘤的研究,这是一种罕见但极具破坏性的肿瘤,最常见于非常年幼的儿童。我们的团队试图了解潜在的基因改变,以提高神经母细胞瘤儿童的诊断、治疗和最终存活率。
参考
封面照片- Unsplash
Kaplan Meier 模拟代码编辑自http://dwoll . de/rex repos/posts/survival km . html # simulated-right-删失事件次数服从威布尔分布
Python 中的字典
理解 Python 中的字典
在 python 中,字典是数据值的无序集合,其中每个元素是一个键/值对。字典有各种各样的应用,包括视频游戏中的映射对象、数据库设置、单元测试等等。在这篇文章中,我们将讨论如何在 python 中定义和执行简单的字典操作。
我们开始吧!
带集合和列表的词典
字典的简单定义是将一个键映射到一个值的数据结构。大多数情况下,字典用于将键映射到多个值。为此,我们需要将字典键映射到容器,比如集合和列表。例如,假设我们有一个包含电影名称和元评论等级的字典:
movie_ratings = {'movie_name': ['Parasite', 'Once Upon a Time in Hollywood', 'Marriage Story'], 'rating': [96, 83, 93]}
在这个字典中,“movie_name”是一个映射到包含三部电影的列表的键。类似地,“rating”是映射到三个电影分级的另一个键。您也可以使用集合来定义类似的字典:
movie_ratings = {'movie_name': {'Parasite', 'Once Upon a Time in Hollywood', 'Marriage Story'}, 'rating': {96, 83, 93}}
其中集合是没有重复的无序集合对象。如果要保持集合中各项的插入顺序,就需要使用列表。如果你不想在你的收藏中有重复的东西,而且顺序也不重要,你可以使用集合。
默认词典
定义带有映射到列表或集合的键的字典的一种简单方法是使用“defaultdict”。让我们使用‘default dict’来构造我们的第一个字典,它使用了列表。首先,让我们导入’ defaultdict ':
from collections import defaultdict
让我们将电影分级字典初始化为列表字典:
movie_ratings = defaultdict(list)
python 中默认字典的一个很好的特性是,它们会自动初始化第一个值,这样您就可以专注于插入值。让我们插入三个电影名称:
movie_ratings['movie_name'].append('Parasite')
movie_ratings['movie_name'].append('Once Upon a Time in Hollywood')
movie_ratings['movie_name'].append('Marriage Story')
现在,让我们打印字典:
print(movie_ratings)
接下来让我们插入电影评级:
movie_ratings['rating'].append(96)
movie_ratings['rating'].append(83)
movie_ratings['rating'].append(93)
让我们再打印一次:
print(movie_ratings)
现在让我们添加一个副本。让我们插入“寄生虫”及其评级:
movie_ratings['movie_name'].append('Parasite')
movie_ratings['rating'].append(96)
让我们打印:
print(movie_ratings)
我们看到我们已经成功地插入了一个副本。让我们试着为一套字典做同样的事情。让我们初始化我们的集合字典:
movie_ratings = defaultdict(set)
我们现在将添加我们的电影名称。请注意,对于集合,我们使用“add()”方法,不要与我们用于列表的“append()”方法混淆:
movie_ratings['movie_name'].add('Parasite')
movie_ratings['movie_name'].add('Once Upon a Time in Hollywood')
movie_ratings['movie_name'].add('Marriage Story')print(movie_ratings)
接下来让我们添加我们的评级:
movie_ratings['rating'].add(96)
movie_ratings['rating'].add(83)
movie_ratings['rating'].add(93)print(movie_ratings)
现在让我们尝试添加一个副本。让我们再次添加“寄生虫”及其评级:
movie_ratings['movie_name'].add('Parasite')
movie_ratings['rating'].add(96)print(movie_ratings)
我们可以看到我们的字典没有被修改。这是因为集合不允许重复。我将在这里停下来,但是我鼓励你研究一下 python 中其他一些重要的字典方法。例如,您可以使用。items()”方法迭代键/值对:
for key, value in movie_ratings.items():
print(key, value)
您可以研究的其他重要方法有。keys()“,”。get()‘、和’。setdefault()'。
结论
总之,在这篇文章中,我们讨论了如何在 python 中定义和操作字典。我们讨论了如何用列表和集合定义字典。我们还讨论了如何使用“defaultdict”来初始化这些字典类型,这使我们能够专注于数据插入。我希望这篇文章对你有用。这篇文章的代码可以在 GitHub 上找到。感谢您的阅读!
Python 中的字典
由玛蒂尔德·德库尔塞尔在 Unsplash 上拍摄的照片
Python 中最重要的数据结构之一
在这篇文章中,我将谈论字典。这是“Python 中的数据结构”系列的第二篇文章。这个系列的第一部分是关于列表的。
字典是 Python 中使用键进行索引的重要数据结构。它们是一个无序的项目序列(键值对),这意味着顺序没有被保留。密钥是不可变的。就像列表一样,字典的值可以保存异构数据即整数、浮点、字符串、NaN、布尔、列表、数组,甚至嵌套字典。
这篇文章将为您提供清晰的理解,并使您能够熟练地使用 Python 字典。
本文涵盖了以下主题:
- 创建字典并添加元素
- 访问字典元素
- 删除字典元素
- 添加/插入新元素
- 合并/连接字典
- 修改字典
- 整理字典
- 字典理解
- 创建词典的替代方法
- 抄字典
- 重命名现有密钥
- 嵌套字典
- 检查字典中是否存在关键字
1)创建字典并添加元素
像列表用方括号([ ])初始化,字典用花括号({ })初始化。当然,空字典的长度为零。
dic_a = {} # An empty dictionarytype(dic_a)
>>> dictlen(dic_a)
>>> 0
一个字典有两个典型特征:键和值。**每个键都有相应的值。**键和值都可以是字符串、浮点、整数、NaN 等类型。向字典中添加元素意味着添加一个键值对。字典由一个或多个键值对组成。
让我们给我们的空字典添加一些元素。下面是这样做的一种方法。这里,'A'
是键,'Apple'
是它的值。您可以添加任意多的元素。
# Adding the first element
dic_a['A'] = 'Apple'print (dic_a)
>>> {'A': 'Apple'}# Adding the second element
dic_a['B'] = 'Ball'print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball'}
注意: Python 区分大小写,'A'
和'a'
充当两个不同的键。
dic_a['a'] = 'apple'print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'a': 'apple'}
一次初始化字典
如果您发现上述逐个添加元素的方法令人厌烦,您还可以通过指定所有的键-值对来立即初始化字典。
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
异质词典
到目前为止,您的字典将字符串作为键和值。字典也可以存储混合类型的数据。以下是有效的 Python 字典。
dic_b = {1: 'Ace', 'B': 123, np.nan: 99.9, 'D': np.nan, 'E': np.inf}
不过,您应该为关键字使用有意义的名称,因为它们表示字典的索引。特别要避免使用 floats 和 np.nan 作为键。
2)访问字典元素
创建了字典之后,让我们看看如何访问它们的元素。
访问键和值
您可以分别使用功能dict.keys()
和dict.values()
访问键和值。您还可以使用items()
函数以元组的形式访问键和值。
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_a.keys()
>>> dict_keys(['A', 'B', 'C'])dic_a.values()
>>> dict_values(['Apple', 'Ball', 'Cat'])dic_a.items()
>>> dict_items([('A', 'Apple'), ('B', 'Ball'), ('C', 'Cat')])
或者,您也可以使用“for”循环一次访问/打印一个。
# Printing keys
for key in dic_a.keys():
print (key, end=' ')
>>> A B C############################## Printing values
for key in dic_a.values():
print (key, end=' ')
>>> Apple Ball Cat
您可以避免两个“for”循环,并使用items()
访问键和值。“for”循环将遍历由items()
返回的键值对。这里,key
和value
是任意的变量名。
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}for key, value in dic_a.items():
print (key, value)
>>> A Apple
B Ball
C Cat
访问单个元素
无法使用列表式索引来访问词典条目。
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_a[0]
>>> **---->** 1 dic_a[**0**] **KeyError**: 0
您需要使用键从字典中访问相应的值。
# Accessing the value "Apple"
dic_a['A']
>>> 'Apple'# Accessing the value "Cat"
dic_a['C']
>>> 'Cat'
如果字典中不存在该键,您将得到一个错误。
dic_a['Z']**>>> KeyError** Traceback (most recent call last)
**----> 1** dic_a**['Z']****KeyError**: 'Z'
如果您想在不存在键的情况下避免这样的键错误,您可以使用get()
功能。当键不存在时,返回None
。您也可以使用自定义消息来返回。
print (dic_a.get('Z'))
>>> None# Custom return message
print (dic_a.get('Z', 'Key does not exist'))
>>> Key does not exist
像在列表中一样访问字典元素
如果您想使用索引来访问字典元素(键或值),您需要首先将它们转换成列表。
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}list(dic_a.keys())[0]
>>> 'A'list(dic_a.keys())[-1]
>>> 'C'list(dic_a.values())[0]
>>> 'Apple'list(dic_a.values())[-1]
>>> 'Cat'
3)删除字典元素
从字典中删除元素意味着一起删除一个键值对。
使用 del
您可以使用del
关键字和您想要删除其值的键来删除字典元素。删除是原位的,这意味着您不需要在删除后重新分配字典的值。
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}# Deleting the key-value pair of 'A': 'Apple'
del dic_a['A']print (dic_a)
>>> {'B': 'Ball', 'C': 'Cat'}# Deleting the key-value pair of 'C': 'Cat'
del dic_a['C']print (dic_a)
>>> {'B': 'Ball'}
使用 pop()
您还可以使用“pop()”函数来删除元素。它返回被弹出(删除)的值,字典被就地修改。
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_a.pop('A')
>>> 'Apple' print (dic_a)
# {'B': 'Ball', 'C': 'Cat'}
在上述两种方法中,如果要删除的键在字典中不存在,您将得到一个 KeyError。在“pop()”的情况下,如果键不存在,您可以指定要显示的错误消息。
key_to_delete = 'E'
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_a.pop(key_to_delete, f'Key {key_to_delete} does not exist.')
>>> 'Key E does not exist.'
删除多个元素
没有直接的方法,但是你可以使用如下所示的“for”循环。
to_delete = ['A', 'C']
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}for key in to_delete:
del dic_a[key]
print (dic_a)
>>> {'B': 'Ball'}
4)添加/插入新元素
您可以一次添加一个元素到一个已经存在的字典中,如下所示。
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_a['D'] = 'Dog'print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}dic_a['E'] = 'Egg'print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog', 'E': 'Egg'}
如果您添加的密钥已经存在,现有值将被覆盖**。**
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_a['A'] = 'Adam' # key 'A' already exists with the value 'Apple'print (dic_a)
>>> {'A': 'Adam', 'B': 'Ball', 'C': 'Cat'}
使用更新( )
您还可以使用update()
函数添加一个新的键-值对,方法是将该对作为参数传递。
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
dic_a.update({'D': 'Dog'})print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
update()
函数还允许您同时向现有字典添加多个键值对。
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
dic_b = {'D':'Dog', 'E':'Egg'}dic_a.update(dic_b)print(dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog', 'E': 'Egg'}
5)合并/连接字典
从 Python 3.5 开始,可以使用解包操作符(**
)合并两个或多个字典。
dic_a = {'A': 'Apple', 'B': 'Ball'}
dic_b = {'C': 'Cat', 'D': 'Dog'}dic_merged = {**dic_a, **dic_b}print (dic_merged)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
如果您不想创建一个新的字典,而只想将dic_b
添加到现有的dic_a
中,您可以简单地更新第一个字典,如前面所示。
dic_a = {'A': 'Apple', 'B': 'Ball'}
dic_b = {'C': 'Cat', 'D': 'Dog'}dic_a.update(dic_b)print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
串联时如何处理重复键?
Python 字典的一个特点是它们不能有重复的键,也就是说,一个键不能出现两次。那么,如果你把两个或更多有一个或多个公共关键字的字典连接起来,会发生什么呢?
答案是最后合并的字典中的键值对(按合并的顺序)会存活下来。在下面的例子中,键'A'
存在于所有三个字典中,因此,最终的字典采用最后一个合并的字典的值(dic_c
)。
dic_a = {'A': 'Apple', 'B': 'Ball'}
dic_b = {'C': 'Cat', 'A': 'Apricot'}
dic_c = {'A': 'Adam', 'E': 'Egg'}dic_merged = {**dic_a, **dic_b, **dic_c}print (dic_merged)
>>> {'A': 'Adam', 'B': 'Ball', 'C': 'Cat', 'E': 'Egg'}
一句警告
我刚刚说过字典不能有重复的键。严格地说,您可以定义一个具有重复键的字典,但是,当您打印它时,将只打印最后一个重复键。如下所示,只返回唯一的键,对于重复的键(此处为“A”),只返回最后一个值。
dic_a = {'A': 'Apple', 'B': 'Ball', 'A': 'Apricot', 'A': 'Assault'}print (dic_a)
>>> {'A': 'Assault', 'B': 'Ball'}
Python 3.9+中更简单的方法
从 Python 3.9 开始,可以使用|
操作符连接两个或更多的字典。
dic_a = {'A': 'Apple', 'B': 'Ball'}
dic_b = {'C': 'Cat', 'D': 'Dog'}dic_c = dic_a | dic_b
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}# Concatenating more than 2 dictionaries
dic_d = dic_a | dic_b | dic_c
6)修改字典
如果想把'A'
的值从'Apple'
改成'Apricot'
,可以用一个简单的赋值。
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_a['A'] = 'Apricot'print (dic_a)
>>> {'A': 'Apricot', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
7)整理字典
字典中不维护顺序。使用sorted()
功能,您可以使用关键字或值对字典进行排序。
使用关键字排序
如果键是字符串(字母),它们将按字母顺序排序。在字典中,我们有两个主要元素:键和值。因此,在根据键进行排序时,我们使用第一个元素,即键,因此,lambda 函数中使用的索引是“[0]”。你可以阅读这篇文章了解更多关于 lambda 函数的信息。
dic_a = {'B': 100, 'C': 10, 'D': 90, 'A': 40}sorted(dic_a.items(), key=lambda x: x[0])
>>> [('A', 40), ('B', 100), ('C', 10), ('D', 90)]
分类不到位。如下所示,如果您现在打印字典,它仍然是无序的,和最初初始化时一样。排序后,您必须重新分配它。
# The dictionary remains unordered if you print it
print (dic_a)
>>> {'B': 100, 'C': 10, 'D': 90, 'A': 40}
如果您想以相反的顺序排序,请指定关键字reverse=True
。
sorted(dic_a.items(), key=lambda x: x[0], reverse=True)
>>> [('D', 90), ('C', 10), ('B', 100), ('A', 40)]
使用值排序
要根据字典的值对其进行排序,需要在 lambda 函数中使用索引“[1]”。
dic_a = {'B': 100, 'C': 10, 'D': 90, 'A': 40}sorted(dic_a.items(), key=lambda x: x[1])
>>> [('C', 10), ('A', 40), ('D', 90), ('B', 100)]
8)词典理解
这是动态创建字典的一个非常有用的方法。假设您想要创建一个字典,其中键是一个整数,值是它的平方。字典的理解应该是这样的。
dic_c = {i: i**2 for i in range(5)}print (dic_c)
>>> {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
如果你希望你的键是字符串,你可以使用“f-strings”。
dic_c = {f'{i}': i**2 for i in range(5)}print (dic_c)
>>> {'0': 0, '1': 1, '2': 4, '3': 9, '4': 16}
9)创建词典的替代方法
从列表创建字典
假设你有两个列表,你想用它们创建一个字典。最简单的方法是使用dict()
构造函数。
names = ['Sam', 'Adam', 'Tom', 'Harry']
marks = [90, 85, 55, 70]dic_grades = dict(zip(names, marks))print (dic_grades)
>>> {'Sam': 90, 'Adam': 85, 'Tom': 55, 'Harry': 70}
您还可以将这两个列表压缩在一起,并使用前面所示的“字典理解”创建字典。
dic_grades = {k:v for k, v in zip(names, marks)}print (dic_grades)
>>> {'Sam': 90, 'Adam': 85, 'Tom': 55, 'Harry': 70}
传递键值对
您还可以将逗号分隔的键值对列表传递给dict()
构造,它将返回一个字典。
dic_a = dict([('A', 'Apple'), ('B', 'Ball'), ('C', 'Cat')])print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
如果您的键是字符串,您甚至可以使用更简单的初始化,只使用变量作为键。
dic_a = dict(A='Apple', B='Ball', C='Cat')print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
10)复制字典
我将用一个简单的例子来解释这一点。字典的复制机制涉及到更多的微妙之处,我建议读者参考这篇堆栈溢出帖子,了解详细的解释。
参考分配
当您简单地将一个现有字典(父字典)重新分配给一个新字典时,两者都指向同一个对象(“引用分配】)。
考虑下面的例子,您将dic_a
重新分配给dic_b
。
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_b = dic_a # Simple reassignment **(Reference assignment)**
现在,如果您修改dic_b
(例如添加一个新元素),您会注意到这一变化也会反映在dic_a
中。
dic_b['D'] = 'Dog'print (dic_b)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
浅拷贝
使用copy()
函数创建一个浅层副本。在浅层拷贝中,两个字典作为两个独立的对象,它们的内容仍然共享相同的引用。如果在新字典(浅层拷贝)中添加一个新的键值对,它将不会出现在父字典中。
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
dic_b = dic_a.copy()dic_b['D'] = 'Dog'# New, shallow copy, has the new key-value pair
print (dic_b)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}# The parent dictionary does not have the new key-value pair
print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
现在,父字典(" dic_a ")中的内容是否会改变取决于值的类型。例如,在下面的例子中,内容是简单的不可变的字符串。因此,改变“dic_b”中给定键的值(本例中为'A'
)不会改变“dic_a”中键'A'
的值。
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
dic_b = dic_a.copy()# Replace an existing key with a new value in the shallow copy
dic_b['A'] = 'Adam'print (dic_b)
>>> {'A': 'Adam', 'B': 'Ball', 'C': 'Cat'}# Strings are immutable so 'Apple' doesn't change to 'Adam' in dic_a
print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
但是,如果“dic_a”中关键字'A'
的值是一个列表,那么在“dic_b”中改变它的值将反映“dic_a”(父字典)中的变化,因为列表是可变的。
dic_a = {'A': ['Apple'], 'B': 'Ball', 'C': 'Cat'}# Make a shallow copy
dic_b = dic_a.copy()dic_b['A'][0] = 'Adam'print (dic_b)
>>> {'A': ['Adam'], 'B': 'Ball', 'C': 'Coal'}# Lists are mutable so the changes get reflected in dic_a too
print (dic_a)
>>> {'A': ['Adam'], 'B': 'Ball', 'C': 'Cat'}
11)重命名现有密钥
假设您想将键“Adam”替换为“Alex”。您可以使用pop
函数,因为它删除传递的键(这里是“亚当”)并返回删除的值(这里是 85)。所以你一箭双雕。使用返回的(已删除的)值将该值分配给新键(此处为“Alex”)。可能有更复杂的情况,其中键是一个元组。这种情况超出了本文的范围。
dic_a = {'Sam': 90, 'Adam': 85, 'Tom': 55, 'Harry': 70}dic_a['Alex'] = dic_a.pop('Adam')print (dic_a)
>>> {'Sam': 90, 'Tom': 55, 'Harry': 70, 'Alex': 85}
12)嵌套字典
嵌套字典在一个字典中有一个或多个字典。下面是具有两层嵌套的嵌套字典的最简单的例子。这里,外部字典(第 1 层)只有一个键值对。然而,该值现在是字典本身。
dic_a = {'A': {'B': 'Ball'}}dic_a['A']
>>> {'B': 'Ball'}type(dic_a['A'])
>>> dict
如果想要进一步访问内部字典(第 2 层)的键-值对,现在需要使用dic_a['A']
作为字典。
dic_a['A']['B']
>>> 'Ball'
三层字典
让我们添加一个额外的嵌套字典层。现在,dic_a['A']
本身就是一个嵌套字典,不像上面最简单的嵌套字典。
dic_a = {'A': {'B': {'C': 'Cat'}}}# Layer 1
dic_a['A']
>>> {'B': {'C': 'Cat'}}# Layer 2
dic_a['A']['B']
>>> {'C': 'Cat'}# Layer 3
dic_a['A']['B']['C']
>>> 'Cat'
13)检查字典中是否存在关键字
您可以使用in
操作符来查找特定的键是否存在于字典中。
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}'A' in dic_a
# True'E' in dic_a
# False
在上面的代码中,不需要使用“in dic_a.keys()”,因为“in dic_a”已经在键中查找了。
这让我想到了这篇文章的结尾。您可以在这里访问本系列的第一部分“Python 中的数据结构”。