SquadEvolutionExamples.md 9.67 KB
Newer Older
Chi Song's avatar
Chi Song committed
1
2
3
4
5
6
# 在阅读理解上使用自动模型架构搜索

该样例展示了如何使用遗传算法为阅读理解任务找到好的模型架构。

## 1. 搜索空间

Chi Song's avatar
Chi Song committed
7
在阅读理解领域,注意力(Attention)和循环神经网络都已被证明是非常有效的方法,因此搜索空间定义如下:
Chi Song's avatar
Chi Song committed
8
9
10
11
12
13
14
15
16

1. IDENTITY (Effectively 表示继续训练)。
2. INSERT-RNN-LAYER (插入 LSTM。 在 Experiment 中比较了 GRU 和 LSTM 的性能后,我们决定在这里采用 LSTM。)
3. REMOVE-RNN-LAYER
4. INSERT-ATTENTION-LAYER (插入注意力层。)
5. REMOVE-ATTENTION-LAYER
6. ADD-SKIP (在随机层之间一致).
7. REMOVE-SKIP (移除随机跳过).

17
![](../../examples/trials/ga_squad/ga_squad.png)
Chi Song's avatar
Chi Song committed
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132

### 新版本

另一个时间更快,性能更好的版本正在开发中。 很快将发布。

## 2. 如何在本机运行此样例?

### 2.1 使用下载脚本来下载数据

执行下列命令来下载所需要的数据:

```bash
chmod +x ./download.sh
./download.sh
```

或手动下载

1. 在 https://rajpurkar.github.io/SQuAD-explorer/ 下载 "dev-v1.1.json" 和 "train-v1.1.json"。

```bash
wget https://rajpurkar.github.io/SQuAD-explorer/dataset/train-v1.1.json
wget https://rajpurkar.github.io/SQuAD-explorer/dataset/dev-v1.1.json
```

2. 在 https://nlp.stanford.edu/projects/glove/ 下载 "glove.840B.300d.txt"。

```bash
wget http://nlp.stanford.edu/data/glove.840B.300d.zip
unzip glove.840B.300d.zip
```

### 2.2 更新配置

修改 `nni/examples/trials/ga_squad/config.yml`,以下是默认配置:

```yaml
authorName: default
experimentName: example_ga_squad
trialConcurrency: 1
maxExecDuration: 1h
maxTrialNum: 1
#可选项: local, remote
trainingServicePlatform: local
#可选项: true, false
useAnnotation: false
tuner:
  codeDir: ~/nni/examples/tuners/ga_customer_tuner
  classFileName: customer_tuner.py
  className: CustomerTuner
  classArgs:
    optimize_mode: maximize
trial:
  command: python3 trial.py
  codeDir: ~/nni/examples/trials/ga_squad
  gpuNum: 0
```

在 "Trial" 部分中,如果需要使用 GPU 来进行架构搜索,可将 `gpuNum``0` 改为 `1`。 根据训练时长,可以增加 `maxTrialNum``maxExecDuration`

### 2.3 提交任务

```bash
nnictl create --config ~/nni/examples/trials/ga_squad/config.yml
```

## 3 在 OpenPAI 上运行此样例

根据上传大小的限制,仅上传源代码,并在训练过程中下载数据。 本 Experiment 需要的内存 `memoryMB >= 32G`,训练过程可能需要数小时。

### 3.1 更新配置

修改 `nni/examples/trials/ga_squad/config_pai.yml`,以下是默认配置:

```yaml
authorName: default
experimentName: example_ga_squad
trialConcurrency: 1
maxExecDuration: 1h
maxTrialNum: 10
#可选项: local, remote, pai
trainingServicePlatform: pai
#可选项: true, false
useAnnotation: false
# nni_manager 的 ip
nniManagerIp: 10.10.10.10
tuner:
  codeDir: ../../tuners/ga_customer_tuner
  classFileName: customer_tuner.py
  className: CustomerTuner
  classArgs:
    optimize_mode: maximize
trial:
  command: chmod +x ./download.sh && ./download.sh && python3 trial.py
  codeDir: https://github.com/Microsoft/nni/tree/master/examples/tuners/ga_customer_tuner
  gpuNum: 0
  cpuNum: 1
  memoryMB: 32869
  #在 OpenPAI 上运行 NNI 任务的 Docker 映像
  image: msranni/nni:latest
  #在 OpenPAI 的 hdfs 目录上存储数据的目录,如:'hdfs://host:port/directory'
  dataDir: hdfs://10.10.10.10:9000/username/nni
  #在 OpenPAI 的 hdfs 目录上存储输出的目录,如:'hdfs://host:port/directory'
  outputDir: hdfs://10.10.10.10:9000/username/nni
paiConfig:
  #登录 OpenPAI 的用户名
  userName: username
  #登录 OpenPAI 的密码
  passWord: password
  # OpenPAI 的 RESTful 服务器地址
  host: 10.10.10.10
```

将默认值改为个人账户和服务器信息。 包括 `nniManagerIp`, `dataDir`, `outputDir`, `userName`, `passWord``host`

133
在 "Trial" 部分中,如果需要使用 GPU 来进行架构搜索,可将 `gpuNum``0` 改为 `1`。 根据训练时长,可以增加 `maxTrialNum``maxExecDuration`
Chi Song's avatar
Chi Song committed
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312

`trialConcurrency` 是并发运行的 Trial 的数量。如果将 `gpuNum` 设置为 1,则需要与 GPU 数量一致。

