要将字符级别的语言模型更改为类似GPT-2这样的模型,我们需要考虑以下几个关键的修改:
- 模型架构:使用更复杂的Transformer架构,GPT-2具有更多的层、头和隐藏单元。
- 词汇表和嵌入:从字符级别转换为词或子词(token)级别,这需要使用预训练的词汇表和嵌入。
- 输入数据预处理:将文本转换为词或子词的表示,而不是字符的表示。
- 输出处理:由于GPT-2使用的是更大的词汇表,因此模型的输出和损失计算也需要相应的调整。
- 预训练模型的加载和微调:可以直接加载预训练的GPT-2模型,然后根据特定任务进行微调。
下面是一个简化的示例,说明如何使用Hugging Face的Transformers库来加载预训练的GPT-2模型并进行微调。这需要先安装transformers
库:
1 |
pip install transformers |
也有可能需要安装下面的模块:
1 2 |
pip install protobuf pip install click |
为了调试方便,建议先下载gpt2模型
https://huggingface.co/openai-community/gpt2
1. 微调GPT-2模型
代码 setup1.1.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
from transformers import GPT2Tokenizer, GPT2LMHeadModel, AdamW, get_linear_schedule_with_warmup import torch # 加载预训练模型和分词器 tokenizer = GPT2Tokenizer.from_pretrained('models/gpt2') model = GPT2LMHeadModel.from_pretrained('models/gpt2') # 为分词器设置填充令牌 # 确保为分词器设置了pad_token if tokenizer.pad_token is None: tokenizer.add_special_tokens({'pad_token': '[PAD]'}) model.resize_token_embeddings(len(tokenizer)) # 准备数据 texts = ["这是一个示例文本。", "这是另一个例子。"] input_ids = [tokenizer.encode(text, return_tensors='pt', padding=True, truncation=True, max_length=512) for text in texts] # 准备模型和优化器 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) model.train() optimizer = AdamW(model.parameters(), lr=5e-5, no_deprecation_warning=True) for epoch in range(1): # 微调epoch for input_id in input_ids: optimizer.zero_grad() input_id = input_id.to(device) outputs = model(input_id, labels=input_id) loss = outputs.loss loss.backward() optimizer.step() print(f"Epoch {epoch}: Loss {loss.item()}") # 文本生成 # 使用`__call__`方法生成`input_ids`和`attention_mask` inputs = tokenizer("这是一个文本生成的例子", return_tensors='pt', padding=True, truncation=True, max_length=50) # 提取`input_ids`和`attention_mask` input_ids = inputs['input_ids'] attention_mask = inputs['attention_mask'] input_ids = input_ids.to(device) attention_mask = attention_mask.to(device) # 文本生成,确保传入`attention_mask` sample_outputs = model.generate( input_ids, attention_mask=attention_mask, max_length=50, num_return_sequences=3, do_sample=True, # 启用采样以增加多样性 top_p=0.92, # 使用top-p采样 ) # 打印生成的文本 for i, sample_output in enumerate(sample_outputs): print("{}: {}".format(i, tokenizer.decode(sample_output, skip_special_tokens=True))) |
运行 setup1.1.py
1 2 3 4 5 6 |
$ python setup1.1.py Epoch 0: Loss 50.36573791503906 Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation. 0: 这是一个文本生成的例子 1: 这是一个文本生成的例子 2: 这是一个文本生成的例子 |
代码运行结果表明,模型已经能够成功进行文本生成,但生成的文本在所有情况下都是相同的,这可能并不是您期望的结果。这种现象可能是由于几个原因导致的:
- 过高的损失值:损失值
50.36573791503906
相对较高,这表明模型在微调过程中可能没有很好地学习到数据的分布。这可能是因为数据量太少、学习率不适当、或者是因为微调的迭代次数太少。 - 生成设置:在您的生成设置中,虽然已经启用了采样(
do_sample=True
)和Top-p采样(top_p=0.92
),但如果模型没有很好地学习到数据的分布,这些设置可能不足以生成多样化的文本。
解决建议:
- 增加数据量:如果可能,增加微调时使用的文本量。更多的数据可以帮助模型更好地理解语言的多样性。
- 调整学习率:尝试不同的学习率。有时候,较小的学习率可能有助于模型更稳定地学习。
- 增加迭代次数:只进行了一个epoch的训练可能不足以使模型适应您的数据。增加迭代次数可能有助于降低损失并改善生成的质量。
- 实验不同的生成参数:调整生成文本时的参数,如
temperature
(控制生成文本的随机性),top_k
(只从概率最高的k个令牌中抽样),或者增加num_beams
进行波束搜索,可能会产生更多样化的输出。 - 评估模型性能:在微调后,使用一些保留的测试数据来评估模型性能,确保模型不仅在训练数据上表现良好,也能泛化到未见过的数据上。
- 注意力掩码和填充令牌:您的代码已经正确设置了
attention_mask
和pad_token_id
,这有助于提高生成质量。确认这些设置是否按预期工作,有时候调整这些设置也能改善结果。
请注意,文本生成任务通常需要大量的数据和计算资源来达到良好的效果,特别是当使用像GPT-2这样的大型语言模型时。持续实验和调整是优化模型性能的关键。
2. 保存微调后的模型
在完成微调后,您可以使用torch.save
来保存模型的状态字典,以及分词器的配置:
1 2 3 4 |
# 完成微调后保存模型和分词器 model_path = "models/gpt2_finetuned" tokenizer.save_pretrained(model_path) model.save_pretrained(model_path) |
代码 setup2.1.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
from transformers import GPT2Tokenizer, GPT2LMHeadModel, AdamW, get_linear_schedule_with_warmup import torch # 加载预训练模型和分词器 tokenizer = GPT2Tokenizer.from_pretrained('models/gpt2') model = GPT2LMHeadModel.from_pretrained('models/gpt2') # 为分词器设置填充令牌 # 确保为分词器设置了pad_token if tokenizer.pad_token is None: tokenizer.add_special_tokens({'pad_token': '[PAD]'}) model.resize_token_embeddings(len(tokenizer)) # 准备数据 texts = ["这是一个示例文本。", "这是另一个例子。"] input_ids = [tokenizer.encode(text, return_tensors='pt', padding=True, truncation=True, max_length=512) for text in texts] # 准备模型和优化器 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) model.train() optimizer = AdamW(model.parameters(), lr=5e-5, no_deprecation_warning=True) for epoch in range(1): # 微调epoch for input_id in input_ids: optimizer.zero_grad() input_id = input_id.to(device) outputs = model(input_id, labels=input_id) loss = outputs.loss loss.backward() optimizer.step() print(f"Epoch {epoch}: Loss {loss.item()}") # 完成微调后保存模型和分词器 model_path = "models/gpt2_finetuned" tokenizer.save_pretrained(model_path) model.save_pretrained(model_path) print(f"Model saved to {model_path}") # print the number of parameters in the model print(sum(p.numel() for p in model.parameters())/1e6, 'M parameters') # 文本生成 # 使用`__call__`方法生成`input_ids`和`attention_mask` inputs = tokenizer("这是一个文本生成的例子", return_tensors='pt', padding=True, truncation=True, max_length=50) # 提取`input_ids`和`attention_mask` input_ids = inputs['input_ids'] attention_mask = inputs['attention_mask'] input_ids = input_ids.to(device) attention_mask = attention_mask.to(device) # 文本生成,确保传入`attention_mask` sample_outputs = model.generate( input_ids, attention_mask=attention_mask, max_length=50, num_return_sequences=3, do_sample=True, # 启用采样以增加多样性 top_p=0.92, # 使用top-p采样 ) # 打印生成的文本 for i, sample_output in enumerate(sample_outputs): print("{}: {}".format(i, tokenizer.decode(sample_output, skip_special_tokens=True))) |
运行 setup2.1.py
1 2 3 4 5 6 7 8 |
$ python setup2.1.py Epoch 0: Loss 51.30159378051758 Model saved to models/gpt2_finetuned 124.440576 M parameters Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation. 0: 这是一个文本生成的例子 1: 这是一个文本生成的例子 2: 这是一个文本生成的例子 |
3. 加载微调后的模型
当您需要再次使用模型时,可以通过加载保存的模型状态和分词器来恢复模型:
1 2 3 4 5 6 |
from transformers import GPT2Tokenizer, GPT2LMHeadModel # 加载微调后的模型和分词器 model_path = "models/gpt2_finetuned" tokenizer = GPT2Tokenizer.from_pretrained(model_path) model = GPT2LMHeadModel.from_pretrained(model_path) |
代码 setup3.1.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
from transformers import GPT2Tokenizer, GPT2LMHeadModel import torch # 加载微调后的模型和分词器 model_path = "models/gpt2_finetuned" tokenizer = GPT2Tokenizer.from_pretrained(model_path) model = GPT2LMHeadModel.from_pretrained(model_path) # print the number of parameters in the model print(sum(p.numel() for p in model.parameters())/1e6, 'M parameters') # 文本生成 # 使用`__call__`方法生成`input_ids`和`attention_mask` inputs = tokenizer("这是一个文本生成的例子", return_tensors='pt', padding=True, truncation=True, max_length=50) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) # 提取`input_ids`和`attention_mask` input_ids = inputs['input_ids'] attention_mask = inputs['attention_mask'] input_ids = input_ids.to(device) attention_mask = attention_mask.to(device) # 文本生成,确保传入`attention_mask` sample_outputs = model.generate( input_ids, attention_mask=attention_mask, max_length=50, num_return_sequences=3, do_sample=True, # 启用采样以增加多样性 top_p=0.92, # 使用top-p采样 ) # 打印生成的文本 for i, sample_output in enumerate(sample_outputs): print("{}: {}".format(i, tokenizer.decode(sample_output, skip_special_tokens=True))) |
运行 setup3.1.py
1 2 3 4 5 6 |
$ python setup3.1.py 124.440576 M parameters Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation. 0: 这是一个文本生成的例子� 1: 这是一个文本生成的例子 2: 这是一个文本生成的例子 |
4. 增加数据量
增加中文数据量,添加论语里一段,试试效果,https://github.com/chinese-poetry/chinese-poetry/blob/master/%E8%AE%BA%E8%AF%AD/lunyu.json
代码 setup4.1.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
from transformers import GPT2Tokenizer, GPT2LMHeadModel, AdamW, get_linear_schedule_with_warmup import torch # 加载预训练模型和分词器 tokenizer = GPT2Tokenizer.from_pretrained('models/gpt2') model = GPT2LMHeadModel.from_pretrained('models/gpt2') # 为分词器设置填充令牌 # 确保为分词器设置了pad_token if tokenizer.pad_token is None: tokenizer.add_special_tokens({'pad_token': '[PAD]'}) model.resize_token_embeddings(len(tokenizer)) # 准备数据 texts = [ "子曰:“学而时习之,不亦说乎?有朋自远方来,不亦乐乎?人不知而不愠,不亦君子乎?”", "有子曰:“其为人也孝弟,而好犯上者,鲜矣;不好犯上而好作乱者,未之有也。君子务本,本立而道生。孝弟也者,其为仁之本与!”", "子曰:“巧言令色,鲜矣仁!”", "曾子曰:“吾日三省吾身:为人谋而不忠乎?与朋友交而不信乎?传不习乎?”", "子曰:“道千乘之国,敬事而信,节用而爱人,使民以时。”", "子曰:“弟子入则孝,出则弟,谨而信,泛爱众,而亲仁,行有余力,则以学文。”", "子夏曰:“贤贤易色;事父母,能竭其力;事君,能致其身;与朋友交,言而有信。虽曰未学,吾必谓之学矣。”", "子曰:“君子不重则不威,学则不固。主忠信,无友不如己者,过,则勿惮改。”", "曾子曰:“慎终追远,民德归厚矣。”", "子禽问于子贡曰:“夫子至于是邦也,必闻其政,求之与,抑与之与?”子贡曰:“夫子温、良、恭、俭、让以得之。夫子之求之也,其诸异乎人之求之与?”", "子曰:“父在,观其志;父没,观其行;三年无改于父之道,可谓孝矣。”", "有子曰:“礼之用,和为贵。先王之道,斯为美,小大由之。有所不行,知和而和,不以礼节之,亦不可行也。”", "有子曰:“信近于义,言可复也。恭近于礼,远耻辱也。因不失其亲,亦可宗也。”", "子曰:“君子食无求饱,居无求安,敏于事而慎于言,就有道而正焉。可谓好学也已。”", "子贡曰:“贫而无谄,富而无骄,何如?”子曰:“可也。未若贫而乐,富而好礼者也。”子贡曰:“《诗》云:‘如切如磋,如琢如磨’,其斯之谓与?”子曰:“赐也,始可与言《诗》已矣,告诸往而知来者。”", "子曰:“不患人之不己知,患不知人也。”" ] input_ids = [tokenizer.encode(text, return_tensors='pt', padding=True, truncation=True, max_length=512) for text in texts] # 准备模型和优化器 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) model.train() optimizer = AdamW(model.parameters(), lr=5e-5, no_deprecation_warning=True) # 微调epochs epochs = 1 for epoch in range(epochs): for input_id in input_ids: optimizer.zero_grad() input_id = input_id.to(device) outputs = model(input_id, labels=input_id) loss = outputs.loss loss.backward() optimizer.step() print(f"Epoch {epoch}: Loss {loss.item()}") # 完成微调后保存模型和分词器 model_path = "models/gpt2_finetuned" tokenizer.save_pretrained(model_path) model.save_pretrained(model_path) print(f"Model saved to {model_path}") # print the number of parameters in the model print(sum(p.numel() for p in model.parameters())/1e6, 'M parameters') # 文本生成 # 使用`__call__`方法生成`input_ids`和`attention_mask` inputs = tokenizer("子曰", return_tensors='pt', padding=True, truncation=True, max_length=50) # 提取`input_ids`和`attention_mask` input_ids = inputs['input_ids'] attention_mask = inputs['attention_mask'] input_ids = input_ids.to(device) attention_mask = attention_mask.to(device) # 文本生成,确保传入`attention_mask` sample_outputs = model.generate( input_ids, attention_mask=attention_mask, max_length=50, num_return_sequences=3, do_sample=True, # 启用采样以增加多样性 top_p=0.92, # 使用top-p采样 ) # 打印生成的文本 for i, sample_output in enumerate(sample_outputs): print("{}: {}".format(i, tokenizer.decode(sample_output, skip_special_tokens=True))) |
运行 setup3.1.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
$ python setup4.1.py Epoch 0: Loss 4.523581504821777 Model saved to models/gpt2_finetuned 124.440576 M parameters Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation. 0: 子曰生生戙暌�気合礒相��暗挟伒送稒羈友生剛�� 1: 子曰, , and, and, and, , Insects, , , , Saying, , Carnals , 2: 子曰連��連這勚連募��勖�砕逤勢勸歗砕逗徛乚逦 |
从上面来看,效果很差,因为epochs 只为1次
那现在把epochs 改为 100次
1 2 |
epochs = 100 |
运行 setup3.1.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
2$ python setup4.1.py Epoch 0: Loss 4.635136127471924 Epoch 1: Loss 3.7040677070617676 Epoch 2: Loss 3.099323034286499 Epoch 3: Loss 2.614231824874878 ...... Epoch 97: Loss 0.08564870804548264 Epoch 98: Loss 0.06109260767698288 Epoch 99: Loss 0.3170224130153656 Model saved to models/gpt2_finetuned 124.440576 M parameters Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation. 0: 子曰:“不患人之不己知,患不知人也。”子曰:“不知 1: 子曰:“父在,观其志;父没;父在;三年无� 2: 子曰:“不患人之不患人不己知,患不知人也。”良近于� |
现在再看运行结果,效果好多了
代码已经很完整地展示了如何使用Transformers库来微调并保存一个GPT-2模型,然后使用这个微调后的模型来生成文本。您的流程包括:
- 加载预训练的模型和分词器:使用
GPT2Tokenizer
和GPT2LMHeadModel
从本地路径'models/gpt2'
加载。 - 设置填充令牌:检查分词器的
pad_token
是否设置,如果没有,则添加一个,并扩展模型的词嵌入以适应新的令牌。 - 准备数据:将一系列文本编码为模型的输入ID,同时确保每个文本都进行了适当的填充和截断。
- 设置模型和优化器:将模型移到适当的设备(GPU或CPU),并初始化
AdamW
优化器。 - 进行微调:通过多个epochs遍历数据,进行梯度下降优化。
- 保存微调后的模型和分词器:将微调后的模型和分词器保存到指定路径。
- 生成文本:使用微调后的模型和分词器生成文本,显示了如何利用
attention_mask
进行生成。
接下来,为了确保代码的完整性和正确性,我会提供一些小的建议和改进:
- 验证模型保存路径:在保存模型之前,确保
model_path
指定的目录存在或者代码中有创建目录的逻辑。 - 微调期间的评估:考虑在微调过程中或之后添加评估步骤,以监控模型在验证集上的性能。这有助于判断模型是否过拟合或者还有改进的空间。
- 学习率调整:
AdamW
优化器初始化时使用了一个固定的学习率。在一些情况下,使用学习率调度器(例如,get_linear_schedule_with_warmup
)来在训练过程中动态调整学习率可能会提高模型性能。 - 更详细的生成控制:根据您的需求,考虑调整生成文本的参数(如
temperature
、top_k
、num_beams
等)以控制生成文本的多样性和创造性。
您的代码已经非常接近于一个完整的微调和文本生成流程。以上建议可以根据具体需求进行调整和实施。
5. 微调期间的评估
在微调期间进行模型评估是一个重要的步骤,它可以帮助您监控模型的学习进度,以及判断模型是否过拟合或者仍有改进的空间。以下是如何在微调期间进行评估的基本步骤:
5.1. 准备数据
首先,您需要将数据分成至少两部分:一部分用于训练(微调),另一部分用于评估(通常称为验证集)。这样,您可以在独立的数据上评估模型的性能,这有助于更准确地估计模型对未见数据的泛化能力。
5.2. 编写评估函数
评估函数的目标是计算和返回模型在验证集上的性能指标。对于语言模型,常见的性能指标包括困惑度(Perplexity)或者特定任务的准确率。
评估函数代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def evaluate(model, tokenizer, device, eval_data): model.eval() # 将模型设置为评估模式 total_eval_loss = 0 for batch in eval_data: inputs = tokenizer(batch, return_tensors='pt', padding=True, truncation=True, max_length=512) input_ids = inputs['input_ids'].to(device) attention_mask = inputs['attention_mask'].to(device) labels = input_ids.clone() # 对于语言模型,输入和标签通常相同 with torch.no_grad(): # 在评估期间不计算梯度 outputs = model(input_ids, attention_mask=attention_mask, labels=labels) loss = outputs.loss total_eval_loss += loss.item() avg_eval_loss = total_eval_loss / len(eval_data) model.train() # 将模型设置回训练模式 return avg_eval_loss |
5.3. 在微调循环中调用评估函数
在您的训练循环的每个epoch结束后,您可以调用评估函数来计算模型在验证集上的性能。然后,您可以根据需要调整模型的超参数或早期停止训练以避免过拟合。
1 2 3 4 5 6 7 8 9 10 |
# 假设 eval_texts 是您的验证集文本 eval_input_ids = [tokenizer.encode(text, return_tensors='pt', padding=True, truncation=True, max_length=512) for text in eval_texts] epochs = 100 for epoch in range(epochs): # 微调代码... eval_loss = evaluate(model, tokenizer, device, eval_input_ids) print(f"Epoch {epoch}: Eval Loss {eval_loss}") |
注意事项:
- 数据处理:确保评估数据的处理方式(如分词、填充)与训练数据一致。
- 模型模式:在评估前后确保正确设置模型的模式(
model.eval()
和model.train()
)。 - 性能指标:选择合适的性能指标来评估您的模型。对于不同的任务,可能需要不同的评估指标。
这个基本框架可以根据您的具体需求进行调整,例如引入更多的性能指标或对验证集进行批处理以提高评估效率。
setup5.1.py 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
from transformers import GPT2Tokenizer, GPT2LMHeadModel, AdamW, get_linear_schedule_with_warmup import torch # 加载预训练模型和分词器 tokenizer = GPT2Tokenizer.from_pretrained('models/gpt2') model = GPT2LMHeadModel.from_pretrained('models/gpt2') # 为分词器设置填充令牌 # 确保为分词器设置了pad_token if tokenizer.pad_token is None: tokenizer.add_special_tokens({'pad_token': '[PAD]'}) model.resize_token_embeddings(len(tokenizer)) # 准备数据 texts = [ # 学而篇 "子曰:“学而时习之,不亦说乎?有朋自远方来,不亦乐乎?人不知而不愠,不亦君子乎?”", "有子曰:“其为人也孝弟,而好犯上者,鲜矣;不好犯上而好作乱者,未之有也。君子务本,本立而道生。孝弟也者,其为仁之本与!”", "子曰:“巧言令色,鲜矣仁!”", "曾子曰:“吾日三省吾身:为人谋而不忠乎?与朋友交而不信乎?传不习乎?”", "子曰:“道千乘之国,敬事而信,节用而爱人,使民以时。”", "子曰:“弟子入则孝,出则弟,谨而信,泛爱众,而亲仁,行有余力,则以学文。”", "子夏曰:“贤贤易色;事父母,能竭其力;事君,能致其身;与朋友交,言而有信。虽曰未学,吾必谓之学矣。”", "子曰:“君子不重则不威,学则不固。主忠信,无友不如己者,过,则勿惮改。”", "曾子曰:“慎终追远,民德归厚矣。”", "子禽问于子贡曰:“夫子至于是邦也,必闻其政,求之与,抑与之与?”子贡曰:“夫子温、良、恭、俭、让以得之。夫子之求之也,其诸异乎人之求之与?”", "子曰:“父在,观其志;父没,观其行;三年无改于父之道,可谓孝矣。”", "有子曰:“礼之用,和为贵。先王之道,斯为美,小大由之。有所不行,知和而和,不以礼节之,亦不可行也。”", "有子曰:“信近于义,言可复也。恭近于礼,远耻辱也。因不失其亲,亦可宗也。”", "子曰:“君子食无求饱,居无求安,敏于事而慎于言,就有道而正焉。可谓好学也已。”", "子贡曰:“贫而无谄,富而无骄,何如?”子曰:“可也。未若贫而乐,富而好礼者也。”子贡曰:“《诗》云:‘如切如磋,如琢如磨’,其斯之谓与?”子曰:“赐也,始可与言《诗》已矣,告诸往而知来者。”", "子曰:“不患人之不己知,患不知人也。”" ] input_ids = [tokenizer.encode(text, return_tensors='pt', padding=True, truncation=True, max_length=512) for text in texts] eval_texts = [ # 为政篇 "子曰:“为政以德,譬如北辰,居其所而众星共之。”", "子曰:“《诗》三百,一言以蔽之,曰:‘思无邪’。”", "子曰:“道之以政,齐之以刑,民免而无耻。道之以德,齐之以礼,有耻且格。”", "子曰:“吾十有五而志于学,三十而立,四十而不惑,五十而知天命,六十而耳顺,七十而从心所欲,不逾矩。”", "孟懿子问孝,子曰:“无违。”樊迟御,子告之曰:“孟孙问孝于我,我对曰‘无违’。”樊迟曰:“何谓也?”子曰:“生,事之以礼;死,葬之以礼,祭之以礼。”", "孟武伯问孝。子曰:“父母唯其疾之忧。”", "子游问孝。子曰:“今之孝者,是谓能养。至于犬马皆能有养;不敬,何以别乎?”", "子夏问孝。子曰:“色难。有事,弟子服其劳;有酒食,先生馔,曾是以为孝乎?”", "子曰:“吾与回言终日,不违,如愚。退而省其私,亦足以发,回也不愚。”", "子曰:“视其所以,观其所由,察其所安,人焉廋哉?人焉廋哉?”", "子曰:“温故而知新,可以为师矣。”", "子曰:“君子不器。”", "子贡问君子。子曰:“先行其言而后从之。”", "子曰:“君子周而不比,小人比而不周。”", "子曰:“学而不思则罔,思而不学则殆。”", "子曰:“攻乎异端,斯害也已!”", "子曰:“由,诲汝知之乎!知之为知之,不知为不知,是知也。”", "子张学干禄。子曰:“多闻阙疑,慎言其余,则寡尤;多见阙殆,慎行其余,则寡悔。言寡尤,行寡悔,禄在其中矣。”", "哀公问曰:“何为则民服?”孔子对曰:“举直错诸枉,则民服;举枉错诸直,则民不服。”", "季康子问:“使民敬、忠以劝,如之何?”子曰:“临之以庄,则敬;孝慈,则忠;举善而教不能,则劝。”", "或谓孔子曰:“子奚不为政?”子曰:“《书》云:‘孝乎惟孝,友于兄弟,施于有政。’是亦为政,奚其为为政?”", "子曰:“人而无信,不知其可也。大车无輗,小车无軏,其何以行之哉?”", "子张问:“十世可知也?”子曰:“殷因于夏礼,所损益,可知也;周因于殷礼,所损益,可知也。其或继周者,虽百世,可知也。”", "子曰:“非其鬼而祭之,谄也;见义不为,无勇也。”" ] # 假设 eval_texts 是您的验证集文本 # eval_input_ids = [tokenizer.encode(text, return_tensors='pt', padding=True, truncation=True, max_length=512) for text in eval_texts] # 准备模型和优化器 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) model.train() optimizer = AdamW(model.parameters(), lr=5e-5, no_deprecation_warning=True) def evaluate(model, tokenizer, device, eval_texts): model.eval() # 将模型设置为评估模式 inputs = tokenizer(eval_texts, return_tensors='pt', padding=True, truncation=True, max_length=512) input_ids = inputs['input_ids'].to(device) attention_mask = inputs['attention_mask'].to(device) labels = input_ids.clone() # 对于语言模型,输入和标签通常相同 with torch.no_grad(): # 在评估期间不计算梯度 outputs = model(input_ids, attention_mask=attention_mask, labels=labels) loss = outputs.loss eval_loss = loss.item() model.train() # 将模型设置回训练模式 return eval_loss # 微调epochs epochs = 100 for epoch in range(epochs): for input_id in input_ids: optimizer.zero_grad() input_id = input_id.to(device) outputs = model(input_id, labels=input_id) loss = outputs.loss loss.backward() optimizer.step() eval_loss = evaluate(model, tokenizer, device, eval_texts) print(f"Epoch {epoch}: Loss {loss.item()}, Eval Loss {eval_loss}") # 完成微调后保存模型和分词器 model_path = "models/gpt2_finetuned" tokenizer.save_pretrained(model_path) model.save_pretrained(model_path) print(f"Model saved to {model_path}") # print the number of parameters in the model print(sum(p.numel() for p in model.parameters())/1e6, 'M parameters') # 文本生成 # 使用`__call__`方法生成`input_ids`和`attention_mask` inputs = tokenizer("子曰", return_tensors='pt', padding=True, truncation=True, max_length=50) # 提取`input_ids`和`attention_mask` input_ids = inputs['input_ids'] attention_mask = inputs['attention_mask'] input_ids = input_ids.to(device) attention_mask = attention_mask.to(device) # 文本生成,确保传入`attention_mask` sample_outputs = model.generate( input_ids, attention_mask=attention_mask, max_length=50, num_return_sequences=3, do_sample=True, # 启用采样以增加多样性 top_p=0.92, # 使用top-p采样 ) # 打印生成的文本 for i, sample_output in enumerate(sample_outputs): print("{}: {}".format(i, tokenizer.decode(sample_output, skip_special_tokens=True))) |
运行 setup5.1.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ python setup5.1.py Epoch 0: Loss 4.835778713226318, Eval Loss 52.580928802490234 Epoch 1: Loss 3.648055076599121, Eval Loss 58.712120056152344 Epoch 2: Loss 3.149979591369629, Eval Loss 62.69910430908203 Epoch 3: Loss 2.6165666580200195, Eval Loss 68.0480728149414 ...... Epoch 97: Loss 0.04511437565088272, Eval Loss 76.11646270751953 Epoch 98: Loss 0.04040110483765602, Eval Loss 73.62688446044922 Epoch 99: Loss 0.04995773732662201, Eval Loss 73.57897186279297 Model saved to models/gpt2_finetuned 124.440576 M parameters Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation. 0: 子曰:“君子不重则不威,学则不固。主忠信,无友不如己 1: 子曰:“巧言令色,鲜矣仁!”曰!”色。色 2: 子曰:“道千乘之国,敬事而信,节用而爱人,使民� |
从提供的输出中可以看出,训练损失(Loss)随着epoch的增加而持续下降,这表明模型在训练集上的性能正在改善,学习到了数据的分布。然而,评估损失(Eval Loss)随着训练的进行不仅没有下降,反而有所增加,这通常是过拟合的一个迹象。过拟合发生时,模型在训练数据上表现良好,但在未见过的数据上表现较差。
解决过拟合的策略:
- 增加数据量:如果可能的话,增加更多的训练数据可以帮助模型学习到更广泛的分布,从而提高其泛化能力。
- 正则化:应用正则化技术(如权重衰减)可以减少模型的复杂度,防止过拟合。在使用
AdamW
优化器时,权重衰减已经作为参数被内置实现。 - 早停(Early Stopping):监控评估损失,并在损失开始增加时停止训练。这可以防止模型在训练数据上过度拟合。
- 数据增强:对训练数据应用轻微的变化或增强,以增加数据的多样性。
- 简化模型:如果模型过于复杂,可能会捕捉到训练数据中的噪声。简化模型的结构可能有助于提高泛化能力。
- Dropout:在训练期间随机丢弃一部分网络的连接,可以有效减少过拟合。
生成文本的质量
生成的文本看起来与训练文本的风格一致,显示出模型已经学习到了一些文本的生成规律。如果生成的文本质量不如预期,可以尝试调整生成参数(如温度temperature
、最大长度max_length
、top_k
、top_p
等)来改善结果。
此外,过拟合可能会导致模型在生成时过分依赖训练数据,从而降低生成文本的多样性。在这种情况下,上述减少过拟合的策略也可能帮助改善生成的质量。
结论
根据您的输出,建议重点关注减少过拟合,并根据需要调整模型参数或训练策略。这可能包括数据处理方面的改进、模型结构的调整、优化器参数的微调,以及生成策略的优化。
6. 在训练损失最低的时候保存模型
为了在训练过程中当损失最低时保存模型,您可以在训练循环中添加一些逻辑来追踪最低的训练损失,并仅在当前epoch的损失低于之前所有epoch的最低损失时保存模型。这种方法确保了您保存的模型是在训练过程中表现最好的那一个。
setup6.1.py 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
from transformers import GPT2Tokenizer, GPT2LMHeadModel, AdamW, get_linear_schedule_with_warmup import torch # 加载预训练模型和分词器 tokenizer = GPT2Tokenizer.from_pretrained('models/gpt2') model = GPT2LMHeadModel.from_pretrained('models/gpt2') # 为分词器设置填充令牌 # 确保为分词器设置了pad_token if tokenizer.pad_token is None: tokenizer.add_special_tokens({'pad_token': '[PAD]'}) model.resize_token_embeddings(len(tokenizer)) # 准备数据 texts = [ # 学而篇 "子曰:“学而时习之,不亦说乎?有朋自远方来,不亦乐乎?人不知而不愠,不亦君子乎?”", "有子曰:“其为人也孝弟,而好犯上者,鲜矣;不好犯上而好作乱者,未之有也。君子务本,本立而道生。孝弟也者,其为仁之本与!”", "子曰:“巧言令色,鲜矣仁!”", "曾子曰:“吾日三省吾身:为人谋而不忠乎?与朋友交而不信乎?传不习乎?”", "子曰:“道千乘之国,敬事而信,节用而爱人,使民以时。”", "子曰:“弟子入则孝,出则弟,谨而信,泛爱众,而亲仁,行有余力,则以学文。”", "子夏曰:“贤贤易色;事父母,能竭其力;事君,能致其身;与朋友交,言而有信。虽曰未学,吾必谓之学矣。”", "子曰:“君子不重则不威,学则不固。主忠信,无友不如己者,过,则勿惮改。”", "曾子曰:“慎终追远,民德归厚矣。”", "子禽问于子贡曰:“夫子至于是邦也,必闻其政,求之与,抑与之与?”子贡曰:“夫子温、良、恭、俭、让以得之。夫子之求之也,其诸异乎人之求之与?”", "子曰:“父在,观其志;父没,观其行;三年无改于父之道,可谓孝矣。”", "有子曰:“礼之用,和为贵。先王之道,斯为美,小大由之。有所不行,知和而和,不以礼节之,亦不可行也。”", "有子曰:“信近于义,言可复也。恭近于礼,远耻辱也。因不失其亲,亦可宗也。”", "子曰:“君子食无求饱,居无求安,敏于事而慎于言,就有道而正焉。可谓好学也已。”", "子贡曰:“贫而无谄,富而无骄,何如?”子曰:“可也。未若贫而乐,富而好礼者也。”子贡曰:“《诗》云:‘如切如磋,如琢如磨’,其斯之谓与?”子曰:“赐也,始可与言《诗》已矣,告诸往而知来者。”", "子曰:“不患人之不己知,患不知人也。”" ] input_ids = [tokenizer.encode(text, return_tensors='pt', padding=True, truncation=True, max_length=512) for text in texts] eval_texts = [ # 为政篇 "子曰:“为政以德,譬如北辰,居其所而众星共之。”", "子曰:“《诗》三百,一言以蔽之,曰:‘思无邪’。”", "子曰:“道之以政,齐之以刑,民免而无耻。道之以德,齐之以礼,有耻且格。”", "子曰:“吾十有五而志于学,三十而立,四十而不惑,五十而知天命,六十而耳顺,七十而从心所欲,不逾矩。”", "孟懿子问孝,子曰:“无违。”樊迟御,子告之曰:“孟孙问孝于我,我对曰‘无违’。”樊迟曰:“何谓也?”子曰:“生,事之以礼;死,葬之以礼,祭之以礼。”", "孟武伯问孝。子曰:“父母唯其疾之忧。”", "子游问孝。子曰:“今之孝者,是谓能养。至于犬马皆能有养;不敬,何以别乎?”", "子夏问孝。子曰:“色难。有事,弟子服其劳;有酒食,先生馔,曾是以为孝乎?”", "子曰:“吾与回言终日,不违,如愚。退而省其私,亦足以发,回也不愚。”", "子曰:“视其所以,观其所由,察其所安,人焉廋哉?人焉廋哉?”", "子曰:“温故而知新,可以为师矣。”", "子曰:“君子不器。”" ] # 假设 eval_texts 是您的验证集文本 # eval_input_ids = [tokenizer.encode(text, return_tensors='pt', padding=True, truncation=True, max_length=512) for text in eval_texts] # 准备模型和优化器 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) model.train() optimizer = AdamW(model.parameters(), lr=5e-5, no_deprecation_warning=True) def evaluate(model, tokenizer, device, eval_texts): model.eval() # 将模型设置为评估模式 inputs = tokenizer(eval_texts, return_tensors='pt', padding=True, truncation=True, max_length=512) input_ids = inputs['input_ids'].to(device) attention_mask = inputs['attention_mask'].to(device) labels = input_ids.clone() # 对于语言模型,输入和标签通常相同 with torch.no_grad(): # 在评估期间不计算梯度 outputs = model(input_ids, attention_mask=attention_mask, labels=labels) loss = outputs.loss eval_loss = loss.item() model.train() # 将模型设置回训练模式 return eval_loss # 初始化最低损失为无穷大 best_loss = float('inf') # 完成微调后保存模型和分词器 model_path = "models/gpt2_finetuned" # 微调epochs epochs = 100 for epoch in range(epochs): total_loss = 0 model.train() # 确保模型处于训练模式 for input_id in input_ids: optimizer.zero_grad() input_id = input_id.to(device) outputs = model(input_id, labels=input_id) loss = outputs.loss loss.backward() optimizer.step() total_loss += loss.item() # 计算平均损失 avg_loss = total_loss / len(input_ids) # 评估验证集损失 eval_loss = evaluate(model, tokenizer, device, eval_texts) print(f"Epoch {epoch}: Loss {loss.item()}, Eval Loss {eval_loss}") # 如果这个epoch的平均损失低于之前的所有epoch,则保存模型 if avg_loss < best_loss: best_loss = avg_loss # 更新最低损失 # 保存模型和分词器 tokenizer.save_pretrained(model_path) model.save_pretrained(model_path) print(f"Model saved to {model_path} at epoch {epoch} with training loss {best_loss}") print(f"Training completed. Best Loss: {best_loss}") # print the number of parameters in the model print(sum(p.numel() for p in model.parameters())/1e6, 'M parameters') # 文本生成 # 使用`__call__`方法生成`input_ids`和`attention_mask` inputs = tokenizer("子曰", return_tensors='pt', padding=True, truncation=True, max_length=50) # 提取`input_ids`和`attention_mask` input_ids = inputs['input_ids'] attention_mask = inputs['attention_mask'] input_ids = input_ids.to(device) attention_mask = attention_mask.to(device) # 文本生成,确保传入`attention_mask` sample_outputs = model.generate( input_ids, attention_mask=attention_mask, max_length=150, num_return_sequences=3, do_sample=True, # 启用采样以增加多样性 top_p=0.92, # 使用top-p采样 ) # 打印生成的文本 for i, sample_output in enumerate(sample_outputs): print("{}: {}".format(i, tokenizer.decode(sample_output, skip_special_tokens=True))) |
运行 setup6.1.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$ python setup6.1.py Epoch 0: Loss 4.671284198760986, Eval Loss 64.36437225341797 Model saved to models/gpt2_finetuned at epoch 0 with training loss 12.848731905221939 Epoch 1: Loss 3.926487922668457, Eval Loss 64.1885986328125 Model saved to models/gpt2_finetuned at epoch 1 with training loss 4.164433002471924 Epoch 2: Loss 3.4086780548095703, Eval Loss 57.92185592651367 ...... Epoch 95: Loss 0.059735193848609924, Eval Loss 71.14655303955078 Epoch 96: Loss 0.08635672181844711, Eval Loss 87.75948333740234 Epoch 97: Loss 0.1042788103222847, Eval Loss 82.40111541748047 Epoch 98: Loss 0.09466272592544556, Eval Loss 78.49815368652344 Epoch 99: Loss 0.03142295777797699, Eval Loss 74.78617095947266 Model saved to models/gpt2_finetuned at epoch 99 with training loss 0.061062929569743574 Training completed. Best Loss: 0.061062929569743574 124.440576 M parameters Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation. 0: 子曰:“父在,观其志;父没,观其行;三年无改于父之道,可谓孝矣。”子曰:“父在,可谓孝矣。谓孝矣。”子曰:“吾必谓 之孝,学文学矯上� 1: 子曰:“父在,观其志;父没,观其行;三年无改于父之道,可谓孝矣。”因改于父为,可谓孝矣。”节其行;三年无改于节用 而有信。”孝弟也,其 2: 子曰:“不患人之不己知,患不知人也。”不知人也。”不知而好�乎,不愠知人也。”不知而好炟�而好作也。”不知而好作也。…不知而好炣勿其力,不愠知而不愠改。”不� |
这段代码在每个epoch结束时计算了平均训练损失,并将其与迄今为止观察到的最低损失进行比较。如果当前epoch的损失低于之前的最低损失,则会保存当前模型和分词器到指定路径。这样,您最终保存的模型将是在整个训练过程中损失最低的模型。
请注意,这个示例仅关注训练损失。在实际应用中,您可能还想根据验证损失来保存模型,因为这通常更能反映模型在未见数据上的泛化能力。调整这段代码以根据验证损失保存模型是相对直接的:只需将avg_loss
和best_loss
替换为eval_loss
和相应的最佳验证损失变量即可。
7. 优化模型保存代码和输出代码
要在训练损失最低时保存模型,您需要在训练过程中跟踪最低的训练损失,并在达到新的最低点时保存模型。这通常涉及到在每个epoch结束时比较当前损失与迄今为止记录的最低损失,并在当前损失更低时更新最低损失并保存模型。
关于生成文本时不依赖于max_length=150
,而是输出适当长度的文本,您可以使用generate
方法的多个参数来控制生成的长度和质量,但不能直接指定“适当的”长度,因为“适当”的定义可能因上下文而异。不过,您可以使用如eos_token_id
来指定一个结束令牌,使得模型在生成到这个令牌时停止。如果您希望模型在逻辑上完成文本后停止,而不是硬编码一个特定的最大长度
setup7.1.py 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
from transformers import GPT2Tokenizer, GPT2LMHeadModel, AdamW, get_linear_schedule_with_warmup import torch # 加载预训练模型和分词器 tokenizer = GPT2Tokenizer.from_pretrained('models/gpt2') model = GPT2LMHeadModel.from_pretrained('models/gpt2') # 为分词器设置填充令牌 # 确保为分词器设置了pad_token if tokenizer.pad_token is None: tokenizer.add_special_tokens({'pad_token': '[PAD]'}) model.resize_token_embeddings(len(tokenizer)) # 准备数据 texts = [ # 学而篇 "子曰:“学而时习之,不亦说乎?有朋自远方来,不亦乐乎?人不知而不愠,不亦君子乎?”", "有子曰:“其为人也孝弟,而好犯上者,鲜矣;不好犯上而好作乱者,未之有也。君子务本,本立而道生。孝弟也者,其为仁之本与!”", "子曰:“巧言令色,鲜矣仁!”", "曾子曰:“吾日三省吾身:为人谋而不忠乎?与朋友交而不信乎?传不习乎?”", "子曰:“道千乘之国,敬事而信,节用而爱人,使民以时。”", "子曰:“弟子入则孝,出则弟,谨而信,泛爱众,而亲仁,行有余力,则以学文。”", "子夏曰:“贤贤易色;事父母,能竭其力;事君,能致其身;与朋友交,言而有信。虽曰未学,吾必谓之学矣。”", "子曰:“君子不重则不威,学则不固。主忠信,无友不如己者,过,则勿惮改。”", "曾子曰:“慎终追远,民德归厚矣。”", "子禽问于子贡曰:“夫子至于是邦也,必闻其政,求之与,抑与之与?”子贡曰:“夫子温、良、恭、俭、让以得之。夫子之求之也,其诸异乎人之求之与?”", "子曰:“父在,观其志;父没,观其行;三年无改于父之道,可谓孝矣。”", "有子曰:“礼之用,和为贵。先王之道,斯为美,小大由之。有所不行,知和而和,不以礼节之,亦不可行也。”", "有子曰:“信近于义,言可复也。恭近于礼,远耻辱也。因不失其亲,亦可宗也。”", "子曰:“君子食无求饱,居无求安,敏于事而慎于言,就有道而正焉。可谓好学也已。”", "子贡曰:“贫而无谄,富而无骄,何如?”子曰:“可也。未若贫而乐,富而好礼者也。”子贡曰:“《诗》云:‘如切如磋,如琢如磨’,其斯之谓与?”子曰:“赐也,始可与言《诗》已矣,告诸往而知来者。”", "子曰:“不患人之不己知,患不知人也。”" ] input_ids = [tokenizer.encode(text, return_tensors='pt', padding=True, truncation=True, max_length=512) for text in texts] eval_texts = [ # 为政篇 "子曰:“为政以德,譬如北辰,居其所而众星共之。”", "子曰:“《诗》三百,一言以蔽之,曰:‘思无邪’。”", "子曰:“道之以政,齐之以刑,民免而无耻。道之以德,齐之以礼,有耻且格。”", "子曰:“吾十有五而志于学,三十而立,四十而不惑,五十而知天命,六十而耳顺,七十而从心所欲,不逾矩。”", "孟懿子问孝,子曰:“无违。”樊迟御,子告之曰:“孟孙问孝于我,我对曰‘无违’。”樊迟曰:“何谓也?”子曰:“生,事之以礼;死,葬之以礼,祭之以礼。”", "孟武伯问孝。子曰:“父母唯其疾之忧。”", "子游问孝。子曰:“今之孝者,是谓能养。至于犬马皆能有养;不敬,何以别乎?”", "子夏问孝。子曰:“色难。有事,弟子服其劳;有酒食,先生馔,曾是以为孝乎?”", "子曰:“吾与回言终日,不违,如愚。退而省其私,亦足以发,回也不愚。”", "子曰:“视其所以,观其所由,察其所安,人焉廋哉?人焉廋哉?”", "子曰:“温故而知新,可以为师矣。”", "子曰:“君子不器。”" ] # 假设 eval_texts 是您的验证集文本 # eval_input_ids = [tokenizer.encode(text, return_tensors='pt', padding=True, truncation=True, max_length=512) for text in eval_texts] # 准备模型和优化器 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) model.train() optimizer = AdamW(model.parameters(), lr=5e-5, no_deprecation_warning=True) def evaluate(model, tokenizer, device, eval_texts): model.eval() # 将模型设置为评估模式 inputs = tokenizer(eval_texts, return_tensors='pt', padding=True, truncation=True, max_length=512) input_ids = inputs['input_ids'].to(device) attention_mask = inputs['attention_mask'].to(device) labels = input_ids.clone() # 对于语言模型,输入和标签通常相同 with torch.no_grad(): # 在评估期间不计算梯度 outputs = model(input_ids, attention_mask=attention_mask, labels=labels) loss = outputs.loss eval_loss = loss.item() model.train() # 将模型设置回训练模式 return eval_loss # 初始化最低损失为无穷大 best_loss = float('inf') best_model_state = None # 完成微调后保存模型和分词器 model_path = "models/gpt2_finetuned" # 微调epochs epochs = 100 for epoch in range(epochs): current_train_loss = 0 model.train() # 确保模型处于训练模式 for input_id in input_ids: optimizer.zero_grad() input_id = input_id.to(device) outputs = model(input_id, labels=input_id) loss = outputs.loss loss.backward() optimizer.step() current_train_loss = loss.item() # 评估验证集损失 eval_loss = evaluate(model, tokenizer, device, eval_texts) #print(f"Epoch {epoch}: Loss {loss.item()}, Eval Loss {eval_loss}") # 检查当前损失是否是迄今为止最低的 if current_train_loss < best_loss: best_loss = current_train_loss best_model_state = model.state_dict() # 保存最佳模型状态 print(f"New best model found at epoch {epoch} with training loss {best_loss} and saved ") # 在训练结束后保存最佳模型状态 model.load_state_dict(best_model_state) model.save_pretrained(model_path) tokenizer.save_pretrained(model_path) print(f"Best model saved to {model_path} with training loss {best_loss}") # print the number of parameters in the model print(sum(p.numel() for p in model.parameters())/1e6, 'M parameters') # 文本生成 # 使用`__call__`方法生成`input_ids`和`attention_mask` inputs = tokenizer("子曰", return_tensors='pt', padding=True, truncation=True, max_length=50) # 提取`input_ids`和`attention_mask` input_ids = inputs['input_ids'] attention_mask = inputs['attention_mask'] input_ids = input_ids.to(device) attention_mask = attention_mask.to(device) # 文本生成,确保传入`attention_mask` sample_outputs = model.generate( input_ids, eos_token_id=tokenizer.eos_token_id, attention_mask=attention_mask, max_length=100, num_return_sequences=3, do_sample=True, # 启用采样以增加多样性 top_p=0.98, # 使用top-p采样 ) # 打印生成的文本 for i, sample_output in enumerate(sample_outputs): print("{}: {}".format(i, tokenizer.decode(sample_output, skip_special_tokens=True))) |
运行 setup7.1.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ python setup7.1.py New best model found at epoch 0 with training loss 4.69078254699707 and saved New best model found at epoch 1 with training loss 4.215892314910889 and saved New best model found at epoch 2 with training loss 3.143723964691162 and saved New best model found at epoch 3 with training loss 2.4889767169952393 and saved New best model found at epoch 5 with training loss 1.9887076616287231 and saved New best model found at epoch 6 with training loss 1.8634755611419678 and saved New best model found at epoch 8 with training loss 1.5359219312667847 and saved ...... New best model found at epoch 92 with training loss 0.03972531855106354 and saved New best model found at epoch 96 with training loss 0.039385080337524414 and saved Best model saved to models/gpt2_finetuned with training loss 0.039385080337524414 124.440576 M parameters Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation. 0: 子曰:“巧言令色,鲜矣仁!”子入则弟!”子求之:“可谓之也。”子谓之知人!”子之亦不知而和!” 1: 子曰:“不患人之不己知,患不知人也。”子不忠信,患不知人也。”子求�不知,患不知人知和,患不知于知 2: 子曰:“不亦说乎?有信,言可也。恭近于义,亦可复也。恭近于礼,亦可宗也。”子曰:“可也。”子曰� |
8. 加载最后微调后的模型
setup7.1.py 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
from transformers import GPT2Tokenizer, GPT2LMHeadModel import torch # 加载微调后的模型和分词器 model_path = "models/gpt2_finetuned" tokenizer = GPT2Tokenizer.from_pretrained(model_path) model = GPT2LMHeadModel.from_pretrained(model_path) # print the number of parameters in the model print(sum(p.numel() for p in model.parameters())/1e6, 'M parameters') # 文本生成 # 使用`__call__`方法生成`input_ids`和`attention_mask` inputs = tokenizer("子曰:", return_tensors='pt', padding=True, truncation=True, max_length=50) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) # 提取`input_ids`和`attention_mask` input_ids = inputs['input_ids'] attention_mask = inputs['attention_mask'] input_ids = input_ids.to(device) attention_mask = attention_mask.to(device) # 文本生成,确保传入`attention_mask` sample_outputs = model.generate( input_ids, attention_mask=attention_mask, max_length=100, num_return_sequences=3, do_sample=True, # 启用采样以增加多样性 top_p=0.98, # 使用top-p采样 ) # 打印生成的文本 for i, sample_output in enumerate(sample_outputs): print("{}: {}".format(i, tokenizer.decode(sample_output, skip_special_tokens=True))) |
运行 setup8.1.py
1 2 3 4 5 6 7 |
$ python setup8.1.py 124.440576 M parameters Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation. 0: 子曰:“贤贤易色;事父母,能竭其力;事君,能致其身;与朋友交,言而有信。虽曰未学,吾� 1: 子曰:“道千乘之国,敬事而信,节用而爱人,使民以时。”子曰:“《诗》云,君子之以礼节之,其诸往而 2: 子曰:“不患人之不己知,患不知人也。”子曰:“不知人也。”子贡曰:“不知人也。”子温、恭、俭、让以得之。� |
可以看到:”不知人也“ 是新词?
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 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
from transformers import GPT2Tokenizer, GPT2LMHeadModel, AdamW, get_linear_schedule_with_warmup import torch # 加载预训练模型和分词器 tokenizer = GPT2Tokenizer.from_pretrained('models/gpt2-xl') model = GPT2LMHeadModel.from_pretrained('models/gpt2-xl') # 为分词器设置填充令牌 # 确保为分词器设置了pad_token if tokenizer.pad_token is None: tokenizer.add_special_tokens({'pad_token': '[PAD]'}) model.resize_token_embeddings(len(tokenizer)) # 准备数据 texts = [ # 学而篇 "子曰:“学而时习之,不亦说乎?有朋自远方来,不亦乐乎?人不知而不愠,不亦君子乎?”", "有子曰:“其为人也孝弟,而好犯上者,鲜矣;不好犯上而好作乱者,未之有也。君子务本,本立而道生。孝弟也者,其为仁之本与!”", "子曰:“巧言令色,鲜矣仁!”", "曾子曰:“吾日三省吾身:为人谋而不忠乎?与朋友交而不信乎?传不习乎?”", "子曰:“道千乘之国,敬事而信,节用而爱人,使民以时。”", "子曰:“弟子入则孝,出则弟,谨而信,泛爱众,而亲仁,行有余力,则以学文。”", "子夏曰:“贤贤易色;事父母,能竭其力;事君,能致其身;与朋友交,言而有信。虽曰未学,吾必谓之学矣。”", "子曰:“君子不重则不威,学则不固。主忠信,无友不如己者,过,则勿惮改。”", "曾子曰:“慎终追远,民德归厚矣。”", "子禽问于子贡曰:“夫子至于是邦也,必闻其政,求之与,抑与之与?”", "子贡曰:“夫子温、良、恭、俭、让以得之。夫子之求之也,其诸异乎人之求之与?”", "子曰:“父在,观其志;父没,观其行;三年无改于父之道,可谓孝矣。”", "有子曰:“礼之用,和为贵。先王之道,斯为美,小大由之。有所不行,知和而和,不以礼节之,亦不可行也。”", "有子曰:“信近于义,言可复也。恭近于礼,远耻辱也。因不失其亲,亦可宗也。”", "子曰:“君子食无求饱,居无求安,敏于事而慎于言,就有道而正焉。可谓好学也已。”", "子贡曰:“贫而无谄,富而无骄,何如?”", "子曰:“可也。未若贫而乐,富而好礼者也。”", "子贡曰:“《诗》云:‘如切如磋,如琢如磨’,其斯之谓与?”", "子曰:“赐也,始可与言《诗》已矣,告诸往而知来者。”", "子曰:“不患人之不己知,患不知人也。”" ] input_ids = [tokenizer.encode(text, return_tensors='pt', padding=True, truncation=True, max_length=512) for text in texts] eval_texts = [ # 为政篇 "子曰:“为政以德,譬如北辰,居其所而众星共之。”", "子曰:“《诗》三百,一言以蔽之,曰:‘思无邪’。”", "子曰:“道之以政,齐之以刑,民免而无耻。道之以德,齐之以礼,有耻且格。”", "子曰:“吾十有五而志于学,三十而立,四十而不惑,五十而知天命,六十而耳顺,七十而从心所欲,不逾矩。”", "孟懿子问孝,子曰:“无违。”樊迟御,子告之曰:“孟孙问孝于我,我对曰‘无违’。”樊迟曰:“何谓也?”", "子曰:“生,事之以礼;死,葬之以礼,祭之以礼。”", "孟武伯问孝。子曰:“父母唯其疾之忧。”", "子游问孝。子曰:“今之孝者,是谓能养。至于犬马皆能有养;不敬,何以别乎?”", "子夏问孝。子曰:“色难。有事,弟子服其劳;有酒食,先生馔,曾是以为孝乎?”", "子曰:“吾与回言终日,不违,如愚。退而省其私,亦足以发,回也不愚。”", "子曰:“视其所以,观其所由,察其所安,人焉廋哉?人焉廋哉?”", "子曰:“温故而知新,可以为师矣。”", "子曰:“君子不器。”" ] # 假设 eval_texts 是您的验证集文本 # eval_input_ids = [tokenizer.encode(text, return_tensors='pt', padding=True, truncation=True, max_length=512) for text in eval_texts] # 准备模型和优化器 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) model.train() optimizer = AdamW(model.parameters(), lr=5e-5, no_deprecation_warning=True) def evaluate(model, tokenizer, device, eval_texts): model.eval() # 将模型设置为评估模式 inputs = tokenizer(eval_texts, return_tensors='pt', padding=True, truncation=True, max_length=512) input_ids = inputs['input_ids'].to(device) attention_mask = inputs['attention_mask'].to(device) labels = input_ids.clone() # 对于语言模型,输入和标签通常相同 with torch.no_grad(): # 在评估期间不计算梯度 outputs = model(input_ids, attention_mask=attention_mask, labels=labels) loss = outputs.loss eval_loss = loss.item() model.train() # 将模型设置回训练模式 return eval_loss # 初始化最低损失为无穷大 best_loss = float('inf') best_model_state = None # 完成微调后保存模型和分词器 model_path = "models/gpt2-xl_finetuned" # 微调epochs epochs = 100 for epoch in range(epochs): current_train_loss = 0 model.train() # 确保模型处于训练模式 for input_id in input_ids: optimizer.zero_grad() input_id = input_id.to(device) outputs = model(input_id, labels=input_id) loss = outputs.loss loss.backward() optimizer.step() current_train_loss = loss.item() # 评估验证集损失 eval_loss = evaluate(model, tokenizer, device, eval_texts) #print(f"Epoch {epoch}: Loss {loss.item()}, Eval Loss {eval_loss}") # 检查当前损失是否是迄今为止最低的 if current_train_loss < best_loss: best_loss = current_train_loss best_model_state = model.state_dict() # 保存最佳模型状态 print(f"New best model found at epoch {epoch} with training loss {best_loss} and saved ") # 在训练结束后保存最佳模型状态 model.load_state_dict(best_model_state) model.save_pretrained(model_path) tokenizer.save_pretrained(model_path) print(f"Best model saved to {model_path} with training loss {best_loss}") # print the number of parameters in the model print(sum(p.numel() for p in model.parameters())/1e6, 'M parameters') # 文本生成 # 使用`__call__`方法生成`input_ids`和`attention_mask` inputs = tokenizer("子曰", return_tensors='pt', padding=True, truncation=True, max_length=50) # 提取`input_ids`和`attention_mask` input_ids = inputs['input_ids'] attention_mask = inputs['attention_mask'] input_ids = input_ids.to(device) attention_mask = attention_mask.to(device) # 文本生成,确保传入`attention_mask` sample_outputs = model.generate( input_ids, eos_token_id=tokenizer.eos_token_id, attention_mask=attention_mask, max_length=150, num_return_sequences=3, do_sample=True, # 启用采样以增加多样性 top_p=0.98, # 使用top-p采样 ) # 打印生成的文本 for i, sample_output in enumerate(sample_outputs): print("{}: {}".format(i, tokenizer.decode(sample_output, skip_special_tokens=True))) |
运行 setup9.3.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
$ python setup9.3.py New best model found at epoch 0 with training loss 1.9190336465835571 and saved New best model found at epoch 1 with training loss 1.4716061353683472 and saved New best model found at epoch 2 with training loss 0.8410051465034485 and saved New best model found at epoch 3 with training loss 0.4602312445640564 and saved New best model found at epoch 4 with training loss 0.3903290331363678 and saved New best model found at epoch 6 with training loss 0.36641600728034973 and saved New best model found at epoch 7 with training loss 0.11104749143123627 and saved New best model found at epoch 9 with training loss 0.11090903729200363 and saved New best model found at epoch 10 with training loss 0.09642010927200317 and saved New best model found at epoch 11 with training loss 0.0929558053612709 and saved New best model found at epoch 13 with training loss 0.08717972785234451 and saved New best model found at epoch 17 with training loss 0.07089772820472717 and saved New best model found at epoch 19 with training loss 0.06960687041282654 and saved New best model found at epoch 25 with training loss 0.06584648787975311 and saved New best model found at epoch 31 with training loss 0.0644926130771637 and saved New best model found at epoch 45 with training loss 0.062989741563797 and saved New best model found at epoch 88 with training loss 0.06280163675546646 and saved Best model saved to models/gpt2-xl_finetuned with training loss 0.06280163675546646 1557.6128 M parameters Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation. 0: 子曰:“父在,观其志;父没,观其行;三年无改于父之道,可谓孝矣。”爱自远方来,可谓孝矣。”知和而和,不以父之道,”知三年无改于爱� 1: 子曰:“不患人之不己知,患不知人也。”患不知不求�不知不也。”患不知不也。”求�不知不也。”求�不知不知不也。”求�不知不也。”求�不知不也。”求�不知不求�不知不 2: 子曰:“巧言令色,鲜矣仁!”矣仁!”知学文。”知省吾身!”知学文。”矣仁之学矣。”知不吾身!”知节之学矣。”知不可谓学 矣 仁之学矣。”� |
10. 对比原模型和微调后的模型
这里使用 gpt2-xl 的模型架构
setup10.1.py 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
from transformers import GPT2Tokenizer, GPT2LMHeadModel import torch # 加载微调后的模型和分词器 model_path = "models/gpt2-xl" tokenizer = GPT2Tokenizer.from_pretrained(model_path) model = GPT2LMHeadModel.from_pretrained(model_path) # print the number of parameters in the model print(sum(p.numel() for p in model.parameters())/1e6, 'M parameters') # 设置填充令牌为结束符号 if tokenizer.pad_token is None: tokenizer.pad_token = tokenizer.eos_token # 如果您在之后保存并重新加载分词器,也需要确保模型的词嵌入大小与分词器的词汇表大小匹配 model.resize_token_embeddings(len(tokenizer)) # 文本生成 # 使用`__call__`方法生成`input_ids`和`attention_mask` inputs = tokenizer("子曰:", return_tensors='pt', padding=True, truncation=True, max_length=50) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) # 提取`input_ids`和`attention_mask` input_ids = inputs['input_ids'] attention_mask = inputs['attention_mask'] input_ids = input_ids.to(device) attention_mask = attention_mask.to(device) # 文本生成,确保传入`attention_mask` sample_outputs = model.generate( input_ids, eos_token_id=tokenizer.eos_token_id, attention_mask=attention_mask, min_length=20, # 设置一个合理的最小长度 max_length=150, # 设置一个较大的最大长度 num_return_sequences=3, do_sample=True, # 启用采样以增加多样性 top_p=0.98, # 使用top-p采样 temperature=0.7, # 调整温度以控制随机性 ) # 打印生成的文本 for i, sample_output in enumerate(sample_outputs): print("{}: {}".format(i, tokenizer.decode(sample_output, skip_special_tokens=True))) |
运行 setup10.1.py
1 2 3 4 5 6 7 8 9 |
$ python setup10.1.py 1557.6112 M parameters Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation. 0: 子曰:ﻧﻬﻴﻬﻬﻮﻧﻭﻮﻫﻭﻮﻮﻭﻮﻭﻮﻮﻭﻮﻭﻮﻮﻭﻮﻮﻭﻮﻭﻮﻭﻮﻭﻮﻭﻮﻭﻮﻭﻮﻭﻮﻭﻮﻭﻮﻭ� 1: 子曰:14@234557;11=484557=484557=48455 >BC@FBEABDDGBFC� 2: 子曰:ﻲﻳ9AﻴCﻮﻯﻴﻶﻷﻸﻹﻺﻻﻼ ﻈ ﻉ ﻊ ﻋ ﻌ ﻍ ﻎ ﻏ ﻐ ﻑ ﻒ ﻓ ﻔ ﻕ ﻖ ﻗ ﻘ ﻙ ﻚ ﻛ ﻜ ﻝ ﻞ ﻟ ﻠ ﻡ ﻢ ﻣ � 可以明显看到,数据里是没有论语内容的 |
修改模型为微调后的模型,再看看效果
1 |
model_path = "models/gpt2-xl_finetuned" |
运行 setup10.2.py
1 2 3 4 5 6 7 8 |
$ python setup10.2.py Loading checkpoint shards: 100%|███████████████████████████████████████████████████| 2/2 [00:33<00:00, 16.92s/it] 1557.6128 M parameters Setting <code>pad_token_id</code> to <code>eos_token_id</code>:50256 for open-end generation. 0: 子曰:“道千乘之国,敬事而信,节用而爱人,使民以时。”道千乘之国,节用而爱不以时。”君子也,使民以时。”孝弟也者,其诸弟”知时。”弟”其诸弟”知时。” 1: 子曰:“不患人之不己知,患不知人也。”不患不知不也。”不患不知不也。”不患不知不也。”不患不知不也。”不患不知不也。”不患不知不知不也。”不患不知不也。”不患不知不� 2: 子曰:“弟子入则孝,出则弟,谨而信,泛爱众,而亲仁,行有余力,则以学文。”弟”学文。”弟”弟”不仁之学矣。””其诸弟” 知则仁之学知知矣。””弟” |
这里可以看到,微调后是有论语内容的
11. 是在较小的模型上微调,还是较大的模型上微调?
这里要比较一下最小的模型和最大的模型微调后的效果,微调的数据是一样的
把setup10.2.py里面的模型修改为gpt2_finetuned,看看效果
1 |
model_path = "models/gpt2_finetuned" |
运行 setup11.1.py 的效果
1 2 3 4 5 6 |
$ python setup11.1.py 124.440576 M parameters Setting `pad_token_id` to `eos_token_id`:50256 for open-end generation. 0: 子曰:“君子不重则不威,学则不固。主忠信,无友不如己者,过,则勿惮改。”子不如己者,则勿惮改。”子不己者,则勿惮 改。”子不知和而和,则勿惮改。”子不知于� 1: 子曰:“巧言令色,鲜矣仁!”子曰:“可谓孝矣。”子贡曰:“可上恭近于义!”子温、良、恭、俭、让以得之。夫子之求之也!”子温、恭、俭、让以得之。夫子 2: 子曰:“不患人之不己知,患不知人也。”子曰:“不患不知人也。”子贡曰:“不知和而患不知和,�”子曰:“不知人也。”子楽 礼节之道,患不知和而�”子 |
比较一下setup10.2.py 的结果,有区别不?不好说?
选择在较小的模型还是较大的模型上进行微调主要取决于几个关键因素:数据集的大小、计算资源、微调和推理的时间要求、以及期望达到的性能。下面是这两种选择的一些考虑因素:
微调较小的模型
优点:
- 计算成本较低:较小的模型需要的计算资源更少,可以在有限的硬件上训练和推理。
- 速度更快:训练和推理时间较短,适合快速迭代和在资源受限的环境中使用。
- 更容易调整:较小的模型参数较少,可能更容易找到良好的超参数设置。
缺点:
- 性能可能较低:较小的模型表达能力有限,可能在复杂任务上达不到较大模型的性能。
微调较大的模型
优点:
- 潜在的更高性能:较大的模型具有更强的表达能力,通常能够在复杂的任务上取得更好的结果。
- 更好的泛化能力:较大的模型由于参数多,能够捕捉到数据中的更细微的模式,可能在未见过的数据上有更好的表现。
缺点:
- 计算成本高:需要更多的计算资源,训练和推理时间更长。
- 调优可能更困难:由于参数量巨大,可能需要更多的实验来找到最佳的超参数设置。
- 过拟合的风险:在数据量不足的情况下,较大的模型更容易过拟合训练数据。
如何选择
- 如果您的计算资源有限,或者您希望快速迭代和部署模型,那么开始时选择较小的模型可能更合适。
- 如果您追求最高的性能,并且有足够的数据和计算资源来支持训练较大的模型,那么微调较大的模型可能是更好的选择。
- 数据集大小也是一个重要因素。较大的模型需要更多的数据来避免过拟合。如果您的数据集相对较小,可能首先考虑使用较小的模型或者使用数据增强、迁移学习等技术。
总的来说,选择的依据应该基于具体任务的需求、可用资源以及期望达到的性能标准。在某些情况下,从较小的模型开始,然后根据需要逐步迁移到较大的模型,可以是一个实际且高效的策略。