QuickStart.rst 10.5 KB
Newer Older
1
2
Quick Start of Retiarii on NNI
==============================
QuanluZhang's avatar
QuanluZhang committed
3

QuanluZhang's avatar
QuanluZhang committed
4
5
6

.. contents::

QuanluZhang's avatar
QuanluZhang committed
7
In this quick start, we use multi-trial NAS as an example to show how to construct and explore a model space. There are mainly three crucial components for a neural architecture search task, namely,
QuanluZhang's avatar
QuanluZhang committed
8

QuanluZhang's avatar
QuanluZhang committed
9
10
11
* Model search space that defines a set of models to explore.
* A proper strategy as the method to explore this model space.
* A model evaluator that reports the performance of every model in the space.
QuanluZhang's avatar
QuanluZhang committed
12

QuanluZhang's avatar
QuanluZhang committed
13
The tutorial for One-shot NAS can be found `here <./OneshotTrainer.rst>`__.
14

15
Currently, PyTorch is the only supported framework by Retiarii, and we have only tested **PyTorch 1.7 to 1.10**. This documentation assumes PyTorch context but it should also apply to other frameworks, which is in our future plan.
QuanluZhang's avatar
QuanluZhang committed
16
17
18
19

Define your Model Space
-----------------------

QuanluZhang's avatar
QuanluZhang committed
20
Model space is defined by users to express a set of models that users want to explore, which contains potentially good-performing models. In this framework, a model space is defined with two parts: a base model and possible mutations on the base model.
QuanluZhang's avatar
QuanluZhang committed
21
22
23
24

Define Base Model
^^^^^^^^^^^^^^^^^

QuanluZhang's avatar
QuanluZhang committed
25
Defining a base model is almost the same as defining a PyTorch (or TensorFlow) model. Usually, you only need to replace the code ``import torch.nn as nn`` with ``import nni.retiarii.nn.pytorch as nn`` to use our wrapped PyTorch modules.
QuanluZhang's avatar
QuanluZhang committed
26

QuanluZhang's avatar
QuanluZhang committed
27
Below is a very simple example of defining a base model.
QuanluZhang's avatar
QuanluZhang committed
28
29
30

.. code-block:: python

QuanluZhang's avatar
QuanluZhang committed
31
  import torch
QuanluZhang's avatar
QuanluZhang committed
32
33
  import torch.nn.functional as F
  import nni.retiarii.nn.pytorch as nn
34
  from nni.retiarii import model_wrapper
QuanluZhang's avatar
QuanluZhang committed
35

QuanluZhang's avatar
QuanluZhang committed
36
37
  @model_wrapper      # this decorator should be put on the out most
  class Net(nn.Module):
QuanluZhang's avatar
QuanluZhang committed
38
39
    def __init__(self):
      super().__init__()
QuanluZhang's avatar
QuanluZhang committed
40
41
42
43
44
45
      self.conv1 = nn.Conv2d(1, 32, 3, 1)
      self.conv2 = nn.Conv2d(32, 64, 3, 1)
      self.dropout1 = nn.Dropout(0.25)
      self.dropout2 = nn.Dropout(0.5)
      self.fc1 = nn.Linear(9216, 128)
      self.fc2 = nn.Linear(128, 10)
QuanluZhang's avatar
QuanluZhang committed
46
47

    def forward(self, x):
QuanluZhang's avatar
QuanluZhang committed
48
49
50
51
52
53
      x = F.relu(self.conv1(x))
      x = F.max_pool2d(self.conv2(x), 2)
      x = torch.flatten(self.dropout1(x), 1)
      x = self.fc2(self.dropout2(F.relu(self.fc1(x))))
      output = F.log_softmax(x, dim=1)
      return output
QuanluZhang's avatar
QuanluZhang committed
54

