Transformers for Machine Learning: A Deep Dive阅读笔记

Transformers: Basics and Introduction

SENQUENCE-TO-SENQUENCE

1. Encoder 编码器

这一段介绍的是在自然语言处理任务中,特别是在序列到序列(Seq2Seq)模型中编码器的工作原理,以及如何通过循环神经网络(RNN)处理序列数据。以下是逐个解释每个公式的含义:

在编码器的处理过程中,输入的句子首先被分解成单词,并将这些单词映射成特征向量,作为编码器的输入。设想输入序列由 x 1 , ⋅ ⋅ ⋅ , x T x_1, ···, x_T x1,⋅⋅⋅,xT组成,其中文本序列中的 x t x_t xt代表第 t t t个token。这些输入tokens x 1 , . . . , x T x_1, ..., x_T x1,...,xT通过嵌入映射被转化为向量 x 1 , ⋅ ⋅ ⋅ , x T x_1, ···, x_T x1,⋅⋅⋅,xT。在单向RNN中,任何时刻 t t t的新隐藏状态是基于先前的隐藏状态 h t − 1 h_{t−1} ht1和当前的输入 x t x_t xt生成的。

什么是嵌入映射?
Embedding mappings(嵌入映射)是一种在机器学习和自然语言处理(NLP)领域常用的技术,尤其是在处理类别数据和文本数据时。简单来说,嵌入映射是一种将大量的类别或者符号(如单词、短语、用户ID、商品ID等)转换成一个更小维度且连续的向量空间中的点的技术。这个过程可以捕捉到原始数据中的复杂关系和模式,如语义相似性或者物品的相似性。

嵌入映射的关键点:

  • 降维:嵌入映射通常将高维的稀疏表示(如独热编码)转换为低维的稠密向量。例如,将数以万计的单词映射到一个几百维的空间中。

  • 语义捕捉:在自然语言处理中,单词或短语的嵌入向量能够捕捉它们的语义信息。具有相似意义的词语在嵌入空间中会彼此靠近。

  • 通用性:嵌入映射不仅限于文本数据,它可以用于任何类型的类别数据,包括用户的点击历史、商品ID、图像的特征表示等。

应用:

  • 文本分析:词嵌入(如Word2Vec、GloVe)是嵌入映射的一个经典例子,用于将单词转换为向量,这些向量能够在许多文本相关的任务中使用,如情感分析、文本分类、机器翻译等。

  • 推荐系统:在推荐系统中,通过将用户和物品映射到同一嵌入空间中,可以计算它们之间的相似度,以推荐用户可能感兴趣的物品。

  • 图嵌入:将图结构数据(如社交网络、知识图谱)中的节点映射到向量空间,以便于使用机器学习算法处理图数据。

公式 2.1

h t = f ( h t − 1 , x t ) h_t = f(h_{t−1}, x_t) ht=f(ht1,xt)
这个公式表明,在给定时间步 t t t,新的隐藏状态 h t h_t ht 是由一个函数 f f f 通过前一时刻的隐藏状态 h t − 1 h_{t−1} ht1 和当前的输入 x t x_t xt 生成的。函数 f f f 通常是非线性的,例如 tanh 或者 ReLU 函数。在RNN中, h t h_t ht 保存了到目前为止序列的信息,因此它可以用来进行预测或作为序列的一个编码。

在这里插入图片描述

梯度消失(Vanishing Gradient)

梯度消失是深度学习中常见的问题,特别是在训练深层神经网络时。它发生在梯度反向传播过程中,当梯度传递通过多个层时,梯度会越来越小,直到对网络权重的更新变得微不足道。这会导致训练过程缓慢,特别是网络中较早的层几乎不更新,因此很难训练深层网络。

梯度消失的原因包括:

  • 使用特定的激活函数(如Sigmoid或Tanh),这些函数的梯度在输入值较大或较小时非常接近于0。
  • 不恰当的权重初始化方法,可能导致网络中的激活值过小。
  • 网络结构过深。

梯度爆炸(Exploding Gradient)

