当前位置: 首页 > news >正文

LLM架构解析:编码器-解码器架构(Encoder-Decoder Architecture)(第四部分)—— 从基础原理到实践应用的深度探索

本专栏深入探究从循环神经网络(RNN)到Transformer等自然语言处理(NLP)模型的架构,以及基于这些模型构建的应用程序。

本系列文章内容:

  1. NLP自然语言处理基础
  2. 词嵌入(Word Embeddings)
  3. 循环神经网络(RNN)、长短期记忆网络(LSTM)和门控循环单元(GRU)
    3.1 循环神经网络(RNN)
    3.2 长短期记忆网络(LSTM)
    3.3 门控循环单元(GRU)
  4. 编码器-解码器架构(Encoder-Decoder Architecture)(本文)
  5. 注意力机制(Attention Mechanism)
  6. Transformer
  7. 编写Transformer代码
  8. 双向编码器表征来自Transformer(BERT)
  9. 生成式预训练Transformer(GPT)
  10. 大语言模型(LLama)
  11. Mistral

1. 简介

自然语言处理(Natural Language Processing,NLP)的快速发展以诸多重要的里程碑为标志,其中没有什么比大语言模型(Large Language Models,LLMs)的出现更具变革性了。这些模型重新定义了机器理解和生成人类语言的可能性边界。许多大语言模型成功的核心是编码器-解码器架构,这一框架在机器翻译、文本摘要和对话式人工智能等任务中实现了突破。

编码器-解码器架构的引入是为了解决序列到序列(Sequence-to-Sequence,Seq2Seq)问题,这标志着在处理序列数据方面的重大突破。
在这里插入图片描述

1.1 数据处理方面的主要发展

  1. 表格数据:最初,重点是利用人工神经网络(Artificial Neural Networks,ANNs)来处理表格数据。这种方法通过增加层数演变成了深度神经网络(Deep Neural Networks,DNNs),从而增强了模型捕捉数据中复杂模式的能力。
  2. 图像数据:在目标识别等任务中,例如判断一张图像中是有一只狗还是一只猫,图像作为二维数据网格。人工神经网络在处理这种类型的结构化数据方面并不是特别有效。这一限制促使了卷积神经网络(Convolutional Neural Networks,CNNs)的发展,卷积神经网络是专门为解释和分析网格格式的视觉信息而设计的
  3. 序列数据:像文本或时间序列这样的序列数据具有时间依赖性和有序性。人工神经网络和卷积神经网络不太适合处理此类数据,因为它们缺乏捕捉序列关系的机制。这一空白由循环神经网络(Recurrent Neural Networks,RNNs)及其高级变体——长短期记忆(Long Short-Term Memory,LSTM)网络和门控循环单元(Gated Recurrent Units,GRUs)填补,它们能够对时间模式进行建模并从中学习。
  4. 序列到序列数据:在某些应用中,输入和输出都是序列,就像在机器翻译任务中看到的那样。传统模型在处理这种类型的数据时会遇到困难,因为在对齐可变长度的输入和输出序列时存在复杂性。这一挑战需要开发能够有效处理序列到序列数据的专门架构,为自然语言处理中更复杂的模型铺平了道路。
    本文的重点是处理序列到序列(Seq2Seq)数据问题。

2. 理解序列建模

序列建模的应用场景涉及输入、输出或两者都由数据序列(如单词或字母)组成的问题。

考虑一个非常简单的预测电影评论是正面还是负面的问题。这里我们的输入是一个单词序列,输出是一个介于0和1之间的单个数字。如果我们使用传统的深度神经网络,那么我们通常必须使用词袋模型(Bag of Words,BOW)、词向量(Word2Vec)等技术将输入文本编码为固定长度的向量。但请注意,这里单词的顺序没有被保留,因此当我们将输入向量输入到模型中时,模型不知道单词的顺序,从而遗漏了关于输入的一个非常重要的信息。

因此,为了解决这个问题,循环神经网络(RNNs)应运而生。本质上,对于任何具有可变数量特征的输入X = (x₀, x₁, x₂, … xₜ),在每个时间步,一个循环神经网络单元将一个元素(或token)xₜ作为输入,并产生一个输出hₜ,同时将一些信息传递到下一个时间步。这些输出可以根据手头的问题来使用。
在这里插入图片描述
电影评论预测问题是一个非常基本的序列问题的例子,称为多对一预测。对于不同类型的序列问题,会使用这种循环神经网络架构的修改版本。

序列问题可以大致分为以下几类:
在这里插入图片描述

每个矩形是一个向量,箭头表示函数(例如矩阵乘法)。输入向量是红色的,输出向量是蓝色的,绿色向量保存循环神经网络的状态(很快会详细介绍)。

从左到右:

(1)没有循环神经网络的普通处理模式,从固定大小的输入到固定大小的输出(例如图像分类)。

(2)序列输出(例如图像字幕生成,输入一张图像并输出一个单词句子)。

(3)序列输入(例如情感分析,对给定的句子进行分类,判断其表达的是正面还是负面情感)。

(4)序列输入和序列输出(例如机器翻译:一个循环神经网络读取一个英语句子,然后输出一个法语句子)。

(5)同步的序列输入和输出(例如视频分类,我们希望标记视频的每一帧)。

请注意,在每种情况下,序列的长度都没有预先指定的限制,因为循环变换(绿色部分)是固定的,并且可以根据需要应用多次。

Andrej Karpathy《循环神经网络的不合理有效性》

2.1 利用编码器-解码器架构掌握序列数据

序列在我们的世界中无处不在——在语言、语音、金融时间序列以及基因组数据中都能找到,在这些领域中元素的顺序至关重要。与固定大小的数据不同,序列在理解、预测和生成信息方面带来了独特的挑战。传统的深度神经网络(Deep Neural Networks,DNNs)在处理具有固定维度输入和输出的任务时表现出色,但在诸如机器翻译这样的序列到序列任务中却面临困难,因为在机器翻译中,输入和输出的长度是可变的,并且没有对齐。因此,需要专门的模型来有效地处理序列数据。

研究论文:《使用神经网络进行序列到序列学习》(Sequence to Sequence Learning with Neural
Networks)

动机
编码器-解码器架构相对较新,在2016年底被谷歌翻译服务采用作为核心技术。它构成了诸如注意力模型(Attention models)、GPT模型、Transformer模型和BERT等先进的序列到序列模型的基础。因此,在深入研究更高级的机制之前,理解这一架构至关重要。

关键组件

  • 编码器:编码器处理输入序列,并将信息编码为固定长度的上下文向量(或向量序列)。这种编码捕捉了输入数据的本质,对其信息内容进行了总结。
  • 解码器:解码器接收编码器提供的上下文向量生成输出序列,期间一次生成一个元素。它利用上下文向量中的信息来生成与输入相关且连贯的输出。
    在这里插入图片描述

3. 编码器-解码器架构

3.1 神经机器翻译问题

在这里插入图片描述

为了说明这个概念,让我们以神经机器翻译(Neural Machine Translation,NMT)为例进行讲解。在神经机器翻译中,输入是一个逐个处理的单词序列,输出是相应的单词序列。

任务:预测一个英语句子的法语翻译。
示例
输入:英语句子:“nice to meet you”
输出:法语翻译:“ravi de vous rencontrer”

使用的术语

  • 输入句子“nice to meet you”将被称为X或输入序列。
  • 输出句子“ravi de vous rencontrer”被称为Y_true或目标序列,这是我们希望模型预测的真实结果。
  • 模型预测的句子是Y_pred,也称为预测序列
  • 英语和法语句子中的每个单词都被称为一个标记(token)。

因此,给定输入序列“nice to meet you”,模型的目标是预测目标序列Y_true,即“ravi de vous rencontrer”。

3.2 概述

从很高的层面来看,编码器-解码器模型可以被看作是两个模块,即编码器和解码器,它们由一个我们称之为“上下文向量”的向量连接起来。
在这里插入图片描述

  • 编码器:编码器处理输入序列中的每个标记。它试图将关于输入序列的所有信息压缩到一个固定长度的向量中,也就是“上下文向量”。在处理完所有标记后,编码器将这个向量传递给解码器。
  • 上下文向量:构建这个向量的方式是期望它封装输入序列的全部含义,并帮助解码器做出准确的预测。我们稍后会看到,这是编码器模块的最终内部状态。
  • 解码器:解码器读取上下文向量,并试图逐个标记地预测目标序列。
    在这里插入图片描述

3.3 内部机制

序列到序列(Seq2Seq)模型是一种基于循环神经网络(RNN)的模型,专为翻译和摘要等任务而设计,在这些任务中,输入是一个序列,输出也是一个序列。
在这里插入图片描述
这是一个将英语句子“I am a student.”翻译成法语“Je suis étudiant.”的序列到序列(Seq2Seq)模型。左边的橙色矩形代表编码器,右边的绿色矩形代表解码器。编码器接收输入句子(“I am a student.”)并输出一个上下文向量,而解码器将上下文向量(以及标记)作为输入,并输出句子(“Je suis étudiant.”)。

