【NLP】图解变压器(transformer)

一、说明

        在这篇文章中,我们将看看 The Transformer——一个利用注意力来提高这些模型训练速度的模型。转换器在特定任务中优于谷歌神经机器翻译模型。然而,最大的好处来自变压器如何适应并行化。事实上,谷歌云建议使用The Transformer作为参考模型来使用他们的Cloud TPU产品。因此,让我们尝试将模型分解并查看其功能。

二、最外层的外观

        让我们首先将模型视为单个黑盒。在机器翻译应用程序中,它将采用一种语言的句子,然后输出另一种语言的翻译。

        打开引擎的顶盖,我们看到一个编码组件,一个解码组件,以及它们之间的联系。

        编码组件是一堆编码器(纸张将其中六个堆叠在一起 - 数字六没有什么神奇之处,人们绝对可以尝试其他安排)。解码组件是一堆相同编号的解码器。

        编码器在结构上都是相同的(但它们不共享权重)。每个子层都分为两个子层:

        编码器的输入首先流经自我注意层 - 该层可帮助编码器在编码特定单词时查看输入句子中的其他单词。我们将在帖子后面仔细研究自我关注。

        自我注意层的输出被馈送到前馈神经网络。完全相同的前馈网络独立应用于每个位置。

        解码器具有这两个层,但在它们之间是一个注意力层,可帮助解码器专注于输入句子的相关部分(类似于 seq2seq 模型中的注意力)。

三、将张量带入图片

现在我们已经了解了模型的主要组件,让我们开始看看各种向量/张量以及它们如何在这些组件之间流动,以将训练模型的输入转换为输出。

与一般的NLP应用程序一样,我们首先使用嵌入算法将每个输入词转换为向量。


每个单词都嵌入到大小为 512 的向量中。我们将用这些简单的框来表示这些向量。

嵌入仅发生在最底部的编码器中。所有编码器的共同抽象是它们接收一个大小为 512 的向量列表 – 在底部编码器中,这将是单词嵌入,但在其他编码器中,它将是编码器的输出正下方。这个列表的大小是我们可以设置的超参数——基本上它是我们训练数据集中最长句子的长度。

        将单词嵌入到我们的输入序列中后,每个单词都流经编码器的两层中的每一层。

        在这里,我们开始看到变压器的一个关键属性,即每个位置的单词在编码器中流经其自己的路径。在自我注意层中,这些路径之间存在依赖关系。但是,前馈层没有这些依赖关系,因此各种路径可以在流经前馈层时并行执行。

        接下来,我们将示例切换为较短的句子,并查看编码器的每个子层中发生的情况。

四、现在我们正在编码!

        正如我们已经提到的,编码器接收一个向量列表作为输入。它通过将这些向量传递到“自我注意”层来处理此列表,然后传递到前馈神经网络,然后将输出向上发送到下一个编码器。


每个位置的单词都经过一个自我注意的过程。然后,它们各自通过一个前馈神经网络——完全相同的网络,每个向量分别流经它。

五、高水平的自我关注

        不要被我抛出“自我关注”这个词所迷惑,就像这是一个每个人都应该熟悉的概念一样。我个人从未遇到过这个概念,直到阅读了“注意力就是你需要的一切”论文。让我们提炼一下它是如何工作的。

        假设以下句子是我们想要翻译的输入句子:

""The animal didn't cross the street because it was too tired

        这句话中的“它”指的是什么?它指的是街道还是动物?这对人类来说是一个简单的问题,但对算法来说却没有那么简单。

        当模型处理单词“it”时,自我注意允许它将“it”与“animal”相关联。

        当模型处理每个单词(输入序列中的每个位置)时,自我注意允许它查看输入序列中的其他位置,以寻找有助于更好地编码该单词的线索。

        如果您熟悉 RNN,请考虑保持隐藏状态如何允许 RNN 将其先前处理的单词/向量的表示与当前正在处理的单词/向量合并。自我注意是变形金刚用来将对其他相关单词的“理解”烘焙到我们目前正在处理的单词中的方法。


当我们在编码器 #5(堆栈中的顶部编码器)中对单词“it”进行编码时,注意力机制的一部分专注于“动物”,并将其表示的一部分烘焙到“it”的编码中。

        请务必查看 Tensor2Tensor 笔记本,您可以在其中加载转换器模型,并使用此交互式可视化对其进行检查。

