SquadEvolutionExamples.rst 10.5 KB
Newer Older
kvartet's avatar
kvartet committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
133
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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
在阅读理解上使用自动模型架构搜索
=============================================================

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

1. Search Space
---------------

在阅读理解领域,注意力(Attention)和循环神经网络都已被证明是非常有效的方法,因此搜索空间定义如下:


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


.. image:: ../../../examples/trials/ga_squad/ga_squad.png
   :target: ../../../examples/trials/ga_squad/ga_squad.png
   :alt: 


新版本
^^^^^^^^^^^

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

2. 如何在本机运行此示例?
------------------------------------

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

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

.. code-block:: bash

   chmod +x ./download.sh
   ./download.sh

或手动下载


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

.. code-block:: 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


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

.. code-block:: 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``,以下是默认配置:

.. code-block:: yaml

   authorName: default
   experimentName: example_ga_squad
   trialConcurrency: 1
   maxExecDuration: 1h
   maxTrialNum: 1
   #choice: 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 提交任务
^^^^^^^^^^^^^^^^^^^

.. code-block:: 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``,以下是默认配置:

.. code-block:: yaml

   authorName: default
   experimentName: example_ga_squad
   trialConcurrency: 1
   maxExecDuration: 1h
   maxTrialNum: 10
   #choice: local, remote, pai
   trainingServicePlatform: pai
   #choice: true, false
   useAnnotation: false
   # 你的 nni_manager ip 地址
   nniManagerIp: 10.10.10.10
   tuner:
     codeDir: https://github.com/Microsoft/nni/tree/v1.9/examples/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: .
     gpuNum: 0
     cpuNum: 1
     memoryMB: 32869
     # 在 OpenPAI 上运行 NNI 的 Docker 映像
     image: msranni/nni:latest
   paiConfig:
     # 登录 OpenPAI 的用户名
     userName: username
     # 登录 OpenPAI 的密码
     passWord: password
     # OpenPAI 的 RestFUL 服务器地址
     host: 10.10.10.10

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

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

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

3.2 提交任务
^^^^^^^^^^^^^^^^^^^

.. code-block:: 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`` 的函数,其框架代码如下:

.. code-block:: 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 的计算图。

.. code-block:: python

   topology = graph.is_topology()

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

.. code-block:: python

   for _, topo_i in enumerate(topology):

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

4.3 Tuner
^^^^^^^^^^^^^

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

.. code-block:: 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`` 实现了简单的变异算法。 代码如下:

.. code-block:: 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 的代码。

.. code-block:: 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": "More layers will be here for actual graphs."}
       ]
   }

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


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