梯度爆炸与梯度消失相对,是另一种在训练深层神经网络时可能遇到的问题。在梯度爆炸的情况下,梯度在反向传播过程中随层的增加而指数级增长,导致权重更新过大。这可能会使网络权重迅速增长到非常大的数值,导致数值计算上的溢出,从而使得模型无法收敛,表现为训练过程中损失函数突然变为NaN(非数字)。

梯度爆炸的原因包括:

  • 不合适的权重初始化。
  • 使用某些激活函数时。
  • 网络结构过深。

解决方法

梯度消失:
  • 使用ReLU及其变体(如Leaky ReLU、Parametric ReLU)作为激活函数,因为它们在正区间内的梯度为常数。
  • 使用残差网络(ResNet)等架构,它们通过引入跳跃连接来缓解梯度消失的问题。
  • 适当的权重初始化策略,如He初始化或Xavier初始化。
  • 使用批量归一化(Batch Normalization)来稳定激活函数的输出。
梯度爆炸:
  • 梯度裁剪(Gradient Clipping),即在反向传播过程中对梯度的大小进行限制。
  • 权重正则化,如L1或L2正则化,可以避免权重变得过大。
  • 使用适当的权重初始化方法。
  • 使用更稳定的网络架构或训练策略。
公式 2.2

h t = t a n h ( W ( h h ) h t − 1 + W ( h x ) x t ) h_t = tanh(W^{(hh)}h_{t−1} + W^{(hx)}x_t) ht=tanh(W(hh)ht1+W(hx)xt)

这个公式是函数 f f f 的具体实现。它表明,时间步 t t t 的隐藏状态 h t h_t ht 是通过对前一隐藏状态 h t − 1 h_{t−1} ht1 应用权重 W ( h h ) W^{(hh)} W(hh),并将其与当前输入 x t x_t xt 乘以权重 W ( h x ) W^{(hx)} W(hx) 后的和,然后通过激活函数 tanh 获得的。tanh 函数能够将任意值规范化到 (-1, 1) 的范围内。

公式2.2是循环神经网络(RNN)的核心,用于计算在任意时刻 t t t的新隐藏状态 h t h_t ht

组件解析

  • h t h_t ht: 时刻 t t t的隐藏状态,是当前时刻模型的内部状态,捕捉了截至当前输入的序列历史信息。
  • h t − 1 h_{t-1} ht1: 时刻 t − 1 t-1 t1的隐藏状态,即前一时刻的内部状态。
  • x t x_t xt: 时刻 t t t的输入向量,通常是通过嵌入层将输入的token转换得到的稠密向量。
  • W ( h h ) W^{(hh)} W(hh): 隐藏到隐藏的权重矩阵,用于调整前一时刻隐藏状态 h t − 1 h_{t-1} ht1的贡献。
  • W ( h x ) W^{(hx)} W(hx): 输入到隐藏的权重矩阵,用于调整当前输入 x t x_t xt的贡献。
  • t a n h tanh tanh: 双曲正切激活函数,用于引入非线性,使得网络能够学习复杂的数据模式。

运算步骤

  1. 线性组合:首先,计算 h t − 1 h_{t-1} ht1 x t x_t xt的线性组合。这是通过将前一时刻的隐藏状态 h t − 1 h_{t-1} ht1与权重矩阵 W ( h h ) W^{(hh)} W(hh)相乘,并将当前输入 x t x_t xt与权重矩阵 W ( h x ) W^{(hx)} W(hx)相乘,然后将这两个乘积相加得到。

  2. 非线性激活:接着,将上述线性组合的结果通过激活函数 t a n h tanh tanh。这个步骤的目的是引入非线性,使得RNN能够捕捉输入数据中的复杂模式。 t a n h tanh tanh函数的输出范围是 [ − 1 , 1 ] [-1, 1] [1,1],有助于控制隐藏状态的值,使其在训练过程中保持稳定。

