找回密码
 立即注册
首页 业界区 业界 你应该懂的AI大模型(八)之 微调 之 增量微调 ...

你应该懂的AI大模型(八)之 微调 之 增量微调

孩负范 2025-6-25 21:06:48
一、什么是微调

1.1、什么是微调?为什么要做微调?

模型微调(Fine-tuning)指的是将一个预训练好的模型(通常在大规模通用数据集上训练)针对特定任务或领域进行优化的过程。
那么什么是预训练好的模型呢?
预训练好的模型(Pre-trained Model)是指在大规模通用数据集上经过预先训练,具备基础特征提取能力的机器学习模型。这类模型无需针对具体任务从零训练,而是作为 “起点”,通过微调(Fine-tuning)快速适配不同场景。
讲的通俗一点就是:只调整模型的一部分结构(参数),为了让模型能够适应当前的任务,就只调整一部分就足够了。
至于为什么要做微调,说白了就是我想白嫖模型已经训练好的部分参数,以此缩短自己训练模型的时间。
模型微调分为:

  • 全量微调

    • 对模型的所有参数进行微调
    • 效果最好
    • 消耗的算力资源大

  • 局部微调

    • 只调整某些或者某部分参数
    • 对算力要求一般

  • 增量微调

    • 通过新增参数的方式完成微调,把新的知识存储在新增的参数中
    • 效果一般

微调之后模型一定会变得更强吗?
不一定,一般百亿参数以下的参数可以考虑微调。但是对于大模型而言微调之后效果可能会变得更差了。
1.2、增量微调

增量微调是在原有的模型的基础上增加一段逻辑,并通过训练保存最后的训练参数。       
增量微调的本质是 **“有控制地进化模型”:通过新增模块或调整部分参数,让模型在保留旧能力的同时学会新技能,最终保存的是新旧能力融合后的参数 **。这种方式既节省资源(不用从头训练),又能应对动态变化的场景(如新数据、新任务不断出现)。
增量微调的实现方式


  • 增加新模块(类似 “加装插件”)

    • 例如:在预训练的语言模型后增加一个 “情感分析层”,专门学习判断文本的褒贬(新增逻辑不影响原有模型结构)。
    • 训练:固定原有模型参数,只训练新增的情感层,保存时只更新情感层的参数。

  • 调整部分参数(类似 “修改局部程序”)

    • 例如:在图像分类模型中,发现对 “猫” 的识别准确率低,解冻并微调与 “猫特征” 相关的卷积层参数(如耳朵、尾巴的检测模块)。
    • 训练:冻结大部分参数,只更新与 “猫” 相关的少量参数,保存时只改动这部分参数。

  • 混合方式(既加新模块又调旧参数)

    • 例如:在预训练的翻译模型中,新增一个 “领域术语校正层”,同时微调编码器的最后几层,让模型更好地处理医学翻译。
    • 训练:先固定原有模型训练新增层,再解冻部分旧层联合训练,保存时更新所有参与训练的参数。

为什么要用增量微调?


  • 数据不是一下子全有的:比如电商平台的用户评价,每天都会新增,模型需要每天 “学一点新评价”,同时记住之前的评价规律。
  • 任务会慢慢变复杂:比如翻译模型先学中英翻译,后来要加中日翻译,增量微调能让它同时精通多门语言,而不是学了日语就忘了英语。
  • 省算力省时间:不用每次都把整个模型重新训练一遍,就像你复习功课只需要重点看新内容,不用把所有书再读一遍。
增量微调的过程如下图:
1.png

二、从设计模型开始

2.1、先了解模型开发的逻辑

AI 项目的开发过程:

  • 准备数据
  • 构建设计模型
  • 模型训练
  • 评估测试
  • 打包部署
2.2、设计模型

