模型优化(剪枝,蒸馏,量化)

1.模型优化的方式有哪些?

答:模型优化有剪枝,蒸馏,量化

1. 剪枝

2.什么是非结构化剪枝?什么是结构化剪枝?他们的区别是什么?

答:非结构化剪枝:如果是对权重剪枝则对卷积核中的部分数值(这个看传参是传入的0.2的话,就是百分之20的抹0)进行抹0。如果是对权重剪枝则对每个卷积核对应的唯一bias进行抹0,依旧是按照参数进行百分比抹除。抹除的时候还会按照先抹除最接近0的数。因为越接近0的数影响越小。否则影响越大。

3.剪枝的参数有哪些?

答:conv1, name='weight', amount=0.5  conv1是指剪哪一层卷积。name是指剪权重还是剪偏移量 amount小于1是指剪去百分之多少。大于1指的是剪去多少个值

4.非结构化剪枝的方法有哪些?各个参数的意思是什么?

答:非结构化剪枝的方法有prune.l1_unstructured(conv1, name='weight', amount=0.5)

prune.random_unstructured(conv1, name='weight', amount=0.3)

参数意思都一样。第一个是l1级非结构化剪枝。第二个是随机非结构化剪枝

5.结构化剪枝的方法有哪些?各个参数的意思是什么?

答:结构化剪枝的方法有prune.ln_structures(conv1, name='weight', n=2, amount=2, dim=0)

n的意思是l几级别的剪枝,l1是绝对值计算,l2是平方计算的方式进行剪枝,print(conv1.weight.shape) # torch.Size([6, 1, 5, 5]) 输出通道, 输入通道, 卷积核宽高

dim是指:对哪个维度进行剪枝,如果dim=0则就是对输出通道剪枝条(一般最好就选0),

如果dim=1则就是对输入通道剪枝条(这种对模型影响太大不建议)

如果dim=2则就是对卷积核宽进行剪枝,如果dim=3则就是对卷积核高进行剪枝

6.如何对整个网络进行剪枝的?

for name, module in model.named_modules():
    # 判断是否是Conv2d类型
    if isinstance(module, torch.nn.Conv2d):
        prune.l1_unstructured(module, name='weight', amount=0.2)
    elif isinstance(module, torch.nn.Linear):
        prune.l1_unstructured(module, name='weight', amount=0.3)

7.什么是全局剪枝?

就是对整个网络进行指定部分或全部的卷积进行对权重或(和)偏移量的剪枝

全局剪枝(Global Pruning)是指在神经网络训练中,通过删除网络中不重要或冗余的权重、神经元、或者层,来减少网络的复杂度和提高计算效率的技术。与局部剪枝不同,局部剪枝只在单个神经元或单个层级上进行剪枝,而全局剪枝则是基于整个网络的全局视角来判断哪些部分可以被剪除。

具体步骤通常包括:

  1. 权重评估:根据某种标准(如权重的绝对值、梯度、或者其他评估准则),计算出各个权重、神经元或层的重要性。
  2. 剪枝:将被评估为不重要的部分(如权重值较小的连接、激活值较小的神经元)从网络中删除。
  3. 重训练:剪枝后,通常需要对网络进行微调或重新训练,以恢复由于剪枝带来的性能损失。

全局剪枝的优点在于能更有效地减少计算量和内存使用,从而提高推理速度和减少模型大小,特别适用于资源受限的环境(如嵌入式设备)。但是,全局剪枝也可能导致模型性能下降,特别是在剪枝过度时。

import torch
from torch import nn
import torch.nn.utils.prune as prune
import torch.nn.functional as F

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        # 1 input image channel, 6 output channels, 5x5 square conv kernel
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)  # 5x5 image dimension
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.reshape(-1, int(x.nelement() / x.shape[0]))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

model = LeNet().to(device=device)

parameters_to_prune = (
    (model.conv1, 'weight'),
    # (model.conv2, 'weight'),
    (model.fc1, 'weight'),
    (model.fc2, 'bias'),
    # (model.fc3, 'weight'),
)

prune.global_unstructured(
    parameters_to_prune,
    pruning_method=prune.L1Unstructured,
    amount=0.2,
)

8.找一个一训练好的模型,对其进行剪枝操作,比对剪枝前后的准确率和模型的大小

import torch
from torch import nn, optim
from torch.nn.utils import prune
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import tqdm


class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.layers = nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Flatten(),
            nn.Linear(in_features=128 * 4 * 4, out_features=1024),
            nn.ReLU(inplace=True),
            nn.Linear(in_features=1024, out_features=512),
            nn.ReLU(inplace=True),
            nn.Linear(in_features=512, out_features=10)
        )

    def forward(self, x):
        return self.layers(x)