就架构而言,它相当简单明了。这个模型可以被看作是两个之间有某种连接的长短期记忆(LSTM)单元。这里的关键在于我们如何处理输入和输出。我将逐一解释每个部分。

3.4 编码器模块

编码器部分是一个长短期记忆(LSTM)单元。随着时间推移,它接收输入序列,并试图封装输入序列的所有信息,并将其存储在最终的内部状态hₜ(隐藏状态)和cₜ(细胞状态)中。然后,这些内部状态被传递到解码器部分,解码器将利用这些状态来尝试生成目标序列。这就是我们之前提到的“上下文向量”。

编码器部分在每个时间步的输出都被丢弃。
在这里插入图片描述

注意:上面的图表是当我们在时间轴上展开长短期记忆(LSTM)/门控循环单元(GRU)单元时的样子。也就是说,它是单个的长短期记忆(LSTM)/门控循环单元(GRU)单元,在每个时间戳接收单个单词/标记。

在论文中,他们使用了长短期记忆(LSTM)单元而不是传统的循环神经网络(RNN),因为长短期记忆(LSTM)在处理长期依赖关系方面表现更好。

3.4.1 数学基础:编码器

给定一个输入序列:
在这里插入图片描述

编码器按顺序处理每个元素:

初始化:编码器的初始隐藏状态 h 0 h_0 h0通常初始化为零或通过学习得到的参数。
隐藏状态更新:对于输入序列中的每个时间步 t t t
在这里插入图片描述
其中:

  • h t h_t ht是时间步 t t t的隐藏状态。
  • f e n c f_{enc} fenc是编码器的激活函数(例如,长短期记忆(LSTM)单元或门控循环单元(GRU)单元)。

上下文向量:在处理完整个输入序列后,最终的隐藏状态 h T x h_{T_x} hTx成为上下文向量 c c c
在这里插入图片描述

3.5 解码器模块

在读取完整个输入序列后,编码器将内部状态传递给解码器,从这里开始预测输出序列。
在这里插入图片描述

解码器模块也是一个长短期记忆(LSTM)单元。这里需要注意的主要一点是,解码器的初始状态 ( h 0 , c 0 ) (h_0, c_0) (h0,c0)被设置为编码器的最终状态 ( h t , c t ) (h_t, c_t) (ht,ct)。这些状态充当“上下文”向量,帮助解码器生成期望的目标序列。

现在来解释解码器的工作方式,它在任何时间步 t t t的输出应该是目标序列/ Y t r u e Y_{true} Ytrue(“ravi de vous rencontrer”)中的第 t t t个单词。为了解释这一点,让我们看看每个时间步会发生什么。

在时间步1

在第一个时间步输入到解码器的是一个特殊符号<START>,这用于表示输出序列的开始。现在解码器使用这个输入以及内部状态 ( h t , c t ) (h_t, c_t) (ht,ct)来生成第一个时间步的输出,该输出应该是目标序列中的第一个单词/标记,即“ravi”。

在时间步2

在时间步2,第一个时间步的输出“ravi”被作为输入送到第二个时间步。第二个时间步的输出应该是目标序列中的第二个单词,即“de”。

类似地,每个时间步的输出都被作为输入送到下一个时间步。这个过程一直持续,直到我们得到<END>符号,这同样是一个用于标记输出序列结束的特殊符号。解码器的最终内部状态将被丢弃。

注意,这些特殊符号不一定只能是<START><END>。只要这些符号不在我们的数据语料库中,它们可以是任何字符串,这样模型就不会将它们与其他任何单词混淆。在论文中,他们使用了符号<EOS>,并且方式略有不同。我稍后会再详细介绍这一点。

上述过程是理想情况下解码器在测试阶段的工作方式。但在训练阶段,需要稍微不同的实现方式,以便更快地进行训练。我将在下一部分对此进行解释。

3.5.1 数学基础:解码器

解码器生成一个输出序列:
在这里插入图片描述
使用上下文向量 c c c

初始化:解码器的初始隐藏状态 s 0 s_0 s0被设置为上下文向量:
在这里插入图片描述

输出生成:对于输出序列中的每个时间步 t t t
在这里插入图片描述
并且,
在这里插入图片描述
其中:

  • s t s_t st是时间步 t t t时解码器的隐藏状态。
  • f d e c f_{dec} fdec是解码器的激活函数。
  • y t − 1 y_{t - 1} yt1是之前生成的输出( y 0 y_0 y0是一个序列开始标记)。
  • W W W是输出层的权重矩阵。
  • p ( y t ∣ y < t , X ) p(y_t | y_{<t}, X) p(yty<t,X)是在时间步 t t t时可能输出的概率分布。

4. 训练编码器-解码器模型

4.1 数据向量化

在深入了解其细节之前,我们首先需要对数据进行向量化处理。

我们拥有的原始数据是:

X = “nice to meet you” → Y_true = “ravi de vous rencontrer”

现在我们在目标序列的开头和结尾分别添加特殊符号<START><END>

X = “nice to meet you” → Y_true =<START> ravi de vous rencontrer <END>

接下来,使用独热编码(one-hot-encoding,ohe)对输入和输出数据进行向量化。设输入和输出表示为:

X = (x1, x2, x3, x4) → Y_true = (y0_true, y1_true, y2_true, y3_true, y4_true, y5_true)

其中 x i x_i xi y i y_i yi分别表示输入序列和输出序列的独热编码向量。它们可以表示为:

对于输入X

‘nice’ → x1 : [1 0 0 0]‘to’ → x2 : [0 1 0 0 ]‘meet’ →x3 : [0 0 1 0]‘you’ → x4 : [0 0 0 1]

对于输出 Y t r u e Y_{true} Ytrue

<START>’ → y0_true : [1 0 0 0 0 0]‘ravi’ → y1_true : [0 1 0 0 0 0]‘de’ → y2_true : [0 0 1 0 0 0]‘vous’ → y3_true : [0 0 0 1 0 0]‘rencontrer’ → y4_true : [0 0 0 0 1 0]<END>’ → y5_true : [0 0 0 0 0 1]

注意:我使用这种表示方式是为了更方便地解释。“真实序列”和“目标序列”这两个术语都用于指代我们希望模型学习的同一个句子“ravi de vous rencontrer”。

4.2 编码器的训练与测试

编码器在训练阶段和测试阶段的工作方式是相同的。它逐个接收输入序列的每个标记/单词,并将最终状态发送给解码器。随着时间的推移,其参数通过反向传播进行更新。
在这里插入图片描述

4.3 训练阶段的解码器:教师强制法

与编码器部分不同,解码器在训练阶段和测试阶段的工作方式是不一样的。因此,我们将分别来看这两个阶段。

为了训练我们的解码器模型,我们使用一种称为“教师强制法”的技术。在这种技术中,我们将上一个时间步的真实输出/标记(而不是预测的输出/标记)作为当前时间步的输入。

为了解释这一点,让我们来看一下训练的第一次迭代。在这里,我们将输入序列输入到编码器中,编码器对其进行处理,并将其最终的内部状态传递给解码器。现在看解码器部分,参考下面的图表。
在这里插入图片描述

在继续之前,请注意在解码器中,在任何时间步 t t t,输出 y t _ p r e d y_{t\_pred} yt_pred是输出数据集中整个词汇表上的概率分布,它是通过使用Softmax激活函数生成的。具有最大概率的标记被选作预测的单词。

例如,参考上面的图表, y 1 _ p r e d = [ 0.02 0.12 0.36 0.1 0.3 0.1 ] y_{1\_pred} = [0.02\ 0.12\ 0.36\ 0.1\ 0.3\ 0.1] y1_pred=[0.02 0.12 0.36 0.1 0.3 0.1]告诉我们,我们的模型认为输出序列中的第一个标记是<START>的概率是0.02,是“ravi”的概率是0.12,是“de”的概率是0.36,以此类推。我们将预测的单词取为概率最高的那个。因此,这里预测的单词/标记是“de”,概率为0.36。

在时间步1

单词<START>对应的向量 [ 1 0 0 0 0 0 ] [1\ 0\ 0\ 0\ 0\ 0] [1 0 0 0 0 0]被作为输入向量输入。现在,我希望我的模型将输出预测为 y 1 _ t r u e = [ 0 1 0 0 0 0 ] y_{1\_true}=[0\ 1\ 0\ 0\ 0\ 0] y1_true=[0 1 0 0 0 0],但由于我的模型刚刚开始训练,它会输出一些随机的值。设时间步1的预测值为 y 1 _ p r e d = [ 0.02 0.12 0.36 0.1 0.3 0.1 ] y_{1\_pred}=[0.02\ 0.12\ 0.36\ 0.1\ 0.3\ 0.1] y1_pred=[0.02 0.12 0.36 0.1 0.3 0.1],这意味着它预测第一个标记是“de”。现在,我们应该将这个 y 1 _ p r e d y_{1\_pred} y1_pred作为时间步2的输入吗?我们可以这么做,但在实践中发现,这会导致一些问题,比如收敛速度慢、模型不稳定以及性能不佳,仔细想想这是很合理的。