在本文中设计模型与定义数据集的过程与上文中的模型开发过程有所颠倒,不用过于纠结,在实际项目中这两个过程也是有可能同步进行的。
  1. '''
  2. 我们以Bert模型为基座,进行增量微调,在原来模型的基础上加上以下逻辑进行微调训练
  3. '''
  4. from transformers import BertModel
  5. import torch
  6. #定义设备信息
  7. DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  8. print(DEVICE)
  9. #加载预训练模型
  10. pretrained = BertModel.from_pretrained(r"D:\PycharmProjects\disanqi\demo_5\model\bert-base-chinese\models--bert-base-chinese\snapshots\c30a6ed22ab4564dc1e3b2ecbf6e766b0611a33f").to(DEVICE)
  11. print(pretrained)
  12. #定义下游任务(增量模型)
  13. class Model(torch.nn.Module):
  14.     def __init__(self):
  15.         super().__init__()
  16.         #设计全连接网络,实现二分类任务
  17.         self.fc = torch.nn.Linear(768,2)
  18.     def forward(self,input_ids,attention_mask,token_type_ids):
  19.         #冻结Bert模型的参数,让其不参与训练
  20.         with torch.no_grad():
  21.             out = pretrained(input_ids=input_ids,attention_mask=attention_mask,token_type_ids=token_type_ids)
  22.         #增量模型参与训练
  23.         out = self.fc(out.last_hidden_state[:,0])
  24.         return out
复制代码
2.3 、自定义数据集
  1. '''
  2. 我们训练模型的时候需要区分数据属于哪个部分,分别是train、test、validation
  3. '''
  4. #自定义数据集
  5. from torch.utils.data import Dataset
  6. from datasets import load_from_disk
  7. class MyDataset(Dataset):
  8.     def __init__(self,split):
  9.         #从磁盘加载数据
  10.         self.dataset = load_from_disk(r"D:\XXX\XXX\XXXX\XXXX\XXXX")
  11.         if split == 'train':
  12.             self.dataset = self.dataset["train"]
  13.         elif split == "test":
  14.             self.dataset = self.dataset["test"]
  15.         elif split == "validation":
  16.             self.dataset = self.dataset["validation"]
  17.     def __len__(self):
  18.         return len(self.dataset)
  19.     def __getitem__(self, item):
  20.         text = self.dataset[item]['text']
  21.         label = self.dataset[item]['label']
  22.         return text,label
  23. if __name__ == '__main__':
  24.     dataset = MyDataset("test")
  25.     for data in dataset:
  26.         print(data)
复制代码
2.4、写一个模型训练类
  1. #模型训练
  2. import torch
  3. from MyData import MyDataset
  4. from torch.utils.data import DataLoader
  5. from net import Model
  6. from transformers import BertTokenizer,AdamW
  7. #定义设备信息
  8. DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  9. #定义训练的轮次
  10. EPOCH= 30000
  11. token = BertTokenizer.from_pretrained(r"D:\PycharmProjects\disanqi\demo_5\model\bert-base-chinese\models--bert-base-chinese\snapshots\c30a6ed22ab4564dc1e3b2ecbf6e766b0611a33f")
  12. def collate_fn(data):
  13.     sents = [i[0]for i in data]
  14.     label = [i[1] for i in data]
  15.     #编码
  16.     data = token.batch_encode_plus(
  17.         batch_text_or_text_pairs=sents,
  18.         truncation=True,
  19.         max_length=500,
  20.         padding="max_length",
  21.         return_tensors="pt",
  22.         return_length=True
  23.     )
  24.     input_ids = data["input_ids"]
  25.     attention_mask = data["attention_mask"]
  26.     token_type_ids = data["token_type_ids"]
  27.     labels = torch.LongTensor(label)
  28.     return input_ids,attention_mask,token_type_ids,labels
  29. #创建数据集
  30. train_dataset = MyDataset("train")
  31. train_loader = DataLoader(
  32.     dataset=train_dataset,
  33.     batch_size=100,
  34.     shuffle=True,
  35.     #舍弃最后一个批次的数据,防止形状出错
  36.         #比如我们有 100 条数据,每次去 10条,10 次能取完,如果99条数据,最后一次形状就会发生变化。因此我们要舍弃
  37.     drop_last=True,
  38.     #对加载进来的数据进行编码
  39.     collate_fn=collate_fn
  40. )
  41. if __name__ == '__main__':
  42.     #开始训练
  43.     print(DEVICE)
  44.     model = Model().to(DEVICE)
  45.     #定义优化器
  46.     optimizer = AdamW(model.parameters())
  47.     #定义损失函数
  48.     loss_func = torch.nn.CrossEntropyLoss()
  49.     for epoch in range(EPOCH):
  50.         for i,(input_ids,attention_mask,token_type_ids,labels) in enumerate(train_loader):
  51.             #将数据存放到DEVICE上
  52.             input_ids, attention_mask, token_type_ids, labels = input_ids.to(DEVICE),attention_mask.to(DEVICE),token_type_ids.to(DEVICE),labels.to(DEVICE)
  53.             #前向计算(将数据输入模型,得到输出)
  54.             out = model(input_ids=input_ids,attention_mask=attention_mask,token_type_ids=token_type_ids)
  55.             #根据输出,计算损失
  56.             loss = loss_func(out,labels)
  57.             #根据损失,优化参数
  58.             optimizer.zero_grad()
  59.             loss.backward()
  60.             optimizer.step()
  61.             #每隔5个批次输出训练信息
  62.             if i%5==0:
  63.                 out = out.argmax(dim=1)
  64.                 acc = (out==labels).sum().item()/len(labels)
  65.                 print(f"epoch:{epoch},i:{i},loss:{loss.item()},acc:{acc}")
  66.         #每训练完一轮,保存一次参数
  67.                 #一轮就是把所有数据都训练一遍,批次是一次取多少数据
  68.         torch.save(mode        l.state_dict(),f"params/{epoch}_bert.pth")
  69.         print(epoch,"参数保存成功!")
