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

一、什么是微调

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

模型微调(Fine-tuning)指的是将一个预训练好的模型(通常在大规模通用数据集上训练)针对特定任务或领域进行优化的过程。

那么什么是预训练好的模型呢?

预训练好的模型(Pre-trained Model)是指在大规模通用数据集上经过预先训练,具备基础特征提取能力的机器学习模型。这类模型无需针对具体任务从零训练,而是作为 “起点”,通过微调(Fine-tuning)快速适配不同场景。

讲的通俗一点就是:只调整模型的一部分结构(参数),为了让模型能够适应当前的任务,就只调整一部分就足够了。

至于为什么要做微调,说白了就是我想白嫖模型已经训练好的部分参数,以此缩短自己训练模型的时间。

模型微调分为:

  • 全量微调

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

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

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

微调之后模型一定会变得更强吗?

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

1.2、增量微调

增量微调是在原有的模型的基础上增加一段逻辑,并通过训练保存最后的训练参数。

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

增量微调的实现方式

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

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

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

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

为什么要用增量微调?

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

增量微调的过程如下图:

exported_image (1)

二、从设计模型开始

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

AI 项目的开发过程:

  1. 准备数据
  2. 构建设计模型
  3. 模型训练
  4. 评估测试
  5. 打包部署

2.2、设计模型

在本文中设计模型与定义数据集的过程与上文中的模型开发过程有所颠倒,不用过于纠结,在实际项目中这两个过程也是有可能同步进行的。

'''
我们以Bert模型为基座,进行增量微调,在原来模型的基础上加上以下逻辑进行微调训练
'''
from transformers import BertModel
import torch

#定义设备信息
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(DEVICE)

#加载预训练模型
pretrained = BertModel.from_pretrained(r"D:\PycharmProjects\disanqi\demo_5\model\bert-base-chinese\models--bert-base-chinese\snapshots\c30a6ed22ab4564dc1e3b2ecbf6e766b0611a33f").to(DEVICE)
print(pretrained)
#定义下游任务(增量模型)
class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()
        #设计全连接网络,实现二分类任务
        self.fc = torch.nn.Linear(768,2)

    def forward(self,input_ids,attention_mask,token_type_ids):
        #冻结Bert模型的参数,让其不参与训练
        with torch.no_grad():
            out = pretrained(input_ids=input_ids,attention_mask=attention_mask,token_type_ids=token_type_ids)
        #增量模型参与训练
        out = self.fc(out.last_hidden_state[:,0])
        return out

2.3 、自定义数据集

'''
我们训练模型的时候需要区分数据属于哪个部分,分别是train、test、validation
'''
#自定义数据集
from torch.utils.data import Dataset
from datasets import load_from_disk

class MyDataset(Dataset):
    def __init__(self,split):
        #从磁盘加载数据
        self.dataset = load_from_disk(r"D:\XXX\XXX\XXXX\XXXX\XXXX")
        if split == 'train':
            self.dataset = self.dataset["train"]
        elif split == "test":
            self.dataset = self.dataset["test"]
        elif split == "validation":
            self.dataset = self.dataset["validation"]

    def __len__(self):
        return len(self.dataset)

    def __getitem__(self, item):
        text = self.dataset[item]['text']
        label = self.dataset[item]['label']

        return text,label

if __name__ == '__main__':
    dataset = MyDataset("test")
    for data in dataset:
        print(data)

2.4、写一个模型训练类

#模型训练
import torch
from MyData import MyDataset
from torch.utils.data import DataLoader
from net import Model
from transformers import BertTokenizer,AdamW

#定义设备信息
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
#定义训练的轮次
EPOCH= 30000

token = BertTokenizer.from_pretrained(r"D:\PycharmProjects\disanqi\demo_5\model\bert-base-chinese\models--bert-base-chinese\snapshots\c30a6ed22ab4564dc1e3b2ecbf6e766b0611a33f")

def collate_fn(data):
    sents = [i[0]for i in data]
    label = [i[1] for i in data]
    #编码
    data = token.batch_encode_plus(
        batch_text_or_text_pairs=sents,
        truncation=True,
        max_length=500,
        padding="max_length",
        return_tensors="pt",
        return_length=True
    )
    input_ids = data["input_ids"]
    attention_mask = data["attention_mask"]
    token_type_ids = data["token_type_ids"]
    labels = torch.LongTensor(label)

    return input_ids,attention_mask,token_type_ids,labels


#创建数据集
train_dataset = MyDataset("train")
train_loader = DataLoader(
    dataset=train_dataset,
    batch_size=100,
    shuffle=True,
    #舍弃最后一个批次的数据,防止形状出错
	#比如我们有 100 条数据,每次去 10条,10 次能取完,如果99条数据,最后一次形状就会发生变化。因此我们要舍弃
    drop_last=True,
    #对加载进来的数据进行编码
    collate_fn=collate_fn
)

