论文笔记 Grasp(梯度信号)

文章以剪枝后的梯度范数为标准,对剪枝后梯度范数下降最小的权重进行剪枝,因为文章依赖于保留梯度流来修剪网络,所以作者将他们的方法命名为梯度信号保存(Grasp)。

 

一、算法思路

        

 从数学上讲,梯度范数越大,表示在一阶内,每次梯度更新的损失减少越大,可以表述为:

\Delta \L \left ( \Theta \right )=\lim_{\varepsilon \rightarrow 0}\frac{\L \left ( \Theta +\varepsilon \triangledown \L (\Theta ) \right )-\L \left ( \Theta \right )}{\varepsilon }=\triangledown \L \left ( \Theta \right )^{T}\triangledown \L \left ( \Theta \right )\left ( 1 \right )

由于文章只关心修剪网络的性能,因此目标是保留甚至增加修剪后的梯度流(即修剪网络的梯度流)。为了实现这一点,他们将修剪操作转换为在初始权重是增加扰动\delta,以表征去除一个权重将如何影响修剪后的梯度流。

S\left ( \delta \right )=\Delta \L \left ( \Theta _{0}+\delta \right )-\underbrace{\Delta \L \left ( \Theta _{0} \right )}_{Const}=2\delta ^{T}\triangledown ^{2}\L \left ( \Theta _{0} \right )\triangledown \L \left ( \Theta _{0} \right )+\wp \left ( \left \| \delta \right \|_{2}^{2} \right )=2\delta ^{T}Hg+\wp \left ( \left \| \delta \right \|_{2}^{2} \right )(2)

其中S\left ( \delta \right )\delta的函数,它表示(1)式对初始值\Theta _{0}\delta扰动的变化。黑森矩阵H捕获每个权重之间的相关性,从而调节修剪对剩余权重的影响,当H为恒等时,上述判断依据恢复SNIP到绝对值(回想SNIP判据为\left | \delta > g \right |),然而,已经观察到不同的权重时高度耦合的,这表明Hessian不相同。在实际应用中,我们采用(2)作为衡量各权重重要性的尺度,具体来说,如果S\left ( \delta \right )是负的,那么去掉相应的权重会减少梯度流动,否则不会。因此,我们倾向于先去掉那些不会降低梯度流的权重,对于每个权重,其重要性可以通过以下方式计算:

                                           S\left ( -\Theta _{q} \right )=-\Theta _{q}\cdot \left [ Hg \right ]_{q}

对于给定的简直比例p,我们可以通过一次计算剪枝条件,并对其进行排序,然后去除权重的前p%来得到最终的剪枝掩码。粗略的说,Grasp考虑了剪枝后梯度流的变化,而SNIP只保留剪枝后的损失,这可能不会保持梯度流。

二、原文部分培养代码展示

# 定义一个函数GraSP,用于实现GraSP算法,该算法是一种基于梯度的神经网络剪枝方法
    # 参数说明:
    # net: 一个神经网络对象
    # ratio: 一个浮点数,表示要保留的权重比例
    # train_dataloader: 一个数据加载器对象,用于获取训练数据
    # device: 一个字符串或者torch.device对象,表示要使用的设备(CPU或GPU)
    # num_classes: 一个整数,表示类别的数量,默认为10
    # samples_per_class: 一个整数,表示每个类别要采样的样本数量,默认为25
    # num_iters: 一个整数,表示要进行的迭代次数,默认为1
    # T: 一个整数,表示温度参数,默认为200
    # reinit: 一个布尔值,表示是否要重新初始化线性层的权重,默认为True