55
56
.. tip:: Always keep in mind that you should use ``import nni.retiarii.nn.pytorch as nn`` and :meth:`nni.retiarii.model_wrapper`. Many mistakes are a result of forgetting one of those. Also, please use ``torch.nn`` for submodules of ``nn.init``, e.g., ``torch.nn.init`` instead of ``nn.init``. 

QuanluZhang's avatar
QuanluZhang committed
57
58
59
Define Model Mutations
^^^^^^^^^^^^^^^^^^^^^^

QuanluZhang's avatar
QuanluZhang committed
60
A base model is only one concrete model not a model space. We provide `APIs and primitives <./MutationPrimitives.rst>`__ for users to express how the base model can be mutated. That is, to build a model space which includes many models.
QuanluZhang's avatar
QuanluZhang committed
61

QuanluZhang's avatar
QuanluZhang committed
62
Based on the above base model, we can define a model space as below. 
QuanluZhang's avatar
QuanluZhang committed
63

QuanluZhang's avatar
QuanluZhang committed
64
.. code-block:: diff
QuanluZhang's avatar
QuanluZhang committed
65

QuanluZhang's avatar
QuanluZhang committed
66
67
68
69
  import torch
  import torch.nn.functional as F
  import nni.retiarii.nn.pytorch as nn
  from nni.retiarii import model_wrapper
QuanluZhang's avatar
QuanluZhang committed
70

QuanluZhang's avatar
QuanluZhang committed
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
  @model_wrapper
  class Net(nn.Module):
    def __init__(self):
      super().__init__()
      self.conv1 = nn.Conv2d(1, 32, 3, 1)
  -   self.conv2 = nn.Conv2d(32, 64, 3, 1)
  +   self.conv2 = nn.LayerChoice([
  +       nn.Conv2d(32, 64, 3, 1),
  +       DepthwiseSeparableConv(32, 64)
  +   ])
  -   self.dropout1 = nn.Dropout(0.25)
  +   self.dropout1 = nn.Dropout(nn.ValueChoice([0.25, 0.5, 0.75]))
      self.dropout2 = nn.Dropout(0.5)
  -   self.fc1 = nn.Linear(9216, 128)
  -   self.fc2 = nn.Linear(128, 10)
  +   feature = nn.ValueChoice([64, 128, 256])
  +   self.fc1 = nn.Linear(9216, feature)
  +   self.fc2 = nn.Linear(feature, 10)
QuanluZhang's avatar
QuanluZhang committed
89

QuanluZhang's avatar
QuanluZhang committed
90
91
92
93
94
95
96
    def forward(self, x):
      x = F.relu(self.conv1(x))
      x = F.max_pool2d(self.conv2(x), 2)
      x = torch.flatten(self.dropout1(x), 1)
      x = self.fc2(self.dropout2(F.relu(self.fc1(x))))
      output = F.log_softmax(x, dim=1)
      return output
QuanluZhang's avatar
QuanluZhang committed
97

QuanluZhang's avatar
QuanluZhang committed
98
This example uses two mutation APIs, ``nn.LayerChoice`` and ``nn.ValueChoice``. ``nn.LayerChoice`` takes a list of candidate modules (two in this example), one will be chosen for each sampled model. It can be used like normal PyTorch module. ``nn.ValueChoice`` takes a list of candidate values, one will be chosen to take effect for each sampled model.
QuanluZhang's avatar
QuanluZhang committed
99

100
More detailed API description and usage can be found `here <./construct_space.rst>`__ .
QuanluZhang's avatar
QuanluZhang committed
101

QuanluZhang's avatar
QuanluZhang committed
102
.. note:: We are actively enriching the mutation APIs, to facilitate easy construction of model space. If the currently supported mutation APIs cannot express your model space, please refer to `this doc <./Mutators.rst>`__ for customizing mutators.
QuanluZhang's avatar
QuanluZhang committed
103

QuanluZhang's avatar
QuanluZhang committed
104
105
Explore the Defined Model Space
-------------------------------
QuanluZhang's avatar
QuanluZhang committed
106