if __name__ == '__main__':
    #开始训练
    print(DEVICE)
    model = Model().to(DEVICE)
    #定义优化器
    optimizer = AdamW(model.parameters())
    #定义损失函数
    loss_func = torch.nn.CrossEntropyLoss()

    for epoch in range(EPOCH):
        for i,(input_ids,attention_mask,token_type_ids,labels) in enumerate(train_loader):
            #将数据存放到DEVICE上
            input_ids, attention_mask, token_type_ids, labels = input_ids.to(DEVICE),attention_mask.to(DEVICE),token_type_ids.to(DEVICE),labels.to(DEVICE)
            #前向计算(将数据输入模型,得到输出)
            out = model(input_ids=input_ids,attention_mask=attention_mask,token_type_ids=token_type_ids)
            #根据输出,计算损失
            loss = loss_func(out,labels)
            #根据损失,优化参数
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            #每隔5个批次输出训练信息
            if i%5==0:
                out = out.argmax(dim=1)
                acc = (out==labels).sum().item()/len(labels)
                print(f"epoch:{epoch},i:{i},loss:{loss.item()},acc:{acc}")
        #每训练完一轮,保存一次参数
		#一轮就是把所有数据都训练一遍,批次是一次取多少数据
        torch.save(mode	l.state_dict(),f"params/{epoch}_bert.pth")
        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、模型评估

模型评估使用的是反向传播算法,损失在下降、精度在上涨代表模型的训练就是没有问题的,其实我们评估模型训练的结果看的不是那一轮次的具体结果而是看的趋势,趋势向好偶有噪声,不能说明模型训练的不好。

在实际做项目中我们要向客户明确的一件事是:模型训练的数据应该是提前准备好的,而不是我在训练的过程中再去加数据,

原创作者: bricheersz 转载于: https://www.cnblogs.com/bricheersz/p/18948960
### 增量预训练与微调中的主要挑战 #### 高质量数据获取困难 对于垂直化训练而言,获得并处理大量的高质量专业领域数据是一项艰巨的任务。这些数据不仅数量庞大,而且需要具备高度的相关性和准确性来支持模型的有效训练[^1]。 #### 平衡特定能力和通用性能 当致力于提高某一具体领域的效能时,如何确保不会削弱该模型在其他广泛任务上的表现也是一个重要考量因素。这意味着任何针对特殊应用场景所做的优化都应当谨慎实施,以免影响其跨行业的适用范围。 #### 资源消耗巨大 相比起构建一般性的AI系统来说,在某个专门方向上深入挖掘往往意味着更高的成本支出——无论是人力方面还是物质条件上皆然如此。特别是涉及到复杂的数据清洗、标注以及后续维护工作时更是如此。 #### 模型持续更新的需求 随着行业环境和技术趋势的变化,原有的专业知识可能会变得过时或不再完全适用;因此,为了维持最佳的服务水平,就必须定期对企业内部积累的新资料加以利用,并据此调整现有算法结构及其参数设置[^2]。 --- ### 解决方案概述 #### 利用Scaling Laws指导资源配置 基于对scaling laws的研究成果表明,适当扩大神经网络架构尺寸(如增加层数/节点数)、扩充样本集容量均有助于改善最终产出的质量。遵循这一原则可以帮助决策者更加科学合理地分配有限的研发预算给到最有可能带来回报的地方去。 #### 实施Compute-Optimal策略 按照compute-optimal理论建议的比例同步扩展输入素材的数量级和权重矩阵维度,则可以在一定程度上缓解因硬件设施不足而导致的时间延迟现象发生频率过高问题,从而加快整个开发周期进度条向前推进速度。 #### 推动Domain Continue Pretraining 考虑到基础版本可能存在某些局限性无法很好满足实际应用场合下的多样化诉求,故而有必要选取合适的初始状态作为起点继续开展针对性更强的再教育过程。此做法既有利于继承先前所学到的知识经验又便于引入新鲜血液注入其中实现双赢局面[^4]。 #### 应用检索增强生成技术 面对动态变化着的企业运营状况所带来的不确定性风险,采用类似于retrieval-augmented generation (RAG) 的框架或许不失为一种明智的选择。它允许机器一边查询外部数据库寻找最新资讯补充进来,另一边则依据当前上下文语境灵活组合成连贯表达形式输出给用户端查看参考使用[^3]。 ```python def domain_continue_pretrain(model, new_data): """ 对已有模型进行领域内增量预训练 :param model: 已有大语言模型实例 :param new_data: 新增的专业领域数据集合 """ # 更新词典以适应新词汇 update_vocab(model.vocab, new_data) # 使用新增数据进一步训练模型 train_model(model, new_data) def retrieval_augmented_generation(query, knowledge_base): """ 结合检索机制辅助生成回复 :param query: 用户提出的询问字符串 :param knowledge_base: 可供查阅的信息库对象 """ retrieved_info = search(knowledge_base, query) response = generate_response(retrieved_info + context_from_query(query)) return response ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值