search.py 5.15 KB
Newer Older
1
import os
2
import random
3
from pathlib import Path
4

5
import nni
QuanluZhang's avatar
QuanluZhang committed
6
import torch
7
8
import torch.nn.functional as F
# remember to import nni.retiarii.nn.pytorch as nn, instead of torch.nn as nn
9
import nni.retiarii.nn.pytorch as nn
10
import nni.retiarii.strategy as strategy
11
12
from nni.retiarii import model_wrapper
from nni.retiarii.evaluator import FunctionalEvaluator
13
from nni.retiarii.experiment.pytorch import RetiariiExeConfig, RetiariiExperiment, debug_mutated_model
14
15
16
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision.datasets import MNIST
17

18

QuanluZhang's avatar
QuanluZhang committed
19
20
21
22
23
24
25
26
27
class DepthwiseSeparableConv(nn.Module):
    def __init__(self, in_ch, out_ch):
        super().__init__()
        self.depthwise = nn.Conv2d(in_ch, in_ch, kernel_size=3, groups=in_ch)
        self.pointwise = nn.Conv2d(in_ch, out_ch, kernel_size=1)

    def forward(self, x):
        return self.pointwise(self.depthwise(x))

28

29
@model_wrapper
30
class Net(nn.Module):
QuanluZhang's avatar
QuanluZhang committed
31
    def __init__(self):
32
        super().__init__()
QuanluZhang's avatar
QuanluZhang committed
33
        self.conv1 = nn.Conv2d(1, 32, 3, 1)
34
        # LayerChoice is used to select a layer between Conv2d and DwConv.
QuanluZhang's avatar
QuanluZhang committed
35
36
37
38
        self.conv2 = nn.LayerChoice([
            nn.Conv2d(32, 64, 3, 1),
            DepthwiseSeparableConv(32, 64)
        ])
39
40
41
        # ValueChoice is used to select a dropout rate.
        # ValueChoice can be used as parameter of modules wrapped in `nni.retiarii.nn.pytorch`
        # or customized modules wrapped with `@basic_unit`.
QuanluZhang's avatar
QuanluZhang committed
42
43
44
        self.dropout1 = nn.Dropout(nn.ValueChoice([0.25, 0.5, 0.75]))
        self.dropout2 = nn.Dropout(0.5)
        feature = nn.ValueChoice([64, 128, 256])
45
        # Same value choice can be used multiple times
QuanluZhang's avatar
QuanluZhang committed
46
47
        self.fc1 = nn.Linear(9216, feature)
        self.fc2 = nn.Linear(feature, 10)
48
49
50

    def forward(self, x):
        x = F.relu(self.conv1(x))
QuanluZhang's avatar
QuanluZhang committed
51
52
53
        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))))
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
        return x


def train_epoch(model, device, train_loader, optimizer, epoch):
    loss_fn = torch.nn.CrossEntropyLoss()
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)
        optimizer.zero_grad()
        output = model(data)
        loss = loss_fn(output, target)
        loss.backward()
        optimizer.step()
        if batch_idx % 10 == 0:
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, batch_idx * len(data), len(train_loader.dataset),
                100. * batch_idx / len(train_loader), loss.item()))


def test_epoch(model, device, test_loader):
    model.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            data, target = data.to(device), target.to(device)
            output = model(data)
            pred = output.argmax(dim=1, keepdim=True)
            correct += pred.eq(target.view_as(pred)).sum().item()

    test_loss /= len(test_loader.dataset)

    accuracy = 100. * correct / len(test_loader.dataset)

    print('\nTest set: Accuracy: {}/{} ({:.0f}%)\n'.format(
        correct, len(test_loader.dataset), accuracy))

    return accuracy


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

98
99
100
101
102
    # export model for visualization
    if 'NNI_OUTPUT_DIR' in os.environ:
        torch.onnx.export(model, (torch.randn(1, 1, 28, 28), ),
                          Path(os.environ['NNI_OUTPUT_DIR']) / 'model.onnx')

103
104
105
106
107
108
109
    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)

    device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')

110
    model.to(device)
111
112
113
114
115
116
117
118
119
120
121
    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)

    # report final test result
    nni.report_final_result(accuracy)

122
123

if __name__ == '__main__':
QuanluZhang's avatar
QuanluZhang committed
124
    base_model = Net()
125

126
127
    search_strategy = strategy.Random()
    model_evaluator = FunctionalEvaluator(evaluate_model)
128

129
    exp = RetiariiExperiment(base_model, model_evaluator, [], search_strategy)
130
131
132
133

    exp_config = RetiariiExeConfig('local')
    exp_config.experiment_name = 'mnist_search'
    exp_config.trial_concurrency = 2
QuanluZhang's avatar
QuanluZhang committed
134
    exp_config.max_trial_number = 20
135
    exp_config.training_service.use_active_gpu = False
136
    export_formatter = 'dict'
137

138
139
140
    # uncomment this for graph-based execution engine
    # exp_config.execution_engine = 'base'
    # export_formatter = 'code'
141
142

    exp.run(exp_config, 8081 + random.randint(0, 100))
143
    print('Final model:')
144
    for model_code in exp.export_top_models(formatter=export_formatter):
145
        print(model_code)