当前位置: 首页 > news >正文

低硬件资源微调预训练Mamba模型的方法

深入探索Mamba模型架构与应用 - 商品搜索 - 京东

在前面的章节中,我们详细介绍了基于预训练模型Mamba的全参数微调方法,并展示了其在实际应用中的有效性。这种全参数微调确实是一种切实可行的模型训练与预测手段,通过对模型的所有参数进行调整,可以使其更好地适应特定的任务和数据集。然而,微调的方法并非仅限于此。

事实上,除全参数微调外,还存在多种灵活的微调策略,能够在满足我们需求的同时,完成对模型的精细化调整。例如,部分参数微调是一种更加聚焦的调整方式,只针对模型中的特定层或参数进行调整,而保持其他部分不变。这种方法既可以节省计算资源,又能有针对性地提升模型在特定任务上的性能。

此外,还有基于适配器的微调方法,这种方法通过在预训练模型中插入额外的适配器模块,并仅对这些模块进行训练来实现对模型的微调。这种方式能够在保留预训练模型大部分知识的同时,快速适应新的任务。

因此,除全参数微调外,还有多种微调方法可供选择,以便根据实际情况灵活调整模型,以实现最佳性能。在接下来的章节中,我们将深入探讨这些微调方法的具体实现和应用场景。

8.3.1  使用冻结模型参数的微调方法

我们的目标是利用预训练模型来完成模型微调,以实现更高的适应性和性能。一个直观且实用的策略是,在原有模型的基础上,冻结部分底层参数,而集中训练模型的高层参数。

通常模型的底层负责抽取基础且相对“粗糙”的特征,这些特征虽然对理解输入数据至关重要,但在不同的任务和领域具有一定的通用性。相对而言,模型的高层更专注于抽取“细粒度”的特征,这些特征对于特定任务的性能提升尤为关键,如图8-7所示。

图8-7  只在部分层上进行训练

基于这一特性,我们可以采用一种策略:冻结模型的底层参数,仅对高层参数进行训练。这种微调方式不仅可以减少训练过程中的计算负担,还能使模型更快地适应新任务,因为高层参数的调整能够更直接地影响模型的最终输出。

通过这种方法,我们可以在保留预训练模型强大特征抽取能力的同时,使模型更好地适应特定任务的需求。在接下来的实验中,我们将验证这种微调策略的有效性,并探索其对模型性能的具体影响。

在具体实现时,我们首先获取所有层的名称,此时PyTorch给我们提供了打印所有模型名称的函数,代码如下:

# 冻结所有层

for name,param in mamba_model.named_parameters():

    print(name)

param.requires_grad = False

可以看到,在打印层名称的同时,param.requires_grad=False这个函数帮助我们完成了对所有层的冻结。打印结果如下:

embedding.weight
layers.0.mixer.A_log
layers.0.mixer.D
...
layers.23.mixer.dt_proj.bias
layers.23.mixer.out_proj.weight
layers.23.norm.weight
norm_f.weight

可以看到,这里实际上是根据不同的层号对每个位置所处的层进行编号,核对原有的Mamba的config设置参数:

{
    "d_model": 768,
    "n_layer": 24,
    "vocab_size": 50277,
    "ssm_cfg": {},
    "rms_norm": true,
    "residual_in_fp32": true,
    "fused_add_norm": true,
    "pad_vocab_size_multiple": 8
}

可以很容易地发现,在预训练的Mamba层中,层是按照数字顺序从0~23排列的,下面我们仅根据名称,在使用名称的基础上对模型进行微调,代码如下:

model_dir = snapshot_download('AI-ModelScope/mamba-130m',cache_dir="./mamba/")
mamba_model = Mamba.from_pretrained("./mamba/AI-ModelScope/mamba-130m")
# 冻结除目标层外的所有层
for name,param in mamba_model.named_parameters():
    print(name)
    if "23" not in name:
        param.requires_grad = False

BATCH_SIZE = 320