因此,引入了教师强制法来纠正这个问题。在教师强制法中,我们将上一个时间步的真实输出/标记(而不是预测的输出)作为当前时间步的输入。这意味着时间步2的输入将是 y 1 _ t r u e = [ 0 1 0 0 0 0 ] y_{1\_true}=[0\ 1\ 0\ 0\ 0\ 0] y1_true=[0 1 0 0 0 0],而不是 y 1 _ p r e d y_{1\_pred} y1_pred

现在时间步2的输出将是某个随机向量 y 2 _ p r e d y_{2\_pred} y2_pred。但在时间步3,我们将使用 y 2 _ t r u e = [ 0 0 1 0 0 0 ] y_{2\_true}=[0\ 0\ 1\ 0\ 0\ 0] y2_true=[0 0 1 0 0 0]作为输入,而不是 y 2 _ p r e d y_{2\_pred} y2_pred。类似地,在每个时间步,我们都将使用上一个时间步的真实输出。

最后,根据每个时间步的预测输出计算损失,并通过时间反向传播误差来更新模型的参数。使用的损失函数是目标序列/ Y t r u e Y_{true} Ytrue和预测序列/ Y p r e d Y_{pred} Ypred之间的分类交叉熵损失函数,即:

Y_true = [y0_true, y1_true, y2_true, y3_true, y4_true, y5_true]
Y_pred = [<START>’, y1_pred, y2_pred, y3_pred, y4_pred, y5_pred]

解码器的最终状态将被丢弃。

4.4 测试阶段的解码器

在实际应用中,我们不会有 Y t r u e Y_{true} Ytrue,而只有 X X X。因此,我们不能使用在训练阶段的做法,因为我们没有目标序列/ Y t r u e Y_{true} Ytrue。所以当我们测试模型时,上一个时间步的预测输出(与训练阶段不同,不是真实输出)被作为当前时间步的输入。其余部分与训练阶段相同。

假设我们已经训练好了模型,现在我们用训练时使用的单个句子来测试它。如果我们把模型训练得很好,而且只在一个句子上训练,那么它应该表现得几乎完美,但为了解释方便,假设我们的模型训练得不好或者只是部分训练好了,现在我们来测试它。让我们用下面的图表来描述这个场景。
在这里插入图片描述

在时间步1

y 1 _ p r e d = [ 0 0.92 0.08 0 0 0 ] y_{1\_pred} = [0\ 0.92\ 0.08\ 0\ 0\ 0] y1_pred=[0 0.92 0.08 0 0 0]表明模型预测输出序列中的第一个标记/单词是“ravi”的概率为(0.92),所以现在在下一个时间步,这个预测的单词/标记将被用作输入。

在时间步2

第一个时间步预测的单词/标记“ravi”在这里被用作输入。在这里,模型预测输出序列中的下一个单词/标记是“de”的概率为(0.98),然后这个“de”在时间步3被用作输入。

类似的过程在每个时间步重复,直到到达<END>标记。

更好的可视化表示如下:
在这里插入图片描述

所以根据我们训练好的模型,测试时的预测序列是“ravi de rencontrer rencontrer”。因此,尽管模型在第三次预测时是错误的,但我们仍然将其作为下一个时间步的输入。模型的正确性取决于可用数据的数量以及训练的好坏程度。模型可能会预测出错误的输出,但在测试阶段,无论如何,这个输出仍会被输入到下一个时间步。

4.5 嵌入层

我之前没有提到的一个重要细节是,编码器和解码器都会通过一个嵌入层来处理输入序列。这一步骤会降低输入单词向量的维度,因为在实际中,独热编码的向量往往维度非常大。嵌入向量为单词提供了一种更高效且有意义的表示方式。对于编码器来说,这可以通过嵌入层如何压缩单词向量维度来体现,例如,将维度从4维降低到3维。
在这里插入图片描述

这个嵌入层可以像词向量(Word2Vec)嵌入那样进行预训练,也可以与模型本身一起进行训练。

4.6 测试时的最终可视化

在这里插入图片描述

  • 在左边,编码器处理输入序列(“nice to meet you”),每个单词先经过一个嵌入层(以降低维度),然后再经过一系列的长短期记忆(LSTM)层。编码器输出一个包含隐藏状态 h t h_t ht和细胞状态 c t c_t ct上下文向量,这个上下文向量总结了输入序列。
  • 在右边,解码器接收上下文向量并生成输出序列(“ravi de rencontrer”)。解码器使用长短期记忆(LSTM)单元来生成每个单词,将前一个单词作为输入(从一个特殊标记“”开始),经过另一个嵌入层,并通过Softmax层生成预测结果。

这张图展示了模型如何使用嵌入层和循环层将输入序列翻译成目标序列。

5. 编码器-解码器模型的缺点

这种架构存在两个主要缺点,都与长度相关。

首先,和人类一样,这种架构的记忆能力有限。长短期记忆网络(Long Short Term Memory,LSTM)的最终隐藏状态S或W,负责封装整个待翻译的句子。通常,S或W仅由几百个单元(即浮点数)组成。然而,往这个固定维度的向量中塞入过多信息会增加神经网络的模糊性。从“有损压缩”的角度来理解神经网络必须执行的操作,有时是非常有用的。

其次,一般来说,神经网络越深,训练难度就越大。对于循环神经网络而言,序列越长,沿时间维度的神经网络就越深。这会导致梯度消失问题,即循环神经网络从目标中学习到的梯度信号在反向传播时会消失。即使是专门为防止梯度消失而设计的循环神经网络,比如长短期记忆网络(LSTM),这仍然是一个根本性的问题。
在这里插入图片描述

此外,对于更复杂和更长的句子,我们还有诸如注意力模型(Attention Models)和Transformer模型。

6. 编码器-解码器架构的改进

6.1 添加嵌入层

嵌入层将输入标记转换为密集的向量表示,使模型能够学习输入序列中单词或标记的有意义表示。
在这里插入图片描述
通过使用可训练的嵌入层,并探索诸如预训练词嵌入或上下文嵌入等技术,我们可以丰富输入表示,使模型能够更有效地捕捉细微的语义和句法信息。这种增强有助于更好地理解和生成序列数据。
在这里插入图片描述

6.2 使用深度长短期记忆网络(LSTM)

长短期记忆网络(LSTM)是循环神经网络(RNN)的一种变体,以其捕捉序列数据中长距离依赖关系的能力而闻名。加深长短期记忆网络(LSTM)层能使模型学习输入和输出序列的层次表示,从而提高性能。
在这里插入图片描述
增加长短期记忆网络(LSTM)层的深度,并结合残差连接或层归一化等技术,有助于缓解梯度消失等问题,并促进更深层次网络的训练。这些改进使模型能够学习数据中更复杂的模式和依赖关系,从而实现更好的序列生成和理解。

6.3 反转输入

在机器翻译中,例如英语到印地语或英语到法语的转换,反转输入序列在某些情况下已被证明可以通过帮助捕捉长距离依赖关系和缓解梯度消失问题来提高模型性能。
在这里插入图片描述

然而,其有效性可能因语言特征和数据集的复杂性而异,并且它可能不会在所有情况下都持续提高性能。需要进行仔细的评估和实验,以确定反转输入序列对于特定任务和数据集是否有益。

我们现在已经理解了编码器-解码器的概念。现在,如果我们研读伊利亚·苏茨克弗(Ilya Sutskever)撰写的著名研究论文《使用神经网络进行序列到序列学习》(“Sequence to Sequence Learning with Neural Networks”),那么我们将能很好地理解这篇论文的概念。下面我总结一下这篇论文的内容:

  • 在翻译中的应用:该模型专注于将英语翻译成法语,展示了序列到序列学习在神经机器翻译中的有效性。
  • 特殊的句尾符号:数据集中的每个句子都以一个独特的句尾符号<EOS>结束,使模型能够识别序列的结束。
  • 数据集:该模型在一个包含1200万个句子的子集上进行训练,这些句子包含3.48亿个法语单词和3.04亿个英语单词,数据来自一个公开可用的数据集。
  • 词汇限制:为了控制计算复杂度,两种语言都使用了固定的词汇表,英语使用了最常见的16万个单词,法语使用了8万个单词。不在这些词汇表中的单词被替换为一个特殊的“UNK”标记。
  • 反转输入序列:在将输入句子输入模型之前将其反转,结果发现这显著提高了模型的学习效率,特别是对于较长的句子。
  • 词嵌入:该模型使用了一个1000维的词嵌入层来表示输入单词,为每个单词提供了密集且有意义的表示。
  • 架构细节:输入(编码器)和输出(解码器)模型都有4层,每层包含1000个单元,展示了一个基于深度长短期记忆网络(LSTM)的架构。
  • 输出层和训练:输出层使用SoftMax函数在最大词汇表上生成概率分布。该模型在这些设置下进行端到端的训练。
  • 性能——BLEU分数:该模型的BLEU分数达到了34.81,超过了同一数据集上基于统计机器翻译系统的基础文件的分数33.30,标志着神经机器翻译的重大进步。