目的和作用

  • 信息融合:通过公式2.2,RNN在每个时刻 t t t都能够融合当前输入 x t x_t xt和过去的信息(通过 h t − 1 h_{t-1} ht1体现)来更新当前的隐藏状态 h t h_t ht。这样, h t h_t ht就编码了到当前时刻为止序列的全部历史信息。

  • 序列处理能力:这种依次处理并更新隐藏状态的机制,使得RNN特别适合处理序列数据,如文本、时间序列数据等,因为它可以在每个时间点上考虑之前的上下文信息。

简而言之,公式2.2描述了RNN如何在每一时间步更新其隐藏状态,这是RNN能够处理序列数据并捕捉时间序列中的依赖关系的关键。

公式2.2Python模拟

通过使用Python进行的计算,我们得到了每个时间步的隐藏状态,假设输入向量 x t x_t xt 是二维的,且使用了特定的权重矩阵
W ( h x ) W^{(hx)} W(hx) W ( h h ) W^{(hh)} W(hh),以及初始隐藏状态。隐藏状态的计算结果如下:

  • 在时间步1,隐藏状态为 ≈ [ 0.604 , 0.664 ] T \approx [0.604, 0.664]^T [0.604,0.664]T
  • 在时间步2,隐藏状态为 ≈ [ 0.924 , 0.936 ] T \approx [0.924, 0.936]^T [0.924,0.936]T
  • 在时间步3,隐藏状态为 ≈ [ 0.977 , 0.977 ] T \approx [0.977, 0.977]^T [0.977,0.977]T

这些结果展示了随着时间步的推进,如何利用前一时间步的隐藏状态和当前的输入来更新当前的隐藏状态。这里使用的激活函数是
t a n h tanh tanh,它帮助引入非线性,并且可以确保隐藏状态的值保持在合理的范围内(即 -1 到 1
之间)。每个时间步的隐藏状态都依赖于前一个隐藏状态和当前的输入,这是通过矩阵 W ( h h ) W^{(hh)} W(hh) W ( h x ) W^{(hx)} W(hx)
的加权和,再通过 tanh 激活函数来实现的。

import numpy as np
# 定义输入向量(假设每个xt是二维向量)
x = np.array([[1.0, 2.0], [1.5, 2.5], [2.0, 3.0]])

# 定义权重矩阵(考虑到xt是二维的,这里的W_hx需要是2x2,W_hh也是2x2)
W_hx = np.array([[0.5, 0.1], [0.2, 0.3]])
W_hh = np.array([[0.8, 0.2], [0.2, 0.8]])

# 定义初始隐藏状态(考虑到h是二维的)
h_previous = np.array([[0.0], [0.0]])

# 用于存储每一步的隐藏状态
hidden_states = []

# 对每个时间步进行操作
for t in range(3):  # 我们有3个时间步
    # 计算当前时间步的隐藏状态
    h_current = np.tanh(np.dot(W_hh, h_previous) + np.dot(W_hx, x[t].reshape(-1, 1)))
    # 更新前一个隐藏状态为当前的隐藏状态
    h_previous = h_current
    # 存储当前的隐藏状态
    hidden_states.append(h_current)

hidden_states

公式 2.3

c = m ( h 1 , ⋅ ⋅ ⋅ , h T ) c = m(h_1, ··· , h_T ) c=m(h1,⋅⋅⋅,hT)

在这里,c 表示上下文变量,它是整个输入序列经过编码后的表示。m 是映射函数,它的目的是从所有隐藏状态中提取出有用的信息并生成上下文向量 c上下文向量 c 被用作解码器的初始状态,以便解码器能够产生输出序列。

公式 2.4

c = m ( h 1 , ⋅ ⋅ ⋅ , h T ) = h T c = m(h_1, · · · , h_T ) = h_T c=m(h1,⋅⋅⋅,hT)=hT
这个公式指出,在最简单的情况下,映射函数 m 可以直接将上下文变量 c 映射为输入序列中最后一个隐藏状态 h_T这是因为在单向RNN中,最后一个隐藏状态理论上包含了之前所有输入的信息。