# 自定义剪枝方法
def advanced_prune(model, prune_percentage=0.3):
    """ 通过权重绝对值对每一层进行剪枝 """
    for name, module in model.named_modules():
        # 对卷积层进行剪枝
        if isinstance(module, nn.Conv2d):
            print(f"对卷积层 {name} 进行剪枝")
            prune.l1_unstructured(module, name='weight', amount=prune_percentage)
            # 移除剪枝的权重并手动清除剪枝部分
            mask = module.weight_mask
            module.weight.data.mul_(mask)  # 强制将被剪枝掉的权重设置为0
            prune.remove(module, 'weight')  # 移除剪枝后的掩码

        # 对全连接层进行剪枝
        elif isinstance(module, nn.Linear):
            print(f"对全连接层 {name} 进行剪枝")
            prune.l1_unstructured(module, name='weight', amount=prune_percentage)
            # 同样,手动清除剪枝部分
            mask = module.weight_mask
            module.weight.data.mul_(mask)  # 强制将被剪枝掉的权重设置为0
            prune.remove(module, 'weight')  # 移除剪枝后的掩码

    return model


# 计算非零参数数量
def count_nonzero_params(model):
    nonzero_params = 0
    for param in model.parameters():
        nonzero_params += torch.count_nonzero(param).item()
    return nonzero_params


# 测试函数
def test(net, test_loader, loss_fn, device):
    net.eval()
    total_test_loss = 0
    correct_predictions = 0
    total_samples = 0
    with torch.no_grad():
        for images, labels in test_loader:
            images = images.to(device)
            labels = labels.to(device)
            logits = net(images)
            loss = loss_fn(logits, labels)
            total_test_loss += loss.item()
            pred = torch.argmax(logits, dim=1)
            correct_predictions += (pred == labels).sum().item()
            total_samples += labels.size(0)

    accuracy = correct_predictions / total_samples
    avg_test_loss = total_test_loss / len(test_loader)
    return avg_test_loss, accuracy


# 训练函数
def train(net, train_loader, loss_fn, optimizer, device, epoches):
    for epoch in tqdm.tqdm(range(epoches)):
        net.train()
        total_train_loss = 0
        for images, labels in train_loader:
            images = images.to(device)
            labels = labels.to(device)
            logits = net(images)
            loss = loss_fn(logits, labels)
            total_train_loss += loss.item()
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()


if __name__ == "__main__":
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    net = Net().to(device)
    loss_fn = nn.CrossEntropyLoss()
    optimizer = optim.Adam(net.parameters(), lr=0.001)

    train_dataset = datasets.CIFAR10(root="data", train=True, download=True, transform=transforms.ToTensor())
    test_dataset = datasets.CIFAR10(root="data", train=False, download=True, transform=transforms.ToTensor())
    train_loader = DataLoader(train_dataset, batch_size=100, shuffle=True)
    test_loader = DataLoader(test_dataset, batch_size=100, shuffle=True)

    epoches = 5
    prune_percentage = 0.3  # 每次剪枝的百分比
    prune_rounds = 5  # 剪枝的次数

    # 训练并测试剪枝前的模型
    print("训练模型---------------")
    train(net, train_loader, loss_fn, optimizer, device, epoches)
    print("训练完成,开始测试剪枝前的准确率和模型大小---------------")
    test_loss, test_accuracy = test(net, test_loader, loss_fn, device)
    pre_train_params = count_nonzero_params(net)
    print(f"剪枝前的模型参数量: {pre_train_params}")
    print(f"剪枝前的测试准确率: {test_accuracy:.2f}%")

    # 循环剪枝和训练
    for round_num in range(prune_rounds):
        print(f"第 {round_num + 1} 次剪枝和训练---------------")

        # 进行剪枝操作
        print("进行剪枝操作---------------")
        net = advanced_prune(net, prune_percentage=prune_percentage)
        print("剪枝后的模型---------------")
        ofter_train_params = count_nonzero_params(net)
        print(f"剪枝后的模型参数量: {ofter_train_params}")

        # 重新训练剪枝后的模型
        print("剪枝后重新训练模型---------------")
        train(net, train_loader, loss_fn, optimizer, device, epoches)
        test_loss, test_accuracy = test(net, test_loader, loss_fn, device)
        ofter_retrain_params = count_nonzero_params(net)
        print(f"重新训练后的模型参数量: {ofter_retrain_params}")
        print(f"剪枝后重新训练的测试准确率: {test_accuracy:.2f}%")

9.什么是知识蒸馏?

答:就是使用一个已经训练好的大模型,再初始化一个小模型,让大模型预测的结果和小模型预测的结果进行求损失,让让小模型和真实结果进行求损失。将两个损失进行求KL散度损失。进行纠正小模型。使小模型可以快速学习,学习到大模型提纯出来的知识。这样起到压缩模型参数量和计算量,提高性能的作用。

10.什么是softTarget

import torch
import torch.nn.functional as F

# logits: 模型的原始输出(未通过 softmax 的分数)
# T: 温度参数
def soft_targets(logits, T):
    return F.softmax(logits / T, dim=-1)

解释

  • logits 是神经网络的输出,未经过 softmax 处理。
  • T 是温度参数,用于调整类别分布的平滑程度。
  • F.softmax(logits / T, dim=-1) 将 logits 除以 T,并计算 softmax 概率。

2. 蒸馏

11.详细说明蒸馏过程是怎么样的?

知识蒸馏是一种通过教师模型(Teacher Model)向学生模型(Student Model)传递知识的技术,用于在学生模型的训练中提升性能。以下是蒸馏过程的详细说明:

1. 蒸馏的基本思想

知识蒸馏通过利用教师模型输出的“软标签”(Soft Labels)和“硬标签”(Hard Labels),帮助学生模型学习更丰富的知识。

  • 硬标签(Hard Labels):由数据集提供的真实类别标签(one-hot)。
  • 软标签(Soft Labels):教师模型的预测输出,包含类别之间的概率关系。

2. 蒸馏的主要步骤

Step 1: 准备教师模型

  • 教师模型通常是一个经过充分训练的复杂、高性能模型(如大型神经网络)。
  • 它的输出概率分布 PTP_TPT​ 作为学生模型的学习目标。

Step 2: 构造学生模型

  • 学生模型是一个较小、计算效率更高的模型。
  • 学生模型需要通过蒸馏过程学习教师模型的知识,同时保持高效推理能力。

Step 3: 调整输出分布(通过温度 TTT)

  • 使用 softmax 函数和温度系数 TTT 调整教师模型和学生模型的输出分布:

Step 4: 定义蒸馏损失

蒸馏过程中,学生模型需要同时学习:

1. 从硬标签学习(监督学习):
使用交叉熵损失计算学生模型与真实标签之间的差异:

其中 PS 是学生模型的输出概率。

2. 从软标签学习(蒸馏损失):
使用 Kullback-Leibler (KL) 散度计算学生模型与教师模型输出分布之间的差异:

T用于平衡蒸馏过程中损失的缩放。

3. 组合损失函数
将硬标签损失和软标签损失组合起来,形成总损失函数:

α 控制两种损失的权重。

12.蒸馏T的作用是什么?

答:作用是将差异比较大的类别预测分数,进行降低差别,使模型不会太笃定某一结果。使模型进行更好的训练

在知识蒸馏(Knowledge Distillation)中,温度参数 TTT 的作用是对模型的输出(通常是 softmax 分数)进行平滑处理,以降低类别之间的预测分数差异。这种操作有助于模型从教师模型(Teacher)学习更丰富的类别关系,而不仅仅是简单的标签分类。

具体作用

  1. 平滑类别分布
    当 T>1T > 1T>1 时,softmax 函数的输出会变得更加平滑,类别之间的概率差异被缩小。

  2. 显式类别关系的学习
    原始预测中差异过大的类别会被平滑处理,这使得学生模型(Student)能够关注到被忽视的次要类别信息,而非仅仅专注于概率最高的类别。

  3. 防止过拟合
    通过降低模型对某一类别的过度确信,蒸馏过程引导学生模型学会更广泛的特征,从而提高模型的泛化能力。

  4. 提高训练效率
    在平滑的分布下,损失函数中包含了更多的类别信息,优化过程更加有效,有助于学生模型更好地逼近教师模型的知识。

温度 TTT 的调节

  • T=1:softmax 与普通分类任务无差异,输出分布比较尖锐。
  • T>1:平滑分布,强化类别之间的关系建模(更常见)。
  • T<1:输出更加尖锐(较少使用)。

总结

温度 TTT 的主要作用是调节软目标分布的平滑程度,让模型在训练中学习教师模型输出中包含的隐含类别关系,从而更好地提升学生模型的性能。

13.训练CIFAR10数据集(大模型精度90以上),训练小模型,观察小模型损失和精度的变化情况
(可以跟单独训练小模型来对比知识蒸馏训练小模型的区别)

3. 量化

14.什么是量化?

答:就是将例如模型中权重或偏置项的数进行降低占用的bit数。以到达减少参数量和计算量的效果。一般是将例如float32位的数值占用空间降低为float16或Int8.虽然会丢失精度。小幅度降低准确率,但是可以大大提高模型推理速度。并可以部署到移动端等板子上。

量化(Quantization)是一种用于减少深度学习模型计算复杂度和存储需求的优化技术。其核心思想是通过降低模型中权重和激活值的数值表示精度(通常从32位浮点数降低到8位或更少),来减少计算和存储的资源消耗。

量化的主要类型

  1. 权重量化(Weight Quantization)

    • 仅将模型的权重从高精度(如32位浮点)转换为低精度(如8位整数)。
    • 目标是减少模型存储大小。
  2. 激活量化(Activation Quantization)

    • 将前向传播中的激活值也转化为低精度表示。
    • 目标是减少推理时的计算复杂度和内存使用。
  3. 全量化(Full Quantization)

    • 同时对权重和激活值进行量化。
    • 在推理阶段实现全面加速。

量化的关键技术

  • 线性量化(Linear Quantization)
    将浮点数值范围线性映射到整数值范围。

    其中,minscale是映射的参数。

  • 对称量化(Symmetric Quantization)
    使用零点对称分布,无需偏移处理,计算简单。

  • 非对称量化(Asymmetric Quantization)
    支持零点不对称分布,可以适应更复杂的数据分布。

量化的优点

  1. 减少模型大小:低精度权重占用更少的存储空间。
  2. 加速推理:低精度运算在硬件上执行更快(如8位整数运算)。
  3. 降低功耗:适合嵌入式设备和移动端应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

serenity宁静

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值