Pytorch——GPT-2 预训练模型及文本生成

介绍

在本次将学习另一个有着优秀表现的预训练模型:GPT-2 模型,以及使用它进行文本生成任务实践。

知识点

  • GPT-2 的核心思想
  • GPT-2 模型结构详解
  • GPT-2 进行文本生成


OpenAI 在论文 Improving Language Understanding by Generative Pre-Training 中提出了 GPT 模型。GPT 模型是由单向 Transformer 的解码器构建的模型,OpenAI 团队在一个非常大的书籍数据集 the Toronto Book Corpus 上对其进行了无监督预训练。

而后,OpenAI 团队又提出了 GPT-2 模型,GPT-2 模型是 GPT 模型的后继,使用了更大的训练集训练,有更多的参数,是 GPT 模型的扩大版。GPT-2 的训练集为数量有 8 百万的网页,由研究人员从网络上爬取得到,大小共有 40 GB 的文本数据,训练任务为给出上文,使模型预测下一个单词。

由于 GPT 与 GPT-2 模型的差别就在于 GPT-2 使用了更多的训练数据,增加了模型参数,在具体结构上并无较大差异。所以,下面我们主要介绍实际表现更优异的 GPT-2 模型。

GPT-2 核心思想

根据研究发现,语言有灵活的表达能力,即能够将任务、输入、输出表示成一个字符串,并且模型可以用这种形式的字符串进行训练,学习相应任务。例如,在翻译任务中,一个训练样本可以被写成

(translate to french, english text, french text)

同样地,在阅读理解任务中,一个训练样本可以被写成

(answer the question, document, question, answer)

并且,人们可以用以上格式的多种任务的训练样本同时训练一个模型,使得该模型获得同时执行多种任务的能力。

于是 OpenAI 研究人员推测,一个具有足够能力的语言模型将学习到推理和执行训练样本中所展示出的任务,以便更好地预测它们。如果一个语言模型能够做到这一点,那么它实际上就是在进行无监督的多任务学习。于是研究人员决定通过分析语言模型在各种各样的任务上的性能来测试这种情况是否属实,这样便有了 GPT-2 的产生。

而且正如他们所推测的,GPT-2 在多种任务上的性能表现很好,具体如下图:

来源

所以 GPT-2 对于其他预训练模型的一个突破就在于,它能够在未针对特定下游任务进行训练的条件下,就在下游任务如:阅读理解、机器翻译、问答和文本概括上有很好的表现。这也表明了,在模型足够大,训练数据足够充足时,无监督训练技术也能训练出在多种下游任务上有很好表现的模型。

因为监督学习需要大量的数据,并且需要被仔细清理过的数据,想要得到这样的数据需要昂贵的人力成本。无监督学习可以克服这个缺点,因为它不需要人工标注,有大量现成的数据可以利用。这也表明了 GPT-2 模型研究的意义。

在了解了构建 GPT-2 模型的思想后,接下来我们将详细了解一下 GPT-2 模型的结构。

GPT-2 模型结构

GPT-2 的整体结构如下图,GPT-2 是以 Transformer 为基础构建的, 使用字节对编码的方法进行数据预处理,通过预测下一个词任务进行预训练的语言模型,下面我们从 GPT-2 的预处理方法出发,来一步步详细解析一下 GPT-2。

来源

字节对编码

GPT-2 模型在数据预处理时使用了字节对编码(Byte Pair Encoding,简称 BPE)方法,BPE 是一种能够解决未登录词问题,并减小词典大小的方法。它综合利用了单词层面编码和字符层面编码的优势,举例来说,我们要对下面的字符串编码,

aaabdaaabac

字节对 aa 出现的次数最多,所以我们将它替换成一个没在字符串中被用过的字符 Z

ZabdZabac
Z=aa

然后我们重复这个过程,用 Y 替换 ab

ZYdZYac
Y=ab
Z=aa

继续,用 X 替换 ZY

