Transformer 是如何工作的:600 行 Python 代码实现两个(文本分类+文本生成)

Transformer算法组件详解 Transformer是谷歌在2017年的论文《Attention Is All You Need》中提出的,用于NLP的各项任务,现在是谷歌云TPU推荐的参考模型。网上有关Transformer原理的介绍很多,在本文中我们将尽量模型简化,让普通读者也能轻松理解。Transformer由于可并行、效果好等特点,如今已经成为机器翻译、特征抽取等任务的基础模块,目前ChatGPT特征抽取的模块用的就是Transformer,这对于后面理解ChatGPT的原理做了好的铺垫。 阅读详情

译者水平有限,不免存在遗漏或错误之处。如有疑问,敬请查阅原文。

以下是译文。



摘要

Transformer 是一类非常令人着迷的机器学习架构(a family of machine learning architectures)。 之前已经有一些不错的介绍文章(例如 [1, 2]),但过去几年 transformer 变得简单了很多, 因此要解释清楚现代架构(modern architectures)是如何工作的,比以前容易多了。

本文试图丢掉历史包袱,开门见山地解释现代 transformer 的工作原理。

神经网络和反向传播(neural networks and backpropagation)的基本知识有助于更好地理解本文,

  • 这个讲座 介绍了神经网络的基础知识;
  • 这个讲座 介绍了神经网络如何应用于现代深度学习系统。

另外,理解本文程序需要一点 Pytorch 基础, 但没有关系也不大。

1 self-attention(自注意力)模型

self-attention 运算是所有 transformer 架构的基本运算。

1.0 Attention(注意力):名字由来

从最简形式上来说,神经网络是一系列对输入进行加权计算,得到一个输出的过程。

具体来说,比如给定一个向量 [1,2,3,4,5] 作为输入,权重矩阵可能是 [0, 0, 0, 0.5, 0.5], 也就是说最终的 output 实际上只与 input 中的最后两个元素有关系 —— 换句话说, 这一层神经网络只关注最后两个元素(注意力在最后两个元素上), 其他元素是什么值对结果没有影响 —— 这就是 attention 这一名字的由来。

注意力模型大大降低了神经网络的计算量:经典神经网络是全连接的,而上面的例子中, 这一层神经网络不需要全连接了,每个输出连接到最后两个输入就行了,也就是从 1x5 维降低到了 1x2 维。

图像处理中的卷积神经网络(CNN)也是类似原理:只用一小块图像计算下一层的输出,而不是用整帧图像。

1.1 输入输出:vector-to-vector 运算

Self-attention 是一个 sequence-to-sequence 运算: 输入一个向量序列(a sequence of vectors),输出另一个向量序列。

我们用 𝐱1,𝐱2,…,𝐱t 表示输入向量,用 𝐲1,𝐲2,…,𝐲t 表示相应的输出向量,这些向量都是 k 维的。 要计算输出向量 𝐲i ,self-attention 只需对所有输入向量做加权平均(weighted average),

𝐲i=∑jwij𝐱j

在传统神经网络中,权重都是(常量)参数, 但这里的权重并不是:wij 是根据 𝐱i 和 𝐱j 计算出来的。 计算它有很多种方式(算法),接下来看一种最简单的。

1.2 权重矩阵计算和归一化

计算权重矩阵的 最简单函数就是点积(dot product):

w′ij=𝐱iT𝐱j

注意到权重矩阵的计算跟它所在的位置 (i,j) 直接相关,也就是说,每个位置 (i,j) 对应的权重矩阵都不一样。

点积得到的结果取值范围是正负无穷,为了使累加和(表示概率)等于 100%, 需要对它们做归一化:用 pytorch 术语来说就是 softmax,

wij=exp w′ij∑jexp w′ij

这会将每个权重矩阵归一化到 [0,1],并且累加和等于 1。

1.3 直观展示与小结

以上就是关于 self-attention 的基本运算。总结起来就是两点:

  1. vector-to-vector 运算:self-attention 是对 input vector 做矩阵运算,得到一个加权结果作为 output vector;
  2. 加权矩阵计算:权重矩阵不是常量,而是跟它所在的位置 (i,j) 直接相关,根据对应位置的 input vector 计算。

用图来表示如下:

self-attention 基本运算

  • output vector 中的每个元素 yj 都是对 input vector 中所有元素的加权和;
  • 对于 yj,加权矩阵由 input 元素 xj 与每个 input 元素计算得到;

要构建一个完整的 transformer 还需要一点其他东西,但最核心的运算就是以上这两个了。 更重要的是,

  • 这是整个架构中,唯一在 input & output vector 之间 所做的运算;
  • Transformer 架构中的其他运算都是单纯对 input vector 做运算。

2 self-attention 为什么有效?以电影推荐为例

上面看到 self-attention 模型非常简单,本质上就一个加权平均公式,那为什么这个加权平均机制的效果这么好呢? 为了直观解释这个问题,我们看个具体例子 —— 电影推荐 —— 假设你经营着一家电影租赁公司, 想向用户推荐他们可能会喜欢的电影。接下来看看基于传统方式和 transformer 方式分别是怎么做的。

2.1 传统推荐系统:特性向量点积用户偏好

步骤很简单:

  1. 人工设计一些电影特征,比如浪漫指数、动作指数,
  2. 人工设计一些用户特征,例如他们喜欢浪漫电影或动作片的可能性;

有了这两个维度的数据(特征向量)之后,对二者做点积(dot product), 得到的就是电影属性与用户喜欢程度之间的匹配程度,用得分表示,

电影推荐:电影特征向量(浪漫、动作、喜剧)与用户特性向量(喜欢浪漫、动作、喜剧的程度)做点积运算

关于计算结果(得分):

  • 如果特征的符号相同,例如“浪漫电影 && 用户喜欢浪漫电影”, 或者“不是浪漫电影 && 用户不喜欢浪漫电影”,得到的点积就是正数;反之就是负数;
  • 特征值的大小决定该特征对总分的贡献大小: 一部电影可能有点浪漫,但不是很明显,或者用户可能只是不喜欢浪漫,但也没到讨厌的程度。

这种推荐模型的好处是简单直接,很容易上手;缺点是规模大了很难搞, 因为对几百万部电影打标的成本非常高,精确标记用户喜欢或不喜欢什么也几乎是不可能的。

2.2 基于 self-attention 的推荐系统

接下来看基于 self-attention 的推荐系统是怎么设计的。

2.2.1 电影特征和用户特征作为模型参数,匹配已知的用户偏好

也是两步:

  1. 电影特征和用户特征不再直接做点积运算,而是作为模型的参数(parameters of the model);
  2. 收集少量的用户偏好作为目标,然后通过优化用户特征和电影特征(模型参数), 使二者的点积匹配已知的用户喜好。

这就是 self-attention 的基本原理。注意, 尽管我们没有告诉模型某个特征意味着什么(表示什么), 但实践证明,训练之后的特征确实反映了关于电影内容的合理语义。

从一个基本的 matrix factorization 模型学习到的前两个特征。 模型只用到了“哪些用户喜欢哪些电影”信息,而没有用到任何电影内容信息。 横轴:从流俗到高雅;纵轴:从小众到主流。信息来自 [4]。

这些已经足够说明 dot product 是如何表示对象和它们的关系的。 更多关于推荐系统的内容,可移步 mlvu.github.io/lecture12。

2.2.2 嵌入层:对输入进行处理

假设我们有一串单词作为输入,原理上只要将其作为 input vector 送到 self-attention 模型。 但实际上我们需要对这个 input vector 做一下预处理(下一节会解释为什么),生成一个中间表示, 这就是序列建模中的嵌入层。 具体来说,会为每个单词 t 分配一个嵌入向量(embedding vector) 𝐯t(我们后面将学习到这个值)。