import all_config
device = all_config.device
model = mamba_model
model.to(device)

可以看到,此时我们冻结了除目标层外的所有层,而在具体代码编写上,我们通过传入特定字符的方式将无须冻结的层排除,同时冻结特定目标层[晓王1] ,此时完整代码如下:

import os
import math
from tqdm import tqdm
import torch
from torch.utils.data import DataLoader
from model import Mamba
from modelscope import snapshot_download,AutoTokenizer
model_dir = snapshot_download('AI-ModelScope/mamba-130m',cache_dir="./mamba/")
mamba_model = Mamba.from_pretrained("./mamba/AI-ModelScope/mamba-130m")

# 冻结所有层
for name,param in mamba_model.named_parameters():
    print(name)
    if "23" not in name:
        param.requires_grad = False

BATCH_SIZE = 320
import all_config
device = all_config.device
model = mamba_model
model.to(device)

import get_data_emotion
train_dataset = get_data_emotion.TextSamplerDataset(get_data_emotion.token_list)
train_loader = (DataLoader(train_dataset, batch_size=BATCH_SIZE,shuffle=True))

save_path = "./saver/glm_text_generator.pth"

optimizer = torch.optim.AdamW(model.parameters(), lr = 2e-5)
lr_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer,T_max = 1200,eta_min=2e-7,last_epoch=-1)
criterion = torch.nn.CrossEntropyLoss()

for epoch in range(12):
    pbar = tqdm(train_loader,total=len(train_loader))
    for token_inp,token_tgt in pbar:
        token_inp = token_inp.to(device)
        token_tgt = token_tgt.to(device)
        logits = model(token_inp)
        loss = criterion(logits.view(-1, logits.size(-1)), token_tgt.view(-1))

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        lr_scheduler.step()  # 执行优化器
        pbar.set_description(f"epoch:{epoch +1}, train_loss:{loss.item():.5f}, lr:{lr_scheduler.get_last_lr()[0]*1000:.5f}")

    torch.save(model.state_dict(), save_path)

可以看到,在原有的架构上,我们没有进行修改,而是采用前面讨论的冻结部分层(编号23)的方式对模型进行微调。此时可以采用载入预测的方法对模型进行输出,这点请读者按8.2.4节的预训练模型输出方案对结果进行打印。部分结果如下:

酒店里没有讲住这的估计,服务人员的地理位置还好,也不如酒店的优越性价比
位置,但是在海有不合观,另外大型的服务怎么说,服务员也很不错。※房间干净、洗
-------------
酒店房间台总经理人员有称客意见,但是本人所说没有态度反映,所以让我先说没有意见,
位置很好。房间有宽带,但晚上睡觉很吵 
-------------
酒店服务员态度、房间服务员态度都一般,而服务态度都好像不错。入住酒店就在市中心太
位置很高,在我们的五星级酒店,环境也很吵。但酒店环境不太好,所
-------------
酒店设施,否则在酒店服务态度很好
位置,没有预定的,不仅因我们自己没有
-------------
酒店了,这次我们还在还要去。服务员可以带热的水果
位置比较少。早餐还要10元的房价格也不符合星级标准酒店房间太

可以看到,此时的输出结果并不是很符合我们的输出要求,究其原因,一般是需要对更多的层进行微调,此时我们可以修改冻结部分的代码如下:

for name,param in mamba_model.named_parameters():
    print(name)
    if "23" not in name or "22" not in name:
        param.requires_grad = False

这样即可通过修改冻结层的数目,从而设定可训练的参数数目,这点请读者基于自身的硬件设备进行处理。

8.3.2  通过替换制定层的方式完成微调

除冻结部分层而对某些特定层进行微调外,还有一种方法是通过替换一些关键的特定层,从而完成预训练模型的微调。首先打印完整的模型层名称:

from model import Mamba
from modelscope import snapshot_download,AutoTokenizer
model_dir = snapshot_download('AI-ModelScope/mamba-130m',cache_dir="./mamba/")
mamba_model = Mamba.from_pretrained("./mamba/AI-ModelScope/mamba-130m")
print(mamba_model)

打印结果如下:

Mamba(
  (embedding): Embedding(50280, 768)
  (layers): ModuleList(
    (0-23): 24 x ResidualBlock(
      (mixer): MambaBlock(
        (in_proj): Linear(in_features=768, out_features=3072, bias=False)
        (conv1d): Conv1d(1536, 1536, kernel_size=(4,), stride=(1,), padding=(3,), groups=1536)
        (x_proj): Linear(in_features=1536, out_features=80, bias=False)
        (dt_proj): Linear(in_features=48, out_features=1536, bias=True)
        (out_proj): Linear(in_features=1536, out_features=768, bias=False)
      )
      (norm): RMSNorm()
    )
  )
  (norm_f): RMSNorm()
  (lm_head): Linear(in_features=768, out_features=50280, bias=False)
)

在这里,我们希望替换最后一层lm_head,并冻结其他所有层的内容,只保持对这一层的训练。因此,具体来看,我们需要根据lm_head的大小和架构样式,重新构建一个相同的层完成模型的设计。这部分代码如下:

for param in mamba_model.parameters():
    param.requires_grad = False

lm_head_new = torch.nn.Linear(in_features=768, out_features=50280, bias=False,device=device)
model.lm_head = lm_head_new
train_params = (model.lm_head.parameters())

optimizer = torch.optim.AdamW(train_params  , lr = 2e-5)

可以看到,首先我们对所有的层和参数设置了不再更新,之后使用了一个新的全连接层lm_head_new = torch.nn.Linear(in_features=768, out_features=50280, bias=False,device=device)替换原有Mamba模型的lm_head层。而在具体的torch.optim.AdamW更新时,制定了只对这一层的参数进行更新,从而完成模型的微调。完整训练代码如下:

import os
import math
from tqdm import tqdm

import torch
from torch.utils.data import DataLoader

from model import Mamba
from modelscope import snapshot_download,AutoTokenizer
model_dir = snapshot_download('AI-ModelScope/mamba-130m',cache_dir="./mamba/")
mamba_model = Mamba.from_pretrained("./mamba/AI-ModelScope/mamba-130m")

BATCH_SIZE = 512

import all_config
device = all_config.device

model = mamba_model
model.to(device)

import get_data_emotion
train_dataset = get_data_emotion.TextSamplerDataset(get_data_emotion.token_list)
train_loader = (DataLoader(train_dataset, batch_size=BATCH_SIZE,shuffle=True))

save_path = "./saver/glm_text_generator.pth"
model.load_state_dict(torch.load(save_path))

for param in mamba_model.parameters():
    param.requires_grad = False

lm_head_new = torch.nn.Linear(in_features=768, out_features=50280, bias=False,device=device)
model.lm_head = lm_head_new
train_params = (model.lm_head.parameters())

optimizer = torch.optim.AdamW(train_params  , lr = 2e-3)
lr_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer,T_max = 1200,eta_min=2e-7,last_epoch=-1)
criterion = torch.nn.CrossEntropyLoss()

for epoch in range(24):
    pbar = tqdm(train_loader,total=len(train_loader))
    for token_inp,token_tgt in pbar:
        optimizer.zero_grad()
        token_inp = token_inp.to(device)
        token_tgt = token_tgt.to(device)
        logits = model(token_inp)
        loss = criterion(logits.view(-1, logits.size(-1)), token_tgt.view(-1))

        loss.backward()
        optimizer.step()
        lr_scheduler.step()  # 执行优化器
        pbar.set_description(f"epoch:{epoch +1}, train_loss:{loss.item():.5f}, lr:{lr_scheduler.get_last_lr()[0]*1000:.5f}")

    torch.save(model.state_dict(), save_path)

预测部分请读者自行参考上面的预测模块进行处理。