XdXac
X=ZY
Y=ab
Z=aa

这个过程重复进行,直到没有字节对出现超过一次。当需要解码时,就将上述替换过程反向进行。

下面是一段 BPE 算法原文中对 BPE 算法的实现:

import re
import collections

def get_stats(vocab):
    pairs = collections.defaultdict(int)
    for word, freq in vocab.items():
        symbols = word.split()
        for i in range(len(symbols)-1):
            pairs[symbols[i], symbols[i+1]] += freq  # 计算字节对出现频率
    return pairs

def merge_vocab(pair, v_in):
    v_out = {}
    bigram = re.escape(' '.join(pair))  # 将字节对中可解释为正则运算符的字符转义
    p = re.compile(r'(?<!\S)' + bigram + r'(?!\S)')  # 将要合并的字节对前后只能为空白字符
    for word in v_in:
        w_out = p.sub(''.join(pair), word)  # 合并符合条件的字节对
        v_out[w_out] = v_in[word]
    return v_out

vocab = {'l o w </w>': 5, 'l o w e r </w>': 2,
         'n e w e s t </w>': 6, 'w i d e s t </w>': 3}
num_merges = 10
for i in range(num_merges):
    pairs = get_stats(vocab)
    best = max(pairs, key=pairs.get)  # 选择频率最大的字节对
    vocab = merge_vocab(best, vocab)
    print(best)

单向 Transformer 解码器结构

GPT-2 模型由多层单向 Transformer 的解码器部分构成,本质上是自回归模型,自回归的意思是指,每次产生新单词后,将新单词加到原输入句后面,作为新的输入句。其中 Transformer 解码器结构如下图:

来源

GPT-2 模型中只使用了多个 Masked Self-Attention 和 Feed Forward Neural Network 两个模块。如下图所示:

来源

可以看到,GPT-2 模型会将语句输入上图所示的结构中,预测下一个词,然后再将新单词加入,作为新的输入,继续预测。损失函数会计算预测值与实际值之间的偏差。

从上一节我们了解到 BERT 是基于双向 Transformer 结构构建,而 GPT-2 是基于单向 Transformer,这里的双向与单向,是指在进行注意力计算时,BERT会同时考虑被遮蔽词左右的词对其的影响,而 GPT-2 只会考虑在待预测词位置左侧的词对待预测词的影响。

通过上述数据预处理方法和模型结构,以及大量的数据训练出了 GPT-2 模型。OpenAI 团队由于安全考虑,没有开源全部训练参数,而是提供了小型的预训练模型,接下来我们将在 GPT-2 预训练模型的基础上进行。

GPT-2 文本生成

GPT-2 就是一个语言模型,能够根据上文预测下一个单词,所以它就可以利用预训练已经学到的知识来生成文本,如生成新闻。也可以使用另一些数据进行微调,生成有特定格式或者主题的文本,如诗歌、戏剧。所以接下来,我们会用 GPT-2 模型进行一个文本生成。

预训练模型生成新闻

想要直接运行一个预训练好的 GPT-2 模型,最简单的方法是让它自由工作,即随机生成文本。换句话说,在开始时,我们给它一点提示,即一个预定好的起始单词,然后让它自行地随机生成后续的文本。

但这样有时可能会出现问题,例如模型陷入一个循环,不断生成同一个单词。为了避免这种情况, GPT-2 设置了一个 top-k 参数,这样模型就会从概率前 k 大的单词中随机选取一个单词,作为下一个单词。下面是选择 top-k 的函数的实现,

import random

def select_top_k(predictions, k=10):
    predicted_index = random.choice(
        predictions[0, -1, :].sort(descending=True)[1][:10]).item()
    return predicted_index

下面引入 GPT-2 模型,我们将使用在 PyTorch-Transformers 模型库中封装好的 GPT2Tokenizer()GPT2LMHeadModel() 类来实际看一下 GPT-2 在预训练后的对下一个词预测的能力。首先,需要安装 PyTorch-Transformers。