嵌入层将 input vector:

the,cat,walks,on,the,street

转换为 embedding vector(注意:每个单词的维度从 1x1 变成了 1xN):

𝐯the,𝐯cat,𝐯walks,𝐯on,𝐯the,𝐯street

将这个 embedding vectors 输入 self-attention 层,得到的就是 output vector:

𝐲the,𝐲cat,𝐲walks,𝐲on,𝐲the,𝐲street

其中 𝐲cat 是所有嵌入向量的加权和(weighted sum),由它们与 𝐯cat 的(归一化)点积加权。

2.2.3 直观解释

由于我们正在学习(learning) 𝐯t 的值是什么,两个词的“相关”程度完全由任务决定。

  • 在大多数情况下,定冠词 “the” 与句子中其他单词表示什么意思(the interpretation of the other words)关系不大; 因此我们最终得到的嵌入层 𝐯the 与所有其他单词的点积可能很小或为负数;
  • 另一方面,要解释这句话中 “walks” 的意思,弄清楚谁在走路是非常有用的。这很可能由名词表达, 因此对于像 cat 这样的名词和像 walks 这样的动词,我们可能最终学习到的 𝐯cat and 𝐯walks 点积是个较大的正数。

这就是 self-attention 背后的基本直觉:

  1. 点积表示输入序列中两个向量的相关程度,“相关”由学习任务(learning task)定义,
  2. 输出向量是整个输入序列的加权和,权重由这些点积决定。
2.2.4 self-attention 特殊属性

在继续之前,有些特殊属性需要提及一下,因为不同于在一般的 sequence-to-sequence 运算:

  1. 到目前为止,我们的 self-attention 模型还没有参数( 虽然下文中,我们还是会为 self-attention 添加几个参数)。

    换句话说,基本的 self-attention 实际上做什么完全取决于生成输入序列的上游机制。 例如嵌入层这种机制会驱动着 self-attention 学习基于点积的表示。

  2. self-attention 将输入当做一个集合(set)而不是序列(sequence)。

    如果我们对输入序列进行重排(permute),输出序列除了也跟着重排,其他方面将完全相同, 也就是说 self-attention 是排列等变的(permutation equivariant)。 后面会看到,构建完整的 transformer 时,我们还是会引入一些东西来保持输入的顺序信息, 但要明白 self-attention 本身是不关心输入的顺序属性的(sequential nature)。

3. 实现一个基本的 self-attention

What I cannot create, I do not understand. —— Feynman.

纸上得来终觉浅,绝知此事要躬行 —— 陆游《冬夜读书示子聿》

接下来我们基于 pytorch 实现前面介绍的最基础 self-attention 模型。

3.1 输入的表示:tensor(多维矩阵)

我们面临的第一个问题是如何用矩阵乘法表示 self-attention: 按照定义,直接遍历所有 input vectors 来计算 weight 和 output 就行, 但显然这种方式效率太低;改进的方式就是用 pytorch 的 tensor 来表示, 这是一个多维矩阵数据结构:

A torch.Tensor is a multi-dimensional matrix containing elements of a single data type.

pytorch.org/docs/stable/tensors.html

  • 输入 𝐗 由 t 个 k-维 vector 组成的序列,
  • 引入一个 mini-batch dimension b,

就得到了一个三维矩阵 (b,t,k),这就是一个 tensor。

3.2 计算权重矩阵:输入矩阵 * 转置矩阵

接下来计算加权矩阵,它表示的是 input vector 之间的相关性, 因此用输入矩阵 𝐗 乘以它的转置矩阵(transpose),用 pytorch 库来计算非常方便。

import torch
import torch.nn.functional as F

# 假设我们有一些 tensor x 作为输入,它是 (b, t, k) 维矩阵
x = ...

# torch.bmm() 是批量矩阵乘法(batched matrix multiplication)函数,对一批矩阵执行乘法操作
raw_weights = torch.bmm(x, x.transpose(1, 2))

然后对权重矩阵进行正值化和归一化,以使得一个 row 内所有权重加起来为 1,

weights = F.softmax(raw_weights, dim=2)

3.3 计算输出

有了权重矩阵,计算输出就非常简单了:只需要将输入 𝐗 和权重矩阵相乘即可,一行代码搞定:

y = torch.bmm(weights, x)

输出矩阵 𝐘 就是 size (b, t, k) 的 tensor,每一行都是对 𝐗 的行的加权。

这就是 最基础的 self-attention 模型的实现: 两次矩阵乘法和一次归一化(softmax)。

3.4 现代 transformer 对 self-attention 的扩展

现代 transformer 中实际使用的 self-attention 依赖于三个额外技巧。

3.4.1 引入控制参数(for queries, keys and values)

对于位置 i 处的 input vector 𝐱i,它在 self-attention 中会被使用三次, 根据角色的不同分别称为 queries、keys、values(查询、键和值,后面再解释这些名称的来源),

  1. query:与其他所有 input vector 联合计算 i 位置的 output vector 𝐲i 所需的权重;
  2. key:与 query 类似,与其他所有 input vector 联合计算 j 位置的 output vector 𝐲j 所需的权重,这里 j≠i;
  3. value:在计算每个 output vector 时,作为输入值参与加权求和。

在我们目前的基本 self-attention 中,每个 input vector 必须承担所有三个角色。 换句话说,对原始 input vector 应用线性变换,我们就能够为每个角色衍生(derive)出一个新向量,这可以简化 self-attention。 具体来说,引入三个 k×k 权重矩阵 𝐖q, 𝐖k, 𝐖v(来自 query/key/value 首字母) 对每个输入 xi 计算三个线性变换,

𝐪i=𝐖q𝐱i𝐤i=𝐖k𝐱i𝐯i=𝐖v𝐱i

那么 (i,j) 位置处的权重矩阵就可以表示为:

w′ij=𝐪iT𝐤j

做归一化处理,

wij=softmax(w′ij)

最后,output vector 中位置 j 处的值为:

𝐲i=∑jwij𝐯j

这就给 self-attention layer 引入了几个可控制的参数(controllable parameters, 𝐖q, 𝐖k, 𝐖v), 对同一份输入应用不同的线性变换,就可以得到不同角色所需的值,如下图所示,

self-attention key/query/value transformation 的直观解释

3.4.2 对点积做缩放处理(scaling the dot product)

softmax 函数对非常大的输入值敏感。这些 input 会梯度消失,学习变慢甚至完全停止。 由于点积的平均值随着嵌入维度 k 的增加而增大,因此点积送到 softmax 之前进行缩放有助于缓解这个问题。

原来执行 softmax 之前的权重矩阵:

w′ij=𝐪iT𝐤j

现在:

w′ij=𝐪iT𝐤jk−−√

Why k−−√? Imagine a vector in ℝk with values all c. Its Euclidean length is k−−√c. Therefore, we are dividing out the amount by which the increase in dimension increases the length of the average vectors

3.4.3 引入 multi-head attention

最后,需要考虑到,同一个单词随着相邻单词们的不同表示的意思也可能不同。例如下面这个句子:

mary,gave,roses,to,susan

我们看到 “gave” 这个词与句子的不同部分有不同的关系:

  • “mary” 表示谁在 “gave”,
  • “roses” 表示 “gave” 的是什么,
  • “susan” 表示接受者是谁。
需求:输出中嵌入更多信息

