这个不多说 , 网上一大堆。
GPT 的论文 (Improving Language Understanding by Generative Pre-Training) 关于 postional encoding 的内容如下 :
We used learned position embeddings instead of the sinusoidal version proposed in the original work.
也就是说 ,GPT 用的是随机位置编码 , 而不是原 transformer 论文中用的正弦编码。
摘抄 GPT2 的 github 代码如下 , 可以看出构建模型时 , 位置编码是直接创建了一个标准差为 0.01 的随机矩阵。
if params["precision"] == "bfloat16": wpe = tf.get_variable('wpe', [params["n_ctx"], params["n_embd"]], initializer=tf.random_normal_initializer(stddev=0.01, dtype=tf.bfloat16), dtype=tf.bfloat16) wte = tf.get_variable('wte', [params["n_vocab"], params["n_embd"]], initializer=tf.random_normal_initializer(stddev=0.02, dtype=tf.bfloat16), dtype=tf.bfloat16) else: wpe = tf.get_variable('wpe', [params["n_ctx"], params["n_embd"]], initializer=tf.random_normal_initializer(stddev=0.01)) wte = tf.get_variable('wte', [params["n_vocab"], params["n_embd"]], initializer=tf.random_normal_initializer(stddev=0.02)) past_length = 0 if past is None else tf.shape(past)[-2] wpe = dropout(wpe, params["embed_dropout"], train) wte = dropout(wte, params["embed_dropout"], train)
GPT 是一种大规模语言预训练模型 , 并且发展到 GPT3 时 , 发现了 NLP 预训练和训练的两个最关键技术 , 一个是 in context learning; 一个是 chain of thought。
下面针对这两个分别进行分析 , 以及解析训练时如何体现这两点的。
下面讲一下 in context learning 的训练方式
in context learning 是一种大规模预训练方式。也即给定输入序列 START I1 I2 … In, 让 GPT 去预测输出 I1 I2 I3,…,In,END。也即是 START token 位的输出结果是 I1,I1 token 为额输出结果 I2, 依次类推。
GPT 巧妙的是这种 in context learning 完全可以进行 token 级别的并行 ! 一般人想的是 , 先输入 START I1, 去预测 I2, 然后反向传播训练 ; 然后输入 START I1 I2, 去预测 I3, 然后反向传播训练等等。这种训练方式类似 RNN, 实在太低效了 , 训练时间和 seq 的长度线性相关。但是 GPT 用了 casual mask self attention,I1 不会用到 I2 I3 的信息 , 因此可以直接输入 START I1 I2 … In, 然后预测 I1 I2 … IN END。因为训练的时候每个 token 都没有用到后面的 token, 因此不会影响解码。
最后讲一下 chain of thought 的训练方式
很显然 chain of thought 并不是用于大规模预训练的 , 他是一种针对特定任务的标注方式。也即是给每个任务标注出推理过程和结果。然后对 GPT 给定任务输入 , 去预测推理过程和结果 , 最终返回结果。因此需要构造一个数据集 , 然后去微调。
根据之前的推导发现 , 生成第 n +1 个 token 时 , 完全不需要对前 n - 1 个 token 再次运行推理 , 因为前 n - 1 个 token 完全没有用到第 n 个 token 的信息。因此只需要对第 n 个 token 进行推理 , 与前 n - 1 个 token 的中间结果进行 cross attention。因此推理速度非常快 , 但是需要保存所有的中间激活结果。查看了 hugging face 的 GPT2 实现 , 是否使用历史 token 的中间结果 , 由变量 use_cache 决定。也即是实现中确实利用了这个来进行推理加速。训练时则设置 use_cache 为 false。
其实从这个角度来看 GPT 其实就是 RNN, 只不过 GPT 的状态量是之前的所有计算结果 , 而 RNN 会把之前的所有状态量压缩到一个固定 size 的状态量 , 且因为这个压缩过程 , 导致 RNN 无法并行训练。
理论上来说 ,GPT 对 casual mask 限制得太死了。在生成第 n +1 个 token 时 , 第 0 个 token 明显可以看到且用到第 1 到 n 个 token 的信息。但是 GPT 限制死了即便这种情况第 0 个 token 也不能用到第 1 到 n 个 token 的信息。好处在于 , 训练的时候 ,n 个 token 可以完全并行训练 ; 推理的时候 , 生成第 n +1 个 token 时 , 前 n 个 token 没必要再次计算。
最近看了不少 efficient transformer 的论文。也自己尝试推导了一下一些线性的 attention 公式。虽然推导到最后发现 , 得到的公式是一种低秩方法。而且从自己的推导过程中发现 , 所有的低秩方法很可能都是将 QUERY 或者某次 QUERY 到的所有 KEY 固定到一个常数个数。这是个人猜测 , 但很可能有个证明方法证明确实是如此。
总而言之在这个过程中 , 发现很有可能所有降低 transformer 复杂度的尝试都是徒劳。这涉及到计算复杂度 , 而且个人认为将来学术界用可计算理论 , 计算复杂度理论和形式语言理论研究 transformer 和 GPT 必定成为潮流。下面以 GPT 为例说明为什么这样的操作是徒劳的。注意下面的都是个人思考 , 不保证正确。
很有可能证明如下的定理 :
这个停机判断并非是 GPT 解码的停机判断 , 单纯是针对输入编码的 GPT 网络停机判断。
一种可以初步证明这个推断的问题是 :
给定输入 x 1 , x 2 , . . . , x n x_1,x_2,…,x_n x1,x2,…,xn, 判断 所有 ∣ x j − x i ∣ |x_j – x_i| ∣xj−xi∣ 是否小于某一常数 α \alpha α, 如果是 , 输出 1 , 否则输出 0。
很显然 , 如果 GPT 不给出额外的推断 token, 那么网络的复杂度必为 O (n 2) O(n^2) O(n2)。
如果 GPT 可以给出额外的推断 token, 那么网络的复杂度可以小于 O (n 2) O(n^2) O(n2), 但此时推断步长复杂度必定为 O (n 2) O(n^2) O(n2)。
原文链接:https://blog.csdn.net/LYF1993/article/details/131417170