test_models.py 8.6 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
import random


12
def set_rng_seed(seed):
eellison's avatar
eellison committed
13
14
15
    torch.manual_seed(seed)
    random.seed(seed)
    np.random.seed(seed)
16
17


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


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
# models that are in torch hub, as well as r3d_18. we tried testing all models
# but the test was too slow. not included are detection models, because
# they are not yet supported in JIT.
script_test_models = [
42
    "deeplabv3_resnet101",
43
44
    "mobilenet_v2",
    "resnext50_32x4d",
45
    "fcn_resnet101",
46
47
48
49
50
51
52
53
    "googlenet",
    "densenet121",
    "resnet18",
    "alexnet",
    "shufflenet_v2_x1_0",
    "squeezenet1_0",
    "vgg11",
    "inception_v3",
54
    'r3d_18',
55
]
56
57


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

72
73
74
    def _test_classification_model(self, name, input_shape):
        # passing num_class equal to a number other than 1000 helps in making the test
        # more enforcing in nature
75
        set_rng_seed(0)
76
77
78
79
80
        model = models.__dict__[name](num_classes=50)
        self.check_script(model, name)
        model.eval()
        x = torch.rand(input_shape)
        out = model(x)
81
        self.assertExpected(out, rtol=1e-2, atol=0.)
82
        self.assertEqual(out.shape[-1], 50)
83

84
85
86
87
    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)
88
        self.check_script(model, name)
89
90
91
92
93
94
        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))

95
    def _test_detection_model(self, name):
eellison's avatar
eellison committed
96
        set_rng_seed(0)
97
        model = models.detection.__dict__[name](num_classes=50, pretrained_backbone=False)
98
        self.check_script(model, name)
99
100
101
        model.eval()
        input_shape = (3, 300, 300)
        x = torch.rand(input_shape)
102
103
104
        model_input = [x]
        out = model(model_input)
        self.assertIs(model_input[0], x)
105
        self.assertEqual(len(out), 1)
eellison's avatar
eellison committed
106

107
108
109
110
111
112
113
114
115
116
        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]

eellison's avatar
eellison committed
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
        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))

133
134
135
136
        self.assertTrue("boxes" in out[0])
        self.assertTrue("scores" in out[0])
        self.assertTrue("labels" in out[0])

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
    def _test_video_model(self, name):
        # the default input shape is
        # bs * num_channels * clip_len * h *w
        input_shape = (1, 3, 4, 112, 112)
        # test both basicblock and Bottleneck
        model = models.video.__dict__[name](num_classes=50)
        self.check_script(model, name)
        x = torch.rand(input_shape)
        out = model(x)
        self.assertEqual(out.shape[-1], 50)

    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

    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)

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

    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)

195
196
197
198
199
200
201
202
203
204
205
206
207
208
    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])

209

210
211
212
213
214
215
216
217
218
219
for model_name in get_available_classification_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):
        input_shape = (1, 3, 224, 224)
        if model_name in ['inception_v3']:
            input_shape = (1, 3, 299, 299)
        self._test_classification_model(model_name, input_shape)

    setattr(ModelTester, "test_" + model_name, do_test)
220
221
222
223
224
225
226


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

228
    setattr(ModelTester, "test_" + model_name, do_test)
229
230


231
232
233
234
235
236
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)

237
    setattr(ModelTester, "test_" + model_name, do_test)
238

239

240
241
242
243
244
for model_name in get_available_video_models():

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

245
    setattr(ModelTester, "test_" + model_name, do_test)
246

247
248
if __name__ == '__main__':
    unittest.main()