GPT1:Improving Language Understanding by Generative Pre-Training

发布时间:2024年01月06日

目录

一、背景

二、卖点与创新

三、几个问题?

四、具体是如何做的

1、预训练

2、微调

五、GPT1预训练的简易训练代码

六、GPT与BERT

七、一些资料


一、背景

  • 自然语言理解的挑战:在自然语言处理(NLP)领域,存在许多不同的任务,但可用的标记数据相对较少。

  • 预训练模型的价值:为了解决标记数据稀缺的问题,可以采用在没有标注的数据上进行预训练,然后在有标记数据上进行微调的方法。

  • 计算机视觉的启示:这种预训练加微调的方法在计算机视觉领域已经成为一种主流的做法,尤其是依托于大型标记数据集(如ImageNet)的成功案例,但在自然语言处理中这种方法却一直未能广泛采用。

  • 句子级别的数据需要:由于单个句子包含的信息量通常少于一张图片,因此,为了有效地训练大型的NLP模型,需要更大的句子级别标准数据集。

二、卖点与创新

  • 迁移学习架构: GPT-1采用了预训练和微调(pre-train then fine-tune)的范式,这在NLP中是比较新颖的。先在大规模的未标注文本数据上进行预训练,以学习语言的通用特征,然后在特定任务上进行微调以适应任务需求。

  • 大规模语料库预训练: 使用了大量的未标注数据进行无监督预训练,这使得模型能够学习到丰富的语言表示,从而在后续的任务中即使只有少量标注数据也能取得良好的性能。

  • 自回归特性: GPT-1作为一个自回归语言模型,通过逐一预测下一个单词的方式生成文本,允许模型利用已有的上下文信息来预测序列。

  • Transformer模型的创新应用: GPT-1是基于Transformer解码器架构开发的,充分利用了自注意力机制来处理长距离依赖问题,这在以往的循环神经网络(RNN)或长短期记忆网络(LSTM)模型中是一个难点。

  • 任务泛化能力: 通过对同一模型架构在不同NLP任务上进行微调,GPT-1显示出了良好的泛化能力,这表明了一个统一模型可以处理多种任务的可能性。同时,?由于使用相同的预训练模型进行微调,GPT-1减少了为每个NLP任务定制特定模型的需要,简化了模型的构建和训练流程。

三、几个问题?

  • 如何更好的利用无监督的文本?
  • 优化目标是什么,损失函数应该是什么样子?
  • 如何有效地将所学的文本表示传递到现有的子任务上?

四、具体是如何做的

????????GPT-1(Generative Pre-trained Transformer 1)的训练过程分为两个阶段:预训练(pre-training)和微调(fine-tuning)。

1、预训练

????????GPT-1的预训练目标是自回归语言模型,即通过最大化给定前面词(上下文)的条件下,下一个词出现的概率。具体来说,对于一个词的序列x1, x2, ..., xn,目标是最大化序列概率P(x1, x2, ..., xn),这通常通过逐词最大化条件概率P(xi | x1, ..., xi-1)来实现。

????????模型使用了Transformer的解码器,transformer包含编码器和解码器两个部分。编码器和解码器最大的不同在于,编码器输入一个序列时,当提取第i个元素的特征时,可以看到整个序列里的所有元素。而对于解码器来说,由于存在掩码,当提取第I个元素的特征时,它只能看到当前元素和它之前的元素,后面的元素被掩码处理,注意力机制计算时会被视为0,所以解码器无法看到后面的内容。因此,我们只能使用Transformer的解码器,而不能使用编码器。)

  1. 输入序列准备: 假设正在预测单词U的概率,我们首先需要确定一个固定的窗口大小K,这个窗口将决定序列中每个单词的上下文大小。我们提取单词U前的K个单词,形成序列[word1, word2, ..., wordK]

  2. 词嵌入: 将每个单词映射到一个高维空间,以获取单词的词嵌入(word embeddings)。

  3. 位置编码: 由于Transformer模型不像传统的序列模型(如RNN)具有内在的顺序感知能力,因此我们需要添加位置编码(positional encodings),以确保模型能够理解单词的顺序。位置编码通常以相同的维度作为词嵌入,并与之相加来提供顺序信息。

  4. Transformer层: 输入序列(带有词嵌入和位置编码)被送入一系列的Transformer层。每个Transformer层由自注意力机制(self-attention)和前馈神经网络(feed-forward neural network)组成。模型中可能有多个这样的层(N层),每一层都会接受上一层的输出作为输入,并产生新的输出。

  5. 自注意力机制: 在自注意力部分,模型计算序列中每个单词与其他单词之间的关系,这允许模型捕捉长距离依赖关系。多头自注意力(multi-head attention)允许模型在多个不同的表示子空间中并行学习信息。

  6. 层归一化和残差连接: 每个子层(自注意力和前馈网络)的输出都会经过层归一化(layer normalization),并且通过残差连接与输入相加。残差连接帮助缓解深层网络中的梯度消失问题。

  7. 输出投影: 经过N层Transformer处理后,我们得到最后一层的输出。这个输出将被投影到词汇表的大小,以便预测每个单词的分数。

  8. Softmax层: 投影后的分数通过Softmax层转换为概率分布。Softmax层会对每个单词生成一个概率,表示给定上下文后,下一个单词是该单词的概率。