六、细节上的自我关注

        让我们首先看看如何使用向量计算自我注意,然后继续看看它实际上是如何使用矩阵实现的。

        计算自我注意的第一步是从编码器的每个输入向量创建三个向量(在本例中为每个单词的嵌入)。因此,对于每个单词,我们创建一个查询向量、一个键向量和一个值向量。这些向量是通过将嵌入乘以我们在训练过程中训练的三个矩阵来创建的。

        请注意,这些新向量的维度小于嵌入向量。它们的维数为 64,而嵌入和编码器输入/输出向量的维数为 512。它们不必更小,这是一种架构选择,可以使多头注意力的计算(大部分)恒定。


将 x1 乘以 WQ 权重矩阵得到 q1,即与该单词关联的“查询”向量。我们最终为输入句子中的每个单词创建一个“查询”、“键”和“值”投影。


 

什么是“查询”、“键”和“值”向量?

它们是对计算和思考注意力有用的抽象概念。一旦你继续阅读下面的注意力是如何计算的,你就会知道几乎所有你需要知道的关于这些向量所扮演的角色。

计算自我注意力的第二步是计算分数。假设我们正在计算此示例中第一个单词“思考”的自我注意。我们需要根据该单词对输入句子的每个单词进行评分。分数决定了当我们在某个位置对单词进行编码时,对输入句子的其他部分的关注程度。

分数的计算方法是将查询向量的点积与我们评分的相应单词的关键向量相提并论。因此,如果我们处理位置 #1 中单词的自我注意,第一个分数将是 q1 和 k1 的点积。第二个分数是 q1 和 k2 的点积。

第三步和第四步是将分数除以 8(论文中使用的关键向量维度的平方根 – 64。这导致具有更稳定的梯度。这里可能还有其他可能的值,但这是默认值),然后通过 softmax 操作传递结果。Softmax 对分数进行归一化,因此它们都是正数,加起来为 1。

这个softmax分数决定了每个单词在这个位置的表达量。显然,这个位置的单词将具有最高的softmax分数,但有时关注与当前单词相关的另一个单词很有用。

第五步是将每个值向量乘以softmax分数(准备将它们相加)。这里的直觉是保持我们想要关注的单词的值不变,并淹没不相关的单词(例如,将它们乘以像 0.001 这样的小数字)。

第六步是总结加权值向量。这会在此位置(对于第一个单词)产生自我注意层的输出。

        自我注意力计算到此结束。生成的向量是我们可以发送到前馈神经网络的向量。然而,在实际实现中,这种计算是以矩阵形式完成的,以便更快地处理。因此,现在让我们看看,我们已经看到了单词级别的计算直觉。

七、自我注意的矩阵计算

        第一步是计算查询、键和值矩阵。为此,我们将嵌入打包到矩阵X中,并将其乘以我们训练的权重矩阵(WQ,WK,WV)。


X 矩阵中的每一行对应于输入句子中的一个单词。我们再次看到嵌入向量(图中 512 或 4 个框)和 q/k/v 向量(图中 64 个或 3 个框)的大小差异

最后,由于我们正在处理矩阵,我们可以将步骤 2 到 6 压缩在一个公式中,以计算自我注意层的输出。


矩阵形式的自我注意计算

八、多头兽

        该论文通过添加一种称为“多头”注意的机制进一步完善了自我注意层。这通过两种方式提高了注意力层的性能:

  1. 它扩展了模型专注于不同位置的能力。是的,在上面的例子中,z1 包含一点其他编码,但它可能由实际单词本身主导。如果我们翻译一个句子,比如“动物没有过马路,因为它太累了”,知道“它”指的是哪个词会很有用。

  2. 它为注意力层提供了多个“表示子空间”。正如我们接下来将看到的,对于多头注意力,我们不仅有一组,而是多组查询/键/值权重矩阵(转换器使用八个注意力头,所以我们最终为每个编码器/解码器有八组)。这些集合中的每一个都是随机初始化的。然后,在训练后,每个集合用于将输入嵌入(或来自较低编码器/解码器的向量)投影到不同的表示子空间中。


通过多头注意,我们为每个头部维护单独的 Q/K/V 权重矩阵,从而产生不同的 Q/K/V 矩阵。和以前一样,我们将 X 乘以 WQ/WK/WV 矩阵以产生 Q/K/V 矩阵。


如果我们做上面概述的相同的自我注意计算,只有八次不同的时间使用不同的权重矩阵,我们最终得到八个不同的 Z 矩阵