### 3.2 提交任务

```bash
nnictl create --config ~/nni/examples/trials/ga_squad/config_pai.yml
```

## 4. 代码实现

### 4.1 实现方法

基于进化算法架构的问答和其它样例一样,有两个部分:Trial 和 Tuner。

### 4.2 Trial

Trial 有大量的文件、函数和类。 这里只简单介绍最重要的文件:

* `attention.py` 包含了 Tensorflow 注意力算法的实现。
* `data.py` 包含了数据处理函数。
* `evaluate.py` 包含了评估脚本。
* `graph.py` 包含了计算图的定义。
* `rnn.py` 包含了 TensorFlow 的 GRU 实现。
* `train_model.py` 是整个文档模型的封装。

这些文件中,`trial.py``graph_to_tf.py` 非常特别。

`graph_to_tf.py` 有一个叫做 `graph_to_network`的函数,其框架代码如下:

```python
def graph_to_network(input1,
                     input2,
                     input1_lengths,
                     input2_lengths,
                     graph,
                     dropout_rate,
                     is_training,
                     num_heads=1,
                     rnn_units=256):
    topology = graph.is_topology()
    layers = dict()
    layers_sequence_lengths = dict()
    num_units = input1.get_shape().as_list()[-1]
    layers[0] = input1*tf.sqrt(tf.cast(num_units, tf.float32)) + \
        positional_encoding(input1, scale=False, zero_pad=False)
    layers[1] = input2*tf.sqrt(tf.cast(num_units, tf.float32))
    layers[0] = dropout(layers[0], dropout_rate, is_training)
    layers[1] = dropout(layers[1], dropout_rate, is_training)
    layers_sequence_lengths[0] = input1_lengths
    layers_sequence_lengths[1] = input2_lengths
    for _, topo_i in enumerate(topology):
        if topo_i == '|':
            continue
        if graph.layers[topo_i].graph_type == LayerType.input.value:
            # ......
        elif graph.layers[topo_i].graph_type == LayerType.attention.value:
            # ......
        # 处理更多层
```

正如我们看到的,这个函数实际上是个编译器。它将内部模型的 DAG 配置`图`(在`模型配置格式`章节介绍)转换为 Tensorflow 的计算图。

```python
topology = graph.is_topology()
```

将内部图表示进行拓扑排序,代码在下列循环中:

```python
for _, topo_i in enumerate(topology):
```

执行实际转换,将每层映射为 TensorFlow 计算图中的一部分。

### 4.3 Tuner

Tuner 比 Trial 代码简单很多。 它们共用了同样的 `graph.py`。 此外,Tuner 有 `customer_tuner.py`,其中最重要的类是 `CustomerTuner`

```python
class CustomerTuner(Tuner):
    # ......

    def generate_parameters(self, parameter_id):
        """将一组 Trial 图配置作为序列化对象返回。
        parameter_id : int
        """
        if len(self.population) <= 0:
            logger.debug("the len of poplution lower than zero.")
            raise Exception('The population is empty')
        pos = -1
        for i in range(len(self.population)):
            if self.population[i].result == None:
                pos = i
                break
        if pos != -1:
            indiv = copy.deepcopy(self.population[pos])
            self.population.pop(pos)
            temp = json.loads(graph_dumps(indiv.config))
        else:
            random.shuffle(self.population)
            if self.population[0].result > self.population[1].result:
                self.population[0] = self.population[1]
            indiv = copy.deepcopy(self.population[0])
            self.population.pop(1)
            indiv.mutation()
            graph = indiv.config
            temp =  json.loads(graph_dumps(graph))

    # ......
```

重载函数 `generate_parameters` 实现了简单的变异算法。 代码如下:

```python
            if self.population[0].result > self.population[1].result:
                self.population[0] = self.population[1]
            indiv = copy.deepcopy(self.population[0])
```

控制突变过程。 它会在种群中随机取出两个个体,对更好结果的一个保留数据,并突变另一个。

### 4.4 模型配置格式

这是模型配置的样例,在架构搜索过程中,从 Tuner 传入 Trial 的代码。

```json
{
    "max_layer_num": 50,
    "layers": [
        {
            "input_size": 0,
            "type": 3,
            "output_size": 1,
            "input": [],
            "size": "x",
            "output": [4, 5],
            "is_delete": false
        },
        {
            "input_size": 0,
            "type": 3,
            "output_size": 1,
            "input": [],
            "size": "y",
            "output": [4, 5],
            "is_delete": false
        },
        {
            "input_size": 1,
            "type": 4,
            "output_size": 0,
            "input": [6],
            "size": "x",
            "output": [],
            "is_delete": false
        },
        {
            "input_size": 1,
            "type": 4,
            "output_size": 0,
            "input": [5],
            "size": "y",
            "output": [],
            "is_delete": false
        },
        {"Comment": "实际图会有更多层。"}
    ]
}
```

每个模型配置都有一个 "layers" 部分,这是层定义的 JSON 列表。 每层的定义也是一个 JSON 对象:

* `type` 是层的类型。 0, 1, 2, 3, 4 对应注意力、自注意力、RNN、输入和输出层。
* `size` 是输出的长度。 "x", "y" 对应文档长度和问题长度。
* `input_size` 是该层的输入数量。
* `input` 表示输入层的索引。
* `output` 是输出层的索引,该层会作为这些层的输入。
* `is_delete` 表示此层是否可用。