x 射线变压器信息图
通过一个单一的视觉潜入变形金刚训练和推理计算
本文末尾提供了完整信息图的更高分辨率版本的链接。哈维尔·Ideami@losslandscape.com 的可视化
transformer 架构已经在 NLP 领域和深度学习领域产生了一场革命。大量的应用程序受益于这些模型并行处理序列的能力,同时通过它们实现的注意机制来实现对它们的上下文的更深入理解。而 GPT-3 现在是深度学习社区的热门话题。
一开始,理解转换器如何处理序列可能有点困难。当处理一个复杂的模型时,许多人喜欢研究模型的计算如何改变通过它的张量的形状。
为此,我创建了 X 射线变压器信息图,它允许你在训练和推理阶段完成从变压器计算开始到结束的旅程。它的目标是通过对单个可视资产的分析和探索,快速而深入地理解变压器模型的内部计算。
本文末尾有一个链接,可以下载下面完整信息图的更高分辨率版本。
本文末尾提供了分辨率更高的完整信息图的链接。哈维尔·Ideami@losslandscape.com 的可视化
颜色代码
在查看该信息图时,首先要考虑的是图右下方显示的代表不同重要阶段的颜色。
- 蓝色灯表示训练阶段。
- 绿灯绿色表示推断阶段。
- 紫色表示编码阶段(用于两个阶段),在编码器中,紫色模块属于训练阶段,绿色模块属于推理阶段。
- 深色红色表示解码阶段(用于两个阶段)。在解码器中,紫色模块表示编码器数据,深红色模块表示解码器数据,绿色模块通常表示推理阶段。
一旦弄清楚了颜色代码,接下来就是注意里面有数字的粉色圆圈。这些帮助我们看到执行的一般路径,首先通过编码器,然后是解码器。
两侧的两个大箭头是编码器和解码器阶段执行的一些关键阶段的提示。
模型
为了生成这个信息图,我使用了一个生成聊天机器人的小型 transformer 模型。聊天机器人通过成对的问题和答案进行训练。这个特定的模型是针对与电影和连续剧,尤其是科幻相关的问题和答案进行训练的。问答示例:
- “在《扩展》系列中,你最喜欢的角色是什么?”:“娜奥米永田肯定!”
- “你最喜欢太空堡垒卡拉狄加中的哪个角色?”:“卡拉·瑟瑞斯,她很棒”
在信息图的标题下面,我们可以回顾一下在研究计算的形状时要考虑的最重要的参数。
- 这个小模型是用 8 的批量训练出来的。
- 该模型的多头关注部分有 4 头。
- 有 3 个编码器层和 3 个解码器层。
- 该模型的输出词汇的大小为 950
- 横跨模型使用的嵌入尺寸为 32 。
培训阶段。新的一批
当我们开始训练模型时,我们从信息图的左下方开始旅程。
我们从数据加载器中获得一个批处理。我们使用的批量大小为 8,因此每批包含 8 个输入序列和 8 个输出序列。
标记化、数值化、填充和遮罩
8 个输入序列根据需要进行填充(添加填充标记),以便它们都具有相同的长度,在该特定批次的情况下,长度为 10(该批次中最长序列的长度)。批处理的输出序列也是如此。
这些序列已经被标记化和数值化,以准备被模型摄取。当训练循环提取新的一批时,序列被数值化并以维度为 8x10 (BS x SeqLen) 的张量来构造。
编码器中的屏蔽
接下来,我们需要创建一个遮罩,它将帮助我们确保序列中的额外填充元素不会被注意机制所考虑。因此,我们将掩码中属于输入序列中填充标记的那些位置设置为假或 0。
嵌入和位置编码
现在我们必须创建我们的嵌入,所以我们将 8x10 张量发送到嵌入模块,并得到一个**8x 10x 32(BS x SeqLen x EmbedSize)**张量,因为在这个小示例中我们的嵌入大小是 32(512 是 transformer 模型的典型嵌入大小)。
为此,我们添加了位置编码模块的结果,这将有助于模型考虑整个序列中定位的差异。
我们编码器的第一层准备接收这个 8x10x32 张量。
编码器
编码器做的第一件事就是创建 3 个 8x10x32 张量的副本来产生模型的 Q,K,V 元素,也就是查询,键和值。
这 3 个张量首先通过 3 个线性模块(每个张量一个)。在这个小例子中,这些线性模块不会改变维度(但是如果我们希望的话,它们可以)。
在通过这些线性模块之后,我们到达必须将计算分成 4 个头的点(8 是头的数量的典型值。在这个小例子中,我使用 4)。使用 4 个头部将允许注意力机制从不同角度解释序列。
在计算上,我们可以用两个简单的步骤来准备这个阶段。
- 首先,我们可以将张量重新配置为将嵌入大小维度 32 拆分为两个维度 4 和 8 。4 是人头数。8,在我们的例子中,等于嵌入维数/头数 32 / 4 = **8,**我们称之为 dimK 或 dK (这个 dimK 值可以用不同的方法计算)。
- 现在我们做一个转置操作来定位批量大小为 1 之后的头数的维度。这就产生了新的形状:8 x 4 x 10 x 8(BS x Heads x SeqLen x dimK)。这个形状告诉我们:对于该批中的每个元素,我们将有 4 个头。并且这些头部中的每一个都将具有 10(序列长度)x 8(dimK)的矩阵。
自我关注
我们现在的目标是计算注意力得分。编码器执行所谓的自我关注。自我关注有助于我们将每个输入序列的不同部分与它们自身以及序列本身的其余部分进行比较。
从概念上来说,我们正在探索序列的不同部分应该给予自身的不同部分多大的关注。
为了找出这一点,我们将把查询和键张量相乘。
- 为了将它们相乘,我们需要转置 K 张量的后一半。
- 一旦 K 张量被转置,我们得到两个可以相乘的形状: 8x4x10x8 * 8x4x8x10 。
- 注意,我们真正相乘的是最后两个维度: 10x8 * 8x10 。
- 这将产生注意力分数张量,其形状为:8 x4 X10 x 10(BS x Heads x SeqLen x SeqLen)。
- 这些是我们的自我关注分数。对于批次中的每个元素,以及 4 个头中的每一个,我们有一个 10x10 矩阵,它表示我们序列中的每个部分应该对同一序列中的每个部分给予多少关注。
接下来我们要做的是应用一个面具。这是因为,请记住,我们确保了批处理中的所有序列都具有完全相同的长度。为此,我们必须将填充标记添加到比该批中最大的序列更短的序列中。
所以我们现在应该屏蔽(使之非常小或非常负)那些张量部分,这些张量部分引用序列中有填充标记的部分。因此,我们应用掩码并消除序列中与这些填充标记相对应的部分的影响。
现在我们将把一个 softmax 模块应用于张量的 10x10 矩阵,这样每一行的所有数字总和为 1,将每一行转换成一个概率分布。
这些是我们的软自我关注分数。对于每批中的每一个序列,在每一个头部中,序列的每一部分和它自身的每一部分之间的联系有多强,对序列的每一部分的所有影响的总和等于 1。
现在我们有了注意力分数,我们应该把它们应用到 V 张量的值上。我们希望根据****自关注计算的结果来转换编码器的值。
我们的关注分有8 x4 X10 X10(BS x Heads x SeqLen x SeqLen)的形状。而我们的 V 张量的形状是8 x4 X10 x8(BS x Heads x SeqLen x dimK)。请记住,我们实际上是在乘以后两个维度,所以我们是在乘以 10x10 * 10x8 。这就产生了一个新的维度为8 x4 X10 x8(BS x Heads x SeqLen x dimK)的张量。
至此,我们已经结束了自我关注阶段。我们通过乘以查询和关键字得出了注意力分数。然后将这些注意力分数应用于值以获得最终的注意力张量。
是时候让把 4 个头合二为一了。要做到这一点,我们做之前的逆操作,结合换位和重新配置得到一个新的形状8 X10 x32(BS x Heads x EmbSize)。
在将结果张量通过一个线性模块之后,我们到达了我们的第一个跳跃连接。我们将把我们当前的张量添加到进入编码器层的原始张量中。然后我们将应用层标准化来保持数据值在一个好的范围内。
接下来,我们将我们的 8x10x32 张量通过一个前馈层,然后应用另一个跳过连接,将结果张量添加到进入前馈层的张量中(并像前面一样归一化结果)。
我们可以可选地在先前计算的不同阶段应用丢失模块,例如在执行跳过连接添加时或在注意阶段结束时。
重复和上升
精彩!这是编码器的一层。完全相同的计算将被应用 x 次,对应于我们在编码器中的层数。
注意,进入编码器层的张量和退出编码器层的张量具有完全相同的形状** : 8x10x32。这就是为什么我们可以一个接一个地链接尽可能多的编码器层。**
一旦我们到达最终的编码器层,我们就获得了最终的 8x10x32 张量。该编码器输出张量稍后将在编码器-解码器注意机制(出现在**解码器层)**中使用,以提供将与解码器的问题张量交互的键和值。
解码器
但在此之前,让我们进入下一步。解码器的底部。
在解码器的底部,我们有一个 8x14 张量(BS x SeqLen) ,它包含 8 个回复短语序列。像往常一样,在创建数据集和数据加载器时,这些短语已经被标记化和数值化了(并且它们根据需要包含填充标记)。
移动一位
需要注意的是,在解码器中,我们将序列向右移动一个位置。因此第一个记号将是句子的开始记号,而不是句子的第一个单词。我们为什么要这样做?
我们这样做是因为我们不希望我们的模型只是将解码器的输入复制并粘贴到其输出中。我们希望它预测下一个单词(或者字符,但是在这个例子中我们是预测单词)**。**因此,如果我们不将所有内容向右移动一位,位置 N 的预测将是解码器输入中位置 N 处的当前单词,我们可以直接访问它。为了防止这种情况发生,我们将解码器的输入向右移动一个位置。这样,在每个阶段,解码器必须预测位置 N,但是只能看到现有短语的位置 N-1。
解码器中的掩蔽
我们还创建了解码器掩码,它包含对角线上方的真和对角线下方的假。这个掩码有助于防止解码器考虑尚未看到的句子部分。
让我们更深入地了解这一点,因为它至关重要。解码器的自我关注掩码确保每个自我关注向量不会关注未来的位置。
因此,如果我在计算序列中位置 3 的单词的自我关注分数,我会屏蔽掉那个位置之后的所有位置。这是必要的,因为当我们构建我们的输出短语时,我们需要基于迄今为止生成的单词来执行我们的计算,并且我们不应该能够知道稍后将出现的未来单词。
在某种程度上,我们正在防止解码器在训练过程中作弊。例如,如果我们想要预测一个短语的第二个单词,我们应该只考虑输出短语的第一个位置。如果我们想预测第五个单词,我们应该只考虑第一、第二、第三和第四个位置。
请注意,解码器的掩码也屏蔽了输出序列中可能存在的填充标记。因此解码器的屏蔽将填充标记的屏蔽添加到序列中未来位置的屏蔽中。
与编码器一样, 8x14 张量被发送到嵌入模块,该模块输出一个**8x 14x 32****(BS x Heads x SeqLen x EmbSize)**张量,因为嵌入大小是 32。接下来,将位置编码模块的结果添加到其中。
在这一点上,我们到达第一个解码器层,这将被重复许多次,作为我们希望有多少解码器层。
在解码器层,我们进入两个连续的注意阶段。
- 首先,我们将有一个自我关注阶段,非常类似于编码器阶段,但使用解码器数据。
- 接下来,我们将有一个编码器-解码器关注阶段,其中问题(Q)张量将来自解码器的**,但是键(K)和值(V)张量将来自之前执行的编码器的输出。您可以在信息图中找到这个混音阶段,如大的箭头所示,该箭头将编码器的末端连接到解码器层的第二个注意阶段的键和值。**
解码器的第一自关注级与编码器的相同,除了使用输出序列作为数据,并使用解码器的掩码。
在第二注意阶段、编码器-解码器注意中,发生了类似的过程,但有一些关键区别:
- 问题 Q 矢量由 8x14x32 (BS x SeqLen x EmbSize)解码器矢量构成
- 键和值向量、 K 和 V 由来自编码器相位结果的相同**8x10x 32(BS x SeqLen x EmbSize)**张量的两个副本形成。
我们在屏蔽阶段使用的屏蔽是在编码器中使用的,即输入序列一。这样,我们确保只考虑输出序列和没有填充标记的输入序列部分之间的连接。输出序列本身已经被解码器层的第一级屏蔽。
注意力分数不再是一个方阵。我们在8x 4x 14x10(BS x Heads x InSeqLen x outeqlen)张量内获得一个 14x 10 的矩阵,反映出我们正在获得的不同部分、的输出序列与的不同部分输入序列之间的关系。
像往常一样,在执行注意力计算之后,我们将结果连接起来,在这种情况下获得一个 8x14x32 (BS x SeqLen x EmbSize)张量。
在我们执行解码器层的自关注和编码器-解码器关注阶段之后,我们移动到同一层内的最后阶段,首先将 8x14x32 张量通过前馈模块,然后,正如我们在编码器中所做的那样,将该计算的结果添加到该模块(前馈模块)的输入,并将层归一化模块应用于该结果。(如编码器部分所述,在此过程以及其他过程中使用 dropout 是一种潜在的可选添加。)
然后,在现有数量的解码器层上重复该解码器层过程**。像以前一样,每个解码器层的输入和输出具有相同的形状,8x 14x 32(BS x Heads x EmbSize),这使得很容易将这些层/过程中的一些链接起来。**
解码器的输出
一旦我们对所有解码器层进行了迭代**,我们就获得了一个最终的 8x14x32 张量,然后我们将该张量传递给一个线性层,该层的输出形状为8 x14 x950(BS x SeqLen x OutVocabSize), 950 是聊天机器人输出的**词汇大小。****
这个 8x14x950 张量包含了我们对于这次迭代的预测**。对于该批中的每个序列,并且对于每个序列的 14 个部分中的每一个,我们获得 950 个值,对应于作为输出短语的下一个位置的候选的潜在的 950 个单词。**
是时候计算损失了,我们的目标和我们当前的预测之间的差异。
我们将那个预测张量放入一个交叉熵损失模块**,它也接收我们的 8x14 目标张量。交叉熵模块结果是训练过程的这次迭代的损失值。**
该损失值然后通过网络反向传播**,权重被更新**,并且过程再次重启,编码器处理新的批次。****
我们继续尽可能多的时期的训练过程,直到我们达到我们的目标(在准确性、损失值、验证损失等方面)。每 x 次迭代或历元,我们保存网络的权重,以防我们想在其他时间重新开始训练,或者让最近训练的权重准备好随时执行推理。
推理
这就是训练过程。现在让我们快速看一下推理过程。一旦转换器被训练,我们如何执行和运行它?
为此,我们必须关注信息图的绿色部分。
在图形的左下方,我们看到开始推理过程的绿色的推理列。****
当我们运行训练好的变形金刚时,我们将输入一个的单一输入短语,例如****:你最喜欢《扩展》系列中的哪个角色?****
这就是为什么我们的批量会是 1 的原因。我们仍然需要指定批量大小,因为计算需要它。这个短语被标记化和数字化,得到一个 1x9 (BS x SeqLen) 张量,因为这个示例短语有 9 个标记(["what’s “,” your “,” favourite “,” character “,” in “,” The “,” expanse “,” series ‘,’?'].请注意,我们可以用许多不同的方式来标记短语,并且有许多标记器可供您使用。这个小例子使用了一种简单的方法来标记短语。
我们还创建我们的输入掩码,在这个推断阶段,它将在每个位置都有真**。**
接下来,我们将输入张量传递给嵌入模块,并将其与位置编码模块输出相加,以获得一个1x9x 32(BS x Heads x EmbSize)张量。
编码器相位,推断
编码器的第一层以与训练迭代期间类似的计算开始,但这次使用这个 1x9x32 张量。编码器层重复直到我们到达最后一层,在那里我们获得一个 1x9x32 张量,它将被解码器用来提供编码器-解码器注意阶段的键和值。
我们转到解码器,这里的情况有所不同。
解码器阶段、推理
- 我们对解码器的输入最初将由句首令牌形成。(请记住,我们将所有内容向右移动一个位置,以防止解码器将其输入复制到输出中)。
- 然后,解码器将输出我们应该添加到回复句子中的下一个单词作为结果(最初仅由句首标记形成)。
- 我们将采用新的预测单词并将其添加到解码器的输入端,重复过程和生成回复句子的下一个单词**。**
- 这再次被添加到解码器的输入,我们继续这样,直到解码器输出句尾标记。
因此,我们对解码器的输入最初将具有 1x1 的形状。在下一次迭代中,它将变成 1x2 ,然后是 1x3 等等,直到它达到 1xN ,其中 N 是解码器循环的迭代次数,直到我们获得句尾令牌。
在循环中的每一点,我们创建一个适应每次迭代的新掩码。初始形状为 1x1x1 (BS x SeqLen x SeqLen) 。在下一次迭代中,它变成 1x2x2 ,然后是 1x3x3 ,直到到达 1xNxN ,这时我们到达了句尾标记。像以前一样,这个掩码帮助我们防止模型在计算注意力分数时关注序列中的未来位置(超出它正在考虑的当前位置)。
然后,我们将浏览多个解码器层,,每一层都执行我们之前看到的相同计算:
- 一个自我关注阶段
- 一个编码器-解码器注意级
- ****前馈阶段。
在解码器层的末尾,我们将获得一个1 NX 950(BS x SeqLen x OutVocabSize)张量,其中 N 是我们在解码器循环中的位置。第一次迭代我们得到一个 1x1x950 张量,第二次得到一个 1x2x950 张量等等。
我们将得到的张量通过一个 softmax 模块来获得一个概率分布**。这种分布给了我们对于输出短语的每个单词获得输出词汇表的每个元素的概率。我们将考虑张量最后部分的概率,属于我们想要预测的下一个单词的概率。**
我们可以从这个概率分布中以多种方式对进行采样,以获得一个 1x1 张量,它包含将被添加到当前输出句子末尾的新单词**。******
然后我们继续循环并添加新单词到输出句子中,直到我们找到句尾标记**。**
就是这样,我们有一个很酷的变形金刚聊天机器人,它的计算已经通过这个 x 射线变形金刚可视化展示给我们了。
您可以从专门的 github repo 下载更大版本(10488 x 14000 像素)的 x 射线变压器可视化**😗*
通过一个单一的视觉潜入变形金刚训练和推理计算。X 射线转换器信息图…
github.com](https://github.com/javismiles/X-Ray-Transformer)**
例外:使用 Tensorflow 从头实现
甚至比《盗梦空间》还要好
图一。Xception 架构(来源:图片来自原论文)
卷积神经网络(CNN)已经走过了漫长的道路,从 LeNet-style、AlexNet、VGG 模型(使用简单的卷积层堆栈用于特征提取,最大池层用于空间子采样,一个接一个地堆叠)到 Inception 和 ResNet 网络(在每层中使用跳过连接和多个卷积和最大池块)。自从引入以来,计算机视觉中最好的网络之一就是盗梦空间网络。Inception 模型使用一堆模块,每个模块包含一堆特征提取器,这允许它们用更少的参数学习更丰富的表示。
例外纸—https://arxiv.org/abs/1610.02357
正如我们在图 1 中看到的,异常模块有 3 个主要部分。入口流、中间流(重复 8 次)和出口流。
图二。Xception 架构的入口流(来源:图片来自原论文)
入口流具有两个卷积层块,后跟一个 ReLU 激活。该图还详细提到了过滤器的数量、过滤器大小(内核大小)和步长。
还有各种可分离的卷积层。还有最大池层。当步幅不等于 1 时,也提到步幅。也有跳过连接,我们使用“添加”来合并两个张量。它还显示了每个流中输入张量的形状。例如,我们从 299x299x3 的图像大小开始,在进入流之后,我们得到 19x19x728 的图像大小。
图 3。Xception 架构的中间和出口流程(来源:图片来自原论文)
类似地,对于中间流和出口流,该图清楚地解释了图像大小、各种层、过滤器的数量、过滤器的形状、池的类型、重复的数量以及最后添加完全连接的层的选项。
此外,所有卷积和可分离卷积层都要进行批量归一化。
可分离卷积层
图 4。可分离卷积层(来源:图片由作者创建)
可分离卷积包括首先执行深度方向的空间卷积(其分别作用于每个输入声道),然后执行混合所得输出声道的点方向卷积。-来自 Keras 文档
假设我们有一个大小为(K,K,3)的输入张量。k 是空间维度,3 是特征图/通道的数量。正如我们从上面的 Keras 文档中看到的,首先我们需要对每个输入通道分别实施深度方向的空间卷积。所以我们用 K,K,1——图像/张量的第一通道。假设我们使用大小为 3x3x1 的过滤器。并且该滤波器应用于输入张量的所有三个通道。因为有 3 个通道,所以我们得到的维数是 3x3x1x3。这在图 4 的深度方向卷积部分中示出。
在这之后,将所有 3 个输出集合在一起,我们获得大小为(L,L,3)的张量。L 的维数可以与 K 相同,也可以不同,这取决于先前卷积中使用的步长和填充。
然后应用逐点卷积。该滤波器的尺寸为 1x1x3 (3 个通道)。过滤器的数量可以是我们想要的任何数量。假设我们使用 64 个过滤器。所以总尺寸是 1x1x3x64。最后,我们得到一个大小为 LxLx64 的输出张量。这显示在图 4 的逐点卷积部分。
为什么可分卷积比正规卷积好?
如果我们在输入张量上使用正常卷积,并且我们使用 3x3x3 的滤波器/核大小(核大小— (3,3)和 3 个特征图)。我们需要的过滤器总数是 64 个。所以一共 3x3x3x64。
相反,在可分离卷积中,我们首先在深度方向卷积中使用 3x3x1x3,在点方向卷积中使用 1x1x3x64。
区别在于过滤器的维度。
传统卷积层= 3 x3x 3 x 64 = 1728
可分离卷积层=(3 x3x 1 x 3)+(1 x1 x3 x 64)= 27+192 = 219
正如我们所见,无论是在计算成本还是在内存方面,可分离卷积层都比传统卷积层更具优势。主要区别是在正常卷积中,我们是多次变换图像。而每一次变换都要用掉 3 x3x 3 x 64 = 1728 次乘法。在可分离卷积中,我们只对图像进行一次变换——在深度方向卷积中。然后,我们把变换后的图像简单地拉长到 64 通道。不必一次又一次地变换图像,我们可以节省计算能力。
图 5。ImageNet 上的异常性能 vs Inception(来源:图片来自原始论文)
图 6。JFT 数据集上的异常性能与初始(来源:图片来自原始论文)
算法:
- 导入所有必要的层
- 为以下各项编写所有必要的函数:
a.conv-巴特诺姆块体
b.可分离 Conv- BatchNorm 块
3.为 3 个流中的每一个写一个函数——入口、中间和出口
4.使用这些函数构建完整的模型
使用张量流创建异常
*#import necessary libraries*
**import** **tensorflow** **as** **tf**
**from** **tensorflow.keras.layers** **import** Input,Dense,Conv2D,Add
**from** **tensorflow.keras.layers** **import** SeparableConv2D,ReLU
**from** **tensorflow.keras.layers** **import** BatchNormalization,MaxPool2D
**from** **tensorflow.keras.layers** **import** GlobalAvgPool2D
**from** **tensorflow.keras** **import** Model
创建 conv-巴特诺姆块:
*# creating the Conv-Batch Norm block*
**def** conv_bn(x, filters, kernel_size, strides=1):
x = Conv2D(filters=filters,
kernel_size = kernel_size,
strides=strides,
padding = 'same',
use_bias = **False**)(x)
x = BatchNormalization()(x)**return** x
Conv 批处理范数块将张量——x、滤波器数量——滤波器、卷积层的内核大小——内核大小、卷积层的步距——步距作为输入。然后我们对 x 应用一个卷积层,然后应用批量归一化。我们添加 use_bias = False,这样最终模型的参数数量,将与原始论文的参数数量相同。
创建 SeparableConv- BatchNorm 块:
*# creating separableConv-Batch Norm block*
**def** sep_bn(x, filters, kernel_size, strides=1):
x = SeparableConv2D(filters=filters,
kernel_size = kernel_size,
strides=strides,
padding = 'same',
use_bias = **False**)(x)
x = BatchNormalization()(x)**return** x
与 Conv 批处理范数模块结构相似,只是我们使用了 SeparableConv2D 而不是 Conv2D。
入口、中间和出口流量功能:
*# entry flow*
**def** entry_flow(x):
x = conv_bn(x, filters =32, kernel_size =3, strides=2)
x = ReLU()(x)
x = conv_bn(x, filters =64, kernel_size =3, strides=1)
tensor = ReLU()(x)
x = sep_bn(tensor, filters = 128, kernel_size =3)
x = ReLU()(x)
x = sep_bn(x, filters = 128, kernel_size =3)
x = MaxPool2D(pool_size=3, strides=2, padding = 'same')(x)
tensor = conv_bn(tensor, filters=128, kernel_size = 1,strides=2)
x = Add()([tensor,x])
x = ReLU()(x)
x = sep_bn(x, filters =256, kernel_size=3)
x = ReLU()(x)
x = sep_bn(x, filters =256, kernel_size=3)
x = MaxPool2D(pool_size=3, strides=2, padding = 'same')(x)
tensor = conv_bn(tensor, filters=256, kernel_size = 1,strides=2)
x = Add()([tensor,x])
x = ReLU()(x)
x = sep_bn(x, filters =728, kernel_size=3)
x = ReLU()(x)
x = sep_bn(x, filters =728, kernel_size=3)
x = MaxPool2D(pool_size=3, strides=2, padding = 'same')(x)
tensor = conv_bn(tensor, filters=728, kernel_size = 1,strides=2)
x = Add()([tensor,x])**return** x
这里我们只需遵循图 2。它从分别具有 32 和 64 个滤波器的两个 Conv 层开始。每次激活后都有一个 ReLU 激活。
然后是一个跳过连接,这是通过使用 Add 完成的。
在每个 skip 连接块中,有两个可分离的 Conv 层,后跟 MaxPooling。跳跃连接本身具有跨度为 2 的 1x1 的 Conv 层。
图 7。中流量(来源:图片来自原论文)
*# middle flow*
**def** middle_flow(tensor):
**for** _ **in** range(8):
x = ReLU()(tensor)
x = sep_bn(x, filters = 728, kernel_size = 3)
x = ReLU()(x)
x = sep_bn(x, filters = 728, kernel_size = 3)
x = ReLU()(x)
x = sep_bn(x, filters = 728, kernel_size = 3)
x = ReLU()(x)
tensor = Add()([tensor,x])
**return** tensor
中间的流程遵循图 7 所示的步骤。
图 8。退出流程(来源:图片来自原纸)
*# exit flow*
**def** exit_flow(tensor):
x = ReLU()(tensor)
x = sep_bn(x, filters = 728, kernel_size=3)
x = ReLU()(x)
x = sep_bn(x, filters = 1024, kernel_size=3)
x = MaxPool2D(pool_size = 3, strides = 2, padding ='same')(x)
tensor = conv_bn(tensor, filters =1024, kernel_size=1, strides =2)
x = Add()([tensor,x])
x = sep_bn(x, filters = 1536, kernel_size=3)
x = ReLU()(x)
x = sep_bn(x, filters = 2048, kernel_size=3)
x = GlobalAvgPool2D()(x)
x = Dense (units = 1000, activation = 'softmax')(x)
**return** x
退出流程遵循图 8 所示的步骤。
创建异常模型:
*# model code*
input = Input(shape = (299,299,3))
x = entry_flow(input)
x = middle_flow(x)
output = exit_flow(x)
model = Model (inputs=input, outputs=output)
model.summary()
输出片段:
**from** **tensorflow.python.keras.utils.vis_utils** **import** model_to_dot
**from** **IPython.display** **import** SVG
**import** **pydot**
**import** **graphviz**
SVG(model_to_dot(model, show_shapes=**True**, show_layer_names=**True**, rankdir='TB',expand_nested=**False**, dpi=60, subgraph=**False**).create(prog='dot',format='svg'))
输出片段:
**import** **numpy** **as** **np**
**import** **tensorflow.keras.backend** **as** **K** np.sum([K.count_params(p) **for** p **in** model.trainable_weights])
输出:22855952
上述代码显示了可训练参数的数量。
使用 Tensorflow 从头开始创建异常模型的完整代码:
***#import necessary libraries***
**import** **tensorflow** **as** **tf**
**from** **tensorflow.keras.layers** **import** Input,Dense,Conv2D,Add
**from** **tensorflow.keras.layers** **import** SeparableConv2D,ReLU
**from** **tensorflow.keras.layers** **import** BatchNormalization,MaxPool2D
**from** **tensorflow.keras.layers** **import** GlobalAvgPool2D
**from** **tensorflow.keras** **import** Model***# creating the Conv-Batch Norm block***
**def** conv_bn(x, filters, kernel_size, strides=1):
x = Conv2D(filters=filters,
kernel_size = kernel_size,
strides=strides,
padding = 'same',
use_bias = **False**)(x)
x = BatchNormalization()(x)**return** x***# creating separableConv-Batch Norm block***
**def** sep_bn(x, filters, kernel_size, strides=1):
x = SeparableConv2D(filters=filters,
kernel_size = kernel_size,
strides=strides,
padding = 'same',
use_bias = **False**)(x)
x = BatchNormalization()(x)**return** x***# entry flow***
**def** entry_flow(x):
x = conv_bn(x, filters =32, kernel_size =3, strides=2)
x = ReLU()(x)
x = conv_bn(x, filters =64, kernel_size =3, strides=1)
tensor = ReLU()(x)
x = sep_bn(tensor, filters = 128, kernel_size =3)
x = ReLU()(x)
x = sep_bn(x, filters = 128, kernel_size =3)
x = MaxPool2D(pool_size=3, strides=2, padding = 'same')(x)
tensor = conv_bn(tensor, filters=128, kernel_size = 1,strides=2)
x = Add()([tensor,x])
x = ReLU()(x)
x = sep_bn(x, filters =256, kernel_size=3)
x = ReLU()(x)
x = sep_bn(x, filters =256, kernel_size=3)
x = MaxPool2D(pool_size=3, strides=2, padding = 'same')(x)
tensor = conv_bn(tensor, filters=256, kernel_size = 1,strides=2)
x = Add()([tensor,x])
x = ReLU()(x)
x = sep_bn(x, filters =728, kernel_size=3)
x = ReLU()(x)
x = sep_bn(x, filters =728, kernel_size=3)
x = MaxPool2D(pool_size=3, strides=2, padding = 'same')(x)
tensor = conv_bn(tensor, filters=728, kernel_size = 1,strides=2)
x = Add()([tensor,x])**return** x***# middle flow***
**def** middle_flow(tensor):
**for** _ **in** range(8):
x = ReLU()(tensor)
x = sep_bn(x, filters = 728, kernel_size = 3)
x = ReLU()(x)
x = sep_bn(x, filters = 728, kernel_size = 3)
x = ReLU()(x)
x = sep_bn(x, filters = 728, kernel_size = 3)
x = ReLU()(x)
tensor = Add()([tensor,x])
**return** tensor***# exit flow***
**def** exit_flow(tensor):
x = ReLU()(tensor)
x = sep_bn(x, filters = 728, kernel_size=3)
x = ReLU()(x)
x = sep_bn(x, filters = 1024, kernel_size=3)
x = MaxPool2D(pool_size = 3, strides = 2, padding ='same')(x)
tensor = conv_bn(tensor, filters =1024, kernel_size=1, strides =2)
x = Add()([tensor,x])
x = sep_bn(x, filters = 1536, kernel_size=3)
x = ReLU()(x)
x = sep_bn(x, filters = 2048, kernel_size=3)
x = GlobalAvgPool2D()(x)
x = Dense (units = 1000, activation = 'softmax')(x)
**return** x***# model code***
input = Input(shape = (299,299,3))
x = entry_flow(input)
x = middle_flow(x)
output = exit_flow(x)
model = Model (inputs=input, outputs=output)
model.summary()
结论:
如图 5 和图 6 所示,与 ImageNet 数据集相比,Xception 架构在 JFT 数据集上显示出比 Inception 网络更好的性能提升。《例外》的作者认为,这是因为《盗梦空间》被设计成专注于 ImageNet,因此可能过度适合特定的任务。另一方面,这两种架构都没有针对 JFT 数据集进行过调整。
此外,Inception 有大约 2360 万个参数,而 Xception 有 2280 万个参数。
如图 1 所示,本文中很容易解释 Xception 架构,这使得使用 TensorFlow 实现网络架构变得非常容易。
参考文献:
- Franç ois Chollet,Xception:深度可分卷积深度学习,arXiv:1610.02357 v3【cs。CV],2017
例外:迎接极限开端
除了作者的极端盗梦空间- 图片
异常简介
例外-极限盗梦空间!听起来又酷又极端!而是“为什么取这样的名字?”,有人可能会想!一个显而易见的事情是,作者 Francois Chollet(《Keras》的创作者)受到了《盗梦空间》架构的启发。他在他的摘要中讲述了他是如何看待盗梦空间架构的,我在下面引用了他的摘要。
我们将卷积神经网络中的初始模块解释为常规卷积和深度方向可分离卷积运算之间的中间步骤
——弗朗索瓦·乔莱在《例外报》
今天另一个新的深度学习术语,“”深度可分卷积。现在,我知道你们大多数人会猜测它是某种层次。但是对于那些从未听说过的人来说,不要担心。我在这篇文章中为你做了介绍。我们将深入了解什么是 深度方向可分离卷积 以及如何使用它来构建异常模型。以及它是如何建立在盗梦空间假设上的。和往常一样,我会试着插入更多的插图,让细节更加清晰。让我们开始吧!
盗梦空间假说
在“ 用卷积 ”一文中介绍了 Inception 风格的架构。作者将论文中介绍的模型称为 GoogLeNet, 使用了 Inception 块。这是一个新颖和创新的建筑,现在仍然如此。此外,由于当时的许多体系结构是越来越多的层的堆叠,以增加网络容量,因此它受到了广泛关注。另一方面,《盗梦空间》更有创意,更巧妙!
它不仅仅是通过简单地增加更多的层而变得更深,还变得更广。我们很快就会明白我所说的“宽”是什么意思。初始块接受输入张量,并并行执行卷积和池化的组合。
作者图片
对于看过或读过《盗梦空间》的人来说,你可能会发现这并不完全像一个盗梦空间。是啊,你说得对!我只是用这种方式举例说明,这样每个人都能大致了解它的功能。正如作者所说,你可以称之为“天真版的盗梦空间”。
现在,实际的 Inception 块在卷积的数量、大小以及分层方式方面有一点不同。但是这个幼稚的插图传达了我之前说的“宽”的意思。该模块并行执行不同滤波器大小的卷积。并且输出张量沿着信道维度连接,即一个接一个地堆叠。
再深入一点
现在您已经看到了并行卷积模块,我将更深入地介绍上面的模块。由不同卷积处理的输入张量将是形状(批量、高度、宽度、通道)。在初始块中,在应用 3x3 或 5x5 卷积之前,使用 1x1 卷积来降低输入张量的通道维度。这种通道尺寸的减小是为了在将该张量馈送到后续层时减少计算。你可以在 1x1 卷积文章中找到这个概念的详细解释。
作者图片
同样,这只是另一个需要理解的描述,并不与最初的 Inception 块相同。你可以给或拿一些层,甚至更宽,并做出你自己的版本。输入张量由三个卷积塔分别处理。并且三个独立的输出张量沿着信道维度连接。GoogLeNet 使用多个 Inception 块和一些其他技巧和调整来实现它的性能。我相信,现在你已经知道盗梦空间是做什么的了。
接下来,我们将改造上述区块,使其成为*“极限”!***
让它成为极致
我们将用单层替换每个平行塔中的三个独立的 1x1 层。它看起来会像这样。
具有普通 1x1 层的盗梦空间-作者图片**
我们不是将 1x1 层的输出传递给后面的 3x3 层,而是将它切片并分别传递每个通道。我举个例子说明一下。比方说, 1x1 层的输出是形状 (1x5x5x5)。 我们先不考虑批量维度,只把它看成一个 (5x5x5) 张量。这是沿通道尺寸切割*,如下图所示,并分别送入后续层。*
作者沿信道维度对 1x1 卷积的输出张量进行切片-图像
现在,每个切片都被传递到一个单独的 3x3 层。这意味着每个 3x3 块将不得不处理一个形状为 (5x5x1)的张量。 因此,将有 5 个单独的卷积块,每个切片一个。并且每个卷积块将只有一个滤波器。
每个通道切片被馈送到一个单独的 3×3 层,每个层只有一个过滤器
同样的过程也适用于更大的输入张量。如果逐点卷积的输出是**(5x5x 100)**,则有 100 个卷积块,每个卷积块有一个滤波器。他们所有的输出最终会被连接起来。这种做卷积的方式就是它被命名为 极端 的原因,因为输入张量的每个通道都是单独处理的。
我们已经看够了《盗梦空间》的想法和它的一个 极限 版本。现在,让我们进一步看看是什么驱动了 Xception 架构。
深度方向可分卷积:驱动异常的东西
深度方向上可分离的卷积层是这个例外的动力。它在建筑中大量使用了这一点。这种卷积类似于我们上面看到的初始块的极端版本。但其工作原理略有不同。让我们看看如何!
考虑在**(1x 10 X10 X100)**张量上操作的具有十个 5×5 滤波器的典型卷积层。十个滤波器中的每一个都具有 (5x5x100) 的形状,并且在输入张量上滑动以产生输出。每个 5x5 滤波器在滑过输入时覆盖整个通道尺寸(整个 100)。这意味着典型的卷积运算包括空间维度(高度、宽度)和通道维度。
作者在(10x10x100)张量上滑动的 5x5 卷积滤波器— 图像
如果你不熟悉卷积,我建议你浏览一下卷积是如何工作的?
深度方向可分离层具有两个功能部分,这两个功能部分分割了传统卷积层的工作。这两部分是深度方向卷积和点方向卷积。我们会一个一个地检查。
深度方向卷积
让我们以具有滤波器的深度方向卷积层为例,该滤波器对形状为***(1×5×5×5)*的输入张量进行操作。同样,为了简单起见,我们去掉批处理维,因为它不会改变任何东西,并将其视为一个 (5x5x5) 张量。我们的深度方向卷积将具有五个 3x3 滤波器,每个滤波器用于输入张量的每个通道。并且每个滤波器将在空间上滑过单个通道,并为该通道生成输出特征图。
由于滤波器的数量等于输入通道的数量,输出张量也将具有相同数量的通道。让我们在卷积运算中没有任何零填充,保持 步距 为 1。
作者应用卷积- 图像后输出高度和宽度计算
按照卷积后输出大小的公式,我们的 (5x5x5) 将变成一个 (3x3x5) 张量。下面的插图将使这个想法变得清晰!
作者对深度方向卷积运算-图像的说明
这是深度方向的卷积!你可以看到这和我们在《盗梦空间》中做 极限 卷积的方式差不多。
接下来,我们必须将这个输出张量馈送到执行跨通道相关的逐点卷积。这仅仅意味着它在张量的所有通道上运行。
逐点卷积
逐点卷积是 1x1 卷积的另一个名字。如果我们想增加或减少张量的深度(通道维度),我们可以使用逐点卷积。这也是为什么在《盗梦空间》中使用它来减少 3x3 或 5x5 层之前的深度。在这里,我们要用它来增加深度。但是怎么做呢?
逐点卷积只是一个普通的卷积层,滤镜大小为一个** ( 1x1 滤镜)。因此,它不会改变卷积后的空间输出大小。在我们的例子中,深度方向卷积的输出张量的大小为 (8x8x5) 。如果我们应用 50 个 1x1 滤镜,我们将得到输出为 (8x8x50) 。并且 RELU 激活被应用于逐点卷积层。**
参见逐点卷积了解更多详细说明及其优点。
结合深度方向卷积和点方向卷积,我们得到了深度方向可分离卷积。从这里姑且称之为 DSC 。
DSC 和 e Xtreme Inception 的区别
在初始块中,首先是逐点卷积,然后是 3x3 或 5x5 层。因为我们将把 DSC 模块堆叠在其他模块之上,所以顺序并不重要。初始块在逐点和随后的卷积层上应用激活函数。但是在 DSC 中, 只是应用一次,在逐点卷积之后。
作者图片
e Xception 作者讨论了激活对 DSC 中深度方向和点方向步骤的影响。并且观察到当没有中间激活时,学习会更快。
作者对带有和不带有中间激活图像的 DSC 的图解
例外架构
作者将整个 Xception 架构分成 14 个模块,其中每个模块只是一堆*【DSC】和池层。这 14 个单元分为三组,即入口流、中间流和出口流。并且每个组分别具有四个、八个和两个模块。最后一组,即出口流,可以选择性地在末端具有完全连接的层。*****
注意:架构中的所有 DSC 层都使用 3x3 的滤波器大小、步长 1 和“相同”填充。并且所有的 MaxPooling 层都使用 3x3 内核和步幅 2。
异常的进入流程
作者的 exception-图片进入流程
上图是例外论文中给出的详细版本。乍一看可能有点吓人,但再看看,这非常简单。
第一个模块包含常规卷积层,它们没有任何 DSC 层。它们接受大小为(-1,299,299,3)的输入张量。第一维度的 -1 代表批量。负的 -1 仅表示批量大小可以是任何值。
常规卷积层和 DSC 卷积层之后都有一个批量归一化层。步幅为 2 的卷积将其减少了近一半。输出的形状显示在使用我们之前看到的卷积公式计算的一侧。
入口流中的第一个卷积模块- 作者的图像
除了第一个模块,入口流中的所有其他模块都有剩余的跳过连接。并行跳跃连接具有一个逐点卷积图层,该图层被添加到主路径的输出中。
例外的中间流程
作者对中间流图像的说明
中间流程中,有 八个 这样的模块,一个接一个。以上模块重复八次,形成中流。中间流中的所有 8 个模块都使用 1 的步距,并且没有任何池层。因此,从入口流通过的张量的空间大小保持不变。通道深度也保持不变,因为所有中间流模块都有 728 个过滤器。这与输入的深度相同。
例外的出口流量
退出流程- 作者图片
出口流只有两个卷积模块,第二个没有任何跳过连接。第二个模块使用全局平均池,不像前面的模块使用最大池。平均汇集层的输出向量可以直接馈送到逻辑回归层。但是我们也可以选择使用中间全连接层。
综上
Xception 模型包含与 Inception V3 几乎相同数量的参数,但是在 ImageNet 数据集上比 Inception V3 略胜一筹。但它在 JFT 图像分类数据集(谷歌的内部数据集)上以更好的优势击败了 Inception V3。在几乎相同的参数数量下表现更好,可以归功于其架构工程。
XGBoost
梯度助推器
震撼世界的 GBM
来源: Unsplash
现在让我们来解决这个问题——XGBoost。这是梯度推进家族里最受欢迎的堂弟。XGBoost 以其闪电般的速度冲进了现场,几乎一致地扭转了对它有利的局面。很快,通过 XGBoost 的梯度推进成为 Kaggle 竞赛中的王者,很快,它就渗透到了商业世界。
有几项关键创新让 XGBoost 如此有效:
显式正则化
与正则化贪婪森林相似,XGBoost 在目标函数中也有一个显式正则项。
γ是惩罚 T 的正则项,即树中叶子的数量,λ是惩罚 w 的正则项,即不同叶子的权重。
这是一个比我们在正则化贪婪森林中看到的一些方法简单得多的正则化术语。
目标函数不可知的实现
梯度推进算法的关键要素之一是目标函数的梯度或导数。我们之前看到的所有实现都使用了针对特定损失函数的预先计算的梯度公式,从而将算法中可以使用的目标限制为库中已经实现的集合。
XGBoost 使用我们在本系列前一部分讨论过的牛顿-拉夫森方法来逼近损失函数。
现在,由树结构组成的复杂递归函数可以使用泰勒近似法近似为可微分形式。在梯度推进的情况下,我们采用二阶近似,这意味着我们使用两项——一阶导数和二阶导数——来近似函数。
让,
近似损失函数:
第一项,损失,在树构建阶段 t 是恒定的,因此它不会给优化目标增加任何值。所以去掉它并简化我们得到的,
将ω代入正则项,我们得到:
我们正在讨论的 f(x)本质上是一棵具有叶权重 w 的树。因此,如果我们将 Iⱼ定义为叶 j 中的实例集,我们可以将树结构直接代入方程,并简化为:
将该等式设为零,我们可以找到 wⱼ的最佳值,如下所示:
将它放回损失函数并简化,我们得到:
所有这些使我们能够做的是将目标函数从算法的核心工作中分离出来。通过采用这个公式,对目标函数/损失函数的唯一要求是它必须是可微的。具体来说,损失函数应该返回一阶和二阶导数。
参见此处的获取所有预构建到 XGBoost 中的目标函数列表。
树构建策略
树构建策略在某种程度上介于经典梯度推进和正则化贪婪森林之间。传统的梯度增强将每一阶段的树视为一个黑盒,而正则化贪婪森林通过在每一步更新森林的任何部分来在叶子级别上操作。XGBoost 采取了中间立场,认为在前一次迭代中生成的树是神圣不可侵犯的,但是在为任何迭代生成树时,它不使用标准的杂质度量,而是使用我们在树构建过程的上一节中导出的基于梯度的损失函数。在经典的梯度提升中,损失函数的优化发生在树建立之后,而 XGBoost 也在树建立过程中得到优化。
通常不可能列举出所有可能的树形结构。因此,实现了一种贪婪算法,该算法从单叶开始,并基于简化的损失函数迭代地向树添加分支
分裂查找算法
树学习的一个关键问题是寻找最佳分裂。通常,我们必须列举所有特征上所有可能的分裂,然后使用杂质标准来选择最佳分裂。这就是所谓的精确贪婪算法。它需要大量的计算,尤其是对于连续的和高基数的分类特征。当数据放不进内存时,这也是不可行的。
为了克服这些低效,本文提出了一种近似算法。它首先根据特征的百分位数提出候选分割点。概括地说,算法是:
- 使用特征的百分位数为所有特征提出候选分割点
- 将连续要素映射到由候选分割点分割的桶
- 基于候选分割点聚合所有要素的梯度统计数据
- 根据汇总统计数据在建议中寻找最佳解决方案
加权分位数草图
上面讨论的算法中的一个重要步骤是候选分裂的提议。通常,特征的百分位数用于使候选项在数据上均匀分布。一组以分布式方式快速有效地完成这项工作的算法称为分位数绘制算法。但是在这里,问题有点复杂,因为需要一个加权分位数草图算法,该算法根据梯度对实例进行加权(这样我们就可以从误差最大的实例中学习到最多的东西)。因此他们提出了一种新的数据结构,这种数据结构具有可证明的理论保证。
稀疏感知的分裂发现
这是 XGBoost 的另一个关键创新,它来自于现实世界的数据集是稀疏的。这种稀疏性可能有多种原因,
- 数据中存在缺失值
- 频繁零值
- 特征工程的产物,例如一键编码
为此,作者决定使算法意识到稀疏性,以便它能被智能地处理。他们制作的方式简单得令人难以置信。
他们在每个树节点给出了一个默认的方向。即,如果值缺失或为零,则该实例在分支中沿默认方向向下流动。并且从数据中学习最佳默认方向
这项创新有双重好处-
- 它有助于对丢失的值进行分组,学习处理它们的最佳方式
- 它通过确保不浪费计算能力来寻找缺失值的梯度统计数据,从而提高了性能。
性能改进
梯度推进算法的所有实现的主要缺点之一是它们非常慢。虽然随机森林中的森林创建是开箱即用的,但梯度提升是在旧基础上建立新基础学习者的顺序过程。XGBoost 出名的原因之一是它的速度惊人。它比现有的实现至少快 10 倍,并且由于具有核外学习功能,它能够处理大型数据集。性能改进的主要创新包括:
用于并行学习的列块
树学习最耗时的部分是将数据排序。该论文的作者建议将数据存储在内存单元中,称为块。每个块中的数据以压缩列(CSC)格式排序。这种输入数据布局在训练之前计算一次,然后再使用。通过以这种排序格式处理数据,树分裂算法简化为对排序列的线性扫描
缓存感知访问
虽然所提出的块结构有助于优化分裂查找的计算复杂度,但是它需要通过行索引间接获取梯度统计。为了克服过程中缓慢的读写操作,作者为每个线程实现了一个内部缓冲区,并在迷你批处理中累积梯度统计信息。当行很大时,这有助于减少运行时开销。
核外计算的块
算法的目标之一是充分利用机器的资源。当 CPU 被进程的并行化利用时,可用的磁盘空间被核外学习利用。我们之前看到的这些数据块存储在磁盘上,一个单独的预取线程不断将数据提取到内存中,以便计算继续进行。他们使用两种技术来提高磁盘 I/O 操作的速度
- 数据块压缩,即每个数据块在存储前被压缩,在读取时被动态解压缩。
- 块分片,将一个块分成多个部分,并存储在多个磁盘上(如果有)。并且预取器通过在两个盘之间交替来读取块,由此增加了盘读取的吞吐量。
超参数
XGBoost 有很多关于它的文章和博客,涵盖了超参数以及如何调优它们,所以我甚至不打算尝试。
任何超参数的唯一真实来源是官方文档。看着那里长长的超参数列表可能有些吓人,但是在正常的用例中,您不会碰到其中的大多数。
主要的有:
eta[默认值=0.3,别名:learning_rate]
- 更新中使用的步长收缩以防止过度拟合。每一步 boosting 后,我们可以直接得到新特征的权重,eta 收缩特征权重,使 boosting 过程更加保守。
- 范围:[0,1]
伽马[默认值=0,别名:min_split_loss]
- 在树的叶节点上进行进一步划分所需的最小损失减少。伽玛越大,算法就越保守。
- 范围:[0,∞]
最大深度[默认值=6]
- 树的最大深度。增加该值将使模型更加复杂,并且更有可能过度拟合。当 tree_method 设置为 hist 时,0 仅在损失导向增长策略中被接受,并且它指示对深度没有限制。请注意,XGBoost 在训练深度树时会大量消耗内存。
- 范围:[0,∞](当 tree_method 设置为 hist 时,仅在损失导向增长策略中接受 0)
最小 _ 子 _ 重量[默认值=1]
- 一个孩子所需的最小体重总和。如果树划分步骤导致实例权重之和小于 min_child_weight 的叶节点,则构建过程将放弃进一步的划分。在线性回归任务中,这仅仅对应于每个节点中需要的最小实例数。min_child_weight 越大,算法就越保守。
- 范围:[0,∞]
子样本[默认值=1]
- 训练实例的子样本比率。将其设置为 0.5 意味着 XGBoost 会在生成树之前随机采样一半的训练数据。这将防止过度拟合。子采样将在每个提升迭代中发生一次。
- 范围:(0,1)
列样本 _ 字节树,列样本 _ by 级别,列样本 _ by 节点[默认值=1]
- 这是一组用于对列进行二次采样的参数。
- 所有 colsample_by*参数的范围为(0,1),默认值为 1,并指定要进行二次抽样的列的分数。
- colsample_bytree 是构造每个树时列的子样本比率。对于每个构建的树,进行一次子采样。
- colsample_bylevel 是每个级别的列的子样本比率。对于树中达到的每个新的深度级别,进行一次子采样。从为当前树选择的列集中对列进行子采样。
- colsample_bynode 是每个节点的列的子样本比率(拆分)。每次评估新的分割时,进行一次二次采样。从为当前级别选择的列集中对列进行子采样。
- colsample_by*参数累积工作。例如,具有 64 个特征的组合{‘colsample_bytree’:0.5,‘colsample_bylevel’:0.5,‘colsample_bynode’:0.5}将在每次分割时留下 8 个特征可供选择。
lambda[默认值=1,别名:reg_lambda]
- 关于权重的 L2 正则化项。增加该值将使模型更加保守。
alpha[默认值=0,别名:reg_alpha]
- 关于权重的 L1 正则项。增加该值将使模型更加保守。
更新—2020 年 2 月 13 日
树木逐叶生长
在发表这篇文章之后,我意识到我还没有谈到 XGBoost 中的一些后续开发,比如逐叶树的生长,以及如何针对新的更快的实现对参数进行微调。
我们将在本系列的下一篇博客中讨论 LightGBM,它实现了逐叶的树增长,这带来了巨大的性能提升。XGBoost 还在基于直方图的树分裂策略中实现了逐叶策略。
逐叶增长策略虽然更快,但如果数据很小,也会更快地过度拟合。因此,通过超参数使用正则化或帽树复杂性是非常重要的。但是如果我们只是像以前一样继续调 max_depth 来控制复杂度,那就不行了。 num_leaves (控制一棵树的叶子数量)也需要一起调。这是因为在相同的深度下,逐叶树生长算法可以生成更复杂的树。
通过将 tree_method 参数设置为“hist”并将 grow_policy 参数设置为“lossguide ”,可以在 XGBoost 中启用逐叶树构建
参考
- 贪婪函数近似:一种梯度推进机器。安。统计学家。29 (2001 年),第 5 号,1189-1232。
- 陈,田琦&盖斯特林,卡洛斯。(2016).XGBoost:一个可扩展的树提升系统。785–794.10.1145/2939672.2939785.
原载于 2020 年 2 月 12 日【http://deep-and-shallow.com】。
XGBoost 用于多类分类
在这篇文章中,我使用 XGBoost 根据客户的个人资料来预测不同的接触点
照片由阿图尔·斯兹比罗在 Shutterstock 上拍摄
客户接触点是你的品牌自始至终的客户接触点。例如,客户可能会通过邮件折扣、短信、电子邮件促销等找到你的业务。根据您公司的营销团队,有许多类型的接触点!
接触点预测是一个类似于客户细分的重要工具,因为如果一个公司的营销工作以接触点更有可能最终购买的特定客户群为目标,它可以更好地服务于该公司的营销工作。通过他们的客户档案,公司将更深入地了解客户的偏好,并针对每个客户执行精确定制的营销材料。
该项目的目的是根据每个客户的个人资料和之前的接触点,预测他们的下一个最佳接触点行动。让我们看看我们需要做些什么!
图一。客户数据
当我探索一个数据集时,我喜欢做笔记,这允许我保留一个清单,以便在项目的后期进一步清理/探索。从第一眼看,我们可以看到 SocialMedia 列中缺少值,而在 touch points 列下,我们看到一系列可能导致购买的接触点。
数据清理:每个数据科学项目中最重要的一步
检查缺失值和插补!
我们看到 25%的条目没有接触点,2.9%的条目没有信用评级。缺少接触点可能意味着客户无需通过任何在线推广链接就可以购买。信用评级的缺失可能会带来新的客户。
我还注意到 SocialMedia 列下缺少的值用空格表示。
处理每列的缺失值:
1。社交媒体—将“”改为“U”(表示未知的社交媒体状态)
2。信用评级—将 NaN 改为“新”(表示新客户)
3 .接触点—我假设接触点是按从左到右的顺序排列的,所以最后一个值是客户在购买前的最近接触点。因此,我将只取最后一个值作为标签,来预测应该为未来的客户分配哪个接触点
data = df[df['nTouchpoints']!=0].reset_index().drop('index', axis=1)
data['recent_touchpoint'] = data['touchpoints'].apply(lambda x: x.split()[-1])
data.recent_touchpoint.value_counts()
我们标签的接触点价值计数
社会媒体和信用评级的估算值
数据可视化
我想探究细分变量与数据集中其他变量的关系。一些可能性可以是基于收入、平均支出、信用评级或组合的细分。我们必须检查任何变量之间的多重共线性。多重共线性降低了估计系数的精度,这削弱了我们的回归模型的统计能力。我将用图表和热图来探索这些关系。
收入图
平均支出图
从 P1 到 P4 的每条线图中,我们无法观察到每个信用评级的平均支出的一般模式。紫色线显示了每个信用评级组的平均支出及其 95%的置信区间。比较两个图,似乎信用等级为 6 的高收入者比其他人花费少,信用等级为 7 的低收入者比其他人花费多。
共线性图
我们看到接触点的数量与平均支出高度相关,但是相关性低于 0.5。由于多重共线性的程度并不严重,所以我将保持变量不变。
数据探索
当您构建模型时,偏态分布可能是一个棘手的问题,尤其是在有异常值的情况下。检查异常值的最好方法是绘制分布图!机器学习的一般规则是确保我们所有的数值变量都在大致相同的范围内,并且呈正态分布,因此我们必须进行标准化/规范化。
我做的一些观察:
1.年龄分布看起来呈正态分布,略微偏左
2。已婚>单身>离异>未知客户数量。
3。对于细分市场和社交媒体,每个类别中的客户数量大致相同。
4。不同信用等级的客户分布看起来很正常,只是稍微有点偏右。
5。大多数顾客只经过一个接触点
6。收入分配看起来很正常。
7。平均支出分布可以被认为是指数分布,但显然有异常值,我们必须处理。
支出中的异常值
我删除了所有收入为 18156.7 美元的客户,因为我们的平均支出仅为 91.52 美元,远远低于最大值。将它们留在数据中只会扭曲我们的支出分布。移除异常值时,我们必须确保平均值/中值不会受到很大影响,并注意我们不会引入任何偏差。
请注意,所有 3 个变量的最大-最小范围互不相同。当我们做进一步的分析时,比如多元线性回归,归因收入会由于其更大的值而内在地影响结果。因此,重要的是将数据标准化和规范化,使所有变量都在同一范围内。
我使用一个稳健的定标器(QuantileTransformer):类似于归一化,但它使用四分位数范围,所以它对异常值是稳健的。
标准化数值变量
为模型构建准备数据
我们使用一次性编码将分类变量(婚姻、细分、社交媒体、信用评级)转换为二进制变量。我们可以使用来自 sklearn 的内置 OneHotEncoder 中的,但是出于同样的目的,我选择编写自己的函数!
一个热编码分类变量
现在我们的数值变量遵循正态分布。我们将开始使用最近的接触点来标记我们的数据。让我们看看在我们的数据中有多少可能的标签。
创建从标签到唯一整数的映射,反之亦然,以便稍后进行标签和预测
对于我们的模型数据,选择我们希望在模型中使用的列,我将使用分层抽样来检索它们。关于分层抽样的更多细节,我在之前的帖子中解释了这个过程——Keras,告诉我我的书的类型。
用 GridSearchCV 建模和调优
作为基线模型,我使用随机森林。基线是一种使用启发式、简单汇总统计、随机性或机器学习来为数据集创建预测的方法。你可以使用这些预测来衡量基线的性能(例如,准确性)——这个指标将成为你与任何其他机器学习算法进行比较的标准。在这种情况下,我使用多类逻辑损失,因为我们预测下一个接触点的概率,我想找到所有概率分布之间的平均差异。此外,我还使用了微型 F1 分数,因为我们的标签类别不平衡。
随机森林模型的结果
我选择随机森林分类器只是因为它运行速度快,并且我能够使用 GridSearchCV 高效地迭代到可能的最佳模型。在用 GridSearchCV 初始化和调整我的 RandomForestClassifier 模型之后,我得到了 1.0 的训练精度和 0.77688 的测试精度,这表明过度拟合。
我们的随机森林分类器似乎更关注平均支出、收入和年龄。
XGBoost
XGBoost 是一种基于决策树的集成机器学习算法,使用了梯度提升框架。在涉及非结构化数据(图像、文本等)的预测问题中。)人工神经网络往往优于所有其他算法或框架。然而,当涉及到中小型结构化/表格数据时,基于决策树的算法目前被认为是同类最佳的。
XGBoost 的实现为模型调优、计算环境和算法增强提供了几个高级特性。它能够执行三种主要形式的梯度增强(梯度增强(GB)、随机 GB 和正则化 GB),并且足够健壮以支持微调和增加正则化参数。
这种集成方法试图基于先前的“较弱”分类器来创建强分类器。通过迭代地在彼此之上添加模型,前一个模型的误差被下一个预测器校正,直到训练数据被模型准确地预测或再现。长话短说,我们正在使用梯度下降更新模型!XGBoost 一直是赢得许多 Kaggle 比赛的秘密秘诀,所以现在你知道为什么这种方法在机器学习爱好者中如此受欢迎了。
对于我们最初的模型,这是我得到的结果。
正如我们所看到的,XGBoost 在第一次模型迭代中已经超越了 Random Forest。让我们开始微调我们的模型,尽管我不会详细介绍如何调整我的模型。关于逐步调谐的更多信息可以在这里找到!
我调整的参数:
min_child_weight:一个节点可以表示的最小样本数,以便进一步拆分
max_depth:调整这个来避免我们的树长得太深而导致过度拟合
reg_alpha:规范化程度
最终模型
与我们的 XGBoost 模型的第一次迭代相比,我们设法在准确性和微观 F1 分数方面略有提高。我们实现了更低的多级物流损失和分类错误!
我们看到高特征重要性分数被分配给“未知”婚姻状态。这可能是因为只有 44 个客户的婚姻状况“未知”,因此为了减少偏差,我们的 XGBoost 模型为“未知”特征分配了更多的权重。
如果我有足够的计算能力,我会调整 gamma,subsample 和 colsample_bytree 以及学习率。由于时间有限,我只关注 max_depth 和 reg_alpha(应用正则化来减少过度拟合)。试一试,用参数玩一玩!
完整的代码实现可以在我的 Github 这里找到!
感谢您阅读我的文章。如果我能得到关于如何改进我的数据科学项目的评论,我将不胜感激,我也一直希望与任何对机器学习感兴趣的人合作:)
干杯!
XGBoost Python 示例
杰拉尔特皮克斯贝
XGBoost 是 Extreme Gradient Boost 的缩写(我写过一篇文章提供了渐变提升的要点这里)。与渐变增强不同,XGBoost 利用正则化参数来帮助防止过度拟合。
假设我们想要构建一个模型来预测给定平方英尺的房子的价格。
我们从一个任意的初始预测开始。在回归的情况下,这可以是平均值,在分类的情况下,这可以是 0.5。
对于每个样本,我们用下面的公式计算残差。
残差=实际值—预测值
假设,应用公式后,我们最终得到以下残差,从左到右开始采样。
接下来,我们使用线性扫描来决定沿给定特征(平方英尺)的最佳分割。线性扫描意味着我们在第一对点(它们的平均值)之间选择一个阈值,然后在下一对点(它们的平均值)之间选择一个阈值,以此类推,直到我们探索了所有的可能性。
在我们的例子中,我们从选择阈值 500 开始。
对应的树是:
注意每片叶子中的值是怎样的残差。也就是自变量的预测值和实际值之差,而不是给定样本的房价。
为了比较分割,我们引入了增益的概念。增益是由分割带来的精度提高。增益计算如下。
在哪里
λ和γ都是超参数。Lambda 是一个正则化参数,可降低预测对单个观测值的敏感度,而 Gamma 是在树的叶节点上进行进一步划分所需的最小损失减少量。
比方说,我们任意地将λ和γ设置为如下。
我们可以继续计算初始分割的增益。
我们继续计算对应于剩余排列的增益。
然后,我们使用产生最大增益的阈值。在这种情况下,最佳阈值是平方英尺< 1000 。因此,我们以下面的树结束。
我们对每片叶子重复这个过程。也就是说,我们选择一个阈值来
当增益为负时,这意味着分割不会产生比我们让树保持原样的情况下更好的结果。
我们仍然需要检查在分割树叶时使用的不同阈值不会提高模型的准确性。
增益为正。因此,我们仍然受益于进一步拆分树。这样做,我们得到了下面的树。
我们检验了将样本面积在 1,000 平方英尺和 1,600 平方英尺之间的进行拆分是否有益。
增益为负。因此,我们让这棵树保持原样。
我们仍然需要检查是否应该分割左边的叶子(平方英尺< 1000)。
同样,增益为负。因此,最终的决策树是:
当提供样本时,决策树必须返回单个标量值。因此,我们使用下面的公式,该公式考虑了单个叶节点中的多个残差。
第一个预测是初始预测和树的预测之和乘以学习率。
假设学习率为 0.5,该模型做出以下预测。
新的残差是:
然后,我们使用这些残差构建另一个决策树,并重复这个过程,直到我们达到最大估计数(默认为 100)。一旦我们完成了模型的训练,XGBoost 模型作为一个整体所做的预测就是初始预测和每个决策树所做的预测之和乘以学习率。
Python 代码
与其他机器学习模型不同,XGBoost 不包含在 Scikit-Learn 包中。因此,
XGBoost 库有很多依赖项,这使得安装它成为一场噩梦。你很幸运,我经历了那个过程,所以你不必经历。到目前为止,安装 XGBoost 最简单的方法是安装 Anaconda(如果您还没有安装)并运行以下命令。
conda install -c conda-forge xgboostconda install -c anaconda py-xgboost
一旦安装了 XGBoost,我们就可以继续导入所需的库。
import pandas as pd
import xgboost as xgb
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
就像上面的例子一样,我们将使用 XGBoost 模型来预测房价。我们使用 Scikit-Learn API 将波士顿房价数据集加载到笔记本中。
boston = load_boston()
X = pd.DataFrame(boston.data, columns=boston.feature_names)
y = pd.Series(boston.target)
我们使用 head 函数来检查数据。
X.head()
以下是不同特性及其缩写的列表。
- 按城镇分列的人均犯罪率
- ZN 面积超过 25,000 平方英尺的住宅用地比例
- 每个城镇非零售商业英亩数的比例
- CHAS Charles River 虚拟变量(= 1,如果区域边界为河流;否则为 0)
- 氮氧化物浓度(百万分之一)
- RM 每个住宅的平均房间数
- 1940 年以前建造的自有住房的年龄比例
- DIS 加权到五个波士顿就业中心的距离
- 放射状公路可达性指数
- 每 10,000 美元征收全价值财产税
- 按城镇分列的师生比例
- B 1000(Bk — 0.63),其中 Bk 是按城镇划分的黑人比例
- LSTAT %较低的人口地位
- 以千美元为单位的 MEDV 自有住房中值
为了评估我们模型的性能,我们将数据分为训练集和测试集。
X_train, X_test, y_train, y_test = train_test_split(X, y)
接下来,我们初始化 XGBRegressor 类的一个实例。我们可以选择λ和γ的值,以及估计器的数量和最大树深度。
regressor = xgb.XGBRegressor(
n_estimators=100,
reg_lambda=1,
gamma=0,
max_depth=3
)
我们使我们的模型适合训练集。
regressor.fit(X_train, y_train)
在决定房价时,我们可以检查每个特征的相对重要性。
pd.DataFrame(regressor.feature_importances_.reshape(1, -1), columns=boston.feature_names)
正如我们所看到的,低阶层人口的比例是房价的最大预测因素。
最后,我们用我们的模型预测了波士顿的一所房子的价格。
y_pred = regressor.predict(X_test)
我们使用均方差来评估模型的性能。均方误差是预测值和实际值的平方之差的平均值。
mean_squared_error(y_test, y_pred)
XGBoost:理论与实践
Marc-Olivier Jodoin 在 Unsplash 上拍摄的照片
了解最流行的算法之一是如何工作的,以及如何使用它
介绍
XGBoost 代表 eXtremeGradientBoosting,它是梯度提升树算法的开源实现。由于其预测能力和易用性,它已经成为 Kaggle 竞赛中最受欢迎的机器学习技术之一。它是一种监督学习算法,可用于回归或分类任务。
不管它的未来主义名称如何,只要我们先过几个概念,它其实并没有那么难理解:决策树和梯度提升。如果你已经熟悉了这些,可以直接跳到“XGBoost 如何工作”。
决策树
决策树可以说是你能找到的最容易解释的 ML 算法,如果与正确的技术结合使用,可能会非常强大。
决策树有这个名字是因为它的视觉形状,看起来像一棵树,有一个根和许多节点和叶子。想象一下,你有一份泰坦尼克号幸存者的名单,上面有一些信息,比如他们的年龄和性别,还有一个二元变量告诉你谁在灾难中幸存,谁没有。您现在想要创建一个分类模型,根据这些数据来预测谁将幸存下来。一个非常简单的例子是这样的:
作者图片
正如您所看到的,决策树只是一系列简单的决策规则,这些规则组合在一起,产生了对所需变量的预测。
梯度推进
Boosting 是一种集成方法,这意味着它是一种将几个模型的预测组合成一个模型的方法。它是通过依次获取每个预测值并基于其前任的误差对其建模(对表现更好的预测值给予更大的权重)来实现的:
- 使用原始数据拟合第一模型
- 使用第一模型的残差拟合第二模型
- 使用模型 1 和模型 2 的总和创建第三个模型
梯度增强是一种特定类型的增强,之所以这样称呼是因为它使用梯度下降算法来最小化损失函数。
XGBoost 如何工作
既然您已经理解了决策树和梯度提升,那么理解 XGBoost 就变得容易了:它是一种梯度提升算法,使用决策树作为其“弱”预测器。除此之外,它的实现是专门为优化性能和速度而设计的。
从历史上看,XGBoost 对于结构化表格数据表现得相当好。如果您正在处理非结构化数据,如图像,神经网络通常是更好的选择。
超参数
实现 XGBoost 时要选择哪些最重要的超参数,如何调优?
助推器
booster
是 boosting 算法,有三种选择:gbtree
、gblinear
或dart
。默认选项是gbtree
,这是我在本文中解释的版本。dart
是一个类似的版本,它使用 dropout 技术来避免过度拟合,而gblinear
使用广义线性回归来代替决策树。
寄存器 _alpha 和寄存器 _lambda
reg_alpha
和reg_lambda
分别是 L1 和 L2 的正规化术语。这些数字越大,模型就越保守(不容易过度拟合,但可能会遗漏相关信息)。两者的推荐值都在 0-1000 之间。
最大深度
max_depth
设置决策树的最大深度。这个数字越大,模型就越不保守。如果设置为 0,那么树的深度没有限制。
子样品
subsample
是训练预测器时使用的样本比率的大小。默认值为 1,表示没有采样,我们使用全部数据。例如,如果设置为 0.7,则 70%的观测值将被随机采样以用于每次提升迭代(每次迭代取一个新样本)。这有助于防止过度拟合。
数量估计者
num_estimators
设置助推轮数,等于设置要使用的助推树数。这个数字越大,过度拟合的风险就越大(但是低数字也会导致低性能)。
如何使用 XGBoost
为了展示 XGBoost 在实践中是如何工作的,让我们做一个简单的练习,使用 Python 实际预测 Kaggle 比赛中的泰坦尼克号幸存者。
从 Kaggle 下载我们的数据后,我们将导入所有必要的库和我们的训练数据:
**IN:** import pandas as pd
from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_scoredf = pd.read_csv("C:/Users/p005520/Downloads/titanic/train.csv")
请注意from xgboost import XGBClassifier
。这仅仅是因为我们已经通过从终端运行pip install xgboost
在我们的计算机上安装了 xgboost。XGBClassifier
用在这里是因为这是一个分类问题。对于回归问题,用XGBRegressor
代替。用于处理数据和计算性能指标的其他库。
**IN:**
dummies = pd.get_dummies(df['Sex'])
df = pd.concat([df, dummies], axis=1)X = df[[‘Age’,’female’,’male’]]
y = df[‘Survived’]seed = 42
test_size = 0.3
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, random_state=seed)
我们的数据中有许多变量,但这一次,我们将坚持上一个示例中的两个变量:“性别”和“年龄”。
这个程序块的前两行从“Sex”中创建虚拟变量。这需要将“性别”从字符串转换为整数,成为两个不同的变量:“男性”和“女性”,根据乘客的性别,这两个变量等于 1 或 0。
接下来的两行定义了我们的目标变量(“存活”)和我们将用于预测它的变量。最后 4 行用于分割我们的训练集和测试集:我们的训练集将用于创建我们的 XGBoost 模型,而测试集将用于测量它的性能。
**IN:** model = XGBClassifier(subsample = 0.7, max_depth = 4)
model.fit(X_train, y_train)
print(model)y_pred = model.predict(X_test)accuracy = accuracy_score(y_test, y_pred)
print(“Accuracy: %.2f%%” % (accuracy * 100.0))**OUT:** XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
colsample_bynode=1, colsample_bytree=1, gamma=0,
learning_rate=0.1, max_delta_step=0, max_depth=4,
min_child_weight=1, missing=None, n_estimators=100, n_jobs=1, nthread=None, objective='binary:logistic', random_state=0,
reg_alpha=0, reg_lambda=1, scale_pos_weight=1, seed=None,silent=None, subsample=1, verbosity=1)Accuracy: 80.60%
这里是我们实际训练 XGBoost 的地方,在前两行。注意subsample = 0.7
和max_depth = 4
,这里我手动定义了一些超参数。如果您想设置不同于默认选项的新超参数,只需将它们添加到列表中。我们的模型在测试集中产生了 80.6%的准确率,现在让我们看看如何将它应用于新数据:
submission_input = pd.read_csv("C:/Users/p005520/Downloads/titanic/test.csv")dummies = pd.get_dummies(submission_input[‘Sex’])
submission_input = pd.concat([submission_input, dummies], axis=1)submission_X = submission_input[[‘Age’,’female’,’male’]]submission = model.predict(submission_X)
submission_df = pd.DataFrame({‘PassengerId’:submission_input[‘PassengerId’],
‘Survived’:submission})
submission_df.to_csv(‘submission.csv’, index = False)
在这个模块中,我们导入了 Kaggle 的提交文件,进行了与我们的训练集相同的虚拟处理,用model.predict(submission_X)
应用了我们训练过的预测器,然后将其保存到 csv。它在 Kaggle 的排行榜上得分如何?
作者图片
正如你所看到的,我们离排行榜的顶端还很远,但我们仍然成功地预测了 76%的人的存活率,只使用了两个特征,没有特征工程和没有超参数调整,这将是我们工作的逻辑下一步。
我希望您现在理解了 XGBoost 是如何工作的,以及如何将它应用到真实数据中。尽管 XGBoost 具有固有的性能,但是超参数调整和特征工程可以使您的结果产生巨大的差异。
如果你想了解更多关于特征工程的知识来改进你的预测,你应该阅读这篇文章,它概述了你可以用来转换你的变量和创建新变量的主要技术:
了解数据科学工作流程中最重要的步骤
towardsdatascience.com](/feature-engineering-3a380ad1aa36)
随时联系我 LinkedIn 如果你想进一步讨论,这将是一种荣幸(老实说)。
高维数据集上的 XGBoost 与 LightGBM
速度和性能的比较
乔希·卡拉布雷斯在 Unsplash 上的照片
我最近完成了一个多类分类问题,作为一个数据科学家职位的家庭作业。这是比较梯度推进决策树的两种最新实现 XGBoost 和 LightGBM 的好机会。
这两种算法都非常强大,在性能最好的机器学习模型中非常突出。
该数据集包含超过 6 万个观测值和 103 个数值特征。目标变量包含 9 个不同的类。
(图片由作者提供)
由于这篇文章的重点是 XGBoost 和 LightGBM 的比较,所以我将跳过探索性的数据分析和数据争论部分。我已经设法消除了 11 个特征和大约 2000 个异常值。
值得注意的是,数据争论过程中应用的所有技术都是在训练集和测试集分离后完成的。否则,我们将不得不处理数据泄露,这是机器学习中的一个严重问题。
特征空间是高度稀疏的(即特征大部分由零组成)。下面是显示特征稀疏率的直方图。
(图片由作者提供)
LightGBM
由微软研究人员创建的 LightGBM 是梯度推进决策树(GBDT)的一种实现。在 LightGBM 之前,GBDT before 的现有实现会随着实例或特性数量的增加而变慢。LightGBM 旨在解决这个效率问题,尤其是对于大型数据集。
LightGBM 使用两种技术来解决大型数据集的效率问题:
- 梯度单侧采样
- EFB(独家功能捆绑)
如果你想了解更多,我有一篇关于 LightGBM 如何使用这些技术的独立文章:
是什么让它更快更高效
towardsdatascience.com](/understanding-the-lightgbm-772ca08aabfa)
让我们回到我们的实现。LightGBM 要求数据采用特定的格式,因此我们使用 Dataset 函数。
import lightgbm as lgblgb_train = lgb.Dataset(X_train, y_train)
lgb_test = lgb.Dataset(X_test, y_test)
超参数在 LightGBM 和 XGBoost 的性能中起着关键作用。您可能需要花费大量时间来调优超参数。最终,你会创造出你自己的方法或策略来加速调整的过程。
有很多超参数。有些在准确性和速度方面更重要。其中一些主要用于防止过度拟合。
超参数可以作为字典传递给模型。这些超参数给了我最好的参数。
params = {
'boosting_type': 'gbdt',
'objective': 'multiclass',
'metric': 'multi_logloss',
'num_class':9,
'max_depth':9,
'num_leaves': 100,
'min_data_in_leaf':300,
'learning_rate': 0.03,
'feature_fraction': 0.7,
'bagging_fraction':0.8,
'bagging_freq':10,
'lambda_l1': 1,
'verbose': 0
}
没有严格的规则来定义最佳超参数值。这更像是一个受过教育的试错过程。如果你知道一个特定的超参数是做什么的,调优过程将比尝试随机值更有效。
超参数
- Max_depth:单棵树的最大深度。
- Num_leaves:它控制一棵树的叶子数量。LightGBM 使用逐叶的树生长算法,因此 num_leaves 是控制树复杂度的主要参数。
- Min_data_in_leaf:它表示一片叶子上所需的最小样本数(即观察值),这对控制过拟合非常重要。
- Feature_fraction:在每个节点随机选择的要素的比率。
- Bagging_fraction 和 bagging_freq 也有助于避免过度拟合。
LightGBM 超参数的完整列表可以在这里找到。
我们现在可以训练模型了。下面的代码块使用定型集对模型进行定型,并在定型集和验证集上评估模型的性能。
%%timeitgbm = lgb.train(params, lgb_train, num_boost_round=700,
valid_sets=[lgb_train, lgb_test], early_stopping_rounds=10)
训练集和验证集的对数损失分别为 0.383 和 0.418。执行训练平均需要 2 分 26 秒。
您可以在这些模型上实现非常低的损耗,但这会导致过度拟合。更重要的是在训练和测试精度方面有一个平衡的模型。此外,任务的目标是实现 0.41 的对数损失。
这是一个很好的实践,摆弄超参数值,看看它们对精度和过度拟合的影响。
XGBoost
XGBoost 是 GBDT 算法的一个实现。它的效率如此之高,以至于统治了 Kaggle 上的一些主要比赛。
对于 XGBoost,数据集也应该以特定的方式进行格式化。
dtrain = xgb.DMatrix(X_train, y_train)
dtest = xgb.DMatrix(X_test, y_test)
XGBoost 还有许多超参数,需要正确地进行调整,以便创建一个健壮而准确的模型。这里是我发现的超参数值,可以达到令人满意的结果,同时还可以最小化过度拟合。
import xgboost as xgbparams_xgb = {
'boosting_type': 'dart',
'objective':'multi:softmax',
'num_class':9,
'max_depth':7,
'min_child_weight':20,
'gamma':1,
'subsample':0.8,
'colsample_bytree':0.7,
'tree_method':'hist',
'eval_metric':'mlogloss',
'eta':0.04,
'alpha': 1,
'verbose': 2
}
超参数
- Max_depth:一棵树的最大深度。
- Min_child_weight:一个孩子所需的实例权重的最小总和。将它保持在较高的位置可以防止孩子过于具体,从而有助于避免过度适应。
- Gamma:在树的叶节点上进行进一步划分所需的最小损失减少。同样,游戏越大,模型越不可能过度拟合。
- 子样本:在生长树之前随机选择的行的比率。子样也可以用来避免过度拟合。
- Eta:学习率。保持较高的值会使模型快速学习,但同时也会增加过度拟合的机会。
- α:L1 正则化项。
XGBoost 的整个超参数列表可以在这里找到。
我们现在可以训练我们的模型了。我们传递参数、训练集和要运行的回合数。您可以增加回合数并获得更好的精度,但总是有过度拟合的风险。
%%timeitbst = xgb.train(
params_xgb, dtrain , 700,
evals=[(dtrain,'eval'),(dtest, 'eval')],
verbose_eval=True
)
训练集和验证集的对数损失分别为 0.369 和 0.415。执行训练平均需要 3 分 52 秒。
结论
LightGBM
XGBoost
损失非常接近,因此我们可以得出结论,就准确性而言,这些模型在具有所选超参数值的数据集上表现大致相同。
在速度方面,LightGBM 比 XGBoost 快 40%。
感谢您的阅读。如果您有任何反馈,请告诉我。
XLM:跨语言语言模型
了解基于变压器的自监督架构
莱昂纳多·大久保俊郎在 Unsplash 上的照片
像伯特这样的模特(德夫林等人。艾尔。)或 GPT ( 拉德福德等人。艾尔。在语言理解方面达到了最先进的水平。然而,这些模型仅针对一种语言进行预训练。最近,已经做出努力来减轻单语表示并建立通用的跨语言模型,该模型能够将任何句子编码到共享的嵌入空间中。
在这篇文章中,我们将讨论由艾提出的论文。作者提出了两种跨语言语言建模的方法:
- 无人监管,依赖单语数据
- 受监督,依赖并行数据。
跨语言语言模型(XLM)
在本节中,我们将讨论训练 XLM 的建议方法。
共享子词词汇
该模型对所有语言使用相同的共享词汇表。这有助于为所有语言的标记建立一个公共的嵌入空间。因此,很明显,具有相同文字(字母表)或相似单词的语言更好地映射到这个公共嵌入空间。
为了对语料库进行标记,使用了字节对编码(BPE)。
因果语言建模(CLM)
这是常规的语言建模目标,其中我们最大化一个标记 x_t 出现在给定序列中第’ t ‘个位置的概率,给定该序列中的所有标记 x_ < t (在第’ t 个标记之前的所有标记)。即
通过 XLNet 论文进行因果语言建模
OpenAI 的 GPT 和 GPT-2 就是为此目的而训练的。如果你对这个目标的细节感兴趣,可以参考我写的关于 GPT 和 GPT-2 的文章。
掩蔽语言建模(MLM)
MLM 经 XLM 纸
这是一种去噪自动编码目标,也称为完形填空任务。这里,我们最大化给定屏蔽记号 x_t 出现在给定序列中第‘t位置的概率,给定该序列中的所有记号, x_hat 。即
通过 XLNet 论文进行屏蔽语言建模
伯特和罗伯塔接受过这方面的训练。如果你对这个目标的细节感兴趣,你可以参考我写的关于伯特和罗伯塔的文章。
请注意,伯特和 XLM 的方法之间的唯一区别是,伯特使用成对的句子,而 XLM 使用任意数量的句子流,一旦长度为 256 就截断。
翻译语言建模(TLM)
TLM 经 XLM 纸
CLM 和 MLM 的任务在单语语料库上工作良好,然而,它们没有利用可用的平行翻译数据。因此,作者提出了一个翻译语言建模目标,其中我们从翻译数据中提取一系列平行句子,并从源句子和目标句子中随机屏蔽标记**。例如,在上图中,我们有来自英语和法语句子的屏蔽词。序列中的所有单词都有助于预测给定的屏蔽单词,从而在记号之间建立跨语言映射。**
XLM
在这项工作中,我们考虑了跨语言语言模型预处理与 CLM,MLM,或 MLM 结合 TLM 使用。
— XLM 纸业
XLM 预培训
在本节中,我们将讨论如何在下游任务中利用 XLM 预培训,例如:
- 零镜头跨语言分类
- 监督和非监督神经机器翻译
- 低资源语言的语言模型
- 无监督的跨语言单词嵌入
零镜头跨语言分类
就像在任何其他基于 Transformer 的单语模型中一样,XLM 也在 XNLI 数据集上进行了微调,以获得跨语言分类。
将分类图层添加到 XLM 的顶部,并在英语 NLI 训练数据集上对其进行训练。然后在 15 种 XNLI 语言上对该模型进行了测试。
由于模型还没有被调整来对来自这些语言的句子进行分类,所以它是一个零尝试的学习例子。
无人监管的 NMT
对于这项任务,作者建议以跨语言语言建模为目标,预训练一个完整的编码器-解码器架构。该模型在几个翻译基准上进行评估,包括 WMT 的 14 英-法,WMT 的 16 英-德和 WMT 的 16 英-罗。
监督 NMT
这里,编码器和解码器加载了来自 XLM 的预训练权重,然后在监督翻译数据集上进行微调。这实质上实现了多语言的语言翻译。
更多关于多语言的 NMT 的信息,请参考这篇博客。
低资源语言建模
这就是“具有相同脚本或相似单词的语言提供更好的映射”的原因。例如,维基百科上用尼泊尔语写的句子有 10 万个,用印地语写的句子大约多 6 倍。此外,这些语言有 80%的标记是相同的。
因此,跨语言语言模型将明显有益于尼泊尔语的语言模型,因为它是在相对更多的相似对应数据上训练的。
无监督的跨语言单词嵌入
最后,由于我们有一个共享的词汇表,XLM 模型的查找表(或嵌入矩阵)给了我们跨语言的单词嵌入。
结论
在本文中,我们讨论了跨语言语言模型不仅有利于在一般的下游任务中获得更好的结果,而且有利于通过对类似的高资源语言进行训练来提高低资源语言的模型质量,从而获得更多相关数据。
这里有一个链接指向最初的 XLM GitHub 库。
这里有一个链接链接到 huggingface 的 XLM 架构实现和预训练权重。
参考
[## 跨语言语言模型预训练
最近的研究证明了生成性预训练对英语自然语言理解的有效性
arxiv.org](https://arxiv.org/abs/1901.07291)
XLNet:用于语言理解的自回归预训练
了解基于变压器的自监督架构
蒂姆·莫斯霍尔德在 Unsplash 上的照片
像 BERT,OpenAI GPT 这样的艺术语言模型在最近的自然语言处理中已经成为明星。这些模型基于转换器架构,该架构将基于 RNN 和基于卷积的模型挤出了市场。
在本文中,我们将讨论 XLNET 模型,它是在最近的一篇论文中提出的: XLNet:用于语言理解的广义自回归预训练。该模型解决了 BERT 的某些缺点,并通过在 20 项任务中超越 BERT成功克服了这些缺点。
如果你有兴趣了解 BERT 或 Transformers 背后的概念,可以考虑阅读一下 this (BERT) 和 this (Transformer) 。
伯特怎么了?
由 AllenNLP 进行的屏蔽 LM 演示
输入噪声
BERT 的一个主要问题本质上是它对屏蔽序列的预训练目标,即去噪自动编码目标。屏蔽序列非常有助于理解语言语料库中的趋势,然而,在微调时,序列不会被屏蔽。
然而,BERT 在预训练期间使用的【MASK】等人工符号在微调时间的真实数据中不存在,从而导致预训练-微调差异。
独立性假设
BERT 最大化联合条件概率 p(x_t | x_hat) ,其中 x_t 为掩码项 x_hat 为记号序列。它读作,在给定序列 x_hat 中所有记号的情况下,屏蔽记号 x_t 出现在第‘t位置的概率。
这给出了独立性假设的直觉,即每个被屏蔽的记号被单独重建。我们将在后面的部分中清除这一点。
XLNET
与 BERT 相反, XLNet 是一个自回归模型。这实质上消除了对输入去噪的依赖。
然而,自回归模型大多因其单向性而受到批评。因此,为了克服这一点,XLNet 提出了一个新的置换语言建模目标来克服这种单向性。
置换语言建模
如前所述,XLNet 提出了一种从两个世界(即自动编码和自回归)中吸取精华的机制。它没有自动编码目标中的输入去噪,并消除了传统自回归目标的单向性。
为了实现这一点,在对联合概率p(x _ t | x _(I<t)),进行因式分解时,XLNet 不像在传统的自回归模型中那样使用固定的向前或向后因式分解顺序,而是最大化一个序列的对数似然性 w.r.t 因式分解顺序的所有可能排列。
具体来说,对于长度为 T 的序列 x ,有 T!不同的订单执行一个有效的自回归因子分解。直观地说,如果模型参数在所有分解订单中共享,那么模型将学习从两侧的所有位置收集信息。
置换语言建模来自 XLNet 论文
为了更详细地说明这个目标,让我们举一个例子。考虑上图,一个序列 x 有 4 个令牌。为简单起见,我们只考虑x3的注意力计算。观察上面每个图下面的排列顺序。
- 而取顺序3->2->4->1, 3 恰好是序列中的第一个令牌。因此,没有其他标记对其注意力计算有贡献。因为它们在当前排列中不先于 3。
- 按照2->4->3->1, 3 的顺序,前面是和 4 ,因此它们有助于其注意力计算。
- 同理,对于1->4->2->3和4->3->1->2-,对应的x _(I<t)贡献给**x _ t .T41 的注意力计算******
更正式地说:
目标函数来自 XLNet 论文
注意在训练时,实际获得序列的排列是不正确的,因为在下游任务的微调或推理过程中,序列不能被排列。因此,变压器中的注意屏蔽被适当地操纵以获得正确的排列;这也是有意义的,因为所提出的架构讨论的是因子分解顺序上的置换,而不是序列顺序。
目标感知表征的双流自我注意
XLNet 论文关于目标感知表征的双流自我关注
常规转换器参数化可能不适用于置换语言模型。为了理解这一点,让我们考虑使用 softmax 的分布的标准公式,其由下式给出:
具有标准变压器参数化的置换 LM
这里的h _θ(x _(z _(<t)),是变压器的隐藏状态为x _(<t)。* 这个术语,绝不依赖于它所预测的位置,即***【z _(<**。这意味着,无论预测的位置是什么,这种分布都是相同的;从而导致无法了解有用的趋势。
因此,为了克服这一点,XLNet 论文提出了一个重新参数化,用于下一个令牌分发,使其成为目标感知的:
具有重新参数化表示的置换 LM
使用修改的表示 g_θ ,其另外将目标位置 z_t 作为输入。因此,使用了两个隐藏状态,而不是一个:
内容流关注
- 内容表示,本质上与标准变压器隐藏状态相同。这种表示对和都进行了编码;上下文x _(z (<t))***以及原令牌 ***x(z_t)。**
数学上:
内容表示
查询流注意
- 查询表示,即只能访问上下文信息**【z _(<t))和目标位置【z _ t】。
数学上:
查询表示
注意最初内容流( h_i )本质上是对应的嵌入向量 ( e_x_i ),而查询流( g_i )最初是可训练向量( w ) 。使用上面的表达式在每一层上更新它们。
部分预测
抛开置换 LM 的所有优点不谈,我们必须承认它很贵。由于置换,这是一个具有挑战性的优化问题。
因此,为了解决这个问题,在给定的序列 z 中,只有一个子序列 z_( > c) 被选择用于预测**,其中 c 被称为切割点。我们只考虑 z_( > c) ,因为它在该序列中具有最长的上下文。**
此外,使用另一个超参数 K ,使得K ~ | z |/(| z | c)。并且我们只选择 1/K 个令牌用于预测**。对于未选择的令牌,不计算它们的查询表示,这样可以节省速度和内存。**
我们将这个部分预测与 BERT 的部分预测进行比较。BERT 使用部分预测,因为屏蔽所有记号没有任何意义。XLNet 做部分预测是因为优化难度大。比如:来个序列:【深,学,是,伟大】。假设 BERT 和 XLNet 都选择预测令牌【深度,学习】。又假设 XLNet 将样本因式分解为【是,伟大,深度,学习】。在这种情况下,
伯特最大化:
- L(BERT) = log p(深度|很棒)+ log p(学习|很棒)
XLNet 最大化:
- L(XLNet) = log p(深度|很棒)+ log p( 深度 |学习很棒)
这清楚地解释了 XLNet 如何捕捉更多的依赖性,即深度和学习之间的依赖性。毫无疑问,伯特学会了大部分的依赖性;但是 XLNet 了解更多。另外,这是上一节中提到的 BERT 中的独立性假设的一个例子。
从《变形金刚 XL》中汲取灵感
最后,提到 Transformer XL 模型,XLNet 从这里借用了关系编码和段递归机制的概念,这使得 Transformer XL 能够在很长的序列上操作。
有趣的事实:Transformer XL 可以参与比 RNNs 长 80%和比 vanilla Transformer 长 450%的序列,并且在评估期间比 vanilla Transformers 快 1800 多倍。
结论
我们已经介绍了另一个最新的模型 XLNet,并讨论了它背后的概念。
XLNet 的代码是作者开源的,你可以在这里找到它。
你可以通过拥抱面部变形金刚找到预先训练好的权重和一个易于使用的模型架构 API。
****新:我已经为 XLNet 写了一篇论文总结和评论。有兴趣的可以看看:https://docs . Google . com/document/d/1 nepiw 67 oqw 1 hprikosub-n8hk 2 a 07-3 pnmtrvmqmkta/edit?usp =分享
参考
**[## XLNet:用于语言理解的广义自回归预训练
有了双向上下文建模的能力,基于去噪自动编码的预训练如 BERT 实现了更好的性能
arxiv.org](https://arxiv.org/abs/1906.08237)** **[## Transformer-XL:超越固定长度上下文的注意力语言模型
变形金刚有学习长期依赖性的潜力,但是受限于…
arxiv.org](https://arxiv.org/abs/1901.02860)** ** [## 伯特:语言理解变形金刚的前期训练
了解基于变压器的自监督架构
medium.com](https://medium.com/swlh/bert-pre-training-of-transformers-for-language-understanding-5214fba4a9af) [## 变形金刚解释
对谷歌 Transformer 模型的详尽解释;从理论到实施
towardsdatascience.com](/transformers-explained-65454c0f3fa7)**
Oracle 中的 XML 功能
Oracle 数据库中 XML 的实现和支持概述
XML:简介
总是有在不同来源之间交换数据的广泛需求,而不用担心接收者将如何使用它或它将如何显示。XML 为我们做了那件事。这是 W3C(万维网联盟)的一项倡议,允许信息以人类和机器容易理解的有意义的结构和规则进行编码。
XML 代表 e X 可扩展 M arkup L 语言。XML 不是 HTML 的替代品。
HTML 的设计重点是如何“显示”数据,而 XML 的设计重点是如何“存储”和“传输”数据。
XML 本身不做任何事情。
让我们看一个样本 xml 文档:
**<message>
<to>Tom</to>
<from>Bill</from>
<body>Send me your phone number</body>
</message>**
上面的 XML 文档包含一条消息,其主体包含发送者和接收者信息。但是它本身并不做任何事情。它只是构造和储存信息。必须有人编写软件代码来接收、发送或显示它。
像 <从> 或者 <到> 这样的标签不是预定义的。它们是由这个 XML 文档的作者编写的。
从这个意义上说,XML 是“可扩展的”。这意味着它没有固定的元素集(不像 HTML)。
因此,简而言之,我们可以说 XML 是一种独立于软件和硬件的工具或方法来构造、存储和携带信息。
XML 结构
XML 文档有一个从“根”元素开始的树形结构。一个例子如下:
<?xml version=”1.0"?>
<Employees>
<Empl id=”1">
<FirstName>Bill</FirstName>
<LastName>
Watterson
</LastName>
<Dept>
Finance
</Dept>
</Empl>
</Employees>
<Employees>
<Empl id=”1">
<FirstName>
Bill
</FirstName>
<LastName>
Watterson
</LastName>
<Dept>
Finance
</Dept>
</Empl>
</Employees>
第一行是 XML 声明。它定义了 XML 版本。下一行声明了这个 XML 文档的“根”元素。因此,“员工”是这里的根元素。像“雇员”、“名字”、“姓氏”和“部门”这样的其他元素是子元素。在 EMPL 元素中,有一个值为“1”的字段 id。它被称为该元素的属性。属性提供了关于元素的附加信息。属性值总是用引号括起来(单引号或双引号)。语法正确的 XML 文档称为“格式良好的”XML。
一个文档类型定义(DTD) 定义了一个 XML 文档的合法构件。它定义了一个包含合法元素和属性列表的文档结构。DTD 文档示例如下:
**<?xml version=”1.0"?>
<!DOCTYPE message [
<!ELEMENT message (to,from,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>**
上面的 DTD 是这样解释的:
上面的 DTD 是这样解释的:
!DOCTYPE note 定义了这个文档的根元素是 message。
!元素 note 定义了 note 元素包含四个元素:“to、from、body”。
!元素 to 将 to 元素定义为“#PCDATA”类型。
!元素 from 将 from 元素定义为“#PCDATA”类型。
!元素 body 将 body 元素定义为“#PCDATA”类型。
description (#PCDATA)指定已解析的字符数据。解析的数据是 XML 元素的开始标记和结束标记之间的文本。解析的字符数据是没有子元素的文本。
XML 模式定义(XSD) 文档是基于 XML 的 DTD 的替代方案。
“有效的”XML 文档是“格式良好的”XML 文档,它也符合 DTD 或 XSD 的规则。
可扩展样式表语言(Extensible Stylesheet Language 的缩写)
乔尔·那仁在 Unsplash 上的照片
如前所述,HTML 标签是预定义的。在 HTML 中定义了一个表格,浏览器已经知道如何显示它。然而,在 XML 中
可以表示任何东西。XML 专注于结构化和存储数据。因此,我们需要一种机制来定义这些数据应该如何在浏览器、手机等中显示。XSL(可扩展风格语言)是完成这一任务的语言。它定义了解释 XML 文档元素的规则。
让我们以一个 XML 文档“CDCatalog.xml”为例,其定义如下:
**<?xml version=”1.0" encoding=”ISO-8859–1"?>
<?xml-stylesheet type=”text/xsl” href=”DisplayCD.xsl”?>
<catalog>
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
</catalog>**
在第二行中,它引用了一个 XSL 文档 DisplayCD.xsl。该文档可以定义如下:
**<?xml version=”1.0" encoding=”ISO-8859–1"?>
<xsl:stylesheet version=”1.0" xmlns:xsl=”**[**http://www.w3.org/1999/XSL/Transform**](http://www.w3.org/1999/XSL/Transform)**">
<xsl:template match=”/”>
<html>
<body>
<h2>Title</h2>
<xsl:for-each select=”catalog/cd”>
<xsl:value-of select=”title”/> <br/>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>**
让我们一行一行地理解 DisplayCD.xsl。
第一行,定义了使用的 XML 版本和编码。
下一行声明了要使用的 XSL 版本和名称空间。xmlns:XSL = " http://www . w3 . org/1999/XSL/Transform "指向官方的 W3C XSLT 名称空间。
元素定义了一个模板。属性将模板与 XML 源文档的根相关联。
元素内部的内容定义了一些要写入输出的 HTML。
元素的 xsl:value-of > 可以用来提取 XML 元素的值。上例中的选择属性包含一个 XPath 表达式。XPath 表达式的工作方式类似于导航文件系统;正斜杠(/)选择子目录。XSL**XSL:for-each**元素可用于选择指定节点集的每个 XML 元素。
当 CDCatalog.xml 在 web 浏览器中打开时,它将显示如下数据,如 DisplayCD.xsl 中所定义:
XMLType
Oracle 使用一种新的数据类型,XML 类型,来帮助处理数据库中的 XML 数据。若要以 XML 格式查看现有表的数据,可以编写如下查询:
**select XMLTYPE(cursor(select * from dept)) XML_Data from dual**
输出如下所示:
**XML_Data
==============**<?xml version=”1.0"?>
<ROWSET>
<ROW>
<DEPTID>10</DEPTID>
<DEPTNAME>ACCOUNTING</DEPTNAME>
<LOC>NEW YORK</LOC>
</ROW>
<ROW>
<DEPTID>20</DEPTID>
<DEPTNAME>RESEARCH</DEPTNAME>
<LOC>DALLAS</LOC>
</ROW>
<ROW>
<DEPTID>30</DEPTID>
<DEPTNAME>SALES</DEPTNAME>
<LOC>CHICAGO</LOC>
</ROW>
<ROW>
<DEPTID>40</DEPTID>
<DEPTNAME>OPERATIONS</DEPTNAME>
<LOC>BOSTON</LOC>
</ROW>
</ROWSET>
XMLType 可以接受引用游标作为参数
要将 XML 文件的内容插入 Oracle 表,请执行以下步骤:
- 创建 xmltype 类型的表,例如创建 XMLTYPE 的表目录
- 创建一个包含要加载的 XML 文件的 Oracle 目录。(一个 Oracle 目录是指向数据库服务器机器上的操作系统 目录的数据库对象,用于读写文件。)例如:
**CREATE OR REPLACE DIRECTORY MYXMLDIR AS ‘D:\Gaurav\Trainings\Files’;**
3.执行 insert 语句,如下所示:
**INSERT INTO catalog VALUES(XMLType(bfilename(‘MYXMLDIR’,’CDCatalog.xml’),nls_charset_id(‘AL32UTF8’)));**
传递给nls_charset_id
的值表示要读取的文件的编码是 UTF-8。
4.可以使用对象值选择该值,例如
**select OBJECT_VALUE from catalog**
XMLELEMENT
XMLELEMENT 是一个返回 XML 类型的函数。它接受两个参数:第一个参数是标记的名称,第二个参数是值,可以是字符串、XMLTYPE、数字或日期。
**select empno, ename from emp where ename like ‘F%’;**
这将返回姓名中包含字母“S”的雇员的编号和姓名,如下所示:
使用 XMLELEMENT,我们可以添加带有用户定义的标记名和值的 xmltype 作为雇员名,例如
select empno, xmlelement(name,ename) name
from
emp
where ename like ‘%S%’;
它生成一个 XMLType,以标记名和雇员名作为值:
XMLELEMENT 可以嵌套,并且可以包含属性:
select empno, xmlelement(
emp,
xmlattributes (empno, deptno),
xmlelement(name, ename),
xmlelement(job,job)
emp
)
from
emp
where ename like '%S%'
用 SQL 函数查询 XML 数据
提取和提取值:
EXTRACT 函数接受两个参数:一个 XMLTYPE 元素和一个 XPATH 字符串,并返回一个 XMLTYPE 实例,例如
为了选择 country =“USA”数据,将在“catalog”表上使用以下查询。
select extract(OBJECT_VALUE,’/catalog/cd[country =”USA”]’) cd_usa
from
catalog
EXTRACTVALUE 用于提取一个节点下的特定值。例如,如果我们必须在 title=“仍然有忧郁”的地方找到艺术家的名字,查询将被写成如下:
select extractvalue(OBJECT_VALUE,’/catalog/cd[title =”Still got the blues”]//artist/text()’) artist_name
from
catalog
输出:
存在节点:
EXISTSNODE 检查 XPATH 表达式的存在性,例如,如果我们想知道标题“仍然有忧郁”是否存在于目录中,我们可以编写如下查询:
select existsnode(OBJECT_VALUE,’/catalog/cd[title =”Still got the blues”]’) exist_flg
from
catalog
输出 1 表示它存在,而 0 表示它不存在。
XMLAGG:
XMLAGG 用于在单个 XML 文档中聚合多行。例如,要汇总每个部门的员工,查询可以写成:
select
xmlelement(
emp,
xmlagg(
xmlelement(dept,
xmlagg(
xmlelement(name,ename) order by ename
)
)
)
)
from emp
group by empno
更新 XML:
UPDATEXML 搜索 XPATH 表达式并更新它。例如
UPDATE catalog SET object_value =
UPDATEXML(object_value,
‘/catalog/cd/title/text()’,’changed’)
这将把表格目录中的所有标题更新为“已更改”。
结论:
本文旨在让读者对 XML 和相关技术有一个基本的了解,并了解 Oracle 数据库是如何处理 XML 数据的。有关 Oracle XML 功能的更多详细信息,请参考以下链接:
http://docs . Oracle . com/CD/e 11882 _ 01/app dev . 112/e 23094/TOC . htm
XML 抓取做得很好!
使用 Python 一步步抓取任何“XML”文件
弗兰基·查马基在 Unsplash 上的照片
ata 是新的石油——但它绝对不便宜。我们有来自四面八方的数据;web、应用程序、社交媒体等,数据科学家必须能够挖掘其中的一些内容。在接下来的博客中,我们将学习如何使用 Python 库’ BeautifulSoup '快速从网站中挖掘/收集数据(为了好玩)
行动(或活动、袭击)计划
目录
- 介绍使用案例
- 什么是 BeautifulSoup?
- BS4 行动——理解并提取数据
- 最后评论
介绍使用案例
任何在客户体验或酒店行业工作过的人都明白客户满意度的重要性。NPS 或净推介值被视为客户体验的基准。虽然 NPS 是一项专门设计的调查,但还有其他方法来了解客户情绪。其中之一是 Appstore 上的客户反馈和评级(当然,前提是你的应用在那里可用)。
所以我们要做的是—
→随机选择一个应用程序(例如:脸书)
→访问 iTune 评论
→提取不同用户给出的评级、评论、日期等
→以干净的“csv/xlsx”格式导出它们。
什么是 BeautifulSoup?
美汤*(又名 BS4)* 是一个解析 HTML 和 XML 文档的 Python 包。它为解析过的页面创建了一个解析树,可以用来从 HTML 中提取数据,这对 web 抓取很有用。它适用于 Python 2.7 和 Python 3
BS4 行动——理解并提取数据
iTunes 使得从苹果应用商店获得应用评论变得非常容易。脸书的应用程序 id 是28488215
,我们只需要在下面的 URL 中添加相同的 id
[https://itunes.apple.com/us/rss/customerreviews/page=1/id=284882215/sortBy=mostrecent/xml](https://itunes.apple.com/us/rss/customerreviews/page=1/id=284882215/sortBy=mostrecent/xml)
单击上面的 URL,将会出现一个如下所示的页面:
XML 文档被形成为元素树。一个 XML 树从一个根元素开始,从根分支到子元素。术语父、子和兄弟用于描述元素之间的关系。
在上图中我们可以看到<feed>
是的父也就是多子。其中一个孩子是<entry>
,它又有许多孩子。在某种程度上——<feed>
是爷爷,<entry>
是儿子,其他人是孙子。
我们需要提取什么?
现在,让我们假设我们想从每个评论中提取以下内容:
- 审查日期
- 审查标题
- 内容
- 评级
- 审查人姓名
我们想写 10 页。
Python 代码
代码非常简单
→首先使用request
从 URL 导入数据
→使用BeautifulSoup
将其转换为 BS4 对象
→Extra:可以使用soup.findall()
查找数据中的所有文本
→找到子/标签entry
并将其保存为对象。(这是第一次审核,包含所有与审核相关的信息)
→搜索其子节点updated
title
content
im:rating
name
以提取所有需要的信息并保存在列表中
→通过使用find_next_sibling()
找到子/标签entry
的同级(这将引导您进入下一个审查)
→继续循环,直到找不到更多评论。继续将数据追加到数据帧中。
→ Extra:在上面的代码中,我添加了另一个循环来提取前 10 个评论页面中的所有上述信息。
仅此而已。很整洁,对吧!
结果
我们应该得到一个你看起来像这样的数据帧:
瞧 —您已经成功地从 iTunes 中提取了信息。现在,你可以在它们的基础上开发你的漂亮的 NLP 模型,并了解你的客户对你的看法。
要浏览我的其他数据科学/机器学习博客,请访问:
阅读希利什·古普塔在媒介上的作品。我是学术界的经济学家,专业的数据科学家和旅行者…
medium.com](https://medium.com/@shirishgupta)
暂时结束了。有什么想法来改善这一点或希望我尝试任何新的想法?请在评论中给出你的建议。再见。