7. 示例:基于神经网络的编码器-解码器架构

我们可以在编码器-解码器架构中使用卷积神经网络(CNN)、循环神经网络(RNN)和长短期记忆网络(LSTM)来解决不同类型的问题。结合使用不同类型的网络有助于捕捉输入和输出数据序列之间的复杂关系。以下是一些可以使用卷积神经网络(CNN)、循环神经网络(RNN)、长短期记忆网络(LSTM)、Transformer等的不同场景或问题示例:

  • 卷积神经网络(CNN)作为编码器,循环神经网络(RNN)/长短期记忆网络(LSTM)作为解码器:这种架构可用于图像字幕生成等任务,其中输入是一张图像,输出是描述该图像的单词序列。卷积神经网络(CNN)可以从图像中提取特征,而循环神经网络(RNN)/长短期记忆网络(LSTM)可以生成相应的文本序列。回想一下,卷积神经网络(CNN)擅长从图像中提取特征,这就是为什么在涉及图像的任务中它们可以用作编码器。此外,循环神经网络(RNN)/长短期记忆网络(LSTM)擅长处理诸如单词序列之类的序列数据,并且可以在涉及文本序列的任务中用作解码器。
  • 循环神经网络(RNN)/长短期记忆网络(LSTM)作为编码器,循环神经网络(RNN)/长短期记忆网络(LSTM)作为解码器:这种架构可用于机器翻译等任务,其中输入和输出都是长度可变的单词序列。编码器中的循环神经网络(RNN)/长短期记忆网络(LSTM)可以将输入的单词序列编码为隐藏状态或数值表示,而解码器中的循环神经网络(RNN)/长短期记忆网络(LSTM)可以生成不同语言的相应单词输出序列。下面的图片展示了在编码器和解码器网络中都使用循环神经网络(RNN)的编码器-解码器架构。作为输入的单词序列是英语,输出是德语的机器翻译结果。
    在这里插入图片描述

在编码器-解码器架构中使用循环神经网络(RNN)存在一个缺点。编码器网络中的最终数值表示或隐藏状态必须表示数据序列的整个上下文和含义。如果数据序列足够长,这可能会变得具有挑战性,并且在以数值表示的形式压缩整个信息的过程中,关于序列开头的信息可能会丢失。

在编码器-解码器架构中使用不同类型的神经网络,如卷积神经网络(CNN)、循环神经网络(RNN)、长短期记忆网络(LSTM)等时,需要记住以下一些限制:

  • 卷积神经网络(CNN)的计算成本可能很高,并且可能需要大量的训练数据。
  • 循环神经网络(RNN)/长短期记忆网络(LSTM)可能会受到梯度消失/梯度爆炸的影响,并且可能需要仔细的初始化和正则化。
  • 结合使用不同类型的网络会使模型更加复杂,并且难以训练。

8. 编码器-解码器神经网络架构的应用

以下是编码器-解码器神经网络架构在现实生活/实际世界中的一些应用:

  • Transformer模型:正如瓦斯瓦尼(Vaswani)等人在论文《注意力就是你所需要的一切》(“Attention Is All You Need”)中最初提出的那样,Transformer模型由编码器和解码器组成。每个部分都由使用自注意力机制的层构成。编码器处理输入数据(如文本)并创建其富含上下文的表示。解码器使用这些表示以及它的输入(如句子中的前一个单词)来生成输出序列。T5(文本到文本转换Transformer)使用了编码器-解码器架构。还有另一个例子是BART(双向自回归Transformer),它将双向编码器(如BERT)与自回归解码器(如GPT)结合在一起。
  • Make-a-Video:脸书/元(Facebook / Meta)最近推出的人工智能系统Make-a-Video可能由深度学习技术驱动,可能包括用于将文本提示转换为视频内容的编码器-解码器架构。这种常用于序列到序列转换的架构,将使用编码器将输入文本转换为密集向量表示,并使用解码器将该向量转换为视频内容。然而,考虑到从文本创建视频的复杂性,该系统可能还会采用诸如生成对抗网络(Generative Adversarial Networks,GANs)或变分自编码器(Variational Autoencoders,VAEs)等先进的生成模型,这些模型在生成高质量、逼真的图像方面表现出色。此外,为了学习从文本到视觉的映射并理解世界的动态,它可能会利用大量的配对文本-图像数据和视频片段,可能采用无监督学习或自监督学习技术。
  • 机器翻译:编码器-解码器架构最常见的应用之一是机器翻译。正如上面所示(使用循环神经网络(RNN)的编码器-解码器架构),将一种语言的单词序列翻译成另一种语言。编码器-解码器模型可以在大量的双语文本语料库上进行训练,以学习如何将一种语言的单词序列映射到另一种语言的等效序列。
  • 图像字幕生成:图像字幕生成是编码器-解码器架构的另一个应用。在这种应用中,图像由编码器(使用卷积神经网络(CNN))进行处理,输出被传递给解码器(循环神经网络(RNN)或长短期记忆网络(LSTM)),解码器生成图像的文本描述。这可用于自动图像标记和字幕生成等应用。

9. 用神经网络进行序列到序列学习(实践操作)

在本教程中,我们将逐步介绍如何使用PyTorch实现一个基于编码器-解码器架构的基本序列到序列(Seq2Seq)模型。序列到序列(Seq2Seq)模型广泛用于机器翻译、文本摘要和问答等任务。它的工作方式是将一个序列(如一个句子)作为输入,并生成一个序列(如一个翻译后的句子)作为输出。

我们将使用Multi30k数据集,执行一个简化的机器翻译任务,即把德语翻译成英语。

什么是Multi30k数据集?

Multi30k数据集是一组英语-德语平行句子的集合。数据集中的每个句子都是一对德语句子及其相应的英语翻译。这个数据集通常用于训练机器翻译模型。

对于我们的模型,我们将对句子进行分词,为两种语言构建词汇表,并训练模型将德语句子翻译成英语。

项目概述

  1. 数据预处理:我们将对德语和英语句子进行分词,并为训练准备数据集。
  2. 模型架构:我们将创建两个组件:
    • 编码器:读取源语言(德语)句子,并将其转换为上下文向量。
    • 解码器:使用上下文向量生成目标语言(英语)句子。
  3. 训练:在数据集上训练模型,以最小化预测的目标句子与实际目标句子之间的损失。
  4. 评估:我们将通过翻译新句子并计算BLEU分数来评估模型,BLEU分数通常用于评估机器翻译的质量。

步骤1:安装和导入库

我们将使用几个库:用于深度学习的torch、用于处理数据集的torchtext,以及用于分词的spacy

使用以下命令安装所需的库:

pip install torch torchtext spacy
python -m spacy download de_core_news_sm
python -m spacy download en_core_web_sm

接下来,导入库:

import torch
import torch.nn as nn
import torch.optim as optim
import random
import spacy
from torchtext.data import Field, BucketIterator
from torchtext.datasets import Multi30k

步骤2:数据预处理

我们将使用Multi30k数据集,它提供了英语和德语的句子对。为了帮助模型更好地学习源语言和目标语言句子之间的对齐关系,德语句子将被反转。

分词

为了将句子拆分成单词,我们将使用spacy

spacy_de = spacy.load('de_core_news_sm')
spacy_en = spacy.load('en_core_web_sm')
def tokenize_de(text):return [tok.text for tok in spacy_de.tokenizer(text)][::-1]
def tokenize_en(text):return [tok.text for tok in spacy_en.tokenizer(text)]

字段定义

我们使用torchtextField来定义数据将如何处理。我们指定如何对数据进行分词,以及如何处理起始(<sos>)和结束(<eos>)标记。

SRC = Field(tokenize=tokenize_de, init_token='<sos>', eos_token='<eos>', lower=True)
TRG = Field(tokenize=tokenize_en, init_token='<sos>', eos_token='<eos>', lower=True)

加载和构建词汇表

现在我们加载数据集,并为源语言(德语)和目标语言(英语)构建词汇表。

train_data, valid_data, test_data = Multi30k.splits(exts=('.de', '.en'), fields=(SRC, TRG))
SRC.build_vocab(train_data, min_freq=2)
TRG.build_vocab(train_data, min_freq=2)

