test_models.py 8.52 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
75
76
77
78
79
80
    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
        model = models.__dict__[name](num_classes=50)
        self.check_script(model, name)
        model.eval()
        x = torch.rand(input_shape)
        out = model(x)
        self.assertEqual(out.shape[-1], 50)
81

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

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

105
106
107
108
109
110
111
112
113
114
        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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
        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))

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

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

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

207

208
209
210
211
212
213
214
215
216
217
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)
218
219
220
221
222
223
224


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

226
    setattr(ModelTester, "test_" + model_name, do_test)
227
228


229
230
231
232
233
234
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)

235
    setattr(ModelTester, "test_" + model_name, do_test)
236

237

238
239
240
241
242
for model_name in get_available_video_models():

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

243
    setattr(ModelTester, "test_" + model_name, do_test)
244

245
246
if __name__ == '__main__':
    unittest.main()