QuanluZhang's avatar
QuanluZhang committed
107
There are basically two exploration approaches: (1) search by evaluating each sampled model independently, which is the search approach in multi-trial NAS and (2) one-shot weight-sharing based search, which is used in one-shot NAS. We demonstrate the first approach in this tutorial. Users can refer to `here <./OneshotTrainer.rst>`__ for the second approach.
QuanluZhang's avatar
QuanluZhang committed
108

QuanluZhang's avatar
QuanluZhang committed
109
First, users need to pick a proper exploration strategy to explore the defined model space. Second, users need to pick or customize a model evaluator to evaluate the performance of each explored model.
QuanluZhang's avatar
QuanluZhang committed
110

QuanluZhang's avatar
QuanluZhang committed
111
112
Pick an exploration strategy
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
QuanluZhang's avatar
QuanluZhang committed
113

114
Retiarii supports many `exploration strategies <./ExplorationStrategies.rst>`__.
QuanluZhang's avatar
QuanluZhang committed
115

116
Simply choosing (i.e., instantiate) an exploration strategy as below.
QuanluZhang's avatar
QuanluZhang committed
117
118
119

.. code-block:: python

QuanluZhang's avatar
QuanluZhang committed
120
  import nni.retiarii.strategy as strategy
QuanluZhang's avatar
QuanluZhang committed
121

QuanluZhang's avatar
QuanluZhang committed
122
  search_strategy = strategy.Random(dedup=True)  # dedup=False if deduplication is not wanted
QuanluZhang's avatar
QuanluZhang committed
123

QuanluZhang's avatar
QuanluZhang committed
124
125
Pick or customize a model evaluator
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
QuanluZhang's avatar
QuanluZhang committed
126

QuanluZhang's avatar
QuanluZhang committed
127
In the exploration process, the exploration strategy repeatedly generates new models. A model evaluator is for training and validating each generated model to obtain the model's performance. The performance is sent to the exploration strategy for the strategy to generate better models.
QuanluZhang's avatar
QuanluZhang committed
128

129
Retiarii has provided `built-in model evaluators <./ModelEvaluators.rst>`__, but to start with, it is recommended to use ``FunctionalEvaluator``, that is, to wrap your own training and evaluation code with one single function. This function should receive one single model class and uses ``nni.report_final_result`` to report the final score of this model.
QuanluZhang's avatar
QuanluZhang committed
130

QuanluZhang's avatar
QuanluZhang committed
131
An example here creates a simple evaluator that runs on MNIST dataset, trains for 2 epochs, and reports its validation accuracy.
QuanluZhang's avatar
QuanluZhang committed
132

133
134
135
136
137
138
139
140
141
142
..  code-block:: python

    def evaluate_model(model_cls):
      # "model_cls" is a class, need to instantiate
      model = model_cls()

      optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
      transf = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])
      train_loader = DataLoader(MNIST('data/mnist', download=True, transform=transf), batch_size=64, shuffle=True)
      test_loader = DataLoader(MNIST('data/mnist', download=True, train=False, transform=transf), batch_size=64)
QuanluZhang's avatar
QuanluZhang committed
143

144
      device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')
QuanluZhang's avatar
QuanluZhang committed
145

146
147
148
149
150
151
152
      for epoch in range(3):
        # train the model for one epoch
        train_epoch(model, device, train_loader, optimizer, epoch)
        # test the model for one epoch
        accuracy = test_epoch(model, device, test_loader)
        # call report intermediate result. Result can be float or dict
        nni.report_intermediate_result(accuracy)
153

154
155
      # report final test result
      nni.report_final_result(accuracy)
QuanluZhang's avatar
QuanluZhang committed
156

157
158
    # Create the evaluator
    evaluator = nni.retiarii.evaluator.FunctionalEvaluator(evaluate_model)