min_freq=2确保只有在训练数据中至少出现两次的单词才会被包含在词汇表中。

步骤3:构建编码器

编码器是一个循环神经网络(RNN),它读取单词序列(在这种情况下是德语句子),并将其编码为上下文向量。

class Encoder(nn.Module):def __init__(self, input_dim, emb_dim, hid_dim, n_layers, dropout):super().__init__()self.embedding = nn.Embedding(input_dim, emb_dim)self.rnn = nn.GRU(emb_dim, hid_dim, n_layers, dropout=dropout)self.dropout = nn.Dropout(dropout)def forward(self, src):# src = [src_len, batch_size]embedded = self.dropout(self.embedding(src)) # [src_len, batch_size, emb_dim]outputs, hidden = self.rnn(embedded)return hidden
  • input_dim:源语言词汇表的大小。
  • emb_dim:嵌入维度(将每个单词转换为固定大小的向量)。
  • hid_dim:门控循环单元(GRU)的隐藏维度(循环神经网络(RNN)每层的单元数量)。
  • n_layers:门控循环单元(GRU)的层数。
  • dropout:用于防止过拟合的正则化。

步骤4:构建解码器

解码器根据编码器生成的上下文向量,一次生成一个目标句子(英语)中的单词。

class Decoder(nn.Module):def __init__(self, output_dim, emb_dim, hid_dim, n_layers, dropout):super().__init__()self.embedding = nn.Embedding(output_dim, emb_dim)self.rnn = nn.GRU(emb_dim, hid_dim, n_layers, dropout=dropout)self.fc_out = nn.Linear(hid_dim, output_dim)self.dropout = nn.Dropout(dropout)def forward(self, input, hidden):# input = [batch_size]input = input.unsqueeze(0) # [1, batch_size]embedded = self.dropout(self.embedding(input)) # [1, batch_size, emb_dim]output, hidden = self.rnn(embedded, hidden) # [1, batch_size, hid_dim], [n_layers, batch_size, hid_dim]prediction = self.fc_out(output.squeeze(0)) # [batch_size, output_dim]return prediction, hidden
  • output_dim:目标语言词汇表的大小(英语)。
  • embedding:将单词转换为向量。
  • fc_out:全连接层,用于将隐藏状态映射到输出词汇表。

步骤5:构建序列到序列(Seq2Seq)模型

序列到序列(Seq2Seq)模型将编码器和解码器结合在一起。它使用编码器将源序列编码为上下文向量,并使用解码器生成目标序列。

class Seq2Seq(nn.Module):def __init__(self, encoder, decoder, device):super().__init__()self.encoder = encoderself.decoder = decoderself.device = devicedef forward(self, src, trg, teacher_forcing_ratio=0.5):trg_len = trg.shape[0]batch_size = trg.shape[1]trg_vocab_size = self.decoder.fc_out.out_featuresoutputs = torch.zeros(trg_len, batch_size, trg_vocab_size).to(self.device)hidden = self.encoder(src)input = trg[0, :]for t in range(1, trg_len):output, hidden = self.decoder(input, hidden)outputs[t] = outputtop1 = output.argmax(1)input = trg[t] if random.random() < teacher_forcing_ratio else top1return outputs
  • teacher_forcing_ratio:确定在训练期间是使用实际的目标标记还是预测的标记作为下一个输入(有助于稳定训练)。
  • outputs:存储每个时间步的预测结果。

步骤6:训练模型

我们定义训练循环,在其中输入德语句子,通过序列到序列(Seq2Seq)模型运行它,并使用交叉熵计算损失。

def train(model, iterator, optimizer, criterion, clip):model.train()epoch_loss = 0for i, batch in enumerate(iterator):src = batch.srctrg = batch.trgoptimizer.zero_grad()output = model(src, trg)output_dim = output.shape[-1]output = output[1:].view(-1, output_dim)trg = trg[1:].view(-1)loss = criterion(output, trg)loss.backward()torch.nn.utils.clip_grad_norm_(model.parameters(), clip)optimizer.step()epoch_loss += loss.item()return epoch_loss / len(iterator)
  • 梯度裁剪:用于防止梯度爆炸。
  • 交叉熵损失:衡量预测标记与实际标记之间的误差。

步骤7:评估模型

评估的工作方式与训练类似,但我们禁用反向传播,因为我们只对模型的性能感兴趣。

def evaluate(model, iterator, criterion):model.eval()epoch_loss = 0with torch.no_grad():for i, batch in enumerate(iterator):src = batch.srctrg = batch.trgoutput = model(src, trg, 0)output_dim = output.shape[-1]output = output[1:].view(-1, output_dim)trg = trg[1:].view(-1)loss = criterion(output, trg)epoch_loss += loss.item()return epoch_loss / len(iterator)

步骤8:初始化模型和超参数

现在,我们初始化模型、优化器和损失函数,并指定超参数。

INPUT_DIM = len(SRC.vocab)
OUTPUT_DIM = len(TRG.vocab)
ENC_EMB_DIM = 256
DEC_EMB_DIM = 256
HID_DIM = 512
N_LAYERS = 2
ENC_DROPOUT = 0.5
DEC_DROPOUT = 0.5enc = Encoder(INPUT_DIM, ENC_EMB_DIM, HID_DIM, N_LAYERS, ENC_DROPOUT)
dec = Decoder(OUTPUT_DIM, DEC_EMB_DIM, HID_DIM, N_LAYERS, DEC_DROPOUT)device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = Seq2Seq(enc, dec, device).to(device)
optimizer = optim.Adam(model.parameters())
TRG_PAD_IDX = TRG.vocab.stoi[TRG.pad_token]
criterion = nn.CrossEntropyLoss(ignore_index=TRG_PAD_IDX)

步骤9:训练循环

我们在一定数量的训练轮次(epoch)中训练模型。

N_EPOCHS = 10
CLIP = 1for epoch in range(N_EPOCHS):train_loss = train(model, train_iterator, optimizer, criterion, CLIP)valid_loss = evaluate(model, valid_iterator, criterion)print(f'Epoch: {epoch+1}')print(f'Train Loss: {train_loss:.3f} | Val. Loss: {valid_loss:.3f}')

步骤10:使用BLEU分数进行测试

最后,我们可以通过翻译新句子并评估BLEU分数来测试我们的模型。

def translate_sentence(sentence, src_field, trg_field, model, device, max_len=50):model.eval()tokens = [token.text.lower() for token in spacy_de(sentence)]tokens = [src_field.init_token] + tokens + [src_field.eos_token]src_indexes = [src_field.vocab.stoi[token] for token in tokens]src_tensor = torch.LongTensor(src_indexes).unsqueeze(1).to(device)with torch.no_grad():hidden = model.encoder(src_tensor)trg_indexes = [trg_field.vocab.stoi[trg_field.init_token]]for i in range(max_len):trg_tensor = torch.LongTensor([trg_indexes[-1]]).to(device)with torch.no_grad():output, hidden = model.decoder(trg_tensor, hidden)pred_token = output.argmax(1).item()trg_indexes.append(pred_token)if pred_token == trg_field.vocab.stoi[trg_field.eos_token]:breaktrg_tokens = [trg_field.vocab.itos[i] for i in trg_indexes]return trg_tokens[1:]

这个函数使用训练好的Seq2Seq模型将德语句子翻译成英语。

10. 总结

在这篇博客中,我们借助强大的编码器 - 解码器架构探索了序列建模。从处理序列数据面临的挑战入手,我们探讨了该架构如何高效处理诸如神经机器翻译等序列到序列的任务。我们深入研究了编码器在捕捉输入信息方面的作用,以及解码器在生成连贯输出方面的功能,并阐述了它们背后的数学原理。

我们还介绍了像“教师强制”这样的训练技巧、实际的实现步骤,并探讨了“苏茨克弗模型”作为编码器 - 解码器方法在现实世界中的应用。实践示例为构建和评估序列到序列模型提供了详细的分步指南。

该架构的应用范围不止于翻译,还广泛用于文本摘要、语音识别等领域。通过掌握其理论和实际应用,我们能够利用编码器 - 解码器架构解决不同领域的各种复杂问题。