2、微调

? ? ? ? 微调是针对下游各个子任务的,用少量特定任务的有监督数据进行微调。微调时需要对任务进行适配,gpt1主要对模型的输入和输出进行微小的调整,以适应特定任务的格式。例如,对于分类任务,通常在模型的最终输出层添加一个线性层,以预测每个类别的概率。

具体的

  • 分类(classification):例如对一段文本进行情感分类,判断其是正面还是负面。输入是一个序列(在原始文本前添加一个起始标记,后面添加一个结束标记),label是对应的监督信息(正面或者负面)。在微调中,需要重新构造一个新的线性层,其中权重可能是随机初始化的,并且维度大小与标签数量相同。通过这个线性层,可以将输入序列转换为需要的标签。
  • 蕴含(entailment):即判断一段文本是否蕴含某种假设。输入上,需要将两段文本串联成一个长序列,采用特殊的开始符、分隔符和抽取符来区分不同部分。
  • 相似”(similarity):即判断两段文本的相似程度。由于需要考虑相似关系的对称性。因此,对于每一对文本,需要构造两个序列,分别将它们放在不同的位置上,并用特殊标记区分不同部分。这些序列将分别输入模型中进行处理,得到最终的输出结果,然后通过一个线性层进行判断,是相似还是不相似。
  • 多选题(multiple choice):即从多个答案中选择正确的答案。该应用需要构造多个序列,其中每个序列的开头都是同一个问题,而每个答案则依次作为第二个序列进行处理。这些序列将分别输入模型中进行处理,最终通过一个线性投影层输出对应的答案得分,用softmax函数将得分转换为对每个答案的选择概率。

五、GPT1预训练的简易训练代码

import torch
import torch.nn as nn
from torch.nn import TransformerDecoder, TransformerDecoderLayer
from torch.utils.data import DataLoader, Dataset

class GPTDataset(Dataset):
    def __init__(self, corpus, vocab_size, max_seq_length):
        tokenized_corpus = self.tokenize_corpus(corpus, vocab_size)
        self.data = self.create_sequences(tokenized_corpus, max_seq_length)

    def tokenize_corpus(self, corpus, vocab_size):
        # 简单的分词和编码,实际应用中应使用更复杂的分词方法
        # 这里将每个字符当作一个token
        token_to_id = {tok: i for i, tok in enumerate(set(corpus))}
        return [token_to_id[tok] for tok in corpus if tok in token_to_id]

    def create_sequences(self, tokenized_corpus, max_seq_length):
        sequences = []
        for i in range(len(tokenized_corpus) - max_seq_length):
            sequences.append(tokenized_corpus[i:i+max_seq_length+1])
        return sequences

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        # 直接返回输入序列和目标序列(目标序列是输入序列向右移动一位)
        return torch.tensor(self.data[idx][:-1]), torch.tensor(self.data[idx][1:])

class GPTModel(nn.Module):
    def __init__(self, vocab_size, max_seq_length, nhead, nhid, nlayers):
        super(GPTModel, self).__init__()
        self.model_type = 'Transformer'
        self.pos_encoder = nn.Embedding(max_seq_length, nhid)
        self.embedder = nn.Embedding(vocab_size, nhid)
        self.transformer_decoder = TransformerDecoder(
            TransformerDecoderLayer(d_model=nhid, nhead=nhead),
            num_layers=nlayers
        )
        self.out = nn.Linear(nhid, vocab_size)

    def forward(self, src, src_mask):
        src = self.embedder(src) * math.sqrt(self.nhid)
        src_positions = torch.arange(0, src.size(1)).unsqueeze(0)
        src = src + self.pos_encoder(src_positions)
        output = self.transformer_decoder(src, src_mask)
        output = self.out(output)
        return output

def train(model, data_loader, optimizer, criterion, epochs):
    model.train()
    for epoch in range(epochs):
        for i, (src, tgt) in enumerate(data_loader):
            src_mask = model.generate_square_subsequent_mask(src.size(1))
            optimizer.zero_grad()
            output = model(src, src_mask)
            loss = criterion(output.view(-1, model.vocab_size), tgt.view(-1))
            loss.backward()
            optimizer.step()

            if i % 10 == 0:
                print(f"Epoch [{epoch + 1}/{epochs}], Loss: {loss.item():.4f}")

def main():
    # 超参数设置
    vocab_size = 256  # 假设有256个不同的token
    max_seq_length = 30
    nhead = 8
    nhid = 512
    nlayers = 4
    batch_size = 32
    epochs = 5

    # 假设的语料数据
    corpus = "Some large text corpus for training..."

    # 准备数据集和数据加载器
    dataset = GPTDataset(corpus, vocab_size, max_seq_length)
    data_loader = DataLoader(dataset, batch_size=batch_size, shuffle=True)

    # 初始化模型、损失函数和优化器
    model = GPTModel(vocab_size, max_seq_length, nhead, nhid, nlayers)
    criterion = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(model.parameters())

    # 训练模型
    train(model, data_loader, optimizer, criterion, epochs)