在我们的基本 self-attention 中,所有这些信息是混合在一起的: 输入 𝐱mary 和 𝐱susan 可以不同程度地影响输出 𝐲gave ,这取决于它们与 𝐱gave 的点积。

但是,如果我们想以其他方式影响输出,这种模型就不行了。 例如,如果 “roses” 的给予方和接受方信息都出现在 𝐲gave ,但位于不同部分。 也就是说,基本的 self-attention 欠缺了很多灵活性。

This leaves aside how we figure out who gave the roses. We can do that based on prior knowledge about Mary and Susan, encoded in the embeddings. We can also look at the order of the words, but we’ll look at how to achieve that later.

解决方式:引入多个 self-attention(multi-head)

要实现这个目的,就需要让我们的模型有更强的辨识力,一种做法就是 组合多个 self-attention(用 r 索引), 每个对应不同的 query/key/value 参数矩阵 𝐖rq, 𝐖rk,𝐖rv, 这些就称为 attention heads(注意力头)。

对于 input 𝐱i,每个 attention head 产生不同的 output vector 𝐲ri(一部分输出)。 最后再将这些部分输出连接起来,通过线性变换来降维回 k。

提升 multi-head self-attention 效率:query/key/value 降维

理解 multi-head self-attention 最简单的方法是把它看作多个并行的 self-attention 机制, 每个都有自己的键、值和查询转换。

Multi-head self-attention 的缺点是慢,对于 R 头,慢 R 倍。 不过有办法优化:我们可以实现这样的 multi-head self-attention,它既能利用多个 self-attention 提升辨识力, 又与 single-head self-attention 基本一样快。要实现这个目的,每个 head 需要对 query/key/value 降维。 如果输入向量有 k=256 维,我们的模型有 h=4 个 attention head,则降维操作包括:

  • 将输入向量乘以一个 256×64 矩阵,这会将 input vector 从 256 维降到 64 维;
  • 对于每个 head 需要执行 3 次降维:分别针对 query/key/value 的计算。

我们甚至只用三次 k×k 矩阵乘法就能实现 multi-head 功能, 唯一需要的额外操作是将生成的 output vector 重新按块排序:

To compute multi-head attention efficiently, we combine the computation of the projections down to a lower dimensional representation and the computations of the keys, queries and values into three k×k matrices.

完整工作流

下图展示了的整个 multi-head self-attention 过程:

4-head self-attention 的直观解释。对输入进行降维,针对 key/value/query 分别进行矩阵运算来实现。

从左到右分为 5 列:

  1. 原始 256-维 input vector;

  2. 输入降维:将 input vector 乘以 256x64 矩阵,降维到 64 维;

    注意:对每个 input vector 需要分别针对 query/key/value 降维,总共是 3 遍;

  3. 将降维后的 input 分别输入多个并行的 self-attention;

  4. 计算得到多个降维之后的 output vector;

  5. 对低维度 output vectors 进行拼接,重新回到与 input vectors 一样的维度。

3.5 multi-head vs. single-head 模型参数数量对比

参数指的是在将 input vector 变成 output vector 过程中用到的那些系数(权重矩阵)。

我们假设输入的是 k-维 input vectors,接下来分别看下 multi-head 和 single-head 的参数数量。

3.5.1 single-head
  • 权重矩阵 wij,其中 i,j∈[0,k];
  • 3 个平面:query/key/value;

因此总参数数量是 3k2。

3.5.2 multi-head

假设有 4 个 head,即 h=4,

  • 每个 head 对应一个 self-attention,每个 self-attention 3 个平面(query/key/value),因此总共 3h 个平面;
  • 每个平面的权重矩阵 wij,其中 i∈[0,k],j∈[0,k/h];

因此总的参数个数:3hkkh=3k2,与 single-head self-attention 的参数数量相同。

4-head self-attention 的直观解释。对输入进行降维,针对 key/value/query 分别进行矩阵运算来实现。

唯一的区别是 multi-head self-attention 最后拼接 output vector 时多了一个矩阵 Wo。与 single-head 相比,这增加了 k2 个参数。 在大多数 Transformer 中,每次 self-attention 之后会紧跟着一个前馈层(feed-forward layer),因此这可能不是绝对必要的。 但我还未见过能否把 Wo 去掉的严肃讨论。

4.4 self-attention 主要代码实现

接下来将我们的 self-attention 实现为一个 python 模块,方便复用:

import torch
from torch import nn
import torch.nn.functional as F

class SelfAttention(nn.Module):
    def __init__(self, k, heads=4, mask=False):
    super().__init__()

    assert k % heads == 0 # input vector size 必须是 heads 的整数倍
    self.k, self.heads = k, heads

然后,初始化几个 k*k 的线性变换矩阵, nn.Linear(bias=False) 能实现这个效果,并做了适当的初始化:

    # Compute the queries, keys and values for all heads
    self.tokeys    = nn.Linear(k, k, bias=False)
    self.toqueries = nn.Linear(k, k, bias=False)
    self.tovalues  = nn.Linear(k, k, bias=False)

	# This will be applied after the multi-head self-attention operation.
    self.unifyheads = nn.Linear(k, k)

接下来就可以实现了 self-attention 的计算了,在模型中对应的是 forward() 函数。

def forward(self, x):
    b, t, k = x.size()
    h = self.heads

    # 首先,为所有 heads 计算 query/key/value,得到的是完整嵌入维度的 k*k 矩阵
    queries = self.toqueries(x)
    keys    = self.tokeys(x)
    values  = self.tovalues(x)

    # 接下来将 queries/keys/values 切块(降维),分别送到不同的 head
    s = k // h
    keys    = keys.view(b, t, h, s)
    queries = queries.view(b, t, h, s)
    values  = values.view(b, t, h, s)

这对 tensors 进行了简单 reshape,现在 tensors 增加了一个 head 维度。 对于每个 input vector,可以理解为将这个 k*1 矩阵变成了一个 h * k//h 矩阵,

接下来计算点积。每个 head 的点积运算都是一样的,因为我们将 heads fold 到 batch dimention。 这样我们就可以使用 torch.bmm()(batch matrix multiplification),而 keys, queries and values 可以看做是 batch,只是 batch size 稍大了一点。

由于 head 和 batch dimension 没有相邻,因此我们在 reshape 之前需要转置。 这个操作开销很大,但似乎无法避免:

    # - fold heads into the batch dimension
    keys = keys.transpose(1, 2).contiguous().view(b * h, t, s)
    queries = queries.transpose(1, 2).contiguous().view(b * h, t, s)
    values = values.transpose(1, 2).contiguous().view(b * h, t, s)

You can avoid these calls to contiguous() by using reshape() instead of view() but I prefer to make it explicit when we are copying a tensor, and when we are just viewing it. See this notebook for an explanation of the difference.

跟之前一样,点积可以用单个矩阵乘法实现,但现在是 queries 乘以 keys,

    # Get dot product of queries and keys, and scale
    dot = torch.bmm(queries, keys.transpose(1, 2)) # -- dot has size (b*h, t, t) containing raw weights
    dot = dot / (k ** (1/2))                       # scale the dot product
    dot = F.softmax(dot, dim=2)                    # normalize, dot now contains row-wise normalized weights

然后用得到的权重再和 values 做点积,得到的就是每个 attention head 的输出:

    out = torch.bmm(dot, values).view(b, h, t, s) # apply the self attention to the values

为了将每个 head 的输出重新串联起来得到 k-维的最终输出,我们需要再次转置,然后将转置后的矩阵送到 unifyheads layer 做最好的维度变换:

    # swap h, t back, unify heads
    out = out.transpose(1, 2).contiguous().view(b, t, s * h)

    return self.unifyheads(out)