8.3.3  对模型参数进行部分保存和载入的方法

在完成模型的微调,并设定部分参数可参与梯度更新后,大部分原有预训练模型的参数通常不会发生变化,即只有部分模型参数的值被更新。在这种情况下,如果按照原有全部参数量进行保存和处理,显然有些多余。

PyTorch还为我们提供了仅仅对部分参数进行保存和读取的方法,当我们只需要对某个特定层的参数进行保存时,可以这样:

...
for param in mamba_model.parameters():
    param.requires_grad = False
...
lm_head_new = torch.nn.Linear(in_features=768, out_features=50280, bias=False,device=device)
model.lm_head = lm_head_new
train_params = (model.lm_head.parameters())
...
torch.save([model.lm_head.state_dict()],"./saver/lm_head.pth")	

即在选定特定层后,直接对参数输入新的地址并保存即可。这里我们只是显式地对model.lm_head.state_dict()的内容进行了保存,同时读者需要注意,在这里实际上可以对多个层的内容进行保存,作者使用list作为一个存储目标,有兴趣的读者可以仔细尝试。

而重新载入这部分参数只需要在预训练载入完成后,仅载入保存的新的部分存档即可,代码如下:

import model
from model import Mamba
import torch

from modelscope import snapshot_download,AutoTokenizer
model_dir = snapshot_download('AI-ModelScope/mamba-130m',cache_dir="./mamba/")
mamba_model = Mamba.from_pretrained("./mamba/AI-ModelScope/mamba-130m")

save_path = "./saver/lm_head.pth"
mamba_model.load_state_dict(torch.load(save_path) [0],strict=False)

tokenizer = AutoTokenizer.from_pretrained('./mamba/tokenizer')
for _ in range(10):
    print(model.generate(mamba_model, tokenizer, '酒店'))
    print(model.generate(mamba_model, tokenizer, '位置'))
    print("-------------")

可以看到,当前的保存地址就是前面保存的部分参数,而此时对模型参数的载入也仅限于这部分更新后的参数。另外需要注意的是,当前保存的模型是以列表(list)的形式保存的,因此在载入时也需要按照要求对特定的目标进行载入。

http://www.dtcms.com/a/123028.html

相关文章:

  • 在 transformers 中,return_tensors=‘pt‘ 里的 pt 是什么 tf,np
  • gdal_shp文件的组成
  • 【设备连接涂鸦阿里云】
  • [数据结构]Trie字典树
  • RocketMQ和kafka 的区别
  • 算法导论(递归回溯)——⼆叉树中的深搜
  • lanqiaoOJ 498 回文日期
  • ValueError: Cannot handle batch sizes > 1 if no padding token is defined`
  • 数据结构(五)——AVL树(平衡二叉搜索树)
  • 【Python语言基础】17、继承
  • 深入理解 Spring 的 MethodParameter 类
  • 测试的分类
  • OpenCV 图形API(25)图像滤波-----均值滤波(模糊处理)函数blur()
  • 写一个简单的demo来理解数据库外键
  • Java中的参数是值传递还是引用传递?
  • webpack配置导致浏览器自动刷新
  • Pytest+Allure+Excel接口自动化测试框架实战
  • 【Kafka基础】监控与维护:分区健康检查,确保数据高可用
  • 【C++算法】53.链表_重排链表
  • 数据可视化 —— 柱形图应用(大全)
  • 《Uniapp-Vue 3-TS 实战开发》构建HTTP请求拦截器
  • Windows 2016 如何关闭自动更新
  • HLSL Complex Shapes With For Loops
  • Linux启动端口,Windows 看是否通
  • 使用 Vue + PDF.js 构建在线 PDF 阅读器(支持目录与缩放)
  • Petalinux最简开发
  • (2)网络学习之堡垒机
  • 如何避免Python爬虫重复抓取相同页面?
  • 【数据结构】树状数组
  • RTT中断管理学习