最后一段提到的是,为了能够从序列的两个方向捕获信息,可以使用双向RNN。在双向RNN中,每个时间步 t t t 的隐藏状态不仅依赖于前一个时刻的状态 h t − 1 h_{t−1} ht1,而且还依赖于后一个时刻的状态 h t + 1 h_{t+1} ht+1。这意味着,网络同时考虑了之前和之后的信息来生成每个时间步的隐藏状态。这在处理例如语言中的上下文信息时非常有用,因为通常一个词的含义不仅依赖于它之前的词,也依赖于它之后的词。

Decoder 解码器

解码器接收编码器的输出,即上下文变量 c c c,以及给定的输出序列 y 1 , . . . , y T ′ y_1, ..., y_T' y1,...,yT,进而生成解码后的输出。在Sutskever等人的研究中,编码器的上下文变量会在模型中转为隐藏层,并用其初始化解码器。而在Cho等人的研究中,上下文变量在每个步长的时间周期内都传递给解码器。

  • Sutskever等人的方法:在这种方法中,上下文变量 c c c 被直接用来初始化解码器的隐藏状态。这意味着解码过程的开始会考虑到编码阶段捕获的全部输入序列信息。
  • Cho等人的方法:与Sutskever的方法不同,Cho等人提出在每个解码步骤都使用上下文变量 c c c,而不仅仅是在初始化解码器时使用。这意味着在生成每一个输出token时,都会重新考虑输入序列的上下文信息。
  • 在每个时间点 t ′ t' t,解码器基于当前的隐藏状态 s t ′ s_{t'} st、先前的输出 y t ′ − 1 y_{t'-1} yt1 和上下文变量 c c c 来生成新的输出。

与编码器相似,解码器在任意时间点 t ′ t' t的隐藏状态可以由以下公式给出:

公式2.5

s t ′ = g ( s t ′ − 1 , y t ′ − 1 , c ) (2.5) s_{t'} = g(s_{t'−1}, y_{t'−1}, c) \tag{2.5} st=g(st1,yt1,c)(2.5)

这个公式描述了如何计算解码器在时间点 t ′ t' t 的隐藏状态 s t ′ s_{t'} st。函数 g g g 通常是一个非线性函数,如
t a n h tanh tanh,而 s t ′ − 1 s_{t'-1} st1 是先前的隐藏状态, y t ′ − 1 y_{t'-1} yt1 是上一个时间点生成的输出token,
c c c是上下文变量。这个步骤确保了解码的过程考虑到了之前所有已生成的输出以及整个输入序列的信息。

解码器的隐藏状态被传递到输出层,而在时间点 t ′ t' t,下一个token的条件分布则由以下公式定义:

公式2.6

P ( y t ′ ∣ y t ′ − 1 , ⋅ ⋅ ⋅ , y 1 , c ) = s o f t m a x ( s t ′ − 1 , y t ′ − 1 , c ) (2.6) P(y_{t'}|y_{t'−1}, · · · , y_1, c) = softmax(s_{t'−1}, y_{t'−1}, c) \tag{2.6} P(ytyt1,⋅⋅⋅,y1,c)=softmax(st1,yt1,c)(2.6)

公式2.6是,在序列到序列(Seq2Seq)模型的解码器部分定义了在给定上下文变量 c c c,以及之前所有生成的输出序列 y 1 , … , y t ′ − 1 y_1, \dots, y_{t'-1} y1,,yt1的条件下,生成下一个输出 y t ′ y_{t'} yt的条件概率分布。这里,我们逐部分详细解释这个公式:

条件概率分布 P ( y t ′ ∣ y t ′ − 1 , … , y 1 , c ) P(y_{t'}|y_{t'−1}, \dots, y_1, c) P(ytyt1,,y1,c)

  • 这表示在已知之前所有输出 y 1 , … , y t ′ − 1 y_1, \dots, y_{t'-1} y1,,yt1和上下文变量 c c c的情况下,生成下一个输出 y t ′ y_{t'} yt的概率分布。上下文变量 c c c通常来自编码器,它编码了输入序列的信息。

softmax 函数

  • s o f t m a x softmax softmax 是一个非线性函数,用于将一个向量转换为一个概率分布。每个元素的值在 0 0 0 1 1 1之间,且所有元素值的和为 1 1 1。在这个公式中,它作用于解码器的某种函数 g g g的输出,该函数考虑了解码器在前一时间步 t ′ − 1 t'−1 t1的隐藏状态 s t ′ − 1 s_{t'−1} st1,前一时间步的输出 y t ′ − 1 y_{t'−1} yt1,以及从编码器接收的上下文变量 c c c

函数 g g g

  • 函数 g g g是一个神经网络,它计算当前时间步 t ′ t' t的解码器隐藏状态。它的输入包括解码器在前一时间步 t ′ − 1 t'−1 t1的隐藏状态 s t ′ − 1 s_{t'−1} st1,前一时间步的输出 y t ′ − 1 y_{t'−1} yt1,以及上下文变量 c c c。这反映了解码器如何综合考虑之前的输出、隐藏状态和编码的输入信息来生成当前步的状态。

输出层和下一个token的生成

  • 公式的左侧 P ( y t ′ ∣ y t ′ − 1 , … , y 1 , c ) P(y_{t'}|y_{t'−1}, \dots, y_1, c) P(ytyt1,,y1,c)通过右侧的(softmax)函数被计算出来,这个概率分布用于选择下一个最可能的输出(y_{t’})。在实际应用中,这可能涉及到从概率分布中采样或选择概率最高的输出作为(y_{t’})。

总结来说,公式2.6描述了如何在解码器中利用前一时间步的信息(包括隐藏状态和输出)和编码器提供的上下文信息,来预测序列中下一个输出token的概率分布。通过反复应用这个过程,解码器可以生成整个输出序列。

Softmax函数解释及代码

Softmax函数是一种在机器学习和深度学习中广泛使用的函数,特别是在分类任务中。它的作用是将一个实数向量转换成一个概率分布。每个元素的值代表了该类别的概率,且所有元素的和为1。这使得Softmax函数非常适用于多类别的概率输出,如在神经网络的最后一层,用于将输出解释为概率分布。

数学定义

给定一个实数向量 Z = [ z 1 , z 2 , . . . , z K ] Z = [z_1, z_2, ..., z_K] Z=[z1,z2,...,zK],其中 K K K是类别的总数,Softmax函数定义为:

Softmax ( z i ) = e z i ∑ j = 1 K e z j \text{Softmax}(z_i) = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}} Softmax(zi)=j=1Kezjezi

