如何创建一个 GPT 模型(微调GPT-2)

要将字符级别的语言模型更改为类似GPT-2这样的模型,我们需要考虑以下几个关键的修改:

  1. 模型架构:使用更复杂的Transformer架构,GPT-2具有更多的层、头和隐藏单元。
  2. 词汇表和嵌入:从字符级别转换为词或子词(token)级别,这需要使用预训练的词汇表和嵌入。
  3. 输入数据预处理:将文本转换为词或子词的表示,而不是字符的表示。
  4. 输出处理:由于GPT-2使用的是更大的词汇表,因此模型的输出和损失计算也需要相应的调整。
  5. 预训练模型的加载和微调:可以直接加载预训练的GPT-2模型,然后根据特定任务进行微调。

下面是一个简化的示例,说明如何使用Hugging Face的Transformers库来加载预训练的GPT-2模型并进行微调。这需要先安装transformers库:

也有可能需要安装下面的模块:

为了调试方便,建议先下载gpt2模型

https://huggingface.co/openai-community/gpt2

1. 微调GPT-2模型

代码 setup1.1.py

运行 setup1.1.py

代码运行结果表明,模型已经能够成功进行文本生成,但生成的文本在所有情况下都是相同的,这可能并不是您期望的结果。这种现象可能是由于几个原因导致的:

  1. 过高的损失值:损失值50.36573791503906相对较高,这表明模型在微调过程中可能没有很好地学习到数据的分布。这可能是因为数据量太少、学习率不适当、或者是因为微调的迭代次数太少。
  2. 生成设置:在您的生成设置中,虽然已经启用了采样(do_sample=True)和Top-p采样(top_p=0.92),但如果模型没有很好地学习到数据的分布,这些设置可能不足以生成多样化的文本。

解决建议:

  1. 增加数据量:如果可能,增加微调时使用的文本量。更多的数据可以帮助模型更好地理解语言的多样性。
  2. 调整学习率:尝试不同的学习率。有时候,较小的学习率可能有助于模型更稳定地学习。
  3. 增加迭代次数:只进行了一个epoch的训练可能不足以使模型适应您的数据。增加迭代次数可能有助于降低损失并改善生成的质量。
  4. 实验不同的生成参数:调整生成文本时的参数,如temperature(控制生成文本的随机性),top_k(只从概率最高的k个令牌中抽样),或者增加num_beams进行波束搜索,可能会产生更多样化的输出。
  5. 评估模型性能:在微调后,使用一些保留的测试数据来评估模型性能,确保模型不仅在训练数据上表现良好,也能泛化到未见过的数据上。
  6. 注意力掩码和填充令牌:您的代码已经正确设置了attention_maskpad_token_id,这有助于提高生成质量。确认这些设置是否按预期工作,有时候调整这些设置也能改善结果。

请注意,文本生成任务通常需要大量的数据和计算资源来达到良好的效果,特别是当使用像GPT-2这样的大型语言模型时。持续实验和调整是优化模型性能的关键。

2. 保存微调后的模型

在完成微调后,您可以使用torch.save来保存模型的状态字典,以及分词器的配置:

代码 setup2.1.py

运行 setup2.1.py

3. 加载微调后的模型

当您需要再次使用模型时,可以通过加载保存的模型状态和分词器来恢复模型:

代码 setup3.1.py

运行 setup3.1.py

4. 增加数据量

增加中文数据量,添加论语里一段,试试效果,https://github.com/chinese-poetry/chinese-poetry/blob/master/%E8%AE%BA%E8%AF%AD/lunyu.json

代码 setup4.1.py

运行 setup3.1.py

从上面来看,效果很差,因为epochs 只为1次

那现在把epochs 改为 100次

运行 setup3.1.py

现在再看运行结果,效果好多了

代码已经很完整地展示了如何使用Transformers库来微调并保存一个GPT-2模型,然后使用这个微调后的模型来生成文本。您的流程包括:

  1. 加载预训练的模型和分词器:使用GPT2TokenizerGPT2LMHeadModel从本地路径'models/gpt2'加载。
  2. 设置填充令牌:检查分词器的pad_token是否设置,如果没有,则添加一个,并扩展模型的词嵌入以适应新的令牌。
  3. 准备数据:将一系列文本编码为模型的输入ID,同时确保每个文本都进行了适当的填充和截断。
  4. 设置模型和优化器:将模型移到适当的设备(GPU或CPU),并初始化AdamW优化器。
  5. 进行微调:通过多个epochs遍历数据,进行梯度下降优化。
  6. 保存微调后的模型和分词器:将微调后的模型和分词器保存到指定路径。
  7. 生成文本:使用微调后的模型和分词器生成文本,显示了如何利用attention_mask进行生成。