!pip install pytorch_transformers==1.0  # 安装 PyTorch-Transformers

使用 PyTorch-Transformers 模型库,先设置好准备输入模型的例子,使用 GPT2Tokenizer() 建立分词器对象对原句编码。

import torch
from pytorch_transformers import GPT2Tokenizer

import logging
logging.basicConfig(level=logging.INFO)

# 载入预训练模型的分词器
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')

# 使用 GPT2Tokenizer 对输入进行编码
text = "Yesterday, a man named Jack said he saw an alien,"
indexed_tokens = tokenizer.encode(text)
tokens_tensor = torch.tensor([indexed_tokens])
tokens_tensor.shape

接下来使用 GPT2LMHeadModel() 建立模型,并将模型模式设为验证模式。由于预训练模型参数体积很大,且托管在外网,所以本次先从网盘下载预训练模型,本地无需此步骤。

from pytorch_transformers import GPT2LMHeadModel

# 读取 GPT-2 预训练模型
model = GPT2LMHeadModel.from_pretrained("./")
model.eval()

total_predicted_text = text
n = 100  # 预测过程的循环次数
for _ in range(n):
    with torch.no_grad():
        outputs = model(tokens_tensor)
        predictions = outputs[0]

    predicted_index = select_top_k(predictions, k=10)
    predicted_text = tokenizer.decode(indexed_tokens + [predicted_index])
    total_predicted_text += tokenizer.decode(predicted_index)

    if '<|endoftext|>' in total_predicted_text:
        # 如果出现文本结束标志,就结束文本生成
        break

    indexed_tokens += [predicted_index]
    tokens_tensor = torch.tensor([indexed_tokens])

print(total_predicted_text)

运行结束后,我们观察一下模型生成的文本,可以看到,大致感觉上这好像是一段正常的文本,不过,仔细看就会发现语句中的逻辑问题,这也是之后研究人员会继续攻克的问题。

除了直接利用预训练模型生成文本,我们还可以使用微调的方法使 GPT-2 模型生成有特定风格和格式的文本。

微调生成戏剧文本

接下来,我们将使用一些戏剧剧本对 GPT-2 进行微调。由于 OpenAI 团队开源的 GPT-2 模型预训练参数为使用英文数据集预训练后得到的,虽然可以在微调时使用中文数据集,但需要大量数据和时间才会有好的效果,所以这里我们使用了英文数据集进行微调,从而更好地展现 GPT-2 模型的能力。

首先,下载训练数据集,这里使用了莎士比亚的戏剧作品《罗密欧与朱丽叶》作为训练样本。数据集已经提前下载好并放在云盘中,链接:https://pan.baidu.com/s/1LiTgiake1KC8qptjRncJ5w 提取码:km06

with open('./romeo_and_juliet.txt', 'r') as f:
    dataset = f.read()

len(dataset)

预处理训练集,将训练集编码、分段。

indexed_text = tokenizer.encode(dataset)
del(dataset)

dataset_cut = []
for i in range(len(indexed_text)//512):
    # 将字符串分段成长度为 512
    dataset_cut.append(indexed_text[i*512:i*512+512])
del(indexed_text)

dataset_tensor = torch.tensor(dataset_cut)
dataset_tensor.shape

这里使用 PyTorch 提供的 DataLoader() 构建训练集数据集表示,使用 TensorDataset() 构建训练集数据迭代器。

from torch.utils.data import DataLoader, TensorDataset

# 构建数据集和数据迭代器,设定 batch_size 大小为 2
train_set = TensorDataset(dataset_tensor,
                          dataset_tensor)  # 标签与样本数据相同
train_loader = DataLoader(dataset=train_set,
                          batch_size=2,
                          shuffle=False)
train_loader

检查是否机器有 GPU,如果有就在 GPU 运行,否则就在 CPU 运行。

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device

开始训练。

from torch import nn
from torch.autograd import Variable
import time

pre = time.time()

epoch = 30  # 循环学习 30 次

model.to(device)
model.train()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-5)  # 定义优化器

