人工智能的快速发展推动了大模型的广泛应用,它们在语言、视觉、语音等领域的应用效果已经越来越好。但是,训练一个大模型需要巨大的计算资源和时间,为了减少这种资源的浪费,微调已经成为一种流行的技术。微调是指在预训练模型的基础上,通过在小数据集上的训练来适应新的任务。AIGC(AI芯片)的出现进一步加快了大模型的推广,它可以提供更快的计算速度和更大的存储容量。本文将介绍AIGC下大模型微调的方法,包括微调所有层、微调顶层、冻结底层、逐层微调和迁移学习。我们将使用PaddlePaddle这个开源框架,以自然语言处理和计算机视觉为例,来说明这些方法的原理和实现步骤。
在AIGC大模型下,我们目前最熟知一个大模型就是Chatgpt,目前国外大佬也正在研究能否在计算机视觉角度,做到上传一个图片或一个视频,我告诉他做视觉上的任务,就可以实现相应的视觉需求。
这样的大模型,我们虽然暂时没办法用到开源的模型,而且模型涉及到的参数也太大了,如果自己训练累死机器,在面对不同业务的情况下, 我们更多的方法是基于大模型进行模型微调的方法来实现我们的应用
在深度学习中,微调是一种重要的技术,用于改进预训练模型的性能。除了微调ChatGPT之外,还有许多其他预训练模型可以进行微调。以下是一些微调预训练模型的方法:
对于模型的参数微调,我认为可以这样理解,以原始的chatgpt为例,它像是一个通用的大模型,
像是一个在大学学习到所有专业的知识的大学生,基于过往的学习经验以及对生活中的一些事情,已经有了属于自己的一套学习方法思维逻辑(这个就是模型的参数)
现在这个大学生毕业后从事某一种行业的工作,那他就要开始学习工作上的内容,来产出工作的成果。那在他学习的过程,他以往在大学学到专业知识学习方法是不是也可以拿来应用呢,是不是可以用同样的学习方法学习工作的东西呢(这个就是微调)
微调,通过我过去积累学到东西,来应用到现在新的内容中来产出新的结果。
回到对不同层进行微调,如何选择那些层需要微调?就需要知道模型这些层在原始数据集上学习到了什么经验?
那些经验是我们可以拿来复用到另一个数据集中的?
在计算机视觉中,卷积神经网络 (Convolutional Neural Networks, CNNs) 通常会学习到如下经验:
此外,近年来也出现了一些基于注意力机制的模型,如自注意力模块(Self-Attention Model)、transformer模块可以通过学习到的注意力权重来对图像中的特征进行加权和,从而更加精细地提取特征
在自然语言处理中,循环神经网络 (Recurrent Neural Networks, RNNs) 和 Transformer 网络通常会学习到如下经验:
可以说对于处理计算机视觉和自然语言处理任务,模型上游的部分都是在一个学习经验的过程
但计算机视觉和自然语言处理在做微调模型时,区别:
对于计算机视觉,不同的图像,学习到的经验,可能完全是不同的,但是对于自然语言处理不同的文本,可能学习到的经验是一样的,因为文本的数据,特征更多是从上下文依赖,语言时序性。这些特征在不同内容的文本中是可以套用的。(比如说写论文和写作文,写作上很大的相似地方)
以下是使用PaddlePaddle框架对上述五种微调方法的示例代码:
import paddle
from paddle import nn
# 加载预训练的Transformer模型
pretrained_model = paddle.vision.models.Transformer()
# 1. 微调所有层
for param in pretrained_model.parameters():
param.trainable = True
# 2. 微调顶层
for param in pretrained_model.decoder.parameters():
param.trainable = True
# 3. 冻结底层
for param in pretrained_model.encoder.parameters():
param.trainable = False
# 4. 逐层微调
for i, layer in enumerate(pretrained_model.encoder.layers):
if i >= 6: # 只微调第6层及以上的层
for param in layer.parameters():
param.trainable = True
else:
for param in layer.parameters():
param.trainable = False
# 5. 迁移学习
# 加载预训练的模型
pretrained_model = paddle.vision.models.ResNet50(pretrained=True)
# 新建分类器
num_classes = 10
classifier = nn.Linear(2048, num_classes)
# 冻结预训练模型的所有层
for param in pretrained_model.parameters():
param.trainable = False
# 微调新建分类器的参数
for param in classifier.parameters():
param.trainable = True
# 将预训练模型和新建分类器组合成新的模型
model = nn.Sequential(pretrained_model, classifier)
上述代码中,我们首先通过paddle.vision.models.Transformer()加载了预训练的Transformer模型。然后根据不同的微调方法,分别对模型的不同层进行微调或冻结。最后,我们使用迁移学习的方法将预训练模型和新建分类器组合起来,形成一个新的模型。
import paddle
from paddlenlp.transformers import GPT2Model, GPT2ForPretraining, GPT2PretrainingCriterion
# 加载预训练模型
model = GPT2ForPretraining.from_pretrained('gpt2-medium-en')
tokenizer = GPT2Tokenizer.from_pretrained('gpt2-medium-en')
# 定义新的分类头
class_num = 2
cls = paddle.nn.Linear(model.config["hidden_size"], class_num)
# 将新的分类头添加到模型中
model.cls = cls
# 通过微调所有层来适应新任务
optimizer = paddle.optimizer.Adam(learning_rate=1e-5, parameters=model.parameters())
criterion = GPT2PretrainingCriterion()
import paddle
from paddlenlp.transformers import GPT2Model, GPT2ForPretraining, GPT2PretrainingCriterion
# 加载预训练模型
model = GPT2ForPretraining.from_pretrained('gpt2-medium-en')
tokenizer = GPT2Tokenizer.from_pretrained('gpt2-medium-en')
# 固定模型底层,只微调顶层
for param in model.parameters():
param.trainable = False
# 定义新的分类头
class_num = 2
cls = paddle.nn.Linear(model.config["hidden_size"], class_num)
# 将新的分类头添加到模型中
model.cls = cls
# 通过微调顶层来适应新任务
for param in model.cls.parameters():
param.trainable = True
optimizer = paddle.optimizer.Adam(learning_rate=1e-5, parameters=model.cls.parameters())
criterion = paddle.nn.CrossEntropyLoss()
import paddle
import paddle.nn.functional as F
from paddlenlp.transformers import GPTForPretraining, GPTChineseTokenizer
# 加载预训练模型和分词器
model = GPTForPretraining.from_pretrained('gpt-cpm-large-cn')
tokenizer = GPTChineseTokenizer.from_pretrained('gpt-cpm-large-cn')
# 构造数据集和数据加载器
train_ds = [['今天天气不错'], ['明天要下雨'], ['这个季节很适合旅游']]
train_ds = [{'text': text} for text in train_ds]
def batch_iter(data, batch_size):
num_batches = len(data) // batch_size
if len(data) % batch_size != 0:
num_batches += 1
for i in range(num_batches):
batch = data[i * batch_size: (i + 1) * batch_size]
yield batch
batch_size = 2
train_loader = paddle.io.DataLoader(train_ds, batch_size=batch_size, shuffle=True, drop_last=True)
# 构造优化器和损失函数
optimizer = paddle.optimizer.AdamW(parameters=model.parameters(), learning_rate=1e-4)
criterion = F.cross_entropy
# 冻结底层
for layer in model.layers[:6]:
layer.eval()
for param in layer.parameters():
param.trainable = False
# 微调模型
for epoch in range(3):
for batch in train_loader:
texts = [example['text'] for example in batch]
encoded_inputs = tokenizer(texts, return_attention_mask=True, return_length=True, padding=True)
input_ids = paddle.to_tensor(encoded_inputs['input_ids'])
attention_mask = paddle.to_tensor(encoded_inputs['attention_mask'])
logits = model(input_ids, attention_mask=attention_mask)[0]
loss = criterion(logits.reshape(-1, logits.shape[-1]), input_ids.reshape(-1))
loss.backward()
optimizer.step()
optimizer.clear_grad()
print(f'Epoch {epoch + 1}: loss={loss.numpy():.4f}')
# 保存微调后的模型
paddle.save(model.state_dict(), 'gpt-cpm-large-cn-finetuned
import paddle
import paddle.nn.functional as F
from paddlenlp.transformers import GPTForPretraining, GPTChineseTokenizer
# 加载预训练模型和分词器
model = GPTForPretraining.from_pretrained('gpt-cpm-large-cn')
tokenizer = GPTChineseTokenizer.from_pretrained('gpt-cpm-large-cn')
# 构造数据集和数据加载器
train_ds = [['今天天气不错'], ['明天要下雨'], ['这个季节很适合旅游']]
train_ds = [{'text': text} for text in train_ds]
def batch_iter(data, batch_size):
num_batches = len(data) // batch_size
if len(data) % batch_size != 0:
num_batches += 1
for i in range(num_batches):
batch = data[i * batch_size: (i + 1) * batch_size]
yield batch
batch_size = 2
train_loader = paddle.io.DataLoader(train_ds, batch_size=batch_size, shuffle=True, drop_last=True)
# 构造优化器和损失函数
optimizer = paddle.optimizer.AdamW(parameters=model.parameters(), learning_rate=1e-4)
criterion = F.cross_entropy
# 迁移学习微调模型
for epoch in range(3):
for batch in train_loader:
texts = [example['text'] for example in batch]
encoded_inputs = tokenizer(texts, return_attention_mask=True, return_length=True, padding=True)
input_ids = paddle.to_tensor(encoded_inputs['input_ids'])
attention_mask = paddle.to_tensor(encoded_inputs['attention_mask'])
logits = model(input_ids, attention_mask=attention_mask)[0]
loss = criterion(logits.reshape(-1, logits.shape[-1]), input_ids.reshape(-1))
loss.backward()
optimizer.step()
optimizer.clear_grad()
print(f'Epoch {epoch + 1}: loss={loss.numpy():.4f}')
# 保存微调后的模型
paddle.save(model.state_dict(), 'gpt-cpm-large-cn-finetuned-transfer-learning.pdparams')
在上面的代码中,我将模型微调的方法从逐层微调改为了迁移学习微调。具体来说,我将原来的逐层微调中的隐藏状态计算和获取每一层的输出等相关代码去掉了,并直接将输入和注意力掩码传入模型,获取最后一层的输出,并计算损失进行反向传播和优化。
同时,我将保存模型时的文件名从 gpt-cpm-large-cn-finetuned-layer-wise.pdparams 改为了 gpt-cpm-large-cn-finetuned-transfer-learning.pdparams,以便于区分逐层微调和迁移学习微调两种方法。
import paddle
import paddle.nn.functional as F
from paddlenlp.transformers import GPTForPretraining, GPTChineseTokenizer
# 加载预训练模型和分词器
model = GPTForPretraining.from_pretrained('gpt-cpm-large-cn')
tokenizer = GPTChineseTokenizer.from_pretrained('gpt-cpm-large-cn')
# 构造数据集和数据加载器
train_ds = [['今天天气不错'], ['明天要下雨'], ['这个季节很适合旅游']]
train_ds = [{'text': text} for text in train_ds]
def batch_iter(data, batch_size):
num_batches = len(data) // batch_size
if len(data) % batch_size != 0:
num_batches += 1
for i in range(num_batches):
batch = data[i * batch_size: (i + 1) * batch_size]
yield batch
batch_size = 2
train_loader = paddle.io.DataLoader(train_ds, batch_size=batch_size, shuffle=True, drop_last=True)
# 构造优化器和损失函数
optimizer = paddle.optimizer.AdamW(parameters=model.parameters(), learning_rate=1e-4)
criterion = F.cross_entropy
# 训练模型
epochs = 3
for epoch in range(epochs):
for batch in train_loader:
texts = [example['text'] for example in batch]
encoded_inputs = tokenizer(texts, return_attention_mask=True, return_length=True, padding=True)
input_ids = paddle.to_tensor(encoded_inputs['input_ids'])
attention_mask = paddle.to_tensor(encoded_inputs['attention_mask'])
logits = model(input_ids, attention_mask=attention_mask)[0]
loss = criterion(logits.reshape(-1, logits.shape[-1]), input_ids.reshape(-1))
loss.backward()
optimizer.step()
optimizer.clear_grad()
print(f'Epoch {epoch + 1}: loss={loss.numpy():.4f}')
# 保存微调后的模型
paddle.save(model.state_dict(), 'gpt-cpm-large-cn-finetuned.pdparams')
在上面的代码中,我们首先加载了预训练的 GPT 模型和分词器,然后构造了一个简单的数据集和数据加载器。接着,我们使用 AdamW 优化器和交叉熵损失函数来训练模型,训练完后保存微调后的模型。

我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/