从头搭建GPT(Andrej Karpathy) 笔记

本文通过Andrej Karpathy的教程,详细介绍了如何从头构建一个简单的GPT模型,即nanoGPT。讲解了数据处理、tokenize、计算loss、self-attention的直觉和实现,以及在训练过程中如何逐步改进模型,实现transformer的基本结构。nanoGPT仅涉及预训练步骤,使用莎士比亚的文章作为toy dataset,最终模型的loss有所降低,但生成的句子质量仍有待提升。
摘要由CSDN通过智能技术生成

本文来自openAI联合创始人,曾担任特斯拉视觉部门总监的Andrej的视频Let’s build GPT

当然这里的GPT并不是原版GPT,只是一个nanoGPT, 不过大体结构差不多,旨在帮助大家理解GPT的原理。
(nanoGPTgithub地址)
另外,这里只有pre-train步骤,并不涉及fine-tune.

简单介绍

chatGPT的核心是transformer。
它是一个supervised learning, 会根据语言序列预测下一个会出现的文字,不停地重复,就预测出了语句。
预测的是token level, 而不是word level.

这里会用transformer搭建一个类似gpt的模型,
gpt的训练数据来自网络,而且有pre-training, fine-tuning stage, 很复杂。
这里不整那么复杂,就用一个toy dataset训练,
toy dataset是一个包含莎士比亚文章的txt文件。

数据处理部分

下载toy数据

import requests
data_url = 'https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt'
with open('./input.txt', 'w', encoding='utf-8') as f:
    f.write(requests.get(data_url).text)

下完之后要把这个txt里面的string给tokenize.

什么是tokenize

什么是tokenize? 就是把string用a list of integer表示,
举个简单的例子,你可以按顺序unique地列出string中所有出现的字母。
那么每个字母就对应一个index, 你可以用index表示这个string, 如下:
一共65个unique的字母和符号,integer的取值范围在0~65.

在这里插入图片描述

当然上面只是一个简单的例子,实际tokenize的时候可以用google的sentense piece, openAI的tiktoken等. gpt用的是tiktoken.
这种tokenizer是sub word level, 既不是一个字母一个字母地换成integer, 也不是一整个word.
它用的词汇量非常大,所以和上面的相比,integer的list会短一些,但是数字的范围会更大。

不过这里为了浅显易懂,还是用这个简单的tokenizer。
可以看下前1000个字母tokenize之后的样子。
在这里插入图片描述

接下来是拆分train和test集。这里按90%和10%拆分。

在这里插入图片描述
现在拆分出来的训练集和测试集是很长的integer list. 不会一下子放进transformer(内存和计算expensive),
把它们拆成短句子,比如说长度为8的list.

label就是根据一个序列预测的下一个子母,这里用code来解释。
这里取长度为8+1.是为了在长度正好为8时能预测它的下一个字母(第9个字母)。
在这里插入图片描述

定义batch_size, 把短句子压成batch, 让gpu并行处理。
在这里插入图片描述

这里面x是输入的短句子, y是x中到每个字母为止的这段句子预测的下一个字母。
在这里插入图片描述

计算loss

先把这些放入最简单的网络,称为bigramLanguageModel.
这里是用了nn.Embedding得到了65 * 65的table,
token_embedding_table(idx) 就是从table中取出idx对应的那一行向量作为word embedding.
cross_entropy需要的输入shape是(N, C), channel要在第2个,所以做了reshape.
cross_entropy是如何计算的请参考cross entropy用法

在这里插入图片描述

可以看到算出的cross entropy为4.87,
而现有65个unique的字母,平均猜测的话cross entropy应该是-log(1/65), 约为4.17, 可见初始预测不是特别的平均。

上面已经可以evaluate一个model.
现在开始用model生成。

为了节省时间,就不贴那么详细的图了。

直觉上的self-attention

前面都是只考虑一个字母,根据一个字母推测下一个字母,而没有考虑历史信息,
如果把[: t+1]的embedding求平均或求sum, 就可以考虑到之前所有的信息。

有一个trick, 用下三角的ones矩阵 * 矩阵B, 可以得到矩阵C,C的每行是B对应的前面行求和。

在这里插入图片描述

上面求的是sum, 如果平均,只需要把a的每行除以对应一行的和。

在这里插入图片描述

这种求平均也可以用softmax实现,
把上三角的0位置置为-inf
在这里插入图片描述
然后用softmax就相当于每行的每个元素求平均。

每个weight元素都代表了一个token的weight, 也就是self-attention.

embedding size设为32.
前面的token_embedding_table不直接得到logits, 先得到token_embedding, 再过一个linear layer才是logits.
linear映射32的embedding to vocab_size(65)

position embedding
用nn.Embedding再建一个table作为position embedding
最后把word embedding和position embedding加起来。
这种方案在这个简单的模型里面不起作用,因为它只看一个位置,但是在transformer里面有用。

在这里插入图片描述

矩阵乘法实现self-attention:

每个token会产生2个vector, 一个query, 一个key.
query: 我要找谁?
key: 我有什么?
找两个token的关系就用query 点乘 key.

根据这一思想再写一遍weight, 就会看到每个token的weight不是平均的了。

在这里插入图片描述

前面都是直接用weight * input的x,
现在用x过linear产生value vector,
然后用weight * value.

在这里插入图片描述
和CNN相比:CNN是在空间层面操作的,而self-attention你可以理解为散在四处的一堆向量,这些向量之间的互相交流,
它们并没有space的概念,所以你想让它们有空间概念,就需要人为加进去,也就是position embedding.