对于向量 Z Z Z中的每一个元素 z i z_i zi,Softmax函数首先计算 e z i e^{z_i} ezi,即 z i z_i zi的指数。然后,将这个指数除以所有元素指数的总和。这样,每个元素被转换成一个0到1之间的值,且所有这些值的和为1。

直观理解

  • 将分数转换为概率:在分类问题中,模型的原始输出(通常称为“logits”)被视为每个类别的分数。Softmax函数将这些分数转换为概率,使得分数较高的类别被赋予较高的概率。

  • 指数函数的作用:通过使用指数函数,Softmax确保了所有的输出都是正数。同时,它放大了分数之间的差异。即使是较小的分数差异,在经过Softmax处理后也可能变得更加显著。

应用

  • 多类分类:在多类分类问题中,Softmax常用作神经网络的输出层,提供每个类别的预测概率。
  • 交叉熵损失:与Softmax函数结合使用的另一个重要概念是交叉熵损失(Cross-Entropy Loss),它通常用于训练分类模型,通过最小化预测概率分布与真实分布之间的差异来优化模型参数。

总的来说,Softmax函数是将模型的原始输出转换为明确的概率分布的一个有效工具,特别适用于处理分类任务中的多类别预测。

例子

假设一个分类模型的原始输出为 Z = [ 2 , 1 , 0.1 ] Z = [2, 1, 0.1] Z=[2,1,0.1],应用Softmax函数后,我们计算:

  • e 2 = 7.39 , e 1 = 2.72 , e 0.1 = 1.11 e^{2} = 7.39, e^{1} = 2.72, e^{0.1} = 1.11 e2=7.39,e1=2.72,e0.1=1.11
  • 概率分布为: 7.39 7.39 + 2.72 + 1.11 , 2.72 7.39 + 2.72 + 1.11 , 1.11 7.39 + 2.72 + 1.11 \frac{7.39}{7.39 + 2.72 + 1.11}, \frac{2.72}{7.39 + 2.72 + 1.11}, \frac{1.11}{7.39 + 2.72 + 1.11} 7.39+2.72+1.117.39,7.39+2.72+1.112.72,7.39+2.72+1.111.11
  • 即: [ 0.72 , 0.27 , 0.01 ] [0.72, 0.27, 0.01] [0.72,0.27,0.01]

