大模型原理:生成文本原理、演示

介绍

本文编写代码,将GPT模型的 输出张量转换成文本,即:生成文本。

如何逐词(词元)生成文本

下图展示了GPT模型如何根据输入上下文(比如 “Hello, I am”)逐步生成文本的过程:

大语言模型逐步生成文本的过程,每次生成一个词元。从初始输入上下文( “Hello, I am” )开始,模型在每轮迭代中预测下一个词元,并将其添加到输入上下文中以进行下一轮预测。

如图所示:

  1. 第一轮迭代添加了 “a”
  2. 第二轮迭代添加了 “model”
  3. 第三轮迭代添加了 “ready”
  4. 逐步形成了完整的句子

在第6次迭代时,模型生成了完整的句子“Hello, I am a model ready to help.” 已经看到当前的GPTModel实现输出的张量形状为 [batch_size, num_token, vocab_size]

如何将输出张量转化为文本

GPT模型将 输出张量 转化为 生成文本 的过程涉及多个步骤,如图所示。这些步骤包括解码输出张量、根据概率分布选择词元,以及将这些词元转换为人类可读的文本。

上图展示如何在给定输入的情况下,生成下一个词元。

  1. 在每一步中,模型输出一个矩阵,其中的向量表示有可能的下一个词元。
  2. 将与下一个词元对应的向量提取出来,并通过 softmax 函数 转换为概率分布。在包含这些概率分数的向量中,找到最高值的索引,这个索引对应于词元ID。
  3. 然后将这个 词元ID 解码为 文本,生成序列中的下一个词元。
  4. 最后,将这个词元 添加 到之前的输入中,形成新的输入序列,供下一次迭代使用。

这个逐步的过程使得模型能够按顺序生成文本,从最初的输入上下文中构建连贯的短语和句子。

在实际操作中,会多次重复这一过程(参见第1张图),直到生成预定数量的词元。

实现

为模型生成循环的简单实现:

  1. 首先循环指定次数以生成新词元
  2. 然后将当前上下文裁剪到模型的最大上下文大小
  3. 接下来进行预测计算,并根据最高概率选择下一个词元。

使用 softmax 函数将 logits 转换为 概率分布,并通过 torch.argmax 确定 最大值的位置。

softmax 函数是单调的(对每个输入值来说,输入的值越大,输出的值也越大,反之越小),因此,实际上 softmax 步骤是冗余的,因为 softmax 输出张量中最高分的位置与 logits张量 中的位置是相同的。换句话说,可以直接对 logits张量 应用 torch.argmax 函数,得到相同的结果。

不过,本例提供了转换代码来展示将 logits转换为概率 的完整过程,这有助于更好地理解模型如何生成最有可能的下一个词元,这一过程被称为 贪心解码。

import torch
import torch.nn as nn


def generate_text_simple(model, idx, max_new_tokens, context_size):
    """
    生成文本
    :param model:
    :param idx:当前文本的索引数组,其形状为(batch,n_tokens)
    :param max_new_tokens:
    :param context_size:
    :return:
    """
    for _ in range(max_new_tokens):
        # 将当前文本截断至支持的长度。如果大语言模型仅支持5个词元,但此时文本长度为10,则只有最后5个词元会被用作输入文本
        idx_cond = idx[:, -context_size:]
        with torch.no_grad():
            logits = model(idx_cond)

        # 只关注最后一个输出的内容,因此形状会从(batch,n_token,vocab_size)变为(batch,vocab_size)
        logits = logits[:, -1, :]
        # probas 的形状为(batch,vocab_size)
        probas = torch.softmax(logits, dim=-1)
        # idx_next 的形状为(batch,1)
        idx_next = torch.argmax(probas, dim=-1, keepdim=True)
        # 将计算出的下一个字符的索引添加到索引数组中,此时 idx的形状会变为(batch,n_tokens+1)
        idx = torch.cat((idx, idx_next), dim=1)
    return idx

函数执行过程

下图展示了 generate_text_simple() 函数逐步生成一个 词元ID,并将其 附加到上下文中的过程。(每次迭代的词元ID生成过程已在上图中详细描述)通过迭代的方式生成了词元ID。

例如:在第一轮迭代中,模型获取了与“Hello, I am”对应的词元,以此预测下一个词元( “a”,ID为 257),并将其添加到输入中。
这个过程会重复进行,直到模型经过6轮迭代生成完整的句子

“Hello, I am a model ready to help.”

执行

使用 "Hello, I am" 上下文作为模型输入来调用 generate_text_simple() 函数

import tiktoken
tokenizer = tiktoken.get_encoding("gpt2")
start_context = "Hello, I am"
encoded = tokenizer.encode(start_context)
print("编码:", encoded)
# 在行上增加一个维度,即:一行4列
encoded_tensor = torch.tensor(encoded).unsqueeze(0)
print("增加维度后的shape:", encoded_tensor.shape)
print("增加维度后的张量:", encoded_tensor)

执行结果:

编码: [15496, 11, 314, 716]
增加维度后的shape: torch.Size([1, 4])
增加维度后的张量: tensor([[15496,    11,   314,   716]])

启用 .eval() 模式

将模型设置为 .eval() 模式,这将禁用诸如 dropout 等只在训练期间使用的随机组件。然后对编码后的输入张量使用 generate_text_simple() 函数:

准备代码

从 大模型原理:全部代码 复制以下类或函数:

  • MultiHeadAttention
  • GELU
  • FeedForward
  • LayerNorm
  • TransformerBlock
  • GPTModel
  • GPT_CONFIG_124M

启用 .eval() 模式

# 启用 eval() 模式,禁用诸如dropout等只在训练期间使用的随机组件
torch.manual_seed(123)
model = GPTModel(GPT_CONFIG_124M)

model.eval()
out = generate_text_simple(
    model=model,
    idx=encoded_tensor,
    max_new_tokens=6,
    context_size=GPT_CONFIG_124M["context_length"]
)
print("Output:", out)
print("Output 1ength:", len(out[0]))

执行结果:

Output: tensor([[15496,    11,   314,   716, 27018, 24086, 47843, 30961, 42348,  7267]])
Output 1ength: 10

将ID转换回文本

使用分词器的.decode方法可以将ID转换回文本

# 使用分词器的.decode方法可以将ID转换回文本
decoded_text = tokenizer.decode(out.squeeze(0).tolist())
print(decoded_text)

执行结果如下:

Hello, I am Featureiman Byeswickattribute argue

可以看到,模型生成了无意义的内容,完全不像连贯的文本

“Hello, I am a model ready to help.”

模型不能生成连贯文本的原因是:还没有对其进行训练。到目前为止,只是实现了GPT架构,并用初始随机权重初始化了GPT模型实例


原文出处:/show_1GW2esSDq8xF.html