至此,一个 multi-head, scaled dot-product self attention 模型就实现好了。

The implementation can be made more concise using einsum notation (see an example here).

4 基于 multi-head self-attention 实现 transformers

4.1 Transformer 定义

transformer 不仅仅是一个 self-attention layer,还是一种架构(architecture)。 如何精确地判断一个东西是或者不是 transformer 还不是很明确,本文采用如下的定义:

任何设计用来处理一组连接的单元(例如序列中的 token 或图像中的像素), 如果单元之间的唯一交互方式是 self-attention,那这样的架构就称为 transformer。

与**其他机制(如卷积)**一样,可以基于 self-attention 层构建成更大的网络。但在此之前, 我们需要将 self-attention 重构为一个可以复用的 block。

4.2 Transformer block

构建基本的 transformer 有几种略微不同的方式,但大多数结构都大致如下:

各块依次执行:

  1. self-attention 层;
  2. 归一化层;
  3. 前馈层(feed forward layer),每个 MLP(multi-layer perceptron)分别与每个 input 做运算;
  4. 另一个层归一化。

两次归一化之前都会添加残差连接(residual connections)。

各组件的顺序并不是只能这样,重要的是

  1. 将 self-attention 与局部前馈相结合(combine self-attention with a local feedforward),
  2. 添加归一化和残差连接。

归一化和残差连接是常规技巧,用于使深度神经网络的训练更快、更准确。 层归一化仅应用于嵌入维度(layer normalization is applied over the embedding dimension only)。

实现:

class TransformerBlock(nn.Module):
  def __init__(self, k, heads):
    super().__init__()

    self.attention = SelfAttention(k, heads=heads)

    self.norm1 = nn.LayerNorm(k)
    self.norm2 = nn.LayerNorm(k)

    self.ff = nn.Sequential(
      nn.Linear(k, 4 * k),
      nn.ReLU(),
      nn.Linear(4 * k, k))

  def forward(self, x):
    attended = self.attention(x)
    x = self.norm1(attended + x)

    fedforward = self.ff(x)
    return self.norm2(fedforward + x)

这里我们选择了让 feed forward 隐藏层比 input/output 大 4 倍,这个倍数的选择是随意的, 更小的倍数可能也能工作,并且占用内存更少,但最小不能小于 input/output layer 大小。

4.3 文本分类(text classification)transformer

我们能构建的最简单 transformer 叫 sequence classifier(顺序分类器)。 我们用 IMDb(Internet Movie Database)sentiment classification 数据集:

  • 数据内容是影评,
  • token 化成了单词序列,
  • 分类标签是 positive 和 negative(对电影的正面/负面评价)

架构的核心部分非常简单,就是一长串 transformer block。所需做的事情:

  • 如何将 input sequence feed 给这个长链,
  • 如何对最终 output sequence 进行变换,得到单个分类结果。
4.3.1 输出:单个分类结果

从 sequence-to-sequence layers 构建 sequence classifier 的最常见方法是 对最终输出序列做 global average pooling,并将结果映射到 softmaxed class vector。

Overview of a simple sequence classification transformer. The output sequence is averaged to produce a single vector representing the whole sequence. This vector is projected down to a vector with one element per class and softmaxed to produce probabilities.

4.3.2 输入:词序敏感(using the positions)

前面已经讨论了嵌入层的原理,接下来我们将用它来表示单词。

正如前面已经提到的,我们正在堆叠(stacking)排列等变层(permutation equivariant layers), 最终的 global average pooling 是排列不变的(permutation _in_variant), 因此整个网络也是排列不变的。用白话来说, 即使我们打乱句子中的单词顺序,无论我们学到什么权重,都会得到完全相同的分类结果。 显然,我们希望这个先进的语言模型至少对词序具有一定的敏感性,因此我们需要解决这个问题。

解决方案很简单:创建一个与 input 等长的向量记录当前句子中单词的位置,并将其添加到 word embedding 中。 具体到实现上,有两种选择。

位置嵌入(position embeddings)

像嵌入文字一样嵌入位置。就像创建嵌入向量𝐯cat 和 𝐯susan 一样, 我们创建嵌入向量𝐯12 和 𝐯25。

缺点是在训练期间必须看到每个不同长度的序列,否则相关的位置嵌入得不到训练。 优点是效果还不错,而且很容易实现。

位置编码(position encodings)

位置编码与位置嵌入的工作方式类似,但不学习位置向量,而只是选择一些函数

f:ℕ→ℝk

将位置映射到实值向量,并让网络弄清楚如何解释这些编码。

好处是,对于精心选择的函数,网络能够处理比训练期间看到的序列更长的序列(在它们上表现应该不会太好,但至少我们可以 check)。 缺点是编码函数的选择是一个复杂的超参数(a complicated hyperparameter),实现起来有点复杂。

4.3.3 基于 Pytorch 实现

简单起见,本文使用位置嵌入(position embeddings)来记录 input 顺序。

以下就是我们的 text classification transformer 的完整实现:

class Transformer(nn.Module):
    def __init__(self, k, heads, depth, seq_length, num_tokens, num_classes):
        super().__init__()

        self.num_tokens = num_tokens
        self.token_emb = nn.Embedding(num_tokens, k)
        self.pos_emb = nn.Embedding(seq_length, k)

        # The sequence of transformer blocks that does all the heavy lifting
        tblocks = []
        for i in range(depth):
            tblocks.append(TransformerBlock(k=k, heads=heads))
        self.tblocks = nn.Sequential(*tblocks)

        # Maps the final output sequence to class logits
        self.toprobs = nn.Linear(k, num_classes)

    def forward(self, x):
        """
        :param x: A (b, t) tensor of integer values representing words (in some predetermined vocabulary).
        :return: A (b, c) tensor of log-probabilities over the classes (where c is the nr. of classes).
        """
        # generate token embeddings
        tokens = self.token_emb(x)
        b, t, k = tokens.size()

        # generate position embeddings
        positions = torch.arange(t)
        positions = self.pos_emb(positions)[None, :, :].expand(b, t, k)

        x = tokens + positions # 为什么文本嵌入和位置嵌入相加,没有理论,可能就是实验下来效果不错。
                               # https://writings.stephenwolfram.com/2023/02/what-is-chatgpt-doing-and-why-does-it-work/
        x = self.tblocks(x)

        # Average-pool over the t dimension and project to class
        # probabilities
        x = self.toprobs(x.mean(dim=1))
        return F.log_softmax(x, dim=1)

在深度为 6 ,最大序列长度为 512 时,这个 transformer 取得了 85% 的准确度,与 RNN(循环神经网络)模型的结果相当,但训练速度快得多。 要看到这个 transformer 真正接近人类的性能,就需要在更多数据上训练更深的模型。后文将详细介绍怎么做。

4.4 文本生成(text generation)transformer

接下来尝试一下自回归模型(autoregressive model): 训练一个字符级别(character level)的 transformer 来预测序列中的下一个字符。

4.4.1 自回归模型和掩码

训练方式很简单(并且在 transformer 出现之前就已经存在很久了)。 我们给 sequence-to-sequence 模型一个序列作为输入,然后要求它预测序列中下一个位置的字符。 换句话说,目标输出是向左移动一个字符的相同序列:

  • 如果是 RNN 模型,那这就是我们所需做的所有事情, 因为它不能往前看,output i 只依赖 inputs 0 ~ i。
  • 而对于 transformer,output 取决于整个 input sequence, 因此预测下一个单词就简单多了,只需从 input 中挑选。