11. 知识测试!

  1. 编码器中使用双向LSTM如何提升Seq2Seq模型的性能?
    • 预期答案:双向LSTM会从正向和反向两个方向处理输入序列,能从过去和未来的状态中捕捉更多上下文信息。这有助于编码器构建更全面的输入序列表示,从而提升翻译或摘要等下游任务的性能。
  2. 描述如何为实时翻译系统实现一个Seq2Seq模型,可能会面临哪些挑战?
    • 预期答案:实时翻译系统需要低延迟,因此在不降低准确性的前提下优化模型速度至关重要。挑战包括处理不同长度的句子、保持上下文连贯以及确保目标语言表达流畅。像注意力机制和束搜索这样的技术会有所帮助,但必须仔细调整以平衡准确性和速度。
  3. 解释注意力机制与传统Seq2Seq模型的区别,以及它们如何处理长序列?
    • 预期答案:传统Seq2Seq模型会将输入序列压缩成固定大小的向量,这可能导致信息丢失,尤其是在处理长序列时。而注意力机制允许模型在生成输出的每个单词时聚焦于输入序列的特定部分,并根据上下文动态调整“焦点”,从而更有效地处理长序列。
  4. 在哪些场景下你会更倾向于使用Transformer模型而非传统基于RNN的Seq2Seq模型?
    • 预期答案:在处理非常长的序列或者并行计算至关重要的情况下,会优先选择Transformer模型,因为它们不像RNN那样依赖顺序处理。通过自注意力机制,Transformer模型还能有效捕捉长距离依赖关系,适用于语言建模、翻译等任务,甚至非语言任务,如图像处理。
  5. Transformer模型中位置编码的作用是什么,为什么它是必要的?
    • 预期答案:由于Transformer模型是并行处理输入的,而非按顺序处理,位置编码能为模型提供序列中每个标记的位置信息。这种编码使模型能够捕捉标记的顺序,这对于理解输入序列的结构和含义至关重要。
  6. 如何解决Seq2Seq模型中的过拟合问题?
    • 预期答案:可以通过一些技术来缓解Seq2Seq模型的过拟合问题,如正则化(例如L2正则化、丢弃法)、使用更大的数据集、数据增强、提前停止训练,以及采用更复杂的架构(如注意力机制)来提高泛化能力。此外,监控验证损失并根据需要调整模型复杂度也有助于防止过拟合。
  7. 请讨论在Seq2Seq模型中使用束搜索和贪心搜索的权衡?
    • 预期答案:贪心搜索在每一步都选择最可能的单词,速度更快,但可能会错过全局最优序列。而束搜索在每一步会考虑多个潜在序列(束宽),更有可能找到最优序列,但会增加计算复杂度。选择哪种方法取决于应用对准确性和速度的需求。
  8. 如何修改Seq2Seq模型以处理多语言翻译任务?
    • 预期答案:对于多语言翻译任务,可以引入一个共享编码器和多个解码器,每个解码器对应一种不同的目标语言,或者使用一个带有特殊标记来指示目标语言的单一解码器。在多种语言上微调模型并结合迁移学习也能提升跨语言的性能。
  9. 描述如何为聊天机器人应用实现一个Seq2Seq模型,关键考虑因素有哪些?
    • 预期答案:为聊天机器人实现Seq2Seq模型需要确保模型能够生成连贯且符合上下文的回复。关键考虑因素包括处理多轮对话、在交互过程中保持上下文连贯、确保回复的多样性以及减少生成文本中的偏差。引入强化学习或根据人类反馈进行微调可以提高聊天机器人的有效性。
  10. Seq2Seq模型在处理文本摘要或翻译等任务时有哪些局限性,如何解决这些问题?
    • 预期答案:Seq2Seq模型可能难以保持连贯性、处理长序列以及生成语法正确的输出。可以通过使用注意力机制、引入更大且更多样化的数据集、采用预训练模型(如BERT或GPT)并在特定任务上进行微调来解决这些局限性。像BLEU和ROUGE这样的评估指标有助于评估和提升性能。
  11. 编码器和解码器必须是同一类型的神经网络吗?
    • 预期答案:不,编码器和解码器不必是同一类型的神经网络。在许多实现中,两者都是RNN(例如LSTM或GRU),但它们可以根据任务需求有所不同。例如,编码器可以是用于处理图像的卷积神经网络(CNN),而解码器可以是用于生成序列的RNN。关键要求是编码器的输出应与解码器的输入兼容。
  12. 除了机器翻译,你能想到编码器 - 解码器架构还可以应用于哪些其他场景?
    • 预期答案:可以,编码器 - 解码器架构可应用于多种任务,包括文本摘要。在文本摘要中,编码器处理输入文档并创建压缩表示,解码器则生成捕捉关键信息的简短版本。其他应用包括图像字幕生成(编码器是处理图像的CNN,解码器是生成字幕的RNN)和语音识别(编码器处理音频信号,解码器生成相应的文本)。
  13. 在编码器 - 解码器模型中训练解码器时,“教师强制”是什么,为什么要使用它?讨论这种方法可能存在的缺点。
    • 预期答案:“教师强制”是一种训练技术,在训练过程中,将实际的目标输出作为解码器的下一个输入,而非模型的预测结果。它通过在每一步提供正确的上下文来加速收敛并提高学习效果。
  14. 从数学角度看,编码器如何处理输入序列?
    • 预期答案:在每个时间步t,编码器使用 h t = f ( e t , h t − 1 ) h_t = f(e_t, h_{t - 1}) ht=f(et,ht1)更新其隐藏状态,其中 e t e_t et是嵌入后的输入, f f f是像LSTM或GRU这样的循环函数。
  15. 讨论解码器测试阶段嵌入层的作用。如果有区别的话,它与训练阶段的作用有何不同?
    • 预期答案:嵌入层将标记转换为捕捉语义关系的密集向量表示,使模型能够更有效地处理和学习文本数据。
  16. 举例说明编码器 - 解码器架构在机器翻译之外的应用场景。描述问题以及该架构如何解决该问题。
    • 预期答案:在文本摘要中,编码器 - 解码器模型将长篇文档(编码器)浓缩为简短摘要(解码器),有效捕捉要点。
  17. 编码器 - 解码器架构中解码器模块的数学基础是什么?解释解码器如何生成输出序列。
    • 预期答案:解码器在每一步计算 s t = f ( e t , s t − 1 , c ) s_t = f(e_t, s_{t - 1}, c) st=f(et,st1,c),其中 e t e_t et是前一个标记的嵌入, s t − 1 s_{t - 1} st1是前一个状态, c c c是上下文向量,然后预测下一个标记。
  18. 假设我们使用神经网络来实现编码器 - 解码器架构,编码器和解码器必须是同一类型的神经网络吗?
    • 预期答案:不,编码器和解码器不必是同一类型的神经网络。例如,在图像字幕生成等实际应用中,通常使用卷积神经网络(CNN)作为编码器处理图像,而使用循环神经网络(RNN)或Transformer作为解码器生成描述性文本。

参考文献

  • https://medium.com/@vipra_singh/llm-architectures-explained-encoder-decoder-architecture-part-4-b96ace71394c

相关文章:

LLM架构解析:编码器-解码器架构(Encoder-Decoder Architecture)(第四部分)—— 从基础原理到实践应用的深度探索

本专栏深入探究从循环神经网络&#xff08;RNN&#xff09;到Transformer等自然语言处理&#xff08;NLP&#xff09;模型的架构&#xff0c;以及基于这些模型构建的应用程序。 本系列文章内容&#xff1a; NLP自然语言处理基础词嵌入&#xff08;Word Embeddings&#xff09…...

Unity VideoPlayer 播放无声音

增加一个videoPlayer下挂&#xff0c;audiorSource脚本 this.videoPlayer.EnableAudioTrack(0, true); this.videoPlayer.audioOutputMode VideoAudioOutputMode.AudioSource; this.videoPlayer.SetTargetAudioSource(0, this.videoPlayer.GetComponent<AudioSource>()…...

【Kafka基础】监控与维护:动态配置管理,灵活调整集群行为

1 基础配置操作 1.1 修改主题保留时间 /export/home/kafka_zk/kafka_2.13-2.7.1/bin/kafka-configs.sh --alter \--bootstrap-server 192.168.10.33:9092 \--entity-type topics \--entity-name yourtopic \--add-config retention.ms86400000 参数说明&#xff1a; retention…...

AutoGen深度解析:从核心架构到多智能体协作的完整指南

AutoGen是微软推出的一个革命性多智能体(Multi-Agent)框架&#xff0c;它通过模块化设计和灵活的对话机制&#xff0c;极大地简化了基于大型语言模型(LLM)的智能体系统开发。本文将深入剖析AutoGen的两个核心模块——core基础架构和agentchat多智能体对话系统&#xff0c;带您全…...

接口请求控制工具

接口请求控制工具 功能说明代理转发安全控制访问控制错误处理配置管理日志管理 技术栈快速开始环境要求配置说明启动服务 工具源码 功能说明 代理转发 支持多路由配置支持静态资源代理灵活的路由规则配置支持请求转发和响应处理支持负载均衡 支持多目标服务器配置提供多种负载…...

Git 实践笔记

这里写自定义目录标题 一、将当前改动追加到某次commit上二、git 强制修改分支位置 一、将当前改动追加到某次commit上 stash工作区中的当前改动 git stash假设需要修改的commit是 f744c32&#xff0c;将HEAD移动到需要改动的commit的父提交上 git rebase f744c32^ --interact…...