这给我们带来了一些挑战。前馈层不需要八个矩阵 - 它期望单个矩阵(每个单词的向量)。因此,我们需要一种方法将这八个压缩成一个矩阵。

我们如何做到这一点?我们将矩阵连接起来,然后将它们乘以一个额外的权重矩阵 WO。

这几乎就是多头自我关注的全部内容。我意识到这是相当多的矩阵。让我尝试将它们全部放在一个视觉对象中,以便我们可以在一个地方查看它们

现在我们已经触及了注意力头,让我们重新审视一下之前的例子,看看当我们在例句中编码单词“it”时,不同的注意力头集中在哪里:


当我们对“它”这个词进行编码时,一个注意力头最关注“动物”,而另一个注意力集中在“疲惫”上——从某种意义上说,模型对“它”这个词的表示融入了“动物”和“疲惫”的一些表示。

但是,如果我们将所有注意力都添加到图片中,事情可能更难解释:

九、使用位置编码表示序列的顺序

        到目前为止,模型中缺少的一件事是解释输入序列中单词顺序的方法。

        为了解决这个问题,转换器向每个输入嵌入添加一个向量。这些向量遵循模型学习的特定模式,这有助于它确定每个单词的位置或序列中不同单词之间的距离。这里的直觉是,将这些值添加到嵌入中,一旦嵌入向量投影到 Q/K/V 向量中,并且在点积注意期间,嵌入向量之间就会提供有意义的距离。

为了让模型了解单词的顺序,我们添加了位置编码向量 - 其值遵循特定的模式。

        如果我们假设嵌入的维数为 4,则实际的位置编码如下所示:


玩具嵌入大小为 4 的位置编码的真实示例

        这种模式可能是什么样子的?

        在下图中,每一行对应于一个向量的位置编码。因此,第一行是我们添加到输入序列中第一个单词嵌入的向量。每行包含 512 个值 - 每个值的值介于 1 和 -1 之间。我们对它们进行了颜色编码,因此图案可见。


嵌入大小为 20(列)的 512 个单词(行)的位置编码的真实示例。您可以看到它似乎在中心向下分成两半。这是因为左半部分的值是由一个函数(使用正弦)生成的,而右半部分的值是由另一个函数(使用余弦)生成的。然后将它们连接起来形成每个位置编码向量。

        论文中描述了位置编码的公式(第3.5节)。您可以在 get_timing_signal_1d() 中看到用于生成位置编码的代码。这不是位置编码的唯一可能方法。然而,它的优势在于能够扩展到看不见的序列长度(例如,如果我们训练的模型被要求翻译一个句子比我们训练集中的任何句子都长)。

        2020 年 2 月更新:上面显示的位置编码来自转换器的 Tensor<>Tensor 实现。论文中显示的方法略有不同,因为它不直接连接,而是交织两个信号。下图显示了它的外观。下面是生成它的代码:

十、残余物(残差)

        在继续之前,我们需要提及编码器架构中的一个细节是,每个编码器中的每个子层(自我注意,ffnn)周围都有一个残差连接,然后是层规范化步骤。

        如果我们要可视化与自我注意相关的向量和层范数运算,它看起来像这样:

        这也适用于解码器的子层。如果我们要考虑一个由 2 个堆叠编码器和解码器的 Transformer 组成,它看起来像这样:

十一、解码器端

        现在我们已经介绍了编码器端的大多数概念,我们基本上也知道解码器的组件是如何工作的。但是,让我们来看看它们是如何协同工作的。

        编码器首先处理输入序列。然后将顶部编码器的输出转换为一组注意力向量 K 和 V。每个解码器在其“编码器-解码器注意”层中使用这些,这有助于解码器专注于输入序列中的适当位置:

完成编码阶段后,我们开始解码阶段。解码阶段的每个步骤都会从输出序列中输出一个元素(在本例中为英语翻译句子)。

以下步骤重复该过程,直到到达指示转换器解码器已完成其输出的特殊符号。每个步骤的输出在下一个时间步中馈送到底部解码器,解码器就像编码器一样冒出解码结果。就像我们对编码器输入所做的那样,我们在这些解码器输入中嵌入并添加位置编码,以指示每个单词的位置。

解码器中的自注意层的工作方式与编码器中的自注意层略有不同:

在解码器中,自我注意层只允许关注输出序列中的较早位置。这是通过在自我注意计算中的softmax步骤之前屏蔽未来位置(将它们设置为)来完成的。-inf