for i in range(epoch):
    total_loss = 0
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = Variable(data).to(device), Variable(
            target).to(device)

        optimizer.zero_grad()

        loss, logits, _ = model(data, labels=target)

        total_loss += loss

        loss.backward()
        optimizer.step()

        if batch_idx == len(train_loader)-1:
            # 在每个 Epoch 的最后输出一下结果
            print('average loss:', total_loss/len(train_loader))

print('训练时间:', time.time()-pre)

训练结束后,可以使模型生成文本,观察输出。

text = "From fairest creatures we desire"  # 这里也可以输入不同的英文文本
indexed_tokens = tokenizer.encode(text)
tokens_tensor = torch.tensor([indexed_tokens])

model.eval()
total_predicted_text = text

# 使训练后的模型进行 500 次预测
for _ in range(500):
    tokens_tensor = tokens_tensor.to('cuda')

    with torch.no_grad():
        outputs = model(tokens_tensor)
        predictions = outputs[0]

    predicted_index = select_top_k(predictions, k=10)

    predicted_text = tokenizer.decode(indexed_tokens + [predicted_index])
    total_predicted_text += tokenizer.decode(predicted_index)
    if '<|endoftext|>' in total_predicted_text:
        # 如果出现文本结束标志,就结束文本生成
        break

    indexed_tokens += [predicted_index]

    if len(indexed_tokens) > 1023:
        # 模型最长输入长度为1024,如果长度过长则截断
        indexed_tokens = indexed_tokens[-1023:]

    tokens_tensor = torch.tensor([indexed_tokens])

print(total_predicted_text)

从生成结果可以看到,模型已经学习到了戏剧剧本的文本结构。但是仔细读起来会发现缺少逻辑和关联,这是因为由于时间和设备的限制,对模型的训练比较有限。如果有条件可以用更多的数据,训练更长的时间,这样模型也会有更好的表现。

总结

本次中,我们首先学习了 GPT-2 的构建思想和它的结构,并观察了预训练结果,最后使用了 Fine-Tuning 方法应用 GPT-2 预训练模型进行了戏剧文本生成任务。

相关链接

原文地址:https://www.cnblogs.com/wwj99/p/12503545.html

时间: 2024-11-08 06:31:56

Pytorch——GPT-2 预训练模型及文本生成的相关文章

BERT 预训练模型及文本分类

BERT 预训练模型及文本分类 介绍 如果你关注自然语言处理技术的发展,那你一定听说过 BERT,它的诞生对自然语言处理领域具有着里程碑式的意义.本次试验将介绍 BERT 的模型结构,以及将其应用于文本分类实践. 知识点 语言模型和词向量 BERT 结构详解 BERT 文本分类 BERT 全称为 Bidirectional Encoder Representations from Transformer,是谷歌在 2018 年 10 月发布的语言表示模型.BERT 通过维基百科和书籍语料组成的庞

pytorch预训练模型的下载地址以及解决下载速度慢的方法

https://github.com/pytorch/vision/tree/master/torchvision/models 几乎所有的常用预训练模型都在这里面 总结下各种模型的下载地址: 1 Resnet: 2 3 model_urls = { 4 'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth', 5 'resnet34': 'https://download.pytorch.org/model

pytorch中修改后的模型如何加载预训练模型

问题描述 简单来说,比如你要加载一个vgg16模型,但是你自己需要的网络结构并不是原本的vgg16网络,可能你删掉某些层,可能你改掉某些层,这时你去加载预训练模型,就会报错,错误原因就是你的模型和原本的模型不匹配. 此时有两种解决方法: 1.重新解析参数的字典,将预训练模型的参数提取出来,然后放在自己的模型中对应的位置 2.直接用原本的vgg16网络去加载预训练模型,然后再修改网络. 具体操作待续吧...... 我个人推荐第一种方法. 原文地址:https://www.cnblogs.com/y