记一个Unity中Humanoid中骨骼与武器脱离的问题

在Untiy中&#xff0c;有时人物的Humanoid的骨骼对应上了&#xff0c;但是套用动画的时候武器等节点有时会脱离&#xff0c;这是因为Humanoid只包含了人物骨骼&#xff0c;不包括其他额外的骨骼&#xff0c;因此如果想要武器节点也跟随&#xff0c;需要在Humanoid中也绑定骨骼设…...

Python asyncio

一些Pre关键概念 asyncio 本质上还是单进程单线程的Python程序&#xff1b; 建立event_loop 概念&#xff0c;上面event_loop 可以理解为大脑&#xff0c;下面是若干个可执行的Task&#xff1b; Task 没有控制权&#xff0c;没有办法控制event_loop 执行某个Task&#xff0c;只…...

【前端分享】JavaScript异步编程详解!

JavaScript 的异步编程是其核心特性之一&#xff0c;主要用于处理非阻塞操作&#xff08;如网络请求、文件读写、定时任务等&#xff09;。由于 JavaScript 是单线程的&#xff0c;异步机制可以避免代码阻塞&#xff0c;提高性能和用户体验。以下是 JavaScript 异步编程的核心概…...

深度学习基础--CNN经典网络之InceptionV1研究与复现(pytorch)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 InceptionV1是提出并行卷积结构&#xff0c;是CNN的经典网络之一&#xff1b;本次任务是探究InceptionV1结构并进行复现实验&#xff1b;欢迎收藏 关注…...

用 Vue 3 + D3.js 实现动态数据流图

文章目录 一、项目背景与功能概览二、项目准备与依赖安装2.1 安装 Vue 3 项目2.2 安装 D3.js2.3 项目结构 三、实现动态数据流图3.1 创建 DataFlowChart 组件3.2 动态更新数据流3.2.1 动态更新边和节点位置3.2.2 动画效果 四、节点拖拽与编辑功能实现4.1 添加节点拖拽功能4.2 编…...

46、Spring Boot 详细讲义(三)

五、Spring Boot 与 Web 开发 1. 简介 Spring Boot 是基于 Spring Framework 开发的一个框架,旨在简化配置,快速构建应用。它内嵌 Tomcat 等 servlet 容器,支持 RESTful API 开发,处理静态资源,以及集成视图层技术如 Thymeleaf 和 Freemarker。 2. Spring MVC 集成 Sp…...

热门面试题第15天|最大二叉树 合并二叉树 验证二叉搜索树 二叉搜索树中的搜索

654.最大二叉树 力扣题目地址(opens new window) 给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下&#xff1a; 二叉树的根是数组中的最大元素。左子树是通过数组中最大值左边部分构造出的最大二叉树。右子树是通过数组中最大值右边部分构造出的最大…...

为了避免unboundLocalError和为什么X的值一直不变呢?

## 1.为了避免unboundLocalError 发生unboundLocalError&#xff01; def generate_integer(level):if level 1:X randint(1,9)return X这里出错的原因在于&#xff0c;一旦if 后面的条件没有成立&#xff0c;然后X根本没出生&#xff0c;然后你去使用它&#xff0c;这是有…...

Express中间件(Middleware)详解:从零开始掌握(1)

1. 中间件是什么&#xff1f; 想象中间件就像一个"加工流水线"&#xff0c;请求(Request)从进入服务器到返回响应(Response)的过程中&#xff0c;会经过一个个"工作站"进行处理。 简单定义&#xff1a;中间件是能够访问请求对象(req)、响应对象(res)和下…...

Linux升级gcc版本

目录 1.安装 scl 工具集 2.安装新版本gcc 3.启用新版本 gcc 4.将启动新版本gcc指令写入配置文件 本文主要讲述如何去升级 linux 操作系统下的 gcc 编译器版本。 1.安装 scl 工具集 sudo yum install centos-release-scl scl-utils-build 由于作者已经安装过&#xff0c;…...

【概念】什么是UI(User interface)什么是UX(User experience)?

1. 软件生命周期管理 (Software Life Cycle Management) 解释&#xff1a; 中文&#xff1a; 软件生命周期管理是指从软件规划、设计、开发、测试、部署到后续维护甚至退役的整个过程。English: Software Life Cycle Management refers to the systematic process of plannin…...

【GIT】git pull --rebase 功能解析

1. git pull 命令基础 git pull 是一个常用的 Git 命令&#xff0c;用于从远程仓库获取最新的更改&#xff0c;并尝试将这些更改合并到当前分支中。这通常涉及两个步骤&#xff1a;首先&#xff0c;git fetch 命令从远程仓库下载最新的更改&#xff1b;然后&#xff0c;git me…...

难度偏低,25西电人工智能学院821、833、834考研录取情况

1、人工智能学院各个方向 2、人工智能学院近三年复试分数线对比 学长、学姐分析 由表可看出&#xff1a; 1、智能院25年院线相对于24年院线 全部专业下降比较多&#xff0c;其中控制科学与工程下降20分&#xff0c;计算机科学与技术下降20分&#xff0c;计算机技术[专硕]下降…...

L2-051 满树的遍历

L2-051 满树的遍历 - 团体程序设计天梯赛-练习集 (pintia.cn) 题解 数据结构选择 为了表示树的结构&#xff0c;我们可以使用邻接表。邻接表是一种常用的图和树的表示方法&#xff0c;它能够高效地存储每个节点的子节点信息。在本题中&#xff0c;我们可以使用一个数组 g&am…...

2025年电子电气与新材料国际学术会议

重要信息 官网&#xff1a;www.iceenm.org&#xff08;点击了解详情&#xff09; 时间&#xff1a;2025年4月25-27日 地点&#xff1a;中国-杭州 部分介绍 征稿主题 电子电气 新材料 电力电子器件和系统设计 可再生能源与电网集成技术 下一代半导体…...

数字人:打破次元壁,从娱乐舞台迈向教育新课堂(4/10)

摘要&#xff1a;数字人正从娱乐领域的璀璨明星跨界到教育领域的智慧导师&#xff0c;展现出无限潜力。从虚拟偶像、影视游戏到直播短视频&#xff0c;数字人在娱乐产业中大放异彩&#xff0c;创造巨大商业价值。在教育领域&#xff0c;数字人助力个性化学习、互动课堂和虚拟实…...

【Hyperlane 】轻松实现大文件分块上传!

【Hyperlane 】轻松实现大文件分块上传&#xff01; 痛点直击&#xff1a;大文件上传不再是难题 在云存储、音视频处理、文件协作等场景中&#xff0c;大文件上传常面临中断重试成本高、内存占用大、网络不稳定等挑战。传统方案要么复杂笨重&#xff0c;要么性能瓶颈明显。 现…...

【深入浅出 Git】:从入门到精通

这篇文章介绍下版本控制器。 【深入浅出 Git】&#xff1a;从入门到精通 Git是什么Git的安装Git的基本操作建立本地仓库配置本地仓库认识工作区、暂存区、版本库的概念添加文件添加文件到暂存区提交文件到版本库提交文件演示 理解.git目录中的文件HEAD指针与暂存区objects对象 …...

APP动态交互原型实例|墨刀变量控制+条件判断教程

引言 不同行业的产品经理在绘制原型图时&#xff0c;拥有不同的呈现方式。对于第三方软件技术服务公司的产品经理来说&#xff0c;高保真动态交互原型不仅可以在开发前验证交互逻辑&#xff0c;还能为甲方客户带来更直观、真实的体验。 本文第三部分将分享一个实战案例&#…...

第二节:React 基础篇-受控组件 vs 非受控组件

一、场景题&#xff1a;设计一个实时搜索输入框&#xff0c;说明选择依据 受控组件 vs 非受控组件 核心区别 特征受控组件非受控组件数据管理由React状态&#xff08;state&#xff09;控制通过DOM元素&#xff08;ref&#xff09;直接访问更新时机每次输入触发onChange提交…...

电脑的usb端口电压会大于开发板需要的电压吗

电脑的USB端口电压通常不会大于开发板所需的电压&#xff0c;以下是详细解释&#xff1a; 1. USB端口电压标准 根据USB规范&#xff0c;USB接口的标称输出电压为5V。实际测量时&#xff0c;USB接口的输出电压会略有偏差&#xff0c;通常在4.75V到5.25V之间。USB 2.0和USB 3.0…...

软件界面设计:打造用户喜爱的交互体验

在数字化飞速发展的当下&#xff0c;软件已渗透进生活的各个角落&#xff0c;从日常使用的社交、购物软件&#xff0c;到专业领域的办公、设计软件&#xff0c;其重要性不言而喻。而软件界面作为用户与软件交互的桥梁&#xff0c;直接决定了用户对软件的第一印象与使用体验。出…...

7、linux基础操作2

