search.py 4.88 KB
Newer Older
1
2
import random

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

16

QuanluZhang's avatar
QuanluZhang committed
17
18
19
20
21
22
23
24
25
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))

26

27
@model_wrapper
28
class Net(nn.Module):
QuanluZhang's avatar
QuanluZhang committed
29
    def __init__(self):
30
        super().__init__()
QuanluZhang's avatar
QuanluZhang committed
31
        self.conv1 = nn.Conv2d(1, 32, 3, 1)
32
        # LayerChoice is used to select a layer between Conv2d and DwConv.
QuanluZhang's avatar
QuanluZhang committed
33
34
35
36
        self.conv2 = nn.LayerChoice([
            nn.Conv2d(32, 64, 3, 1),
            DepthwiseSeparableConv(32, 64)
        ])
37
38
39
        # 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
40
41
42
        self.dropout1 = nn.Dropout(nn.ValueChoice([0.25, 0.5, 0.75]))
        self.dropout2 = nn.Dropout(0.5)
        feature = nn.ValueChoice([64, 128, 256])
43
        # Same value choice can be used multiple times
QuanluZhang's avatar
QuanluZhang committed
44
45
        self.fc1 = nn.Linear(9216, feature)
        self.fc2 = nn.Linear(feature, 10)
46
47
48

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

    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')

    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)

114
115

if __name__ == '__main__':
QuanluZhang's avatar
QuanluZhang committed
116
    base_model = Net()
117

118
119
    search_strategy = strategy.Random()
    model_evaluator = FunctionalEvaluator(evaluate_model)
120

121
    exp = RetiariiExperiment(base_model, model_evaluator, [], search_strategy)
122
123
124
125

    exp_config = RetiariiExeConfig('local')
    exp_config.experiment_name = 'mnist_search'
    exp_config.trial_concurrency = 2
QuanluZhang's avatar
QuanluZhang committed
126
    exp_config.max_trial_number = 20
127
    exp_config.training_service.use_active_gpu = False
128
    export_formatter = 'dict'
129

130
131
132
    # uncomment this for graph-based execution engine
    # exp_config.execution_engine = 'base'
    # export_formatter = 'code'
133
134

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