“编码器-解码器注意力”层的工作方式与多头自我注意类似,只是它从其下方的层创建其查询矩阵,并从编码器堆栈的输出中获取键和值矩阵。

十二、最终的线性层和软最大值层

解码器堆栈输出浮点数向量。我们如何把它变成一个词?这是最终线性层的工作,然后是Softmax层。

线性层是一个简单的完全连接的神经网络,它将解码器堆栈产生的向量投射到一个更大的向量中,称为logits向量。

假设我们的模型知道 10,000 个独特的英语单词(我们模型的“输出词汇表”),这些单词是从其训练数据集中学习的。这将使对数向量宽 10,000 个单元格 - 每个单元格对应于一个唯一单词的分数。这就是我们如何解释模型的输出,然后是线性层。

然后,softmax层将这些分数转换为概率(全部为正,加起来为1.0)。选择概率最高的像元,并生成与之关联的单词作为此时间步长的输出。


该图从底部开始,作为解码器堆栈输出生成的矢量。然后将其转换为输出字。

十三、 训练回顾

        现在我们已经通过经过训练的转换器涵盖了整个正向传递过程,那么浏览一下训练模型的直觉会很有用。

        在训练期间,未经训练的模型将经历完全相同的正向传递。但是由于我们在标记的训练数据集上训练它,我们可以将其输出与实际正确的输出进行比较。

        为了形象化这一点,让我们假设我们的输出词汇表只包含六个单词(“a”、“am”、“i”、“谢谢”、“学生”和“<eos>”(“句子结尾”的缩写))。


我们模型的输出词汇表是在我们开始训练之前的预处理阶段创建的。

一旦我们定义了输出词汇表,我们就可以使用相同宽度的向量来指示词汇表中的每个单词。这也称为独热编码。例如,我们可以使用以下向量来表示单词“am”:


示例:输出词汇表的独热编码

在此回顾之后,让我们讨论模型的损失函数 - 我们在训练阶段优化的指标,以导致一个经过训练且有望令人惊讶的准确模型。

十四、损失函数

        假设我们正在训练我们的模型。假设这是我们在培训阶段的第一步,我们正在通过一个简单的例子来训练它 - 将“怜悯”翻译成“谢谢”。

        这意味着,我们希望输出是一个概率分布,指示单词“谢谢”。但由于这个模型还没有经过训练,所以现在不太可能发生。


由于模型的参数(权重)都是随机初始化的,因此(未经训练的)模型为每个单元格/单词生成具有任意值的概率分布。我们可以将其与实际输出进行比较,然后使用反向传播调整模型的所有权重,以使输出更接近所需的输出。

您如何比较两个概率分布?我们只是从另一个中减去一个。有关更多详细信息,请查看交叉熵和Kullback-Leibler散度。

但请注意,这是一个过于简化的示例。更现实地说,我们将使用一个比一个单词更长的句子。例如 – 输入:“je suis étudiant”和预期输出:“我是学生”。这真正意味着,我们希望我们的模型连续输出概率分布,其中:

  • 每个概率分布都由宽度vocab_size向量表示(在我们的玩具示例中为 6,但更现实的数字为 30,000 或 50,000)
  • 第一个概率分布在与单词“i”关联的单元格处具有最高的概率
  • 第二个概率分布在与单词“am”相关的单元格处具有最高的概率
  • 依此类推,直到第五个输出分布指示 '' 符号,该符号也有一个与 10,000 个元素词汇表关联的单元格。<end of sentence>


我们将在一个样本句子的训练示例中训练模型的目标概率分布。

        在足够大的数据集上训练模型足够长的时间后,我们希望生成的概率分布如下所示:


希望经过训练,模型将输出我们期望的正确翻译。当然,这并不能真正表明这个短语是否是训练数据集的一部分(参见:交叉验证)。请注意,每个位置都有一点概率,即使它不太可能是该时间步的输出 - 这是softmax的一个非常有用的属性,有助于训练过程。

        现在,由于模型一次生成一个输出,因此我们可以假设模型正在从该概率分布中选择概率最高的单词,并丢弃其余的单词。这是一种方法(称为贪婪解码)。另一种方法是保留,比如说,前两个单词(例如,“I”和“a”),然后在下一步中,运行模型两次:一次假设第一个输出位置是单词“I”,另一次假设第一个输出位置是单词“a”,考虑到位置 #1 和 #2,哪个版本产生的错误较小,则保留。我们对位置 #2 和 #3 重复此操作...等。这种方法称为“波束搜索”,在我们的示例中,beam_size是两个(意味着始终将两个部分假设(未完成的翻译)保存在内存中),top_beams也是两个(意味着我们将返回两个翻译)。这些都是您可以试验的超参数。