这表明第一个类别的概率最高,模型预测输入属于第一个类别的可能性为72%。

import torch
import torch.nn.functional as F
# 定义2D张量,表示3个样本的类别分数
logits_2d = torch.tensor([[1.0, 2.0, 3.0, 4.0],  
                          [2.0, 2.0, 2.0, 2.0],  
                          [1.5, 2.5, 3.5, 4.5]])

# 应用softmax函数,dim=1表示沿着每个样本的类别分数进行概率分布的计算
probabilities_2d = F.softmax(logits_2d, dim=1)

probabilities_2d

这里,我们将详细展示如何将每一行的类别分数转换为概率分布。

示例

假设我们的2D张量(或在这个上下文中的矩阵)是:

logits = [ 1.0 2.0 3.0 4.0 2.0 2.0 2.0 2.0 1.5 2.5 3.5 4.5 ] \text{logits} = \begin{bmatrix} 1.0 & 2.0 & 3.0 & 4.0 \\ 2.0 & 2.0 & 2.0 & 2.0 \\ 1.5 & 2.5 & 3.5 & 4.5 \\ \end{bmatrix} logits= 1.02.01.52.02.02.53.02.03.54.02.04.5

我们要对这个张量应用softmax函数,特别是指定dim=1进行计算。

步骤

  1. 对每一行应用指数函数

    对于第一行 [ 1.0 , 2.0 , 3.0 , 4.0 ] [1.0, 2.0, 3.0, 4.0] [1.0,2.0,3.0,4.0],应用指数函数得到 [ e 1.0 , e 2.0 , e 3.0 , e 4.0 ] [e^{1.0}, e^{2.0}, e^{3.0}, e^{4.0}] [e1.0,e2.0,e3.0,e4.0]
    类似地,对第二行和第三行进行相同的操作。

  2. 计算每一行的指数和

    计算每一行指数结果的总和。这将作为每一行概率分布的分母。

  3. 计算概率

    对于每一行,将每个元素的指数值除以该行的指数和,得到该元素的概率。

概率分布计算

以第一行为例:

  • 原始分数: [ 1.0 , 2.0 , 3.0 , 4.0 ] [1.0, 2.0, 3.0, 4.0] [1.0,2.0,3.0,4.0]
  • 应用指数: [ e 1.0 , e 2.0 , e 3.0 , e 4.0 ] [e^{1.0}, e^{2.0}, e^{3.0}, e^{4.0}] [e1.0,e2.0,e3.0,e4.0]
  • 指数和: e 1.0 + e 2.0 + e 3.0 + e 4.0 e^{1.0} + e^{2.0} + e^{3.0} + e^{4.0} e1.0+e2.0+e3.0+e4.0
  • 概率计算:每个元素的指数除以指数和

最终,每一行将转换为一个概率分布,其中每个元素的值介于0到1之间,且每一行的元素值总和为1。

dim=1的意义

在PyTorch中,dim=1表示我们沿着张量的第二维度(即列)进行操作,但是我们实际上是在每一行内部进行操作,因为我们的目标是将行内的类别分数转换为行内的概率分布。这就是为什么在处理分类任务中的批量数据时,通常会指定dim=1,以确保我们能够为每个样本独立计算概率分布。

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值