目录
自然语言理解的挑战:在自然语言处理(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)。
????????GPT-1的预训练目标是自回归语言模型,即通过最大化给定前面词(上下文)的条件下,下一个词出现的概率。具体来说,对于一个词的序列x1, x2, ..., xn,目标是最大化序列概率P(x1, x2, ..., xn),这通常通过逐词最大化条件概率P(xi | x1, ..., xi-1)来实现。
????????模型使用了Transformer的解码器,transformer包含编码器和解码器两个部分。编码器和解码器最大的不同在于,编码器输入一个序列时,当提取第i个元素的特征时,可以看到整个序列里的所有元素。而对于解码器来说,由于存在掩码,当提取第I个元素的特征时,它只能看到当前元素和它之前的元素,后面的元素被掩码处理,注意力机制计算时会被视为0,所以解码器无法看到后面的内容。因此,我们只能使用Transformer的解码器,而不能使用编码器。)
输入序列准备: 假设正在预测单词U的概率,我们首先需要确定一个固定的窗口大小K,这个窗口将决定序列中每个单词的上下文大小。我们提取单词U前的K个单词,形成序列[word1, word2, ..., wordK]
。
词嵌入: 将每个单词映射到一个高维空间,以获取单词的词嵌入(word embeddings)。
位置编码: 由于Transformer模型不像传统的序列模型(如RNN)具有内在的顺序感知能力,因此我们需要添加位置编码(positional encodings),以确保模型能够理解单词的顺序。位置编码通常以相同的维度作为词嵌入,并与之相加来提供顺序信息。
Transformer层: 输入序列(带有词嵌入和位置编码)被送入一系列的Transformer层。每个Transformer层由自注意力机制(self-attention)和前馈神经网络(feed-forward neural network)组成。模型中可能有多个这样的层(N层),每一层都会接受上一层的输出作为输入,并产生新的输出。
自注意力机制: 在自注意力部分,模型计算序列中每个单词与其他单词之间的关系,这允许模型捕捉长距离依赖关系。多头自注意力(multi-head attention)允许模型在多个不同的表示子空间中并行学习信息。
层归一化和残差连接: 每个子层(自注意力和前馈网络)的输出都会经过层归一化(layer normalization),并且通过残差连接与输入相加。残差连接帮助缓解深层网络中的梯度消失问题。
输出投影: 经过N层Transformer处理后,我们得到最后一层的输出。这个输出将被投影到词汇表的大小,以便预测每个单词的分数。
Softmax层: 投影后的分数通过Softmax层转换为概率分布。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()
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-1(Generative Pre-trained Transformer 1)和BERT(Bidirectional Encoder Representations from Transformers)是两种基于Transformer架构的语言表示模型,它们在NLP领域都取得了显著的成绩。尽管两者都使用预训练和微调的训练范式,但在模型架构、预训练方法、目标任务和性能上有着明显的区别。
以下是GPT-1和BERT之间的几个主要对比点:
????????总的来说,GPT-1和BERT各有千秋,体现了NLP预训练模型设计和训练范式的不同方向和方法。
? ? ? ? ?更通俗一点的讲,BERT并不使用标准的语言模型,而是采用带有掩码的语言模型,即完形填空。在完形填空任务中,一个句子中间的一个单词被挖去,然后让你去猜测这个单词。在这个任务中,我们可以看到挖去的单词之前和之后的单词,因此我们可以使用Transformer的编码器来完成这个任务,编码器可以看到所有单词。而GPT选择了一种更加困难的目标,即从前面的一段文本预测接下来的一个单词,这比完形填空要更具挑战性,因为预测一个开放式的结局,要比预测一个中间状态难很多。这也是为什么GPT在某些场景下训练效果比BERT差的原因之一。
????????不过反过来思考,如果一个模型可以真正预测未来,那么将比通过完形填空训练的BERT模型更加强大。作者选择了一条更加困难的技术路线,但同时也有更高的天花板。