# 模型剪裁教程 > 原文: **作者**: [Michela Paganini](https://github.com/mickypaganini) 最新的深度学习技术依赖于难以部署的过度参数化模型。 相反,已知生物神经网络使用有效的稀疏连通性。 为了减少内存,电池和硬件消耗,同时又不牺牲精度,在设备上部署轻量级模型并通过私有设备上计算来确保私密性,确定通过减少模型中的参数数量来压缩模型的最佳技术很重要。 在研究方面,剪裁用于研究参数过度配置和参数不足网络在学习动态方面的差异,以研究幸运的稀疏子网络的作用([“彩票”](https://arxiv.org/abs/1803.03635)),以及初始化,作为破坏性的神经结构搜索技术等等。 在本教程中,您将学习如何使用`torch.nn.utils.prune`稀疏神经网络,以及如何扩展它以实现自己的自定义剪裁技术。 ## 要求 `"torch>=1.4.0a0+8e8a5e0"` ```py import torch from torch import nn import torch.nn.utils.prune as prune import torch.nn.functional as F ``` ## 创建模型 在本教程中,我们使用 LeCun 等人,1998 年的 [LeNet](http://yann.lecun.com/exdb/publis/pdf/lecun-98.pdf) 架构。 ```py 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, 3x3 square conv kernel self.conv1 = nn.Conv2d(1, 6, 3) self.conv2 = nn.Conv2d(6, 16, 3) 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.view(-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) ``` ## 检查模块 让我们检查一下 LeNet 模型中的(未剪裁)`conv1`层。 现在它将包含两个参数`weight`和`bias`,并且没有缓冲区。 ```py module = model.conv1 print(list(module.named_parameters())) ``` 出: ```py [('weight', Parameter containing: tensor([[[[ 0.1552, 0.0102, -0.1944], [ 0.0263, 0.1374, -0.3139], [ 0.2838, 0.1943, 0.0948]]], [[[-0.0296, -0.2514, 0.1300], [ 0.0756, -0.3155, -0.2900], [-0.1840, 0.1143, -0.0120]]], [[[-0.2383, -0.3022, 0.2295], [-0.0050, 0.2485, -0.3230], [-0.1317, -0.0054, 0.2659]]], [[[-0.0932, 0.1316, 0.0670], [ 0.0572, -0.1845, 0.0870], [ 0.1372, 0.1080, 0.0324]]], [[[ 0.0908, -0.3280, 0.0365], [-0.3108, 0.2317, -0.2271], [ 0.1171, 0.2113, -0.2259]]], [[[ 0.0407, 0.0512, 0.0954], [-0.0437, 0.0302, -0.1317], [ 0.2573, 0.0626, 0.0883]]]], device='cuda:0', requires_grad=True)), ('bias', Parameter containing: tensor([-0.1803, 0.1331, -0.3267, 0.3173, -0.0349, 0.1828], device='cuda:0', requires_grad=True))] ``` ```py print(list(module.named_buffers())) ``` 出: ```py [] ``` ## 剪裁模块 要剪裁模块(在此示例中,为 LeNet 架构的`conv1`层),请首先从`torch.nn.utils.prune`中可用的那些技术中选择一种剪裁技术(或[通过子类化`BasePruningMethod`实现您自己的东西](#extending-torch-nn-utils-pruning-with-custom-pruning-functions))。 然后,指定模块和该模块中要剪裁的参数的名称。 最后,使用所选剪裁技术所需的适当关键字参数,指定剪裁参数。 在此示例中,我们将在`conv1`层中名为`weight`的参数中随机剪裁 30% 的连接。 模块作为第一个参数传递给函数; `name`使用其字符串标识符在该模块中标识参数; `amount`表示与剪裁的连接百分比(如果是介于 0 和 1 之间的浮点数),或表示与剪裁的连接的绝对数量(如果它是非负整数)。 ```py prune.random_unstructured(module, name="weight", amount=0.3) ``` 剪裁是通过从参数中删除`weight`并将其替换为名为`weight_orig`的新参数(即,将`"_orig"`附加到初始参数`name`)来进行的。 `weight_orig`存储未剪裁的张量版本。 `bias`未剪裁,因此它将保持完整。 ```py print(list(module.named_parameters())) ``` 出: ```py [('bias', Parameter containing: tensor([-0.1803, 0.1331, -0.3267, 0.3173, -0.0349, 0.1828], device='cuda:0', requires_grad=True)), ('weight_orig', Parameter containing: tensor([[[[ 0.1552, 0.0102, -0.1944], [ 0.0263, 0.1374, -0.3139], [ 0.2838, 0.1943, 0.0948]]], [[[-0.0296, -0.2514, 0.1300], [ 0.0756, -0.3155, -0.2900], [-0.1840, 0.1143, -0.0120]]], [[[-0.2383, -0.3022, 0.2295], [-0.0050, 0.2485, -0.3230], [-0.1317, -0.0054, 0.2659]]], [[[-0.0932, 0.1316, 0.0670], [ 0.0572, -0.1845, 0.0870], [ 0.1372, 0.1080, 0.0324]]], [[[ 0.0908, -0.3280, 0.0365], [-0.3108, 0.2317, -0.2271], [ 0.1171, 0.2113, -0.2259]]], [[[ 0.0407, 0.0512, 0.0954], [-0.0437, 0.0302, -0.1317], [ 0.2573, 0.0626, 0.0883]]]], device='cuda:0', requires_grad=True))] ``` 通过以上选择的剪裁技术生成的剪裁掩码将保存为名为`weight_mask`的模块缓冲区(即,将`"_mask"`附加到初始参数`name`)。 ```py print(list(module.named_buffers())) ``` 出: ```py [('weight_mask', tensor([[[[1., 1., 0.], [0., 0., 1.], [1., 0., 1.]]], [[[1., 1., 1.], [1., 1., 1.], [1., 1., 1.]]], [[[1., 1., 0.], [1., 0., 0.], [1., 0., 1.]]], [[[1., 1., 1.], [1., 0., 1.], [1., 1., 1.]]], [[[1., 1., 1.], [0., 0., 1.], [1., 1., 1.]]], [[[1., 0., 0.], [1., 0., 1.], [1., 0., 0.]]]], device='cuda:0'))] ``` 为了使正向传播不更改即可工作,需要存在`weight`属性。 在`torch.nn.utils.prune`中实现的剪裁技术计算权重的剪裁版本(通过将掩码与原始参数组合)并将它们存储在属性`weight`中。 注意,这不再是`module`的参数,现在只是一个属性。 ```py print(module.weight) ``` 出: ```py tensor([[[[ 0.1552, 0.0102, -0.0000], [ 0.0000, 0.0000, -0.3139], [ 0.2838, 0.0000, 0.0948]]], [[[-0.0296, -0.2514, 0.1300], [ 0.0756, -0.3155, -0.2900], [-0.1840, 0.1143, -0.0120]]], [[[-0.2383, -0.3022, 0.0000], [-0.0050, 0.0000, -0.0000], [-0.1317, -0.0000, 0.2659]]], [[[-0.0932, 0.1316, 0.0670], [ 0.0572, -0.0000, 0.0870], [ 0.1372, 0.1080, 0.0324]]], [[[ 0.0908, -0.3280, 0.0365], [-0.0000, 0.0000, -0.2271], [ 0.1171, 0.2113, -0.2259]]], [[[ 0.0407, 0.0000, 0.0000], [-0.0437, 0.0000, -0.1317], [ 0.2573, 0.0000, 0.0000]]]], device='cuda:0', grad_fn=) ``` 最后,使用 PyTorch 的`forward_pre_hooks`在每次向前传递之前应用剪裁。 具体来说,当剪裁`module`时(如我们在此处所做的那样),它将为与之关联的每个参数获取`forward_pre_hook`进行剪裁。 在这种情况下,由于到目前为止我们只剪裁了名称为`weight`的原始参数,因此只会出现一个钩子。 ```py print(module._forward_pre_hooks) ``` 出: ```py OrderedDict([(0, )]) ``` 为了完整起见,我们现在也可以剪裁`bias`,以查看`module`的参数,缓冲区,挂钩和属性如何变化。 仅出于尝试另一种剪裁技术的目的,在此我们按 L1 范数剪裁偏差中的 3 个最小条目,如`l1_unstructured`剪裁函数中所实现的。 ```py prune.l1_unstructured(module, name="bias", amount=3) ``` 现在,我们希望命名参数同时包含`weight_orig`(从前)和`bias_orig`。 缓冲区将包括`weight_mask`和`bias_mask`。 两个张量的剪裁后的版本将作为模块属性存在,并且该模块现在将具有两个`forward_pre_hooks`。 ```py print(list(module.named_parameters())) ``` 出: ```py [('weight_orig', Parameter containing: tensor([[[[ 0.1552, 0.0102, -0.1944], [ 0.0263, 0.1374, -0.3139], [ 0.2838, 0.1943, 0.0948]]], [[[-0.0296, -0.2514, 0.1300], [ 0.0756, -0.3155, -0.2900], [-0.1840, 0.1143, -0.0120]]], [[[-0.2383, -0.3022, 0.2295], [-0.0050, 0.2485, -0.3230], [-0.1317, -0.0054, 0.2659]]], [[[-0.0932, 0.1316, 0.0670], [ 0.0572, -0.1845, 0.0870], [ 0.1372, 0.1080, 0.0324]]], [[[ 0.0908, -0.3280, 0.0365], [-0.3108, 0.2317, -0.2271], [ 0.1171, 0.2113, -0.2259]]], [[[ 0.0407, 0.0512, 0.0954], [-0.0437, 0.0302, -0.1317], [ 0.2573, 0.0626, 0.0883]]]], device='cuda:0', requires_grad=True)), ('bias_orig', Parameter containing: tensor([-0.1803, 0.1331, -0.3267, 0.3173, -0.0349, 0.1828], device='cuda:0', requires_grad=True))] ``` ```py print(list(module.named_buffers())) ``` 出: ```py [('weight_mask', tensor([[[[1., 1., 0.], [0., 0., 1.], [1., 0., 1.]]], [[[1., 1., 1.], [1., 1., 1.], [1., 1., 1.]]], [[[1., 1., 0.], [1., 0., 0.], [1., 0., 1.]]], [[[1., 1., 1.], [1., 0., 1.], [1., 1., 1.]]], [[[1., 1., 1.], [0., 0., 1.], [1., 1., 1.]]], [[[1., 0., 0.], [1., 0., 1.], [1., 0., 0.]]]], device='cuda:0')), ('bias_mask', tensor([0., 0., 1., 1., 0., 1.], device='cuda:0'))] ``` ```py print(module.bias) ``` 出: ```py tensor([-0.0000, 0.0000, -0.3267, 0.3173, -0.0000, 0.1828], device='cuda:0', grad_fn=) ``` ```py print(module._forward_pre_hooks) ``` 出: ```py OrderedDict([(0, ), (1, )]) ``` ## 迭代式剪裁 一个模块中的同一参数可以被多次剪裁,各种剪裁调用的效果等于连接应用的各种蒙版的组合。 `PruningContainer`的`compute_mask`方法可处理新遮罩与旧遮罩的组合。 例如,假设我们现在想进一步剪裁`module.weight`,这一次是使用沿着张量的第 0 轴的结构化剪裁(第 0 轴对应于卷积层的输出通道,并且对于`conv1`具有 6 维) ,基于渠道的 L2 规范。 这可以通过`ln_structured`和`n=2`和`dim=0`函数来实现。 ```py prune.ln_structured(module, name="weight", amount=0.5, n=2, dim=0) # As we can verify, this will zero out all the connections corresponding to # 50% (3 out of 6) of the channels, while preserving the action of the # previous mask. print(module.weight) ``` 出: ```py tensor([[[[ 0.0000, 0.0000, -0.0000], [ 0.0000, 0.0000, -0.0000], [ 0.0000, 0.0000, 0.0000]]], [[[-0.0296, -0.2514, 0.1300], [ 0.0756, -0.3155, -0.2900], [-0.1840, 0.1143, -0.0120]]], [[[-0.2383, -0.3022, 0.0000], [-0.0050, 0.0000, -0.0000], [-0.1317, -0.0000, 0.2659]]], [[[-0.0000, 0.0000, 0.0000], [ 0.0000, -0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000]]], [[[ 0.0908, -0.3280, 0.0365], [-0.0000, 0.0000, -0.2271], [ 0.1171, 0.2113, -0.2259]]], [[[ 0.0000, 0.0000, 0.0000], [-0.0000, 0.0000, -0.0000], [ 0.0000, 0.0000, 0.0000]]]], device='cuda:0', grad_fn=) ``` 现在,对应的钩子将为`torch.nn.utils.prune.PruningContainer`类型,并将存储应用于`weight`参数的剪裁历史。 ```py for hook in module._forward_pre_hooks.values(): if hook._tensor_name == "weight": # select out the correct hook break print(list(hook)) # pruning history in the container ``` 出: ```py [, ] ``` ## 序列化剪裁的模型 所有相关的张量,包括掩码缓冲区和用于计算剪裁的张量的原始参数,都存储在模型的`state_dict`中,因此可以根据需要轻松地序列化和保存。 ```py print(model.state_dict().keys()) ``` 出: ```py odict_keys(['conv1.weight_orig', 'conv1.bias_orig', 'conv1.weight_mask', 'conv1.bias_mask', 'conv2.weight', 'conv2.bias', 'fc1.weight', 'fc1.bias', 'fc2.weight', 'fc2.bias', 'fc3.weight', 'fc3.bias']) ``` ## 删除剪裁重新参数化 要使剪裁永久化,请删除`weight_orig`和`weight_mask`的重新参数化,然后删除`forward_pre_hook`,我们可以使用`torch.nn.utils.prune`的`remove`函数。 请注意,这不会撤消剪裁,好像从未发生过。 而是通过将参数`weight`重新分配给模型参数(剪裁后的版本)来使其永久不变。 删除重新参数化之前: ```py print(list(module.named_parameters())) ``` 出: ```py [('weight_orig', Parameter containing: tensor([[[[ 0.1552, 0.0102, -0.1944], [ 0.0263, 0.1374, -0.3139], [ 0.2838, 0.1943, 0.0948]]], [[[-0.0296, -0.2514, 0.1300], [ 0.0756, -0.3155, -0.2900], [-0.1840, 0.1143, -0.0120]]], [[[-0.2383, -0.3022, 0.2295], [-0.0050, 0.2485, -0.3230], [-0.1317, -0.0054, 0.2659]]], [[[-0.0932, 0.1316, 0.0670], [ 0.0572, -0.1845, 0.0870], [ 0.1372, 0.1080, 0.0324]]], [[[ 0.0908, -0.3280, 0.0365], [-0.3108, 0.2317, -0.2271], [ 0.1171, 0.2113, -0.2259]]], [[[ 0.0407, 0.0512, 0.0954], [-0.0437, 0.0302, -0.1317], [ 0.2573, 0.0626, 0.0883]]]], device='cuda:0', requires_grad=True)), ('bias_orig', Parameter containing: tensor([-0.1803, 0.1331, -0.3267, 0.3173, -0.0349, 0.1828], device='cuda:0', requires_grad=True))] ``` ```py print(list(module.named_buffers())) ``` 出: ```py [('weight_mask', tensor([[[[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]]], [[[1., 1., 1.], [1., 1., 1.], [1., 1., 1.]]], [[[1., 1., 0.], [1., 0., 0.], [1., 0., 1.]]], [[[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]]], [[[1., 1., 1.], [0., 0., 1.], [1., 1., 1.]]], [[[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]]]], device='cuda:0')), ('bias_mask', tensor([0., 0., 1., 1., 0., 1.], device='cuda:0'))] ``` ```py print(module.weight) ``` 出: ```py tensor([[[[ 0.0000, 0.0000, -0.0000], [ 0.0000, 0.0000, -0.0000], [ 0.0000, 0.0000, 0.0000]]], [[[-0.0296, -0.2514, 0.1300], [ 0.0756, -0.3155, -0.2900], [-0.1840, 0.1143, -0.0120]]], [[[-0.2383, -0.3022, 0.0000], [-0.0050, 0.0000, -0.0000], [-0.1317, -0.0000, 0.2659]]], [[[-0.0000, 0.0000, 0.0000], [ 0.0000, -0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000]]], [[[ 0.0908, -0.3280, 0.0365], [-0.0000, 0.0000, -0.2271], [ 0.1171, 0.2113, -0.2259]]], [[[ 0.0000, 0.0000, 0.0000], [-0.0000, 0.0000, -0.0000], [ 0.0000, 0.0000, 0.0000]]]], device='cuda:0', grad_fn=) ``` 删除重新参数化后: ```py prune.remove(module, 'weight') print(list(module.named_parameters())) ``` 出: ```py [('bias_orig', Parameter containing: tensor([-0.1803, 0.1331, -0.3267, 0.3173, -0.0349, 0.1828], device='cuda:0', requires_grad=True)), ('weight', Parameter containing: tensor([[[[ 0.0000, 0.0000, -0.0000], [ 0.0000, 0.0000, -0.0000], [ 0.0000, 0.0000, 0.0000]]], [[[-0.0296, -0.2514, 0.1300], [ 0.0756, -0.3155, -0.2900], [-0.1840, 0.1143, -0.0120]]], [[[-0.2383, -0.3022, 0.0000], [-0.0050, 0.0000, -0.0000], [-0.1317, -0.0000, 0.2659]]], [[[-0.0000, 0.0000, 0.0000], [ 0.0000, -0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000]]], [[[ 0.0908, -0.3280, 0.0365], [-0.0000, 0.0000, -0.2271], [ 0.1171, 0.2113, -0.2259]]], [[[ 0.0000, 0.0000, 0.0000], [-0.0000, 0.0000, -0.0000], [ 0.0000, 0.0000, 0.0000]]]], device='cuda:0', requires_grad=True))] ``` ```py print(list(module.named_buffers())) ``` 出: ```py [('bias_mask', tensor([0., 0., 1., 1., 0., 1.], device='cuda:0'))] ``` ## 剪裁模型中的多个参数 通过指定所需的剪裁技术和参数,我们可以轻松地剪裁网络中的多个张量,也许根据它们的类型,如在本示例中将看到的那样。 ```py new_model = LeNet() for name, module in new_model.named_modules(): # prune 20% of connections in all 2D-conv layers if isinstance(module, torch.nn.Conv2d): prune.l1_unstructured(module, name='weight', amount=0.2) # prune 40% of connections in all linear layers elif isinstance(module, torch.nn.Linear): prune.l1_unstructured(module, name='weight', amount=0.4) print(dict(new_model.named_buffers()).keys()) # to verify that all masks exist ``` 出: ```py dict_keys(['conv1.weight_mask', 'conv2.weight_mask', 'fc1.weight_mask', 'fc2.weight_mask', 'fc3.weight_mask']) ``` ## 全局剪裁 到目前为止,我们仅查看了通常称为“局部”剪裁的情况,即通过比较每个条目的统计信息(权重,激活度,梯度等)来逐个剪裁模型中的张量的做法。 到该张量中的其他条目。 但是,一种通用且可能更强大的技术是通过删除(例如)删除整个模型中最低的 20% 的连接,而不是删除每一层中最低的 20% 的连接来一次剪裁模型。 这很可能导致每个层的剪裁百分比不同。 让我们看看如何使用`torch.nn.utils.prune`中的`global_unstructured`进行操作。 ```py model = LeNet() parameters_to_prune = ( (model.conv1, 'weight'), (model.conv2, 'weight'), (model.fc1, 'weight'), (model.fc2, 'weight'), (model.fc3, 'weight'), ) prune.global_unstructured( parameters_to_prune, pruning_method=prune.L1Unstructured, amount=0.2, ) ``` 现在,我们可以检查每个剪裁参数中引起的稀疏性,该稀疏性将不等于每层中的 20%。 但是,全局稀疏度将(大约)为 20%。 ```py print( "Sparsity in conv1.weight: {:.2f}%".format( 100\. * float(torch.sum(model.conv1.weight == 0)) / float(model.conv1.weight.nelement()) ) ) print( "Sparsity in conv2.weight: {:.2f}%".format( 100\. * float(torch.sum(model.conv2.weight == 0)) / float(model.conv2.weight.nelement()) ) ) print( "Sparsity in fc1.weight: {:.2f}%".format( 100\. * float(torch.sum(model.fc1.weight == 0)) / float(model.fc1.weight.nelement()) ) ) print( "Sparsity in fc2.weight: {:.2f}%".format( 100\. * float(torch.sum(model.fc2.weight == 0)) / float(model.fc2.weight.nelement()) ) ) print( "Sparsity in fc3.weight: {:.2f}%".format( 100\. * float(torch.sum(model.fc3.weight == 0)) / float(model.fc3.weight.nelement()) ) ) print( "Global sparsity: {:.2f}%".format( 100\. * float( torch.sum(model.conv1.weight == 0) + torch.sum(model.conv2.weight == 0) + torch.sum(model.fc1.weight == 0) + torch.sum(model.fc2.weight == 0) + torch.sum(model.fc3.weight == 0) ) / float( model.conv1.weight.nelement() + model.conv2.weight.nelement() + model.fc1.weight.nelement() + model.fc2.weight.nelement() + model.fc3.weight.nelement() ) ) ) ``` 出: ```py Sparsity in conv1.weight: 3.70% Sparsity in conv2.weight: 8.10% Sparsity in fc1.weight: 22.05% Sparsity in fc2.weight: 12.29% Sparsity in fc3.weight: 8.45% Global sparsity: 20.00% ``` ## 使用自定义剪裁函数扩展`torch.nn.utils.prune` 要实现自己的剪裁函数,可以通过继承`BasePruningMethod`基类的子类来扩展`nn.utils.prune`模块,这与所有其他剪裁方法一样。 基类为您实现以下方法:`__call__`,`apply_mask`,`apply`,`prune`和`remove`。 除了一些特殊情况外,您无需为新的剪裁技术重新实现这些方法。 但是,您将必须实现`__init__`(构造器)和`compute_mask`(有关如何根据剪裁技术的逻辑为给定张量计算掩码的说明)。 另外,您将必须指定此技术实现的剪裁类型(支持的选项为`global`,`structured`和`unstructured`)。 需要确定在迭代应用剪裁的情况下如何组合蒙版。 换句话说,当剪裁预剪裁的参数时,当前的剪裁技术应作用于参数的未剪裁部分。 指定`PRUNING_TYPE`将使`PruningContainer`(处理剪裁掩码的迭代应用)正确识别要剪裁的参数。 例如,假设您要实现一种剪裁技术,以剪裁张量中的所有其他条目(或者-如果先前已剪裁过张量,则剪裁张量的其余未剪裁部分)。 这将是`PRUNING_TYPE='unstructured'`,因为它作用于层中的单个连接,而不作用于整个单元/通道(`'structured'`),或作用于不同的参数(`'global'`)。 ```py class FooBarPruningMethod(prune.BasePruningMethod): """Prune every other entry in a tensor """ PRUNING_TYPE = 'unstructured' def compute_mask(self, t, default_mask): mask = default_mask.clone() mask.view(-1)[::2] = 0 return mask ``` 现在,要将其应用于`nn.Module`中的参数,还应该提供一个简单的函数来实例化该方法并将其应用。 ```py def foobar_unstructured(module, name): """Prunes tensor corresponding to parameter called `name` in `module` by removing every other entry in the tensors. Modifies module in place (and also return the modified module) by: 1) adding a named buffer called `name+'_mask'` corresponding to the binary mask applied to the parameter `name` by the pruning method. The parameter `name` is replaced by its pruned version, while the original (unpruned) parameter is stored in a new parameter named `name+'_orig'`. Args: module (nn.Module): module containing the tensor to prune name (string): parameter name within `module` on which pruning will act. Returns: module (nn.Module): modified (i.e. pruned) version of the input module Examples: >>> m = nn.Linear(3, 4) >>> foobar_unstructured(m, name='bias') """ FooBarPruningMethod.apply(module, name) return module ``` 试试吧! ```py model = LeNet() foobar_unstructured(model.fc3, name='bias') print(model.fc3.bias_mask) ``` 出: ```py tensor([0., 1., 0., 1., 0., 1., 0., 1., 0., 1.]) ``` **脚本的总运行时间**:(0 分钟 0.135 秒) [下载 Python 源码:`pruning_tutorial.py`](../_downloads/8eb4a30bf66c6a1a0d1faba246c07bb3/pruning_tutorial.py) [下载 Jupyter 笔记本:`pruning_tutorial.ipynb`](../_downloads/f40ae04715cdb214ecba048c12f8dddf/pruning_tutorial.ipynb) [由 Sphinx 画廊](https://sphinx-gallery.readthedocs.io)生成的画廊