图形神经网络导论(一)
图形神经网络及其应用
艾莉娜·格鲁布尼亚克在 Unsplash 上的照片
在过去的三年里,图形神经网络(GNN)的受欢迎程度呈指数级增长。这是因为他们能够有效地从图表数据中学习。大多数系统背后都有一个错综复杂的网络连接,它定义了网络组件之间的交互。这种相互作用可以用图表来表示。要对这些系统建模,理解它们背后的网络是至关重要的。图形神经网络帮助我们利用网络的关系结构来更好地建模和预测结果。
什么是图形?
图是一种结构化的非线性数据类型,具有节点(也称为顶点)和边。数学上表示为 G(V,E)。例如,NO₂的分子可以被认为是一个图,其中氮原子和两个氧原子被认为是节点,原子之间的键被认为是边。另一个图的例子可以是你的家庭,每个人是一个节点,两个人的关系是边。
作为图表的 NO₂(图片由作者提供)
一个图在其节点和边上都可以有标签。它可以是数字,也可以是文本。每个节点都有一些定义该节点的特征。在 NO₂图中,每个节点的原子序数、原子量和价电子数等元素可以是其各自的特征。根据图表的类型,边可能有也可能没有特征。在 NO₂,边缘的特征可以是键强度、键类型(单键或双键)等。
图表是在许多不同的基础上分类的。最常见的是基于图的边。这些是有向和无向图。在有向图中,从一个节点到另一个节点的边是有向的,而在无向图中,节点通过边连接,没有方向。
有向图(图片由作者提供)
无向图(图片由作者提供)
有向图的一个实际例子是 Instagram。你跟着一个人,他不一定跟着你回去。从某种意义上说,这是单向的。另一方面,脸书朋友请求是无向图的一个例子。一旦好友请求被接受,双方都可以看到对方的内容。
图表示例
- **社交网络:**社交网络是以节点代表人的图,两个人的关系就是边。这种关系可以是任何东西,从简单的熟人到家庭。
- 分子:一个分子可以用一个图来表示,图中的节点代表原子,边代表它们之间的键。
- 互联网:互联网是一个图形,设备、路由器、网站和服务器是节点,互联网连接是边。
分析图表(详解)
- 节点分类 —预测给定节点的类型。例如,社交网络中给定的人可以基于他们的兴趣、信仰或特征进行分类。
- 链接预测 —预测两个节点是否链接以及如何链接。例如,查找两个给定的人(节点)之间是否有任何关系。
- 社区检测 —识别密集链接的节点集群。例如,发现一群人是否有任何话题相似之处。
- 网络相似度 —测量两个节点/网络的相似度。在这里你可以发现两个人或两个不同的群体是否彼此相似。
图形神经网络的一些应用
- 推荐系统:使用 GNNs,推荐系统的能力可以成倍增加。使用 GNNs,推荐将基于从附近的节点借用信息,从而使节点嵌入更准确。Pinterest 使用基于 GNN 的推荐系统。
- 处理错误信息:一篇恶作剧文章可以通过它的链接识别出来。真文章链接比较连贯,假文章会有散尾。根据 2016 年一篇题为“网络上的虚假信息”的论文,人类可以在 100 次中检测到 66 次恶作剧文章,而 GNN 可以在 100 次中检测到 86 次。
- 药物开发:所有的分子都可以用一个图形来表示。使用 GNNs,可以模拟复杂的网络,如蛋白质-蛋白质相互作用(PPI)网络和代谢网络。这种模式有助于开发更好、更稳定的疾病治疗药物。
- Twitter 上的两极分化:根据一个人喜欢的帖子和他们关注的人,可以检测出一个人是否对某个话题(政治、环境等)持特定观点。)还是没有。
- **社交圈检测:**使用 GNNs,可以根据一个人与他人的互动来检测他的社交圈。这个圈子可以是同事,大学朋友,家人,同班同学等。
为什么卷积不能应用于图?
图像具有固定的大小和基于网格的结构数据,具有定义的空间位置。另一方面,图具有任意的大小、复杂的拓扑和非欧几里得结构。它也没有固定的节点顺序。众所周知,神经网络是为特定的大小、网格和结构定义的。因此卷积不能直接应用于图。
结论
在接下来的博客中,我会写关于将图转换成矩阵的内容,比如邻接矩阵、关联矩阵、度矩阵和拉普拉斯矩阵。此外,我将讨论在图上使用卷积的不同方法及其优缺点,创建图的节点嵌入,以及 GNN 中的前向传播。
知识图导论
如何以图表形式表示和操作数据
由克林特·王茂林拍摄
这是论文 “知识图的关系机器学习综述(2015 年 9 月 28 日)”【1】中一些要点的总结,它很好地介绍了知识图以及用于构建和扩展它们的一些方法。
关键的外卖
信息可以以图形的形式组织,节点代表实体,边代表实体之间的关系。知识图可以手动构建,也可以在某些源文本(例如维基百科)上使用自动信息提取方法构建。给定一个知识图,统计模型可以用来通过推断缺失的事实来扩展和完善它。
真相
涵盖的主题:
- 知识图表的基础知识
- 统计关系学习
2.1 潜在特征模型
2.1.1 重标度
2.1.2 多层感知器
2.1.3 潜在距离模型
2.2 图特征模型
2.2.1 路径排序算法
2.3 结合潜在和图特征模型 - 一些更酷的东西
1.知识图的基础
摘自论文[1]
知识图(KGs)是一种以图表形式组织信息的方式,通过将实体(例如:人、地点、物体)表示为节点,将实体之间的关系(例如:结婚、位于)表示为边。事实典型表示为“SPO”三元组:(主语、谓语、宾语)。本质上,由关系连接的两个节点形成一个事实。例如,上图中的一个事实可能是:“斯波克是《星际迷航》中的一个角色”。这个事实是由两个节点Spock
和Star Trek
以及关系characterIn
形成的 SPO 三元组 (Spock,characterIn,Star Trek) 。
所以,现有的边表示已知的事实。缺边怎么办?
有两种可能:
- 封闭世界假设【CWA】**:不存在的三元组/边表示虚假关系:例如,既然《伦纳德·尼莫伊到星球大战》中没有
starredIn
边,我们就推断《星球大战》中伦纳德·尼莫伊没有星 - 开放世界假设 (OWA) :不存在的三元组/边简单代表未知数:由于从伦纳德·尼莫伊到星战没有
starredIn
边,我们不知道伦纳德·尼莫伊是否出演过星战
KGs 一般包括各种类型的等级制度(伦纳德·尼莫伊是演员,是人,是活物)约束(一个人只能娶另一个人,不能娶一个东西)。
构建知识图表的方法:
- 由专家或志愿者手动操作
- 通过从半结构化文本中自动提取它们(例如:维基百科信息框)
- 通过从非结构化文本中自动提取它们(使用自然语言处理技术)
知识图管理的主要任务:
- 链接预测:预测图中缺失的边(即:缺失的事实)
- 实体解析:寻找实际上指同一事物的不同节点和/或不同边。例如,一个系统可能包含三元组,如(奥巴马,博宁,夏威夷)和(巴拉克·奥巴马,普莱瑟夫伯斯,檀香山)。我们可能想要合并
Obama
和Barack Obama
节点,因为它们可能指的是同一个实体。 - 基于链接的聚类:根据链接的相似性对实体进行分组
2.知识密集型企业的统计关系学习(SRL)
假设:一个图中所有的实体和类型的关系都是已知的(有 N_e 个实体和 N_r 种类型的关系)。然而,三元组是不完全的*:也就是说,图中的一些节点是连接的,但也有一些节点对应该连接但没有连接。这意味着:有一定数量的真实事实,但我们只知道其中的一部分。还可能存在实体和关系的副本。*
我们姑且称 e_i 为主语节点(例如:Spock
), e_j 为宾语节点(Star Trek
),而 r_k 为关系类型(characterIn
)。我们现在可以将每个可能的三元组 x_ijk = ( e_i,r_k,e_j )建模为一个二元随机变量 y_ijk ∈ {0,1}。这里,如果三元组存在, y_ijk 为 1,否则为 0。在封闭世界假设中,0 表示错误的三元组,而在开放世界中,它表示未知。这些随机变量彼此相关,因为某些三联体的存在可以预测其他三联体的存在/不存在。
我们可以把所有可能的三元组组合成一个维度为 N_e x N_e x N_r 的三阶张量 Y ∈ {0,1},见下图。
摘自论文[1]
对 Y 的每一个可能的实现都是一个可能的“世界”,是事实的某种组合。我们想弄清楚,在已知的有限数量的三元组中,哪一种实现最有可能是准确的。为此,我们需要从 N_d 个观察到的三元组的子集𝒟中估计分布 P( Y )。Y 可能非常大,所以这个任务可能非常复杂。例如, Freebase 有大约 4000 万个实体和 35k 个关系,给出 10 个⁹可能的三元组。
然而,由于某些限制,这些三元组中只有一小部分是可行的。例如,我们知道关系marriedTo
只能链接指向人的两个节点,所以我们已经可以排除所有的三元组( e_i,r _ marriedTo,e_j ),其中一个或两个实体都不是人。理想情况下,我们希望找到一种方法来轻松识别并丢弃所有这些“不可能的”三元组。
知识图的统计性质
正如已经看到的,kg 通常遵循一组确定性规则,例如:
- 类型约束:关系
marriedTo
只能指一个人 - 传递性:如果 A 位于 B and B 位于 C,那么 A 位于 C
他们也经常松散地遵循一套统计模式:
- 同向(或“自相关”):实体倾向于与具有相似特征的实体相关
- 块结构:一些实体可以被分组为“块”,使得一个块的所有成员与另一个块的成员具有相似的关系
SRL 模型的类型
本文涵盖了 3 种主要类型的统计关系学习模型:
- 潜在特征模型:我们假设给定一些潜在特征和附加参数,所有 y_ijk 都是独立的
- 图形特征模型:我们假设给定观察到的图形特征和附加参数,所有 y_ijk 都是独立的
- 马尔可夫随机场:我们假设所有的 y_ijk 都有局部相互作用[不在本概述中]
**潜在特征模型和图形特征模型使用评分函数 f(x _ ijk;θ),其中θ是某组参数。
2.1 潜在特征模型
在潜在特征模型中,我们通过潜在变量来解释三元组。例如,我们可以用“亚历克·伊兹高尼是一个好演员”这一潜在变量来解释“亚历克·伊兹高尼获得奥斯卡奖”这一事实。
给定一个实体 e_i,我们用向量 e _i ∈ ℝ^{H_e}.来表达它的潜在特征例如,假设我们有两个潜在的特征(H_e = 2):成为一个好演员,和获得一个有声望的奖项。我们可以将实体AlecGuinness
和AcademyAward
的潜在特征向量表示如下:
其中,潜在向量的第一元素表示“好演员”,第二元素表示“有声望的奖项”。
为了预测三元组,我们需要对这些潜在变量之间的相互作用进行建模。本文回顾了已开发的几种方法,我将总结其中的主要方法:
- 重新校准
- 多层感知器
- 潜在距离模型
重新校准
也许最直观的是,RESCAL“通过潜在特征的成对相互作用来解释三元组”,因此三元组的得分由下式给出:
其中 W _k 是维数为 H_e x H_e 的权重矩阵“其条目 w_abk 指定了潜在特征 a 和 b 在 k 个关系中相互作用的程度”。以亚历克·伊兹高尼获得奥斯卡奖为例,第 k 个关系是receivedAward
,其权重矩阵可以是:
其中右上元素指示“好演员”和“有声望的奖项”的组合(使用之前的潜在向量的结构)。这将模拟一个潜在的事实,即好演员会获得有声望的奖项。
因此,在 RESCAL 中,每个实体有一个潜在向量,每个关系类型有一个权重矩阵,因此参数θ为:
重写评分函数
我们也可以用不同的方式编写 RESCAL 的评分函数。正如我们将看到的,这将有助于稍后比较 RESCAL 与其他方法。以前我们有:
我们可以看到,我们可以通过首先创建一个包含潜在特征向量元素的所有组合的向量来重写这个(在上面的公式中,这些都是组合 e_ia x e_jb )。我们可以这样得到这个向量:
其中,⊗是两个向量 a ∈ ℝ^N 和 b ∈ ℝ^M 的克罗内克乘积,这给出了维度为 NM 的向量:
因此,使用克罗内克乘积,我们可以重写评分函数:
其中 w _k = vec( W _k)(即:矩阵 W 的元素存储为一个向量)。
总而言之,我们可以将重标模型改写如下:
直观地表示出来(这里,潜在向量的大小是 H_e = 3):
摘自论文[1]
2.1.2 多层感知器(MLP)
实体多层感知器(E-MLP)
我们也可以选择使用标准的 MLP,以主体和客体实体的潜在向量作为输入,来模拟潜在特征的相互作用。因此,我们将有一个输入层,它接受潜在向量的串联:
然后我们有一个大小为 H_a 的隐藏层 h _{ijk}^a,它通过一个矩阵ak 来模拟潜在特征之间的相互作用:
注意 RESCAL 总是通过产品 e _i ⊗ e _j 考虑潜在特征的所有可能的交互,而 E-MLP 模型通过 A _k 学习这些交互。最后,我们输出分数:
其中 g (⋅)是某个激活函数。
我们可以形象地表示这一点(这里,潜在向量的大小是 H_e = 3,隐藏层的大小是 H_a = 2):
根据我们对 H_a 的选择,与 RESCAL 相比,这可以减少所需的参数数量。
实体关系多层感知器(ER-MLP)
如何进一步减少所需的参数数量?
我们可以将关系的潜在向量嵌入向量 r _k ∈ ℝ^{H_r},并将其与输入中的主体和客体向量连接在一起:
然后我们建立我们的隐藏层:
我们的输出层:
注意,通过嵌入关系,我们现在有了一个全局矩阵 C 而不是 k 相关的 A _k,以及一个全局向量 w 而不是 w _k。这可以大大减少所需的参数数量。
我们可以直观地表示这一点(这里潜向量的大小为 H_e = 3,H_r = 3,隐藏层的大小为 H_c = 3):
摘自论文[1]
比较这三种不同模型的结构:
2.1.3 潜在距离模型
在这些模型中,两个实体之间存在关系的概率由它们潜在表示的距离给出。这些表示越接近,这两个实体就越有可能处于关系中。对于单关系数据(因此当只有一种关系时),这是相当简单的:我们可以通过一个得分函数来模拟成对关系 x_ij 的概率
其中 d(⋅,⋅)是一些距离的措施(如:欧几里德距离)。
我们如何将此扩展到多关系数据?
一种可能的解决方案是结构化嵌入 (SE)模型,该模型对三重 x_ijk 使用以下评分函数:
因此,潜在特征通过矩阵 A _k^s 和 A _k^o 进行转换并进行比较。这些矩阵是“以现有关系中的实体对比不存在关系中的实体对彼此更接近的方式”学习的
2.2 图形特征模型
潜在特征模型使用潜在变量来预测图中的新链接。相比之下,图特征模型直接根据观察到的三元组进行预测。
一些类型的图形特征模型:
- 单一关系数据的相似性度量:这里的想法是使用某种相似性度量来预测两个节点之间的链接,因为相似的实体很可能是相关的。这种相似性可以以各种方式导出,例如通过查看节点的邻居(例如:“这两个节点有许多共同的邻居吗?”)
- 规则挖掘和归纳逻辑编程:如果我们能从观察到的三元组中提取出一些逻辑规则,就可以用它们来预测图中新的三元组。这可以使模型具有高度的可解释性,因为它只是由一组规则给出的。然而,学会所有的规则和模式并不容易
- 路径排序算法,如下所述
路径排序算法(PRA)
路径排序算法实质上是在图中寻找路径*,这些路径对于预测某些新边是有用的。例如,假设有两个节点 e_i 和 e_j ,它们都从第三个节点接收类型为bossOf
的输入链接。那么 PRA 可能了解到 e_i 和 e_j 被边colleagueOf
连接的概率很高。*
更一般地说,我们对构建一个模型感兴趣,该模型可以使用两个节点之间的更长路径来预测连接它们的某条直接边。
连接 e_i 和 e_j 的更长的路径能帮助我们预测它们之间直接关系 r_k 的存在吗?
我们可以使用逻辑回归模型来构建评分函数
其中,ϕ_ij 是对应于 e_i 和 e_j 之间所有可能路径的特征。
[ 注:我使用的符号与本文中用于路径特征的符号略有不同。我发现这与其他论文中使用的路径排序算法更清晰、更一致。]
所以我们现在需要找到一些方法来表达这些不同的路径,用一种数字的,可量化的形式,我们可以用它作为ϕ_ij.
设π=⟨R1,r_2,…,r_L⟩是一个长度为 l 的路类型*,即:一个确定的边类型序列【2】。我们如何表达 e_i 和 e_j 之间的路径,该路径遵循路径类型π作为特征?如果我们从 e_i 开始,沿着严格遵循π定义的边类型的路径,假设在每个交叉点,我们将随机均匀地选择一个可能的输出链接,我们可以使用我们将在 e_j 结束的概率。
我们可以把这个概率表示为 P(i → j,π)。*
*例如,让我们定义一个路径类型π=⟨f *里恩多夫,⟩.的父母如果我们看上面的图表,我们可以看到 P(i → j,π) = 0.5。事实上,从 e_i 开始,只有一条链路满足关系frienfOf
,所以我们将以 1 的概率到达节点 e_b。然而,从 e_b 有两个可能的parentOf
链接,所以从这里我们将以 1/2 的概率到达 e_j。
如果我们称π= {π₁,π₂,…,π_n}为我们要考虑的所有路径类型的集合,我们可以将我们的特征向量定义为:
我们的评分函数是:
使用这个模型的一个优点是它很容易解释。事实上,我们可以查看获得最高权重的路径类型,并将它们视为模型已经识别的“规则”。论文以学习到的权重为例,预测三元组 (p,college,c) ,从而预测一个人上过哪所大学。其中权重最高的是由⟩、学校起草的路径类型⟨ ,这意味着如果一个人是由某个学校(学院)所属的团队起草的,那么他很可能去了那个学院。**
摘自论文[1]
2.3 结合潜在和图形特征模型
潜在特征模型和图形特征模型具有不同的优势:
- 潜在特征模型适用于建模全球关系模式,当只有少量潜在变量足以解释三元组时
- 图形特征模型适用于本地关系模式的建模,当三元组可以用图形中实体的邻域或短路径来解释时
因此,将这两种类型的方法结合起来利用它们各自的优势是很有用的。一种可能的方法是通过加性关系效应 (ARE)模型。例如,我们可以将 RESCAL 和 PRA 结合起来:
通过这种方式,PRA 可以模拟可观察的图形模式*,而 RESCAL 可以模拟 PRA 无法模拟的“残余误差”。这意味着,与必须自己对所有事物建模相比,RESCAL 需要的潜在特征数量要少得多。*
3.一些更酷的东西
我会给你留下一些潜在的主题来研究,以扩展我在这篇文章中提到的内容:
- 马尔可夫随机场:另一种值得研究的统计关系学习模型。这在论文中有所涉及,不过如果你以前从未见过它们,我会建议你寻找一些对初学者更友好的资源
- 高阶关系:本文关注的是二元关系(我相信这是知识图的默认设置),但是也可以构建包含两个以上术语的关系图
- 时间呢?有些事实只在某个时刻或某个时间间隔内成立,我们如何对此建模?
参考文献
[1] Nickel,m .,Murphy,k .,Tresp,v .和 Gabrilovich,E. 知识图的关系机器学习综述 (2015)。IEEE 会议录,104(1)。
【2】Gardner,m .、Talukdar,p .、Kisiel,b .和 Mitchell,T. 利用潜在的句法线索提高大型知识库中的学习和推理 (2013)。2013 年自然语言处理经验方法会议录。
觉得这个故事有帮助?考虑 订阅 到媒体支持作家!
分类数据分析的逻辑回归导论
从逻辑回归的推导到解释
导出分类数据的模型
通常,当我们有一个连续变量 y(响应变量)和一个连续变量 x(解释变量)时,我们假设关系 E(Y|X) = β₀ +β₁X.这个等式对您来说应该很熟悉,因为它代表了简单线性回归的模型。这里,E(Y|X)是一个随机变量。
另一方面,如果 Y 是一个取值为 0 或 1 的二元变量,那么 E(Y|X)是一个概率。这意味着 0 < β₀ +β₁X < 1,这是一个不总是成立的假设。但是,如果我们考虑 log(E(Y|X)),我们将得到-∞ < β₀ +β₁X < 0。这又是一个受限空间,但比最初的情况好得多。如果你熟悉简单的逻辑回归模型,你会注意到我们正在接近它的实际形式。让我们考虑一个比值比,它被定义为ω=π/(1-π)其中 0
所以我们有范围(-∞,∞)。最后一个方程是普通逻辑回归方程。
理解范畴分析中的第三变量
在试图建立我们的模型或解释逻辑回归参数的意义之前,我们必须首先考虑可能影响我们实际建立和分析模型的额外变量。在处理分类数据分析时,通常会有被称为第三个变量的东西,它会对您将要尝试构建的模型产生一定的影响。根据你正在处理的第三个变量的类型,应该采取不同的措施来避免错误的结论。
混淆术语
与混杂变量 Z 的关系
当从 Z 到 X 和 Z 到 Y 存在直接关系,而 Y 依赖于 X 时,第三分类变量 Z(具有 k 个分类)是混杂变量。换句话说,混杂变量影响因变量和自变量,并且经常“隐藏”关联。后一种现象被称为虚假关系,这是一种两个或两个以上变量相关联,但由于第三个变量的存在而没有因果关系的关系。
相互作用项
一个相互作用的术语,通常意味着第三个变量改变了比如说一个暴露对结果的影响。也就是说,如果两个感兴趣的变量相互作用,那么它们和因变量之间的关系取决于另一个相互作用项的值。
解释逻辑回归
首先考虑简单的线性回归,其中 Y 是连续的,X 是二进制的。当 X = 0 时,E(Y|X=0) = β₀,当 X = 1 时,E(Y|X=1) = β₀ + β₁.因此,当解释β₁的含义时,我们说它代表两组之间的平均差异,即 X = 0 时(参考组)和 X = 1 时(比较组)的平均差异。
现在,让我们假设一个简单的例子,Y 和 X 是取值为 0 或 1 的二元变量。说到逻辑回归,β₁differs 的解释就像我们不再看的意思。回想一下,逻辑回归的模型 log(E(Y|X)/(1-E(Y|X)) = β₀ + β₁X,或者为了简化起见,log(π/(1-π)) = β₀ + β₁X.,这都是基于优势比。当我们考虑 X 的所有可能值时,
如果我们希望从上述两个例子中解释β₁,我们将把它当作一个简单的线性回归来分析。也就是说,β₁是从 X = 1 时的结果减去 X = 0 时的结果得到的:
这表明β₁是一个对数比值比,而 exp(β₁是一个比值比。
混杂解释
如果逻辑模型考虑了第三个变量,无论它是混杂项还是交互项,都可能有不同的方式来解释模型参数。
设 Y 和 X 如前所述,Z 是第三个变量,取值为 0 或 1。调整混杂因素的模型是 log(E(Y|X,Z)/(1-E(Y|X,Z))) = log(π/(1-π)) = β₀ + β₁X + β₂Z.。再次,让我们看看独立变量的每个值会得到什么:
类似地,我们会发现,对于每个 z,β₁是 X = 1 和 X = 0 之间的对数(比值),而 exp(β₁是比值比。
带交互项的解释
当第三个变量是交互项时,为其进行调整的模型是 log(E(Y|X,Z)/(1-E(Y|X,z))= log(π/(1-π))=β₀+β₁x+β₂z+β₁₂xz.这里,我们说β₁、β₂是主要效应,β₁₂是交互效应。
所以我们注意到,当 Z = 0 时,exp( β₁)又是一个优势比,当 Z = 1 时,exp( β₁ + β₁₂)又是一个优势比。那么,β₁₂一个人呢?事实上,exp(β₁₂)被解释为比值比根据 z 的水平变化多少。这被称为“比值比的比率”或“差异中的差异”。
示例和解释
让我们考虑 R 中一个大型内置数据集的随机子集,称为美国国家健康和营养检查研究(NHANES)。
上面显示的数据是原始数据的子集,只包括原始数据集中的几个变量。如果您想了解关于数据集的更多信息,您可以在此阅读更多信息。
然后,我们将对数据拟合逻辑回归模型,因为我们希望预测受试者是否是吸烟者(SmokeNow)。拟合的模型汇总如下所示。
对于这一特定模型,显著变量是那些 p 值小于 0.05 的变量。你会注意到它们旁边都有*、、,或者它们是否接近那个显著性水平。注意,这只是 R 中的特性,帮助用户直观地识别重要的协变量。
婚姻状况
MaritalStatus 变量是一个有六个类别的分类变量。请注意,这比上面的回归总结中列出的类别数多了一个。这意味着未列出的类别是参考类别。如果我们查看这个变量的所有级别,我们会发现类别是离婚(参照组)、同居伴侣、已婚、未婚、分居和丧偶。在这种情况下,已婚群体是显著的,其β估计值为-0.8162。这个数字到底是什么意思?回想一下,我们之前已经确定 exp(β)通常是两组之间的优势比。这里,这两个组是已婚组和离婚组(即参照组)。那么,exp(-0.8162) = 0.4421。因此,当控制所有其他变量时,已婚组吸烟的几率是离婚组吸烟的 0.4421 倍。
年龄
年龄变量是一个连续变量,因此不需要考虑类别/级别。这个的β = -0.0363,所以 exp(β) = 0.9644。我们将此解释为,在其他条件不变的情况下,年龄每变化一个单位,优势比就会变化 0.9644 个单位,因为该模型适用于 log(odds) = log( π/(1-π))。
家庭收入
与 MaritalStatus 类似,这是一个分类变量,我们会发现它有 12 个级别:0–4999(参考组)、5000–9999、10000–14999、15000–19999、20000–24999、25000–34999、35000–44999、45000–54999、55000–64999、55000–64999 综上所述,55000–64999 组具有显著性,估计值为β = 1.9478。那么,当控制所有其他变量时,在 55000-64999 收入组中吸烟的几率是在 0-4999 收入组中吸烟的几率的 7.0132 倍。
教育
如果需要,可以对最高教育水平得出类似的解释。
用 D3 制作动态交互式图形的介绍
使用 D3.js 让您的科学数据变得生动
照片由 chuttersnap 在 Unsplash 上拍摄
我一直对学习如何使用 D3.js 犹豫不决。它似乎总是比其他库(如matplotlib
)更不直观,我已经写了很多关于它的文章。然而,在这里,我发现自己正在编写一个关于在 D3 中制作绘图的教程——如果你正在开发一个网站或 web 应用程序,并且想要动态和交互式的数据可视化,熟悉这个库可能会很有用。在本文中,我收集了许多反复试验的结果,希望它既能作为参考,又能帮助您在这个过程中避免一些简单的初学者错误。本文的目标是再现我早期作品中的样本吸光度图:
介绍如何使用 Python 为科学出版物绘制数据
towardsdatascience.com](/an-introduction-to-making-scientific-publication-plots-with-python-ea19dfa7f51e)
在我们开始之前,我想先退一步。我以前在培养基方面的很多工作都是面向实验科学家的,因为这是我自己的个人背景。说到这一人群,我相信将这一新工具(D3)添加到您的工具箱将有助于您使您的数据变得生动,并且能够接触到比通常仅通过共享您发布的期刊文章更广泛的受众。所以,事不宜迟,我们开始吧。作为免责声明,对 JavaScript、HTML 和 CSS 的基本理解肯定会对读者有所帮助。
创建我们的文件
首先,我们需要创建 3 个文件(将它们都放在计算机上的同一个文件夹中):
index.html
—这是主 HTML 文件,我们将能够在我们的网络浏览器中打开它来查看我们的可视化style.css
——我们将使用 CSS 样式化我们可视化中的一些元素plot.js
——这将是我们项目的主力,包含我们绘制地图所需的所有 D3 代码
设置我们的 HTML 文件
我们的index.html
不会包含太多内容,因为我们将使用 JavaScript 操作文档。我们从任何 HTML 文件的一般框架代码开始。
<!DOCTYPE html><html>
</html>
在我们的html
标签中,我们将创建两个部分:head
和body
。在head
部分,我们将添加对 CSS 样式表的引用,以及到 D3.js 的链接,这样我们以后就可以使用所有的模块。
**<!--Import D3 and our CSS in the head section-->**<head>
<link *rel*='stylesheet' *href*='./style.css'>
<script *src*="https://d3js.org/d3.v5.min.js"></script>
</head>
现在,在我们的 HTML 文件的body
中,我们将创建一个名为plot_area
的div
,我们将用我们的plot.js
文件操作它,我们也将把它导入到当前文件中。
**<!--Create div for our plot and import plot.js-->**<body>
<div *id*='plot_area'></div>
<script *src*='./plot.js'></script>
</body>
我们已经完成了对index.html
的所有编辑——现在让我们继续我们可视化的主要工作,JavaScript。
使用 D3.js
D3 是一个 JavaScript 库,是数据驱动文档的缩写,提供了一系列使用和操作 SVG(可缩放矢量图形)的函数。我们要做的第一件事是定义我们的 SVG 的高度和宽度(以像素为单位)—除此之外,我们将定义一个填充值,以确保我们的绘图轴不与我们的 SVG 框架的边界垂直。
**// Set SVG width, height, and padding**const w = 500;
const h = 500;
const padding = 60;
加载我们的数据
接下来,我们需要加载我们的数据——为此,我们可以利用一个内置函数,其中format_fnc()
是我们格式化数据的 JavaScript 函数,而manipulate_fnc()
是我们将用来操作数据的函数:
d3.csv('path_to_file', format_fnc()).then(manipulate_fnc());
我们将使用的文件路径是我上一篇文章中的 CSV 文件,我们将把它传递给一个回调函数,该函数将我们的数据重新格式化为多维数组。我们可以通过列名(’ Wavelength ‘,’ Sample _ 1 _ absorption ‘和’ Sample _ 2 _ absorption ')引用每个 CSV 行中的字段—我们必须添加一个+
,因为默认情况下d3.csv()
将数据作为字符串加载,所以这可以确保我们存储的是数值。我们的最终数据数组将包含每个数据点的子数组,其中第一个元素是波长,第二个元素是样品 1 的吸光度,第三个元素是样品 2 的吸光度。最后,我们将格式化后的数据传递给我们接下来要编写的plot_data()
函数。
**// Load our CSV data**d3.csv('https://raw.githubusercontent.com/naveenv92/python-science-tutorial/master/intro/Absorbance_Data.csv', function (d) {
return [
+d['Wavelength'],
+d['Sample_1_Absorbance'],
+d['Sample_2_Absorbance']
]
}).then(plot_data);
现在,我们可以开始构建plot_data()
函数——我们要做的第一件事是定义轴限值:
**// Data plotting function**function plot_data(data) { **// Set axis limits**const xMin = 400;
const xMax = 950;
const yMin = 0;
const yMax = 2;
}
创建轴刻度
我们将继续添加这个函数的主体——我们现在需要设置轴缩放,我们将使用d3.scaleLinear()
来完成,因为我们的两个轴都将具有线性缩放。在此基础上,我们将链接另外两个方法:domain()
和range()
。域对应于我们将传递给标尺的输入值,范围对应于将在 SVG 上绘制的输出(以像素为单位)。你会注意到 D3 的一个主题是我们可以链接任意数量的方法。在这两个范围中,我们将通过我们的padding
变量的值来偏移端点,以便我们在轴周围有一些空白。你会注意到的另一个有趣的事情是,y 轴范围似乎是向后的!这是因为 SVG 的原点在左上角,所以我们需要翻转 y 值,以便将这个原点移到左下角。
**// Set x and y-axis scales**const xScale = d3.scaleLinear()
.domain([xMin, xMax])
.range([padding, w - padding]);const yScale = d3.scaleLinear()
.domain([yMin, yMax])
.range([h - padding, padding]);
尽管我们已经为输入值指定了一个域,但我们的缩放函数仍将绘制这些范围之外的数据。为了确保所有数据都在我们的 x 轴域中,我们将创建一个新数组,只包含该区域中的点:
**// Trim data points to only be in range of x-axis**let data_in_range = [];
data.forEach(function (e) {
if (e[0] >= xMin && e[0] <= xMax) {
data_in_range.push(e);
}
});
创建初始 SVG
我们现在可以创建 SVG 对象了——在 D3 中,这通常是由函数d3.select()
和d3.selectAll()
驱动的。我们将选择我们之前用id=plot_area
创建的div
,并用append('svg')
为它附加一个 SVG。然后我们可以用attr()
编辑 SVG 的属性,并传递我们已经定义的高度和宽度值。
**// Append an svg to the plot_area div**const svg = d3.select('#plot_area')
.append('svg')
.attr('width', w)
.attr('height', h);
绘制我们的第一行
为了绘制第一条线(样品 1 的吸光度),我们将在 SVG 中添加一个path
对象。然后,我们可以使用datum()
函数将相关数据绑定到该行。然后我们可以像上面一样编辑属性,但是要实际创建行,我们需要编辑d
属性并使用d3.line()
。我们将为 x 和 y 数据传递回调函数,其中我们将数据点与我们之前创建的xScale
和yScale
函数结合使用。
**// Append path object for sample 1**svg.append('path')
.datum(data_in_range)
.attr('stroke', 'black')
.attr('stroke-width', 2)
.attr('fill', 'none')
.attr('d', d3.line()
.x((d) => xScale(d[0]))
.y((d) => yScale(d[1])));
如果一切顺利,我们现在可以在浏览器中打开index.html
文件,应该会看到以下内容:
婴儿学步!现在让我们添加第二行,就像我们添加第一行一样:
**// Append path object for sample 2**svg.append('path')
.datum(data_in_range)
.attr('stroke', 'steelblue')
.attr('stroke-width', 2)
.attr('fill', 'none')
.attr('d', d3.line()
.x((d) => xScale(d[0]))
.y((d) => yScale(d[2])));
我们已经成功地绘制了两条线,但是没有任何轴,没有数据的上下文,所以让我们添加这些。
给我们的剧情添加了坐标轴
为了创建 x 轴,我们使用d3.axisBottom()
,为了创建 y 轴,我们使用d3.axisLeft()
。然而,这两种方法都是让 x 和 y 轴指向正确的方向(x 轴在 SVG 的顶部,y 轴在 SVG 的左侧)——我们仍然需要将它们转换到 SVG 中它们各自的部分,这可以通过transform
属性来实现:
**// Add x-axis**svg.append('g')
.style('font-size', '12px')
.attr('transform', 'translate(0,' + (h - padding) + ')')
.call(d3.axisBottom(xScale));**// Add y-axis**svg.append('g')
.style('font-size', '12px')
.attr('transform', 'translate(' + padding + ',0)')
.call(d3.axisLeft(yScale));
添加轴标签
D3 没有直接创建轴标签的方法,所以我们通过添加text
对象来实现。我们可以使用已经存储的高度和宽度变量轻松定位标签。在下面的代码块中,我将标签从 SVG 的边缘偏移 15 个像素。同样,但是将text-anchor
属性设置为middle
,我们可以将文本对象在轴上居中。
对于 y 轴标签,我们必须做一个额外的步骤。当旋转一个文本对象时,默认情况下它会围绕原点旋转,即使我们手动设置了x
和y
属性。为了解决这个问题,我们将在旋转的同时使用平移来重置旋转点,并旋转-90 度。
**// Add x-axis label**svg.append('text')
.attr('x', w/2)
.attr('y', h - 15)
.attr('text-anchor', 'middle')
.style('font-family', 'sans-serif')
.text('Wavelength (nm)');**// Add y-axis label**svg.append('text')
.attr('text-anchor', 'middle')
.attr('transform', 'translate(15,' + h/2 + ')rotate(-90)')
.style('font-family', 'sans-serif')
.text('Absorbance (O.D.)');
添加图例
再说一次,就创造一个传奇而言,我们几乎只能靠自己了。我们的行动方针如下:
- 通过创建两点数据集为图例创建线
- 为每个图例标签追加文本对象
在我们的图中,图例从[750,800]开始,样本 1 的 y 值为 1.9,样本 2 的 y 值为 1.7。
**// Add legend**svg.append('path')
.datum([[750, 1.9], [800, 1.9]])
.attr('stroke', 'black')
.attr('stroke-width', 2)
.attr('d', d3.line()
.x((d) => xScale(d[0]))
.y((d) => yScale(d[1])));svg.append('path')
.datum([[750, 1.7], [800, 1.7]])
.attr('stroke', 'steelblue')
.attr('stroke-width', 2)
.attr('d', d3.line()
.x((d) => xScale(d[0]))
.y((d) => yScale(d[1])));
现在,我们可以在每条新绘制的线旁边附加文本标签:
svg.append('text')
.attr('x', xScale(805))
.attr('y', yScale(1.9))
.attr('alignment-baseline', 'central')
.style('font-family', 'sans-serif')
.style('font-size', '16px')
.text('Sample 1');svg.append('text')
.attr('x', xScale(805))
.attr('y', yScale(1.7))
.attr('alignment-baseline', 'central')
.style('font-family', 'sans-serif')
.style('font-size', '16px')
.text('Sample 2');
让我们的剧情动态化
现在,如果你认为我们费了很大的劲才做出你在上面看到的这个简单的图,你可能是对的。用 R 或 Python 等其他编程软件制作静态图形非常简单。但是让我们的剧情动态化才是 D3 真正展示威力的地方。首先,让我们创建一个小动画,其中两条线都从 0 开始,然后在一秒钟内上升到它们的值。
为此,我们将返回并编辑路径的初始绘图。我们使用transition()
方法和duration()
来改变我们的属性,在本例中是d
属性。我们最初将所有的 y 值设置为 0,然后将它们更改为各自的值。
**// Append path object for sample 1**svg.append('path')
.datum(data_in_range)
.attr('stroke', 'black')
.attr('stroke-width', 2)
.attr('fill', 'none')
.attr('d', d3.line()
.x((d) => xScale(d[0]))
.y(yScale(0)))
.transition()
.duration(1000)
.attr('d', d3.line()
.x((d) => xScale(d[0]))
.y((d) => yScale(d[1])));**// Append path object for sample 2**svg.append('path')
.datum(data_in_range)
.attr('stroke', 'steelblue')
.attr('stroke-width', 2)
.attr('fill', 'none')
.attr('d', d3.line()
.x((d) => xScale(d[0]))
.y(yScale(0)))
.transition()
.duration(1000)
.attr('d', d3.line()
.x((d) => xScale(d[0]))
.y((d) => yScale(d[2])));
现在,让我们制作它,这样我们就可以用鼠标悬停在实际的数据点上,并获得它们的确切值。为此,我将为每个数据点添加圆圈。在这种情况下,我们将使用d3.selectAll()
来选择我们将要创建的圆形对象。我们使用data()
而不是datum()
来绑定数据,因为我们正在为每个数据点创建一个新的圆,而不是像上面那样使用所有的数据点来创建一个路径。
对于每个圆,我们分别为圆的 x 和 y 坐标编辑属性cx
和cy
,为圆的半径编辑属性r
。我们将填充颜色设置为与线条相同。然后每个圆被赋予一个类值points
,我们将使用 CSS 来修改它。最后,我们将pointer-events
设置为all
,这样如果圆圈的任何部分进入鼠标指针,我们就可以触发一个事件。
为了创建工具提示,我们将title
对象附加到每个圆上,并将它们的text
属性设置为对应于该点的数据。所以当我们悬停在其中一个点上时,我们会看到数据。
**// Append circles for hovering points for sample 1**svg.selectAll('circle_samp_1')
.data(data_in_range)
.enter()
.append('circle')
.attr('cx', (d) => xScale(d[0]))
.attr('cy', (d) => yScale(d[1]))
.attr('r', 4)
.attr('fill', 'black')
.attr('class', 'points')
.style('pointer-events', 'all')
.append('title')
.text(function (d) {
return (
'Wavelength: ' + d[0] + ' nm' + '\n' + 'Absorbance: ' + d[1]
);
});**// Append circles for hovering for sample 2**svg.selectAll('circle_samp_2')
.data(data_in_range)
.enter()
.append('circle')
.attr('cx', (d) => xScale(d[0]))
.attr('cy', (d) => yScale(d[2]))
.attr('r', 4)
.attr('fill', 'steelblue')
.attr('class', 'points')
.style('pointer-events', 'all')
.append('title')
.text(function (d) {
return (
'Wavelength: ' + d[0] + ' nm' + '\n' + 'Absorbance: ' + d[2]
);
});
编辑 CSS 将所有内容整合在一起
我希望我们在上面画的圆圈是看不见的,直到我们悬停在它们上面。我们可以通过编辑visibility
属性在 CSS 中非常简单地做到这一点。我们从隐藏它们开始,然后在触发points:hover
事件时使它们可见。
**/* Make points visible on hover */** *.points* {
visibility: hidden;
}.points:hover {
visibility: visible;
}
现在你知道了!用 D3 制作的吸光度图的交互式版本!
结束语
感谢您的阅读!本文的所有分析都可以在这个 Github 资源库中找到。我很感激任何反馈,你可以在 Twitter 上找到我,并在 LinkedIn 上与我联系以获得更多更新和文章。
用 Python 制作科学出版物图简介
Python 科学绘图
介绍如何使用 Python 为科学出版物绘制数据
照片由 Isaac Smith 在 Unsplash 上拍摄
几年来,我一直在使用 Python 进行科学计算,绘制我所有的图。我的主要动机是:( 1) Python 是开源的,以及(MATLAB 占用的硬盘空间(尤其是在我的笔记本电脑上,那里的硬盘空间非常宝贵)。此外,永远不必担心保持软件许可证最新也是一个额外的优势。在为我的情节找到“完美”的参数之前,我不得不进行一连串的试错和谷歌搜索,这促使我撰写了这篇文章——既是为外部读者提供信息的工具,也是我为自己记录事情的一种方式。审美是主观的,但我希望本教程可以指出重要的设置和参数,允许您根据个人喜好定制任何数据集。这是我希望成为的系列教程的第一篇文章——我将继续为不同类型的可视化添加章节。如果你还没有 Anaconda 的话,我建议你安装它,因为它包含了所有你需要的数据分析和可视化所需的包。
导入包
使用的大多数函数在matplotlib
包中(大多数绘图函数来自matplotlib.pyplot
子包)。此外,我通常导入numpy
用于快速计算,导入pylab
用于从任何内置色图中快速生成颜色。很多时候,当我们导入包时,我们会创建一个简短的别名(即 mpl
代表matplotlib
),这样我们就可以使用别名来引用它的函数,而不是每次都键入matplotlib
。
# Import required packages
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
from pylab import cm
加载数据
由于科学仪器数据通常相当简单(通常只有一个我们控制的自变量和一个测量的因变量),我们可以使用numpy.loadtxt
来导入我们的数据。对于更复杂的数据集,我强烈推荐使用pandas
,它有非常复杂的功能来加载和净化可视化数据——在这里可以找到全面的文档。对于这个例子,我有一个名为Absorbance_Data.csv
的文件,其中有一些在分光光度计上收集的两个样品的吸光度数据。
两个样品吸光度数据的 CSV 文件
我们可以使用以下命令将这些数据加载到我们的脚本中:
# Use numpy.loadtxt to import our datawavelength, samp_1_abs, samp_2_abs = np.loadtxt('Absorbance_Data.csv', unpack=True, delimiter=',', skiprows=1)
我们将文件和参数一起传递给numpy.loadtxt
函数:
unpack
—将每一列转置到一个数组中,允许您一次解包多个变量(wavelength
、samp_1_abs
、samp_2_abs
)
delimiter
—用于分隔列的分隔符
skiprows
—在文件顶部跳过的行数(因为第一行是列标题,所以我们想跳过它skiprows=1
)
绘制我们的数据
加载吸光度数据后,我们可以使用以下代码快速绘制和检查两个数据集:
# Create figure and add axes object
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1])# Plot and show our data
ax.plot(wavelength, samp_1_abs)
ax.plot(wavelength, samp_2_abs)
plt.show()
使用默认 matplotlib 设置绘制吸光度数据的初始图
数据绘制正确,但默认的matplotlib
设置不能给出出版质量的数字。当我们改变下面的一些参数时,我们会得到一个更好看的图。
字体
这是一个我花费了大量时间的场景——为我的情节选择合适的字体。您的系统已经预装了一长串字体,您可以通过以下方式检查哪些字体已经可供matplotlib
使用:
import matplotlib.font_manager as fm# Collect all the font names available to matplotlib
font_names = [f.name for f in fm.fontManager.ttflist]
print(font_names)
如果你想安装一个新的字体到你的电脑,然后用它来绘图,这也是可能的。首先,你必须下载并安装你想要的字体——在这里你可以找到很多选项。安装后,您必须重新构建字体缓存,以便在您制作图形时可用于matplotlib
。我们的做法如下:
import matplotlib.font_manager as fm# Rebuild the matplotlib font cache
fm._rebuild()
如果您现在检查可用字体列表,您应该会看到刚刚安装的新字体。
通用绘图参数
我在绘图脚本开始时设置的三个常规参数是:(1)字体,(2)字体大小,和(3)轴线宽度。这些基本上是全局参数,我以后不会编辑它们,所以在开始时设置它们会使一切变得更容易(即不必为每个标签明确设置字体/大小)。在生成任何图形之前,我们必须添加以下代码,所以我通常在导入包之后立即将它放在脚本的顶部。
# Edit the font, font size, and axes widthmpl.rcParams['font.family'] = 'Avenir'
plt.rcParams['font.size'] = 18
plt.rcParams['axes.linewidth'] = 2
生成一组颜色
如果你有一套你喜欢用的颜色,你可以跳过这一步。在这种情况下,由于我们只有两个样本,最好手动选择两种高对比度的颜色。但是,如果你想生成一个颜色列表而不需要太多的努力,我们可以使用我们导入的pylab
包从各种matplotlib
内置颜色图中生成一个颜色列表,可以在这里找到。当您需要大量颜色时,这变得非常有用,因为您可以通过编程生成它们。
对于我们的数据集,我们只对轻松区分我们的轨迹感兴趣,因此我们最好使用定性部分中的一个颜色图(在本例中,我将使用“tab10”)。我们使用以下代码—第一个参数是色彩映射表名称,第二个参数是我们想要生成的颜色数量:
# Generate 2 colors from the 'tab10' colormap
colors = cm.get_cmap('tab10', 2)
例如,如果我们要测量单个样品的温度依赖性,并想要绘制不同温度下的光谱,我们可以使用发散色图,如“coolwarm”。最终,您选择的色彩映射表将由您决定,并基于您正在绘制的数据类型。
创建图形和轴
我们必须创建一个图形,它是一个空白窗口,然后为我们的绘图添加一个 axes 对象。为了生成该图,我们有以下内容:
# Create figure object and store it in a variable called 'fig'
fig = plt.figure(figsize=(3, 3))
figsize
—我们的图形尺寸(宽度、高度),以英寸为单位,默认为(6.4,4.8)
现在,我们必须通过指定左下角坐标和相对坐标中的宽度和高度(1 是图形窗口的全尺寸)来将 axes 对象添加到空白图形中。如果我们希望它填充整个图形,我们可以指定[0, 0, 1, 1]
,它将左下角设置为(0,0),宽度和高度设置为 1。
# Add axes object to our figure that takes up entire figure
ax = fig.add_axes([0, 0, 1, 1])
轴在(0,0)处的空白图形,宽度和高度为 1
我们可以使用这种轴结构,通过制作多个轴对象来创建嵌板图形和插图,如下所示:
# Add two axes objects to create a paneled figure
ax1 = fig.add_axes([0, 0, 1, 0.4])
ax2 = fig.add_axes([0, 0.6, 1, 0.4])
带有两个面板轴的空白图形,一个位于(0,0),另一个位于(0,0.6),宽度为 1,高度为 0.4
# Add two axes objects to create an inset figure
ax1 = fig.add_axes([0, 0, 1, 1])
ax2 = fig.add_axes([0.5, 0.5, 0.4, 0.4])
带插图的空白图形-第一个轴占据整个图形,第二个轴在(0.5,0.5)处,宽度和高度为 0.4
去除棘刺
如果我们不希望我们的地块完全封闭,我们可以删除顶部和右侧的脊柱如下:
# Hide the top and right spines of the axis
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
去掉顶部和右侧脊线的空白图形和轴
勾选参数
我们可以用下面的代码编辑记号的宽度和长度,以匹配我们的轴参数。如果我们有次要分笔成交点,我们还可以编辑这些属性:
# Edit the major and minor ticks of the x and y axesax.xaxis.set_tick_params(which='major', size=10, width=2, direction='in', top='on')ax.xaxis.set_tick_params(which='minor', size=7, width=2, direction='in', top='on')ax.yaxis.set_tick_params(which='major', size=10, width=2, direction='in', right='on')ax.yaxis.set_tick_params(which='minor', size=7, width=2, direction='in', right='on')
which
—我们是在编辑major
、minor
还是both
分笔成交点
size
—以点为单位的刻度长度
width
—刻度的线宽(我们可以将其设置为与我们的轴线宽相同)
direction
—分笔成交点是面向in
、out
还是inout
(两者)
top
/ right
—次轴(上/右)上是否有刻度
带有更新记号参数的空白图形和轴
绘制和调整范围/刻度
我们现在可以再次绘制我们的数据,使用我们从色图生成的颜色来区分样本:
# Plot the two sample absorbances, using previously generated colorsax.plot(wavelength, samp_1_abs, linewidth=2, color=colors(0), label='Sample 1')ax.plot(wavelength, samp_2_abs, linewidth=2, color=colors(1), label='Sample 2')
linewidth
—图中线的线宽
color
—图中线条的颜色
label
—跟踪标签(图例参考)
现在,我们可以使用以下代码行设置 x 和 y 轴范围:
# Set the axis limits
ax.set_xlim(370, 930)
ax.set_ylim(-0.2, 2.2)
使用生成的颜色和手动设置的轴限值绘制样品吸光度图
我们注意到两个轴之间的刻度线似乎不平衡——我们也可以使用一个名为MultipleLocator
的函数进行半自动编辑,该函数将在我们提供的基数的每个倍数处创建刻度线。我们必须编辑主要刻度的major_locator
和次要刻度的minor_locator
。我们将为 x 轴设置每 100 纳米的主刻度,每 50 纳米的次刻度,为 y 轴设置每 0.5 纳米的主刻度和每 0.25 纳米的次刻度。
# Edit the major and minor tick locationsax.xaxis.set_major_locator(mpl.ticker.MultipleLocator(100))
ax.xaxis.set_minor_locator(mpl.ticker.MultipleLocator(50))
ax.yaxis.set_major_locator(mpl.ticker.MultipleLocator(0.5))
ax.yaxis.set_minor_locator(mpl.ticker.MultipleLocator(0.25))
手动调整刻度间隔后的先前吸光度图
轴标签
我们必须向 x 轴和 y 轴添加标签,这可以通过下面的代码轻松完成:
# Add the x and y-axis labelsax.set_xlabel('Wavelength (nm)', labelpad=10)
ax.set_ylabel('Absorbance (O.D.)', labelpad=10)
labelpad
—刻度标签和轴标签之间的额外填充
添加了轴标签的吸光度图
如果您想在标签中包含希腊字符,可以使用 LaTeX 语法来实现。我们通过在字符串前面加上r
并在 LaTeX 命令后面加上$$
来创建一个原始字符串。然而,这将为希腊字符使用默认的 LaTeX 字体——如果我们想使用相同的字体作为情节的其余部分(假设字符存在),我们用$\mathregular{'Command goes here'}$
括起我们的命令。
# Add the x-axis label with λ for wavelengthax.set_xlabel(r'$\mathregular{\lambda}$ (nm)', labelpad=10)
使用 LaTeX 排版波长λ,用 x 轴标记的吸光度图
副轴刻度
如果我们想要在其中一个辅助(顶部/右侧)轴上放置刻度,以显示不同的数据集或缩放比例,我们可以使用寄生轴来实现。此轴对象复制原始绘图的一个轴,允许您更改另一个轴的缩放比例。为了说明这一点,我们可以使用我们的吸光度数据作为例子。当前的 x 轴是吸收光的波长,但是基于应用,该光的能量可能是更相关的参数。
我们可以在图的顶部创建第二个 x 轴来显示能量比例。首先,我们必须用twinx()
或twiny()
命令创建一个寄生轴,分别克隆 x 轴或 y 轴。在本例中,我们想要 y 轴数据常量,因此我们将克隆 y 轴。我们还需要将这个新 x 轴的刻度参数与旧图的 x 轴匹配(并从原始 x 轴参数中删除top='on'
)。
# Create new axes object by cloning the y-axis of the first plot
ax2 = ax.twiny()# Edit the tick parameters of the new x-axis
ax2.xaxis.set_tick_params(which='major', size=10, width=2, direction='in')ax2.xaxis.set_tick_params(which='minor', size=7, width=2, direction='in')
为了使我们向该轴添加以能量单位表示的记号的工作更容易,我们可以编写一个函数来将能量转换为波长(因为我们将把记号放在波长轴上能量值对应的点上)。我们将输入 E 视为一个数组,这样我们就可以一次完成所有转换:
# Function to convert energy (eV) to wavelength (nm)
def E_to_WL(E):
return [1240/i for i in E]
由于这是一个非线性转换,我们不能轻易使用MultipleLocator
函数,我们将使用一个名为FixedLocator
的函数手动添加刻度线。为了使用FixedLocator
,我们提供了一个我们希望有刻度线的所有位置的数组:
# Add ticks manually to energy axisax2.xaxis.set_major_locator(mpl.ticker.FixedLocator(E_to_WL(np.linspace(1.5, 3.0, 4))))ax2.xaxis.set_minor_locator(mpl.ticker.FixedLocator(E_to_WL(np.linspace(1.4, 3.2, 19))))
因为我们手动添加了记号,所以我们也必须手动添加主要记号标签。
# Add tick labels manually to energy axisax2.set_xticklabels(['1.5', '2.0', '2.5', '3.0'])
最后,我们还想为新的 x 轴添加一个轴标签,并确保轴限制与原始的 x 轴相同:
# Add energy axis label
ax2.set_xlabel('Energy (eV)', labelpad=10)# Set energy axis limits
ax2.set_xlim(370, 930)
具有二次非线性能量 x 轴的吸光度图
添加图例
我们必须在我们的图中添加的最后一件事是一个图例,以便读者可以知道哪个轨迹对应于哪个样本。为此,我们可以使用以下代码:
# Add legend to plot
ax.legend(bbox_to_anchor=(1, 1), loc=1, frameon=False, fontsize=16)
bbox_to_anchor
—图例边界框的坐标
loc
—使用bbox_to_anchor
值的坐标的边界框的哪一部分(0
为自动,1
为右上角,2
为左上角,3
为左下角,4
为右下角,5
为右侧,6
为中间偏左,7
为中间偏右,8
为中间偏下,9
为中间偏上,10
为中间偏上)
frameon
—是否在图例周围画一个框
fontsize
—图例条目的字体大小(如果不同于通用参数)
最终吸光度图,带有波长和能量 x 轴,以及图例
保存你的剧情
最后,保存你的最终图非常简单——我们可以使用函数plt.savefig
来完成。
# Save figure
plt.savefig('Final_Plot.png', dpi=300, transparent=False, bbox_inches='tight')
dpi
—光栅图像文件格式的分辨率(在这种情况下,我们保存为.png
文件,这意味着我们以每英寸 300 点的分辨率保存。您可以保存的其他可能的文件格式有.ps
、.pdf
和.svg
,它们都是矢量图形格式,在这种情况下,您不需要指定一个dpi
值)
transparent
—是使图形透明,还是带有白色背景
bbox_inches
-定义图形周围的边界框(tight
确保图形周围没有多余的空白)
要在图形窗口中实际查看我们的最终绘图,我们必须在保存图形后添加一个plt.show()
命令。
# Show figure
plt.show()
结论
就是这样!我们已经成功地使用 Python 制作了出版物质量图!这个例子和所有后续的例子都可以在这个 Github 库上免费获得。
感谢您的阅读,我将继续用新的例子和教程来更新这个系列!你可以在 Twitter 上关注我,或者在 T2 的 LinkedIn 上联系我,获取更多文章和更新。
神经网络入门,用 Python 从头开始实现
神经网络初学者指南,以及如何在没有任何框架的情况下使用 Python 从头开始实现一个神经网络
深度学习是人工智能在过去二十年中蓬勃发展的领域之一。它是机器学习的一个子集,以一种受人脑工作启发的方式处理复杂模式的学习。
深度学习在最近一段时间激增的原因是它能够扩展到大型数据集。众所周知,机器学习模型的性能会在一定数量的数据和额外的数据没有区别后饱和,但我们不希望这样。随着大数据的出现,如今产生了大量可用的数据集,我们希望我们的算法能够在越来越多的数据下保持更好的性能。这就是深度学习发挥作用的地方。
通常,深度学习与术语“人工神经网络”或简单的神经网络互换使用。这是因为深度学习本质上是由从这个模型的许多变体中派生出来的模型组成的。但你想到的第一个问题可能是这样的:“为什么我们需要深度学习模型,它们与机器学习模型有什么不同?”先来回答这个问题。
为什么是神经网络?
我假设你们都熟悉回归,或者至少知道它是什么。如果你不知道它是什么,让我简单总结一下。基本上,回归意味着“将一组输入数据映射到一个连续的输出形式”。让我们看看机器学习中最流行的回归形式:线性回归。
工资和工作经验的数据集
看看上面的数据集,它基本上有一个输入特征:员工的工作年限和一个输出特征:薪水。现在假设你得到了一个新员工的多年经验数据,并被告知去预测他的工资会是多少。你的大脑会怎么做?你会试图在这些数据中找到一个合理的模式,并据此进行预测。对于本例,近似值可能采用直线形式,如下所示:
最佳拟合线
似乎是一个很好的近似,对不对?这就是线性回归,找到最符合数据的直线。但是这是不是看起来不现实?现实世界中的数据很少具有如此简单的模式,以至于您可以用一条直线来拟合它,您可能需要二次、三次、双二次函数来更好地逼近数据(称为多项式回归)。但是,如何最好地确定应该为该任务使用什么样的次数多项式呢?为什么只有多项式,难道不应该也考虑其他函数吗?此外,真实世界的数据集远不止 2 个变量,它们可能有数百个变量,在这种情况下,可视化和获得关于数据的直觉几乎是不可能的。最终的问题是这样的:“我们能有一个模型,自动归纳并选择最适合我们的数据,就像人脑一样,不受可用函数类型的限制吗?”答案是神经网络。
什么是神经网络?
简化的神经网络
让我们以一个简化的回归问题为例,其中我们必须基于 3 个输入特征来预测房价 y:平方 feet(X₁的大小、bedrooms(X₂的数量以及与城市 hub(X₃).的距离让我们使用如上所示的简单神经网络,而不是应用回归模型。神经网络的特征如下
- 有一个神经元层的集合(每个神经元保存一个称为该神经元激活的值)。总共有 3 层,因为输入层没有计算在内。
- 有一个由 3 个神经元组成的输入层,每个神经元保存输入变量,还有一个输出层保存预测的房价。
- 中间有两层,每层两个神经元。这些被称为隐藏层,因为它们仅用于计算目的,我们不关心它们在运行时的值。
- 第一隐藏层中的第一神经元的激活(或值)是 A₁,第二神经元是 A₂,第二隐藏层的第一神经元是 B₁,第二隐藏层的第二神经元是 B₂.
- 每个神经元通过称为权重(W)和偏差(b)的数字连接到前一层的所有神经元。权重以形状矩阵的形式组织(当前层中的单元数,前一层中的单元数)。这基本上意味着 Wᵢⱼ指的是从当前层的第 I 个神经元到前一层的第 j 个神经元的连接的权重。偏置以(当前层中的单元数,1)的形状组织,因此 Bᵢ对应于当前层中第 I 个神经元的偏置。因此,W₁的形状为(2,3),,b₁的形状为(2,1),W₂的形状为(2,2),以此类推。
- A₁的计算如下
f(x₁,x₂,x₃)=w₁₁x₁+w₁₂x₂+w₁₃*x₃+b₁₁
然后通过激活函数 g(x)。因此,
a₁=g(f(x₁,x₂,x₃))=g(w₁₁x₁+w₁₂x₂+w₁₃*x₃+b₁₁)
这里 f 是线性函数,g 是非线性函数,因此请注意,神经元正在逼近或计算的函数是非线性函数。激活函数的用途是,首先,它将非线性引入模型,其次,它将结果转换成更易解释的形式。让我们看几个激活函数。
Sigmoid 激活函数
这是 sigmoid 激活函数,给出为 g(x) = 1/(1 + e^-x)。它是一个单调函数,当 x 变得非常负时接近 0,当 x 非常正时接近 1,当 x=0 时接近 0.5。它将输入压缩到范围(0,1)内,由于这种简单的可解释性,它在很多年前被广泛使用。但是,由于饱和区域较大(梯度较小),训练速度较慢,因此被 relu 函数(即校正线性单元)取代。
ReLU 激活功能
ReLU 定义为 g(x) = max(0,x)。x 为负时为 0,为正时等于 x。由于其较低的饱和区域,它是高度可训练的,并且比 sigmoid 更快地降低成本函数。
总的来说,我们的神经网络采用一个输入层,并通过计算计算所有隐藏层中神经元的值,并产生与输出神经元的值相对应的最终输出,这就是房价的预测。直观上,我们可以说 A₁和 A₂是输入的一些非线性函数,B₁和 B₂是 A₁和 A₂本身的非线性函数,输出层将它们组合在一起进行预测。有趣的是,层次越深,它们计算的函数就越复杂。您可以将 A₁和 A₂视为计算要素,例如居住空间和污染水平,而 B₁和 B₂可能代表生活水平和房屋的位置质量。最终的价格预测将 B₁和 B₂考虑在内。这是一个简化的神经网络,真实模型的每一层都有数百个这样的单元,从 3 层到 100 层不等。
让我们从建立第一个神经网络开始。我们将使用流行的波士顿房价数据集。它由 13 个输入变量和 1 个目标(或输出)变量组成。我们使用 ReLU 激活。第一项工作是定义 relu 函数并初始化我们网络的参数。
神经网络中的学习
我们将使用回归的标准均方损失函数-
J(W,B) = 1/(2*m) * ∑ (Y_pred - Y_true)并使用梯度下降将其最小化。但是我们如何计算梯度呢?我们做了以下事情-
- 从输入层开始。用 b₁w₁来计算 a₂.a₁使用等式- A = W₁.X + b₁.在这里,W₁的形状是(2,3),而 x 的形状是(3,1),所以它们将相乘产生一个形状为(2,1)的矩阵,并与相同形状的 b₁相加。这是第一个隐藏层的激活。对所有图层重复相同的过程,直到得到 Y_pred。请注意,这里您没有将激活应用到输出层,因为我们不会使用它来计算任何其他神经元值。此外,您必须对训练集中的所有 m 个示例执行此过程。哦,顺便说一下,这个过程被称为前向传播,因为你是从先前的神经元计算神经元的激活,直到你得到输出。矢量化的方程如下-
正向传播方程
在所有情况下,我们都假设训练样本按列堆叠,行表示神经元的激活。
2.使用训练集中的 Y_pred 值和 Y_true 值计算上述 MSE 损失。
3.现在,让我们找到成本相对于每个权重和偏差的梯度,以便我们可以相应地调整它们,从而改变 Y_pred 并最小化成本。这是使用反向传播算法完成的,因为我们没有明确的给定函数来区分我们自己。从名字就可以看出,它是前进道具的反义词。这里,我们使用下一层的权重和偏差的梯度来找出上一层的权重和偏差的梯度。这部分有点数学化,如果你对微积分没有理解,可以跳到下一步。
我们的神经网络
这里是我们之前的神经网络,让我们从输出层开始。在这里,我将只考虑从单个训练示例计算的梯度,以避免事情进一步复杂化。那里有 Y_pred。我们的成本取决于 Y_pred 吗?是的,它是。怎么会?梯度 w.r.t Y_pred 会告诉我们。*∂j/∂y_pred = 1/m (y _ pred—y _ true)。现在我们要找出 j 是如何依赖 W₃和 B₃.的我们知道 ∂J/∂Y_pred ,那么我们可以用它来找到 ∂J/∂W₃ 和 ∂J/∂b₃ 吗?是的,我们可以!还记得链式法则吗?
∂j/∂w₃*= ∂j/∂y_pred * ∂y_pred/∂w*₃
我们知道第一项。让我们找到第二个。W₃有两个组成部分/值= w₁₁和 w₁₂.假设我们想求出 w₁₁.的梯度 Y_pred 是怎么依赖的?还记得函数-f(x₁,x₂,x₃)=w₁₁x₁+w₁₂x₂+w₁₃x₃+b₁₁换 A₁吗?同样,对于最后一层,Y_pred = w₁₁B₁ + w₁₂*B₂ + b₁₁.对这个 w . r . t .w₁₁求微分得到 B₁,然后我们把它乘以 ∂J/∂Y_pred ,这就是梯度 wrt Y_pred。同样,对于 w₁₂,我们乘以 B₂(区分并查看)。通常,特定 wᵢⱼ的梯度是当前层(它所连接的层)中第 I 个神经元的梯度和前一层(它所连接的层)的第 j 个神经元的激活的乘积。这不是很美吗?对于偏差 b₁₁, ∂Y_pred/∂b₁₁ 为 1,因此其梯度等于 Y_pred 的梯度。现在我们将利用这些梯度来计算前一层激活的梯度,根据-
∂j/∂b = ∂j/∂y_pred * ∂y_pred/∂b
我们来计算第二项。b 有两个组成部分 B₁和 B₂.让我们找出 B₁.的梯度再看最后一层的函数——y _ pred =w₁₁b₁+w₁₂b₂+b₁₁.区分 B₁和 w₁₁.因此,∂j/∂b₁*=w₁₁ y _ pred 的梯度和∂j/∂b₂=w₁₂* y _ pred 的梯度。现在,我们准备计算∂j/∂w₂和∂j/∂b ₂.就像上次一样,从 W ₂中抓取每个权重,并将其设置为(它所连接的神经元的激活)*(它所连接的神经元的梯度)。对于 b ₂中的每个偏置,将其设置为等于其被添加到的相应神经元的梯度。让我们计算一下 ∂J/∂A -
∂J/∂A = ∂J/∂B * ∂B/∂A
这和我们之前做的有点不同,上次当我们计算 ∂Y_pred/∂B, Y_pred 只有一个神经元,但这里 b 本身有两个神经元。那么 ∂J/∂B 在这里是什么意思呢?嗯,这是一个矩阵本身有两个组成部分- ∂J/∂B ₁和 ∂J/∂B ₂.我们将使用它们来确定梯度 w . r . t . a(a₁和 A₂ -这将是∂J/∂A 的两个组成部分)。现在正确地看神经元 A₁,改变它会导致 B₁和 B₂都改变,所以为了计算 A₁的总梯度,我们可以把这些变化加在一起。所以我们写-∂j/∂a₁*=(∂j/∂b₁**∂b₁*/∂a₁)+(∂j/∂b∂b/∂a)。形式上,这样做是因为微分运算是线性的(换句话说,变化是相加的)。我们知道∂j/∂b₁和∂j/∂b ₂.∂b₁/∂a₁=w₁₁g’(w₁₁a₁+w₁₂a₂+b₁₁)和∂b‖/∂a‖=w‖* g '(w‖* a‖+ w‖* a‖+ b)这个术语是从哪里来的?你猜对了,B 是由 A 上的一个线性函数 f 后接一个激活函数 g 导出的,所以我们先区分 g 后接 f(又是链式法则)。最后,我们计算∂j/∂w₁和∂j/∂b₁,我们就完成了。我们不需要关于输入层神经元的梯度,因为我们不会改变它们,它们是来自我们训练集的特征。如果你都做到了,那么恭喜你。你已经成功地掌握了这个题目最难的部分。这是矢量化的方程-
反向传播方程
关于我所用的符号,有些词指的是逐元素乘法,而。指矩阵乘法。此外,求和符号对各列求和。我将把它作为一项任务留给您,让您来弄清楚它与我上面讨论的计算有什么关系。
4.现在,我们已经费力地计算了成本相对于参数的梯度,我们将执行梯度下降并更新参数,从而降低成本,并再次执行正向传播并保持迭代,直到达到收敛。如果想详细了解梯度下降和优化,可以参考我的博客:https://towardsdatascience . com/understanding-gradient-descent-and-Adam-optimization-472 AE 8 a 78 c 10
将这一切结合在一起
让我们再构建 3 个函数:model()训练整个神经网络,compute_accuracy()使用均方根误差度量计算精确度,predict()对数据样本进行预测。
现在剩下的就是将数据加载到训练集和测试集中,设置隐藏层的大小(输入层和输出层的大小固定为 13 和 1,我们无法更改),设置学习率和迭代次数,最后训练模型并找到精确度。顺便说一下,隐藏层的大小、学习速率、迭代次数——这些也统称为超参数,因为它们控制模型的学习,但不被模型学习。相反,我们必须分别调整它们以获得最佳性能。激活函数的类型,隐藏层的数量也是模型的超参数,还有很多,但超出了这个博客的范围。完整的代码如下-
我们在训练和测试数据上的 RMSE 分数分别为 8 和 8.5 左右,考虑到我们设计的神经网络模型是如此简单,这已经算不错了。尝试自己看看是否可以通过调整超参数获得更好的结果。您可以尝试通过改变优化器、使用批量梯度下降、引入正则化或使用 Tensorflow 和 Keras 等框架来创建更好的模型,但我的朋友们,这是另一个故事了!
九种基本机器学习算法简介
最流行的机器学习模型的直观解释。
如果这是你喜欢的那种东西,成为第一批订阅 我的新 YouTube 频道在这里 !虽然还没有任何视频,但我会以视频的形式分享很多像这样的精彩内容。感谢大家的支持:)
在我之前的文章中,我解释了什么是回归,并展示了如何在应用程序中使用它。本周,我将回顾实践中使用的大多数常见机器学习模型,以便我可以花更多的时间来建立和改进模型,而不是解释其背后的理论。让我们深入研究一下。
机器学习模型的基本分段
所有的机器学习模型被分类为监督的或非监督的。如果模型是监督模型,那么它被细分为回归或分类模型。我们将讨论这些术语的含义以及下面每个类别中对应的模型。
监督学习
监督学习涉及学习基于示例输入-输出对将输入映射到输出的函数[1]。
例如,如果我有一个包含两个变量的数据集,年龄(输入)和身高(输出),我可以实现一个监督学习模型,根据年龄预测一个人的身高。
监督学习的例子
重复一下,在监督学习中,有两个子类别:回归和分类。
回归
在回归模型中,输出是连续的。下面是一些最常见的回归模型。
线性回归
线性回归的例子
线性回归的概念就是找到一条最符合数据的直线。线性回归的扩展包括多元线性回归(例如,找到最佳拟合的平面)和多项式回归(例如,找到最佳拟合的曲线)。你可以在我的上一篇文章中了解更多关于线性回归的知识。
决策图表
图片来自 Kaggle
决策树是一种流行的模型,用于运筹学、战略规划和机器学习。上面的每个方块被称为一个节点,节点越多,你的决策树就越精确(一般来说)。决策树中做出决策的最后节点被称为树的叶。决策树直观且易于构建,但在准确性方面有所欠缺。
随机森林
随机森林是一种基于决策树的集成学习技术。随机森林包括使用原始数据的自举数据集创建多个决策树,并在决策树的每一步随机选择变量的子集。然后,该模型选择每个决策树的所有预测的模式。这有什么意义?依靠“多数获胜”模型,它降低了单个树出错的风险。
例如,如果我们创建一个决策树,第三个,它会预测 0。但是如果我们依赖所有 4 个决策树的模式,预测值将是 1。这就是随机森林的力量。
StatQuest 做了一项了不起的工作,更详细地说明了这一点。见此处。
神经网络
神经网络的可视化表示
一个神经网络是一个受人脑启发的多层模型。就像我们大脑中的神经元一样,上面的圆圈代表一个节点。蓝色圆圈代表**输入层,黑色圆圈代表隐藏层,绿色圆圈代表输出层。**隐藏层中的每个节点代表一个功能,输入经过该功能,最终导致绿色圆圈中的输出。
神经网络实际上是非常复杂和非常数学化的,所以我不会进入它的细节,但…
饶彤彤的文章对神经网络背后的过程给出了直观的解释(见此处)。
如果你想更进一步,理解神经网络背后的数学,请点击这里查看这本免费的在线书籍。
如果你是一名视觉/音频学习者,3Blue1Brown 在 YouTube 上有一个关于神经网络和深度学习的惊人系列这里。
分类
在分类模型中,输出是离散的。下面是一些最常见的分类模型。
逻辑回归
逻辑回归类似于线性回归,但用于模拟有限数量结果的概率,通常为两个。在对结果的概率建模时,使用逻辑回归而不是线性回归的原因有很多(见这里)。实质上,逻辑方程是以这样一种方式创建的,即输出值只能在 0 和 1 之间(见下文)。
支持向量机
一个支持向量机是一种监督分类技术,实际上可以变得非常复杂,但在最基本的层面上非常直观。
让我们假设有两类数据。支持向量机将找到一个超平面或两类数据之间的边界,以最大化两类数据之间的差距(见下文)。有许多平面可以分隔这两个类别,但只有一个平面可以最大化类别之间的边距或距离。
如果你想了解更多细节,Savan 在这里写了一篇关于支持向量机的文章。
朴素贝叶斯
朴素贝叶斯是数据科学中使用的另一种流行的分类器。背后的想法是由贝叶斯定理驱动的:
虽然有许多关于朴素贝叶斯的不切实际的假设(这就是为什么它被称为‘朴素’),但它已经被证明在大多数情况下都是有效的,而且构建起来也相对较快。
如果你想了解更多,请点击这里。
决策树,随机森林,神经网络
这些模型遵循与前面解释的相同的逻辑。唯一区别是输出是离散的而不是连续的。
无监督学习
与监督学习不同,非监督学习用于从输入数据中进行推断和发现模式,而不参考标记的结果。无监督学习中使用的两种主要方法包括聚类和降维。
使聚集
摘自 GeeksforGeeks
聚类是一种无监督的技术,涉及数据点的分组或聚类。它经常用于客户细分、欺诈检测和文档分类。
常见的聚类技术有 k-means 聚类、分层聚类、均值漂移聚类、基于密度的聚类。虽然每种技术在寻找聚类时有不同的方法,但它们的目标都是一样的。
降维
降维是通过获得一组主变量来减少所考虑的随机变量的数量的过程[2]。简单来说,就是减少特性集的维数的过程(更简单来说,就是减少特性的数量)。大多数降维技术可以分为特征消除或特征提取。
一种流行的降维方法叫做主成分分析。
主成分分析
从最简单的意义上来说, PCA 涉及到将高维数据(如 3 维)投影到更小的空间(如 2 维)。这导致数据的维度降低(2 维而不是 3 维),同时保持模型中的所有原始变量。
这涉及到相当多的数学问题。如果你想了解更多…
点击查看这篇关于 PCA 的精彩文章。
如果你更想看视频,StatQuest 在 5 分钟内解释 PCA这里。
结论
显然,如果你深入到任何特定的模型,都会有大量的复杂性,但这应该会让你对每个机器学习算法是如何工作的有一个基本的了解!
查看此 链接 如果你想学习 所有数据科学的基础统计学。
看看这个*链接 如果你想学习一个 的循序渐进的过程来进行探索性的数据分析(EDA)。*
参考
[1] Stuart J. Russell,Peter Norvig,人工智能:一种现代方法(2010 年),普伦蒂斯霍尔
[2] Roweis,S. T .,Saul,L. K .,通过局部线性嵌入进行非线性降维(2000),科学
感谢阅读!
如果你喜欢我的工作,想支持我…
感谢阅读!
如果你喜欢我的工作,想支持我…
- 支持我的最好方式就是在媒体T2 上关注我。
- 在推特 这里成为第一批关注我的人之一。我会在这里发布很多更新和有趣的东西!
- 此外,成为第一批订阅我的新 YouTube 频道 这里!
- 在 LinkedIn 这里关注我。
- 在我的邮箱列表 这里报名。
- 查看我的网站,terenceshin.com。
面向数据科学家的面向对象编程介绍
入门
面向对象的基础知识,适合那些以前可能没有接触过这个概念或者想知道更多的人
首先,什么是面向对象编程?这就是所谓的编程范式,本质上意味着它是做某事或构建代码的一种特定方式。OOP 的意思是你围绕对象构建你的代码,这有利于构建框架和工具,或者使代码更加可用和可伸缩。这些对象本质上是将数据和方法存储在一个结构中,可以反复使用该结构来创建该结构的新实例,这样您就不必重复了。这样做的好处是,您可以让不同的对象和实例相互交互,同时在单个结构中存储属性和行为。
这与数据科学中传统使用的过程化编程形成对比。在这里,代码遵循一系列步骤,使用特定的代码块来完成任务。这可以利用函数,这些函数可以在整个脚本中多次循环使用,但通常遵循给定的使用顺序,这在 Jupyter 笔记本中很常见。
两者的区别在于,过程化编程可以被认为是关注于需要做什么,而 OOP 关注于构成整个系统的结构[1]。
OOP 范例通过类来创建对象,这些类被用作创建新对象的蓝图。该类描述了对象的总体情况,但与对象本身是分开的。这里的介绍主要是向您介绍面向对象编程中使用的结构,这样当您在代码中遇到它们时,您就可以理解对象的基本结构。
定义一个类
因此,要做的第一件事是定义一个新的类,这个类是使用关键字class
创建的,后面是包含方法的缩进代码块(方法是对象的一部分的函数)。
对于我们的例子,我们可以使用一个需要存储雇员信息的公司。这方面的一个例子如下:
class Employee: pass
这将创建 Employee 类(该名称的标题是遵循 [CamelCase](https://en.wikipedia.org/wiki/Camel_case#:~:text=Camel%20case%20(stylized%20as%20camelCase,word%20starting%20with%20either%20case.) 逻辑),它目前只使用pass
参数,因此不做任何事情。我们可以如下创建一个雇员的实例,方法是调用该类并将其赋给一个名为 Steve 的变量。
Steve = Employee()
然后,我们可以使用下面这段代码来检查 Steve 是否是雇员:
Steve.__class.__name__# out: 'Employee'
在这里,.__class__
检查类的类型,而.__name__
用它只打印类名。从这里可以看出,我们可以看出 Steve 是一名员工。
添加属性
创建类的下一步是开始向我们的 Employee 类添加属性。这是通过使用__init__()
方法来完成的,该方法在创建类的实例时被调用。本质上,这允许您将属性附加到任何新创建的对象上。
出于我们的目的,我们可以为员工指定工资、级别和为该公司工作的年数:
class Employee:
def __init__(self, wage, grade, years_worked):
self.wage = wage
self.grade = grade
self.exp = years_worked
Steve = Employee(25000, 3, 2)
这里值得注意的是,所有方法都必须以self
属性开始,虽然在创建实例时没有显式调用,但它用于表示类的实例。因此,我们指定self.wage
能够存储实例的工资。
为了访问创建的每个属性,我们可以使用点符号,这意味着我们将.
和属性放在实例名称的后面。在这种情况下,访问 Steve 的属性如下:
print("Steve's wage is:", Steve.wage)
print("Steve's grade is:", Steve.grade)
print("Steve has worked for", Steve.exp, "years")# out: Steve's wage is: 25000
Steve's grade is: 3
Steve has worked for 2 years
使用__init__()
方法创建的属性是实例属性,因为它们的值特定于类的特定实例。在这里,虽然所有雇员都有工资、级别和工作年限,但它们将特定于所创建的类的实例。
我们也可以通过在__init__()
方法之前赋值来为所有类实例设置具有相同值的类属性。在这里,我们可以指定员工工作的公司,假设这些都是同一家公司的,我们可以像访问属性一样访问这些信息:
class Employee:
#class attribute
company = "Data Sci"
#instance attributes
def __init__(self, wage = 20_000, grade=1, years_worked=0):
self.wage = wage
self.grade = grade
self.exp = years_worked#create an instance of Julie as an employee
Julie = Employee(40000, 3, 5)#print out the company name
print("Julie works for:", Julie.company)#out: Julie works for: Data Sci
添加方法
既然我们已经添加了属性,那么我们可以开始向我们的对象添加方法或行为。
__init__()
是我们已经介绍过的一种用于分配属性的方法,但是我们可以开始定义自己的方法来执行某些动作或与某些行为相关联。这可能包括更改它们的属性或执行某些操作。
对于我们的员工,我们可以创建一种方法,通过这种方法我们可以给他们升职,工资增加 5000 英镑,级别增加 1 级。这可以定义如下:
class Employee:
def __init__(self, wage = 20_000, grade=1, years_worked=0):
self.wage = wage
self.grade = grade
self.exp = years_worked
def promotion(self):
self.wage += 5000
self.grade += 1
通过生成一个名为 Sarah 的新员工并给她升职,可以看到这样做的结果。我们可以通过查看她晋升前后的工资来检查差异:
Sarah = Employee(50000, 5, 12)#Checking the original objects attributes
print("Sarah's wage is:", Sarah.wage)
print("Sarah's grade is:", Sarah.grade)
print("Sarah has worked for", Sarah.exp, "years\n")#Giving Sarah an promotion
print("Sarah has got a promotion\n")
Sarah.promotion()#Checking to see that the grade and wage have changed
print("Sarah's wage is now:", Sarah.wage)
print("Sarah's grade is now:", Sarah.grade) # out: Sarah's wage is: 50000
Sarah's grade is: 5
Sarah has worked for 12 years
Sarah has got a promotion
Sarah's wage is now: 55000
Sarah's grade is now: 6
为此可以使用其他方法来改变特性或执行某些行为。例如,可以使用一种方法来定义一个周年纪念日,从而为他们的经历添加一年,或者我们可以添加一种方法来比较两个对象的资历。你可以自己测试一下!
进一步 OOP
定义属性和将方法附加到对象上仅仅是 OOP 的开始,还有更多的东西需要探索。这包括类继承,由此可以定义原始类的子类,例如基于 Employee 类的基础创建经理、数据科学家、分析师和其他雇员类型。这些子类本质上继承了初始父类的属性和行为,但是可以添加更多的功能。还有对象比较,通过它可以比较对象,因为您可以设置哪些属性用于比较操作,例如==,!=,≥或≤。有一种字符串表示法,可以用一个字符串来表示一个对象,这样就可以很容易地创建该实例的副本。还有更多…
这篇文章只是给出了一个进入 OOP 世界的基本入口,并不包括定义属性或创建方法。然而,希望这能让你更详细地理解 OOP、类和对象,这样当你遇到它们的时候,你就能理解一些基础知识和它们的结构。
[1]https://isaacuterscience . org/concepts/Prog _ OOP _ paradigm?topic =面向对象编程
作为一个媒体会员,你的会员费的一部分会给你阅读的作家,你可以完全接触到每一个故事…
philip-wilkinson.medium.com](https://philip-wilkinson.medium.com/membership) [## scikit-learn 决策树分类器简介
towardsdatascience.com](/introduction-to-decision-tree-classifiers-from-scikit-learn-32cd5d23f4d) [## UCL 数据科学协会:Python 基础
研讨会 1: Jupyter 笔记本,变量,数据类型和操作
towardsdatascience.com](/ucl-data-science-society-python-fundamentals-3fb30ec020fa) [## 随机森林分类器简介
预测 NBA 球员的位置——我们正在看到一个真正的“无位置”联盟吗?
towardsdatascience.com](/introduction-to-random-forest-classifiers-9a3b8d8d3fa7)
初学者光学字符识别入门
从非结构化数据中读取文本的第一步
在这篇文章中,你将学习
- 什么是光学字符识别(OCR)?
- OCR 的用法
- 从 PDF 文件中读取文本和图像的简单代码
你已经扫描了几份文件,比如候选人所学课程的证书。课程证书可以是 PDF 或 JPEG 或 PNG 文件。如何提取重要信息,如考生姓名、完成的课程名称以及课程开始的日期?
光学字符识别(OCR)
OCR 是一种将手写、打字、扫描文本或图像中的文本转换为机器可读文本的技术。
您可以对任何包含文本的图像文件、PDF 文档或任何可清晰提取文本的扫描文档、打印文档或手写文档使用 OCR。
OCR 的使用
OCR 的一些常见用法有
- 通过跨不同业务部门数字化 PDF 文档来创建自动化工作流程
- 通过数字化打印文档,如阅读护照、发票、银行对账单等,消除手动数据输入。
- 通过数字化身份证、信用卡等创建对敏感信息的安全访问。
- 数字化印刷书籍,如古腾堡项目
阅读 PDF 文件
在这里,您将阅读 PDF 文件的内容。您需要安装 pypdf2 库,它是基于 python 构建的,用于处理不同的 pdf 功能,如
- 提取文档信息,如标题、作者等
- 逐页拆分文档
- 加密和解密 PDF 文件
**!pip install pypdf2**
你可以下载一份 PDF 格式的W4 表格样本
导入库
**import PyPDF2**
提取页数和 PDF 文件信息
使用模式’ rb’ '以二进制模式打开要读取的 PDF 文件。将 pdfFileObj 传递给***PdfFileReader()来读取文件流。 numPages 将获得 PDF 文件的总页数。使用getDocumentInfo()***在字典中提取 PDF 文件的作者、创建者、制作者、主题、标题等信息
**filename=r'\PDFfiles\W4.pdf'
pdfFileObj = open(filename,'rb')
pdfReader = PyPDF2.PdfFileReader(pdfFileObj)
num_pages = pdfReader.numPages
info=pdfReader.getDocumentInfo()****print("No. of Pages: ", num_pages)
print("Titel: ", info.title)
print("Author: ",info.author)
print("Subject: ",info.subject)
print("Creator: ",info.creator)
print("Producer: ",info.producer)**
从 PDF 文件的所有页面中检索文本
遍历 PDF 文件中的所有页面,然后使用***【get page(),*** 从 PDF 文件中按编号检索页面。您现在可以使用 extractText()从 PDF 文件中提取文本。 最后,使用***【close()】***关闭文件
**count = 0
text = “”
#The while loop will read each page.
while count < num_pages:
pageObj = pdfReader.getPage(count)
count +=1
text += pageObj.extractText()
print(“Page Number”,count)
print(“Content”,text)
pdfFileObj.close()**
**提醒一句:**使用 extractText ()提取的文本并不总是按正确的顺序排列,间距也可能略有不同。
从图像中读取文本
您将使用 pytesseract ,它是用于光学字符识别(OCR)的 Google tesseract 的 python 包装器,用来读取图像中嵌入的文本。
您需要了解一些可以使用 pytesseract 应用的配置选项
- 页面分段模式(psm)
- OCR 引擎模式(oem)
- 语言(左)
页面分割方法
psm 定义了 tesseract 如何将图像分割或分段为文本行或单词行
页面分割模式(psm)选项:
0:仅方向和脚本检测(OSD)。
1:带 OSD 的自动页面分割。
2:自动页面分割,但是没有 OSD,或者 OCR。
3:全自动页面分割,但没有 OSD。 (默认)
4:假设一列可变大小的文本。
5:假设一个垂直对齐的文本的单一统一块。假设一个单一的统一文本块。7:将图像视为一个单独的文本行。
8:把图像当成一个单词。
9:把图像当成一个圆圈里的一个单词。
10:将图像视为单个字符。
11:稀疏文字。不按特定顺序查找尽可能多的文本。
12:带有 OSD 的稀疏文本。
13:生线。将图像视为单个文本行,绕过特定于 Tesseract 的 hacks。
OCR 引擎模式(oem)
宇宙魔方有不同的引擎模式来提高速度和性能
0:仅传统引擎。
1:神经网络仅适用于 LSTM 发动机。2:莱格赛+ LSTM 发动机。
3: 默认 ,根据什么可用。
语言(l)
Pytessercat 支持多种语言,您可以在安装 pytesseract 时指定想要使用的语言,它将下载语言包。默认情况下,英语是默认语言
用于阅读文本的图像
导入所需的库
**import pytesseract
import cv2**
使用 openCV 读取图像文件。为 pytesseract 应用配置选项以从图像中读取文本。您可以尝试 psm 和 oem 的不同选项,并检查输出中的差异
**image_Filename=r'\Apparel_tag.jpg'***# Read the file using opencv and show the image*
**img=cv2.imread(image_Filename)
cv2.imshow("Apparel Tag", img)
cv2.waitKey(0)**#set the configuration for redaing text from image using pytesseract
**custom_config = r'--oem 1 --psm 8 -l eng'**
**text=pytesseract.image_to_string(img, config=custom_config)
print(text)**
从图像中提取文本
使用 pytesseract 进行 OCR 的最佳实践
- 尝试 pytesseract 的不同配置组合,以获得适合您的用例的最佳结果
- 文本不应倾斜,在文本周围留一些空白以获得更好的效果,并确保图像有更好的照明以消除深色边框
- 最低 300- 600 DPI 效果很好
- 字体大小为 12 磅。或更多给出更好的结果
- 应用不同的预处理技术,如二值化、图像去噪、旋转图像以消除扭曲、增加图像的锐度等。
结论:
OCR 结果取决于输入数据的质量。文本的清晰分割和背景中的无噪声给出了更好的结果。在现实世界中,这并不总是可能的,因此我们需要为 OCR 应用多种预处理技术以给出更好的结果。
参考资料:
Python-tesseract 是 Python 的光学字符识别(OCR)工具。也就是说,它会识别并“读取”…
pypi.org](https://pypi.org/project/pytesseract/)
https://pypi.org/project/PyPDF2/
https://stack overflow . com/questions/9480013/image-processing-to-improve-tessera CT-ocr-accuracy
熊猫简介
我想,既然您发现自己导航到了这个页面,那么您可能有大量的数据需要分析,并且您可能想知道最好和最有效的方法来回答一些关于您的数据的问题。你的问题的答案可以通过使用 python 包:Pandas 找到。
如何接触熊猫
由于熊猫的受欢迎程度,它有自己的常规缩写,所以任何时候当你将熊猫导入 python 时,使用下面的命名法:
import pandas as pd
熊猫套餐的主要用途是数据框
pandas API 将 pandas 数据帧定义为:
二维的、大小可变的、潜在异构的表格数据。数据结构还包含带标签的轴(行和列)。算术运算在行标签和列标签上都对齐。可以被认为是一个类似 dict 的系列对象容器。初级熊猫数据结构。
基本上,这意味着您的数据包含在如下所示的格式中。在行和列中找到的数据:
带有数据、行和列标签的示例数据帧。来自https://www . ka ggle . com/yamerenay/Spotify-dataset-19212020-160k-tracks的数据集
数据帧非常有用,因为它们提供了一种简单的方法来打印可视化表格,然后按照您想要的方式操作它。这些行可以很容易地被索引引用,索引是数据帧最左边的数字。索引将是从零开始的相应行的编号,除非您为每一行指定名称。也可以通过列名(如“轨道名”)或它们在数据帧中的位置方便地引用这些列。我们将在本文后面更详细地讨论引用行和列。
创作时间!
创建熊猫数据框架有几种方法:
一般来说,您主要是将. csv 文件或某种类型的数据源(即 SQL 数据库)中的数据放入 pandas 数据帧中。你不会从零开始,因为根据你所拥有的数据量,这将需要很长的时间。然而,如果你需要,这里有一个来自 python 字典的快速、简单的例子:
import pandas as pd
dict1 = {'Exercises': ['Running','Walking','Cycling'],
'Mileage': [250, 1000, 550]}
df = pd.DataFrame(dict1)
df
输出:
由上述代码构成的基本数据框架
字典关键字(“练习”和“里程”)成为相应的列标题。本例中作为列表的字典中的值成为数据帧中的单个数据点。由于跑步是“练习”列表中的第一项,因此列表的顺序将放在第一行,而 250 将放在第二列的第一行,因为它是“里程”列表中的第一项。另外,您会注意到,因为我没有为数据帧的索引指定标签,所以它会自动标记为 0、1 和 2。
然而,就像我之前说过的,创建熊猫数据框架的最有可能的方式是从 csv 或其他类型的文件中导入,以分析数据。这可以通过以下方式轻松完成:
df = pd.read_csv("file_location.../file_name.csv")
pd.read_csv()是一个非常强大和通用的方法,根据您导入数据的方式,它将非常有用。如果您的 csv 文件已经带有标题或索引,您可以在导入时指定,这样会使您的生活更加轻松。为了了解 pd.read_csv()的全部能力,我建议你看看熊猫 API 这里。
重要的事情先来
现在,您已经将数据导入到 python 编辑器中,并准备对其进行分析。然而,在我们开始回答你的分析问题的有趣部分之前,你必须熟悉你的数据,看看它是什么样子的。作为分析这些数据的人,你必须熟悉数据集。我喜欢用四种方法来了解我的数据,以及哪些熊猫让这变得超级简单。
- 。head() &。尾部()
- 。信息()
- 。描述()
- 。样本()
raw_song.head()
上面的线是我在这一页顶部的图片中的线。它将显示数据帧的前 5 行和每一列,为您提供数据外观的简单摘要。如果您愿意,也可以在方法的()中指定一定数量的行来显示更多的行。
。来自 Spotify 数据集的歌曲数据的 head()方法
。tail()同样只显示最后 5 行。
raw_song.tail()
。Spotify 数据集中歌曲数据的 tail()方法
通过这两个快速的方法,我从数据集的一个小样本中对列名和数据的样子有了一个大概的了解。这些方法也非常有用,特别是在 Spotify 数据集处理超过 300 万行的情况下,您可以轻松显示数据集并快速了解情况,并且您的计算机不会花费很长时间来显示数据。
。info()也很有用,它向我显示了所有列、它们的数据类型以及是否有空数据点的简明列表。
raw_song.info(verbose=True, null_counts=True)
。Spotify 数据集中歌曲数据的 info()方法
如果您有完整的整数或浮点列(即“位置”、“流”),那么。describe()是了解数据集更多信息的有用方法,因为它将显示关于这些列的许多描述性统计信息。
raw_song.describe()
。对 Spotify 数据集中的歌曲数据使用 describe()方法。请注意,只显示了“Position”和“Streams”列,因为它们是仅有的两个整数列,其他列是字符串,没有描述性统计信息。
最后,。sample()将允许您对数据帧进行随机采样,并查看您所做的任何操作是否错误地更改了数据集中的某些内容,当您第一次浏览数据集时,如果您只是想了解数据集到底包含了哪些在前面的方法中没有显示的内容,这也是非常有用的。
raw_song.sample(10)
。Spotify 数据集中歌曲数据的 sample()方法。
在探索和准备用于分析的数据集时,我始终如一地使用这些方法。每当我更改列中的数据、更改列名或添加/删除行/列时,我都会通过至少快速运行前面 5 种方法中的一些方法来确保一切都按照我想要的方式进行了更改。
选择一行或一列
太棒了,现在你知道如何把你的数据集作为一个整体来看了,但是你真的只想看几列或几行,而把其余的放在一边。
。loc[]和。iloc[]
这两种方法会以不同的方式来完成,这取决于你引用某一行或列的方式。
如果您知道行或列的标签,请使用。loc[]。
如果您知道行或列的索引,请使用。iloc[]。
如果你两个都知道,就选你最喜欢的,抛硬币,或者用你知道不会改变的那个。例如,如果您向数据帧添加行或列,这些行/列的索引将会改变,并可能导致您以后引用错误的行/列。
因此,回到 Spotify 数据集。您可以使用以下任意一种方法来查看“曲目名称”栏。loc[]或。iloc[]。与。loc[]因为我知道列的标签,所以我将使用以下内容:
raw_song.loc[:,'Track Name']
第一个括号后面的冒号指定了我引用的行,因为我希望所有的行都在“Track Name”列中,所以我使用了“:”。
。位置[]
我将收到与。iloc[]只是这次需要指定“曲目名称”列的索引:
raw_song.iloc[:,1]
。iloc[]
。loc[]和。iloc[]对行的作用是相同的,除了在这种情况下,因为行的标签和索引都是相同的,所以它们看起来完全一样。
切片和切块
安妮·斯普拉特在 Unsplash 上的照片
获取数据帧一部分的另一个简单方法是使用[]并在括号内指定列名。
raw_song[['Artist','Streams']].head()
如果你只用一根柱子和一组支架,你将会得到一个熊猫系列。
raw_song['Streams']
从数据帧添加行和列
利用我们已经知道的。loc[]我们可以用它在数据帧中添加一行或一列。您还可以使用其他两种方式添加列。insert()或通过添加数据帧的一部分并在方括号[]内指定列名来实现。如果您尝试添加多行和多列,您可以创建一个单独的数据帧,并将新列或行的新数据帧连接到原始数据帧,以添加该列或行。为此,您可以使用 pd.merge()、concat()或 join();然而,这些方法的进一步讨论和示例将在以后的文章中讨论,不在本文的讨论范围之内。
增加一行:
如果你决定使用。loc[]要将一行添加到数据帧,只能将其添加到数据帧的底部。用指定 dataframe 中的任何其他索引,将擦除当前在该行中的数据,并用插入的数据替换它。在这个例子中,我将一个新索引命名为“last ”,它显示在数据帧的底部。请注意,它不必是一个特定的名称,只要它不同于任何其他索引。
raw_song.loc['last'] = [0,'hello','bluemen',1,"https://open.spotify.com/track/notarealtrack", '2017-02-05','ec']
您可以使用同样的方法将列添加到 dataframe。loc[]。同样,为该列创建一个新名称,除非您尝试用新值替换一个列。您必须放置一个可以为所有行复制的单个值,或者一个长度与数据帧中的行数相同的列表。
raw_song.loc[:,'new_col'] = 0
raw_song.tail()
。loc[]向 Spotify 数据集添加一列。我用 0 来简化输入。
除了在末尾插入新列之外,还有两种方法可以在数据框中插入新列。
的方法。insert()将允许您指定希望将列放入数据帧的位置。它有 3 个参数,放置它的索引、新列的名称和作为列数据放置的值。
raw_song.insert(2,'new_col',0)
raw_song.tail()
使用。insert()在 Spotify 数据集中创建一个新列。
向数据帧添加列的第二种方法是,通过使用[]命名新列并使其等于新数据,就像它是数据帧的一部分一样。
raw_song['new_col'] = 0
raw_song.tail()
向数据帧的末尾添加新列
这样,我无法指定新列的位置,但这是执行操作的另一种有用方式。
从数据帧中删除行、列
简·廷伯格在 Unsplash 上拍摄的照片
如果你想去掉一些行或列,很简单,只需删除它们。
只需指定要删除的轴(0 表示行,1 表示列)以及要删除的行或列的名称,就可以了!
raw_song.drop(labels='new_col',axis=1)
。drop()允许我去掉在上一节中添加的“new_col”列。
重命名索引或列
如果您希望将数据帧的索引更改为数据帧中的不同列,请使用。set_index()并在括号中指定列的名称。但是,如果您确切地知道您想要为索引命名什么,请使用。rename()方法。
raw_song.rename(index={0:'first'}).head()
此数据帧的第一个索引已从 0 更改为“第一个”
若要重命名列,请在。rename()方法要重命名的列以及您希望在{}中为其命名的内容类似于重命名索引。
raw_song.rename(columns={'Position':'POSITION_RENAMED'}).head()
第一列已从“位置”更改为“位置 _ 重命名”
如何迭代你的数据框架
很多时候,当您处理数据帧中的数据时,您需要以某种方式更改数据,并迭代数据帧中的所有值。最简单的方法是在 pandas 中内置一个 for 循环:
for index, col in raw_song.iterrows():
# manipulate data here
如何将数据帧写入文件
完成对数据帧的所有操作后,现在是时候导出它了,这样您就可以将它发送到其他地方。类似于从文件导入数据集,现在正好相反。Pandas 有各种不同的文件类型,您可以将数据帧写入其中,但最常见的是将其写入 csv 文件。
pd.to_csv('file_name.csv')
现在你知道熊猫和数据框的基本知识了。在你的数据分析工具箱中,这些都是非常强大的工具。
可能性理论导论
可能性理论的基本背景
可能性理论是由扎德[1]提出的,并由 Dubois 和 Prade [2]进一步发展,目的是为语言陈述提供一种定义明确和正式的数学表示,允许处理不精确或模糊的信息。例如,根据每个人对廉价的主观定义和上下文,单词 cheap 可以被赋予一大组值。
可能性值可以解释为事件发生的可行性的程度。
与概率论的一个重要区别是高可能性值是非信息性的,而高概率值是信息性的。事实上,事件 A 发生的可能性非常高,这意味着,无论 A 发生与否,我们都不会感到意外。如果 A 有非常高的概率质量,那么我们会惊讶于 A 没有发生。相反,低可能性和概率值都是信息性的,因为它们都表明和不太可能发生。
可能性理论也与信念函数理论相关,因为可以证明,如果质量函数是一致的(即,它具有嵌套的焦点元素),那么它与可能性分布是双射对应的。更一般地说,可能性是一类特殊的不精确概率,在这种框架中,概率值只能用两个界限来表示。事实上,可能性框架中事件的不确定性可以通过一对值(可能性和必要性)更好地提升,这对值可以被视为概率界限。
可能性理论源于模糊集理论。实际上,假设 T 是为真的事件b⊆ω的集合,而 U 是未判定事件(即既非真也非假的事件)的集合。又假设 T 和 U 是模糊的,那么必然性是 T 的隶属函数,可能性是 T∪U 的隶属函数。
可能性分布
在可能性理论中,可能性分布是最简单的一类对象,它完全捕捉了我们不确定性的所有信息。可能性分布π将论域中的每个元素映射到单位区间[0,1],其极值对应于不可能和完全可能的状态。由可能性π导出的具有上下界约束的容许概率分布集合用p(π)⊆p(ω)表示。
如果任何状态c∈ω具有等于 1 的可能性度,那么这个状态(即这个类)是完全可能的,并且按照惯例,可能性分布π被称为归一化的。不精确的概率解释只有在可能性分布是正态的情况下才是有效的,否则我们将得到P(ω)<1,用于某些概率分布 p 中的 P ( π )。****
在处理可能性分布时,我们可以区分零确定性和完全确定性的两种特殊情况:
1.完全确定:∃a∈ω,π( a )=1 且π( b )=0,∀a≦b。****
2.零确定性(无知):π(a)=1∀a∈ω。**
可能性和必要性措施
可能性理论和其他与不精确概率兼容的理论的一个典型方面是存在两种描述不确定性的度量:必要性和可能性。
一个必要性度量考虑了根据可用信息对每个事件的合理信任度。**
相应的可能性度量评估在没有任何矛盾信息的情况下,人们在多大程度上仍然可以说一个事件是可能的。**
必要性和可能性测度分别是不精确概率解释中的下概率和上概率。
给定ω的子集 A ,可能性度量由下式给出:
这意味着子集 A 的可能性等于该子集中的最大可能性度。因此,可能性度量是最大的:
**π(A∪B)= max(π(A),π(B))与概率度量相反,概率度量是求和的。
请注意,该属性说明了这样一个事实,即可能性分布是计算任何子集的可能性度量的足够信息。
必要性度量由下式给出:
必要性度量是这样的: N(A ∩ B) =min(N(A),N(B)) 并且,在归一化的情况下,我们有π(ω)= n(ω)= 1 并且π(∅)=n(∅)= 0。**
概率-可能性转换
假设我们正在处理客观概率分布,我们的目标是将它们转换成可能性分布。因此,推荐的选择是 Dubois 和 Prade [3]提出的变换,它保留了概率中包含的统计信息。
考虑ω上的离散概率分布 p ,我们总是可以置换ω的元素的索引,使得概率值的集合以降序排序:
转换内容如下:
这种转变是可逆的[4](从这个意义上说, p 可以从π中恢复过来)。它产生一个标准化的可能性分布。如果 p 是均匀的,那么 p 被映射到一个常数π。如果 p 是狄拉克质量,那么 p 映射到自身。它还有三个重要的特性[5]:
- 一致性:∀a⊆ω,π(a)≥p(a)其中π为π跨越的可能性测度。所以π是一个明确定义的上概率。****
- 偏好保留 : ∀ ( a ,b)∈ω,p(a)>p(b)⇔π(a)>π(b),所以在由ψ编码的偏好之间存在一种形式的兼容性
- 最大特异性 : π在那些与 p 一致且保持偏好的可能性分布中达到最大特异性。考虑两种可能性分布π1 和π2。如果满足以下条件,则称概率分布π1 比π2 信息量更大:
结论
这是关于可能性理论的简要介绍。如上所述,它是用来处理语言陈述中模糊和不精确的信息的。我们在第一部分提供了一个基本背景,然后我们介绍了可能性分布以及从这些分布中计算出来的可能性和必要性度量。然后我们引入了一个概率-可能性转换,它适合客观概率。就我个人而言,我在关于分类器组合的博士论文中使用了这个框架(可能性框架),其中我使用 Tnorm 函数组合了可能性分布。
链接到我的博士手稿:
将链接到相关论文:
参考资料:
[1]扎德,洛特菲·阿斯克尔。"模糊集是可能性理论的基础."模糊集与系统1.1(1978):3–28。
[2]杜布瓦,迪迪埃和亨利·普拉德。可能性理论:一种计算机处理不确定性的方法。斯普林格科学&商业媒体,2012 年。
[3]杜布瓦,迪迪埃和亨利·普拉德。“在几个不确定的证据主体的陈述。模糊信息和决策过程,167-181,古普塔和桑切斯(1982).
[4]杜布瓦、迪迪埃和亨利·普拉德。“可能性理论及其应用:我们站在哪里?."斯普林格计算智能手册。施普林格,柏林,海德堡,2015。31–60.
[5] Dubois,Didier 等人,“概率-可能性转换,三角模糊集和概率不等式。”可靠计算10.4(2004):273–297。
ProtoDash 介绍-一种更好地理解数据集和机器学习模型的算法
写这篇文章是希望你能发现一种新的方法来探索和采样你的数据。
为了理解我们的数据,我们从描述性分析开始,切片和切块以寻找预期的和有趣的模式。然后我们可能会寻找星团。在文本数据中,这些可能代表主题,而对于客户来说,这些可能代表人物或群体。
或者,我们可能希望在我们的数据中找到与我们感兴趣的子集相似的其他例子。
通过在数据中选择有代表性的例子,可以使用 ProtoDash 来支持这些活动。
什么是原破折号?
ProtoDash 算法是由亚马逊和 IBM Research 合作开发的。这是一种选择能捕捉数据集基本分布的原型示例的方法。它还对每个原型进行加权,以量化它代表数据的程度。
ProtoDash 需要三个关键输入:要解释的数据集、要从中选择原型解释的数据集以及要选择的原型数量。
ProtoDash 的一个主要优点是,它旨在找到不同的原型,即以不同方式反映数据集的示例,以提供更完整的图片。
一旦最初的原型——最典型的数据点——被发现,算法将搜索第二个原型。虽然它再次寻找具有共同行为的例子,但它也试图确保发现新的特征,即第二个原型不同于第一个原型。这种搜索一直持续到找到所请求的原型数量。
用例
子集选择的原划
通过按设计使用 ProtoDash,您可以在一个数据集中发现最能代表另一个数据集分布的例子。当您希望根据以前的客户来确定特定产品的目标客户,或者回顾性地了解某个事件的影响时,这可能非常有用。
例如,您可能想探究某项行动的效果,如“收到营销电子邮件是否影响了销售?”。由于有许多其他因素会影响销售,我们希望确保在比较收到营销邮件的人时,治疗组和没有收到营销邮件的人,对照组具有相似的特征。为此,您可以将处理组作为您希望解释的数据集传递,将对照组作为从中寻找原型的数据集传递。
可解释人工智能的原型
如果您只传递一个数据点作为您想要解释的数据集,那么您需要在第二个数据集中找到与这个例子最相似的原型。
为了信任机器学习模型,我们希望更好地理解它们是如何工作的。其中一个关键问题是“相似的例子会得到相同的结果吗?”。例如,如果一笔贷款被拒绝,我们可能希望确保类似的申请也被拒绝。如果不是这种情况,它可能表明机器学习模型没有按预期运行。
通过使用 ProtoDash 识别相似的示例,您可以评估每个示例的机器学习模型预测是否与您预期的方式相似,如果它按预期工作的话。
分段的原划
如果您只将一个数据集传递给 ProtoDash,表明应该从您希望解释的数据集中选择原型解释,那么所选择的原型将总结底层数据分布。
这可以作为用于分段的聚类的替代方案。每个原型都是一个代表特定细分的例子。例如,您可能想要了解客户电子邮件中的常见主题,而 ProtoDash 可能会提供一些示例,代表有关支付、交付、注册等方面的查询。
要从原型生成聚类,可以将原型用作聚类质心,并将数据集中的所有其他示例分配给它们最近的原型。
它是如何工作的?
ProtoDash 算法的目标是用另一个数据集的加权样本来近似一个数据集的分布。为此,它选择最小化最大平均差异(MMD)的样本。MMD 表示一个数据集的分布被另一个分布的样本所代表的程度。
ProtoDash 要求指定 MMD 的内核。如果使用“线性”,这意味着基础分布将只关注每个特征的平均值。然而,当使用“高斯”时,要考虑均值、方差、偏斜等。对于每个特性,获取更详细的数据理解。
当选择原型和权重时,MMD 的简单重构成为最大化的目标函数。方法如下:
1.给定当前原型集,计算目标函数的值(步骤 2 需要)
2.计算每个例子相对于目标函数的梯度
3.选择具有最大梯度值的示例,并将其添加到原型集中
4.通过使用二次规划优化目标函数来计算权重
5.重复上述步骤,直到选择了适当数量的原型
例子
ProtoDash 白皮书包括一个 MNIST 示例,这是一个包含数字 0 到 9 示例的影像数据集。这些实验旨在验证 ProtoDash 从源数据集中选择与不同目标数据集的分布相匹配的原型的能力。例如,数字 0 的实例作为要解释的数据集被传递,而包含所有数字(0 到 9)的实例的数据集被传递以从中查找原型。
以下是算法发现的原型示例。您可以看到,对于 digit,算法已经发现了底层特性,因为每个返回的特性都被正确地发现了。此外,示例本身的宽度、厚度和倾斜度也各不相同。
图片来自论文:通过选择具有重要性权重的原型进行有效的数据表示
ProtoDash 从一个数据集选择与另一个数据集的分布紧密匹配的原型的能力在迁移学习、多任务学习和协变量偏移校正中有应用。
摘要
ProtoDash 是 AI Explainability 360 Toolkit 的一部分,这是一个开源库,支持数据集和机器学习模型的可解释性和可解释性。
通过寻找原型实例,ProtoDash 提供了一种理解数据集潜在特征的直观方法。对于一系列场景来说,这可能是一个有价值的解决方案,包括子集选择、分割和支持机器学习可解释性。
链接
视频:proto dash:kart hik Gurumoorthy 的快速可解释原型选择
用于无监督聚类的伪半监督学习介绍
这篇文章概述了我们基于深度学习的技术,通过利用半监督模型来执行无监督聚类。获取未标记的数据集,并且使用以完全无监督的方式生成的伪标签来标记该数据集的子集。伪标记数据集结合完整的未标记数据用于训练半监督模型。
这项工作于 2020 年在 ICLR 发表,论文可以在这里找到,源代码可以在这里找到。
介绍
在过去的 5 年中,一些方法在半监督分类中取得了巨大的成功。当给定大量未标记数据和少量标记数据时,这些模型工作得非常好。
未标记的数据有助于模型发现数据集中的新模式,并学习高级信息。标记的数据有助于模型使用学习到的信息对数据点进行分类。例如,梯形网络可以产生 98%的测试准确度,只需标记 100 个数据点,其余的不标记。
为了使用半监督分类模型进行完全无监督的聚类,我们需要以某种方式以完全无监督的方式生成少量的标记样本。这些自动生成的标签称为伪标签。
具有用于训练半监督模型的高质量伪标签是非常重要的。如果标签中有大量噪声,分类性能会下降。因此,假设伪标签中的噪声更少,我们可以接受更少数量的伪标签数据点。
下面是一种简单的方法:
- 从一个未标记的数据集开始。
- 获取数据集的子集并为其生成伪标签,同时确保伪标签的质量良好。
- 通过将完整的未标记数据集与小的伪标记数据集相结合来训练半监督模型。
作者图片
这种方法使用了半监督学习的一些元素,但没有使用实际的标记数据点。因此,我们称这种方法为伪半监督学习。
生成伪标签
生成高质量的伪标签是获得良好的总体聚类性能的最棘手也是最重要的一步。
生成伪标签数据集的简单方法是
- 对整个数据集运行标准聚类模型,并使伪标签等于模型中的聚类 id。
- 运行一个标准的集群模型,其中的集群数量远远超过所需数量。然后只保留几个聚类来标记相应的数据点,而丢弃其余的。
- 运行一个标准的聚类模型,只保留模型的置信度大于某个阈值的数据点。
实际上,上述方法都行不通。
第一种方法是没有用的,因为伪标签只是由标准聚类模型返回的聚类,因此我们不能期望半监督模型比这表现得更好。
第二种方法不起作用,因为没有好的方法来选择不同的集群。
第三种方法行不通,因为在实践中,单个模型的置信度并不是质量的指标。
在试验了几种生成伪标记数据集的方法后,我们观察到多个无监督聚类模型的一致性通常是质量的良好指标。单个模型的聚类并不完美。但是如果大量的聚类模型将数据集的子集分配到同一个聚类中,那么它们实际上很有可能属于同一个类。
在下图中,两个模型的聚类分配的交叉点上的数据点可以被分配以高置信度的相同伪标签。伪标签子集中的 Rest 可以忽略。
作者图片
使用图来生成伪标签
有一种更正式的方法来生成伪标签数据集。我们首先构建所有数据点的图,对模型的成对一致性进行建模。
该图包含两种类型的边。
- 强正边缘—当大部分模型认为两个数据点应该在同一个聚类中时
- 强负边缘—当大部分模型认为两个数据点应该在不同的聚类中时。
可能在两个数据点之间既没有强的正边缘也没有强的负边缘。这意味着这些数据点的聚类分配的置信度很低。
在构建该图之后,我们需要挑选 K 个小聚类,使得一个聚类内的数据点与强正边相连,而不同聚类的数据点与强负边相连。
图表示例如下:
构造图的例子。强正边缘—绿色,强负边缘—红色。图片作者
我们首先选择具有最大数量的强正边的节点。在示例中,该节点被圈起来:
*选中的节点被圈起来。*作者图片
然后,我们将伪标签分配给连接到具有强正边的所选节点的邻居:
作者图片
既没有与强正边也没有与强负边连接的节点被移除,因为我们不能以高置信度分配任何标签:
作者图片
然后,我们重复步骤 K 次以上,以获得 K 个小型集群。一个小型集群中的所有数据点被分配相同的伪标签:
*最终伪标签子集。*作者图片
我们可以看到,在这一步中,许多数据点将被丢弃,因此,将这些伪标记的数据点发送到半监督学习模型进行下一步是理想的。
使用伪标签训练半监督模型
现在,我们有了一个经过修剪的伪标记数据集以及完整的未标记数据集,用于训练半监督分类网络。网络的输出是一个软最大化的向量,它可以被看作是聚类分配。
如果伪标签具有良好的质量,那么与单独的聚类模型相比,这种多阶段训练产生更好的聚类性能。
我们可以有一个能够执行无监督聚类和半监督分类的单一模型,而不是有单独的聚类和半监督模型。实现这一点的一种简单方法是使用通用的神经网络架构,并应用聚类损失和半监督分类损失。
我们决定使用结合信息最大化损失的半监督梯形网络进行聚类。你可以在这里阅读更多关于不同深度学习聚类方法。
把所有东西放在一起
在第一阶段,仅应用聚类损失。在获得伪标签之后,聚类和分类损失都被应用于模型。
在半监督训练之后,我们可以使用更新的模型提取更多的伪标记数据点。生成伪标签和半监督训练的过程可以重复多次。
整体算法如下:
- 使用聚类损失训练多个独立模型
- 构建模型成对一致的图形模型
- 使用该图生成伪标签数据
- 通过应用聚类和分类损失,使用未标记+伪标记数据训练每个模型
- 从步骤 2 开始重复
*最终系统概述。*作者图片
估价
我们希望我们的聚类接近真实标签。但是因为该模型是以完全无监督的方式训练的,所以没有基础事实类和聚类的固定映射。因此,我们首先找到地面真实与具有最大重叠的模型集群的一对一映射。然后,我们可以应用准确性等标准指标来评估集群。这是一个非常标准的集群定量评估指标。
我们可以通过从最终聚类中随机采样图像来可视化聚类。
可视化 MNIST 数据集的聚类。来源:原始论文。
可视化 CIFAR10 数据集的聚类。来源:原始论文。
在这篇文章中,我们讨论了一种基于深度学习的技术,通过利用伪半监督模型来执行无监督聚类。这种技术优于其他几种基于深度学习的聚类技术。如果你有任何问题或想建议任何改变,请随时联系我或在下面写评论。
从 这里 获取完整源代码
原载于 2020 年 10 月 31 日 https://divamgupta.com。
用 Python 和 scikit 介绍随机森林-学习
获得对随机森林的直观理解和数学理解的完整指南,使用 scikit 实现您的第一个模型——学习 Python
随机森林可视化与 50 个不同的决策树
注意:这篇文章假设你对决策树有基本的了解。如果您需要更新决策树的工作方式,我建议您首先阅读Python 和 scikit 决策树介绍-学习。
关于随机森林的好处是,如果我们很好地理解了决策树,那么理解随机森林应该也很容易。随机森林这个名字实际上很好地描述了添加的额外功能。首先,我们现在有了随机,我将更深入地解释它。其次,提醒你自己一个森林是由什么组成的,也就是一堆树,所以我们基本上有一堆决策树,它们被称为一个森林。非常直观地将这两个术语联系起来,实际上只有森林是随机的,因为它由一堆基于随机数据样本的决策树组成。
了解随机森林
为了理解随机森林,我们实际上只需要理解什么是自举,或者换句话说,置换随机抽样。对于每个单独的决策树,我们随机选择给定次数的随机观察(通常对应于观察的总数)。唯一微小的细节是,相同的观察可能会出现多次(否则,我们基本上只会以随机顺序获得每个决策树的相同数据,这将导致每个树的结果完全相同)。
如果你还记得我在决策树上发布的代码(如果不记得,更新后的代码会在下面发布),下面这一行实际上是我们唯一需要添加的内容:
bootstrap = data[np.random.randint(0, rows-1, rows)].reshape(rows, columns)
这行代码所做的,基本上是从完整的数据集中随机地获取一些观察值,也就是带有替换的自举/采样。此外,我们还必须为算法添加一个新的外部循环,它对应于我们想要生成的决策树的数量。
注意:我在外部树循环(第 14 行)之后的第一行中添加了一行选择随机特征,该行随机选择与特征数量的平方根相对应的多个随机特征。为了简单起见,在之前的文章中 GitHub Gist 没有添加这一点。我在这里添加了它,因为它是 sklearn 中的默认设置,它使代码运行得更快,因为我的算法根本没有优化,已经运行得很慢了。
**Out [1]:**TREE 1: Best split-feature : Fare
TREE 1: Value to split on : 52.5542
TREE 1: Weighted gini impurity : 0.42814
TREE 2: Best split-feature : Sex
TREE 2: Value to split on : 1.0
TREE 2: Weighted gini impurity : 0.32462
TREE 3: Best split-feature : Parents/Children Aboard
TREE 3: Value to split on : 0.0
TREE 3: Weighted gini impurity : 0.45518
因此,这将是林中每个决策树的第一次拆分。实际上,我们不应该只给每棵树做一个裂口,而是让它们完全生长出来。类似地,如果你用 3 棵完全生长的不同的树建立一个随机的森林,你会得到这样的结果:
基于自举数据的由 3 棵决策树组成的随机森林
注意:随机森林中的三个决策树不会在相同的初始注释上分裂,因为您必须控制几个随机因素才能获得完全相同的结果,这将导致更多的代码。
使用随机森林进行预测
现在到了随机森林的简单部分,来做预测。我们要做的一切,实际上只是在每棵树上输入一个观察值,看看它能预测什么。然后,我们采用大多数树选择的预测,例如,在上图中,如果三棵树中的两棵树预测有乘客幸存,那么这也将是我们的最终预测。
为什么这是个好主意呢?决策树往往会过度拟合数据,但是通过利用大量的单独决策树来预测最终结果,我们至少可以在某种程度上防止这种过度拟合。
希望这篇文章对你更深入的了解随机森林,理解算法的工作原理有所帮助。请随意留下您的评论或问题。
正则表达式简介
轻松使用正则表达式的初学者指南
作者图片
简介
这篇博客的目标是为那些没有经验或知识的人提供一个简单而直观的 python 正则表达式介绍。正则表达式,也称为正则表达式,是用于在一个字符串或一系列字符串中查找模式的字符序列。为了加深我们对正则表达式及其工作原理的理解,我将在下面进行一些教程。
教程
在以下教程中,我们将学习如何:
- 获得 python 中正则表达式模块的访问权限
- 使用 re.search()匹配字符串的模式
- 使用 regex 的元字符创建更复杂的模式
如何访问正则表达式模块
实际上有两种方法可以导入 regex 模块和搜索函数。
第一种方法是导入整个 regex 模块,然后在想要使用搜索功能时使用模块名作为前缀。
import re
re.search(<regex>, <string>)
第二种方法是通过名称从正则表达式模块导入搜索函数,因此可以直接调用该函数,而不需要任何前缀。
from re import search
search(<regex>, <string>)
正如您所看到的,在使用这种方法调用搜索函数时,没有使用前缀。还要记住,在搜索函数中,第一个参数“regex”是要搜索的模式,第二个参数“string”是要搜索的字符串。此外,需要记住的一个重要注意事项是,regex 搜索函数将只返回查询的第一个匹配项。
正在应用搜索()
为了全面理解搜索功能以及如何使用它,我们来看一些简单的例题。
Code Input:
import re
s='A-b_123_e3'
re.search('123', s)Code Output:
<re.Match object; span=(4, 7), match='123'>
从上面可以看出,regex 搜索函数返回两条重要的信息。
首先是跨度。Span 本质上是子字符串在整个字符串中的位置。所以在这种情况下,span 告诉我们我们想要的第一个字符从哪里开始(第四个元素)以及我们想要的最后一个字符从哪里结束。(第七元素)
请记住,end 元素不包括在内,因此即使“3”字符的索引实际上是 6,搜索函数也将返回 7。
搜索函数给出的第二条信息是匹配项。本质上,匹配是您搜索的也在更大的字符串中找到的字符。
总而言之,search 函数是一个强大的工具,因为它允许您确定一个字符串序列是否在一个更大的字符串序列中,如果是,该函数会通知您搜索查询的相对位置。
关于正则表达式搜索函数的另一个有趣的事实是,它可以很容易地集成到布尔条件语句中。请看下面的例子。
Code Input:
a='Jonny paid $5 for lunch and $10 dollars for dinner.'
if re.search('$', a):
print('Found $ signs.')
else:
print("Did not find $ signs.")
Code Output:
Found $ signs.
如您所见,正则表达式搜索本质上是真或假,其中带有解决方案的搜索被认为是真的,而失败的搜索将返回假的。
当然,这些都是非常简单的例子,但是上面例子中使用的概念仍然适用于更复杂的问题。
使用元字符的复杂正则表达式查询
到目前为止,我们所做的查询肯定是有用的,但实际上,到目前为止的应用非常有限。到目前为止,我们只能匹配精确的子字符串。
但是,如果我们使用元字符,我们真的可以看到正则表达式的威力。
方括号是一个对查询非常有用的元字符。任何放在方括号中的字符形成一个**字符类。**使用搜索功能时,将返回字符类中的任何字符。为了更好地理解这一点,我们来看一个例子。
#First Method
Code Input:
A = 'AB#$_+*87ba_seven'
re.search('[0-9]+', A)
Code Output:
<re.Match object; span=(7, 9), match='87'>
#Second Method
Code Input:
A = 'AB#$_+*87ba_seven'
re.search('[0-9][0-9]', A)
Code Output:
<re.Match object; span=(7, 9), match='87'>
正如您所看到的,上面显示了两种方法。正则表达式的好处之一是通常有多种方法来解决一个问题。
在这种情况下,括号构成了一个数字字符类,其中 0 到 9 之间的任何整数都是有效的。第一种方法使用表中引用的“+”字符。它查找一个或多个子字符串。因此,在这种情况下,因为有两个数字,所以“+”告诉搜索函数在第一个数字后面寻找第二个数字。第二种方法只是重复方括号方法两次。
另一个重要的元字符是句点。点(。)是一个通配符,可以匹配除换行符之外的任何字符。要了解这是如何工作的,让我们看另一个例子。
Code input:
print(re.search('123.abc','123abc'))
print(re.search('123.abc','123\abc'))
print(re.search('123.abc','123/abc'))
Code Output:
None
None
<re.Match object; span=(0, 7), match='123/abc'>
如您所见,点(。)元字符仅在较长字符串中的等效位置存在有效字符时才返回结果。任何字符或反斜杠(换行符)都不会返回任何内容,但是正如您在第三个示例中看到的,任何其他字符都可以。
元字符\d,\D,\w,\s,\S
这些元字符用于标识特定类型的字符。\d 代表任何十进制数字字符,\D 代表任何非十进制数字的字符。同样的想法也适用于带有\w 的字母数字字符。w 相当于我们前面讨论过的[a-zA-Z0–9 _]。像\D 一样,\W 是它的小写对等词的反义词。/s 和/S 对空白字符使用相同的概念。让我们看一些例子。
Code input:
print(re.search('\w', '#@! .h3.&'))
print(re.search('\W', '#@! .h3.&'))
print(re.search('\d', '#@! .h3.&'))
print(re.search('\D', '#@! .h3.&'))
print(re.search('\s', '#@! .h3.&'))
print(re.search('\S', '#@! .h3.&'))
Code Output:
<re.Match object; span=(5, 6), match='h'>
<re.Match object; span=(0, 1), match='#'>
<re.Match object; span=(6, 7), match='3'>
<re.Match object; span=(0, 1), match='#'>
<re.Match object; span=(3, 4), match=' '>
<re.Match object; span=(0, 1), match='#'>
正如您所看到的,使用这些特殊字符,我们可以确认这个字符串是否包含字母数字字符和空白。假设我们确实找到了这些字符,那么我们也将找到这些独特字符的确切位置。
\字符
反斜杠是 regex 工具箱中非常特殊和强大的工具。正如我们之前看到的,反斜杠字符可以用来引入特殊的字符类,如字母数字字符或空格。它也用于另一种元字符类型的锚。它还可以用于转义元字符。
锚点\A,\Z,\B
\A 是一个有用的定位点,用于将查询附加到搜索字符串的开头。因此,只有当搜索字符串的开头与查询完全匹配时,搜索才会返回结果。
Code Input:
print(re.search('\Achoc', 'chocolate bar'))
print(re.search('\Abar', 'chocolate bar'))
Code Output:
<re.Match object; span=(0, 4), match='choc'>
None
/Z 本质上是/A 的反义词。因此,在这种情况下,只有当搜索字符串的结尾与查询完全匹配时,搜索函数才会返回结果。
Code Input:
print(re.search('bar\Z', 'chocolate bar'))
print(re.search('late\Z', 'chocolate bar'))
Code Output:
<re.Match object; span=(10, 13), match='bar'>
None
正如您在上面看到的,只有第一种情况返回结果,因为搜索字符串的结束字符与查询匹配。然而,在第二种情况下,由于我们选择了不同的字符段,搜索函数不返回任何内容。
/b 非常有用,因为它将匹配锚定到边界。所以/b 需要有字界才有结果。/b 断言解析器的当前位置要么是这可能很难用文字来理解,所以让我们看一个例子。
Code Input:
print(re.search(r'\bbar', 'chocolate bar'))
print(re.search(r'\bbar', 'chocolatebar'))
Code Output:
<re.Match object; span=(10, 13), match='bar'>
None
正如你所看到的,当有像第一种情况(空白)的边界时,那么使用/b,我们确实得到了使用搜索函数的结果。然而,在第二种情况下,单词之间没有边界,那么就没有输出。
结论
regex 库非常强大,查询可能会变得非常复杂。因此,这篇博客的目的是让初学者了解什么是正则表达式以及如何使用它。然而,为了保持简单和简洁,许多更复杂的查询和方法从这个条目中被省略了。因此,我建议任何觉得这很有帮助的人,请查看更多的在线正则表达式文档,因为那里有很多。