一、linux调度 1、crontab [选项] 1.1、了解 定时任务调度:指每隔指定的时间&#xff0c;执行特定的命令或程序。 基本语法&#xff1a;crontab [选项] 常用选项&#xff1a; e&#xff1a; 编辑定时任务l&#xff1a;查询定时任务r&#xff1a;删除当前用户的所有定时任务…...

大数据管理专业想求职数据分析岗,如何提升面试通过率?

从技能到策略&#xff0c;解锁高薪岗位的六大核心逻辑 在数字化浪潮席卷全球的今天&#xff0c;数据分析岗位的竞争愈发激烈。对于大数据管理专业的学生而言&#xff0c;如何从众多求职者中脱颖而出&#xff1f;本文结合行业趋势与实战经验&#xff0c;提炼出提升面试通过率的…...

移动端六大语言速记:第15部分 - 其他方面

移动端六大语言速记:第15部分 - 其他方面 本文将对比Java、Kotlin、Flutter(Dart)、Python、ArkTS和Swift这六种移动端开发语言的其他重要特性,帮助开发者全面了解各语言的独特优势和应用场景。 15.1 语言特有功能 各语言特有功能对比: 语言特有功能描述Java注解(Annotat…...

3.1.3.4 Spring Boot使用使用Listener组件

在Spring Boot中&#xff0c;使用Listener组件可以监听和响应应用中的各种事件。首先&#xff0c;创建自定义事件类CustomEvent&#xff0c;继承自ApplicationEvent。然后&#xff0c;创建事件监听器CustomEventListener&#xff0c;使用EventListener注解标记监听方法。接下来…...

基于关键字定位的自动化PDF合同拆分

需求背景&#xff1a; 问题描述&#xff1a; 我有一份包含多份合同的PDF文件&#xff0c;需要将这些合同分开并进行解析。 传统方法&#xff08;如以固定页数作为分割点&#xff09;不够灵活&#xff0c;无法满足需求。 现有方法的不足&#xff1a; 网上找到的工具大多依赖手动…...

ssh连接远程Host key verification failed.

问题描述 在对已部署的项目进行维护过程中&#xff0c;遇到的一个小问题&#xff0c;记录一下。 SSH连接云服务器ssh xxx云服务器IP地址&#xff0c;提示&#xff1a; The authenticity of host xxxxxx (xx.xxx.123.321) cant be established. ECDSA key fingerprint is SHA…...

Matlab 汽车ABS的bangbang控制和模糊PID控制

1、内容简介 Matlab 197-汽车ABS的bangbang控制和模糊PID控制 可以交流、咨询、答疑 2、内容说明 略 摘要&#xff1a;本文旨在设计一种利用模糊控制理论优化的pid控制器&#xff0c;控制abs系统&#xff0c;达到对滑移率最佳控制范围的要求 &#xff0c;所提出的方案采用级联…...

kotlin的takeIf使用

takeIf用于判断指定对象是否满足条件&#xff0c;满足就返回该对象自身&#xff0c;不满足返回null。因为可以返回对象自身&#xff0c;所以可以用作链式调用&#xff0c;以简化代码&#xff0c;又因takeIf可能返回空&#xff0c;所以常常和let结合使用&#xff0c;示例如下&am…...

MySQL 进阶 - 2 ( 9000 字详解)

一&#xff1a; SQL 优化 1.1 插入数据 1.1.1 批量插入 单条 INSERT 语句执行时&#xff0c;需经历语法解析、事务提交、磁盘 I/O 等多个步骤。批量插入将多条数据合并为一条 SQL&#xff0c;能够减少网络通信和事务开销。 -- 单条插入&#xff08;低效&#xff09; INSERT…...

Devops之GitOps:什么是Gitops,以及它有什么优势

GitOps 定义 GitOps 是一种基于版本控制系统&#xff08;如 Git&#xff09;的运维实践&#xff0c;将 Git 作为基础设施和应用程序的唯一事实来源。通过声明式配置&#xff0c;系统自动同步 Git 仓库中的期望状态到实际运行环境&#xff0c;实现持续交付和自动化运维。其核心…...

VSCode和Fitten Code

提示&#xff1a;本文为学习记录&#xff0c;若有错误&#xff0c;请联系作者。 文章目录 一、离线安装二、在线安装总结 一、离线安装 访问 Open VSX 镜像站 打开 https://open-vsx.org&#xff0c;搜索 Fitten Code 点击“从VSIX安装”&#xff0c;选择下载的VSIX即可。安装…...

在 Visual Studio Code 中安装 Python 环境

在 Visual Studio Code 中安装 Python 环境 1. 安装 Visual Studio Code 首先&#xff0c;下载并安装 Visual Studio Code&#xff08;VS Code&#xff09;&#xff1a; 下载链接&#xff1a;Visual Studio Code 官网安装步骤&#xff1a;按照下载页面的说明进行安装。 2. …...

[问题帖] vscode 重启远程终端

原理 有的时候&#xff0c;在vscode 远程ssh连接到服务器的时候&#xff0c;可能遇到需要重启终端才能生效的配置&#xff0c;比如add group的时候&#xff0c;而此时无论你是关闭vscode终端重启&#xff0c;还是reload窗口都是没用的。 因为不管你本地是否连接了远程的vscode服…...

PostgreSQL技术大讲堂 - 第86讲:数据安全之--data_checksums天使与魔鬼

PostgreSQL技术大讲堂 - 第86讲&#xff0c;主题&#xff1a;数据安全之--data_checksums天使与魔鬼 1、data_checksums特性 2、避开DML规则&#xff0c;嫁接非法数据并合法化 3、避开约束规则&#xff0c;嫁接非法数据到表中 4、避开数据检查&#xff0c;读取坏块中的数据…...

No staged files match any configured task

我在拉取一个新项目的时候&#xff0c;进行 git commit 的时候就出现了这个问题 然后我现在来说一下我出现这个问题的解决思路 我们点击 “显示命令输出” 我们把第二行的错误 subject may not be empty [subject-empty] 复制搜索一下 这是其他人写的 博客&#xff1a;subje…...

Sqlite3 查看db文件

以下是一些 SQLite3 常用命令的整理&#xff0c;涵盖数据库操作、表管理、数据查询等场景&#xff1a; 1. 数据库连接与退出 打开/创建数据库&#xff1a;sqlite3 filename.db # 打开或创建数据库文件退出 SQLite3 命令行&#xff1a;.exit # 退出 .quit …...

【leetcode hot 100 152】乘积最大子数组

错误解法&#xff1a;db[i]表示以i结尾的最大的非空连续&#xff0c;动态规划&#xff1a;dp[i] Math.max(nums[i], nums[i] * dp[i - 1]); class Solution {public int maxProduct(int[] nums) {int n nums.length;int[] dp new int[n]; // db[i]表示以i结尾的最大的非空连…...

微信小程序实时日志记录-接口监控

文章目录 微信小程序如何抓取日志&#xff0c;分析用户异常问题可查看用户具体页面行为操作web体验分析![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/dd20bb72606842128aa1eaf0881196f6.png) 腾讯小程序平台&#xff0c;提供了非常好用的&#xff0c;。 web分析工…...

【C++刷题】二叉树基础OJ题

&#x1f4dd;前言说明&#xff1a; 本专栏主要记录本人的基础算法学习以及刷题记录&#xff0c;使用语言为C。 每道题我会给出LeetCode上的题号&#xff08;如果有题号&#xff09;&#xff0c;题目&#xff0c;以及最后通过的代码。没有题号的题目大多来自牛客网。对于题目的…...

CSS高级技巧

目录 一、精灵图 二、字体图标 三、CSS制作三角形 四、CSS用户界面样式 1、鼠标样式 cursor 2、轮廓线 outline 3、防止拖拽文本域 resize 五、vertical-align 属性 六、溢出的文字省略号显示 1、单行文本溢出显示省略号 2、多行文本溢出显示省略号 七、常见布局技…...

70. 爬楼梯:动态规划

题目来源 70. 爬楼梯 - 力扣&#xff08;LeetCode&#xff09; 题目描述 思路 1.观察每个较少的台阶的方法 2.dp[0,1,2,3,5,8,13]---->dp[i]表示爬上第i阶的方法数 3.观察dp&#xff1a;dp[i]dp[i-1]dp[i-2]; 代码 public int climbStairs(int n) {int[] dp new int…...

使用治疗前MR图像预测脑膜瘤Ki-67的多模态深度学习模型

大家好&#xff0c;我是带我去滑雪&#xff01; 脑膜瘤是一种常见的脑部肿瘤&#xff0c;Ki-67作为肿瘤细胞增殖的标志物&#xff0c;对于评估肿瘤的生物学行为、预后以及治疗方案的制定具有至关重要的作用。然而&#xff0c;传统的Ki-67检测依赖于组织学切片和免疫组化染色等方…...