XLNet预训练模型,看这篇就够了!(代码实现)

1. 什么是XLNet XLNet 是一个类似 BERT 的模型,而不是完全不同的模型.总之,XLNet是一种通用的自回归预训练方法.它是CMU和Google Brain团队在2019年6月份发布的模型,最终,XLNet 在 20 个任务上超过了 BERT 的表现,并在 18 个任务上取得了当前最佳效果(state-of-the-art),包括机器问答.自然语言推断.情感分析和文档排序. 作者表示,BERT 这样基于去噪自编码器的预训练模型可以很好地建模双向语境信息,性能优于基于自回归语言模型的

预训练模型:XLNet 和他的先辈们

预训练模型 在CV中,预训练模型如ImagNet取得很大的成功,而在NLP中之前一直没有一个可以承担此角色的模型,目前,预训练模型如雨后春笋,是当今NLP领域最热的研究领域之一. 预训练模型属于迁移学习,即在某一任务上训练的模型,经过微调(finetune)可以应用到其它任务上. 在NLP领域,最早的预训练模型可以说是word2vec, Mikolov应用语言模型进行训练,产生的词向量(word embeddings)可以用于其他任务上,这样的词向量在目标任务上,可以固定不变,也可以随着模型训练

开源一个安全帽佩戴检测数据集及预训练模型

本文开源了一个安全帽佩戴检测数据集及预训练模型,该项目已上传至github,点此链接,感觉有帮助的话请点star .同时简要介绍下实践上如何完成一个端到端的目标检测任务.可以看下效果图: 同时该模型也可以做人头检测,效果如下: 一.背景介绍 最近几年深度学习的发展让很多计算机视觉任务落地成为可能,这些任务渗透到了各行各业,比如工业安全,包含的任务如安全帽佩戴检测.高空坠物检测.异常事故检测(行人跌倒不起等)等等,这里以安全帽检测为例,简单介绍下如何完成一个端到端的任务,包括: 1. 业务场景分析

百度NLP预训练模型ERNIE2.0最强实操课程来袭!【附教程】

2019年3月,百度正式发布NLP模型ERNIE,其在中文任务中全面超越BERT一度引发业界广泛关注和探讨.经过短短几个月时间,百度ERNIE再升级,发布持续学习的语义理解框架ERNIE 2.0,及基于此框架的ERNIE 2.0预训练模型.继1.0后,ERNIE英文任务方面取得全新突破,在共计16个中英文任务上超越了BERT和XLNet, 取得了SOTA效果. 本篇内容可以说是史上最强实操课程,由浅入深完整带大家试跑ERNIE,大家可前往AI Studio fork代码 (https://ais

BERT预训练模型的演进过程!(附代码)

1. 什么是BERT BERT的全称是Bidirectional Encoder Representation from Transformers,是Google2018年提出的预训练模型,即双向Transformer的Encoder,因为decoder是不能获要预测的信息的.模型的主要创新点都在pre-train方法上,即用了Masked LM和Next Sentence Prediction两种方法分别捕捉词语和句子级别的representation. Bert最近很火,应该是最近最火爆的A

NLP预训练模型-百度ERNIE2.0的效果到底有多好【附用户点评】

ERNIE是百度自研的持续学习语义理解框架,该框架支持增量引入词汇(lexical).语法 (syntactic) .语义(semantic)等3个层次的自定义预训练任务,能够全面捕捉训练语料中的词法.语法.语义等潜在信息. ERNIE2.0实现了在中英文16个任务上的最优效果,具体效果见下方列表. 一.ERNIE2.0中文效果验证 我们在 9 个任务上验证 ERNIE 2.0 中文模型的效果.这些任务包括:自然语言推断任务 XNLI:阅读理解任务 DRCD.DuReader.CMRC2018: