test_models.py 8.32 KB
Newer Older
eellison's avatar
eellison committed
1
from common_utils import TestCase, map_nested_tensor_object
2
3
from collections import OrderedDict
from itertools import product
4
import torch
eellison's avatar
eellison committed
5
import numpy as np
6
7
from torchvision import models
import unittest
eellison's avatar
eellison committed
8
import traceback
eellison's avatar
eellison committed
9
10
11
12
13
14
15
import random


def set_rng_seed(seed):
    torch.manual_seed(seed)
    random.seed(seed)
    np.random.seed(seed)
16
17


18
def get_available_classification_models():
19
    # TODO add a registration mechanism to torchvision.models
20
21
22
23
24
25
    return [k for k, v in models.__dict__.items() if callable(v) and k[0].lower() == k[0] and k[0] != "_"]


def get_available_segmentation_models():
    # TODO add a registration mechanism to torchvision.models
    return [k for k, v in models.segmentation.__dict__.items() if callable(v) and k[0].lower() == k[0] and k[0] != "_"]
26
27


28
29
30
31
32
def get_available_detection_models():
    # TODO add a registration mechanism to torchvision.models
    return [k for k, v in models.detection.__dict__.items() if callable(v) and k[0].lower() == k[0] and k[0] != "_"]


33
34
35
36
37
def get_available_video_models():
    # TODO add a registration mechanism to torchvision.models
    return [k for k, v in models.video.__dict__.items() if callable(v) and k[0].lower() == k[0] and k[0] != "_"]


38
39
40
41
42
43
44
45
46
47
48
49
50
51
torchub_models = [
    "deeplabv3_resnet101",
    "mobilenet_v2",
    "resnext50_32x4d",
    "fcn_resnet101",
    "googlenet",
    "densenet121",
    "resnet18",
    "alexnet",
    "shufflenet_v2_x1_0",
    "squeezenet1_0",
    "vgg11",
    "inception_v3",
]
52
53


eellison's avatar
eellison committed
54
class ModelTester(TestCase):
55
56
57
58
    def check_script(self, model, name):
        if name not in torchub_models:
            return
        scriptable = True
eellison's avatar
eellison committed
59
        msg = ""
60
61
        try:
            torch.jit.script(model)
eellison's avatar
eellison committed
62
63
        except Exception as e:
            tb = traceback.format_exc()
64
            scriptable = False
eellison's avatar
eellison committed
65
            msg = str(e) + str(tb)
66
        self.assertTrue(scriptable, msg)
67

68
    def _test_classification_model(self, name, input_shape):
69
70
        # passing num_class equal to a number other than 1000 helps in making the test
        # more enforcing in nature
71
        model = models.__dict__[name](num_classes=50)
72
        self.check_script(model, name)
73
74
75
        model.eval()
        x = torch.rand(input_shape)
        out = model(x)
76
        self.assertEqual(out.shape[-1], 50)
77

78
79
80
81
    def _test_segmentation_model(self, name):
        # passing num_class equal to a number other than 1000 helps in making the test
        # more enforcing in nature
        model = models.segmentation.__dict__[name](num_classes=50, pretrained_backbone=False)
82
        self.check_script(model, name)
83
84
85
86
87
88
        model.eval()
        input_shape = (1, 3, 300, 300)
        x = torch.rand(input_shape)
        out = model(x)
        self.assertEqual(tuple(out["out"].shape), (1, 50, 300, 300))

89
    def _test_detection_model(self, name):
eellison's avatar
eellison committed
90
        set_rng_seed(0)
91
        model = models.detection.__dict__[name](num_classes=50, pretrained_backbone=False)
92
        self.check_script(model, name)
93
94
95
        model.eval()
        input_shape = (3, 300, 300)
        x = torch.rand(input_shape)
96
97
98
        model_input = [x]
        out = model(model_input)
        self.assertIs(model_input[0], x)
99
        self.assertEqual(len(out), 1)
eellison's avatar
eellison committed
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

        def subsample_tensor(tensor):
            num_elems = tensor.numel()
            num_samples = 20
            if num_elems <= num_samples:
                return tensor

            flat_tensor = tensor.flatten()
            ith_index = num_elems // num_samples
            return flat_tensor[ith_index - 1::ith_index]

        def compute_mean_std(tensor):
            # can't compute mean of integral tensor
            tensor = tensor.to(torch.double)
            mean = torch.mean(tensor)
            std = torch.std(tensor)
            return {"mean": mean, "std": std}

        # maskrcnn_resnet_50_fpn numerically unstable across platforms, so for now
        # compare results with mean and std
        if name == "maskrcnn_resnet50_fpn":
            test_value = map_nested_tensor_object(out, tensor_map_fn=compute_mean_std)
            # mean values are small, use large rtol
            self.assertExpected(test_value, rtol=.01, atol=.01)
        else:
            self.assertExpected(map_nested_tensor_object(out, tensor_map_fn=subsample_tensor))