各batch是独立的,token只在自己的batch里面交流。

目前为止每个token只和它之前的token交流,这并不是绝对的条件。
有时比如你做sentiment analyze, 就需要全句子中所有的token都有交流。
这种情况下就要用到encoder-decoder,
在encoder中,不限制token之后的token和它交流,在decoder中要限制(因为要预测下一个)。

self-attention和cross-attention有什么区别

self-attention中query, key, value都是来源于同一个源的数据,
而cross-attention有可能query来源于一组数据,而key, value来源于其他数据。

attention中为什么要做scaling

看一下attention is all you need的paper中attention的部分:

在这里插入图片描述
前面已经实现了softmax(QKT)V,
但是这里为什么要除一个 d k \sqrt{d_{k}} dk 呢。

这里举了例子:
如果你的输入是0-1高斯分布。k,q也是0,1高斯,那么weight的方差就会是head_size的倍数。
在这里插入图片描述

如果你把(QKT)除以 d k \sqrt{d_{k}} dk , weight的方差就会变为1.

在这里插入图片描述

为什么不希望方差太大?
因为如果方差比较大,分布不是那么平均的话,经过softmax之后,数值会类似于one-hot vector, 在初始vector最大的数值处值很大,其他地方值很小。这里也举了个例子。
你可以看到第2个例子经过扩大之后分布不是那么平均,过softmax之后初始最后一个最大值处会变成类似one-hot。

在这里插入图片描述

来个完整版的self-attention.

在这里插入图片描述

不过用了self-attention之后,和不用之前相比loss只提高了0.1, 还需要在其他地方下功夫。

继续增加改进的点, 逐步实现transformer

根据transformer结构加入现在没有的模块,
可以看到模块之间有residual.
用了multihead, head后面有Norm layer, 还有feed forward layer.
左边encoder的输入进入右边decoder, 有cross-attention.

在这里插入图片描述

逐步加上模块验证loss:

加上Multihead之后提高到2.28,
multihead类似于group conv, 先分组再结合。
(仅用了几行就实现了multi-head…)

在这里插入图片描述

又加上feedforward, 提高到2.24, 但是生成出来的句子效果仍然不好。

于是再从paper中找idea, 认为网络太深的话backpropagate容易出问题,
这时需要residual block, 而paper中的结构也用到了residual.

再加上projection.
根据Paper,feedforward的channel应该是 x4的,作修改,然后再映射回原来的channel。
在这里插入图片描述

现在loss降到2.08, 但是生成的句子只有一小部分像英语。

接下来做第2个innovation.
图就不贴了,有兴趣的可以看下视频。或者可以看nanoGPT的model.py。

paper中的结构有 layer norm. 注意不是batch norm. 而是layer norm.
batch norm是对列归一化,把batch norm的代码copy 下来,把对列的归一化改为对行,就实现了layer norm…

这几年transformer的结构有一些进化,paper中是在Head之后做了ADD&Norm,
现在是在进入Head前做layer norm, 叫作pre-norm.

加入layer norm后loss降到了2.06.

在feedforward的后面加了dropout.
dropout可以加在residual connection前面,就在相加运算的前面。
multi-head的softmax后面也可以加上dropout.

顺便讲一下dropout,
它是指在训练时,forward和backward中随机把一些neural置0,
而且每次丢掉的都不一样,
这样就像你在训练时训练的时不同sub-network的集合。
在eval时,所有的神经元都在使用状态,就相当于把这些sub-network组合起来,功能更强大。
不过,目前你只需要知道它是一个regularization的手段。
在这里插入图片描述

修改超参,block size, embedding size加大,因为网络加大的关系,learning rate进一步下调。
head个数增加。
loss降到了1.48.

这个简易gpt只用到了transformer paper中的decoder部分,真实的GPT也是只用了decoder,
为什么没有用paper中的encoder和cross attention呢?

因为paper中是机器翻译,在你预测下一个字母时,还要受到原语言的条件限制,
比如从法语翻译到英语。
encoder部分读入法语,不需要做mask, 也就是不需要一个字母只能受过去时间的字母提示,所有字母都参与weight.
decoder部分需要mask. 只能过去的字母有weight.

encoder输出的部分通过cross attention进入decoder.

而这个简易GPT, 输入就只有shakespear, 并没有其他的条件限制,所以只需要用decoder.

真实的GPT训练步骤

pre-training: decoder only
用网络上大量的文章训练,规模如下。
这时训练的GPT并不能回答问题,只是会输出像网络上那样的文章,新闻之类。
你问它问题,它可能会回你更多的问题,或者忽略你的问题,自己继续输出文章。

在这里插入图片描述

从pre-trained model到GPT还需要如下3步:

在这里插入图片描述

  1. 收集数据,这时的数据就是问答形式的数据,一个问题接一个回答。数据不会像网络数据那样多。
  2. GPT给出的若干个回答,由labeler进行打分,从高到低,根据这个打分再训练一个reward model.
  3. 用PPO强化学习算法优化采样的policy, 根据reward model给出的分数。这样就能从GPT给出的若干答案出采样出最优答案。

nanoGPT只是pre-trained阶段。

如果你想要实现其他任务,也就是说不是一个只能产生文件的模型,那么你就需要进行fine-tuning.
fine-tuning可以是一个简单的监督学习,也可以是GPT那样的fancy work.

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝羽飞鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值