要将 self-attention 用作自回归模型,需要确保它不能 look forward input 序列。 在 softmax 之前对点积矩阵应用一个掩码,禁用矩阵对角线之上的所有元素, 就能帮我们实现这一目的。

对 self-attention 进行 masking 操作,确保 input sequence 中只有当前位置之前的 input elements 能参与计算。 注意图中的乘法符号其实有一点点误导性:我们实际上是将右上角的元素设置为负无穷大 −∞

由于我们希望这些元素在 softmax 之后全是 0,因此将它们设置为 −∞。相应的代码:

dot = torch.bmm(queries, keys.transpose(1, 2))

indices = torch.triu_indices(t, t, offset=1)
dot[:, indices[0], indices[1]] = float('-inf')

dot = F.softmax(dot, dim=2)

这样修改 self-attention 模块之后,模型就不能再 look forward input sequence 了。

4.4.2 训练:基于维基百科数据集 enwik8

我们在标准 enwik8 数据集(取自 Hutter prize) 上进行训练,该数据集包含 108 个维基百科文本中的字符。在训练期间,通过从数据中随机抽取子序列来生成批次。

我们使用由 12 个 transformer block 和 256 个嵌入维度组成的 transformer,对长度为 256 的序列进行训练。 在 RTX 2080Ti(大约 170K 个大小为 32 的批次)上训练了大约 24 小时后, 让模型从 256 个字符的种子开始生成:对于每个字符,输入它前面的 256 个字符, 然后预测下一个字符。 我们从temperature 为 0.5 的那个开始采样,然后移动到下一个字符。

输出如下所示:

1228X Human & Rousseau. Because many of his stories were originally published in long-forgotten magazines and journals, there are a number of [[anthology|anthologies]] by different collators each containing a different selection. His original books have been considered an anthologie in the [[Middle Ages]], and were likely to be one of the most common in the [[Indian Ocean]] in the [[1st century]]. As a result of his death, the Bible was recognised as a counter-attack by the [[Gospel of Matthew]] (1177-1133), and the [[Saxony|Saxons]] of the [[Isle of Matthew]] (1100-1138), the third was a topic of the [[Saxony|Saxon]] throne, and the [[Roman Empire|Roman]] troops of [[Antiochia]] (1145-1148). The [[Roman Empire|Romans]] resigned in [[1148]] and [[1148]] began to collapse. The [[Saxony|Saxons]] of the [[Battle of Valasander]] reported the y

4.4.3 文本生成结果分析

对于上面的输出,应该注意到,

  1. 输出的文本中正确使用了维基百科链接标签语法,链接内的文本准确表达了链接主题。
  2. 生成的内容也与主题大致一致:生成的文本以圣经和罗马帝国为主题,在不同的地方使用不同的相关术语。
  3. 还有一个不那么明显的地方:“Battle of Valasander”,这场“战争”似乎是这个神经网络自己杜撰的。

这虽然与 GPT-2 等模型的性能相去甚远,但与 RNN 等模型相比优势已经很明显:更快的训练速度(类似的 RNN 模型需要很多天来训练)和更好的长期一致性。

另外,该模型在验证集上实现了 1.343bit/byte 的压缩, 这与 GPT-2 模型(下文会展开介绍)实现的每字节 0.93 位的相差不远。

4.5 设计考虑:Transformer 与 RNN/卷积 对比

在 transformer 之前,最先进的架构是 RNN(通常是 LSTM 或 GRU),但它们存在一些问题。

RNN 展开(unrolled)后长这样:

RNN 最大的问题是**级联**(recurrent connection): 虽然它使得信息能沿着 input sequence 一路传导, 但也意味着在计算出 i−1 单元之前,无法计算出 i 单元的输出。

与 RNN 此对比,一维卷积(1D convolution)如下:

在这个模型中,所有输出向量都可以并行计算,因此速度非常快。但缺点是它们 在 long range dependencies 建模方面非常弱。在一个卷积层中,只有距离比 kernel size 小的单词之间才能彼此交互。对于更长的依赖,就需要堆叠许多卷积。

Transformer 试图兼顾二者的优点:

  • 可以像对彼此相邻的单词一样,轻松地对输入序列的整个范围内的依赖关系进行建模(事实上,如果没有位置向量,二者就没有区别);
  • 同时,避免 recurrent connections,因此整个模型可以用非常高效的 feed forward 方式计算。

Transformer 的其余设计主要基于一个考虑因素 —— 深度 —— 大多数选择都是训练大量 transformer block 层,例如,transformer 中只有两个非线性的地方:

  1. self-attention 中的 softmax;
  2. 前馈层中的 ReLU。

模型的其余部分完全由线性变换组成,完美地保留了梯度。

I suppose the layer normalization is also nonlinear, but that is one nonlinearity that actually helps to keep the gradient stable as it propagates back down the network.

5 历史包袱

如果在网上看一些介绍 transformer 的文章,可能会经注意它们提到的一些概念和术语本文并没有介绍。 这是因为我认为那些东西并不是理解现代 transformer 所必需的。 话虽如此,有两个方面还是可以介绍一下,因为它们对于理解网上的那些关于现代 transformer 的文章还是有帮助的。

5.1 为什么叫 self-attention?

重点在 attention 这个单词上。

在 self-attention 提出之前,sequence models 主要指的是由 recurrent networks 或 convolutions 堆叠(stack)而成的网络。 之后人们发现,如果不是将上一层的输出直接 feed 到下一层的输入, 而是引入一种中间机制来判断输入中的哪些元素与输出中的某个特定单词相关, 就能给 sequence models 带来很大改善。具体来说,

  • 我们把 input 称为 values(因为它们是实实在在的值,我们将基于这些值计算输出);
  • 然后,一些(trainable)机制为每个 value 分配一个 key;
  • 最后,对每个 output,一些其他机制分配一个 query。

这些名称源自键值存储(key-value store)数据结构。 在 key-value store 场景中,对于每个 query(查询),store 中(最多)只有一个 item 能匹配到, 这个 item 有唯一的 key,返回这个 key 对应的 value。

Attention(注意力)模型是 key-value store 模型的宽松版:

  • store 中的每个 key 都能在某种程度上(而不是精确 100% 或 0%)匹配到 query;
  • 另外,query 返回的也不是单个 value,而是所有 value,我们根据每个 key 与 query 匹配的程度对相应 value 取一个加权和。

self-attention 的重大突破在于,attention 本身就是一种足够强大的机制,能完成所有学习。 正如作者所说,Attention is all you need。

  • Key/value/query 都来自同一个 input vector(只是各自经过了略微不同的线性变换);
  • 他们关注自己(attend to themselves),因此叫 self-attention;
  • 这种 self-attention 经过多层堆叠之后,就能提供足够的非线性和表征能力(nonlinearity and representational power)来学习非常复杂的功能。

5.2 最初的 transformer: encoders and decoders

当时的 sequence-to-sequence model 的标准结构是带 teacher forcing 的 encoder-decoder 架构,

encoder-decoder 模型

Encoder 获取输入序列并将整个 sequence 映射为一个 latent representations, 这可以是一系列 latent vectors,也可以是如上图中的单个向量。 然后将该向量传递给 decoder,后者将其解码为期望的目标序列(例如,同一句话的另一种语言表示)。

Teacher forcing 指的是允许 decoder 访问 input 的技术 —— 但以自回归(autoregressive)的方式。 也就是说, decoder 基于 latent vectors 和它自己已经生成的单词,逐单词生成输出句子。 这减轻了 latent representations 的一些压力:

  • decoder 可以使用逐词采样(word-by-word sampling)来处理语法(syntax and grammar)等低级结构,
  • 而使用 latent vectors 来 capture 更高级别的语义结构(semantic structure)。

理想情况下,使用相同的 latent representations 进行两次 decoding 会得到两个具有相同含义的不同句子。

在后来的 transformer 中,如 BERT 和 GPT-2, encoder/decoder 被完全去掉了。 简单的 transformer block 做堆叠(stack)就足以在许多基于序列的任务中实现最先进的效果。 这种模型有时被称为 decoder-only transformer(对于自回归模型) 或 encoder-only transformer(对于没有 masking 的模型)。

6 现代 transformers

来看几个有代表性的现代 transformers。

6.1 Google BERT:340M 参数

BERT (Bidirectional Encoder Representations from Transformers) 是首批证明 transformer 可以在各种基于语言的任务上 (question answering, sentiment classification or classifying whether two sentences naturally follow one another) 达到人类水平的模型之一。

BERT 由一些与本文描述的类似的简单 transformer block 堆叠而成,然后在一个大型通用领域语料库上进行预训练, 该语料库由包含 8 亿个(800M)单词的英文书籍(现代作品,from unpublished authors) 和包含 25 亿(2.5B)个单词英文维基百科文章(去掉了 markup)组成。

预训练由两个任务组成:

Masking

A certain number of words in the input sequence are: masked out, replaced with a random word or kept as is. The model is then asked to predict, for these words, what the original words were. Note that the model doesn’t need to predict the entire denoised sentence, just the modified words. Since the model doesn’t know which words it will be asked about, it learns a representation for every word in the sequence.

Next sequence classification

Two sequences of about 256 words are sampled that either (a) follow each other directly in the corpus, or (b) are both taken from random places. The model must then predict whether a or b is the case.

BERT uses WordPiece tokenization, which is somewhere in between word-level and character level sequences. It breaks words like walking up into the tokens walk and ##ing. This allows the model to make some inferences based on word structure: two verbs ending in -ing have similar grammatical functions, and two verbs starting with walk- have similar semantic function.

The input is prepended with a special token. The output vector corresponding to this token is used as a sentence representation in sequence classification tasks like the next sentence classification (as opposed to the global average pooling over all vectors that we used in our classification model above).

After pretraining, a single task-specific layer is placed after the body of transformer blocks, which maps the general purpose representation to a task specific output. For classification tasks, this simply maps the first output token to softmax probabilities over the classes. For more complex tasks, a final sequence-to-sequence layer is designed specifically for the task.

The whole model is then re-trained to finetune the model for the specific task at hand.

In an ablation experiment, 作者展示了与之前的模型相比,最大的改进来自 BERT 的双向特性(bidirectional nature)。 之前的模型,例如 GPT,使用的是 autoregressive mask,只允许 attention 使用前面的 token。 在 BERT 中,all attention is over the whole sequence,这是性能提升的主要来源。

这也是为什么 “BERT” 中的 B 表示 “bidirectional”。

最大的 BERT model 使用了 24 transformer blocks,embedding dimension 1024,16 attention heads, 总参数数量为 3.4 亿(340M)。

6.2 OpenAI GPT-2:1.5B 参数

They show state-of-the art performance on many tasks. On the wikipedia compression task that we tried above, they achieve 0.93 bits per byte.

GPT-2 是第一个真正进入主流新闻的 transformer 模型,原因是 GPT-2 可以生成看起来足够可信的文本,如果 2016 年有这种技术, 那当年美国总统大选中出现的那种大规模假新闻活动只需要一个人就能完成了。

对于 GPT-2,OpenAI 也做出了一个颇受争议的决定 —— 不公布完整模型。

GPT-2 第一个技巧是构建一个新的高质量数据集,

  • 虽然 BERT 使用了高质量的数据,但数据的来源(精心编写的书籍和维基百科文章)在写作风格上缺乏多样性;
  • 为了在不牺牲质量的前提下收集更多不同的数据,作者使用社交媒体网站 Reddit 上的链接来收集大量文本。

GPT2 本质上是一个语言生成模型(language generation model), 因此像我们自己设计的 text generation transformer 一样,它也使用了 masked self-attention。 它使用字节对编码(byte-pair encoding)来 tokenize the language, 这与 WordPiece encoding 一样将单词拆分为比“比单词短、比单个字母长”的 tokens。

GPT2 与我们的 text generation transformer 非常相似,只有很小的层级顺序差异,以及增加了训练深度。 最大的模型使用 48 个 transformer block,序列长度为 1024,嵌入维度为 1600,总共 15 亿个(1.5B)参数。

GPT2 在很多任务上都表现出了最先进的性能。在上面提到的维基百科压缩任务中,它取得了每字节 0.93 位的压缩效率。

如何学习大模型 AI ?

由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。

但是具体到个人,只能说是:

“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。

这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。

我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

在这里插入图片描述

第一阶段(10天):初阶应用

该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。

  • 大模型 AI 能干什么?
  • 大模型是怎样获得「智能」的?
  • 用好 AI 的核心心法
  • 大模型应用业务架构
  • 大模型应用技术架构
  • 代码示例:向 GPT-3.5 灌入新知识
  • 提示工程的意义和核心思想
  • Prompt 典型构成
  • 指令调优方法论
  • 思维链和思维树
  • Prompt 攻击和防范
  • …

第二阶段(30天):高阶应用

该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。

  • 为什么要做 RAG
  • 搭建一个简单的 ChatPDF
  • 检索的基础概念
  • 什么是向量表示(Embeddings)
  • 向量数据库与向量检索
  • 基于向量检索的 RAG
  • 搭建 RAG 系统的扩展知识
  • 混合检索与 RAG-Fusion 简介
  • 向量模型本地部署
  • …

第三阶段(30天):模型训练

恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。

到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?

  • 为什么要做 RAG
  • 什么是模型
  • 什么是模型训练
  • 求解器 & 损失函数简介
  • 小实验2:手写一个简单的神经网络并训练它
  • 什么是训练/预训练/微调/轻量化微调
  • Transformer结构简介
  • 轻量化微调
  • 实验数据集的构建
  • …

第四阶段(20天):商业闭环

对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。

  • 硬件选型
  • 带你了解全球大模型
  • 使用国产大模型服务
  • 搭建 OpenAI 代理
  • 热身:基于阿里云 PAI 部署 Stable Diffusion
  • 在本地计算机运行大模型
  • 大模型的私有化部署
  • 基于 vLLM 部署大模型
  • 案例:如何优雅地在阿里云私有部署开源大模型
  • 部署一套开源 LLM 项目
  • 内容安全
  • 互联网信息服务算法备案
  • …

学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。

如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。

这份完整版的大模型 AI 学习资料已经上传优快云,朋友们如果需要可以微信扫描下方优快云官方认证二维码免费领取【保证100%免费】
【基础模型】Transformer架构 seq2seq任务输入一组数字,输出数字的累加和import torch.optim as optim #优化import numpy as np #计算#数据生成器:生成大小为samples x length 的矩阵,数值位于0-10之间,横坐标表示输入样本数,纵坐标表示序列长度#存储每行累加和return X,Y#数据加载器# data -> tensor 转换函数#模型定义#初始化: 输入大小,输出大小,层数,隐藏层大小,头部数量,丢失率#词嵌入#位置编码器。 阅读详情

相关推荐

Transformer解决在小样本学习的问题 -附实现代码python

Transformer在小样本学习中的应用展示了其在处理有限样本数据时的强大能力。通过结合迁移学习、元学习、多任务学习等方法,Transformer可以在小样本任务中实现良好的泛化性能。然而,仍有许多挑战需要解决,比如如何进一步减少模型的计算复杂度、如何更高效地利用少量样本等。

