神经网络、Transformer 和 GPT | 原创,AI翻译
目录
- 我是如何学习 Transformers 中的 KQV 机制的
- 查询(Query)、键(Key)、值(Value)矩阵代表 token 间的交互
- 理解需要了解维度和形状
- 最初的概念会随着时间推移变得更清晰
- 人工智能时代提供了丰富的学习资源
- 鼓舞人心的故事激励持续学习
- 从神经网络到 GPT
- 从零开始复现神经网络以加深理解
- Transformers 通过 embedding 和 encoding 处理文本
- 自注意力机制计算词语间的相似性
- 观看基础讲座并阅读代码
- 通过项目和论文追随好奇心
- 神经网络如何工作
- 反向传播算法更新权重和偏置
- 输入数据通过网络层激活
- 前向传播通过 sigmoid 计算层输出
- 误差计算指导学习调整
- 理解维度对理解至关重要
我是如何学习 Transformers 中的 KQV 机制的
2025.07.16
在阅读了 K, Q, V 机制在 Transformers 中之后,我不知怎么地就理解了 K, Q, V 是如何工作的。
Q 代表 Query,K 代表 Key,V 代表 Value。对于一个句子,Query 是一个矩阵,存储一个 token 需要向其他 token 查询的值。Key 代表 token 的描述,Value 代表 token 的实际意义矩阵。
它们有特定的形状,所以需要了解它们的维度和细节。
我在 2025 年 6 月初左右理解了这一点。我最初是在 2023 年底左右接触到它的。那时我读了 The Illustrated Transformer 等文章,但并没有理解太多。
大约两年后,我发现现在更容易理解了。在这两年里,我专注于后端工作并准备我的副学士学位考试,并没有大量阅读或学习机器学习。然而,我确实在开车或做其他事情时,不时地思考这些概念。
这让我想起了时间的作用。我们可能刚开始学很多东西,即使理解不多。但不知何故,它触发了我们思考的起点。
随着时间的推移,我发现对于知识和发现,第一次很难思考或理解。但后来,学习和了解似乎变得更容易了。
其中一个原因是,在人工智能时代,学习更容易,因为你可以深入研究任何细节或方面来解决你的疑惑。还有更多相关的 AI 视频可用。更重要的是,你看到如此多的人正在学习并在其基础上构建项目,比如 llama.cpp。
Georgi Gerganov 的故事令人鼓舞。作为一名从 2021 年左右开始的机器学习新手,他在 AI 社区产生了强大的影响。
这种事情会一遍又一遍地发生。所以,对于强化学习和最新的人工智能知识,尽管我仍然无法投入大量时间,但我认为我可以找到一些时间快速学习并尝试多思考它们。大脑会自行发挥作用。
从神经网络到 GPT
2023.09.28
YouTube 视频
Andrej Karpathy - 让我们从零开始,用代码,逐字逐句地构建 GPT。
Umar Jamil - Attention is all you need(Transformer)- 模型解释(包括数学),推理和训练
StatQuest with Josh Starmer - Transformer 神经网络,ChatGPT 的基础,清晰解释!!!
Pascal Poupart - CS480/680 讲座 19: Attention 和 Transformer 网络
The A.I. Hacker - Michael Phi - Transformer 神经网络图解指南:一步一步的解释
我如何学习
当我读完《神经网络与深度学习》一书的一半时,我开始复现识别手写数字的神经网络示例。我在 GitHub 上创建了一个仓库,https://github.com/lzwjava/neural-networks-and-zhiwei-learning。
那才是真正困难的部分。如果一个人能从零开始编写代码,而不是复制任何代码,那说明他理解得非常好。
我的复现代码仍然缺少 update_mini_batch 和 backprop 的实现。然而,通过仔细观察加载数据、前向传播和评估阶段的变量,我对向量、维度、矩阵和对象的形状有了更好的理解。
我开始学习 GPT 和 transformer 的实现。通过 word embedding 和 positional encoding,文本变成了数字。然后,本质上,它与识别手写数字的简单神经网络没有区别。
Andrej Karpathy 的讲座“Let’s build GPT”非常好。他解释得很清楚。
第一个原因,它确实是从零开始的。我们首先看到如何生成文本。这有点模糊和随机。第二个原因,Andrej 能非常直观地表达事物。Andrej 花了几个月的时间做了 nanoGPT 项目。
我刚刚有了一个新的想法来判断讲座的质量。作者真的能写出这些代码吗?为什么我听不懂,作者遗漏了哪个主题?除了这些优雅的图表和动画,它们的缺点和缺陷是什么?
回到机器学习主题本身。正如 Andrej 所提到的,dropout,residual connection,Self-Attention,Multi-Head Attention,Masked Attention。
通过观看更多的上述视频,我开始有所理解。
通过使用 sin 和 cos 函数进行 positional encoding,我们得到了一些权重。通过 word embedding,我们将单词转换为数字。
\[PE_{(pos,2i)} = sin(pos/10000^{2i/d_{model}}) \\ PE_{(pos,2i+1)} = cos(pos/10000^{2i/d_{model}})\]披萨从烤箱里拿出来,味道很好。
在这个句子中,算法如何知道 “it” 指的是披萨还是烤箱?我们如何计算句子中每个词的相似性?
我们想要一组权重。如果我们使用 transformer 网络来做翻译任务,每次我们输入一个句子,它就能输出另一种语言的相应句子。
关于这里的点积。我们在这里使用点积的一个原因是点积会考虑向量中的每个数字。如果我们使用平方点积呢?我们首先计算数字的平方,然后让它们进行点积。如果我们做一些反向点积呢?
关于这里的掩码,我们将一半矩阵的数字改为负无穷。然后我们使用 softmax 使值范围从 0 到 1。如果我们改变左下角的数字为负无穷呢?
计划
继续阅读代码和论文,观看视频。只是享受和追随我的好奇心。
https://github.com/karpathy/nanoGPT
https://github.com/jadore801120/attention-is-all-you-need-pytorch
神经网络如何工作
2023.05.30
让我们直接讨论神经网络的核心,即反向传播算法:
- 输入 x:为输入层设置相应的激活量 \(a^{1}\)。
- 前向传播:对于每个 l=2,3,…,L,计算 \(z^{l} = w^l a^{l-1}+b^l\) 和 \(a^{l} = \sigma(z^{l})\)。
- 输出误差 \(\delta^{L}\):计算向量 \(\delta^{L} = \nabla_a C \odot \sigma'(z^L)\)。
- 反向传播误差:对于每个 l=L−1,L−2,…,2,计算 \(\delta^{l} = ((w^{l+1})^T \delta^{l+1}) \odot \sigma'(z^{l})\)。
- 输出:成本函数的梯度由 \(\frac{\partial C}{\partial w^l_{jk}} = a^{l-1}_k \delta^l_j\) 和 \(\frac{\partial C}{\partial b^l_j} = \delta^l_j\) 给出。
这是从 Michael Nelson 的书《神经网络与深度学习》中复制过来的。是不是很让人不知所措?第一次看到时可能会,但在一个月的学习之后就不会了。让我来解释一下。
输入
总共有 5 个阶段。第一个阶段是输入。这里我们使用手写数字作为输入。我们的任务是识别它们。一个手写数字有 784 像素,即 28*28。每个像素都有一个灰度值,范围从 0 到 255。所以,激活意味着我们使用某个函数来激活它,将其原始值改变为一个新值,以便于处理。
比方说,我们现在有 1000 张 784 像素的图片。我们现在训练它来识别它们显示的数字。我们现在有 100 张图片来测试学习效果。如果程序能识别 97 张图片的数字,我们说它的准确率是 97%。
所以我们将遍历这 1000 张图片,训练出权重和偏置。每次我们给它新的图片学习时,我们都会使权重和偏置更加正确。
一个批次训练结果将反映在 10 个神经元中。这里,这 10 个神经元代表从 0 到 9,其值范围从 0 到 1,以表示它们对其准确性的信心。
输入是 784 个神经元。我们如何将 784 个神经元减少到 10 个神经元?这就是关键。假设我们有两层。层是什么意思?就是第一层,我们有 784 个神经元。在第二层,我们有 10 个神经元。
我们给 784 个神经元中的每个神经元一个权重,比如:
\[w_1, w_2, w_3, w_4, ... , w_{784}\]并给第一层一个偏置,即 \(b_1\)。
对于第二层中的第一个神经元,它的值是:
\[w_1*a_1 + w_2*a_2+...+ w_{784}*a_{784}+b_1\]但是这些权重和一个偏置是针对 \(neuron^2_{1}\)(第二层中的第一个)。对于 \(neuron^2_{2}\),我们需要另一组权重和一个偏置。
sigmoid 函数呢?我们用 sigmoid 函数将上述值映射到 0 到 1 之间。
\[\begin{eqnarray} \sigma(z) \equiv \frac{1}{1+e^{-z}} \end{eqnarray}\] \[\begin{eqnarray} \frac{1}{1+\exp(-\sum_j w_j x_j-b)} \end{eqnarray}\]我们还使用 sigmoid 函数来激活第一层。也就是说,我们将灰度值改变为 0 到 1 的范围。所以现在,每一层中的每个神经元都有一个介于 0 到 1 之间的值。
所以现在对于我们的两层网络,第一层有 784 个神经元,第二层有 10 个神经元。我们训练它来获得权重和偏置。
我们有 784 * 10 个权重和 10 个偏置。在第二层中,对于每个神经元,我们将使用 784 个权重和 1 个偏置来计算其值。这里的代码如下:
def __init__(self, sizes):
self.num_layers = len(sizes)
self.sizes = sizes
self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
self.weights = [np.random.randn(y, x)
for x, y in zip(sizes[:-1], sizes[1:])]
前向传播
前向传播:对于每个 l=2,3,…,L,计算 \(z^{l} = w^l a^{l-1}+b^l\) 和 \(a^{l} = \sigma(z^{l})\)
请注意,这里我们使用上一层的值,即 \(a^{l-1}\) 和当前层的权重 \(w^l\) 及其偏置 \(b^l\) 进行 sigmoid 运算,以获取当前层的值 \(a^{l}\)。
代码:
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
# feedforward
activation = x
activations = [x]
zs = []
for b, w in zip(self.biases, self.weights):
z = np.dot(w, activation)+b
zs.append(z)
activation = sigmoid(z)
activations.append(activation)
输出误差
输出误差 \(\delta^{L}\):计算向量 \(\delta^{L} = \nabla_a C \odot \sigma'(z^L)\)
让我们看看 \(\nabla\) 是什么意思。
\[\begin{eqnarray} w_k & \rightarrow & w_k' = w_k-\eta \frac{\partial C}{\partial w_k} \\ b_l & \rightarrow & b_l' = b_l-\eta \frac{\partial C}{\partial b_l} \end{eqnarray}\]Del,或 nabla,是数学中(尤其是在向量微积分中)用作向量微分算子的运算符,通常用 nabla 符号 ∇ 表示。
这里 \(\eta\) 是学习率。我们使用 C 对权重和偏置的导数,即它们之间的变化率。这就是下面的 sigmoid_prime。
代码:
delta = self.cost_derivative(activations[-1], y) * \
sigmoid_prime(zs[-1])
nabla_b[-1] = delta
nabla_w[-1] = np.dot(delta, activations[-2].transpose())
def cost_derivative(self, output_activations, y):
return (output_activations-y)
反向传播误差
反向传播误差:对于每个 l=L−1,L−2,…,2,计算 \(\delta^{l} = ((w^{l+1})^T \delta^{l+1}) \odot \sigma'(z^{l})\)
for l in range(2, self.num_layers):
z = zs[-l]
sp = sigmoid_prime(z)
delta = np.dot(self.weights[-l+1].transpose(), delta) * sp
nabla_b[-l] = delta
nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
return (nabla_b, nabla_w)
输出
输出:成本函数的梯度由 \(\frac{\partial C}{\partial w^l_{jk}} = a^{l-1}_k \delta^l_j\) 和 \(\frac{\partial C}{\partial b^l_j} = \delta^l_j\) 给出。
def update_mini_batch(self, mini_batch, eta):
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
for x, y in mini_batch:
delta_nabla_b, delta_nabla_w = self.backprop(x, y)
nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
self.weights = [w-(eta/len(mini_batch))*nw
for w, nw in zip(self.weights, nabla_w)]
self.biases = [b-(eta/len(mini_batch))*nb
for b, nb in zip(self.biases, nabla_b)]
总结
这是一篇短文。大部分内容只是展示代码和数学公式。但这对我来说没问题。在写它之前,我并不完全理解。写完或者只是从代码和书中复制片段之后,我理解了大部分。在从老师王垠那里获得信心,阅读了《神经网络与深度学习》约 30% 的内容,听了 Andrej Karpathy 的斯坦福讲座和 Andrew Ng 的课程,和我的朋友 Qi 讨论,以及调整 Anaconda、numpy 和 Theano 库使多年前的代码能够工作之后,我现在理解了它。
关键点之一是维度。我们应该知道每个符号和变量的维度。它只是进行可微计算。让我们以王垠的话作结:
机器学习真的非常有用,甚至可以说它是一种美丽的理论,因为它只是微积分经过一番改造!它是牛顿、莱布尼茨古老而伟大的理论,以一种更简单、优雅和强大的形式呈现。机器学习基本上就是利用微积分来推导和拟合一些函数,而深度学习就是拟合更复杂的函数。
人工智能中没有什么“智能”,神经网络中没有什么“神经”,机器学习中没有什么“学习”,深度学习中没有什么“深度”。真正起作用的领域叫做“微积分”。所以我更喜欢称这个领域为“可微计算”,而构建模型的过程叫做“可微编程”。