>> for name, _ in tf.train.list_variables(tf_ckpt_path):>>>arr = tf.train.load。60行NumPy手搓GPT( 六 )。" />

60行NumPy手搓GPT( 六 )


这些是从原始的加载的:

>>> import tensorflow as tf>>> tf_ckpt_path = tf.train.latest_checkpoint("models/124M")>>> for name, _ in tf.train.list_variables(tf_ckpt_path):>>>arr = tf.train.load_variable(tf_ckpt_path, name).squeeze()>>>print(f"{name}: {arr.shape}")model/h0/attn/c_attn/b: (2304,)model/h0/attn/c_attn/w: (768, 2304)model/h0/attn/c_proj/b: (768,)model/h0/attn/c_proj/w: (768, 768)model/h0/ln_1/b: (768,)model/h0/ln_1/g: (768,)model/h0/ln_2/b: (768,)model/h0/ln_2/g: (768,)model/h0/mlp/c_fc/b: (3072,)model/h0/mlp/c_fc/w: (768, 3072)model/h0/mlp/c_proj/b: (768,)model/h0/mlp/c_proj/w: (3072, 768)model/h1/attn/c_attn/b: (2304,)model/h1/attn/c_attn/w: (768, 2304)...model/h9/mlp/c_proj/b: (768,)model/h9/mlp/c_proj/w: (3072, 768)model/ln_f/b: (768,)model/ln_f/g: (768,)model/wpe: (1024, 768)model/wte: (50257, 768)
下述代码[39]将上面的变量转换为字典 。
为了对比 , 这里显示了的形状 , 但是数字被替代:

60行NumPy手搓GPT

文章插图

{"wpe": [n_ctx, n_embd],"wte": [n_vocab, n_embd],"ln_f": {"b": [n_embd], "g": [n_embd]},"blocks": [{"attn": {"c_attn": {"b": [3*n_embd], "w": [n_embd, 3*n_embd]},"c_proj": {"b": [n_embd], "w": [n_embd, n_embd]},},"ln_1": {"b": [n_embd], "g": [n_embd]},"ln_2": {"b": [n_embd], "g": [n_embd]},"mlp": {"c_fc": {"b": [4*n_embd], "w": [n_embd, 4*n_embd]},"c_proj": {"b": [n_embd], "w": [4*n_embd, n_embd]},},},... # repeat for n_layers]}
在实现GPT的过程中 , 你可能会需要参考这个字典来确认权重的形状 。为了一致性 , 我们将会使代码中的变量名和字典的键值保持对齐 。
基础层
在进入实际GPT架构前的最后一件事 , 让我们来手搓几个基础的神经网络层吧 , 这些基础层可不只是针对GPT的 , 它们在各种情况下都很有用 。
GELU
GPT-2的非线性(激活函数)选择是GELU(高斯误差线性单元)[40] , 这是一种类似ReLU的激活函数:
来自GELU论文的图1
它的函数函数如下:

def gelu(x):return 0.5 * x * (1 + np.tanh(np.sqrt(2 / np.pi) * (x + 0.044715 * x**3)))
和ReLU类似 , GELU也对输入进行逐元素操作:

>>> gelu(np.array([[1, 2], [-2, 0.5]]))array([[ 0.84119,1.9546 ],[-0.0454 ,0.34571]])
下面是最经典的[41]:

def softmax(x):exp_x = np.exp(x - np.max(x, axis=-1, keepdims=True))return exp_x / np.sum(exp_x, axis=-1, keepdims=True)
这里我们使用了`max(x)`技巧[42]来保持数值稳定性 。
用来将一组实数(至之间)转换为概率(至之间 , 其求和为1) 。我们将作用于输入的最末轴上 。

>>> x = softmax(np.array([[2, 100], [-5, 0]]))>>> xarray([[0.00034, 0.99966],[0.26894, 0.73106]])>>> x.sum(axis=-1)array([1., 1.])
层归一化
层归一化[43]将数值标准化为均值为0方差为1的值:
其中是的均值 , 为的方差 , 和为可学习的参数 。

def layer_norm(x, g, b, eps: float = 1e-5):mean = np.mean(x, axis=-1, keepdims=True)variance = np.var(x, axis=-1, keepdims=True)x = (x - mean) / np.sqrt(variance + eps)# normalize x to have mean=0 and var=1 over last axisreturn g * x + b# scale and offset with gamma/beta params
层归一化确保每层的输入总是在一个一致的范围里 , 而这将为训练过程的加速和稳定提供支持 。与批归一化[44]类似 , 归一化之后的输出通过两个可学习参数和进行缩放和偏移 。分母中的小项用来避免计算中的分母为零错误 。