viviwiky的博客 2267

一文彻底搞懂Transformer - 总体架构

*****c**输入嵌入:将输入的文本转换为向量,便于模型处理。位置编码:给输入向量添加位置信息,因为Transformer并行处理数据而不依赖顺序。多头注意力:让模型同时关注输入序列的不同部分,捕获复杂的依赖关系。残差连接与层归一化:通过添加跨层连接和标准化输出,帮助模型更好地训练,防止梯度问题。带掩码的多头注意力:在生成文本时,确保模型只依赖已知的信息,而不是未来的内容。前馈网络:对输入进行非线性变换,提取更高级别的特征。****

m0_59164304的博客 6955

Transformer详解,中文版架构图

2.2.1 “编码器-解码器”架构Seq2Seq 思想就是将网络的输入输出分别看作不同的序列,然后实现序列到序列的映射,其经典实现结构就是“编码器-解码器”框架。编码器-解码器框架如图 2.7 所示。图2.7 编码器-解码器的基本框架在 Seq2Seq 思想应用于自然语言处理之初,编码器解码器主要使用的是 RNN 及其变体。针对输入,编码器会先遍历输入序列,根据循环神经网络的特性,每一层的输入都包含了当前层的输入和前一层的隐藏状态,而。

具身机器人和通用AI 3万+

【自然语言处理】Transformer架构解析

文章目录Transformer架构解析1. 认识Transformer架构Transformer模型的作用Transformer总体架构图2. 输入部分实现文本嵌入层的作用位置编码器的作用3. 编码器部分实现3.1 掩码张量3.2 注意力机制3.3 多头注意力机制3.4 前馈全连接层3.5 规范化层3.6 子层连接结构3.7 编码器层3.8 编码器4. 解码器部分实现4.1 解码器层4.2 解码器5. 输出部分实现线性层的作用softmax层的作用 Transformer架构解析 1. 认识Transfor

张小猪的家 2125

Polars简明基础教程一:Polars快速入门

在这个例子中,我们将添加一个列,其中包含每个舱位中乘客的最大年龄。在Python中(而不是特指Polars),当我们将代码包裹在括号中时,我们可以在新行上调用一个新方法——在这种情况下是select。在这里,我们看到一个使用表达式API的简单示例,其中我们在select语句中选择Pclass、Name和Age列(我们将在第3节中深入了解select语句)。表达式是一个函数,它接受一个Series(或DataFrame中的列)作为输入,并返回一个Series(或DataFrame中的列)。

sosogod的博客 3879

小样本学习概述

目录 前言 小样本学习存在的意义? 什么是小样本学习? 小样本学习的方法有哪些? 结语 前言 小样本学习(Few-Shot Learning)是近几年兴起的一个研究领域,小样本学习旨在解决在数据有限的机器学习任务。 小样本学习存在的意义? 近些年,以深度卷积神经网络为代表的深度学习方法在各类机器学习任务上取得了优异的成绩——很多任务上已经超越了人类表现。狂欢背后,危机四伏。因为这些深度学习方法work的关键之一是海量标注数据的支持。 但是在工业界,很多时候难以获得海量的训练数据,更.

DwD-的博客 3万+

Transformer 是如何工作的:600 行 Python 代码实现两个(文本分类+文本生成)Transformer(2019

Transformer 是一类非常令人着迷的机器学习架构之前已经有一些不错的介绍文章(例如 [1, 2]),但过去几年transformer 变得简单了很多, 因此要解释清楚现代架构(modern architectures)是如何工作的,比以前容易多了。本文试图丢掉历史包袱,开门见山地解释现代 transformer 的工作原理。神经网络和反向传播(neural networks and backpropagation)的基本知识有助于更好地理解本文,这个讲座介绍了神经网络的基础知识;这个讲座。

caoyongsheng的博客 1523

基于PID的定速巡航控制系统设计【MATLAB源码+Word文档】

本文详细介绍了定速巡航控制是一种车辆驾驶辅助系统,它能让驾驶者在高速或长途驾驶时,设定一个固定的车速,然后车辆会自动维持这个速度行驶,无需驾驶者持续踩油门。这大大减轻了驾驶者的疲劳,同时也提高了行驶的安全性。PID定速巡航控制系统是一种利用比例(Proportional)、积分(Integral)和微分(Derivative)控制算法来实现车辆定速巡航的先进系统。这种系统能够根据设定的速度,自动调整油门开度或制动力度,使车辆保持恒定的速度行驶。这种控制系统具有响应速度快、控制精度高、稳定性好等优点。

weixin_761237174的博客 1670

Transformer 是如何工作的:600 行 Python 代码实现 self-attention 和两类 Transformer

Transformer 是一类非常令人着迷的机器学习架构之前已经有一些不错的介绍文章(例如 [1, 2]),但过去几年transformer 变得简单了很多, 因此要解释清楚现代架构(modern architectures)是如何工作的,比以前容易多了。本文试图丢掉历史包袱,开门见山地解释现代 transformer 的工作原理。神经网络和反向传播(neural networks and backpropagation)的基本知识有助于更好地理解本文,这个讲座介绍了神经网络的基础知识;这个讲座。

hao_wujing的专栏 1270

虚拟机器人平台CoppeliaSim(Vrep)的逆运动学教学

相信看这篇博客的人都知道CoppeliaSim是做什么的,因此这里不多赘述。CoppeliaSim从最早的Vrep到现在已经更新了N个版本,从过去Vrep3.x开始使用的我这里小小地吐槽一下这个软件。CoppeliaSim每一个版本都有更新,问题是这些更新有时候不能向前兼容,而且时不时更新地让人不知所措(对于有紧急任务的时候真的很火大啊)。 以本文所展示地逆运动学来说,CoppeliaSim在4.0.0之后又大变,推翻了过往所有的拖动模块链接之类的构建方式,转而采用直接通过代码来实现逆运动学。 简单版逆运动

qq_39243015的博客 6699

HWSD全球土壤数据下载处理

下载地址: http://www.fao.org/soils-portal/soil-survey/soil-maps-and-databases/harmonized-world-soil-database-v12/en/

江北20190411的博客 3万+

大模型训练实战:600行代码实现超越原版GPT-2!

这篇文章详细解析了基于nanogpt仓库的GPT-2训练脚本,通过600行代码实现超越原版效果。教程涵盖模型架构、优化器配置、数据加载、分布式训练等技术,以及混合精度、梯度累积、学习率调度等核心技巧。提供完整训练代码和评测方法,帮助理解大模型训练细节和最佳实践,适合有深度学习基础的程序员学习。

2401_85375151的博客 1045

ArcGIS Pro符号化教程

本文详细介绍了ArcGIS Pro的软件设置和符号化操作步骤。主要包括:1)设置默认工程目录和关闭地图自动加载;2)创建新工程的基本流程;3)点要素的单一符号化方法,包括样式修改和图层调整;4)图表符号显示过滤器的使用,涉及字段添加、图表类型选择和背景设置。文章还提供了操作截图和数据下载链接,适合GIS初学者学习ArcGIS Pro的基础符号化操作。

WwLK123的博客 549

nanoGPT终极指南:3分钟训练莎士比亚风格文本生成模型

想体验GPT的强大功能,但又担心代码复杂、资源消耗大?nanoGPT正是为你设计的解决方案!作为由著名AI研究者Andrej Karpathy创建的极简GPT实现,nanoGPT将复杂的Transformer架构压缩到仅600行代码,让你在个人电脑上就能训练属于自己的语言模型。无论你是深度学习新手还是经验丰富的开发者,nanoGPT都能让你快速上手GPT技术,无需昂贵的硬件投入。 ## 🚀 n