接下来,为了确保代码的完整性和正确性,我会提供一些小的建议和改进:

  • 验证模型保存路径:在保存模型之前,确保model_path指定的目录存在或者代码中有创建目录的逻辑。
  • 微调期间的评估:考虑在微调过程中或之后添加评估步骤,以监控模型在验证集上的性能。这有助于判断模型是否过拟合或者还有改进的空间。
  • 学习率调整AdamW优化器初始化时使用了一个固定的学习率。在一些情况下,使用学习率调度器(例如,get_linear_schedule_with_warmup)来在训练过程中动态调整学习率可能会提高模型性能。
  • 更详细的生成控制:根据您的需求,考虑调整生成文本的参数(如temperaturetop_knum_beams等)以控制生成文本的多样性和创造性。

您的代码已经非常接近于一个完整的微调和文本生成流程。以上建议可以根据具体需求进行调整和实施。

5. 微调期间的评估

在微调期间进行模型评估是一个重要的步骤,它可以帮助您监控模型的学习进度,以及判断模型是否过拟合或者仍有改进的空间。以下是如何在微调期间进行评估的基本步骤:

5.1. 准备数据

首先,您需要将数据分成至少两部分:一部分用于训练(微调),另一部分用于评估(通常称为验证集)。这样,您可以在独立的数据上评估模型的性能,这有助于更准确地估计模型对未见数据的泛化能力。

5.2. 编写评估函数

评估函数的目标是计算和返回模型在验证集上的性能指标。对于语言模型,常见的性能指标包括困惑度(Perplexity)或者特定任务的准确率。

评估函数代码如下:

5.3. 在微调循环中调用评估函数

在您的训练循环的每个epoch结束后,您可以调用评估函数来计算模型在验证集上的性能。然后,您可以根据需要调整模型的超参数或早期停止训练以避免过拟合。

注意事项:

  • 数据处理:确保评估数据的处理方式(如分词、填充)与训练数据一致。
  • 模型模式:在评估前后确保正确设置模型的模式(model.eval()model.train())。
  • 性能指标:选择合适的性能指标来评估您的模型。对于不同的任务,可能需要不同的评估指标。

这个基本框架可以根据您的具体需求进行调整,例如引入更多的性能指标或对验证集进行批处理以提高评估效率。

setup5.1.py 代码如下:

运行 setup5.1.py

从提供的输出中可以看出,训练损失(Loss)随着epoch的增加而持续下降,这表明模型在训练集上的性能正在改善,学习到了数据的分布。然而,评估损失(Eval Loss)随着训练的进行不仅没有下降,反而有所增加,这通常是过拟合的一个迹象。过拟合发生时,模型在训练数据上表现良好,但在未见过的数据上表现较差。

解决过拟合的策略:

  1. 增加数据量:如果可能的话,增加更多的训练数据可以帮助模型学习到更广泛的分布,从而提高其泛化能力。
  2. 正则化:应用正则化技术(如权重衰减)可以减少模型的复杂度,防止过拟合。在使用AdamW优化器时,权重衰减已经作为参数被内置实现。
  3. 早停(Early Stopping):监控评估损失,并在损失开始增加时停止训练。这可以防止模型在训练数据上过度拟合。
  4. 数据增强:对训练数据应用轻微的变化或增强,以增加数据的多样性。
  5. 简化模型:如果模型过于复杂,可能会捕捉到训练数据中的噪声。简化模型的结构可能有助于提高泛化能力。
  6. Dropout:在训练期间随机丢弃一部分网络的连接,可以有效减少过拟合。

生成文本的质量

生成的文本看起来与训练文本的风格一致,显示出模型已经学习到了一些文本的生成规律。如果生成的文本质量不如预期,可以尝试调整生成参数(如温度temperature、最大长度max_lengthtop_ktop_p等)来改善结果。

此外,过拟合可能会导致模型在生成时过分依赖训练数据,从而降低生成文本的多样性。在这种情况下,上述减少过拟合的策略也可能帮助改善生成的质量。

结论

根据您的输出,建议重点关注减少过拟合,并根据需要调整模型参数或训练策略。这可能包括数据处理方面的改进、模型结构的调整、优化器参数的微调,以及生成策略的优化。

6. 在训练损失最低的时候保存模型

为了在训练过程中当损失最低时保存模型,您可以在训练循环中添加一些逻辑来追踪最低的训练损失,并仅在当前epoch的损失低于之前所有epoch的最低损失时保存模型。这种方法确保了您保存的模型是在训练过程中表现最好的那一个。

setup6.1.py 代码如下:

运行 setup6.1.py