QuanluZhang's avatar
QuanluZhang committed
159

160
The ``train_epoch`` and ``test_epoch`` here can be any customized function, where users can write their own training recipe. See :githublink:`examples/nas/multi-trial/mnist/search.py` for the full example.
QuanluZhang's avatar
QuanluZhang committed
161

162
It is recommended that the ``evaluate_model`` here accepts no additional arguments other than ``model_cls``. However, in the `advanced tutorial <./ModelEvaluators.rst>`__, we will show how to use additional arguments in case you actually need those. In future, we will support mutation on the arguments of evaluators, which is commonly called "Hyper-parmeter tuning".
163

QuanluZhang's avatar
QuanluZhang committed
164
165
Launch an Experiment
--------------------
QuanluZhang's avatar
QuanluZhang committed
166

QuanluZhang's avatar
QuanluZhang committed
167
After all the above are prepared, it is time to start an experiment to do the model search. An example is shown below.
QuanluZhang's avatar
QuanluZhang committed
168
169
170

.. code-block:: python

171
  exp = RetiariiExperiment(base_model, evaluator, [], search_strategy)
QuanluZhang's avatar
QuanluZhang committed
172
  exp_config = RetiariiExeConfig('local')
QuanluZhang's avatar
QuanluZhang committed
173
  exp_config.experiment_name = 'mnist_search'
QuanluZhang's avatar
QuanluZhang committed
174
  exp_config.trial_concurrency = 2
QuanluZhang's avatar
QuanluZhang committed
175
  exp_config.max_trial_number = 20
QuanluZhang's avatar
QuanluZhang committed
176
177
178
  exp_config.training_service.use_active_gpu = False
  exp.run(exp_config, 8081)

QuanluZhang's avatar
QuanluZhang committed
179
The complete code of this example can be found :githublink:`here <examples/nas/multi-trial/mnist/search.py>`. Users can also run Retiarii Experiment with `different training services <../training_services.rst>`__ besides ``local`` training service.
180

QuanluZhang's avatar
QuanluZhang committed
181
182
Visualize the Experiment
------------------------
183

184
Users can visualize their experiment in the same way as visualizing a normal hyper-parameter tuning experiment. For example, open ``localhost:8081`` in your browser, 8081 is the port that you set in ``exp.run``. Please refer to `here <../Tutorial/WebUI.rst>`__ for details.
185

186
187
188
189
190
191
192
193
194
195
196
197
198
We support visualizing models with 3rd-party visualization engines (like `Netron <https://netron.app/>`__). This can be used by clicking ``Visualization`` in detail panel for each trial. Note that current visualization is based on `onnx <https://onnx.ai/>`__ , thus visualization is not feasible if the model cannot be exported into onnx.

Built-in evaluators (e.g., Classification) will automatically export the model into a file. For your own evaluator, you need to save your file into ``$NNI_OUTPUT_DIR/model.onnx`` to make this work. For instance,

.. code-block:: python

  def evaluate_model(model_cls):
    model = model_cls()
    # dump the model into an onnx
    if 'NNI_OUTPUT_DIR' in os.environ:
      torch.onnx.export(model, (dummy_input, ),
                        Path(os.environ['NNI_OUTPUT_DIR']) / 'model.onnx')
    # the rest are training and evaluation
Yuge Zhang's avatar
Yuge Zhang committed
199

200
201
202
203
204
205
206
207
208
Export Top Models
-----------------

Users can export top models after the exploration is done using ``export_top_models``.

.. code-block:: python

  for model_code in exp.export_top_models(formatter='dict'):
    print(model_code)
QuanluZhang's avatar
QuanluZhang committed
209
210
211
212
213
214
215

The output is `json` object which records the mutation actions of the top model. If users want to output source code of the top model, they can use graph-based execution engine for the experiment, by simply adding the following two lines.

.. code-block:: python

  exp_config.execution_engine = 'base'
  export_formatter = 'code'