def GraSP(net, ratio, train_dataloader, device, num_classes=10, samples_per_class=25, num_iters=1, T=200, reinit=True):

    eps = 1e-10 # 定义一个很小的正数eps,用于避免除以零或者其他数值稳定性问题
    keep_ratio = 1-ratio # 计算要剪枝掉的权重比例
    old_net = net # 复制原始网络

    net = copy.deepcopy(net)  # .eval() 深度复制原始网络,并将其赋值给net变量
    net.zero_grad()

    weights = [] # 创建一个空列表weights,用于存储net中所有卷积层和线性层的权重张量
    total_parameters = count_total_parameters(net)
    fc_parameters = count_fc_parameters(net)

    # rescale_weights(net)
    for layer in net.modules():
        if isinstance(layer, nn.Conv2d) or isinstance(layer, nn.Linear):
            if isinstance(layer, nn.Linear) and reinit:
                nn.init.xavier_normal(layer.weight)
            weights.append(layer.weight)
    inputs_one = [] # 创建一个空列表inputs_one,用于存储每次迭代的输入数据的一半
    targets_one = [] # 创建一个空列表targets_one,用于存储每次迭代的目标数据的一半
    grad_w = None # 创建一个None对象grad_w,用于存储权重张量的梯度和
    for w in weights:
        w.requires_grad_(True)
    print_once = False#创建一个布尔值变量print_once,并赋值为False,表示是否打印过输出概率分布
    for it in range(num_iters):

        print("(1): Iterations %d/%d." % (it, num_iters))
        inputs, targets = GraSP_fetch_data(train_dataloader, num_classes, samples_per_class) # 调用GraSP_fetch_data函数,从train_dataloader中获取输入数据和目标数据,每个类别采样samples_per_class个样本,并赋值给inputs和targets变量

        N = inputs.shape[0]
        din = copy.deepcopy(inputs)
        dtarget = copy.deepcopy(targets)
        inputs_one.append(din[:N//2])
        targets_one.append(dtarget[:N//2])
        inputs_one.append(din[N // 2:])
        targets_one.append(dtarget[N // 2:])
        inputs = inputs.to(device)
        targets = targets.to(device)

        outputs = net.forward(inputs[:N//2])/T # 调用net的forward方法,将inputs的前一半(即前N//2个样本)作为输入,得到输出,并除以T,得到一个输出概率分布,并赋值给outputs变量
        if print_once:
            x = F.softmax(outputs)
            print(x)
            print(x.max(), x.min())
            print_once = False # 将print_once设置为False,表示已经打印过了
        loss = F.cross_entropy(outputs, targets[:N//2])
        # ===== debug ================
        grad_w_p = autograd.grad(loss, weights) # 使用autograd.grad函数计算loss对weights中的每个权重张量的梯度,并返回一个梯度列表,并赋值给grad_w_p变量
        if grad_w is None:
            grad_w = list(grad_w_p)
        else:
            for idx in range(len(grad_w)):
                grad_w[idx] += grad_w_p[idx]
        outputs = net.forward(inputs[N // 2:])/T
        loss = F.cross_entropy(outputs, targets[N // 2:])
        grad_w_p = autograd.grad(loss, weights, create_graph=False)
        if grad_w is None:
            grad_w = list(grad_w_p)
        else:
            for idx in range(len(grad_w)):
                grad_w[idx] += grad_w_p[idx]
    ret_inputs = []
    ret_targets = []
    for it in range(len(inputs_one)):
        print("(2): Iterations %d/%d." % (it, num_iters))
        inputs = inputs_one.pop(0).to(device) # 从输入列表中弹出第一个元素
        targets = targets_one.pop(0).to(device) # 从输入列表中弹出第一个元素
        ret_inputs.append(inputs)
        ret_targets.append(targets)
        outputs = net.forward(inputs)/T
        loss = F.cross_entropy(outputs, targets)
        grad_f = autograd.grad(loss, weights, create_graph=True)
        z = 0
        count = 0
        for layer in net.modules():
            if isinstance(layer, nn.Conv2d) or isinstance(layer, nn.Linear):
                z += (grad_w[count].data * grad_f[count]).sum() # 计算当前层权重梯度和grad_w中对应索引位置的梯度之间的点积,并累加到z上
                count += 1
        z.backward() # 对z求反向传播,更新权重梯度

    grads = dict() # 定义一个字典,用来存储每一层权重更新后的值(-theta_q Hg)
    old_modules = list(old_net.modules())
    for idx, layer in enumerate(net.modules()):
        if isinstance(layer, nn.Conv2d) or isinstance(layer, nn.Linear):
            grads[old_modules[idx]] = -layer.weight.data * layer.weight.grad  # -theta_q Hg
    # 将grads字典中所有值展平并拼接成一个张量
    all_scores = torch.cat([torch.flatten(x) for x in grads.values()])
    # 计算所有分数绝对值之和,并加上一个很小的正数eps(防止除零错误)
    norm_factor = torch.abs(torch.sum(all_scores)) + eps
    print("** norm factor:", norm_factor)
    all_scores.div_(norm_factor) # 将所有分数除以归一化因子 
    num_params_to_rm = int(len(all_scores) * (1-keep_ratio)) # 计算要移除参数个数(总参数个数乘以保留比例)
    threshold, _ = torch.topk(all_scores, num_params_to_rm, sorted=True) # 获取所有分数中最大的num_params_to_rm个值,并排序

    acceptable_score = threshold[-1]# 获取最后一个值,作为可接受的分数阈值
    print('** accept: ', acceptable_score) # 打印可接受的分数阈值
    keep_masks = dict()
    for m, g in grads.items():
    # 将当前层的梯度除以归一化因子,与可接受的分数阈值比较,得到一个布尔张量,然后转换为浮点型张量,添加到keep_masks字典中
        keep_masks[m] = ((g / norm_factor) <= acceptable_score).float()
    print(torch.sum(torch.cat([torch.flatten(x == 1) for x in keep_masks.values()])))
    return keep_masks

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值