这段代码在每个epoch结束时计算了平均训练损失,并将其与迄今为止观察到的最低损失进行比较。如果当前epoch的损失低于之前的最低损失,则会保存当前模型和分词器到指定路径。这样,您最终保存的模型将是在整个训练过程中损失最低的模型。

请注意,这个示例仅关注训练损失。在实际应用中,您可能还想根据验证损失来保存模型,因为这通常更能反映模型在未见数据上的泛化能力。调整这段代码以根据验证损失保存模型是相对直接的:只需将avg_lossbest_loss替换为eval_loss和相应的最佳验证损失变量即可。

7. 优化模型保存代码和输出代码

要在训练损失最低时保存模型,您需要在训练过程中跟踪最低的训练损失,并在达到新的最低点时保存模型。这通常涉及到在每个epoch结束时比较当前损失与迄今为止记录的最低损失,并在当前损失更低时更新最低损失并保存模型。

关于生成文本时不依赖于max_length=150,而是输出适当长度的文本,您可以使用generate方法的多个参数来控制生成的长度和质量,但不能直接指定“适当的”长度,因为“适当”的定义可能因上下文而异。不过,您可以使用如eos_token_id来指定一个结束令牌,使得模型在生成到这个令牌时停止。如果您希望模型在逻辑上完成文本后停止,而不是硬编码一个特定的最大长度

setup7.1.py 代码如下:

运行 setup7.1.py

8. 加载最后微调后的模型

setup7.1.py 代码如下:

运行 setup8.1.py

可以看到:”不知人也“ 是新词?

9. 使用 gpt2更大的模型架构

GPT-2 模型架构的参数为 124M

https://huggingface.co/openai-community/gpt2

GPT-2 Medium 模型架构的参数为 355M

https://huggingface.co/openai-community/gpt2-medium

GPT-2 Large 模型架构的参数为 774M

https://huggingface.co/openai-community/gpt2-large

GPT-2 XL 模型架构的参数为 1.5B

https://huggingface.co/openai-community/gpt2-xl

现在试试 把模型改为 GPT-2 XL

setup9.3.py 代码如下:

运行 setup9.3.py

10. 对比原模型和微调后的模型

这里使用 gpt2-xl 的模型架构

setup10.1.py 代码

运行 setup10.1.py

修改模型为微调后的模型,再看看效果

运行 setup10.2.py

这里可以看到,微调后是有论语内容的

11. 是在较小的模型上微调,还是较大的模型上微调?

这里要比较一下最小的模型和最大的模型微调后的效果,微调的数据是一样的

把setup10.2.py里面的模型修改为gpt2_finetuned,看看效果

运行 setup11.1.py 的效果

比较一下setup10.2.py 的结果,有区别不?不好说?

选择在较小的模型还是较大的模型上进行微调主要取决于几个关键因素:数据集的大小、计算资源、微调和推理的时间要求、以及期望达到的性能。下面是这两种选择的一些考虑因素:

微调较小的模型

优点

  • 计算成本较低:较小的模型需要的计算资源更少,可以在有限的硬件上训练和推理。
  • 速度更快:训练和推理时间较短,适合快速迭代和在资源受限的环境中使用。
  • 更容易调整:较小的模型参数较少,可能更容易找到良好的超参数设置。

缺点

  • 性能可能较低:较小的模型表达能力有限,可能在复杂任务上达不到较大模型的性能。

微调较大的模型

优点

  • 潜在的更高性能:较大的模型具有更强的表达能力,通常能够在复杂的任务上取得更好的结果。
  • 更好的泛化能力:较大的模型由于参数多,能够捕捉到数据中的更细微的模式,可能在未见过的数据上有更好的表现。

缺点

  • 计算成本高:需要更多的计算资源,训练和推理时间更长。
  • 调优可能更困难:由于参数量巨大,可能需要更多的实验来找到最佳的超参数设置。
  • 过拟合的风险:在数据量不足的情况下,较大的模型更容易过拟合训练数据。

如何选择

  • 如果您的计算资源有限,或者您希望快速迭代和部署模型,那么开始时选择较小的模型可能更合适。
  • 如果您追求最高的性能,并且有足够的数据和计算资源来支持训练较大的模型,那么微调较大的模型可能是更好的选择。
  • 数据集大小也是一个重要因素。较大的模型需要更多的数据来避免过拟合。如果您的数据集相对较小,可能首先考虑使用较小的模型或者使用数据增强、迁移学习等技术。

总的来说,选择的依据应该基于具体任务的需求、可用资源以及期望达到的性能标准。在某些情况下,从较小的模型开始,然后根据需要逐步迁移到较大的模型,可以是一个实际且高效的策略。

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部