简单说清:LLM 微调与从零训练的区别

61 阅读4分钟

简单说清:LLM 微调与从零训练的区别

前言【AI大模型教程】

之前读研的时候一直都是自己构建模型,从零训练模型,这几天打算微调一个NLP模型做一些任务练练手,在操作的过程中总是把微调跟之前的从零训练弄混,于是就打算整理一篇笔记记录一下二者区别

微调与从零训练

微调是指在已经预训练好的模型基础上,使用特定领域或任务的数据进行二次训练的过程。

从零训练则是不依赖任何预训练模型或已有权重,完全从随机初始化的模型参数开始,仅基于目标任务的数据集,对神经网络的所有层、所有参数进行完整训练的方式。

举个例子来通俗点解释:假如你买了一辆已经组装好的自行车,微调就是根据你的身高、体重等个人情况,调整座椅高度、把手角度等细节。

而从零训练就是从原材料和零件生产开始,设计完整的自行车结构,一步步组装所有部件,最终完全定制一辆适合你身高体重的自行车。

从代码层面分析

从代码实现的角度,微调和直接训练在模型初始化、参数处理、优化器设置和训练策略等方面存在明显差异

模型初始化差异

例如,如下一个简单的CNN网络的代码,从零训练需要自定义网络结构所有参数随机初始化:

# 从头定义网络架构class CustomCNN(nn.Module):    def __init__(self):        super(CustomCNN, self).__init__()        self.conv1 = nn.Conv2d(3, 16, 3, padding=1)        self.conv2 = nn.Conv2d(16, 32, 3, padding=1)        self.conv3 = nn.Conv2d(32, 64, 3, padding=1)        self.pool = nn.MaxPool2d(2, 2)        self.fc1 = nn.Linear(64 * 4 * 4, 512)        self.fc2 = nn.Linear(512, 10)        self.relu = nn.ReLU()        self.dropout = nn.Dropout(0.5)    def forward(self, x):        x = self.pool(self.relu(self.conv1(x)))        x = self.pool(self.relu(self.conv2(x)))        x = self.pool(self.relu(self.conv3(x)))        x = x.view(-1, 64 * 4 * 4)        x = self.dropout(self.relu(self.fc1(x)))        x = self.fc2(x)        return x# 创建模型实例 - 完全从头开始model = CustomCNN()

而微调需要加载预训练模型,使用在大规模数据集上训练好的权重

# 加载预训练模型 - 使用已有权重model = models.resnet18(pretrained=True)# 修改最后的全连接层 - 适应新任务num_ftrs = model.fc.in_featuresmodel.fc = nn.Sequential(    nn.Linear(num_ftrs, 512),    nn.ReLU(),    nn.Dropout(0.5),    nn.Linear(512, 10)  # 假设是10分类任务)

参数冻结

从零训练的模型的所有参数都参与训练和更新,不需要冻结操作

# 所有参数默认都参与训练# 不需要额外的冻结操作model = CustomCNN()# 所有参数都在优化器中optimizer = optim.Adam(    model.parameters(),  # 所有参数    lr=0.001)

微调则需要通过设置 requires_grad=False 冻结部分参数,只训练特定层

# 加载预训练模型model = models.resnet18(pretrained=True)# 冻结部分层 - 只训练最后几层ct = 0for child in model.children():    ct += 1    if ct < 7:  # 冻结前7个层组        for param in child.parameters():            param.requires_grad = False            print(f"冻结层组 {ct}: {child.__class__.__name__}")# 只优化未冻结的参数optimizer = optim.Adam(    filter(lambda p: p.requires_grad, model.parameters()),    lr=0.001)

优化器设置

  • 从零训练通常所有参数使用相同的学习率
# 所有参数使用相同的学习率optimizer = optim.Adam(    model.parameters(),    lr=0.001,  # 单一学习率    betas=(0.9, 0.999),    eps=1e-08,    weight_decay=0,    amsgrad=False)
  • 微调常使用分层学习率,新层用较大学习率,预训练层用较小学习率
# 不同层使用不同学习率optimizer = optim.Adam([    # 倒数第二层使用较小学习率    {'params': model.layer4.parameters(), 'lr': 0.001},        # 新的全连接层使用较大学习率    {'params': model.fc.parameters(), 'lr': 0.01}], lr=0.0001)  # 默认学习率# 或者使用学习率调度器scheduler = optim.lr_scheduler.ReduceLROnPlateau(    optimizer,     mode='min',     factor=0.1,     patience=10,    verbose=True)

训练策略

  • 从零训练需要更多训练轮次,使用较大学习率
# 训练配置batch_size = 64epochs = 50  # 更多轮次learning_rate = 0.001# 训练循环for epoch in range(epochs):    model.train()    running_loss = 0.0        for inputs, labels in train_loader:        inputs, labels = inputs.to(device), labels.to(device)                optimizer.zero_grad()        outputs = model(inputs)        loss = criterion(outputs, labels)        loss.backward()        optimizer.step()                running_loss += loss.item()        print(f'Epoch {epoch+1}/{epochs}, Loss: {running_loss/len(train_loader):.4f}')
  • 微调常采用分阶段训练,总轮次更少,学习率更小
# 微调配置batch_size = 32epochs = 10  # 更少轮次learning_rate = 0.0001  # 更小学习率# 第一阶段:只训练新层for epoch in range(5):    model.train()    # 训练代码...# 第二阶段:解冻部分层继续微调for param in model.layer4.parameters():    param.requires_grad = True# 使用更小的学习率optimizer = optim.Adam(    filter(lambda p: p.requires_grad, model.parameters()),    lr=0.00001  # 进一步减小学习率)for epoch in range(5, 10):    model.train()    # 训练代码...

总结

什么时候用哪种方法?

  • 从零训练:当你有大量数据,或者任务很特殊(预训练模型不适合)
  • 微调:当你数据有限,或者任务与预训练任务相似

目前打算做一个简单的诈骗短信分类的任务,最简单方法则感觉是微调一个中文的NLP模型来完成,因此本次先简单记录一下差异,便于后续任务探究~

方面直接训练微调
模型初始化class CustomModel(nn.Module): 或 models.Sequential([...])models.resnet18(pretrained=True) 或 applications.MobileNetV2(weights='imagenet')
参数冻结所有参数默认参与训练param.requires_grad = False 或 base_model.trainable = False
优化器设置optimizer = optim.Adam(model.parameters(), lr=0.001)optimizer = optim.Adam([{'params': layer1, 'lr': 0.001}, {'params': layer2, 'lr': 0.01}])
训练轮次通常 30-100 轮通常 5-20 轮
学习率较大 (0.001)较小 (0.0001 或更小)
数据增强较强的增强策略相对温和的增强策略
训练策略单一阶段训练常采用分阶段训练策略