if __name__ == "__main__":
    main()

六、微调后的gpt1如何做推理

import torch
import torch.nn as nn

# 定义文本分类模型
class GPT1Classifier(nn.Module):
    def __init__(self, num_classes):
        super(GPT1Classifier, self).__init__()
        self.gpt = GPTModel.from_pretrained('gpt1')  # 加载预训练的GPT-1模型
        self.classifier = nn.Linear(self.gpt.config.hidden_size, num_classes)  # 添加一个分类层

    def forward(self, input_ids, attention_mask):
        outputs = self.gpt(input_ids=input_ids, attention_mask=attention_mask)
        pooled_output = outputs[0][:, 0, :]  # 获取CLS token的表示
        logits = self.classifier(pooled_output)
        return logits

# 设置设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 加载预训练的GPT-1模型和tokenizer
tokenizer = load_tokenizer()
model = GPT1Classifier(num_classes=2).to(device)

# 定义分类所需的输入数据
text = "这是一个文本样本"
input_ids = torch.tensor([tokenizer.encode(text, add_special_tokens=True)]).to(device)
attention_mask = torch.ones(input_ids.shape, dtype=torch.long).to(device)

# 进行文本分类预测
model.eval()
with torch.no_grad():
    logits = model(input_ids, attention_mask)
    predictions = torch.argmax(logits, dim=1)

print(f"预测标签:{predictions.item()}")

七、GPT与BERT

GPT-1(Generative Pre-trained Transformer 1)和BERT(Bidirectional Encoder Representations from Transformers)是两种基于Transformer架构的语言表示模型,它们在NLP领域都取得了显著的成绩。尽管两者都使用预训练和微调的训练范式,但在模型架构、预训练方法、目标任务和性能上有着明显的区别。

以下是GPT-1和BERT之间的几个主要对比点:

  1. 模型架构:GPT-1?使用了Transformer的解码器部分。它是一个自回归模型,这意味着在生成文本时,每个新单词都是基于之前生成的单词序列进行预测的。BERT?使用了Transformer的编码器部分。它是一个自编码模型,用掩码语言模型(MLM)的方式来同时考虑一个单词的左右上下文信息,从而实现双向上下文的捕获。
  2. 预训练任务:GPT-1?的预训练是基于传统的语言模型任务,即预测下一个单词的任务。BERT?的预训练包括两部分:一是掩码语言模型任务,它随机地从输入的序列中掩码一部分单词,然后模型需要预测这些被掩码的单词;二是下一句预测任务(Next Sentence Prediction, NSP),模型需要判断两个句子是否是顺序的关系。
  3. 预训练方式:GPT-1?预训练是单向的,即它只考虑了当前单词左边的上下文信息。BERT?预训练是双向的,模型同时考虑了当前单词左右两侧的上下文信息,从而能够捕获更丰富的语境特征。
  4. 微调适应性:GPT-1?在微调阶段通过改变输入序列的形式来适应不同的下游任务,但不改变模型本身的架构。BERT?同样可以微调来适应各种下游任务,而且由于其双向上下文的特性,BERT在某些任务上如实体识别、问答等表现得尤为出色。
  5. 性能:GPT-1?在发布时在多项NLP任务上取得了当时的最好结果,尤其是在生成任务上表现突出。BERT?在发布后不久,刷新了11项NLP任务的记录,尤其在理解任务上(如情感分析、问答、自然语言推理)表现强劲。

????????总的来说,GPT-1和BERT各有千秋,体现了NLP预训练模型设计和训练范式的不同方向和方法。

? ? ? ? ?更通俗一点的讲,BERT并不使用标准的语言模型,而是采用带有掩码的语言模型,即完形填空。在完形填空任务中,一个句子中间的一个单词被挖去,然后让你去猜测这个单词。在这个任务中,我们可以看到挖去的单词之前和之后的单词,因此我们可以使用Transformer的编码器来完成这个任务,编码器可以看到所有单词。而GPT选择了一种更加困难的目标,即从前面的一段文本预测接下来的一个单词,这比完形填空要更具挑战性,因为预测一个开放式的结局,要比预测一个中间状态难很多。这也是为什么GPT在某些场景下训练效果比BERT差的原因之一。

????????不过反过来思考,如果一个模型可以真正预测未来,那么将比通过完形填空训练的BERT模型更加强大。作者选择了一条更加困难的技术路线,但同时也有更高的天花板。

八、一些资料

paper地址icon-default.png?t=N7T8https://s3-us-west-2.amazonaws.com/openai-assets/research-covers/language-unsupervised/language_understanding_paper.pdf

文章来源:https://blog.csdn.net/sinat_34461199/article/details/135404779
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。