复制代码
三、看懂微调结果

3.1、前向计算、反向传播

1、前向计算:从输入到输出的信息传递

  • 计算预测结果:将输入数据通过网络的各层计算,最终得到预测输出。
  • 数据流动方向:输入层 → 隐藏层 → 输出层,逐层计算每个神经元的输出。
2、反向传播:从误差到参数更新的梯度传递

  • 计算梯度:根据预测误差,计算每个参数(权重和偏置)对误差的影响程度(梯度)。
  • 参数更新:基于梯度,使用优化算法(如 SGD、Adam)更新参数,减小误差。
3.2、梯度下降

神经网络预测的过程基于一个简单的公式:z = dot(w,x) + b
公式中的x代表着输入特征向量,假设只有3个特征,那么x就可以用(x1,x2,x3)来表示。w表示权重,它对应于每个输入特征,代表了每个特征的重要程度。b表示阈值[yù zhí],用来影响预测结果。z就是预测结果。公式中的dot()函数表示将w和x进行向量相乘。
损失函数用来衡量预测算法算的怎么样的函数,损失函数运算后的结果越大,那么预测就与实际结果偏差越大,即预测精度不高。即损失函数运算之后得出的值越大带边精度越低,即结果越不好。努力让损失函数的值变得越小就是让结果变得越准确。
所以模型的结果是不是准确就是w和b决定的,那么神经网络学习的目的就是找到合适的w和b,找到合适的w和b的算法就叫梯度下降,记住梯度下降就是个名字,不要管为什么叫这个名字。
因此在上面的训练代码中每训练完一轮就保存一次参数,在训练过程中损失在下降、精度在提升就代表我们的训练没有问题,训练模型的时候我们微调的就是层次和批次。
四、模型评估

4.1 、欠拟合与过拟合

欠拟合(Underfitting)和过拟合(Overfitting)是机器学习模型训练中常见的两种不理想状态。

  • 欠拟合:模型无法捕捉数据中的规律,对训练数据和测试数据的表现都很差。
  • 过拟合:模型过度学习训练数据中的细节和噪声,导致在训练集上表现好,但在测试集上表现差。
    形象比喻

    • 欠拟合:学生 “没学明白”,连课本例题都做不对。
    • 过拟合:学生 “死记硬背”,只记住了课本例题的答案,遇到新题目就不会做。
    • 欠拟合是模型 “能力不足”,需要增强模型复杂度和特征表达。
    • 过拟合是模型 “学太细”,需要约束模型并增加数据多样性。

一旦模型过拟合了,基本就等于训练废了,很难补救。
4.2、训练集、验证集、测试集


  • 训练集: 模型训练时用的数据,等于模型学习的“教材”;
  • 验证集:训练中的评估,判断模型是否过拟合了;
  • 测试集:完成训练之后评测用的,用来出评估报告。
在实际开发中验证集和测试集经常使用一个,比例一般是9:1、8:2、7:3。要特别注意的是三个数据集之间不能有数据重叠,否则就是作弊了。
4.3、模型评估

模型评估使用的是反向传播算法,损失在下降、精度在上涨代表模型的训练就是没有问题的,其实我们评估模型训练的结果看的不是那一轮次的具体结果而是看的趋势,趋势向好偶有噪声,不能说明模型训练的不好。
在实际做项目中我们要向客户明确的一件事是:模型训练的数据应该是提前准备好的,而不是我在训练的过程中再去加数据,

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册