60行NumPy手搓GPT( 二 )


有一个vocab即词汇表 , 可以将字符串token映射到整数索引 。
有一个方法 , 即编码方法 , 可以实现str -> list[int]的转化 。
有一个方法 , 即解码方法 , 可以实现list[int] -> str的转化
输出
输出是一个二维数组 , 其中[i][j]表示模型的预测概率 , 这个概率代表了词汇表中位于vocab[j]的token是下一个[i+1]的概率 。比如:

vocab = ["all", "not", "heroes", "the", "wear", ".", "capes"]inputs = [1, 0, 2, 4] # "not" "all" "heroes" "wear"output = gpt(inputs)#["all", "not", "heroes", "the", "wear", ".", "capes"]# output[0] =[0.750.10.00.150.00.00.0]# 在"not"给出的情况下 , 我们可以看到 , (对于下一个token)模型预测"all"具有最高的概率#["all", "not", "heroes", "the", "wear", ".", "capes"]# output[1] =[0.00.00.80.10.00.00.1]# 在序列["not", "all"]给出的情况下 , (对于下一个token)模型预测"heroes"具有最高的概率#["all", "not", "heroes", "the", "wear", ".", "capes"]# output[-1] = [0.00.00.00.10.00.050.85]# 在整个序列["not", "all", "heroes", "wear"]给出的情况下 , (对于下一个token)模型预测"capes"具有最高的概率
为了针对整个序列获得下一个token预测 ,  我们可以简单的选择[-1]中概率最大的那个token:

【60行NumPy手搓GPT】vocab = ["all", "not", "heroes", "the", "wear", ".", "capes"]inputs = [1, 0, 2, 4] # "not" "all" "heroes" "wear"output = gpt(inputs)next_token_id = np.argmax(output[-1]) # next_token_id = 6next_token = vocab[next_token_id] # next_token = "capes"
将具有最高概率的token作为我们的预测 , 叫做 [21]或者 (贪心采样) 。
在一个序列中预测下一个逻辑词( word)的任务被称之为语言建模 。因此我们可以称GPT为语言模型 。
生成一个单词是挺酷的(但也就那样了) , 但是要是生成整个句子、整篇文章呢?
生成文本 自回归
我们可以迭代地通过模型获取下一个token的预测 , 从而生成整个句子 。在每次迭代中 , 我们将预测的token再添加回输入中去:

def generate(inputs, n_tokens_to_generate):for _ in range(n_tokens_to_generate): # 自回归的解码循环output = gpt(inputs) # 模型前向传递next_id = np.argmax(output[-1]) # 贪心采样inputs.append(int(next_id)) # 将预测添加回输入return inputs[len(inputs) - n_tokens_to_generate :]# 只返回生成的idsinput_ids = [1, 0] # "not" "all"output_ids = generate(input_ids, 3) # output_ids = [2, 4, 6]output_tokens = [vocab[i] for i in output_ids] # "heroes" "wear" "capes"
这个过程是在预测未来的值(回归) , 并且将预测的值添加回输入中去(auto) , 这就是为什么你会看到GPT被描述为自回归模型 。
采样
我们可以通过对概率分布进行采样来替代贪心采样 , 从而为我们的生成引入一些随机性():

inputs = [1, 0, 2, 4] # "not" "all" "heroes" "wear"output = gpt(inputs)np.random.choice(np.arange(vocab_size), p=output[-1]) # capesnp.random.choice(np.arange(vocab_size), p=output[-1]) # hatsnp.random.choice(np.arange(vocab_size), p=output[-1]) # capesnp.random.choice(np.arange(vocab_size), p=output[-1]) # capesnp.random.choice(np.arange(vocab_size), p=output[-1]) # pants
这样子 , 我们就可以基于同一个输入产生不同的输出句子啦 。当我们结合更多的比如top-k[22] , top-p[23]和温度[24]这样的技巧的时候 , (这些技巧能够能更改采样的分布) , 我们输出的质量也会有很大的提高 。这些技巧也引入了一些超参数 , 通过调整这些超参 , 我们可以获得不同的生成表现() 。比如提高温度超参 , 我们的模型就会更加冒进 , 从而变得更有“创造力” 。