十五、转型

        我希望你已经发现这是一个有用的地方,开始打破变形金刚的主要概念。如果你想更深入,我建议这些后续步骤:

  • 阅读《注意力是你需要的一切》论文、Transformer 博客文章(Transformer: A Novel Neural Network Architecture for Language Understanding)和 Tensor2Tensor 公告。
  • 观看Łukasz Kaiser的演讲,了解模型及其细节
  • 使用 Jupyter Notebook 作为 Tensor2Tensor 存储库的一部分提供
  • 探索 Tensor2Tensor 存储库。

参考和后续工作:

  • 用于神经机器翻译的深度可分离卷积
  • 一个模型来学习它们
  • 用于序列模型的离散自动编码器
  • 通过总结长序列生成维基百科
  • 图像转换器
  • 变压器模型的训练技巧
  • 具有相对位置表示的自我注意
  • 使用离散潜在变量在序列模型中快速解码
  • Adafactor:具有次线性内存成本的自适应学习率

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/44963.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux后端服务器开发】协议定制(序列化与反序列化)

目录 一、应用层协议概述 二、序列化与反序列化 Protocal.h头文件 Server.h头文件 Client.h头文件 server.cpp源文件 client.cpp源文件 一、应用层协议概述 什么是应用层&#xff1f;我们通过编写程序解决一个个实际问题、满足我们日常需求的网络程序&#xff0c;都是应…

0基础学习VR全景平台篇 第69篇:VR直播-如何设置广告

直播间可以插入轮播广告&#xff0c;并且支持外链跳转&#xff0c;能够有效地提升VR直播活动的转化率。 1、点击&#xff0c;添加广告 2、广告图展现形式分为两种&#xff1a;普通广告和全屏广告&#xff0c;普通广告在非全屏播放的直播间显示&#xff0c;全屏广告在全屏播放的…

【Visual Studio】VS调用tensorflow C++API的配置(无需编译)

1. 首先下载并安装visual studio Visual Studio 2015 安装教程&#xff08;附安装包&#xff09;&#xff0c;按照博客中顺序来就可以 如果在安装过程中提示安装包损失或毁坏&#xff0c;参考VS2015安装过程中安装包丢失或损坏解决办法 卡在哪个搜索文件上就找到哪个文件再继…

【100天精通python】Day15:python 第三方模块和包,模块如何以主程序形式执行

目录 1 常用的第三方模块 2. 第三方模块的安装和使用 2.1 安装第三方模块&#xff1a; 2.2 导入第三方模块&#xff1a; 2.3 使用第三方模块&#xff1a; 3 模块以主程序形式执行 4 python 中的包 4.1 python程序的包结构 4.2 创建包 4.3 python中包的导入和使用 5 …

lama cleaner

这里写自定义目录标题 安装参数包含的额外plugins 安装 conda create --name lamacleaner python3.10 pip install -r requirements.txt pip install gfpgan pip install realesrgan pip install rembg pip install .如果安装本package报错&#xff0c;可以尝试改&#xff1…

【安全】web中的常见编码浅析浏览器解析机制

