Overview.md 11.9 KB
Newer Older
Chi Song's avatar
Chi Song committed
1
2
3
4
5
6
7
# Compressor

我们很高兴的宣布,基于 NNI 的模型压缩工具发布了 Alpha 版本。该版本仍处于试验阶段,根据用户反馈会进行改进。 诚挚邀请您使用、反馈,或更多贡献。

NNI 提供了易于使用的工具包来帮助用户设计并使用压缩算法。 其使用了统一的接口来支持 TensorFlow 和 PyTorch。 只需要添加几行代码即可压缩模型。 NNI 中也内置了一些流程的模型压缩算法。 用户还可以通过 NNI 强大的自动调参功能来找到最好的压缩后的模型,详见[自动模型压缩](./AutoCompression.md)。 另外,用户还能使用 NNI 的接口,轻松定制新的压缩算法,详见[教程](#customize-new-compression-algorithms)

## 支持的算法
Chi Song's avatar
Chi Song committed
8

Chi Song's avatar
Chi Song committed
9
10
11
12
13
14
NNI 提供了两种朴素压缩算法以及三种流行的压缩算法,包括两种剪枝算法以及三种量化算法:

| 名称                                                  | 算法简介                                                                                                                                                                       |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Level Pruner](./Pruner.md#level-pruner)            | 根据权重的绝对值,来按比例修剪权重。                                                                                                                                                         |
| [AGP Pruner](./Pruner.md#agp-pruner)                | 自动的逐步剪枝(是否剪枝的判断:基于对模型剪枝的效果)[参考论文](https://arxiv.org/abs/1710.01878)                                                                                                        |
Chi Song's avatar
Chi Song committed
15
16
17
18
| [L1Filter Pruner](./Pruner.md#l1filter-pruner)      | 剪除卷积层中最不重要的过滤器 (PRUNING FILTERS FOR EFFICIENT CONVNETS)[参考论文](https://arxiv.org/abs/1608.08710)                                                                            |
| [Slim Pruner](./Pruner.md#slim-pruner)              | 通过修剪 BN 层中的缩放因子来修剪卷积层中的通道 (Learning Efficient Convolutional Networks through Network Slimming)[参考论文](https://arxiv.org/abs/1708.06519)                                     |
| [Lottery Ticket Pruner](./Pruner.md#agp-pruner)     | "The Lottery Ticket Hypothesis: Finding Sparse, Trainable Neural Networks" 提出的剪枝过程。 它会反复修剪模型。 [参考论文](https://arxiv.org/abs/1803.03635)                                     |
| [FPGM Pruner](./Pruner.md#fpgm-pruner)              | Filter Pruning via Geometric Median for Deep Convolutional Neural Networks Acceleration [参考论文](https://arxiv.org/pdf/1811.00250.pdf)                                       |
Chi Song's avatar
Chi Song committed
19
20
21
22
23
24
25
26
27
| [Naive Quantizer](./Quantizer.md#naive-quantizer)   | 默认将权重量化为 8 位                                                                                                                                                               |
| [QAT Quantizer](./Quantizer.md#qat-quantizer)       | 为 Efficient Integer-Arithmetic-Only Inference 量化并训练神经网络。 [参考论文](http://openaccess.thecvf.com/content_cvpr_2018/papers/Jacob_Quantization_and_Training_CVPR_2018_paper.pdf) |
| [DoReFa Quantizer](./Quantizer.md#dorefa-quantizer) | DoReFa-Net: 通过低位宽的梯度算法来训练低位宽的卷积神经网络。 [参考论文](https://arxiv.org/abs/1606.06160)                                                                                              |

## 内置压缩算法的用法

通过简单的示例来展示如何修改 Trial 代码来使用压缩算法。 比如,需要通过 Level Pruner 来将权重剪枝 80%,首先在代码中训练模型前,添加以下内容([完整代码](https://github.com/microsoft/nni/tree/master/examples/model_compress))。

TensorFlow 代码
Chi Song's avatar
Chi Song committed
28

Chi Song's avatar
Chi Song committed
29
30
31
```python
from nni.compression.tensorflow import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
Chi Song's avatar
Chi Song committed
32
33
pruner = LevelPruner(tf.get_default_graph(), config_list)
pruner.compress()
Chi Song's avatar
Chi Song committed
34
35
36
```

PyTorch 代码
Chi Song's avatar
Chi Song committed
37

Chi Song's avatar
Chi Song committed
38
39
40
```python
from nni.compression.torch import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
Chi Song's avatar
Chi Song committed
41
42
pruner = LevelPruner(model, config_list)
pruner.compress()
Chi Song's avatar
Chi Song committed
43
44
45
46
```

可使用 `nni.compression` 中的其它压缩算法。 此算法分别在 `nni.compression.torch``nni.compression.tensorflow` 中实现,支持 PyTorch 和 TensorFlow。 参考 [Pruner](./Pruner.md)[Quantizer](./Quantizer.md) 进一步了解支持的算法。

Chi Song's avatar
Chi Song committed
47
函数调用 `pruner.compress()` 来修改用户定义的模型(在 Tensorflow 中,通过 `tf.get_default_graph()` 来获得模型,而 PyTorch 中 model 是定义的模型类),并修改模型来插入 mask。 然后运行模型时,这些 mask 即会生效。 mask 可在运行时通过算法来调整。
Chi Song's avatar
Chi Song committed
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

实例化压缩算法时,会传入 `config_list`。 配置说明如下。

### 压缩算法中的用户配置

压缩模型时,用户可能希望指定稀疏率,为不同类型的操作指定不同的比例,排除某些类型的操作,或仅压缩某类操作。 配置规范可用于表达此类需求。 可将其视为一个 Python 的 `list` 对象,其中每个元素都是一个 `dict` 对象。 在每个 `dict` 中,有一些 NNI 压缩算法支持的键值:

* __op_types__:指定要压缩的操作类型。 'default' 表示使用算法的默认设置。
* __op_names__:指定需要压缩的操作的名称。 如果没有设置此字段,操作符不会通过名称筛选。
* __exclude__:默认为 False。 如果此字段为 True,表示要通过类型和名称,将一些操作从压缩中排除。

`dict` 还有一些其它键值,由特定的压缩算法所使用。 例如:

`list` 中的 `dict` 会依次被应用,也就是说,如果一个操作出现在两个配置里,后面的 `dict` 会覆盖前面的配置。

配置的简单示例如下:
Chi Song's avatar
Chi Song committed
64

Chi Song's avatar
Chi Song committed
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
```python
[
    {
        'sparsity': 0.8,
        'op_types': ['default']
    },
    {
        'sparsity': 0.6,
        'op_names': ['op_name1', 'op_name2']
    },
    {
        'exclude': True,
        'op_names': ['op_name3']
    }
]
```
Chi Song's avatar
Chi Song committed
81

Chi Song's avatar
Chi Song committed
82
83
84
85
86
87
88
其表示压缩操作的默认稀疏度为 0.8,但`op_name1``op_name2` 会使用 0.6,且不压缩 `op_name3`

### 其它 API

一些压缩算法使用 Epoch 来控制压缩进度(如[AGP](./Pruner.md#agp-pruner)),一些算法需要在每个批处理步骤后执行一些逻辑。 因此提供了另外两个 API。 一个是 `update_epoch`,可参考下例使用:

TensorFlow 代码
Chi Song's avatar
Chi Song committed
89

Chi Song's avatar
Chi Song committed
90
91
92
```python
pruner.update_epoch(epoch, sess)
```
Chi Song's avatar
Chi Song committed
93

Chi Song's avatar
Chi Song committed
94
PyTorch 代码
Chi Song's avatar
Chi Song committed
95

Chi Song's avatar
Chi Song committed
96
97
98
99
100
101
```python
pruner.update_epoch(epoch)
```

另一个是 `step`,可在每个批处理后调用 `pruner.step()`。 注意,并不是所有的算法都需要这两个 API,对于不需要它们的算法,调用它们不会有影响。

Chi Song's avatar
Chi Song committed
102
103
104
105
106
107
108
109
110
111
112
使用下列 API 可轻松将压缩后的模型导出,稀疏模型的 `state_dict` 会保存在 `model.pth` 文件中,可通过 `torch.load('model.pth')` 加载。

```
pruner.export_model(model_path='model.pth')
```

`mask_dict``onnx` 格式的剪枝模型(需要指定 `input_shape`)可这样导出:

```python
pruner.export_model(model_path='model.pth', mask_path='mask.pth', onnx_path='model.onnx', input_shape=[1, 1, 28, 28])
```
Chi Song's avatar
Chi Song committed
113
114
115
116
117
118
119
120
121
122
123

## 定制新的压缩算法

为了简化压缩算法的编写,NNI 设计了简单且灵活的接口。 对于 Pruner 和 Quantizer 分别有相应的接口。

### 剪枝算法

要实现新的剪枝算法,根据使用的框架,添加继承于 `nni.compression.tensorflow.Pruner``nni.compression.torch.Pruner` 的类。 然后,根据算法逻辑来重写成员函数。

```python
# TensorFlow 中定制 Pruner。
Chi Song's avatar
Chi Song committed
124
# PyTorch 的 Pruner,只需将
Chi Song's avatar
Chi Song committed
125
126
127
# nni.compression.tensorflow.Pruner 替换为
# nni.compression.torch.Pruner
class YourPruner(nni.compression.tensorflow.Pruner):
Chi Song's avatar
Chi Song committed
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
    def __init__(self, model, config_list):
        """
        建议使用 NNI 定义的规范来配置
        """
        super().__init__(model, config_list)

    def calc_mask(self, layer, config):
        """
        Pruner 需要重载此方法来为权重提供掩码
        掩码必须与权重有相同的形状和类型。
        将对权重执行 ``mul()`` 操作。
        此方法会挂载到模型的 ``forward()`` 方法上。

        Parameters
        ----------
        layer: LayerInfo
            为 ``layer`` 的权重计算掩码
        config: dict
            生成权重所需要的掩码
        """
Chi Song's avatar
Chi Song committed
148
149
        return your_mask

Chi Song's avatar
Chi Song committed
150
    #  PyTorch 版本不需要 sess 参数
Chi Song's avatar
Chi Song committed
151
152
153
    def update_epoch(self, epoch_num, sess):
        pass

Chi Song's avatar
Chi Song committed
154
    #  PyTorch 版本不需要 sess 参数
Chi Song's avatar
Chi Song committed
155
    def step(self, sess):
Chi Song's avatar
Chi Song committed
156
157
158
        """
        根据需要可基于 bind_model 方法中的模型或权重进行操作
        """
Chi Song's avatar
Chi Song committed
159
160
161
        pass
```

Chi Song's avatar
Chi Song committed
162
对于最简单的算法,只需要重写 `calc_mask` 函数。 它会接收需要压缩的层以及其压缩配置。 可在此函数中为此权重生成 mask 并返回。 NNI 会应用此 mask。
Chi Song's avatar
Chi Song committed
163

Chi Song's avatar
Chi Song committed
164
一些算法根据训练进度来生成 mask,如 Epoch 数量。 Pruner 可使用 `update_epoch` 来了解训练进度。 应在每个 Epoch 之前调用它。
Chi Song's avatar
Chi Song committed
165

Chi Song's avatar
Chi Song committed
166
一些算法可能需要全局的信息来生成 mask,例如模型的所有权重(用于生成统计信息). 可在 Pruner 类中通过 `self.bound_model` 来访问权重。 如果需要优化器的信息(如在 Pytorch 中),可重载 `__init__` 来接收优化器等参数。 然后 `step` 可以根据算法来处理或更新信息。 可参考[内置算法的源码](https://github.com/microsoft/nni/tree/master/src/sdk/pynni/nni/compressors)作为示例。
Chi Song's avatar
Chi Song committed
167
168
169
170
171
172
173

### 量化算法

定制量化算法的接口与剪枝算法类似。 唯一的不同是使用 `quantize_weight` 替换了 `calc_mask``quantize_weight` 直接返回量化后的权重,而不是 mask。这是因为对于量化算法,量化后的权重不能通过应用 mask 来获得。

```python
# TensorFlow 中定制 Quantizer。
Chi Song's avatar
Chi Song committed
174
# PyTorch 的 Quantizer,只需将
Chi Song's avatar
Chi Song committed
175
176
177
# nni.compression.tensorflow.Quantizer 替换为
# nni.compression.torch.Quantizer
class YourQuantizer(nni.compression.tensorflow.Quantizer):
Chi Song's avatar
Chi Song committed
178
179
180
181
182
    def __init__(self, model, config_list):
        """
        建议使用 NNI 定义的规范来配置
        """
        super().__init__(model, config_list)
Chi Song's avatar
Chi Song committed
183
184

    def quantize_weight(self, weight, config, **kwargs):
Chi Song's avatar
Chi Song committed
185
186
187
188
189
190
191
192
193
194
195
196
197
198
        """
        quantize 需要重载此方法来为权重提供掩码
        此方法挂载于模型的 :meth:`forward`。

        Parameters
        ----------
        weight : Tensor
            要被量化的权重
        config : dict
            权重量化的配置
        """

        # 此处逻辑生成 `new_weight`

Chi Song's avatar
Chi Song committed
199
200
        return new_weight

Chi Song's avatar
Chi Song committed
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
    def quantize_output(self, output, config, **kwargs):
        """
        重载此方法输出量化
        此方法挂载于模型的 `:meth:`forward`。

        Parameters
        ----------
        output : Tensor
            需要被量化的输出
        config : dict
            输出量化的配置
        """

        # 实现生成 `new_output`

        return new_output

    def quantize_input(self, *inputs, config, **kwargs):
        """
        重载此方法量化输入
        此方法挂载于模型的 :meth:`forward`。

        Parameters
        ----------
        inputs : Tensor
            需要量化的输入
        config : dict
            输入量化用的配置
        """

        # 实现生成 `new_input`

        return new_input

    # Pytorch 版本不需要 sess 参数
Chi Song's avatar
Chi Song committed
236
237
238
    def update_epoch(self, epoch_num, sess):
        pass

Chi Song's avatar
Chi Song committed
239
    # Pytorch 版本不需要 sess 参数
Chi Song's avatar
Chi Song committed
240
    def step(self, sess):
Chi Song's avatar
Chi Song committed
241
242
243
        """
       根据需要可基于 bind_model 方法中的模型或权重进行操作
        """
Chi Song's avatar
Chi Song committed
244
245
246
247
248
249
        pass
```

### 使用用户自定义的压缩算法

__[TODO]__ ...