gitblog_00895的博客 953

AI测试干货:让大语言模型稳定输出 JSON 的四种实现方案

用大语言模型做测试用例生成、数据提取时,最头疼的就是让它老老实实返回 JSON。你以为它会给你 `{"name":"张三"}`,结果它来一句"好的,这是生成结果:```json...",或者干脆在 JSON 里加注释,直接把你的 `json.loads()` 干崩溃。 模型本质上是个话痨,天生爱解释。但我们要的是结构化数据,不是聊天记录。我整理了四种实战方案,从最简单的提示词技巧到工程化框架,覆盖了从快速验证到生产环境的各种场景。

blues_C的博客 956

分布式计算Ray框架面试题及参考答案

动态任务图执行引擎是一种能够根据任务之间的依赖关系和运行时的实际情况,动态构建和执行任务图的系统。在 Ray 中,动态任务图执行引擎允许任务在运行时根据数据可用性、计算资源状态等因素,灵活地决定后续任务的执行顺序和方式。对于 AI 应用来说,其往往具有动态性的特点。例如,在深度学习模型的训练过程中,可能需要根据训练的结果动态调整下一轮训练的参数、数据增强方式或者模型结构。动态任务图执行引擎可以很好地支持这些动态需求。它能够根据 AI 应用中各个任务的实时输出和依赖关系,动态地生成和调整任务图。

大模型大数据攻城狮的专栏 1350

LKY_OfficeTools:一条命令跑完 Office 下载、安装与 KMS 激活

机房里 10 台新机器等着交付验收,而每台都要装 Office:找下载页、判断 32 还是 64 位、决定装哪些组件、再处理激活——单台 30 分钟起步,人盯着跑完一轮就是一整天。LKY_OfficeTools 是一个命令行工具,一次运行自动完成最新版 Office 的下载、安装与 KMS 激活,面向新装系统的单台部署和机房批量环境。 ## LKY_OfficeTools 启动前的预检关卡 双

gitblog_00776的博客 758

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Teleport Scopes:基于路径层次的作用域隔离与委派管理设计全解析

本文基于仓库 [rfd/0229-scopes.md](https://link.gitcode.com/i/d61c55b3e10bd9dc0c39dfa5fc78955e) 这一设计文档,系统讲解 Teleport 全新引入的 **Scopes(作用域)** 体系:一种以路径式字符串(如 `/staging/west`)组织资源与权限、支持层次化隔离与委派管理的机制。读完本文,你将掌握 sco

gitblog_00269的博客 779

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

600行代码如何训练出GPT-2:nanoGPT 最小化GPT训练库实战指南

如果你想自己训练一个 GPT,但被大框架动辄上千行的训练代码劝退,nanoGPT 解决的就是这个问题:它是 Andrej Karpathy 写的最小化仓库,用约 600 行可读代码就能从零训练或微调中等规模的 GPT,单卡起训练,多卡复现 GPT-2(124M)。 ## 拆解 nanoGPT 训练源码:600 行凭什么够训 GPT ### 砍掉一切"教学装饰",只留训练主干 **动机**:作

gitblog_00447的博客 399

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Wine 下跑词典格式转换?imewlconverter 在 Linux 上的 4 个实操场景

imewlconverter(深蓝词库转换)是一款开源免费的输入法词库转换工具,支持搜狗、QQ、Rime 等 50 余种格式互转。不少新手第一反应是:Linux 上只能靠 Wine 跑它的 Windows 图形界面?其实不一定——下面按真实场景,带你把路走通。 ## 动手前先过一遍自查清单 📋 按这个顺序检查,省得中途返工: - 你手上是否有源词库文件?记下它的扩展名(`.scel`、`.

gitblog_00849的博客 772

终极指南:如何在3分钟内用nanoGPT训练莎士比亚风格文本生成器

想要在个人电脑上快速体验GPT模型的魔力吗?nanoGPT是Andrej Karpathy创建的极简GPT实现,专为快速训练和微调中等规模语言模型而设计。这个开源项目让任何人都能在几分钟内开始训练自己的文本生成模型,无论是莎士比亚风格的诗歌创作还是专业文档生成,都能轻松上手。nanoGPT的核心优势在于其极简设计——训练脚本仅300行,模型定义也仅300行,却完整实现了GPT-2架构,支持从零开始

gitblog_00694的博客 488

Transformer模型代码复现及逻辑说明

1、为什么要进行词嵌入编码: Transformer进行词嵌入编码的原因是将离散的词汇转换为连续的向量表示,使模型能够处理和学习词汇之间的关系。词嵌入将词汇转换为向量,使模型能够捕捉语义关系、降低维度和提高泛化能力,从而更有效地理解和处理自然语言。2、词嵌入编码实现的逻辑: 使用nn.Embedding方法,其实现原理:前向传播,实现该方法时,其内部使用了一张查找表,将输入的词索引映射到对应的向量。 : 创建一个嵌入层,用于将词汇表中的每个词映射到一个固定维度的向量。 : : 词汇表的大小,表示模型可以

weixin_62486274的博客 2546

nanoGPT:几百行代码快速训练 GPT-2 的极简指南

想让训练一个 GPT 到底需要多少代码?nanoGPT 给了一个很直接的答案:[train.py](https://link.gitcode.com/i/d087992db697411ffe3bbc8b765cea0a) 约 300 行训练循环,单节点 8 张 A100 40GB 约 4 天即可复现 GPT-2(124M 参数)。它适合想看懂完整训练流程的新手,和想改动训练逻辑的研究者。 ##

gitblog_00312的博客 599

nanoGPT深度解析:如何在极简代码中实现GPT模型训练的革命性突破

在大型语言模型(Large Language Models)日益复杂的今天,nanoGPT以其极简的代码架构和高效的训练流程,为开发者和研究者提供了一条全新的路径。这个由Andrej Karpathy创建的轻量级GPT实现,不仅重新定义了模型训练的入门门槛,更在代码简洁性和训练效率之间找到了完美平衡点。 ## 极简架构背后的设计哲学 nanoGPT的核心设计理念可以用一个词概括:**"牙齿优先

gitblog_00697的博客 514

重新定义GPT训练:nanoGPT如何用极简代码颠覆语言模型开发范式

在当今AI领域,大型语言模型(LLM)的开发似乎已成为少数科技巨头的专利。开发者们常常面临这样的困境:要么使用现成的API但失去对模型的控制权,要么投入数百万美元的计算资源从头训练。我们站在一个十字路口:是继续依赖少数公司的技术垄断,还是寻找一条让AI民主化的新路径?nanoGPT的出现,正是对这个问题的有力回答。 ## 为什么传统GPT方案不够友好? 传统的GPT实现往往伴随着令人望而生畏的

gitblog_00321的博客 910

nanoGPT:如何用300行代码复现GPT-2训练

训练一个 124M 参数的 GPT-2,到底需要多少代码?nanoGPT 的答案大约 600 行:训练循环 300 行,模型定义 300 行。在 8 块 A100 40GB 的单节点上,它约 4 天复现官方效果,整个训练链路没有任何框架封装,是学习大模型训练很少见的第一手材料。 ## 项目速览 nanoGPT 是对 minGPT 的重写,去掉教学性外壳,只保留训练必需的部分。代码分工极简:tr

gitblog_00476的博客 821
上一篇: Transformer各层网络结构详解!大模型面试必备!(附代码实现)
下一篇: OpenAI 大神亲授,人人都能看懂的大模型入门教程
我爱学大模型
博客等级 码龄3年 7883粉丝 687原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值