127
128
129
130
        self.assertTrue("boxes" in out[0])
        self.assertTrue("scores" in out[0])
        self.assertTrue("labels" in out[0])

131
132
133
    def _test_video_model(self, name):
        # the default input shape is
        # bs * num_channels * clip_len * h *w
134
        input_shape = (1, 3, 4, 112, 112)
135
136
        # test both basicblock and Bottleneck
        model = models.video.__dict__[name](num_classes=50)
137
        self.check_script(model, name)
138
139
140
141
        x = torch.rand(input_shape)
        out = model(x)
        self.assertEqual(out.shape[-1], 50)

142
143
144
145
146
147
148
149
150
    def _make_sliced_model(self, model, stop_layer):
        layers = OrderedDict()
        for name, layer in model.named_children():
            layers[name] = layer
            if name == stop_layer:
                break
        new_model = torch.nn.Sequential(layers)
        return new_model

151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
    def test_memory_efficient_densenet(self):
        input_shape = (1, 3, 300, 300)
        x = torch.rand(input_shape)

        for name in ['densenet121', 'densenet169', 'densenet201', 'densenet161']:
            model1 = models.__dict__[name](num_classes=50, memory_efficient=True)
            params = model1.state_dict()
            model1.eval()
            out1 = model1(x)
            out1.sum().backward()

            model2 = models.__dict__[name](num_classes=50, memory_efficient=False)
            model2.load_state_dict(params)
            model2.eval()
            out2 = model2(x)

            max_diff = (out1 - out2).abs().max()

            self.assertTrue(max_diff < 1e-5)

171
172
173
174
175
176
177
178
179
180
181
    def test_resnet_dilation(self):
        # TODO improve tests to also check that each layer has the right dimensionality
        for i in product([False, True], [False, True], [False, True]):
            model = models.__dict__["resnet50"](replace_stride_with_dilation=i)
            model = self._make_sliced_model(model, stop_layer="layer4")
            model.eval()
            x = torch.rand(1, 3, 224, 224)
            out = model(x)
            f = 2 ** sum(i)
            self.assertEqual(out.shape, (1, 2048, 7 * f, 7 * f))

182
183
184
185
186
187
188
    def test_mobilenetv2_residual_setting(self):
        model = models.__dict__["mobilenet_v2"](inverted_residual_setting=[[1, 16, 1, 1], [6, 24, 2, 2]])
        model.eval()
        x = torch.rand(1, 3, 224, 224)
        out = model(x)
        self.assertEqual(out.shape[-1], 1000)

189
190
191
192
193
194
195
196
197
198
199
200
201
202
    def test_fasterrcnn_double(self):
        model = models.detection.fasterrcnn_resnet50_fpn(num_classes=50, pretrained_backbone=False)
        model.double()
        model.eval()
        input_shape = (3, 300, 300)
        x = torch.rand(input_shape, dtype=torch.float64)
        model_input = [x]
        out = model(model_input)
        self.assertIs(model_input[0], x)
        self.assertEqual(len(out), 1)
        self.assertTrue("boxes" in out[0])
        self.assertTrue("scores" in out[0])
        self.assertTrue("labels" in out[0])

203

204
for model_name in get_available_classification_models():
205
206
207
208
209
210
    # for-loop bodies don't define scopes, so we have to save the variables
    # we want to close over in some way
    def do_test(self, model_name=model_name):
        input_shape = (1, 3, 224, 224)
        if model_name in ['inception_v3']:
            input_shape = (1, 3, 299, 299)
211
212
        self._test_classification_model(model_name, input_shape)

eellison's avatar
eellison committed
213
    setattr(ModelTester, "test_" + model_name, do_test)
214
215
216
217
218
219
220


for model_name in get_available_segmentation_models():
    # for-loop bodies don't define scopes, so we have to save the variables
    # we want to close over in some way
    def do_test(self, model_name=model_name):
        self._test_segmentation_model(model_name)
221

eellison's avatar
eellison committed
222
    setattr(ModelTester, "test_" + model_name, do_test)
223
224


225
226
227
228
229
230
for model_name in get_available_detection_models():
    # for-loop bodies don't define scopes, so we have to save the variables
    # we want to close over in some way
    def do_test(self, model_name=model_name):
        self._test_detection_model(model_name)

eellison's avatar
eellison committed
231
    setattr(ModelTester, "test_" + model_name, do_test)
232

233

234
235
236
237
238
for model_name in get_available_video_models():

    def do_test(self, model_name=model_name):
        self._test_video_model(model_name)

eellison's avatar
eellison committed
239
    setattr(ModelTester, "test_" + model_name, do_test)
240

241
242
if __name__ == '__main__':
    unittest.main()