目录 常见编码 一、ASCII码 二、URL编码 三、Unicode编码 四、HTML实体编码 结合编码理解浏览器解析机制 常见编码 一、ASCII码 ASCII (American Standard Code for Information Interchange&#xff0c;美国信息交换标准代码&#xff09; 计算机内部&#xff0…

Ceph简介和特性

Ceph是一个多版本存储系统&#xff0c;它把每一个待管理的数据流(例如一个文件) 切分为一到多个固定大小的对象数据&#xff0c;并以其为原子单元完成数据存取。 对象数据的底层存储服务是由多个主机 (host) 组成的存储集群&#xff0c;该集群也被称之为 RADOS (ReliableAutoma…

测试覆盖率 JVM 字节码测试运用 - 远程调试、测试覆盖、影子数据库

目录 前言&#xff1a; 简介 基础使用方式介绍 工具特性 前言&#xff1a; 在软件开发中&#xff0c;测试覆盖率是一个非常重要的指标&#xff0c;它表示代码中所有的测试用例是否都已经被覆盖到。JVM 字节码测试是一种比较新的测试方法&#xff0c;它可以对 JVM 字节码进…

php开发实战分析(10):城市区县联动筛选

php开发实战分析系列目录 php开发实战分析(1)&#xff1a;mysql操作字段&#xff08;添加、删除、修改&#xff0c;多数据表中新增多个字段&#xff09;php开发实战分析(2)&#xff1a;cookie的动态使用&#xff08;设置、获取、删除、猜你喜欢原理、购物车调用&#xff09;ph…

DB-GPT:强强联合Langchain-Vicuna的应用实战开源项目,彻底改变与数据库的交互方式

今天看到 蚂蚁科技 Magic 开源的DB-GPT项目&#xff0c;觉得创意很好&#xff0c;集成了当前LLM的主流技术&#xff0c;主要如下 Langchain&#xff1a; 构建在LLM之上的应用开发框架HuggingFace: 模型标准&#xff0c;提供大模型管理功能Vicuna: 一个令GPT-4惊艳的开源聊天机…

❓“如何创业?互联网创业又该如何入手?

&#x1f31f;5大创业建议&#xff0c;让你轻松入门&#xff01; 作为一名互联网创业者&#xff0c;我想分享一下我的创业经验。下面是我的五个建议&#xff0c;希望对你有所帮助&#xff01; &#x1f31f;了解市场需求 在创业之前&#xff0c;了解市场需求非常重要。你需要研…

Docker基本概念+命令

Docker基本概念命令 一、Docker是什么&#xff1f;二、为什么Docker技术受欢迎三、Docker核心概念四、Docker安装五、Docker镜像操作1.搜索镜像2.获取镜像3.镜像加速下载4.查看镜像信息5.查看下载的镜像文件信息6.查看下载到本地的所有镜像7.获取镜像的详细信息8.修改镜像标签9…

STM32MP157驱动开发——按键驱动(异步通知)

文章目录 “异步通知 ”机制&#xff1a;信号的宏定义&#xff1a;信号注册 APP执行过程驱动编程做的事应用编程做的事异步通知方式的按键驱动程序(stm32mp157)button_test.cgpio_key_drv.cMakefile修改设备树文件编译测试 “异步通知 ”机制&#xff1a; 信号的宏定义&#x…

阿里云容器镜像仓库(ACR)的创建和使用

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

【Flume 01】Flume简介、部署、组件

1 Flume简介 Flume是一个高可用、高可靠、分布式的海量日志采集、聚合和传输的系统 主要特性&#xff1a; 它有一个简单、灵活的基于流的数据流结构&#xff08;使用Event封装&#xff09;具有负载均衡机制和故障转移机制一个简单可扩展的数据模型(Source、Channel、Sink) Sou…

若依vue 多table前端HTML页面导出一张Excel表

前言 导入依赖&#xff0c;具体前端vue配置就不介绍了&#xff0c;直接晒具体细节代码 实现 需要在多table外加div&#xff0c;其他都是基本操作js代码 import FileSaver from file-saver import * as XLSX from "xlsx";const htmlToExcel {getExcelNew(classNam…

Windows Server 2019 中文版、英文版下载 (updated Jul 2023)

Windows Server 2019 中文版、英文版下载 (updated Jul 2023) Windows Server 2019 Version 1809&#xff0c;2023 年 7 月更新 请访问原文链接&#xff1a;https://sysin.org/blog/windows-server-2019/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者…

软件测试测试分类(重点)

目录 按照测试对象划分&#xff08;了解&#xff09; ①界面测试 ②可靠性测试&#xff08;可用性&#xff09; ③容错性测试 容错性和可靠性之间的区别 ④文档测试 ⑤兼容性测试 ⑥易用性测试 ⑦安装、卸载测试 ⑧安全测试 ⑨性能测试 内存泄露测试 按照是否查看…

HDFS基本操作命令

这里写目录标题 HDFS Shell CLI客户端说明常用命令hadoop fs -mkdir [-p] <path>hadoop fs -ls [-h] [-R] [<path>...]上传文件到指定目录下方法一:hadoop fs -put [-f] [-p] <localsrc>.....<dst>方法二&#xff1a;hadoop fs -moveFromLocal <loc…

查看docker容器启动参数

查看docker启动参数 1、查看docker容器的自启动策略2、查看docker容器的日志滚动清理策略 以下配置命令以redis容器为例 1、查看docker容器的自启动策略 docker inspect --format{{json .HostConfig.RestartPolicy}} redis输出的name是always 表示此容器是开机自启动的&#x…