From 46a07fb1411f2e9c716b02391bc095f50ec00331 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 12 Jun 2017 21:44:39 +0900 Subject: [PATCH 001/139] add wip vgg --- .../links/model/classification/__init__.py | 0 chainercv/links/model/classification/vgg.py | 122 ++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 chainercv/links/model/classification/__init__.py create mode 100644 chainercv/links/model/classification/vgg.py diff --git a/chainercv/links/model/classification/__init__.py b/chainercv/links/model/classification/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/chainercv/links/model/classification/vgg.py b/chainercv/links/model/classification/vgg.py new file mode 100644 index 0000000000..bca6fd2ddc --- /dev/null +++ b/chainercv/links/model/classification/vgg.py @@ -0,0 +1,122 @@ +import collections + +import chainer +import chainer.functions as F +import chainer.links as L + +from chainer.initializers import constant +from chainer.initializers import normal + + +class VGG16Layers(chainer.Chain): + + def __init__(self, pretrained_model='auto', feature='prob', + initialW=None, initial_bias=None + ): + if pretrained_model: + # As a sampling process is time-consuming, + # we employ a zero initializer for faster computation. + if initialW is None: + initialW = constant.Zero() + if initial_bias is None: + initial_bias = constant.Zero() + else: + # employ default initializers used in the original paper + if initialW is None: + initialW = normal.Normal(0.01) + if initial_bias is None: + initial_bias = constant.Zero() + + kwargs = {'initialW': initialW, 'initial_bias': initial_bias} + + super(VGG16Layers, self).__init__() + + with self.init_scope(): + self.conv1_1 = L.Convolution2D(3, 64, 3, 1, 1, **kwargs) + self.conv1_2 = L.Convolution2D(64, 64, 3, 1, 1, **kwargs) + self.conv2_1 = L.Convolution2D(64, 128, 3, 1, 1, **kwargs) + self.conv2_2 = L.Convolution2D(128, 128, 3, 1, 1, **kwargs) + self.conv3_1 = L.Convolution2D(128, 256, 3, 1, 1, **kwargs) + self.conv3_2 = L.Convolution2D(256, 256, 3, 1, 1, **kwargs) + self.conv3_3 = L.Convolution2D(256, 256, 3, 1, 1, **kwargs) + self.conv4_1 = L.Convolution2D(256, 512, 3, 1, 1, **kwargs) + self.conv4_2 = L.Convolution2D(512, 512, 3, 1, 1, **kwargs) + self.conv4_3 = L.Convolution2D(512, 512, 3, 1, 1, **kwargs) + self.conv5_1 = L.Convolution2D(512, 512, 3, 1, 1, **kwargs) + self.conv5_2 = L.Convolution2D(512, 512, 3, 1, 1, **kwargs) + self.conv5_3 = L.Convolution2D(512, 512, 3, 1, 1, **kwargs) + self.fc6 = L.Linear(512 * 7 * 7, 4096, **kwargs) + self.fc7 = L.Linear(4096, 4096, **kwargs) + self.fc8 = L.Linear(4096, 1000, **kwargs) + + self.functions = collections.OrderedDict([ + ('conv1_1', [self.conv1_1, F.relu]), + ('conv1_2', [self.conv1_2, F.relu]), + ('pool1', [_max_pooling_2d]), + ('conv2_1', [self.conv2_1, F.relu]), + ('conv2_2', [self.conv2_2, F.relu]), + ('pool2', [_max_pooling_2d]), + ('conv3_1', [self.conv3_1, F.relu]), + ('conv3_2', [self.conv3_2, F.relu]), + ('conv3_3', [self.conv3_3, F.relu]), + ('pool3', [_max_pooling_2d]), + ('conv4_1', [self.conv4_1, F.relu]), + ('conv4_2', [self.conv4_2, F.relu]), + ('conv4_3', [self.conv4_3, F.relu]), + ('pool4', [_max_pooling_2d]), + ('conv5_1', [self.conv5_1, F.relu]), + ('conv5_2', [self.conv5_2, F.relu]), + ('conv5_3', [self.conv5_3, F.relu]), + ('pool5', [_max_pooling_2d]), + ('fc6', [self.fc6, F.relu, F.dropout]), + ('fc7', [self.fc7, F.relu, F.dropout]), + ('fc8', [self.fc8]), + ('prob', [F.softmax]), + ]) + if feature not in self.functions: + raise ValueError('`feature` shuold be one of the keys of ' + 'VGG16Layers.functions.') + + # Links can be safely removed because parameters in these links + # are guaranteed to not be in a computational graph. + names = [child.name for child in self.children()] + delete_layers = False + for func in self.functions.keys(): + if delete_layers: + if func in names: + delattr(self, func) + if func == feature: + delete_layers = True + + def __call__(self, x): + h = x + for key, funcs in self.functions.items(): + for func in funcs: + h = func(h) + return h + + def predict(self, imgs, oversample=True): + """Compute class probabilities of given images. + + Args: + imgs (iterable of numpy.ndarray): Array-images. + All images are in CHW and RGB format + and the range of their value is :math:`[0, 255]`. + oversample (bool): If :obj:`True`, it averages results across + center, corners, and mirrors. Otherwise, it uses only the + center. + + Returns: + numpy.ndarray: + A batch of arrays containing class-probabilities. + + """ + raise NotImplementedError + + +def _max_pooling_2d(x): + return F.max_pooling_2d(x, ksize=2) + + +if __name__ == '__main__': + model = VGG16Layers(feature='conv4_1') From 8b350013236abc19515c6a5044432a13936f6288 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 14 Jun 2017 15:54:31 +0900 Subject: [PATCH 002/139] update on classification models --- chainercv/links/__init__.py | 1 + .../model/{classification => vgg}/__init__.py | 0 .../model/{classification => vgg}/vgg.py | 0 examples/classification/README.md | 27 ++++++++++++ examples/classification/eval_imagenet.py | 41 +++++++++++++++++++ 5 files changed, 69 insertions(+) rename chainercv/links/model/{classification => vgg}/__init__.py (100%) rename chainercv/links/model/{classification => vgg}/vgg.py (100%) create mode 100644 examples/classification/README.md create mode 100644 examples/classification/eval_imagenet.py diff --git a/chainercv/links/__init__.py b/chainercv/links/__init__.py index 32df7337f9..0df1a67e8a 100644 --- a/chainercv/links/__init__.py +++ b/chainercv/links/__init__.py @@ -4,3 +4,4 @@ from chainercv.links.model.segnet.segnet_basic import SegNetBasic # NOQA from chainercv.links.model.ssd import SSD300 # NOQA from chainercv.links.model.ssd import SSD512 # NOQA +from chainercv.links.model.vgg.vgg import VGG16Layers # NOQA diff --git a/chainercv/links/model/classification/__init__.py b/chainercv/links/model/vgg/__init__.py similarity index 100% rename from chainercv/links/model/classification/__init__.py rename to chainercv/links/model/vgg/__init__.py diff --git a/chainercv/links/model/classification/vgg.py b/chainercv/links/model/vgg/vgg.py similarity index 100% rename from chainercv/links/model/classification/vgg.py rename to chainercv/links/model/vgg/vgg.py diff --git a/examples/classification/README.md b/examples/classification/README.md new file mode 100644 index 0000000000..7772d89eb1 --- /dev/null +++ b/examples/classification/README.md @@ -0,0 +1,27 @@ +# Classification + + + +## Download the ImageNet dataset + +This instructions are copied from ImageNet training for Torch. + +The ImageNet Large Scale Visual Recognition Challenge (ILSVRC) dataset has 1000 categories and 1.2 million images. The images do not need to be preprocessed or packaged in any database, but the validation images need to be moved into appropriate subfolders. + +1. Download the images from http://image-net.org/download-images + +2. Extract the training data: + ```bash + mkdir train && mv ILSVRC2012_img_train.tar train/ && cd train + tar -xvf ILSVRC2012_img_train.tar && rm -f ILSVRC2012_img_train.tar + find . -name "*.tar" | while read NAME ; do mkdir -p "${NAME%.tar}"; tar -xvf "${NAME}" -C "${NAME%.tar}"; rm -f "${NAME}"; done + cd .. + ``` + +3. Extract the validation data and move images to subfolders: + ```bash + mkdir val && mv ILSVRC2012_img_val.tar val/ && cd val && tar -xvf ILSVRC2012_img_val.tar + wget -qO- https://raw.githubusercontent.com/soumith/imagenetloader.torch/master/valprep.sh | bash + ``` + + diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py new file mode 100644 index 0000000000..8548ac1d38 --- /dev/null +++ b/examples/classification/eval_imagenet.py @@ -0,0 +1,41 @@ +import argparse +import random + +import numpy as np + +import chainer +import chainer.links as L +from chainer import iterators +from chainer import training +from chainer.training import extensions + +from chainercv.datasets import ImageFolderDataset +from chainercv.links import VGG16Layers + + +def main(): + parser = argparse.ArgumentParser( + description='Learning convnet from ILSVRC2012 dataset') + parser.add_argument('val', help='Path to root of the validation dataset') + parser.add_argument('--gpu', type=int, default=-1) + parser.add_argument('--batchsize', type=int, default=32) + args = parser.parse_args() + + dataset = ImageFolderDataset(args.val) + iterator = iterators.MultiprocessIterator( + dataset, args.batchsize, repeat=False, shuffle=False, + n_processes=4) + + model = L.Classifier(VGG16Layers()) + + if args.gpu >= 0: + chainer.cuda.get_device(args.gpu).use() + model.to_gpu() + + + result = extensions.Evaluator(iterator, model) + print result + + +if __name__ == '__main__': + main() From 1cd87b67822c94c49d30ce1eabce792b3db7c272 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 14 Jun 2017 16:14:36 +0900 Subject: [PATCH 003/139] fix the issue with copying vision chain --- chainercv/links/model/vgg/vgg.py | 34 ++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/chainercv/links/model/vgg/vgg.py b/chainercv/links/model/vgg/vgg.py index bca6fd2ddc..f4271a12d7 100644 --- a/chainercv/links/model/vgg/vgg.py +++ b/chainercv/links/model/vgg/vgg.py @@ -48,8 +48,19 @@ def __init__(self, pretrained_model='auto', feature='prob', self.fc6 = L.Linear(512 * 7 * 7, 4096, **kwargs) self.fc7 = L.Linear(4096, 4096, **kwargs) self.fc8 = L.Linear(4096, 1000, **kwargs) + self.feature = feature - self.functions = collections.OrderedDict([ + # Links can be safely removed because parameters in these links + # are guaranteed to not be in a computational graph. + names = [child.name for child in self.children()] + functions = self.functions + for name in names: + if name not in functions: + delattr(self, name) + + @property + def functions(self): + default_funcs = collections.OrderedDict([ ('conv1_1', [self.conv1_1, F.relu]), ('conv1_2', [self.conv1_2, F.relu]), ('pool1', [_max_pooling_2d]), @@ -73,20 +84,17 @@ def __init__(self, pretrained_model='auto', feature='prob', ('fc8', [self.fc8]), ('prob', [F.softmax]), ]) - if feature not in self.functions: + if self.feature not in default_funcs: raise ValueError('`feature` shuold be one of the keys of ' 'VGG16Layers.functions.') - - # Links can be safely removed because parameters in these links - # are guaranteed to not be in a computational graph. - names = [child.name for child in self.children()] - delete_layers = False - for func in self.functions.keys(): - if delete_layers: - if func in names: - delattr(self, func) - if func == feature: - delete_layers = True + pop_funcs = False + for name in default_funcs.keys(): + if pop_funcs: + default_funcs.pop(name) + + if name == self.feature: + pop_funcs = True + return default_funcs def __call__(self, x): h = x From af88d892d7758b338d48add9e4d66cfcebaf09b7 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 14 Jun 2017 17:02:19 +0900 Subject: [PATCH 004/139] add tests and pass tests for feature option --- chainercv/links/__init__.py | 2 +- .../links/model/vgg/{vgg.py => vgg16.py} | 4 +- .../model_tests/vgg_tests/test_vgg16.py | 118 ++++++++++++++++++ 3 files changed, 122 insertions(+), 2 deletions(-) rename chainercv/links/model/vgg/{vgg.py => vgg16.py} (96%) create mode 100644 tests/links_tests/model_tests/vgg_tests/test_vgg16.py diff --git a/chainercv/links/__init__.py b/chainercv/links/__init__.py index 0df1a67e8a..e130a489d9 100644 --- a/chainercv/links/__init__.py +++ b/chainercv/links/__init__.py @@ -4,4 +4,4 @@ from chainercv.links.model.segnet.segnet_basic import SegNetBasic # NOQA from chainercv.links.model.ssd import SSD300 # NOQA from chainercv.links.model.ssd import SSD512 # NOQA -from chainercv.links.model.vgg.vgg import VGG16Layers # NOQA +from chainercv.links.model.vgg.vgg16 import VGG16Layers # NOQA diff --git a/chainercv/links/model/vgg/vgg.py b/chainercv/links/model/vgg/vgg16.py similarity index 96% rename from chainercv/links/model/vgg/vgg.py rename to chainercv/links/model/vgg/vgg16.py index f4271a12d7..552acb15a2 100644 --- a/chainercv/links/model/vgg/vgg.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -57,6 +57,8 @@ def __init__(self, pretrained_model='auto', feature='prob', for name in names: if name not in functions: delattr(self, name) + # Since self.functions access self.name, it needs a value. + setattr(self, name, None) @property def functions(self): @@ -98,7 +100,7 @@ def functions(self): def __call__(self, x): h = x - for key, funcs in self.functions.items(): + for funcs in self.functions.values(): for func in funcs: h = func(h) return h diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py new file mode 100644 index 0000000000..87c40ade27 --- /dev/null +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -0,0 +1,118 @@ +import unittest + +import numpy as np + +from chainer import cuda +from chainer import testing +from chainer.initializers import Zero +from chainer.testing import attr +from chainer.variable import Variable + +from chainercv.links import VGG16Layers + + +_zero_init = Zero + + +@attr.slow +class TestVGG16LayersCall(unittest.TestCase): + + def setUp(self): + self.link = VGG16Layers( + pretrained_model=None) + + def check_call(self): + xp = self.link.xp + + x1 = Variable(xp.asarray(np.random.uniform( + -1, 1, (1, 3, 224, 224)).astype(np.float32))) + y1 = self.link(x1) + self.assertEqual(y1.shape, (1, 1000)) + + def test_call_cpu(self): + self.check_call() + + @attr.gpu + def test_call_gpu(self): + self.link.to_gpu() + self.check_call() + + +@testing.parameterize( + {'feature': 'prob', 'shape': (1, 1000)}, +) +class TestVGG16LayersPredict(unittest.TestCase): + + def setUp(self): + self.link = VGG16Layers(pretrained_model=None, feature=self.feature) + + def check_predict(self): + x1 = np.random.uniform(0, 255, (320, 240, 3)).astype(np.uint8) + x2 = np.random.uniform(0, 255, (320, 240)).astype(np.uint8) + result = self.link.predict([x1, x2], oversample=False) + y = cuda.to_cpu(result.data) + self.assertEqual(y.shape, (2, 1000)) + self.assertEqual(y.dtype, np.float32) + result = self.link.predict([x1, x2], oversample=True) + + def test_predict_cpu(self): + self.check_predict() + + @attr.gpu + def test_predict_gpu(self): + self.link.to_gpu() + self.check_predict() + + +class TestVGG16LayersCopy(unittest.TestCase): + + def setUp(self): + self.link = VGG16Layers(pretrained_model=None, + initialW=Zero(), initial_bias=Zero()) + + def check_copy(self): + copied = self.link.copy() + self.assertIs(copied.conv1_1, copied.functions['conv1_1'][0]) + + def test_copy_cpu(self): + self.check_copy() + + @attr.gpu + def test_copy_gpu(self): + self.link.to_gpu() + self.check_copy() + + +class TestVGG16LayersFeatureOption(unittest.TestCase): + + def setUp(self): + self.link = VGG16Layers(pretrained_model=None, feature='pool4', + initialW=Zero(), initial_bias=Zero()) + + def check_feature_option(self): + self.assertTrue(self.link.conv5_1 is None) + self.assertTrue(self.link.conv5_2 is None) + self.assertTrue(self.link.conv5_3 is None) + self.assertTrue(self.link.fc6 is None) + self.assertTrue(self.link.fc7 is None) + self.assertTrue(self.link.fc8 is None) + + self.assertFalse('conv5_1' in self.link.functions) + self.assertFalse('conv5_2' in self.link.functions) + self.assertFalse('conv5_3' in self.link.functions) + self.assertFalse('pool5' in self.link.functions) + self.assertFalse('fc6' in self.link.functions) + self.assertFalse('fc7' in self.link.functions) + self.assertFalse('fc8' in self.link.functions) + self.assertFalse('prob' in self.link.functions) + self.assertFalse('fc8' in self.link.functions) + + def test_feature_option_cpu(self): + self.check_feature_option() + + @attr.gpu + def test_feature_option_gpu(self): + self.link.to_gpu() + self.check_feature_option() + +testing.run_module(__name__, __file__) From 2bbece29b5bcf05fcc08f5deefc1ee8780124017 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 14 Jun 2017 17:19:51 +0900 Subject: [PATCH 005/139] add predict function --- chainercv/links/model/vgg/vgg16.py | 63 ++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 552acb15a2..f383589f9f 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -1,18 +1,30 @@ +from __future__ import division + import collections +import numpy as np + import chainer import chainer.functions as F -import chainer.links as L - +from chainer import cuda from chainer.initializers import constant from chainer.initializers import normal +import chainer.links as L + +from chainercv.transforms import resize +from chainercv.transforms import ten_crop + + +# RGB order +_imagenet_mean = np.array( + [[[123.68], [116.779], [103.939]]], dtype=np.float32) class VGG16Layers(chainer.Chain): def __init__(self, pretrained_model='auto', feature='prob', - initialW=None, initial_bias=None - ): + initialW=None, initial_bias=None, + mean=_imagenet_mean): if pretrained_model: # As a sampling process is time-consuming, # we employ a zero initializer for faster computation. @@ -105,14 +117,30 @@ def __call__(self, x): h = func(h) return h - def predict(self, imgs, oversample=True): + def _prepare(self, img): + """Transform an image to the input for VGG network. + + Args: + img (numpy.ndarray) + + Returns: + numpy.ndarray: The transformed image. + + """ + + img = resize(img, (256, 256)) + img = img - self.mean + + return img + + def predict(self, imgs, do_ten_crop=True): """Compute class probabilities of given images. Args: imgs (iterable of numpy.ndarray): Array-images. All images are in CHW and RGB format and the range of their value is :math:`[0, 255]`. - oversample (bool): If :obj:`True`, it averages results across + do_ten_crop (bool): If :obj:`True`, it averages results across center, corners, and mirrors. Otherwise, it uses only the center. @@ -121,12 +149,25 @@ def predict(self, imgs, oversample=True): A batch of arrays containing class-probabilities. """ - raise NotImplementedError + if do_ten_crop and self.feature not in ['fc6', 'fc7', 'fc8', 'prob']: + raise ValueError + imgs = [self._prepare(img) for img in imgs] + if do_ten_crop: + imgs = [ten_crop(img, (224, 224)) for img in imgs] + imgs = self.xp.asarray(imgs).reshape(-1, 3, 224, 224) -def _max_pooling_2d(x): - return F.max_pooling_2d(x, ksize=2) + with chainer.function.no_backprop_mode(): + imgs = chainer.Variable(imgs) + y = self(imgs).data + + if do_ten_crop: + n = y.data.shape[0] // 10 + y_shape = y.data.shape[1:] + y = y.reshape((n, 10) + y_shape) + y = self.xp.sum(y, axis=1) / 10 + return cuda.to_cpu(y) -if __name__ == '__main__': - model = VGG16Layers(feature='conv4_1') +def _max_pooling_2d(x): + return F.max_pooling_2d(x, ksize=2) From 17f63334903566e87e462f43f3462f0bc34de4b5 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 14 Jun 2017 17:24:45 +0900 Subject: [PATCH 006/139] update test --- .../model_tests/vgg_tests/test_vgg16.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py index 87c40ade27..e509b4d868 100644 --- a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -39,7 +39,8 @@ def test_call_gpu(self): @testing.parameterize( - {'feature': 'prob', 'shape': (1, 1000)}, + {'feature': 'prob', 'shape': (2, 1000)}, + {'feature': 'conv5_3', 'shape': (2, 512, 7, 7)} ) class TestVGG16LayersPredict(unittest.TestCase): @@ -47,13 +48,14 @@ def setUp(self): self.link = VGG16Layers(pretrained_model=None, feature=self.feature) def check_predict(self): - x1 = np.random.uniform(0, 255, (320, 240, 3)).astype(np.uint8) - x2 = np.random.uniform(0, 255, (320, 240)).astype(np.uint8) - result = self.link.predict([x1, x2], oversample=False) - y = cuda.to_cpu(result.data) - self.assertEqual(y.shape, (2, 1000)) - self.assertEqual(y.dtype, np.float32) + x1 = np.random.uniform(0, 255, (3, 320, 240)).astype(np.float32) + x2 = np.random.uniform(0, 255, (3, 320, 240)).astype(np.float32) + out = self.link.predict([x1, x2], oversample=False) + self.assertEqual(out.shape, self.shape) + self.assertEqual(out.dtype, np.float32) result = self.link.predict([x1, x2], oversample=True) + self.assertEqual(out.shape, self.shape) + self.assertEqual(out.dtype, np.float32) def test_predict_cpu(self): self.check_predict() From 2d4c45e7bec94bdfeb417538d3668610e17a9745 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 14 Jun 2017 17:36:35 +0900 Subject: [PATCH 007/139] add caffe trained pretrained weight loader --- chainercv/links/model/vgg/vgg16.py | 3 ++ examples/classification/convert_from_caffe.py | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 examples/classification/convert_from_caffe.py diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index f383589f9f..0b76e308ca 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -62,6 +62,9 @@ def __init__(self, pretrained_model='auto', feature='prob', self.fc8 = L.Linear(4096, 1000, **kwargs) self.feature = feature + if pretrained_model: + chainer.serializers.load_npz(pretrained_model, self) + # Links can be safely removed because parameters in these links # are guaranteed to not be in a computational graph. names = [child.name for child in self.children()] diff --git a/examples/classification/convert_from_caffe.py b/examples/classification/convert_from_caffe.py new file mode 100644 index 0000000000..9f6ddb3926 --- /dev/null +++ b/examples/classification/convert_from_caffe.py @@ -0,0 +1,33 @@ +from chainer.links import VGG16Layers as VGG16Layers_chainer +import chainer + +from chainercv.links import VGG16Layers as VGG16Layers_cv + + +if __name__ == '__main__': + chainer_model = VGG16Layers_chainer() + cv_model = VGG16Layers_cv() + + cv_model.conv1_1.copyparams(chainer_model.conv1_1) + + # The pretrained weights are trained to accept BGR images. + # Convert weights so that they accept RGB images. + cv_model.conv1_1.W.data[:] = cv_model.conv1_1.W.data[:, ::-1] + + cv_model.conv1_2.copyparams(chainer_model.conv1_2) + cv_model.conv2_1.copyparams(chainer_model.conv2_1) + cv_model.conv2_2.copyparams(chainer_model.conv2_2) + cv_model.conv3_1.copyparams(chainer_model.conv3_1) + cv_model.conv3_2.copyparams(chainer_model.conv3_2) + cv_model.conv3_3.copyparams(chainer_model.conv3_3) + cv_model.conv4_1.copyparams(chainer_model.conv4_1) + cv_model.conv4_2.copyparams(chainer_model.conv4_2) + cv_model.conv4_3.copyparams(chainer_model.conv4_3) + cv_model.conv5_1.copyparams(chainer_model.conv5_1) + cv_model.conv5_2.copyparams(chainer_model.conv5_2) + cv_model.conv5_3.copyparams(chainer_model.conv5_3) + cv_model.fc6.copyparams(chainer_model.fc6) + cv_model.fc7.copyparams(chainer_model.fc7) + cv_model.fc8.copyparams(chainer_model.fc8) + + chainer.serializers.save_npz('vgg_from_caffe.npz', cv_model) From 55ecaca919172d834aecdf7c504ea78c21f2a5a6 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 14 Jun 2017 17:40:59 +0900 Subject: [PATCH 008/139] improve eval_imagenet --- examples/classification/eval_imagenet.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index 8548ac1d38..7ecb81cd1c 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -5,6 +5,7 @@ import chainer import chainer.links as L +import chainer.functions as F from chainer import iterators from chainer import training from chainer.training import extensions @@ -12,6 +13,8 @@ from chainercv.datasets import ImageFolderDataset from chainercv.links import VGG16Layers +from chainercv.utils import apply_prediction_to_iterator + def main(): parser = argparse.ArgumentParser( @@ -26,15 +29,21 @@ def main(): dataset, args.batchsize, repeat=False, shuffle=False, n_processes=4) - model = L.Classifier(VGG16Layers()) + model = VGG16Layers() if args.gpu >= 0: chainer.cuda.get_device(args.gpu).use() model.to_gpu() - - result = extensions.Evaluator(iterator, model) - print result + imgs, pred_values, gt_values = apply_prediction_to_iterator(model.predict, iterator) + del imgs + + pred_labels, = pred_values + gt_labels, = gt_values + + accuracy = F.accuracy( + np.array(list(pred_labels)), np.array(list(gt_labels))).data + print accuracy if __name__ == '__main__': From f570a92057cb5e7254a5b416df23feb968f8af4e Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 14 Jun 2017 17:41:06 +0900 Subject: [PATCH 009/139] add pretrained_model ooption to eval_imagenet --- examples/classification/eval_imagenet.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index 7ecb81cd1c..1332263606 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -20,6 +20,7 @@ def main(): parser = argparse.ArgumentParser( description='Learning convnet from ILSVRC2012 dataset') parser.add_argument('val', help='Path to root of the validation dataset') + parser.add_argument('--pretrained_model') parser.add_argument('--gpu', type=int, default=-1) parser.add_argument('--batchsize', type=int, default=32) args = parser.parse_args() From f479f21741e9f31b544db36057b96a55be511145 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 14 Jun 2017 17:43:43 +0900 Subject: [PATCH 010/139] small fixes --- examples/classification/convert_from_caffe.py | 2 +- examples/classification/eval_imagenet.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/classification/convert_from_caffe.py b/examples/classification/convert_from_caffe.py index 9f6ddb3926..a96da45348 100644 --- a/examples/classification/convert_from_caffe.py +++ b/examples/classification/convert_from_caffe.py @@ -6,7 +6,7 @@ if __name__ == '__main__': chainer_model = VGG16Layers_chainer() - cv_model = VGG16Layers_cv() + cv_model = VGG16Layers_cv(pretrained_model=None) cv_model.conv1_1.copyparams(chainer_model.conv1_1) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index 1332263606..e058065c2a 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -30,7 +30,7 @@ def main(): dataset, args.batchsize, repeat=False, shuffle=False, n_processes=4) - model = VGG16Layers() + model = VGG16Layers(pretrained_model=args.pretrained_model) if args.gpu >= 0: chainer.cuda.get_device(args.gpu).use() From e23e5f52488fbbde8bb54e6700a64c2312b66b92 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 14 Jun 2017 17:53:03 +0900 Subject: [PATCH 011/139] pass test_predict --- chainercv/links/model/vgg/vgg16.py | 10 +++++++--- tests/links_tests/model_tests/vgg_tests/test_vgg16.py | 10 ++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 0b76e308ca..db99a5284f 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -11,13 +11,14 @@ from chainer.initializers import normal import chainer.links as L +from chainercv.transforms import center_crop from chainercv.transforms import resize from chainercv.transforms import ten_crop # RGB order _imagenet_mean = np.array( - [[[123.68], [116.779], [103.939]]], dtype=np.float32) + [123.68, 116.779, 103.939], dtype=np.float32)[:, np.newaxis, np.newaxis] class VGG16Layers(chainer.Chain): @@ -25,6 +26,7 @@ class VGG16Layers(chainer.Chain): def __init__(self, pretrained_model='auto', feature='prob', initialW=None, initial_bias=None, mean=_imagenet_mean): + self.mean = mean if pretrained_model: # As a sampling process is time-consuming, # we employ a zero initializer for faster computation. @@ -158,6 +160,8 @@ def predict(self, imgs, do_ten_crop=True): imgs = [self._prepare(img) for img in imgs] if do_ten_crop: imgs = [ten_crop(img, (224, 224)) for img in imgs] + else: + imgs = [center_crop(img, (224, 224)) for img in imgs] imgs = self.xp.asarray(imgs).reshape(-1, 3, 224, 224) with chainer.function.no_backprop_mode(): @@ -165,8 +169,8 @@ def predict(self, imgs, do_ten_crop=True): y = self(imgs).data if do_ten_crop: - n = y.data.shape[0] // 10 - y_shape = y.data.shape[1:] + n = y.shape[0] // 10 + y_shape = y.shape[1:] y = y.reshape((n, 10) + y_shape) y = self.xp.sum(y, axis=1) / 10 return cuda.to_cpu(y) diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py index e509b4d868..d64cff7443 100644 --- a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -39,8 +39,9 @@ def test_call_gpu(self): @testing.parameterize( - {'feature': 'prob', 'shape': (2, 1000)}, - {'feature': 'conv5_3', 'shape': (2, 512, 7, 7)} + {'feature': 'prob', 'shape': (2, 1000), 'do_ten_crop': False}, + {'feature': 'prob', 'shape': (2, 1000), 'do_ten_crop': True}, + {'feature': 'conv5_3', 'shape': (2, 512, 14, 14), 'do_ten_crop': False} ) class TestVGG16LayersPredict(unittest.TestCase): @@ -50,10 +51,7 @@ def setUp(self): def check_predict(self): x1 = np.random.uniform(0, 255, (3, 320, 240)).astype(np.float32) x2 = np.random.uniform(0, 255, (3, 320, 240)).astype(np.float32) - out = self.link.predict([x1, x2], oversample=False) - self.assertEqual(out.shape, self.shape) - self.assertEqual(out.dtype, np.float32) - result = self.link.predict([x1, x2], oversample=True) + out = self.link.predict([x1, x2], do_ten_crop=self.do_ten_crop) self.assertEqual(out.shape, self.shape) self.assertEqual(out.dtype, np.float32) From b45fe22799af3a872a12ab97c0b6dae035106493 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 14 Jun 2017 18:10:03 +0900 Subject: [PATCH 012/139] more informative evaluation --- examples/classification/eval_imagenet.py | 26 +++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index e058065c2a..b965394f41 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -1,5 +1,7 @@ import argparse import random +import sys +import time import numpy as np @@ -16,6 +18,22 @@ from chainercv.utils import apply_prediction_to_iterator +class ProgressHook(object): + + def __init__(self, n_total): + self.n_total = n_total + self.start = time.time() + self.n_processed = 0 + + def __call__(self, imgs, pred_values, gt_values): + self.n_processed += len(imgs) + fps = self.n_processed / (time.time() - self.start) + sys.stdout.write( + '\r{:d} of {:d} images, {:.2f} FPS'.format( + self.n_processed, self.n_total, fps)) + sys.stdout.flush() + + def main(): parser = argparse.ArgumentParser( description='Learning convnet from ILSVRC2012 dataset') @@ -28,7 +46,7 @@ def main(): dataset = ImageFolderDataset(args.val) iterator = iterators.MultiprocessIterator( dataset, args.batchsize, repeat=False, shuffle=False, - n_processes=4) + n_processes=4, shared_mem=10000000) model = VGG16Layers(pretrained_model=args.pretrained_model) @@ -36,7 +54,8 @@ def main(): chainer.cuda.get_device(args.gpu).use() model.to_gpu() - imgs, pred_values, gt_values = apply_prediction_to_iterator(model.predict, iterator) + imgs, pred_values, gt_values = apply_prediction_to_iterator( + model.predict, iterator, hook=ProgressHook(len(dataset))) del imgs pred_labels, = pred_values @@ -44,7 +63,8 @@ def main(): accuracy = F.accuracy( np.array(list(pred_labels)), np.array(list(gt_labels))).data - print accuracy + print() + print('Top 1 Error {}'.format(1. - accuracy)) if __name__ == '__main__': From 1f3a999732126448bb90e834ea49bf2220df3b21 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 14 Jun 2017 18:11:58 +0900 Subject: [PATCH 013/139] use scale instead of resize --- chainercv/links/model/vgg/vgg16.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index db99a5284f..bebe903ccb 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -12,7 +12,7 @@ import chainer.links as L from chainercv.transforms import center_crop -from chainercv.transforms import resize +from chainercv.transforms import scale from chainercv.transforms import ten_crop @@ -133,7 +133,7 @@ def _prepare(self, img): """ - img = resize(img, (256, 256)) + img = scale(img, size=256) img = img - self.mean return img From 1f0353ab98086ce02b6eb2835a9a3b6578fb33a9 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 14 Jun 2017 18:37:10 +0900 Subject: [PATCH 014/139] fix a bug in scale --- chainercv/transforms/image/scale.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chainercv/transforms/image/scale.py b/chainercv/transforms/image/scale.py index 32b1c13a7c..84e91ca25d 100644 --- a/chainercv/transforms/image/scale.py +++ b/chainercv/transforms/image/scale.py @@ -27,9 +27,9 @@ def scale(img, size, fit_short=True): _, H, W = img.shape # If resizing is not necessary, return the input as is. - if fit_short and (H <= W and H == size) or (W <= H and W == size): + if fit_short and ((H <= W and H == size) or (W <= H and W == size)): return img - if not fit_short and (H >= W and H == size) or (W >= H and W == size): + if not fit_short and ((H >= W and H == size) or (W >= H and W == size)): return img if fit_short: From d184963ad387eae9d3891beedc341d41b04871ab Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 14 Jun 2017 19:56:34 +0900 Subject: [PATCH 015/139] move crop option to __init__ --- chainercv/links/model/vgg/vgg16.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index bebe903ccb..6a7b908fc2 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -25,8 +25,12 @@ class VGG16Layers(chainer.Chain): def __init__(self, pretrained_model='auto', feature='prob', initialW=None, initial_bias=None, - mean=_imagenet_mean): + mean=_imagenet_mean, do_ten_crop=False): self.mean = mean + self.do_ten_crop = do_ten_crop + if do_ten_crop and feature not in ['fc6', 'fc7', 'fc8', 'prob']: + raise ValueError + if pretrained_model: # As a sampling process is time-consuming, # we employ a zero initializer for faster computation. @@ -138,7 +142,7 @@ def _prepare(self, img): return img - def predict(self, imgs, do_ten_crop=True): + def predict(self, imgs): """Compute class probabilities of given images. Args: @@ -154,11 +158,8 @@ def predict(self, imgs, do_ten_crop=True): A batch of arrays containing class-probabilities. """ - if do_ten_crop and self.feature not in ['fc6', 'fc7', 'fc8', 'prob']: - raise ValueError - imgs = [self._prepare(img) for img in imgs] - if do_ten_crop: + if self.do_ten_crop: imgs = [ten_crop(img, (224, 224)) for img in imgs] else: imgs = [center_crop(img, (224, 224)) for img in imgs] @@ -168,7 +169,7 @@ def predict(self, imgs, do_ten_crop=True): imgs = chainer.Variable(imgs) y = self(imgs).data - if do_ten_crop: + if self.do_ten_crop: n = y.shape[0] // 10 y_shape = y.shape[1:] y = y.reshape((n, 10) + y_shape) From c6d62de43e6019a14ba82759022195392aa9a463 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 14 Jun 2017 20:11:09 +0900 Subject: [PATCH 016/139] fix eval_imagenet --- examples/classification/eval_imagenet.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index b965394f41..f794876416 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -35,6 +35,8 @@ def __call__(self, imgs, pred_values, gt_values): def main(): + chainer.config.train = False + parser = argparse.ArgumentParser( description='Learning convnet from ILSVRC2012 dataset') parser.add_argument('val', help='Path to root of the validation dataset') @@ -46,7 +48,7 @@ def main(): dataset = ImageFolderDataset(args.val) iterator = iterators.MultiprocessIterator( dataset, args.batchsize, repeat=False, shuffle=False, - n_processes=4, shared_mem=10000000) + n_processes=6, shared_mem=300000000) model = VGG16Layers(pretrained_model=args.pretrained_model) From d1a4e4661ca2218c9076b998b80319931ef8a4d6 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 15 Jun 2017 10:18:02 +0900 Subject: [PATCH 017/139] add docs --- chainercv/links/model/vgg/__init__.py | 1 + chainercv/links/model/vgg/vgg16.py | 91 ++++++++++++++++++++++----- docs/source/reference/links.rst | 11 ++++ docs/source/reference/links/vgg.rst | 11 ++++ 4 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 docs/source/reference/links/vgg.rst diff --git a/chainercv/links/model/vgg/__init__.py b/chainercv/links/model/vgg/__init__.py index e69de29bb2..1d0ebf7a3b 100644 --- a/chainercv/links/model/vgg/__init__.py +++ b/chainercv/links/model/vgg/__init__.py @@ -0,0 +1 @@ +from chainercv.links.model.vgg.vgg16 import VGG16Layers # NOQA diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 6a7b908fc2..db1004b29f 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -23,13 +23,50 @@ class VGG16Layers(chainer.Chain): + """VGG16 Network for classification and feature extraction. + + This model can be used for both classification task and feature extraction. + The network can choose to output features from set of all + intermediate and final features produced by the original architecture. + + When :obj:`pretrained_model` is thepath of a pre-trained chainer model + serialized as a :obj:`.npz` file in the constructor, this chain model + automatically initializes all the parameters with it. + When a string in the prespecified set is provided, a pretrained model is + loaded from weights distributed on the Internet. + The list of pretrained models supported are as follows: + + * :obj:`imagenet`: Loads weights trained with ImageNet and distributed \ + at `Model Zoo \ + `_. + + Args: + pretrained_model (str): The destination of the pre-trained + chainer model serialized as a :obj:`.npz` file. + If this is one of the strings described + above, it automatically loads weights stored under a directory + :obj:`$CHAINER_DATASET_ROOT/pfnet/chainercv/models/`, + where :obj:`$CHAINER_DATASET_ROOT` is set as + :obj:`$HOME/.chainer/dataset` unless you specify another value + by modifying the environment variable. + feature (str): The name of the feature to output with + :meth:`__call__` and :meth:`predict`. + initialW (callable): Initializer for the weights. + initial_bias (callable): Initializer for the biases. + mean (numpy.ndarray): A value to be subtracted from an image + in :meth:`_prepare`. + do_ten_crop (bool): If :obj:`True`, it averages results across + center, corners, and mirrors in :meth:`predict`. Otherwise, it uses + only the center. + + """ + def __init__(self, pretrained_model='auto', feature='prob', initialW=None, initial_bias=None, - mean=_imagenet_mean, do_ten_crop=False): + mean=_imagenet_mean, do_ten_crop=True): self.mean = mean self.do_ten_crop = do_ten_crop - if do_ten_crop and feature not in ['fc6', 'fc7', 'fc8', 'prob']: - raise ValueError + self._feature = feature if pretrained_model: # As a sampling process is time-consuming, @@ -66,7 +103,6 @@ def __init__(self, pretrained_model='auto', feature='prob', self.fc6 = L.Linear(512 * 7 * 7, 4096, **kwargs) self.fc7 = L.Linear(4096, 4096, **kwargs) self.fc8 = L.Linear(4096, 1000, **kwargs) - self.feature = feature if pretrained_model: chainer.serializers.load_npz(pretrained_model, self) @@ -107,19 +143,29 @@ def functions(self): ('fc8', [self.fc8]), ('prob', [F.softmax]), ]) - if self.feature not in default_funcs: - raise ValueError('`feature` shuold be one of the keys of ' - 'VGG16Layers.functions.') + if self._feature not in default_funcs: + raise ValueError('`feature` shuold be one of ' + '{}.'.format(default_funcs.keys())) pop_funcs = False for name in default_funcs.keys(): if pop_funcs: default_funcs.pop(name) - if name == self.feature: + if name == self._feature: pop_funcs = True return default_funcs def __call__(self, x): + """Fowrard VGG16. + + Args: + x (~chainer.Variable): Batch of image variables. + + Returns: + ~chainer.Variable: + A batch of features. It is selected by :obj:`self._feature`. + + """ h = x for funcs in self.functions.values(): for func in funcs: @@ -130,34 +176,47 @@ def _prepare(self, img): """Transform an image to the input for VGG network. Args: - img (numpy.ndarray) + img (~numpy.ndarray): An image. This is in CHW and RGB format. + The range of its value is :math:`[0, 255]`. Returns: - numpy.ndarray: The transformed image. + ~numpy.ndarray: + A preprocessed image. """ - img = scale(img, size=256) img = img - self.mean return img def predict(self, imgs): - """Compute class probabilities of given images. + """Predict features from images. + + When :obj:`self.do_ten_crop == True`, this extracts features from + patches that are ten-cropped from images. + Otherwise, this extracts features from center-crop of the images. + + When using patches from ten-crop, the features for each crop + is averaged to compute one feature. + Ten-crop only supports calculation of features + :math:`fc6, fc7, fc8, prob`. + + Given :math:`N` input images, this outputs a batched array with + batchsize :math:`N`. Args: imgs (iterable of numpy.ndarray): Array-images. All images are in CHW and RGB format and the range of their value is :math:`[0, 255]`. - do_ten_crop (bool): If :obj:`True`, it averages results across - center, corners, and mirrors. Otherwise, it uses only the - center. Returns: numpy.ndarray: - A batch of arrays containing class-probabilities. + A batch of features. It is selected by :obj:`self._feature`. """ + if self.do_ten_crop and self._feature not in ['fc6', 'fc7', 'fc8', 'prob']: + raise ValueError + imgs = [self._prepare(img) for img in imgs] if self.do_ten_crop: imgs = [ten_crop(img, (224, 224)) for img in imgs] diff --git a/docs/source/reference/links.rst b/docs/source/reference/links.rst index 1d6f645349..5bd6c2eb39 100644 --- a/docs/source/reference/links.rst +++ b/docs/source/reference/links.rst @@ -3,6 +3,17 @@ Links .. module:: chainercv.links.model.faster_rcnn + +Classification +-------------- + +Classification links share a common method :meth:`predict` to classify or extract features with images. +For more details, please read :func:`VGG16Layers.predict`. + +.. toctree:: + + links/vgg + Detection --------- diff --git a/docs/source/reference/links/vgg.rst b/docs/source/reference/links/vgg.rst new file mode 100644 index 0000000000..6f3e695f50 --- /dev/null +++ b/docs/source/reference/links/vgg.rst @@ -0,0 +1,11 @@ +VGG +=== + +.. module:: chainercv.links.model.vgg + + +VGG16Layers +----------- + +.. autoclass:: VGG16Layers + :members: From df66a0a7e0e391ce8b56266be7ca44769e33d8a1 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 15 Jun 2017 10:29:43 +0900 Subject: [PATCH 018/139] support automatic download --- chainercv/links/model/vgg/vgg16.py | 38 ++++++++++++++++--- examples/classification/convert_from_caffe.py | 2 +- examples/classification/eval_imagenet.py | 6 ++- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index db1004b29f..a2a3c90353 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -15,6 +15,8 @@ from chainercv.transforms import scale from chainercv.transforms import ten_crop +from chainercv.utils import download_model + # RGB order _imagenet_mean = np.array( @@ -49,6 +51,7 @@ class VGG16Layers(chainer.Chain): where :obj:`$CHAINER_DATASET_ROOT` is set as :obj:`$HOME/.chainer/dataset` unless you specify another value by modifying the environment variable. + n_class (int): The number of classes to predict when classifying. feature (str): The name of the feature to output with :meth:`__call__` and :meth:`predict`. initialW (callable): Initializer for the weights. @@ -57,13 +60,31 @@ class VGG16Layers(chainer.Chain): in :meth:`_prepare`. do_ten_crop (bool): If :obj:`True`, it averages results across center, corners, and mirrors in :meth:`predict`. Otherwise, it uses - only the center. + only the center. The default value is :obj:`True`. """ - def __init__(self, pretrained_model='auto', feature='prob', - initialW=None, initial_bias=None, + _models = { + 'imagenet': { + 'n_class': 1000, + 'url': 'https://github.com/yuyu2172/share-weights/releases/' + 'download/0.0.3/vgg16_imagenet_convert_2017_06_15.npz' + } + } + + def __init__(self, pretrained_model=None, n_class=None, + feature='prob', initialW=None, initial_bias=None, mean=_imagenet_mean, do_ten_crop=True): + if n_class is None: + if pretrained_model is None and feature not in ['fc8', 'prob']: + # The fc8 weight will not be used. + n_class = 1 + elif pretrained_model not in self._models: + raise ValueError( + 'The n_class needs to be supplied as an argument') + else: + n_class = self._models[pretrained_model]['n_class'] + self.mean = mean self.do_ten_crop = do_ten_crop self._feature = feature @@ -102,9 +123,14 @@ def __init__(self, pretrained_model='auto', feature='prob', self.conv5_3 = L.Convolution2D(512, 512, 3, 1, 1, **kwargs) self.fc6 = L.Linear(512 * 7 * 7, 4096, **kwargs) self.fc7 = L.Linear(4096, 4096, **kwargs) - self.fc8 = L.Linear(4096, 1000, **kwargs) - - if pretrained_model: + self.fc8 = L.Linear(4096, n_class, **kwargs) + + if pretrained_model in self._models: + path = download_model(self._models[pretrained_model]['url']) + chainer.serializers.load_npz(path, self) + elif pretrained_model == 'imagenet': + self._copy_imagenet_pretrained_vgg16() + elif pretrained_model: chainer.serializers.load_npz(pretrained_model, self) # Links can be safely removed because parameters in these links diff --git a/examples/classification/convert_from_caffe.py b/examples/classification/convert_from_caffe.py index a96da45348..e9cf089928 100644 --- a/examples/classification/convert_from_caffe.py +++ b/examples/classification/convert_from_caffe.py @@ -6,7 +6,7 @@ if __name__ == '__main__': chainer_model = VGG16Layers_chainer() - cv_model = VGG16Layers_cv(pretrained_model=None) + cv_model = VGG16Layers_cv(pretrained_model=None, n_class=1000) cv_model.conv1_1.copyparams(chainer_model.conv1_1) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index f794876416..637795bc9b 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -50,7 +50,11 @@ def main(): dataset, args.batchsize, repeat=False, shuffle=False, n_processes=6, shared_mem=300000000) - model = VGG16Layers(pretrained_model=args.pretrained_model) + + if args.pretrained_model: + model = VGG16Layers(pretrained_model=args.pretrained_model) + else: + model = VGG16Layers(pretrained_model='imagenet') if args.gpu >= 0: chainer.cuda.get_device(args.gpu).use() From 84a905d0247b6cabc78863bd689754e51f44d2ad Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 15 Jun 2017 10:37:11 +0900 Subject: [PATCH 019/139] Work without setting n_class or pretrained_model --- chainercv/links/model/vgg/vgg16.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index a2a3c90353..6bf5e1fe44 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -42,6 +42,9 @@ class VGG16Layers(chainer.Chain): at `Model Zoo \ `_. + If :obj:`feature` is not :math:`fc8` or :math:`prob`, + both :obj:`n_class` and :obj:`pretrained_model` can be :obj:`None`. + Args: pretrained_model (str): The destination of the pre-trained chainer model serialized as a :obj:`.npz` file. @@ -81,7 +84,7 @@ def __init__(self, pretrained_model=None, n_class=None, n_class = 1 elif pretrained_model not in self._models: raise ValueError( - 'The n_class needs to be supplied as an argument') + 'The n_class needs to be supplied as an argument.') else: n_class = self._models[pretrained_model]['n_class'] From a83d548739e863ade816299c6e2c1e35eff6b801 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 15 Jun 2017 10:45:56 +0900 Subject: [PATCH 020/139] remove VGG16FeatureExtractor --- chainercv/links/model/faster_rcnn/__init__.py | 1 - .../model/faster_rcnn/faster_rcnn_vgg.py | 78 +------------------ docs/source/reference/links/faster_rcnn.rst | 4 - 3 files changed, 3 insertions(+), 80 deletions(-) diff --git a/chainercv/links/model/faster_rcnn/__init__.py b/chainercv/links/model/faster_rcnn/__init__.py index f4e5faaa73..9f39e1c44f 100644 --- a/chainercv/links/model/faster_rcnn/__init__.py +++ b/chainercv/links/model/faster_rcnn/__init__.py @@ -1,7 +1,6 @@ from chainercv.links.model.faster_rcnn.faster_rcnn import FasterRCNN # NOQA from chainercv.links.model.faster_rcnn.faster_rcnn_train_chain import FasterRCNNTrainChain # NOQA from chainercv.links.model.faster_rcnn.faster_rcnn_vgg import FasterRCNNVGG16 # NOQA -from chainercv.links.model.faster_rcnn.faster_rcnn_vgg import VGG16FeatureExtractor # NOQA from chainercv.links.model.faster_rcnn.faster_rcnn_vgg import VGG16RoIHead # NOQA from chainercv.links.model.faster_rcnn.region_proposal_network import RegionProposalNetwork # NOQA from chainercv.links.model.faster_rcnn.utils.anchor_target_creator import AnchorTargetCreator # NOQA diff --git a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py index c5bb2e157c..58c8761c0d 100644 --- a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py +++ b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py @@ -1,14 +1,13 @@ -import collections import numpy as np import chainer import chainer.functions as F import chainer.links as L -from chainer.links import VGG16Layers from chainercv.links.model.faster_rcnn.faster_rcnn import FasterRCNN from chainercv.links.model.faster_rcnn.region_proposal_network import \ RegionProposalNetwork +from chainercv.links.model.vgg.vgg16 import VGG16Layers from chainercv.utils import download_model @@ -103,7 +102,7 @@ def __init__(self, if vgg_initialW is None and pretrained_model: vgg_initialW = chainer.initializers.constant.Zero() - extractor = VGG16FeatureExtractor(initialW=vgg_initialW) + extractor = VGG16Layers(feature='conv5_3', initialW=vgg_initialW) rpn = RegionProposalNetwork( 512, 512, ratios=ratios, @@ -139,12 +138,8 @@ def __init__(self, chainer.serializers.load_npz(pretrained_model, self) def _copy_imagenet_pretrained_vgg16(self): - pretrained_model = VGG16Layers() + pretrained_model = VGG16Layers(pretrained_model='imagenet') self.extractor.conv1_1.copyparams(pretrained_model.conv1_1) - # The pretrained weights are trained to accept BGR images. - # Convert weights so that they accept RGB images. - self.extractor.conv1_1.W.data[:] =\ - self.extractor.conv1_1.W.data[:, ::-1] self.extractor.conv1_2.copyparams(pretrained_model.conv1_2) self.extractor.conv2_1.copyparams(pretrained_model.conv2_1) self.extractor.conv2_2.copyparams(pretrained_model.conv2_2) @@ -225,75 +220,8 @@ def __call__(self, x, rois, roi_indices): return roi_cls_locs, roi_scores -class VGG16FeatureExtractor(chainer.Chain): - """Truncated VGG-16 that extracts a conv5_3 feature map. - - Args: - initialW (callable): Initializer for the weights. - - """ - - def __init__(self, initialW=None): - super(VGG16FeatureExtractor, self).__init__() - with self.init_scope(): - self.conv1_1 = L.Convolution2D(3, 64, 3, 1, 1, initialW=initialW) - self.conv1_2 = L.Convolution2D(64, 64, 3, 1, 1, initialW=initialW) - self.conv2_1 = L.Convolution2D(64, 128, 3, 1, 1, initialW=initialW) - self.conv2_2 = L.Convolution2D( - 128, 128, 3, 1, 1, initialW=initialW) - self.conv3_1 = L.Convolution2D( - 128, 256, 3, 1, 1, initialW=initialW) - self.conv3_2 = L.Convolution2D( - 256, 256, 3, 1, 1, initialW=initialW) - self.conv3_3 = L.Convolution2D( - 256, 256, 3, 1, 1, initialW=initialW) - self.conv4_1 = L.Convolution2D( - 256, 512, 3, 1, 1, initialW=initialW) - self.conv4_2 = L.Convolution2D( - 512, 512, 3, 1, 1, initialW=initialW) - self.conv4_3 = L.Convolution2D( - 512, 512, 3, 1, 1, initialW=initialW) - self.conv5_1 = L.Convolution2D( - 512, 512, 3, 1, 1, initialW=initialW) - self.conv5_2 = L.Convolution2D( - 512, 512, 3, 1, 1, initialW=initialW) - self.conv5_3 = L.Convolution2D( - 512, 512, 3, 1, 1, initialW=initialW) - - self.functions = collections.OrderedDict([ - ('conv1_1', [self.conv1_1, F.relu]), - ('conv1_2', [self.conv1_2, F.relu]), - ('pool1', [_max_pooling_2d]), - ('conv2_1', [self.conv2_1, F.relu]), - ('conv2_2', [self.conv2_2, F.relu]), - ('pool2', [_max_pooling_2d]), - ('conv3_1', [self.conv3_1, F.relu]), - ('conv3_2', [self.conv3_2, F.relu]), - ('conv3_3', [self.conv3_3, F.relu]), - ('pool3', [_max_pooling_2d]), - ('conv4_1', [self.conv4_1, F.relu]), - ('conv4_2', [self.conv4_2, F.relu]), - ('conv4_3', [self.conv4_3, F.relu]), - ('pool4', [_max_pooling_2d]), - ('conv5_1', [self.conv5_1, F.relu]), - ('conv5_2', [self.conv5_2, F.relu]), - ('conv5_3', [self.conv5_3, F.relu]), - ]) - - def __call__(self, x): - h = x - for key, funcs in self.functions.items(): - for func in funcs: - h = func(h) - return h - - def _roi_pooling_2d_yx(x, indices_and_rois, outh, outw, spatial_scale): xy_indices_and_rois = indices_and_rois[:, [0, 2, 1, 4, 3]] pool = F.roi_pooling_2d( x, xy_indices_and_rois, outh, outw, spatial_scale) return pool - - -def _max_pooling_2d(x): - return F.max_pooling_2d(x, ksize=2) diff --git a/docs/source/reference/links/faster_rcnn.rst b/docs/source/reference/links/faster_rcnn.rst index 8a50ece3c9..bec09c5675 100644 --- a/docs/source/reference/links/faster_rcnn.rst +++ b/docs/source/reference/links/faster_rcnn.rst @@ -45,10 +45,6 @@ RegionProposalNetwork :members: :special-members: __call__ -VGG16FeatureExtractor -~~~~~~~~~~~~~~~~~~~~~ -.. autoclass:: VGG16FeatureExtractor - VGG16RoIHead ~~~~~~~~~~~~ .. autoclass:: VGG16RoIHead From b696389324860b6e0bf2a763f3a1d3dc34ff7937 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 15 Jun 2017 10:54:08 +0900 Subject: [PATCH 021/139] improve print output --- examples/classification/eval_imagenet.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index 637795bc9b..a01a998a45 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -60,6 +60,7 @@ def main(): chainer.cuda.get_device(args.gpu).use() model.to_gpu() + print('Model has been prepared. Evaluation starts.') imgs, pred_values, gt_values = apply_prediction_to_iterator( model.predict, iterator, hook=ProgressHook(len(dataset))) del imgs From 47326fb46c072e9113b46b89b99a891957b0af3d Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 15 Jun 2017 10:55:27 +0900 Subject: [PATCH 022/139] flake8 of eval_imagenet --- examples/classification/eval_imagenet.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index a01a998a45..b1bf194872 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -1,16 +1,12 @@ import argparse -import random import sys import time import numpy as np import chainer -import chainer.links as L import chainer.functions as F from chainer import iterators -from chainer import training -from chainer.training import extensions from chainercv.datasets import ImageFolderDataset from chainercv.links import VGG16Layers @@ -50,7 +46,6 @@ def main(): dataset, args.batchsize, repeat=False, shuffle=False, n_processes=6, shared_mem=300000000) - if args.pretrained_model: model = VGG16Layers(pretrained_model=args.pretrained_model) else: From 30035bbfd7fc44f9249e119b4a492cdfc594fcec Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 15 Jun 2017 10:56:46 +0900 Subject: [PATCH 023/139] improve doc --- chainercv/links/model/vgg/vgg16.py | 2 +- docs/source/reference/links/vgg.rst | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 6bf5e1fe44..8b38d04ba3 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -227,7 +227,7 @@ def predict(self, imgs): When using patches from ten-crop, the features for each crop is averaged to compute one feature. - Ten-crop only supports calculation of features + Ten-crop mode is only supported for calculation of features :math:`fc6, fc7, fc8, prob`. Given :math:`N` input images, this outputs a batched array with diff --git a/docs/source/reference/links/vgg.rst b/docs/source/reference/links/vgg.rst index 6f3e695f50..d6d0c45aec 100644 --- a/docs/source/reference/links/vgg.rst +++ b/docs/source/reference/links/vgg.rst @@ -9,3 +9,4 @@ VGG16Layers .. autoclass:: VGG16Layers :members: + :special-members: __call__ From 2ffae2ba16a8ca316e5f751e481d14af3439d4c5 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 15 Jun 2017 10:57:13 +0900 Subject: [PATCH 024/139] flake8 of tests --- tests/links_tests/model_tests/vgg_tests/test_vgg16.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py index d64cff7443..e2f303c717 100644 --- a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -2,7 +2,6 @@ import numpy as np -from chainer import cuda from chainer import testing from chainer.initializers import Zero from chainer.testing import attr @@ -115,4 +114,5 @@ def test_feature_option_gpu(self): self.link.to_gpu() self.check_feature_option() + testing.run_module(__name__, __file__) From 8b3337c878617280c51b7c5ab65715effede5d10 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 15 Jun 2017 11:08:40 +0900 Subject: [PATCH 025/139] fix tests --- .../model_tests/vgg_tests/test_vgg16.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py index e2f303c717..83dd03047b 100644 --- a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -13,12 +13,16 @@ _zero_init = Zero +@testing.parameterize( + {'feature': 'prob', 'shape': (1, 200), 'n_class': 200}, + {'feature': 'pool5', 'shape': (1, 512, 7, 7), 'n_class': None}, +) @attr.slow class TestVGG16LayersCall(unittest.TestCase): def setUp(self): self.link = VGG16Layers( - pretrained_model=None) + pretrained_model=None, n_class=self.n_class, feature=self.feature) def check_call(self): xp = self.link.xp @@ -26,7 +30,7 @@ def check_call(self): x1 = Variable(xp.asarray(np.random.uniform( -1, 1, (1, 3, 224, 224)).astype(np.float32))) y1 = self.link(x1) - self.assertEqual(y1.shape, (1, 1000)) + self.assertEqual(y1.shape, self.shape) def test_call_cpu(self): self.check_call() @@ -45,12 +49,14 @@ def test_call_gpu(self): class TestVGG16LayersPredict(unittest.TestCase): def setUp(self): - self.link = VGG16Layers(pretrained_model=None, feature=self.feature) + self.link = VGG16Layers(pretrained_model=None, n_class=1000, + feature=self.feature, + do_ten_crop=self.do_ten_crop) def check_predict(self): x1 = np.random.uniform(0, 255, (3, 320, 240)).astype(np.float32) x2 = np.random.uniform(0, 255, (3, 320, 240)).astype(np.float32) - out = self.link.predict([x1, x2], do_ten_crop=self.do_ten_crop) + out = self.link.predict([x1, x2]) self.assertEqual(out.shape, self.shape) self.assertEqual(out.dtype, np.float32) @@ -66,7 +72,7 @@ def test_predict_gpu(self): class TestVGG16LayersCopy(unittest.TestCase): def setUp(self): - self.link = VGG16Layers(pretrained_model=None, + self.link = VGG16Layers(pretrained_model=None, n_class=200, initialW=Zero(), initial_bias=Zero()) def check_copy(self): From fab7a7cd52862d5fd34609c0e06a8fcb380594e9 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 15 Jun 2017 11:41:21 +0900 Subject: [PATCH 026/139] Simplify __init__ --- chainercv/links/model/vgg/vgg16.py | 109 +++++++++--------- .../model_tests/vgg_tests/test_vgg16.py | 16 +-- 2 files changed, 61 insertions(+), 64 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 8b38d04ba3..18116370d9 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -5,8 +5,8 @@ import numpy as np import chainer -import chainer.functions as F from chainer import cuda +import chainer.functions as F from chainer.initializers import constant from chainer.initializers import normal import chainer.links as L @@ -42,9 +42,6 @@ class VGG16Layers(chainer.Chain): at `Model Zoo \ `_. - If :obj:`feature` is not :math:`fc8` or :math:`prob`, - both :obj:`n_class` and :obj:`pretrained_model` can be :obj:`None`. - Args: pretrained_model (str): The destination of the pre-trained chainer model serialized as a :obj:`.npz` file. @@ -54,7 +51,7 @@ class VGG16Layers(chainer.Chain): where :obj:`$CHAINER_DATASET_ROOT` is set as :obj:`$HOME/.chainer/dataset` unless you specify another value by modifying the environment variable. - n_class (int): The number of classes to predict when classifying. + n_class (int): The dimension of the output of fc8. feature (str): The name of the feature to output with :meth:`__call__` and :meth:`predict`. initialW (callable): Initializer for the weights. @@ -80,7 +77,7 @@ def __init__(self, pretrained_model=None, n_class=None, mean=_imagenet_mean, do_ten_crop=True): if n_class is None: if pretrained_model is None and feature not in ['fc8', 'prob']: - # The fc8 weight will not be used. + # fc8 layer is not used in this case. n_class = 1 elif pretrained_model not in self._models: raise ValueError( @@ -110,79 +107,76 @@ def __init__(self, pretrained_model=None, n_class=None, super(VGG16Layers, self).__init__() + links = { + 'conv1_1': L.Convolution2D(3, 64, 3, 1, 1, **kwargs), + 'conv1_2': L.Convolution2D(64, 64, 3, 1, 1, **kwargs), + 'conv2_1': L.Convolution2D(64, 128, 3, 1, 1, **kwargs), + 'conv2_2': L.Convolution2D(128, 128, 3, 1, 1, **kwargs), + 'conv3_1': L.Convolution2D(128, 256, 3, 1, 1, **kwargs), + 'conv3_2': L.Convolution2D(256, 256, 3, 1, 1, **kwargs), + 'conv3_3': L.Convolution2D(256, 256, 3, 1, 1, **kwargs), + 'conv4_1': L.Convolution2D(256, 512, 3, 1, 1, **kwargs), + 'conv4_2': L.Convolution2D(512, 512, 3, 1, 1, **kwargs), + 'conv4_3': L.Convolution2D(512, 512, 3, 1, 1, **kwargs), + 'conv5_1': L.Convolution2D(512, 512, 3, 1, 1, **kwargs), + 'conv5_2': L.Convolution2D(512, 512, 3, 1, 1, **kwargs), + 'conv5_3': L.Convolution2D(512, 512, 3, 1, 1, **kwargs), + 'fc6': L.Linear(512 * 7 * 7, 4096, **kwargs), + 'fc7': L.Linear(4096, 4096, **kwargs), + 'fc8': L.Linear(4096, n_class, **kwargs) + } + with self.init_scope(): - self.conv1_1 = L.Convolution2D(3, 64, 3, 1, 1, **kwargs) - self.conv1_2 = L.Convolution2D(64, 64, 3, 1, 1, **kwargs) - self.conv2_1 = L.Convolution2D(64, 128, 3, 1, 1, **kwargs) - self.conv2_2 = L.Convolution2D(128, 128, 3, 1, 1, **kwargs) - self.conv3_1 = L.Convolution2D(128, 256, 3, 1, 1, **kwargs) - self.conv3_2 = L.Convolution2D(256, 256, 3, 1, 1, **kwargs) - self.conv3_3 = L.Convolution2D(256, 256, 3, 1, 1, **kwargs) - self.conv4_1 = L.Convolution2D(256, 512, 3, 1, 1, **kwargs) - self.conv4_2 = L.Convolution2D(512, 512, 3, 1, 1, **kwargs) - self.conv4_3 = L.Convolution2D(512, 512, 3, 1, 1, **kwargs) - self.conv5_1 = L.Convolution2D(512, 512, 3, 1, 1, **kwargs) - self.conv5_2 = L.Convolution2D(512, 512, 3, 1, 1, **kwargs) - self.conv5_3 = L.Convolution2D(512, 512, 3, 1, 1, **kwargs) - self.fc6 = L.Linear(512 * 7 * 7, 4096, **kwargs) - self.fc7 = L.Linear(4096, 4096, **kwargs) - self.fc8 = L.Linear(4096, n_class, **kwargs) + for name, link in links.items(): + if name in self.functions: + setattr(self, name, link) if pretrained_model in self._models: path = download_model(self._models[pretrained_model]['url']) chainer.serializers.load_npz(path, self) - elif pretrained_model == 'imagenet': - self._copy_imagenet_pretrained_vgg16() elif pretrained_model: chainer.serializers.load_npz(pretrained_model, self) - # Links can be safely removed because parameters in these links - # are guaranteed to not be in a computational graph. - names = [child.name for child in self.children()] - functions = self.functions - for name in names: - if name not in functions: - delattr(self, name) - # Since self.functions access self.name, it needs a value. - setattr(self, name, None) - @property def functions(self): - default_funcs = collections.OrderedDict([ - ('conv1_1', [self.conv1_1, F.relu]), - ('conv1_2', [self.conv1_2, F.relu]), + def _getattr(name): + return getattr(self, name, None) + + funcs = collections.OrderedDict([ + ('conv1_1', [_getattr('conv1_1'), F.relu]), + ('conv1_2', [_getattr('conv1_2'), F.relu]), ('pool1', [_max_pooling_2d]), - ('conv2_1', [self.conv2_1, F.relu]), - ('conv2_2', [self.conv2_2, F.relu]), + ('conv2_1', [_getattr('conv2_1'), F.relu]), + ('conv2_2', [_getattr('conv2_2'), F.relu]), ('pool2', [_max_pooling_2d]), - ('conv3_1', [self.conv3_1, F.relu]), - ('conv3_2', [self.conv3_2, F.relu]), - ('conv3_3', [self.conv3_3, F.relu]), + ('conv3_1', [_getattr('conv3_1'), F.relu]), + ('conv3_2', [_getattr('conv3_2'), F.relu]), + ('conv3_3', [_getattr('conv3_3'), F.relu]), ('pool3', [_max_pooling_2d]), - ('conv4_1', [self.conv4_1, F.relu]), - ('conv4_2', [self.conv4_2, F.relu]), - ('conv4_3', [self.conv4_3, F.relu]), + ('conv4_1', [_getattr('conv4_1'), F.relu]), + ('conv4_2', [_getattr('conv4_2'), F.relu]), + ('conv4_3', [_getattr('conv4_3'), F.relu]), ('pool4', [_max_pooling_2d]), - ('conv5_1', [self.conv5_1, F.relu]), - ('conv5_2', [self.conv5_2, F.relu]), - ('conv5_3', [self.conv5_3, F.relu]), + ('conv5_1', [_getattr('conv5_1'), F.relu]), + ('conv5_2', [_getattr('conv5_2'), F.relu]), + ('conv5_3', [_getattr('conv5_3'), F.relu]), ('pool5', [_max_pooling_2d]), - ('fc6', [self.fc6, F.relu, F.dropout]), - ('fc7', [self.fc7, F.relu, F.dropout]), - ('fc8', [self.fc8]), + ('fc6', [_getattr('fc6'), F.relu, F.dropout]), + ('fc7', [_getattr('fc7'), F.relu, F.dropout]), + ('fc8', [_getattr('fc8')]), ('prob', [F.softmax]), ]) - if self._feature not in default_funcs: + if self._feature not in funcs: raise ValueError('`feature` shuold be one of ' - '{}.'.format(default_funcs.keys())) + '{}.'.format(funcs.keys())) pop_funcs = False - for name in default_funcs.keys(): + for name in funcs.keys(): if pop_funcs: - default_funcs.pop(name) + funcs.pop(name) if name == self._feature: pop_funcs = True - return default_funcs + return funcs def __call__(self, x): """Fowrard VGG16. @@ -243,7 +237,8 @@ def predict(self, imgs): A batch of features. It is selected by :obj:`self._feature`. """ - if self.do_ten_crop and self._feature not in ['fc6', 'fc7', 'fc8', 'prob']: + if (self.do_ten_crop and + self._feature not in ['fc6', 'fc7', 'fc8', 'prob']): raise ValueError imgs = [self._prepare(img) for img in imgs] diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py index 83dd03047b..fce6d1c565 100644 --- a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -73,7 +73,9 @@ class TestVGG16LayersCopy(unittest.TestCase): def setUp(self): self.link = VGG16Layers(pretrained_model=None, n_class=200, - initialW=Zero(), initial_bias=Zero()) + feature='conv2_2', + initialW=Zero(), initial_bias=Zero(), + ) def check_copy(self): copied = self.link.copy() @@ -95,12 +97,12 @@ def setUp(self): initialW=Zero(), initial_bias=Zero()) def check_feature_option(self): - self.assertTrue(self.link.conv5_1 is None) - self.assertTrue(self.link.conv5_2 is None) - self.assertTrue(self.link.conv5_3 is None) - self.assertTrue(self.link.fc6 is None) - self.assertTrue(self.link.fc7 is None) - self.assertTrue(self.link.fc8 is None) + self.assertTrue(not hasattr(self.link, 'conv5_1')) + self.assertTrue(not hasattr(self.link, 'conv5_2')) + self.assertTrue(not hasattr(self.link, 'conv5_3')) + self.assertTrue(not hasattr(self.link, 'fc6')) + self.assertTrue(not hasattr(self.link, 'fc7')) + self.assertTrue(not hasattr(self.link, 'fc8')) self.assertFalse('conv5_1' in self.link.functions) self.assertFalse('conv5_2' in self.link.functions) From 1ac56a1e80b81066565f3247477e56bbfae911fb Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 15 Jun 2017 12:22:50 +0900 Subject: [PATCH 027/139] fix tests --- examples/classification/convert_from_caffe.py | 2 +- tests/links_tests/model_tests/vgg_tests/test_vgg16.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/classification/convert_from_caffe.py b/examples/classification/convert_from_caffe.py index e9cf089928..c91e86f3e2 100644 --- a/examples/classification/convert_from_caffe.py +++ b/examples/classification/convert_from_caffe.py @@ -1,5 +1,5 @@ -from chainer.links import VGG16Layers as VGG16Layers_chainer import chainer +from chainer.links import VGG16Layers as VGG16Layers_chainer from chainercv.links import VGG16Layers as VGG16Layers_cv diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py index fce6d1c565..b59049f1bc 100644 --- a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -2,8 +2,8 @@ import numpy as np -from chainer import testing from chainer.initializers import Zero +from chainer import testing from chainer.testing import attr from chainer.variable import Variable @@ -46,6 +46,7 @@ def test_call_gpu(self): {'feature': 'prob', 'shape': (2, 1000), 'do_ten_crop': True}, {'feature': 'conv5_3', 'shape': (2, 512, 14, 14), 'do_ten_crop': False} ) +@attr.slow class TestVGG16LayersPredict(unittest.TestCase): def setUp(self): From 86f351d836a758aff483324de2bfa3fb5b722c78 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 15 Jun 2017 12:25:54 +0900 Subject: [PATCH 028/139] change convert_from_caffe to convert_vgg --- .../convert_vgg.py} | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) rename examples/classification/{convert_from_caffe.py => convert_from_original/convert_vgg.py} (98%) diff --git a/examples/classification/convert_from_caffe.py b/examples/classification/convert_from_original/convert_vgg.py similarity index 98% rename from examples/classification/convert_from_caffe.py rename to examples/classification/convert_from_original/convert_vgg.py index c91e86f3e2..2034e3f4f5 100644 --- a/examples/classification/convert_from_caffe.py +++ b/examples/classification/convert_from_original/convert_vgg.py @@ -4,7 +4,7 @@ from chainercv.links import VGG16Layers as VGG16Layers_cv -if __name__ == '__main__': +def main(): chainer_model = VGG16Layers_chainer() cv_model = VGG16Layers_cv(pretrained_model=None, n_class=1000) @@ -31,3 +31,7 @@ cv_model.fc8.copyparams(chainer_model.fc8) chainer.serializers.save_npz('vgg_from_caffe.npz', cv_model) + + +if __name__ == '__main__': + main() From fdf763e72060f904cbd2dbbf894478acfeee8369 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 15 Jun 2017 12:39:38 +0900 Subject: [PATCH 029/139] update README --- examples/classification/README.md | 23 ++++++++++++++++++++++- examples/classification/eval_imagenet.py | 11 +++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/examples/classification/README.md b/examples/classification/README.md index 7772d89eb1..d79f3d129f 100644 --- a/examples/classification/README.md +++ b/examples/classification/README.md @@ -1,8 +1,19 @@ # Classification +## Performance +| Model | Top 1 Error | Original Top 1 Error | +|:-:|:-:|:-:| +| VGG16 | 27.06 % | 27.0 % [1] | -## Download the ImageNet dataset +The results can be reproduced by the following command + +``` +$ python eval_imagenet.py [--model vgg16] [--pretrained_model ] [--batchsize ] [--gpu ] +``` + + +## How to prepare ImageNet Dataset This instructions are copied from ImageNet training for Torch. @@ -24,4 +35,14 @@ The ImageNet Large Scale Visual Recognition Challenge (ILSVRC) dataset has 1000 wget -qO- https://raw.githubusercontent.com/soumith/imagenetloader.torch/master/valprep.sh | bash ``` +## Note on implementations + +#### VGG16 + +In the original paper, fully connected layers are used as convolutional layers, and the final output is the spatial average of the outputs of the convolutions. +Our implementation averages predictions from ten-cropped patches. + + +## References +1. Karen Simonyan, Andrew Zisserman. "Very Deep Convolutional Networks for Large-Scale Image Recognition" ICLR 2015 diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index b1bf194872..e387a948aa 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -36,6 +36,8 @@ def main(): parser = argparse.ArgumentParser( description='Learning convnet from ILSVRC2012 dataset') parser.add_argument('val', help='Path to root of the validation dataset') + parser.add_argument( + '--model', choices=('vgg16')) parser.add_argument('--pretrained_model') parser.add_argument('--gpu', type=int, default=-1) parser.add_argument('--batchsize', type=int, default=32) @@ -46,10 +48,11 @@ def main(): dataset, args.batchsize, repeat=False, shuffle=False, n_processes=6, shared_mem=300000000) - if args.pretrained_model: - model = VGG16Layers(pretrained_model=args.pretrained_model) - else: - model = VGG16Layers(pretrained_model='imagenet') + if args.model == 'vgg16': + if args.pretrained_model: + model = VGG16Layers(pretrained_model=args.pretrained_model) + else: + model = VGG16Layers(pretrained_model='imagenet') if args.gpu >= 0: chainer.cuda.get_device(args.gpu).use() From d37d08d9b2a806b3345fba41711c0d517b92a65c Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 16 Jun 2017 10:40:54 +0900 Subject: [PATCH 030/139] accept multiple types of features --- chainercv/links/model/vgg/vgg16.py | 66 ++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 18116370d9..3831d3b443 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -27,7 +27,7 @@ class VGG16Layers(chainer.Chain): """VGG16 Network for classification and feature extraction. - This model can be used for both classification task and feature extraction. + This model is a feature extraction link. The network can choose to output features from set of all intermediate and final features produced by the original architecture. @@ -73,10 +73,10 @@ class VGG16Layers(chainer.Chain): } def __init__(self, pretrained_model=None, n_class=None, - feature='prob', initialW=None, initial_bias=None, + features='prob', initialW=None, initial_bias=None, mean=_imagenet_mean, do_ten_crop=True): if n_class is None: - if pretrained_model is None and feature not in ['fc8', 'prob']: + if pretrained_model is None and features not in ['fc8', 'prob']: # fc8 layer is not used in this case. n_class = 1 elif pretrained_model not in self._models: @@ -85,9 +85,16 @@ def __init__(self, pretrained_model=None, n_class=None, else: n_class = self._models[pretrained_model]['n_class'] + if isinstance(features, list or tuple): + return_dict = True + else: + return_dict = False + features = [features] + + self._return_dict = return_dict + self._features = features self.mean = mean self.do_ten_crop = do_ten_crop - self._feature = feature if pretrained_model: # As a sampling process is time-consuming, @@ -166,16 +173,21 @@ def _getattr(name): ('fc8', [_getattr('fc8')]), ('prob', [F.softmax]), ]) - if self._feature not in funcs: - raise ValueError('`feature` shuold be one of ' - '{}.'.format(funcs.keys())) + for feature in self._features: + if feature not in funcs: + raise ValueError('Elements of `features` shuold be one of ' + '{}.'.format(funcs.keys())) pop_funcs = False + features = list(self._features) for name in funcs.keys(): if pop_funcs: funcs.pop(name) - if name == self._feature: + if name in features: + features.remove(name) + if len(features) == 0: pop_funcs = True + return funcs def __call__(self, x): @@ -189,11 +201,17 @@ def __call__(self, x): A batch of features. It is selected by :obj:`self._feature`. """ + activations = {} h = x - for funcs in self.functions.values(): + for name, funcs in self.functions.items(): for func in funcs: h = func(h) - return h + if name in self._features: + activations[name] = h + + if not self._return_dict: + activations = activations.values()[0] + return activations def _prepare(self, img): """Transform an image to the input for VGG network. @@ -250,14 +268,30 @@ def predict(self, imgs): with chainer.function.no_backprop_mode(): imgs = chainer.Variable(imgs) - y = self(imgs).data + activations = self(imgs) + + if isinstance(activations, dict): + for name, activation in activations.items(): + activation = activation.data + if self.do_ten_crop: + activation = self._gather_ten_crop(activation) + activations[name] = cuda.to_cpu(activations) + else: + activations = cuda.to_cpu(activations.data) if self.do_ten_crop: - n = y.shape[0] // 10 - y_shape = y.shape[1:] - y = y.reshape((n, 10) + y_shape) - y = self.xp.sum(y, axis=1) / 10 - return cuda.to_cpu(y) + activations = self._gather_ten_crop(activations) + + return activations + + def _gather_ten_crop(self, y): + xp = chainer.cuda.get_array_module(y) + n = y.shape[0] // 10 + y_shape = y.shape[1:] + y = y.reshape((n, 10) + y_shape) + y = xp.sum(y, axis=1) / 10 + return y + def _max_pooling_2d(x): From 7f0461679e5cf52186ece6ae7af5e0f21fe2cbef Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 16 Jun 2017 11:06:05 +0900 Subject: [PATCH 031/139] accept multiple features as input --- chainercv/links/model/vgg/vgg16.py | 97 ++++++++++--------- .../model_tests/vgg_tests/test_vgg16.py | 74 +++++++------- 2 files changed, 90 insertions(+), 81 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 3831d3b443..ff3e05bd5b 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -52,15 +52,15 @@ class VGG16Layers(chainer.Chain): :obj:`$HOME/.chainer/dataset` unless you specify another value by modifying the environment variable. n_class (int): The dimension of the output of fc8. - feature (str): The name of the feature to output with - :meth:`__call__` and :meth:`predict`. + feature (str or iterable of strings): The name of the feature to output + with :meth:`__call__` and :meth:`predict`. initialW (callable): Initializer for the weights. initial_bias (callable): Initializer for the biases. mean (numpy.ndarray): A value to be subtracted from an image in :meth:`_prepare`. do_ten_crop (bool): If :obj:`True`, it averages results across center, corners, and mirrors in :meth:`predict`. Otherwise, it uses - only the center. The default value is :obj:`True`. + only the center. The default value is :obj:`False`. """ @@ -74,17 +74,7 @@ class VGG16Layers(chainer.Chain): def __init__(self, pretrained_model=None, n_class=None, features='prob', initialW=None, initial_bias=None, - mean=_imagenet_mean, do_ten_crop=True): - if n_class is None: - if pretrained_model is None and features not in ['fc8', 'prob']: - # fc8 layer is not used in this case. - n_class = 1 - elif pretrained_model not in self._models: - raise ValueError( - 'The n_class needs to be supplied as an argument.') - else: - n_class = self._models[pretrained_model]['n_class'] - + mean=_imagenet_mean, do_ten_crop=False): if isinstance(features, list or tuple): return_dict = True else: @@ -96,6 +86,18 @@ def __init__(self, pretrained_model=None, n_class=None, self.mean = mean self.do_ten_crop = do_ten_crop + if n_class is None: + if (pretrained_model is None and + all([feature not in ['fc8', 'prob'] + for feature in features])): + # fc8 layer is not used in this case. + pass + elif pretrained_model not in self._models: + raise ValueError( + 'The n_class needs to be supplied as an argument.') + else: + n_class = self._models[pretrained_model]['n_class'] + if pretrained_model: # As a sampling process is time-consuming, # we employ a zero initializer for faster computation. @@ -104,7 +106,7 @@ def __init__(self, pretrained_model=None, n_class=None, if initial_bias is None: initial_bias = constant.Zero() else: - # employ default initializers used in the original paper + # Employ default initializers used in the original paper. if initialW is None: initialW = normal.Normal(0.01) if initial_bias is None: @@ -115,28 +117,28 @@ def __init__(self, pretrained_model=None, n_class=None, super(VGG16Layers, self).__init__() links = { - 'conv1_1': L.Convolution2D(3, 64, 3, 1, 1, **kwargs), - 'conv1_2': L.Convolution2D(64, 64, 3, 1, 1, **kwargs), - 'conv2_1': L.Convolution2D(64, 128, 3, 1, 1, **kwargs), - 'conv2_2': L.Convolution2D(128, 128, 3, 1, 1, **kwargs), - 'conv3_1': L.Convolution2D(128, 256, 3, 1, 1, **kwargs), - 'conv3_2': L.Convolution2D(256, 256, 3, 1, 1, **kwargs), - 'conv3_3': L.Convolution2D(256, 256, 3, 1, 1, **kwargs), - 'conv4_1': L.Convolution2D(256, 512, 3, 1, 1, **kwargs), - 'conv4_2': L.Convolution2D(512, 512, 3, 1, 1, **kwargs), - 'conv4_3': L.Convolution2D(512, 512, 3, 1, 1, **kwargs), - 'conv5_1': L.Convolution2D(512, 512, 3, 1, 1, **kwargs), - 'conv5_2': L.Convolution2D(512, 512, 3, 1, 1, **kwargs), - 'conv5_3': L.Convolution2D(512, 512, 3, 1, 1, **kwargs), - 'fc6': L.Linear(512 * 7 * 7, 4096, **kwargs), - 'fc7': L.Linear(4096, 4096, **kwargs), - 'fc8': L.Linear(4096, n_class, **kwargs) + 'conv1_1': lambda: L.Convolution2D(3, 64, 3, 1, 1, **kwargs), + 'conv1_2': lambda: L.Convolution2D(64, 64, 3, 1, 1, **kwargs), + 'conv2_1': lambda: L.Convolution2D(64, 128, 3, 1, 1, **kwargs), + 'conv2_2': lambda: L.Convolution2D(128, 128, 3, 1, 1, **kwargs), + 'conv3_1': lambda: L.Convolution2D(128, 256, 3, 1, 1, **kwargs), + 'conv3_2': lambda: L.Convolution2D(256, 256, 3, 1, 1, **kwargs), + 'conv3_3': lambda: L.Convolution2D(256, 256, 3, 1, 1, **kwargs), + 'conv4_1': lambda: L.Convolution2D(256, 512, 3, 1, 1, **kwargs), + 'conv4_2': lambda: L.Convolution2D(512, 512, 3, 1, 1, **kwargs), + 'conv4_3': lambda: L.Convolution2D(512, 512, 3, 1, 1, **kwargs), + 'conv5_1': lambda: L.Convolution2D(512, 512, 3, 1, 1, **kwargs), + 'conv5_2': lambda: L.Convolution2D(512, 512, 3, 1, 1, **kwargs), + 'conv5_3': lambda: L.Convolution2D(512, 512, 3, 1, 1, **kwargs), + 'fc6': lambda: L.Linear(512 * 7 * 7, 4096, **kwargs), + 'fc7': lambda: L.Linear(4096, 4096, **kwargs), + 'fc8': lambda: L.Linear(4096, n_class, **kwargs) } with self.init_scope(): - for name, link in links.items(): + for name, gen_link in links.items(): if name in self.functions: - setattr(self, name, link) + setattr(self, name, gen_link()) if pretrained_model in self._models: path = download_model(self._models[pretrained_model]['url']) @@ -177,6 +179,8 @@ def _getattr(name): if feature not in funcs: raise ValueError('Elements of `features` shuold be one of ' '{}.'.format(funcs.keys())) + + # Remove all functions that are not necessary. pop_funcs = False features = list(self._features) for name in funcs.keys(): @@ -230,6 +234,14 @@ def _prepare(self, img): return img + def _average_ten_crop(self, y): + xp = chainer.cuda.get_array_module(y) + n = y.shape[0] // 10 + y_shape = y.shape[1:] + y = y.reshape((n, 10) + y_shape) + y = xp.sum(y, axis=1) / 10 + return y + def predict(self, imgs): """Predict features from images. @@ -256,7 +268,8 @@ def predict(self, imgs): """ if (self.do_ten_crop and - self._feature not in ['fc6', 'fc7', 'fc8', 'prob']): + any([feature not in ['fc6', 'fc7', 'fc8', 'prob'] + for feature in self._features])): raise ValueError imgs = [self._prepare(img) for img in imgs] @@ -273,26 +286,16 @@ def predict(self, imgs): if isinstance(activations, dict): for name, activation in activations.items(): activation = activation.data - if self.do_ten_crop: - activation = self._gather_ten_crop(activation) - activations[name] = cuda.to_cpu(activations) + activation = self._average_ten_crop(activation) + activations[name] = cuda.to_cpu(activation) else: activations = cuda.to_cpu(activations.data) if self.do_ten_crop: - activations = self._gather_ten_crop(activations) + activations = self._average_ten_crop(activations) return activations - def _gather_ten_crop(self, y): - xp = chainer.cuda.get_array_module(y) - n = y.shape[0] // 10 - y_shape = y.shape[1:] - y = y.reshape((n, 10) + y_shape) - y = xp.sum(y, axis=1) / 10 - return y - - def _max_pooling_2d(x): return F.max_pooling_2d(x, ksize=2) diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py index b59049f1bc..92674ff962 100644 --- a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -5,24 +5,21 @@ from chainer.initializers import Zero from chainer import testing from chainer.testing import attr -from chainer.variable import Variable +from chainer import Variable from chainercv.links import VGG16Layers -_zero_init = Zero - - @testing.parameterize( - {'feature': 'prob', 'shape': (1, 200), 'n_class': 200}, - {'feature': 'pool5', 'shape': (1, 512, 7, 7), 'n_class': None}, + {'features': 'prob', 'shape': (1, 200), 'n_class': 200}, + {'features': 'pool5', 'shape': (1, 512, 7, 7), 'n_class': None}, ) @attr.slow class TestVGG16LayersCall(unittest.TestCase): def setUp(self): self.link = VGG16Layers( - pretrained_model=None, n_class=self.n_class, feature=self.feature) + pretrained_model=None, n_class=self.n_class, feature=self.features) def check_call(self): xp = self.link.xp @@ -42,24 +39,34 @@ def test_call_gpu(self): @testing.parameterize( - {'feature': 'prob', 'shape': (2, 1000), 'do_ten_crop': False}, - {'feature': 'prob', 'shape': (2, 1000), 'do_ten_crop': True}, - {'feature': 'conv5_3', 'shape': (2, 512, 14, 14), 'do_ten_crop': False} + {'features': 'prob', 'shape': (2, 1000), 'do_ten_crop': False}, + {'features': 'prob', 'shape': (2, 1000), 'do_ten_crop': True}, + {'features': 'conv5_3', 'shape': (2, 512, 14, 14), 'do_ten_crop': False}, + {'features': ['fc6', 'conv3_1'], + 'shape': {'conv3_1': (2, 256, 56, 56), 'fc6': (2, 4096)}, + 'do_ten_crop': False}, + {'features': ['fc6', 'fc7'], + 'shape': {'fc6': (2, 4096), 'fc7': (2, 4096)}, 'do_ten_crop': True} ) @attr.slow class TestVGG16LayersPredict(unittest.TestCase): def setUp(self): self.link = VGG16Layers(pretrained_model=None, n_class=1000, - feature=self.feature, + features=self.features, do_ten_crop=self.do_ten_crop) def check_predict(self): x1 = np.random.uniform(0, 255, (3, 320, 240)).astype(np.float32) x2 = np.random.uniform(0, 255, (3, 320, 240)).astype(np.float32) - out = self.link.predict([x1, x2]) - self.assertEqual(out.shape, self.shape) - self.assertEqual(out.dtype, np.float32) + activations = self.link.predict([x1, x2]) + if isinstance(activations, dict): + for name in self.features: + self.assertEqual(activations[name].shape, self.shape[name]) + self.assertEqual(activations[name].dtype, np.float32) + else: + self.assertEqual(activations.shape, self.shape) + self.assertEqual(activations.dtype, np.float32) def test_predict_cpu(self): self.check_predict() @@ -74,9 +81,8 @@ class TestVGG16LayersCopy(unittest.TestCase): def setUp(self): self.link = VGG16Layers(pretrained_model=None, n_class=200, - feature='conv2_2', - initialW=Zero(), initial_bias=Zero(), - ) + features='conv2_2', + initialW=Zero(), initial_bias=Zero()) def check_copy(self): copied = self.link.copy() @@ -91,29 +97,29 @@ def test_copy_gpu(self): self.check_copy() +@testing.parameterize( + {'features': 'pool4', + 'not_attribute': ['conv5_1', 'conv5_2', 'conv5_3', 'fc6', 'fc7', 'fc8'], + 'not_in_functions': ['conv5_1', 'conv5_2', 'conv5_3', 'pool5', + 'fc6', 'fc7', 'fc8', 'prob'] + }, + {'features': ['pool5', 'pool4'], + 'not_attribute': ['fc6', 'fc7', 'fc8'], + 'not_in_functions': ['fc6', 'fc7', 'fc8', 'prob'] + } +) class TestVGG16LayersFeatureOption(unittest.TestCase): def setUp(self): - self.link = VGG16Layers(pretrained_model=None, feature='pool4', + self.link = VGG16Layers(pretrained_model=None, features=self.features, initialW=Zero(), initial_bias=Zero()) def check_feature_option(self): - self.assertTrue(not hasattr(self.link, 'conv5_1')) - self.assertTrue(not hasattr(self.link, 'conv5_2')) - self.assertTrue(not hasattr(self.link, 'conv5_3')) - self.assertTrue(not hasattr(self.link, 'fc6')) - self.assertTrue(not hasattr(self.link, 'fc7')) - self.assertTrue(not hasattr(self.link, 'fc8')) - - self.assertFalse('conv5_1' in self.link.functions) - self.assertFalse('conv5_2' in self.link.functions) - self.assertFalse('conv5_3' in self.link.functions) - self.assertFalse('pool5' in self.link.functions) - self.assertFalse('fc6' in self.link.functions) - self.assertFalse('fc7' in self.link.functions) - self.assertFalse('fc8' in self.link.functions) - self.assertFalse('prob' in self.link.functions) - self.assertFalse('fc8' in self.link.functions) + for name in self.not_attribute: + self.assertTrue(not hasattr(self.link, name)) + + for name in self.not_in_functions: + self.assertFalse(name in self.link.functions) def test_feature_option_cpu(self): self.check_feature_option() From ce77737f62ae40ff841db0254d47832e94755cf7 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 16 Jun 2017 11:08:10 +0900 Subject: [PATCH 032/139] cosmetic --- chainercv/links/model/vgg/vgg16.py | 1 - 1 file changed, 1 deletion(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index ff3e05bd5b..f50f83f0f7 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -111,7 +111,6 @@ def __init__(self, pretrained_model=None, n_class=None, initialW = normal.Normal(0.01) if initial_bias is None: initial_bias = constant.Zero() - kwargs = {'initialW': initialW, 'initial_bias': initial_bias} super(VGG16Layers, self).__init__() From 2206fc8cef96282ecff3fa189253f415a5969720 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 16 Jun 2017 11:11:33 +0900 Subject: [PATCH 033/139] change name of a variable --- chainercv/links/model/vgg/vgg16.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index f50f83f0f7..f8f6ece3c0 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -115,7 +115,7 @@ def __init__(self, pretrained_model=None, n_class=None, super(VGG16Layers, self).__init__() - links = { + link_generators = { 'conv1_1': lambda: L.Convolution2D(3, 64, 3, 1, 1, **kwargs), 'conv1_2': lambda: L.Convolution2D(64, 64, 3, 1, 1, **kwargs), 'conv2_1': lambda: L.Convolution2D(64, 128, 3, 1, 1, **kwargs), @@ -133,11 +133,10 @@ def __init__(self, pretrained_model=None, n_class=None, 'fc7': lambda: L.Linear(4096, 4096, **kwargs), 'fc8': lambda: L.Linear(4096, n_class, **kwargs) } - with self.init_scope(): - for name, gen_link in links.items(): + for name, link_gen in link_generators.items(): if name in self.functions: - setattr(self, name, gen_link()) + setattr(self, name, link_gen()) if pretrained_model in self._models: path = download_model(self._models[pretrained_model]['url']) From 64efbc05ca56a94728795ad7a669981a21baab7f Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 16 Jun 2017 11:16:31 +0900 Subject: [PATCH 034/139] fix a bug in conditional --- chainercv/links/model/vgg/vgg16.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index f8f6ece3c0..6f64d7c9bf 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -75,7 +75,7 @@ class VGG16Layers(chainer.Chain): def __init__(self, pretrained_model=None, n_class=None, features='prob', initialW=None, initial_bias=None, mean=_imagenet_mean, do_ten_crop=False): - if isinstance(features, list or tuple): + if isinstance(features, (list, tuple)): return_dict = True else: return_dict = False From ab2cfee0406eb9cdd0487655bab00b9864a3dbe3 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 16 Jun 2017 11:17:56 +0900 Subject: [PATCH 035/139] consistency in variable names --- chainercv/links/model/vgg/vgg16.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 6f64d7c9bf..d5afd0992b 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -173,8 +173,8 @@ def _getattr(name): ('fc8', [_getattr('fc8')]), ('prob', [F.softmax]), ]) - for feature in self._features: - if feature not in funcs: + for name in self._features: + if name not in funcs: raise ValueError('Elements of `features` shuold be one of ' '{}.'.format(funcs.keys())) From 3caec05bcee9e9952df3db6ee131773e59b0ce30 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 16 Jun 2017 12:12:11 +0900 Subject: [PATCH 036/139] change api to return tuple instead of dict --- chainercv/links/model/vgg/vgg16.py | 25 +++++++----- .../model_tests/vgg_tests/test_vgg16.py | 38 +++++++++++-------- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index d5afd0992b..1e95834be9 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -76,12 +76,12 @@ def __init__(self, pretrained_model=None, n_class=None, features='prob', initialW=None, initial_bias=None, mean=_imagenet_mean, do_ten_crop=False): if isinstance(features, (list, tuple)): - return_dict = True + return_tuple = True else: - return_dict = False + return_tuple = False features = [features] - self._return_dict = return_dict + self._return_tuple = return_tuple self._features = features self.mean = mean self.do_ten_crop = do_ten_crop @@ -211,7 +211,10 @@ def __call__(self, x): if name in self._features: activations[name] = h - if not self._return_dict: + if self._return_tuple: + activations = tuple( + [activations[name] for name in activations.keys()]) + else: activations = activations.values()[0] return activations @@ -281,18 +284,20 @@ def predict(self, imgs): imgs = chainer.Variable(imgs) activations = self(imgs) - if isinstance(activations, dict): - for name, activation in activations.items(): + if isinstance(activations, tuple): + output = [] + for activation in activations: activation = activation.data if self.do_ten_crop: activation = self._average_ten_crop(activation) - activations[name] = cuda.to_cpu(activation) + output.append(cuda.to_cpu(activation)) + output = tuple(output) else: - activations = cuda.to_cpu(activations.data) + output = cuda.to_cpu(activations.data) if self.do_ten_crop: - activations = self._average_ten_crop(activations) + output = self._average_ten_crop(output) - return activations + return output def _max_pooling_2d(x): diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py index 92674ff962..f777a706fa 100644 --- a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -11,23 +11,31 @@ @testing.parameterize( - {'features': 'prob', 'shape': (1, 200), 'n_class': 200}, - {'features': 'pool5', 'shape': (1, 512, 7, 7), 'n_class': None}, + {'features': 'prob', 'shapes': (1, 200), 'n_class': 200}, + {'features': 'pool5', 'shapes': (1, 512, 7, 7), 'n_class': None}, + {'features': ['conv5_3', 'conv4_2'], + 'shapes': ((1, 512, 14, 14), (1, 512, 28, 28)), 'n_class': None}, ) @attr.slow class TestVGG16LayersCall(unittest.TestCase): def setUp(self): self.link = VGG16Layers( - pretrained_model=None, n_class=self.n_class, feature=self.features) + pretrained_model=None, n_class=self.n_class, + features=self.features) def check_call(self): xp = self.link.xp x1 = Variable(xp.asarray(np.random.uniform( -1, 1, (1, 3, 224, 224)).astype(np.float32))) - y1 = self.link(x1) - self.assertEqual(y1.shape, self.shape) + activations = self.link(x1) + if isinstance(activations, tuple): + for activation, shape in zip(activations, self.shapes): + self.assertEqual(activation.shape, shape) + else: + self.assertEqual(activations.shape, self.shapes) + self.assertEqual(activations.dtype, np.float32) def test_call_cpu(self): self.check_call() @@ -39,14 +47,13 @@ def test_call_gpu(self): @testing.parameterize( - {'features': 'prob', 'shape': (2, 1000), 'do_ten_crop': False}, - {'features': 'prob', 'shape': (2, 1000), 'do_ten_crop': True}, - {'features': 'conv5_3', 'shape': (2, 512, 14, 14), 'do_ten_crop': False}, + {'features': 'prob', 'shapes': (2, 1000), 'do_ten_crop': False}, + {'features': 'prob', 'shapes': (2, 1000), 'do_ten_crop': True}, + {'features': 'conv5_3', 'shapes': (2, 512, 14, 14), 'do_ten_crop': False}, {'features': ['fc6', 'conv3_1'], - 'shape': {'conv3_1': (2, 256, 56, 56), 'fc6': (2, 4096)}, - 'do_ten_crop': False}, + 'shapes': ((2, 4096), (2, 256, 56, 56)), 'do_ten_crop': False}, {'features': ['fc6', 'fc7'], - 'shape': {'fc6': (2, 4096), 'fc7': (2, 4096)}, 'do_ten_crop': True} + 'shapes': ((2, 4096), (2, 4096)), 'do_ten_crop': True} ) @attr.slow class TestVGG16LayersPredict(unittest.TestCase): @@ -60,12 +67,11 @@ def check_predict(self): x1 = np.random.uniform(0, 255, (3, 320, 240)).astype(np.float32) x2 = np.random.uniform(0, 255, (3, 320, 240)).astype(np.float32) activations = self.link.predict([x1, x2]) - if isinstance(activations, dict): - for name in self.features: - self.assertEqual(activations[name].shape, self.shape[name]) - self.assertEqual(activations[name].dtype, np.float32) + if isinstance(activations, tuple): + for activation, shape in zip(activations, self.shapes): + self.assertEqual(activation.shape, shape) else: - self.assertEqual(activations.shape, self.shape) + self.assertEqual(activations.shape, self.shapes) self.assertEqual(activations.dtype, np.float32) def test_predict_cpu(self): From 7b2d2148f2628d5b7f186bfbed5ce498c753fe7f Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 16 Jun 2017 12:26:53 +0900 Subject: [PATCH 037/139] fix doc --- chainercv/links/model/vgg/vgg16.py | 35 ++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 1e95834be9..17d177b984 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -30,8 +30,21 @@ class VGG16Layers(chainer.Chain): This model is a feature extraction link. The network can choose to output features from set of all intermediate and final features produced by the original architecture. + The output features can be an array or tuple of arrays. + When :obj:`features` is an iterable of strings, outputs will be tuple. + When :obj:`features` is a string, output will be an array. - When :obj:`pretrained_model` is thepath of a pre-trained chainer model + Examples: + + >>> model = VGG16Layers(features='conv5_3') + # This is an activation of conv5_3 layer. + >>> feat = model(imgs) + + >>> model = VGG16Layers(features=['conv5_3', 'fc6']) + >>> # These are activations of conv5_3 and fc6 layers respectively. + >>> feat1, feat2 = model(imgs) + + When :obj:`pretrained_model` is the path of a pre-trained chainer model serialized as a :obj:`.npz` file in the constructor, this chain model automatically initializes all the parameters with it. When a string in the prespecified set is provided, a pretrained model is @@ -52,8 +65,8 @@ class VGG16Layers(chainer.Chain): :obj:`$HOME/.chainer/dataset` unless you specify another value by modifying the environment variable. n_class (int): The dimension of the output of fc8. - feature (str or iterable of strings): The name of the feature to output - with :meth:`__call__` and :meth:`predict`. + features (str or iterable of strings): The names of the feature to + output with :meth:`__call__` and :meth:`predict`. initialW (callable): Initializer for the weights. initial_bias (callable): Initializer for the biases. mean (numpy.ndarray): A value to be subtracted from an image @@ -75,14 +88,14 @@ class VGG16Layers(chainer.Chain): def __init__(self, pretrained_model=None, n_class=None, features='prob', initialW=None, initial_bias=None, mean=_imagenet_mean, do_ten_crop=False): - if isinstance(features, (list, tuple)): + if all([isinstance(feature, str) for feature in features]): return_tuple = True else: return_tuple = False features = [features] - self._return_tuple = return_tuple self._features = features + self.mean = mean self.do_ten_crop = do_ten_crop @@ -199,8 +212,10 @@ def __call__(self, x): x (~chainer.Variable): Batch of image variables. Returns: - ~chainer.Variable: - A batch of features. It is selected by :obj:`self._feature`. + Variable or tuple of Variable: + A batch of features or tuple of them. + The features to output are selected by :obj:`features` option + of :meth:`__init__`. """ activations = {} @@ -264,8 +279,10 @@ def predict(self, imgs): and the range of their value is :math:`[0, 255]`. Returns: - numpy.ndarray: - A batch of features. It is selected by :obj:`self._feature`. + Variable or tuple of Variable: + A batch of features or tuple of them. + The features to output are selected by :obj:`features` option + of :meth:`__init__`. """ if (self.do_ten_crop and From 96e285cffc5e65bbff68cf360bf36cdf53798d24 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 16 Jun 2017 12:53:26 +0900 Subject: [PATCH 038/139] fix faster_rcnn_vgg --- chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py index 58c8761c0d..3dfc806794 100644 --- a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py +++ b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py @@ -102,7 +102,7 @@ def __init__(self, if vgg_initialW is None and pretrained_model: vgg_initialW = chainer.initializers.constant.Zero() - extractor = VGG16Layers(feature='conv5_3', initialW=vgg_initialW) + extractor = VGG16Layers(features='conv5_3', initialW=vgg_initialW) rpn = RegionProposalNetwork( 512, 512, ratios=ratios, From b0b070c0f640fd23607c9c9c645a5b7b24052f61 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 16 Jun 2017 12:53:30 +0900 Subject: [PATCH 039/139] fix doc --- chainercv/links/model/vgg/vgg16.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 17d177b984..fe636538a0 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -206,7 +206,7 @@ def _getattr(name): return funcs def __call__(self, x): - """Fowrard VGG16. + """Forward VGG16. Args: x (~chainer.Variable): Batch of image variables. From a1d692780678553fac79e0ea6acf0280247f956f Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 16 Jun 2017 13:00:34 +0900 Subject: [PATCH 040/139] fix init --- chainercv/links/model/vgg/vgg16.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index fe636538a0..d96721cd74 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -88,7 +88,8 @@ class VGG16Layers(chainer.Chain): def __init__(self, pretrained_model=None, n_class=None, features='prob', initialW=None, initial_bias=None, mean=_imagenet_mean, do_ten_crop=False): - if all([isinstance(feature, str) for feature in features]): + if (not isinstance(features, str) and + all([isinstance(feature, str) for feature in features])): return_tuple = True else: return_tuple = False From 338819abe84c1352457e48b611ba085f9f0e9891 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 16 Jun 2017 13:00:41 +0900 Subject: [PATCH 041/139] simplify functions --- chainercv/links/model/vgg/vgg16.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index d96721cd74..da559e8b7f 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -187,10 +187,9 @@ def _getattr(name): ('fc8', [_getattr('fc8')]), ('prob', [F.softmax]), ]) - for name in self._features: - if name not in funcs: - raise ValueError('Elements of `features` shuold be one of ' - '{}.'.format(funcs.keys())) + if any([name not in funcs for name in self._features]): + raise ValueError('Elements of `features` shuold be one of ' + '{}.'.format(funcs.keys())) # Remove all functions that are not necessary. pop_funcs = False From 700c5f4e8a099101b589c9c83a9f6db0c9f99ef9 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 16 Jun 2017 14:53:07 +0900 Subject: [PATCH 042/139] fix links.rst --- docs/source/reference/links.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/source/reference/links.rst b/docs/source/reference/links.rst index 5bd6c2eb39..763b6ffaa5 100644 --- a/docs/source/reference/links.rst +++ b/docs/source/reference/links.rst @@ -4,16 +4,17 @@ Links .. module:: chainercv.links.model.faster_rcnn -Classification --------------- +Feature Extraction +------------------ -Classification links share a common method :meth:`predict` to classify or extract features with images. +Feature extraction links share a common method :meth:`predict` to extract features from images. For more details, please read :func:`VGG16Layers.predict`. .. toctree:: links/vgg + Detection --------- From 5b08bd7a6514c08897e815597db89a7ff9bd42a7 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 16 Jun 2017 21:59:36 +0900 Subject: [PATCH 043/139] fix eval_imagenet --- examples/classification/eval_imagenet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index e387a948aa..e82924b4d3 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -8,7 +8,7 @@ import chainer.functions as F from chainer import iterators -from chainercv.datasets import ImageFolderDataset +from chainercv.datasets import DirectoryParsingClassificationDataset from chainercv.links import VGG16Layers from chainercv.utils import apply_prediction_to_iterator @@ -43,7 +43,7 @@ def main(): parser.add_argument('--batchsize', type=int, default=32) args = parser.parse_args() - dataset = ImageFolderDataset(args.val) + dataset = DirectoryParsingClassificationDataset(args.val) iterator = iterators.MultiprocessIterator( dataset, args.batchsize, repeat=False, shuffle=False, n_processes=6, shared_mem=300000000) From 6a2b43c9318a0b1ac52beb75aa86d207f70c05d9 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Sat, 17 Jun 2017 18:21:53 +0900 Subject: [PATCH 044/139] fix vgg for python3 --- chainercv/links/model/vgg/vgg16.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index da559e8b7f..951491daf6 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -194,7 +194,7 @@ def _getattr(name): # Remove all functions that are not necessary. pop_funcs = False features = list(self._features) - for name in funcs.keys(): + for name in list(funcs.keys()): if pop_funcs: funcs.pop(name) @@ -230,7 +230,7 @@ def __call__(self, x): activations = tuple( [activations[name] for name in activations.keys()]) else: - activations = activations.values()[0] + activations = list(activations.values())[0] return activations def _prepare(self, img): From 724dcb5b6bff305e6500edc1edb6ed2bd2d87fc5 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 19 Jun 2017 09:08:45 +0900 Subject: [PATCH 045/139] remove predict and use SequentialFeatureExtractionChain --- .../sequential_feature_extraction_chain.py | 78 ++++++++ chainercv/links/model/vgg/vgg16.py | 169 +----------------- .../model_tests/vgg_tests/test_vgg16.py | 72 ++------ 3 files changed, 100 insertions(+), 219 deletions(-) create mode 100644 chainercv/links/model/sequential_feature_extraction_chain.py diff --git a/chainercv/links/model/sequential_feature_extraction_chain.py b/chainercv/links/model/sequential_feature_extraction_chain.py new file mode 100644 index 0000000000..e5442a6f04 --- /dev/null +++ b/chainercv/links/model/sequential_feature_extraction_chain.py @@ -0,0 +1,78 @@ +import copy + +import chainer + + +class SequentialFeatureExtractionChain(chainer.Chain): + + def __init__(self, feature_names, link_generators): + if (not isinstance(feature_names, str) and + all([isinstance(feature, str) for feature in feature_names])): + return_tuple = True + else: + return_tuple = False + feature_names = [feature_names] + self._return_tuple = return_tuple + self._feature_names = feature_names + + super(SequentialFeatureExtractionChain, self).__init__() + + if any([name not in self.functions for + name in self._feature_names]): + raise ValueError('Elements of `feature_names` shuold be one of ' + '{}.'.format(self.functions.keys())) + + # Remove all functions that are not necessary. + self._unused_function_names = [] + pop_funcs = False + features = list(self._feature_names) + for name in self.functions.keys(): + if pop_funcs: + self._unused_function_names.append(name) + + if name in features: + features.remove(name) + if len(features) == 0: + pop_funcs = True + + with self.init_scope(): + for name, link_gen in link_generators.items(): + # Ignore layers whose names match functions that are removed. + if name not in self._unused_function_names: + setattr(self, name, link_gen()) + + @property + def functions(self): + raise NotImplementedError + + def __call__(self, x): + """Forward the model. + + Args: + x (~chainer.Variable): Batch of image variables. + + Returns: + Variable or tuple of Variable: + A batch of features or tuple of batched features. + The returned features are selected by :obj:`feature_names` that + is passed to :meth:`__init__`. + + """ + functions = copy.copy(self.functions) + for name in self._unused_function_names: + functions.pop(name) + + features = {} + h = x + for name, funcs in functions.items(): + for func in funcs: + h = func(h) + if name in self._feature_names: + features[name] = h + + if self._return_tuple: + features = tuple( + [features[name] for name in features.keys()]) + else: + features = list(features.values())[0] + return features diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 951491daf6..63e1d8bdb7 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -2,28 +2,19 @@ import collections -import numpy as np - import chainer -from chainer import cuda import chainer.functions as F from chainer.initializers import constant from chainer.initializers import normal import chainer.links as L -from chainercv.transforms import center_crop -from chainercv.transforms import scale -from chainercv.transforms import ten_crop - from chainercv.utils import download_model - -# RGB order -_imagenet_mean = np.array( - [123.68, 116.779, 103.939], dtype=np.float32)[:, np.newaxis, np.newaxis] +from chainercv.links.model.sequential_feature_extraction_chain import \ + SequentialFeatureExtractionChain -class VGG16Layers(chainer.Chain): +class VGG16Layers(SequentialFeatureExtractionChain): """VGG16 Network for classification and feature extraction. @@ -86,24 +77,11 @@ class VGG16Layers(chainer.Chain): } def __init__(self, pretrained_model=None, n_class=None, - features='prob', initialW=None, initial_bias=None, - mean=_imagenet_mean, do_ten_crop=False): - if (not isinstance(features, str) and - all([isinstance(feature, str) for feature in features])): - return_tuple = True - else: - return_tuple = False - features = [features] - self._return_tuple = return_tuple - self._features = features - - self.mean = mean - self.do_ten_crop = do_ten_crop - + feature_names='prob', initialW=None, initial_bias=None): if n_class is None: if (pretrained_model is None and all([feature not in ['fc8', 'prob'] - for feature in features])): + for feature in feature_names])): # fc8 layer is not used in this case. pass elif pretrained_model not in self._models: @@ -127,8 +105,6 @@ def __init__(self, pretrained_model=None, n_class=None, initial_bias = constant.Zero() kwargs = {'initialW': initialW, 'initial_bias': initial_bias} - super(VGG16Layers, self).__init__() - link_generators = { 'conv1_1': lambda: L.Convolution2D(3, 64, 3, 1, 1, **kwargs), 'conv1_2': lambda: L.Convolution2D(64, 64, 3, 1, 1, **kwargs), @@ -147,10 +123,7 @@ def __init__(self, pretrained_model=None, n_class=None, 'fc7': lambda: L.Linear(4096, 4096, **kwargs), 'fc8': lambda: L.Linear(4096, n_class, **kwargs) } - with self.init_scope(): - for name, link_gen in link_generators.items(): - if name in self.functions: - setattr(self, name, link_gen()) + super(VGG16Layers, self).__init__(feature_names, link_generators) if pretrained_model in self._models: path = download_model(self._models[pretrained_model]['url']) @@ -163,7 +136,7 @@ def functions(self): def _getattr(name): return getattr(self, name, None) - funcs = collections.OrderedDict([ + return collections.OrderedDict([ ('conv1_1', [_getattr('conv1_1'), F.relu]), ('conv1_2', [_getattr('conv1_2'), F.relu]), ('pool1', [_max_pooling_2d]), @@ -187,134 +160,6 @@ def _getattr(name): ('fc8', [_getattr('fc8')]), ('prob', [F.softmax]), ]) - if any([name not in funcs for name in self._features]): - raise ValueError('Elements of `features` shuold be one of ' - '{}.'.format(funcs.keys())) - - # Remove all functions that are not necessary. - pop_funcs = False - features = list(self._features) - for name in list(funcs.keys()): - if pop_funcs: - funcs.pop(name) - - if name in features: - features.remove(name) - if len(features) == 0: - pop_funcs = True - - return funcs - - def __call__(self, x): - """Forward VGG16. - - Args: - x (~chainer.Variable): Batch of image variables. - - Returns: - Variable or tuple of Variable: - A batch of features or tuple of them. - The features to output are selected by :obj:`features` option - of :meth:`__init__`. - - """ - activations = {} - h = x - for name, funcs in self.functions.items(): - for func in funcs: - h = func(h) - if name in self._features: - activations[name] = h - - if self._return_tuple: - activations = tuple( - [activations[name] for name in activations.keys()]) - else: - activations = list(activations.values())[0] - return activations - - def _prepare(self, img): - """Transform an image to the input for VGG network. - - Args: - img (~numpy.ndarray): An image. This is in CHW and RGB format. - The range of its value is :math:`[0, 255]`. - - Returns: - ~numpy.ndarray: - A preprocessed image. - - """ - img = scale(img, size=256) - img = img - self.mean - - return img - - def _average_ten_crop(self, y): - xp = chainer.cuda.get_array_module(y) - n = y.shape[0] // 10 - y_shape = y.shape[1:] - y = y.reshape((n, 10) + y_shape) - y = xp.sum(y, axis=1) / 10 - return y - - def predict(self, imgs): - """Predict features from images. - - When :obj:`self.do_ten_crop == True`, this extracts features from - patches that are ten-cropped from images. - Otherwise, this extracts features from center-crop of the images. - - When using patches from ten-crop, the features for each crop - is averaged to compute one feature. - Ten-crop mode is only supported for calculation of features - :math:`fc6, fc7, fc8, prob`. - - Given :math:`N` input images, this outputs a batched array with - batchsize :math:`N`. - - Args: - imgs (iterable of numpy.ndarray): Array-images. - All images are in CHW and RGB format - and the range of their value is :math:`[0, 255]`. - - Returns: - Variable or tuple of Variable: - A batch of features or tuple of them. - The features to output are selected by :obj:`features` option - of :meth:`__init__`. - - """ - if (self.do_ten_crop and - any([feature not in ['fc6', 'fc7', 'fc8', 'prob'] - for feature in self._features])): - raise ValueError - - imgs = [self._prepare(img) for img in imgs] - if self.do_ten_crop: - imgs = [ten_crop(img, (224, 224)) for img in imgs] - else: - imgs = [center_crop(img, (224, 224)) for img in imgs] - imgs = self.xp.asarray(imgs).reshape(-1, 3, 224, 224) - - with chainer.function.no_backprop_mode(): - imgs = chainer.Variable(imgs) - activations = self(imgs) - - if isinstance(activations, tuple): - output = [] - for activation in activations: - activation = activation.data - if self.do_ten_crop: - activation = self._average_ten_crop(activation) - output.append(cuda.to_cpu(activation)) - output = tuple(output) - else: - output = cuda.to_cpu(activations.data) - if self.do_ten_crop: - output = self._average_ten_crop(output) - - return output def _max_pooling_2d(x): diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py index f777a706fa..2851c27f62 100644 --- a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -11,9 +11,9 @@ @testing.parameterize( - {'features': 'prob', 'shapes': (1, 200), 'n_class': 200}, - {'features': 'pool5', 'shapes': (1, 512, 7, 7), 'n_class': None}, - {'features': ['conv5_3', 'conv4_2'], + {'feature_names': 'prob', 'shapes': (1, 200), 'n_class': 200}, + {'feature_names': 'pool5', 'shapes': (1, 512, 7, 7), 'n_class': None}, + {'feature_names': ['conv5_3', 'conv4_2'], 'shapes': ((1, 512, 14, 14), (1, 512, 28, 28)), 'n_class': None}, ) @attr.slow @@ -22,20 +22,20 @@ class TestVGG16LayersCall(unittest.TestCase): def setUp(self): self.link = VGG16Layers( pretrained_model=None, n_class=self.n_class, - features=self.features) + feature_names=self.feature_names) def check_call(self): xp = self.link.xp x1 = Variable(xp.asarray(np.random.uniform( -1, 1, (1, 3, 224, 224)).astype(np.float32))) - activations = self.link(x1) - if isinstance(activations, tuple): - for activation, shape in zip(activations, self.shapes): + features = self.link(x1) + if isinstance(features, tuple): + for activation, shape in zip(features, self.shapes): self.assertEqual(activation.shape, shape) else: - self.assertEqual(activations.shape, self.shapes) - self.assertEqual(activations.dtype, np.float32) + self.assertEqual(features.shape, self.shapes) + self.assertEqual(features.dtype, np.float32) def test_call_cpu(self): self.check_call() @@ -46,48 +46,11 @@ def test_call_gpu(self): self.check_call() -@testing.parameterize( - {'features': 'prob', 'shapes': (2, 1000), 'do_ten_crop': False}, - {'features': 'prob', 'shapes': (2, 1000), 'do_ten_crop': True}, - {'features': 'conv5_3', 'shapes': (2, 512, 14, 14), 'do_ten_crop': False}, - {'features': ['fc6', 'conv3_1'], - 'shapes': ((2, 4096), (2, 256, 56, 56)), 'do_ten_crop': False}, - {'features': ['fc6', 'fc7'], - 'shapes': ((2, 4096), (2, 4096)), 'do_ten_crop': True} -) -@attr.slow -class TestVGG16LayersPredict(unittest.TestCase): - - def setUp(self): - self.link = VGG16Layers(pretrained_model=None, n_class=1000, - features=self.features, - do_ten_crop=self.do_ten_crop) - - def check_predict(self): - x1 = np.random.uniform(0, 255, (3, 320, 240)).astype(np.float32) - x2 = np.random.uniform(0, 255, (3, 320, 240)).astype(np.float32) - activations = self.link.predict([x1, x2]) - if isinstance(activations, tuple): - for activation, shape in zip(activations, self.shapes): - self.assertEqual(activation.shape, shape) - else: - self.assertEqual(activations.shape, self.shapes) - self.assertEqual(activations.dtype, np.float32) - - def test_predict_cpu(self): - self.check_predict() - - @attr.gpu - def test_predict_gpu(self): - self.link.to_gpu() - self.check_predict() - - class TestVGG16LayersCopy(unittest.TestCase): def setUp(self): self.link = VGG16Layers(pretrained_model=None, n_class=200, - features='conv2_2', + feature_names='conv2_2', initialW=Zero(), initial_bias=Zero()) def check_copy(self): @@ -104,29 +67,24 @@ def test_copy_gpu(self): @testing.parameterize( - {'features': 'pool4', + {'feature_names': 'pool4', 'not_attribute': ['conv5_1', 'conv5_2', 'conv5_3', 'fc6', 'fc7', 'fc8'], - 'not_in_functions': ['conv5_1', 'conv5_2', 'conv5_3', 'pool5', - 'fc6', 'fc7', 'fc8', 'prob'] }, - {'features': ['pool5', 'pool4'], + {'feature_names': ['pool5', 'pool4'], 'not_attribute': ['fc6', 'fc7', 'fc8'], - 'not_in_functions': ['fc6', 'fc7', 'fc8', 'prob'] } ) class TestVGG16LayersFeatureOption(unittest.TestCase): def setUp(self): - self.link = VGG16Layers(pretrained_model=None, features=self.features, - initialW=Zero(), initial_bias=Zero()) + self.link = VGG16Layers( + pretrained_model=None, feature_names=self.feature_names, + initialW=Zero(), initial_bias=Zero()) def check_feature_option(self): for name in self.not_attribute: self.assertTrue(not hasattr(self.link, name)) - for name in self.not_in_functions: - self.assertFalse(name in self.link.functions) - def test_feature_option_cpu(self): self.check_feature_option() From 49bee22f06bc1f15a4ec2dd866b684338c8955b4 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 19 Jun 2017 09:28:36 +0900 Subject: [PATCH 046/139] exploit the fact that functions is ordered dict --- .../sequential_feature_extraction_chain.py | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/chainercv/links/model/sequential_feature_extraction_chain.py b/chainercv/links/model/sequential_feature_extraction_chain.py index e5442a6f04..246d3efa67 100644 --- a/chainercv/links/model/sequential_feature_extraction_chain.py +++ b/chainercv/links/model/sequential_feature_extraction_chain.py @@ -1,44 +1,39 @@ -import copy - import chainer class SequentialFeatureExtractionChain(chainer.Chain): def __init__(self, feature_names, link_generators): + super(SequentialFeatureExtractionChain, self).__init__() + if (not isinstance(feature_names, str) and - all([isinstance(feature, str) for feature in feature_names])): + all([isinstance(name, str) for name in feature_names])): return_tuple = True else: return_tuple = False feature_names = [feature_names] self._return_tuple = return_tuple - self._feature_names = feature_names - - super(SequentialFeatureExtractionChain, self).__init__() - + self._feature_names = list(feature_names) if any([name not in self.functions for name in self._feature_names]): raise ValueError('Elements of `feature_names` shuold be one of ' '{}.'.format(self.functions.keys())) - # Remove all functions that are not necessary. - self._unused_function_names = [] - pop_funcs = False - features = list(self._feature_names) + # Collect functions that are not going to be used. + unused_function_names = [] + unused = False for name in self.functions.keys(): - if pop_funcs: - self._unused_function_names.append(name) - - if name in features: - features.remove(name) - if len(features) == 0: - pop_funcs = True + if unused: + unused_function_names.append(name) + if name in feature_names: + feature_names.remove(name) + if len(feature_names) == 0: + unused = True with self.init_scope(): for name, link_gen in link_generators.items(): # Ignore layers whose names match functions that are removed. - if name not in self._unused_function_names: + if name not in unused_function_names: setattr(self, name, link_gen()) @property @@ -58,17 +53,18 @@ def __call__(self, x): is passed to :meth:`__init__`. """ - functions = copy.copy(self.functions) - for name in self._unused_function_names: - functions.pop(name) + feature_names = list(self._feature_names) features = {} h = x - for name, funcs in functions.items(): + for name, funcs in self.functions.items(): + if len(feature_names) == 0: + break for func in funcs: h = func(h) - if name in self._feature_names: + if name in feature_names: features[name] = h + feature_names.remove(name) if self._return_tuple: features = tuple( From 5af7506910bafa92694537f21dabf6fb718a07bc Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 19 Jun 2017 09:30:43 +0900 Subject: [PATCH 047/139] VGG16Layers -> VGG16 --- chainercv/links/__init__.py | 2 +- .../links/model/faster_rcnn/faster_rcnn_vgg.py | 6 +++--- chainercv/links/model/vgg/__init__.py | 2 +- chainercv/links/model/vgg/vgg16.py | 8 ++++---- docs/source/reference/links/vgg.rst | 6 +++--- .../model_tests/vgg_tests/test_vgg16.py | 14 +++++++------- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/chainercv/links/__init__.py b/chainercv/links/__init__.py index e130a489d9..bd3f608a07 100644 --- a/chainercv/links/__init__.py +++ b/chainercv/links/__init__.py @@ -4,4 +4,4 @@ from chainercv.links.model.segnet.segnet_basic import SegNetBasic # NOQA from chainercv.links.model.ssd import SSD300 # NOQA from chainercv.links.model.ssd import SSD512 # NOQA -from chainercv.links.model.vgg.vgg16 import VGG16Layers # NOQA +from chainercv.links.model.vgg import VGG16 # NOQA diff --git a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py index 3dfc806794..703f171d31 100644 --- a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py +++ b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py @@ -7,7 +7,7 @@ from chainercv.links.model.faster_rcnn.faster_rcnn import FasterRCNN from chainercv.links.model.faster_rcnn.region_proposal_network import \ RegionProposalNetwork -from chainercv.links.model.vgg.vgg16 import VGG16Layers +from chainercv.links.model.vgg.vgg16 import VGG16 from chainercv.utils import download_model @@ -102,7 +102,7 @@ def __init__(self, if vgg_initialW is None and pretrained_model: vgg_initialW = chainer.initializers.constant.Zero() - extractor = VGG16Layers(features='conv5_3', initialW=vgg_initialW) + extractor = VGG16(feature_names='conv5_3', initialW=vgg_initialW) rpn = RegionProposalNetwork( 512, 512, ratios=ratios, @@ -138,7 +138,7 @@ def __init__(self, chainer.serializers.load_npz(pretrained_model, self) def _copy_imagenet_pretrained_vgg16(self): - pretrained_model = VGG16Layers(pretrained_model='imagenet') + pretrained_model = VGG16(pretrained_model='imagenet') self.extractor.conv1_1.copyparams(pretrained_model.conv1_1) self.extractor.conv1_2.copyparams(pretrained_model.conv1_2) self.extractor.conv2_1.copyparams(pretrained_model.conv2_1) diff --git a/chainercv/links/model/vgg/__init__.py b/chainercv/links/model/vgg/__init__.py index 1d0ebf7a3b..40faafabb1 100644 --- a/chainercv/links/model/vgg/__init__.py +++ b/chainercv/links/model/vgg/__init__.py @@ -1 +1 @@ -from chainercv.links.model.vgg.vgg16 import VGG16Layers # NOQA +from chainercv.links.model.vgg.vgg16 import VGG16 # NOQA diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 63e1d8bdb7..a7b11551c9 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -14,7 +14,7 @@ SequentialFeatureExtractionChain -class VGG16Layers(SequentialFeatureExtractionChain): +class VGG16(SequentialFeatureExtractionChain): """VGG16 Network for classification and feature extraction. @@ -27,11 +27,11 @@ class VGG16Layers(SequentialFeatureExtractionChain): Examples: - >>> model = VGG16Layers(features='conv5_3') + >>> model = VGG16(features='conv5_3') # This is an activation of conv5_3 layer. >>> feat = model(imgs) - >>> model = VGG16Layers(features=['conv5_3', 'fc6']) + >>> model = VGG16(features=['conv5_3', 'fc6']) >>> # These are activations of conv5_3 and fc6 layers respectively. >>> feat1, feat2 = model(imgs) @@ -123,7 +123,7 @@ def __init__(self, pretrained_model=None, n_class=None, 'fc7': lambda: L.Linear(4096, 4096, **kwargs), 'fc8': lambda: L.Linear(4096, n_class, **kwargs) } - super(VGG16Layers, self).__init__(feature_names, link_generators) + super(VGG16, self).__init__(feature_names, link_generators) if pretrained_model in self._models: path = download_model(self._models[pretrained_model]['url']) diff --git a/docs/source/reference/links/vgg.rst b/docs/source/reference/links/vgg.rst index d6d0c45aec..170aae5e6a 100644 --- a/docs/source/reference/links/vgg.rst +++ b/docs/source/reference/links/vgg.rst @@ -4,9 +4,9 @@ VGG .. module:: chainercv.links.model.vgg -VGG16Layers ------------ +VGG16 +----- -.. autoclass:: VGG16Layers +.. autoclass:: VGG16 :members: :special-members: __call__ diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py index 2851c27f62..5700613372 100644 --- a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -7,7 +7,7 @@ from chainer.testing import attr from chainer import Variable -from chainercv.links import VGG16Layers +from chainercv.links import VGG16 @testing.parameterize( @@ -17,10 +17,10 @@ 'shapes': ((1, 512, 14, 14), (1, 512, 28, 28)), 'n_class': None}, ) @attr.slow -class TestVGG16LayersCall(unittest.TestCase): +class TestVGG16Call(unittest.TestCase): def setUp(self): - self.link = VGG16Layers( + self.link = VGG16( pretrained_model=None, n_class=self.n_class, feature_names=self.feature_names) @@ -46,10 +46,10 @@ def test_call_gpu(self): self.check_call() -class TestVGG16LayersCopy(unittest.TestCase): +class TestVGG16Copy(unittest.TestCase): def setUp(self): - self.link = VGG16Layers(pretrained_model=None, n_class=200, + self.link = VGG16(pretrained_model=None, n_class=200, feature_names='conv2_2', initialW=Zero(), initial_bias=Zero()) @@ -74,10 +74,10 @@ def test_copy_gpu(self): 'not_attribute': ['fc6', 'fc7', 'fc8'], } ) -class TestVGG16LayersFeatureOption(unittest.TestCase): +class TestVGG16FeatureOption(unittest.TestCase): def setUp(self): - self.link = VGG16Layers( + self.link = VGG16( pretrained_model=None, feature_names=self.feature_names, initialW=Zero(), initial_bias=Zero()) From aa18ad7cc4ba0226a6d4d99a210767d6d369f79d Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 19 Jun 2017 10:06:47 +0900 Subject: [PATCH 048/139] add feature_extraction_predictor --- .../model/feature_extraction_predictor.py | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 chainercv/links/model/feature_extraction_predictor.py diff --git a/chainercv/links/model/feature_extraction_predictor.py b/chainercv/links/model/feature_extraction_predictor.py new file mode 100644 index 0000000000..e1835e2b1e --- /dev/null +++ b/chainercv/links/model/feature_extraction_predictor.py @@ -0,0 +1,113 @@ +import numpy as np +import warnings + +import chainer +from chainer import cuda + +from chainercv.transforms import center_crop +from chainercv.transforms import scale +from chainercv.transforms import ten_crop + + +# RGB order +_imagenet_mean = np.array( + [123.68, 116.779, 103.939], dtype=np.float32)[:, np.newaxis, np.newaxis] + + +class FeatureExtractionPredictor(chainer.Chain): + + def __init__(self, extractor, mean=_imagenet_mean, + size=(224, 224), scale_size=256, + do_ten_crop=False): + self.mean = mean + self.scale_size = scale_size + self.size = size + self.do_ten_crop = do_ten_crop + + with self.init_scope(): + self.extractor = extractor + + def _prepare(self, img): + """Transform an image to the input for VGG network. + + Args: + img (~numpy.ndarray): An image. This is in CHW and RGB format. + The range of its value is :math:`[0, 255]`. + + Returns: + ~numpy.ndarray: + A preprocessed image. + + """ + img = scale(img, size=self.scale_size) + if self.do_ten_crop: + img = ten_crop(img, self.size) + img -= self.mean[np.newaxis] + else: + img = center_crop(img, self.size) + img -= self.mean + + return img + + def _average_ten_crop(self, y): + if y.ndim == 4: + warnings.warn( + 'Four dimensional features are averaged. ' + 'If these are batch of 2D spatial features, ' + 'their spatial information would be lost.') + + xp = chainer.cuda.get_array_module(y) + n = y.shape[0] // 10 + y_shape = y.shape[1:] + y = y.reshape((n, 10) + y_shape) + y = xp.sum(y, axis=1) / 10 + return y + + def predict(self, imgs): + """Predict features from images. + + When :obj:`self.do_ten_crop == True`, this extracts features from + patches that are ten-cropped from images. + Otherwise, this extracts features from center-crop of the images. + + When using patches from ten-crop, the features for each crop + is averaged to compute one feature. + Ten-crop mode is only supported for calculation of features + :math:`fc6, fc7, fc8, prob`. + + Given :math:`N` input images, this outputs a batched array with + batchsize :math:`N`. + + Args: + imgs (iterable of numpy.ndarray): Array-images. + All images are in CHW and RGB format + and the range of their value is :math:`[0, 255]`. + + Returns: + Variable or tuple of Variable: + A batch of features or tuple of them. + The features to output are selected by :obj:`features` option + of :meth:`__init__`. + + """ + imgs = [self._prepare(img) for img in imgs] + imgs = self.xp.asarray(imgs).reshape(-1, 3, 224, 224) + + with chainer.function.no_backprop_mode(): + imgs = chainer.Variable(imgs) + activations = self.extractor(imgs) + + if isinstance(activations, tuple): + output = [] + for activation in activations: + activation = activation.data + if self.do_ten_crop: + activation = self._average_ten_crop(activation) + output.append(cuda.to_cpu(activation)) + output = tuple(output) + else: + output = cuda.to_cpu(activations.data) + if self.do_ten_crop: + output = self._average_ten_crop(output) + + return output From 6c5f6f9975d9cc833acb82692d639c190f5a6771 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 19 Jun 2017 20:13:56 +0900 Subject: [PATCH 049/139] use sequential_chain --- chainercv/links/__init__.py | 1 + chainercv/links/model/sequential_chain.py | 66 +++++++++ .../sequential_feature_extraction_chain.py | 74 ---------- chainercv/links/model/vgg/vgg16.py | 98 ++++++++----- .../model_tests/test_sequential_chain.py | 134 ++++++++++++++++++ .../model_tests/vgg_tests/test_vgg16.py | 6 +- 6 files changed, 268 insertions(+), 111 deletions(-) create mode 100644 chainercv/links/model/sequential_chain.py delete mode 100644 chainercv/links/model/sequential_feature_extraction_chain.py create mode 100644 tests/links_tests/model_tests/test_sequential_chain.py diff --git a/chainercv/links/__init__.py b/chainercv/links/__init__.py index bd3f608a07..108eeb3f46 100644 --- a/chainercv/links/__init__.py +++ b/chainercv/links/__init__.py @@ -2,6 +2,7 @@ from chainercv.links.model.faster_rcnn.faster_rcnn_vgg import FasterRCNNVGG16 # NOQA from chainercv.links.model.segnet.segnet_basic import SegNetBasic # NOQA +from chainercv.links.model.sequential_chain import SequentialChain # NOQA from chainercv.links.model.ssd import SSD300 # NOQA from chainercv.links.model.ssd import SSD512 # NOQA from chainercv.links.model.vgg import VGG16 # NOQA diff --git a/chainercv/links/model/sequential_chain.py b/chainercv/links/model/sequential_chain.py new file mode 100644 index 0000000000..d21057b1ed --- /dev/null +++ b/chainercv/links/model/sequential_chain.py @@ -0,0 +1,66 @@ +import chainer +import collections + + +class SequentialChain(chainer.Chain): + + def __init__(self, functions, feature_names=None): + super(SequentialChain, self).__init__() + if not isinstance(functions, collections.OrderedDict): + if feature_names is not None: + raise ValueError('`feature_names` needs to be `None` unless ' + '`functions` is OrderedDict.') + functions = collections.OrderedDict( + [(str(i), function) for i, function in enumerate(functions)]) + self._functions = functions + + if feature_names is None: + feature_names = functions.keys()[-1] + if (not isinstance(feature_names, str) and + all([isinstance(name, str) for name in feature_names])): + return_tuple = True + else: + return_tuple = False + feature_names = [feature_names] + self._return_tuple = return_tuple + self._feature_names = list(feature_names) + + if any([name not in functions.keys() for + name in self._feature_names]): + raise ValueError('Elements of `feature_names` shuold be one of ' + '{}.'.format(functions.keys())) + + with self.init_scope(): + for name, function in functions.items(): + if isinstance(function, chainer.Link): + setattr(self, name, function) + + def __call__(self, x): + feature_names = list(self._feature_names) + + features = {} + h = x + for name, function in self._functions.items(): + if len(feature_names) == 0: + break + h = function(h) + if name in feature_names: + features[name] = h + feature_names.remove(name) + + if self._return_tuple: + features = tuple( + [features[name] for name in self._feature_names]) + else: + features = list(features.values())[0] + return features + + def copy(self): + ret = super(SequentialChain, self).copy() + functions = [] + for name, function in self._functions.items(): + if name in self._children: + function = ret[name] + functions.append((name, function)) + ret.functions = collections.OrderedDict(functions) + return ret diff --git a/chainercv/links/model/sequential_feature_extraction_chain.py b/chainercv/links/model/sequential_feature_extraction_chain.py deleted file mode 100644 index 246d3efa67..0000000000 --- a/chainercv/links/model/sequential_feature_extraction_chain.py +++ /dev/null @@ -1,74 +0,0 @@ -import chainer - - -class SequentialFeatureExtractionChain(chainer.Chain): - - def __init__(self, feature_names, link_generators): - super(SequentialFeatureExtractionChain, self).__init__() - - if (not isinstance(feature_names, str) and - all([isinstance(name, str) for name in feature_names])): - return_tuple = True - else: - return_tuple = False - feature_names = [feature_names] - self._return_tuple = return_tuple - self._feature_names = list(feature_names) - if any([name not in self.functions for - name in self._feature_names]): - raise ValueError('Elements of `feature_names` shuold be one of ' - '{}.'.format(self.functions.keys())) - - # Collect functions that are not going to be used. - unused_function_names = [] - unused = False - for name in self.functions.keys(): - if unused: - unused_function_names.append(name) - if name in feature_names: - feature_names.remove(name) - if len(feature_names) == 0: - unused = True - - with self.init_scope(): - for name, link_gen in link_generators.items(): - # Ignore layers whose names match functions that are removed. - if name not in unused_function_names: - setattr(self, name, link_gen()) - - @property - def functions(self): - raise NotImplementedError - - def __call__(self, x): - """Forward the model. - - Args: - x (~chainer.Variable): Batch of image variables. - - Returns: - Variable or tuple of Variable: - A batch of features or tuple of batched features. - The returned features are selected by :obj:`feature_names` that - is passed to :meth:`__init__`. - - """ - feature_names = list(self._feature_names) - - features = {} - h = x - for name, funcs in self.functions.items(): - if len(feature_names) == 0: - break - for func in funcs: - h = func(h) - if name in feature_names: - features[name] = h - feature_names.remove(name) - - if self._return_tuple: - features = tuple( - [features[name] for name in features.keys()]) - else: - features = list(features.values())[0] - return features diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index a7b11551c9..e80019f889 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -1,6 +1,7 @@ from __future__ import division import collections +from itertools import islice import chainer import chainer.functions as F @@ -10,11 +11,10 @@ from chainercv.utils import download_model -from chainercv.links.model.sequential_feature_extraction_chain import \ - SequentialFeatureExtractionChain +from chainercv.links.model.sequential_chain import SequentialChain -class VGG16(SequentialFeatureExtractionChain): +class VGG16(SequentialChain): """VGG16 Network for classification and feature extraction. @@ -123,7 +123,56 @@ def __init__(self, pretrained_model=None, n_class=None, 'fc7': lambda: L.Linear(4096, 4096, **kwargs), 'fc8': lambda: L.Linear(4096, n_class, **kwargs) } - super(VGG16, self).__init__(feature_names, link_generators) + + # None will be initialized according to link_generators once + # the functions to use are selected. + functions = collections.OrderedDict([ + ('conv1_1', None), + ('conv1_1_relu', F.relu), + ('conv1_2', None), + ('conv1_2_relu', F.relu), + ('pool1', _max_pooling_2d), + ('conv2_1', None), + ('conv2_1_relu', F.relu), + ('conv2_2', None), + ('conv2_2_relu', F.relu), + ('pool2', _max_pooling_2d), + ('conv3_1', None), + ('conv3_1_relu', F.relu), + ('conv3_2', None), + ('conv3_2_relu', F.relu), + ('conv3_3', None), + ('conv3_3_relu', F.relu), + ('pool3', _max_pooling_2d), + ('conv4_1', None), + ('conv4_1_relu', F.relu), + ('conv4_2', None), + ('conv4_2_relu', F.relu), + ('conv4_3', None), + ('conv4_3_relu', F.relu), + ('pool4', _max_pooling_2d), + ('conv5_1', None), + ('conv5_1_relu', F.relu), + ('conv5_2', None), + ('conv5_2_relu', F.relu), + ('conv5_3', None), + ('conv5_3_relu', F.relu), + ('pool5', _max_pooling_2d), + ('fc6', None), + ('fc6_relu', F.relu), + ('fc6_dropout', F.dropout), + ('fc7', None), + ('fc7_relu', F.relu), + ('fc7_dropout', F.dropout), + ('fc8', None), + ('prob', F.softmax) + ]) + functions = _choose_necessary_functions(functions, feature_names) + # Instantiate uninitialized links. + for name in list(functions.keys()): + if name in link_generators: + functions[name] = link_generators[name]() + super(VGG16, self).__init__(functions, feature_names) if pretrained_model in self._models: path = download_model(self._models[pretrained_model]['url']) @@ -131,36 +180,17 @@ def __init__(self, pretrained_model=None, n_class=None, elif pretrained_model: chainer.serializers.load_npz(pretrained_model, self) - @property - def functions(self): - def _getattr(name): - return getattr(self, name, None) - - return collections.OrderedDict([ - ('conv1_1', [_getattr('conv1_1'), F.relu]), - ('conv1_2', [_getattr('conv1_2'), F.relu]), - ('pool1', [_max_pooling_2d]), - ('conv2_1', [_getattr('conv2_1'), F.relu]), - ('conv2_2', [_getattr('conv2_2'), F.relu]), - ('pool2', [_max_pooling_2d]), - ('conv3_1', [_getattr('conv3_1'), F.relu]), - ('conv3_2', [_getattr('conv3_2'), F.relu]), - ('conv3_3', [_getattr('conv3_3'), F.relu]), - ('pool3', [_max_pooling_2d]), - ('conv4_1', [_getattr('conv4_1'), F.relu]), - ('conv4_2', [_getattr('conv4_2'), F.relu]), - ('conv4_3', [_getattr('conv4_3'), F.relu]), - ('pool4', [_max_pooling_2d]), - ('conv5_1', [_getattr('conv5_1'), F.relu]), - ('conv5_2', [_getattr('conv5_2'), F.relu]), - ('conv5_3', [_getattr('conv5_3'), F.relu]), - ('pool5', [_max_pooling_2d]), - ('fc6', [_getattr('fc6'), F.relu, F.dropout]), - ('fc7', [_getattr('fc7'), F.relu, F.dropout]), - ('fc8', [_getattr('fc8')]), - ('prob', [F.softmax]), - ]) - def _max_pooling_2d(x): return F.max_pooling_2d(x, ksize=2) + + +def _choose_necessary_functions(functions, feature_names): + if isinstance(feature_names, str): + feature_names = [feature_names] + last_index = max([list(functions.keys()).index(name) for + name in feature_names]) + # Equivalent to `functions = functions[:last_index + 1]`. + functions = collections.OrderedDict( + islice(functions.items(), None, last_index + 1)) + return functions diff --git a/tests/links_tests/model_tests/test_sequential_chain.py b/tests/links_tests/model_tests/test_sequential_chain.py new file mode 100644 index 0000000000..e03e89c018 --- /dev/null +++ b/tests/links_tests/model_tests/test_sequential_chain.py @@ -0,0 +1,134 @@ +import collections +import unittest + +import numpy as np + +import chainer +from chainer.cuda import to_cpu +from chainer import testing +from chainer.testing import attr + +from chainer.function import Function + +from chainercv.links import SequentialChain +from chainercv.utils.testing import ConstantStubLink + + +class DummyFunc(Function): + + def forward(self, inputs): + return inputs[0] * 2, + + +class TestSequentialChainOrderedDictFunctions(unittest.TestCase): + + def setUp(self): + self.l1 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) + self.f1 = DummyFunc() + self.f2 = DummyFunc() + self.l2 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) + + self.link = SequentialChain( + collections.OrderedDict( + [('l1', self.l1), + ('f1', self.f1), + ('f2', self.f2), + ('l2', self.l2)]), + feature_names=['l1', 'f1', 'f2', 'l2']) + self.x = np.random.uniform(size=(1, 3, 24, 24)) + + def check_call_output(self): + x = self.link.xp.asarray(self.x) + out = self.link(x) + + self.assertEqual(len(out), 4) + self.assertIsInstance(out[0], chainer.Variable) + self.assertIsInstance(out[1], chainer.Variable) + self.assertIsInstance(out[2], chainer.Variable) + self.assertIsInstance(out[3], chainer.Variable) + self.assertIsInstance(out[0].data, self.link.xp.ndarray) + self.assertIsInstance(out[1].data, self.link.xp.ndarray) + self.assertIsInstance(out[2].data, self.link.xp.ndarray) + self.assertIsInstance(out[3].data, self.link.xp.ndarray) + + out_data = [to_cpu(var.data) for var in out] + np.testing.assert_equal(out_data[0], to_cpu(self.l1(x).data)) + np.testing.assert_equal(out_data[1], to_cpu(self.f1(self.l1(x)).data)) + np.testing.assert_equal( + out_data[2], to_cpu(self.f2(self.f1(self.l1(x))).data)) + np.testing.assert_equal( + out_data[3], to_cpu(self.l2(self.f2(self.f1(self.l1(x)))).data)) + + def test_call_output_cpu(self): + self.check_call_output() + + @attr.gpu + def test_call_output_gpu(self): + self.link.to_gpu() + self.check_call_output() + + +class TestSequentialChainListFunctions(unittest.TestCase): + + def setUp(self): + self.l1 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) + self.f1 = DummyFunc() + self.f2 = DummyFunc() + self.l2 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) + + self.link = SequentialChain( + [self.l1, self.f1, self.f2, self.l2]) + self.x = np.random.uniform(size=(1, 3, 24, 24)) + + def check_call_output(self): + x = self.link.xp.asarray(self.x) + out = self.link(x) + + self.assertIsInstance(out, chainer.Variable) + self.assertIsInstance(out.data, self.link.xp.ndarray) + + out = to_cpu(out.data) + np.testing.assert_equal( + out, + to_cpu(self.l2(self.f2(self.f1(self.l1(x)))).data)) + + def test_call_output_cpu(self): + self.check_call_output() + + @attr.gpu + def test_call_output_gpu(self): + self.link.to_gpu() + self.check_call_output() + + +class TestSequentialChainCopy(unittest.TestCase): + + def setUp(self): + self.l1 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) + self.f1 = DummyFunc() + self.f2 = DummyFunc() + self.l2 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) + + self.link = SequentialChain( + collections.OrderedDict( + [('l1', self.l1), + ('f1', self.f1), + ('f2', self.f2), + ('l2', self.l2)]), + feature_names=['l1', 'f1', 'f2', 'l2']) + + def check_copy(self): + copied = self.link.copy() + self.assertIs(copied.l1, copied.functions['l1']) + self.assertIs(copied.l2, copied.functions['l2']) + + def test_copy_cpu(self): + self.check_copy() + + @attr.gpu + def test_copy_gpu(self): + self.link.to_gpu() + self.check_copy() + + +testing.run_module(__name__, __file__) diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py index 5700613372..743f96fee8 100644 --- a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -50,12 +50,12 @@ class TestVGG16Copy(unittest.TestCase): def setUp(self): self.link = VGG16(pretrained_model=None, n_class=200, - feature_names='conv2_2', - initialW=Zero(), initial_bias=Zero()) + feature_names='conv2_2', + initialW=Zero(), initial_bias=Zero()) def check_copy(self): copied = self.link.copy() - self.assertIs(copied.conv1_1, copied.functions['conv1_1'][0]) + self.assertIs(copied.conv1_1, copied.functions['conv1_1']) def test_copy_cpu(self): self.check_copy() From b7030848d78b44812acec10a05ce039c643519ff Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 19 Jun 2017 22:15:25 +0900 Subject: [PATCH 050/139] simplify vgg16 --- chainercv/links/model/vgg/vgg16.py | 120 +++++++++++++---------------- 1 file changed, 53 insertions(+), 67 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index e80019f889..137ad8d2c9 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -4,10 +4,14 @@ from itertools import islice import chainer -import chainer.functions as F +from chainer.functions import dropout +from chainer.functions import max_pooling_2d +from chainer.functions import relu +from chainer.functions import softmax from chainer.initializers import constant from chainer.initializers import normal -import chainer.links as L +from chainer.links import Convolution2D +from chainer.links import Linear from chainercv.utils import download_model @@ -79,15 +83,11 @@ class VGG16(SequentialChain): def __init__(self, pretrained_model=None, n_class=None, feature_names='prob', initialW=None, initial_bias=None): if n_class is None: - if (pretrained_model is None and - all([feature not in ['fc8', 'prob'] - for feature in feature_names])): - # fc8 layer is not used in this case. - pass - elif pretrained_model not in self._models: + if (pretrained_model not in self._models and + any([name in ['fc8', 'prob'] for name in feature_names])): raise ValueError( 'The n_class needs to be supplied as an argument.') - else: + elif pretrained_model: n_class = self._models[pretrained_model]['n_class'] if pretrained_model: @@ -105,73 +105,58 @@ def __init__(self, pretrained_model=None, n_class=None, initial_bias = constant.Zero() kwargs = {'initialW': initialW, 'initial_bias': initial_bias} - link_generators = { - 'conv1_1': lambda: L.Convolution2D(3, 64, 3, 1, 1, **kwargs), - 'conv1_2': lambda: L.Convolution2D(64, 64, 3, 1, 1, **kwargs), - 'conv2_1': lambda: L.Convolution2D(64, 128, 3, 1, 1, **kwargs), - 'conv2_2': lambda: L.Convolution2D(128, 128, 3, 1, 1, **kwargs), - 'conv3_1': lambda: L.Convolution2D(128, 256, 3, 1, 1, **kwargs), - 'conv3_2': lambda: L.Convolution2D(256, 256, 3, 1, 1, **kwargs), - 'conv3_3': lambda: L.Convolution2D(256, 256, 3, 1, 1, **kwargs), - 'conv4_1': lambda: L.Convolution2D(256, 512, 3, 1, 1, **kwargs), - 'conv4_2': lambda: L.Convolution2D(512, 512, 3, 1, 1, **kwargs), - 'conv4_3': lambda: L.Convolution2D(512, 512, 3, 1, 1, **kwargs), - 'conv5_1': lambda: L.Convolution2D(512, 512, 3, 1, 1, **kwargs), - 'conv5_2': lambda: L.Convolution2D(512, 512, 3, 1, 1, **kwargs), - 'conv5_3': lambda: L.Convolution2D(512, 512, 3, 1, 1, **kwargs), - 'fc6': lambda: L.Linear(512 * 7 * 7, 4096, **kwargs), - 'fc7': lambda: L.Linear(4096, 4096, **kwargs), - 'fc8': lambda: L.Linear(4096, n_class, **kwargs) - } - - # None will be initialized according to link_generators once - # the functions to use are selected. + # The links are instantiated once it is decided to use them. functions = collections.OrderedDict([ - ('conv1_1', None), - ('conv1_1_relu', F.relu), - ('conv1_2', None), - ('conv1_2_relu', F.relu), + ('conv1_1', lambda: Convolution2D(3, 64, 3, 1, 1, **kwargs)), + ('conv1_1_relu', relu), + ('conv1_2', lambda: Convolution2D(64, 64, 3, 1, 1, **kwargs)), + ('conv1_2_relu', relu), ('pool1', _max_pooling_2d), - ('conv2_1', None), - ('conv2_1_relu', F.relu), - ('conv2_2', None), - ('conv2_2_relu', F.relu), + ('conv2_1', lambda: Convolution2D(64, 128, 3, 1, 1, **kwargs)), + ('conv2_1_relu', relu), + ('conv2_2', lambda: Convolution2D(128, 128, 3, 1, 1, **kwargs)), + ('conv2_2_relu', relu), ('pool2', _max_pooling_2d), - ('conv3_1', None), - ('conv3_1_relu', F.relu), - ('conv3_2', None), - ('conv3_2_relu', F.relu), - ('conv3_3', None), - ('conv3_3_relu', F.relu), + ('conv3_1', lambda: Convolution2D(128, 256, 3, 1, 1, **kwargs)), + ('conv3_1_relu', relu), + ('conv3_2', lambda: Convolution2D(256, 256, 3, 1, 1, **kwargs)), + ('conv3_2_relu', relu), + ('conv3_3', lambda: Convolution2D(256, 256, 3, 1, 1, **kwargs)), + ('conv3_3_relu', relu), ('pool3', _max_pooling_2d), - ('conv4_1', None), - ('conv4_1_relu', F.relu), - ('conv4_2', None), - ('conv4_2_relu', F.relu), - ('conv4_3', None), - ('conv4_3_relu', F.relu), + ('conv4_1', lambda: Convolution2D(256, 512, 3, 1, 1, **kwargs)), + ('conv4_1_relu', relu), + ('conv4_2', lambda: Convolution2D(512, 512, 3, 1, 1, **kwargs)), + ('conv4_2_relu', relu), + ('conv4_3', lambda: Convolution2D(512, 512, 3, 1, 1, **kwargs)), + ('conv4_3_relu', relu), ('pool4', _max_pooling_2d), - ('conv5_1', None), - ('conv5_1_relu', F.relu), - ('conv5_2', None), - ('conv5_2_relu', F.relu), - ('conv5_3', None), - ('conv5_3_relu', F.relu), + ('conv5_1', lambda: Convolution2D(512, 512, 3, 1, 1, **kwargs)), + ('conv5_1_relu', relu), + ('conv5_2', lambda: Convolution2D(512, 512, 3, 1, 1, **kwargs)), + ('conv5_2_relu', relu), + ('conv5_3', lambda: Convolution2D(512, 512, 3, 1, 1, **kwargs)), + ('conv5_3_relu', relu), ('pool5', _max_pooling_2d), - ('fc6', None), - ('fc6_relu', F.relu), - ('fc6_dropout', F.dropout), - ('fc7', None), - ('fc7_relu', F.relu), - ('fc7_dropout', F.dropout), - ('fc8', None), - ('prob', F.softmax) + ('fc6', lambda: Linear(512 * 7 * 7, 4096, **kwargs)), + ('fc6_relu', relu), + ('fc6_dropout', dropout), + ('fc7', lambda: Linear(4096, 4096, **kwargs)), + ('fc7_relu', relu), + ('fc7_dropout', dropout), + ('fc8', lambda: Linear(4096, n_class, **kwargs)), + ('prob', softmax) ]) functions = _choose_necessary_functions(functions, feature_names) # Instantiate uninitialized links. + link_names = ['conv1_1', 'conv1_2', 'conv2_1', 'conv2_2', + 'conv3_1', 'conv3_2', 'conv3_3', 'conv4_1', + 'conv4_2', 'conv4_3', 'conv5_1', 'conv5_2', + 'conv5_3', 'fc6', 'fc7', 'fc8'] for name in list(functions.keys()): - if name in link_generators: - functions[name] = link_generators[name]() + if name in link_names: + functions[name] = functions[name]() + super(VGG16, self).__init__(functions, feature_names) if pretrained_model in self._models: @@ -182,7 +167,7 @@ def __init__(self, pretrained_model=None, n_class=None, def _max_pooling_2d(x): - return F.max_pooling_2d(x, ksize=2) + return max_pooling_2d(x, ksize=2) def _choose_necessary_functions(functions, feature_names): @@ -190,6 +175,7 @@ def _choose_necessary_functions(functions, feature_names): feature_names = [feature_names] last_index = max([list(functions.keys()).index(name) for name in feature_names]) + # Equivalent to `functions = functions[:last_index + 1]`. functions = collections.OrderedDict( islice(functions.items(), None, last_index + 1)) From 75db1945c8d94db8baac80da2124b1d7703574e5 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 19 Jun 2017 22:24:00 +0900 Subject: [PATCH 051/139] simplify sequential_chain --- chainercv/links/model/sequential_chain.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/chainercv/links/model/sequential_chain.py b/chainercv/links/model/sequential_chain.py index d21057b1ed..39fa2f5365 100644 --- a/chainercv/links/model/sequential_chain.py +++ b/chainercv/links/model/sequential_chain.py @@ -6,6 +6,7 @@ class SequentialChain(chainer.Chain): def __init__(self, functions, feature_names=None): super(SequentialChain, self).__init__() + if not isinstance(functions, collections.OrderedDict): if feature_names is not None: raise ValueError('`feature_names` needs to be `None` unless ' @@ -15,7 +16,7 @@ def __init__(self, functions, feature_names=None): self._functions = functions if feature_names is None: - feature_names = functions.keys()[-1] + feature_names = self._functions.keys()[-1] if (not isinstance(feature_names, str) and all([isinstance(name, str) for name in feature_names])): return_tuple = True @@ -25,28 +26,18 @@ def __init__(self, functions, feature_names=None): self._return_tuple = return_tuple self._feature_names = list(feature_names) - if any([name not in functions.keys() for - name in self._feature_names]): - raise ValueError('Elements of `feature_names` shuold be one of ' - '{}.'.format(functions.keys())) - with self.init_scope(): - for name, function in functions.items(): + for name, function in self._functions.items(): if isinstance(function, chainer.Link): setattr(self, name, function) def __call__(self, x): - feature_names = list(self._feature_names) - features = {} h = x for name, function in self._functions.items(): - if len(feature_names) == 0: - break h = function(h) - if name in feature_names: + if name in self._feature_names: features[name] = h - feature_names.remove(name) if self._return_tuple: features = tuple( From a36323e78c59345d62149560d278ad9a251df7f2 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 28 Jun 2017 10:34:44 +0900 Subject: [PATCH 052/139] sequential_chain --> extraction_chain --- chainercv/links/__init__.py | 2 +- chainercv/links/model/extraction_chain.py | 57 +++++++++++++++++++ chainercv/links/model/sequential_chain.py | 57 ------------------- chainercv/links/model/vgg/vgg16.py | 4 +- ...tial_chain.py => test_extraction_chain.py} | 22 +++---- 5 files changed, 71 insertions(+), 71 deletions(-) create mode 100644 chainercv/links/model/extraction_chain.py delete mode 100644 chainercv/links/model/sequential_chain.py rename tests/links_tests/model_tests/{test_sequential_chain.py => test_extraction_chain.py} (86%) diff --git a/chainercv/links/__init__.py b/chainercv/links/__init__.py index 108eeb3f46..1a1173bfcc 100644 --- a/chainercv/links/__init__.py +++ b/chainercv/links/__init__.py @@ -2,7 +2,7 @@ from chainercv.links.model.faster_rcnn.faster_rcnn_vgg import FasterRCNNVGG16 # NOQA from chainercv.links.model.segnet.segnet_basic import SegNetBasic # NOQA -from chainercv.links.model.sequential_chain import SequentialChain # NOQA +from chainercv.links.model.extraction_chain import ExtractionChain # NOQA from chainercv.links.model.ssd import SSD300 # NOQA from chainercv.links.model.ssd import SSD512 # NOQA from chainercv.links.model.vgg import VGG16 # NOQA diff --git a/chainercv/links/model/extraction_chain.py b/chainercv/links/model/extraction_chain.py new file mode 100644 index 0000000000..6ceb72a49c --- /dev/null +++ b/chainercv/links/model/extraction_chain.py @@ -0,0 +1,57 @@ +import chainer +import collections + + +class ExtractionChain(chainer.Chain): + + def __init__(self, layers, layer_names=None): + super(ExtractionChain, self).__init__() + + if not isinstance(layers, collections.OrderedDict): + if layer_names is not None: + raise ValueError('`layer_names` needs to be `None` unless ' + '`layers` is OrderedDict.') + layers = collections.OrderedDict( + [(str(i), function) for i, function in enumerate(layers)]) + self._layers = layers + + if layer_names is None: + layer_names = self._layers.keys()[-1] + if (not isinstance(layer_names, str) and + all([isinstance(name, str) for name in layer_names])): + return_tuple = True + else: + return_tuple = False + layer_names = [layer_names] + self._return_tuple = return_tuple + self._layer_names = list(layer_names) + + with self.init_scope(): + for name, function in self._layers.items(): + if isinstance(function, chainer.Link): + setattr(self, name, function) + + def __call__(self, x): + features = {} + h = x + for name, function in self._layers.items(): + h = function(h) + if name in self._layer_names: + features[name] = h + + if self._return_tuple: + features = tuple( + [features[name] for name in self._layer_names]) + else: + features = list(features.values())[0] + return features + + def copy(self): + ret = super(ExtractionChain, self).copy() + layers = [] + for name, function in self._layers.items(): + if name in self._children: + function = ret[name] + layers.append((name, function)) + ret.layers = collections.OrderedDict(layers) + return ret diff --git a/chainercv/links/model/sequential_chain.py b/chainercv/links/model/sequential_chain.py deleted file mode 100644 index 39fa2f5365..0000000000 --- a/chainercv/links/model/sequential_chain.py +++ /dev/null @@ -1,57 +0,0 @@ -import chainer -import collections - - -class SequentialChain(chainer.Chain): - - def __init__(self, functions, feature_names=None): - super(SequentialChain, self).__init__() - - if not isinstance(functions, collections.OrderedDict): - if feature_names is not None: - raise ValueError('`feature_names` needs to be `None` unless ' - '`functions` is OrderedDict.') - functions = collections.OrderedDict( - [(str(i), function) for i, function in enumerate(functions)]) - self._functions = functions - - if feature_names is None: - feature_names = self._functions.keys()[-1] - if (not isinstance(feature_names, str) and - all([isinstance(name, str) for name in feature_names])): - return_tuple = True - else: - return_tuple = False - feature_names = [feature_names] - self._return_tuple = return_tuple - self._feature_names = list(feature_names) - - with self.init_scope(): - for name, function in self._functions.items(): - if isinstance(function, chainer.Link): - setattr(self, name, function) - - def __call__(self, x): - features = {} - h = x - for name, function in self._functions.items(): - h = function(h) - if name in self._feature_names: - features[name] = h - - if self._return_tuple: - features = tuple( - [features[name] for name in self._feature_names]) - else: - features = list(features.values())[0] - return features - - def copy(self): - ret = super(SequentialChain, self).copy() - functions = [] - for name, function in self._functions.items(): - if name in self._children: - function = ret[name] - functions.append((name, function)) - ret.functions = collections.OrderedDict(functions) - return ret diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 137ad8d2c9..3c70d4f7e9 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -15,10 +15,10 @@ from chainercv.utils import download_model -from chainercv.links.model.sequential_chain import SequentialChain +from chainercv.links.model.extraction_chain import ExtractionChain -class VGG16(SequentialChain): +class VGG16(ExtractionChain): """VGG16 Network for classification and feature extraction. diff --git a/tests/links_tests/model_tests/test_sequential_chain.py b/tests/links_tests/model_tests/test_extraction_chain.py similarity index 86% rename from tests/links_tests/model_tests/test_sequential_chain.py rename to tests/links_tests/model_tests/test_extraction_chain.py index e03e89c018..3dfd900614 100644 --- a/tests/links_tests/model_tests/test_sequential_chain.py +++ b/tests/links_tests/model_tests/test_extraction_chain.py @@ -10,7 +10,7 @@ from chainer.function import Function -from chainercv.links import SequentialChain +from chainercv.links import ExtractionChain from chainercv.utils.testing import ConstantStubLink @@ -20,7 +20,7 @@ def forward(self, inputs): return inputs[0] * 2, -class TestSequentialChainOrderedDictFunctions(unittest.TestCase): +class TestExtractionChainOrderedDictFunctions(unittest.TestCase): def setUp(self): self.l1 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) @@ -28,13 +28,13 @@ def setUp(self): self.f2 = DummyFunc() self.l2 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) - self.link = SequentialChain( + self.link = ExtractionChain( collections.OrderedDict( [('l1', self.l1), ('f1', self.f1), ('f2', self.f2), ('l2', self.l2)]), - feature_names=['l1', 'f1', 'f2', 'l2']) + layer_names=['l1', 'f1', 'f2', 'l2']) self.x = np.random.uniform(size=(1, 3, 24, 24)) def check_call_output(self): @@ -68,7 +68,7 @@ def test_call_output_gpu(self): self.check_call_output() -class TestSequentialChainListFunctions(unittest.TestCase): +class TestExtractionChainListFunctions(unittest.TestCase): def setUp(self): self.l1 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) @@ -76,7 +76,7 @@ def setUp(self): self.f2 = DummyFunc() self.l2 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) - self.link = SequentialChain( + self.link = ExtractionChain( [self.l1, self.f1, self.f2, self.l2]) self.x = np.random.uniform(size=(1, 3, 24, 24)) @@ -101,7 +101,7 @@ def test_call_output_gpu(self): self.check_call_output() -class TestSequentialChainCopy(unittest.TestCase): +class TestExtractionChainCopy(unittest.TestCase): def setUp(self): self.l1 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) @@ -109,18 +109,18 @@ def setUp(self): self.f2 = DummyFunc() self.l2 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) - self.link = SequentialChain( + self.link = ExtractionChain( collections.OrderedDict( [('l1', self.l1), ('f1', self.f1), ('f2', self.f2), ('l2', self.l2)]), - feature_names=['l1', 'f1', 'f2', 'l2']) + layer_names=['l1', 'f1', 'f2', 'l2']) def check_copy(self): copied = self.link.copy() - self.assertIs(copied.l1, copied.functions['l1']) - self.assertIs(copied.l2, copied.functions['l2']) + self.assertIs(copied.l1, copied.layers['l1']) + self.assertIs(copied.l2, copied.layers['l2']) def test_copy_cpu(self): self.check_copy() From a889d4726189d1a7c9a9fbd074ca2c1d6eca9d98 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 28 Jun 2017 10:37:19 +0900 Subject: [PATCH 053/139] delete unnecessary constraint --- chainercv/links/model/extraction_chain.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/chainercv/links/model/extraction_chain.py b/chainercv/links/model/extraction_chain.py index 6ceb72a49c..600cebdf67 100644 --- a/chainercv/links/model/extraction_chain.py +++ b/chainercv/links/model/extraction_chain.py @@ -8,9 +8,6 @@ def __init__(self, layers, layer_names=None): super(ExtractionChain, self).__init__() if not isinstance(layers, collections.OrderedDict): - if layer_names is not None: - raise ValueError('`layer_names` needs to be `None` unless ' - '`layers` is OrderedDict.') layers = collections.OrderedDict( [(str(i), function) for i, function in enumerate(layers)]) self._layers = layers From a91d29de02dacd5d9c6abe3c50953b324e4fe27d Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 28 Jun 2017 10:38:22 +0900 Subject: [PATCH 054/139] stop using unnecessary list --- chainercv/links/model/extraction_chain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainercv/links/model/extraction_chain.py b/chainercv/links/model/extraction_chain.py index 600cebdf67..15268d958e 100644 --- a/chainercv/links/model/extraction_chain.py +++ b/chainercv/links/model/extraction_chain.py @@ -21,7 +21,7 @@ def __init__(self, layers, layer_names=None): return_tuple = False layer_names = [layer_names] self._return_tuple = return_tuple - self._layer_names = list(layer_names) + self._layer_names = layer_names with self.init_scope(): for name, function in self._layers.items(): From e0d914745597e3445abba69534dd1edf3cf5a7e9 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 28 Jun 2017 11:31:33 +0900 Subject: [PATCH 055/139] use property mean --- .../links/model/feature_extraction_predictor.py | 12 +++++------- chainercv/links/model/vgg/vgg16.py | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/chainercv/links/model/feature_extraction_predictor.py b/chainercv/links/model/feature_extraction_predictor.py index e1835e2b1e..96e638dc63 100644 --- a/chainercv/links/model/feature_extraction_predictor.py +++ b/chainercv/links/model/feature_extraction_predictor.py @@ -9,23 +9,21 @@ from chainercv.transforms import ten_crop -# RGB order -_imagenet_mean = np.array( - [123.68, 116.779, 103.939], dtype=np.float32)[:, np.newaxis, np.newaxis] - - class FeatureExtractionPredictor(chainer.Chain): - def __init__(self, extractor, mean=_imagenet_mean, + def __init__(self, extractor, size=(224, 224), scale_size=256, do_ten_crop=False): - self.mean = mean self.scale_size = scale_size self.size = size self.do_ten_crop = do_ten_crop with self.init_scope(): self.extractor = extractor + + @property + def mean(self): + return self.extractor.mean def _prepare(self, img): """Transform an image to the input for VGG network. diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 3c70d4f7e9..f8148c5cc0 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -2,6 +2,7 @@ import collections from itertools import islice +import numpy as np import chainer from chainer.functions import dropout @@ -18,6 +19,11 @@ from chainercv.links.model.extraction_chain import ExtractionChain +# RGB order +_imagenet_mean = np.array( + [123.68, 116.779, 103.939], dtype=np.float32)[:, np.newaxis, np.newaxis] + + class VGG16(ExtractionChain): """VGG16 Network for classification and feature extraction. @@ -76,11 +82,12 @@ class VGG16(ExtractionChain): 'imagenet': { 'n_class': 1000, 'url': 'https://github.com/yuyu2172/share-weights/releases/' - 'download/0.0.3/vgg16_imagenet_convert_2017_06_15.npz' + 'download/0.0.3/vgg16_imagenet_convert_2017_06_15.npz', + 'mean': _imagenet_mean } } - def __init__(self, pretrained_model=None, n_class=None, + def __init__(self, pretrained_model=None, n_class=None, mean=None, feature_names='prob', initialW=None, initial_bias=None): if n_class is None: if (pretrained_model not in self._models and @@ -90,6 +97,11 @@ def __init__(self, pretrained_model=None, n_class=None, elif pretrained_model: n_class = self._models[pretrained_model]['n_class'] + if mean is None: + if pretrained_model in self._models: + mean = self._models[pretrained_model]['mean'] + self.mean = mean + if pretrained_model: # As a sampling process is time-consuming, # we employ a zero initializer for faster computation. From 0972d022eef312a66600270f1f3f33dd9ebdee59 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 28 Jun 2017 11:36:49 +0900 Subject: [PATCH 056/139] feature_names --> layer_names for VGG --- chainercv/links/model/vgg/vgg16.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index f8148c5cc0..784c075be7 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -88,10 +88,10 @@ class VGG16(ExtractionChain): } def __init__(self, pretrained_model=None, n_class=None, mean=None, - feature_names='prob', initialW=None, initial_bias=None): + layer_names='prob', initialW=None, initial_bias=None): if n_class is None: if (pretrained_model not in self._models and - any([name in ['fc8', 'prob'] for name in feature_names])): + any([name in ['fc8', 'prob'] for name in layer_names])): raise ValueError( 'The n_class needs to be supplied as an argument.') elif pretrained_model: @@ -118,7 +118,7 @@ def __init__(self, pretrained_model=None, n_class=None, mean=None, kwargs = {'initialW': initialW, 'initial_bias': initial_bias} # The links are instantiated once it is decided to use them. - functions = collections.OrderedDict([ + layers = collections.OrderedDict([ ('conv1_1', lambda: Convolution2D(3, 64, 3, 1, 1, **kwargs)), ('conv1_1_relu', relu), ('conv1_2', lambda: Convolution2D(64, 64, 3, 1, 1, **kwargs)), @@ -159,17 +159,17 @@ def __init__(self, pretrained_model=None, n_class=None, mean=None, ('fc8', lambda: Linear(4096, n_class, **kwargs)), ('prob', softmax) ]) - functions = _choose_necessary_functions(functions, feature_names) + layers = _choose_necessary_layers(layers, layer_names) # Instantiate uninitialized links. link_names = ['conv1_1', 'conv1_2', 'conv2_1', 'conv2_2', 'conv3_1', 'conv3_2', 'conv3_3', 'conv4_1', 'conv4_2', 'conv4_3', 'conv5_1', 'conv5_2', 'conv5_3', 'fc6', 'fc7', 'fc8'] - for name in list(functions.keys()): + for name in list(layers.keys()): if name in link_names: - functions[name] = functions[name]() + layers[name] = layers[name]() - super(VGG16, self).__init__(functions, feature_names) + super(VGG16, self).__init__(layers, layer_names) if pretrained_model in self._models: path = download_model(self._models[pretrained_model]['url']) @@ -182,13 +182,13 @@ def _max_pooling_2d(x): return max_pooling_2d(x, ksize=2) -def _choose_necessary_functions(functions, feature_names): - if isinstance(feature_names, str): - feature_names = [feature_names] - last_index = max([list(functions.keys()).index(name) for - name in feature_names]) +def _choose_necessary_layers(layers, layer_names): + if isinstance(layer_names, str): + layer_names = [layer_names] + last_index = max([list(layers.keys()).index(name) for + name in layer_names]) # Equivalent to `functions = functions[:last_index + 1]`. - functions = collections.OrderedDict( - islice(functions.items(), None, last_index + 1)) - return functions + layers = collections.OrderedDict( + islice(layers.items(), None, last_index + 1)) + return layers From d7f3f52d5b5e30e3efdb55e75bc005c24cbe3f00 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 28 Jun 2017 11:44:35 +0900 Subject: [PATCH 057/139] mean doc --- chainercv/links/model/vgg/vgg16.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 784c075be7..e1551009d3 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -66,6 +66,9 @@ class VGG16(ExtractionChain): :obj:`$HOME/.chainer/dataset` unless you specify another value by modifying the environment variable. n_class (int): The dimension of the output of fc8. + mean (numpy.ndarray): A mean image. If :obj:`None` and + a supported pretrained model is used, + the mean image used to train the pretrained model will be used. features (str or iterable of strings): The names of the feature to output with :meth:`__call__` and :meth:`predict`. initialW (callable): Initializer for the weights. From e7940554676e88ebf38bd5d4adf9b5f62774eff2 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 28 Jun 2017 11:54:47 +0900 Subject: [PATCH 058/139] fix tests --- .../model_tests/vgg_tests/test_vgg16.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py index 743f96fee8..a47ddd9cf9 100644 --- a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -11,9 +11,9 @@ @testing.parameterize( - {'feature_names': 'prob', 'shapes': (1, 200), 'n_class': 200}, - {'feature_names': 'pool5', 'shapes': (1, 512, 7, 7), 'n_class': None}, - {'feature_names': ['conv5_3', 'conv4_2'], + {'layer_names': 'prob', 'shapes': (1, 200), 'n_class': 200}, + {'layer_names': 'pool5', 'shapes': (1, 512, 7, 7), 'n_class': None}, + {'layer_names': ['conv5_3', 'conv4_2'], 'shapes': ((1, 512, 14, 14), (1, 512, 28, 28)), 'n_class': None}, ) @attr.slow @@ -22,7 +22,7 @@ class TestVGG16Call(unittest.TestCase): def setUp(self): self.link = VGG16( pretrained_model=None, n_class=self.n_class, - feature_names=self.feature_names) + layer_names=self.layer_names) def check_call(self): xp = self.link.xp @@ -50,12 +50,12 @@ class TestVGG16Copy(unittest.TestCase): def setUp(self): self.link = VGG16(pretrained_model=None, n_class=200, - feature_names='conv2_2', + layer_names='conv2_2', initialW=Zero(), initial_bias=Zero()) def check_copy(self): copied = self.link.copy() - self.assertIs(copied.conv1_1, copied.functions['conv1_1']) + self.assertIs(copied.conv1_1, copied.layers['conv1_1']) def test_copy_cpu(self): self.check_copy() @@ -67,10 +67,10 @@ def test_copy_gpu(self): @testing.parameterize( - {'feature_names': 'pool4', + {'layer_names': 'pool4', 'not_attribute': ['conv5_1', 'conv5_2', 'conv5_3', 'fc6', 'fc7', 'fc8'], }, - {'feature_names': ['pool5', 'pool4'], + {'layer_names': ['pool5', 'pool4'], 'not_attribute': ['fc6', 'fc7', 'fc8'], } ) @@ -78,7 +78,7 @@ class TestVGG16FeatureOption(unittest.TestCase): def setUp(self): self.link = VGG16( - pretrained_model=None, feature_names=self.feature_names, + pretrained_model=None, layer_names=self.layer_names, initialW=Zero(), initial_bias=Zero()) def check_feature_option(self): From 3309415b660635c6efbc66e2c1e274127be4dff9 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 28 Jun 2017 11:55:07 +0900 Subject: [PATCH 059/139] simplify initialization --- chainercv/links/model/vgg/vgg16.py | 67 +++++++++++++----------------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index e1551009d3..28e7a2fada 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -1,7 +1,6 @@ from __future__ import division import collections -from itertools import islice import numpy as np import chainer @@ -120,57 +119,59 @@ def __init__(self, pretrained_model=None, n_class=None, mean=None, initial_bias = constant.Zero() kwargs = {'initialW': initialW, 'initial_bias': initial_bias} + # Since fc layers take long time to instantiate, + # avoid doing so whenever possible. + fc_layer_names = ['fc6', 'fc6_relu', 'fc6_dropout', + 'fc7', 'fc7_relu', 'fc7_dropout', 'fc8', 'prob'] + if (any([name in fc_layer_names for name in layer_names]) + or layer_names in fc_layer_names): + fc_kwargs = {'initialW': constant.Zero(), + 'initial_bias': constant.Zero()} + else: + fc_kwargs = kwargs + # The links are instantiated once it is decided to use them. layers = collections.OrderedDict([ - ('conv1_1', lambda: Convolution2D(3, 64, 3, 1, 1, **kwargs)), + ('conv1_1', Convolution2D(3, 64, 3, 1, 1, **kwargs)), ('conv1_1_relu', relu), - ('conv1_2', lambda: Convolution2D(64, 64, 3, 1, 1, **kwargs)), + ('conv1_2', Convolution2D(64, 64, 3, 1, 1, **kwargs)), ('conv1_2_relu', relu), ('pool1', _max_pooling_2d), - ('conv2_1', lambda: Convolution2D(64, 128, 3, 1, 1, **kwargs)), + ('conv2_1', Convolution2D(64, 128, 3, 1, 1, **kwargs)), ('conv2_1_relu', relu), - ('conv2_2', lambda: Convolution2D(128, 128, 3, 1, 1, **kwargs)), + ('conv2_2', Convolution2D(128, 128, 3, 1, 1, **kwargs)), ('conv2_2_relu', relu), ('pool2', _max_pooling_2d), - ('conv3_1', lambda: Convolution2D(128, 256, 3, 1, 1, **kwargs)), + ('conv3_1', Convolution2D(128, 256, 3, 1, 1, **kwargs)), ('conv3_1_relu', relu), - ('conv3_2', lambda: Convolution2D(256, 256, 3, 1, 1, **kwargs)), + ('conv3_2', Convolution2D(256, 256, 3, 1, 1, **kwargs)), ('conv3_2_relu', relu), - ('conv3_3', lambda: Convolution2D(256, 256, 3, 1, 1, **kwargs)), + ('conv3_3', Convolution2D(256, 256, 3, 1, 1, **kwargs)), ('conv3_3_relu', relu), ('pool3', _max_pooling_2d), - ('conv4_1', lambda: Convolution2D(256, 512, 3, 1, 1, **kwargs)), + ('conv4_1', Convolution2D(256, 512, 3, 1, 1, **kwargs)), ('conv4_1_relu', relu), - ('conv4_2', lambda: Convolution2D(512, 512, 3, 1, 1, **kwargs)), + ('conv4_2', Convolution2D(512, 512, 3, 1, 1, **kwargs)), ('conv4_2_relu', relu), - ('conv4_3', lambda: Convolution2D(512, 512, 3, 1, 1, **kwargs)), + ('conv4_3', Convolution2D(512, 512, 3, 1, 1, **kwargs)), ('conv4_3_relu', relu), ('pool4', _max_pooling_2d), - ('conv5_1', lambda: Convolution2D(512, 512, 3, 1, 1, **kwargs)), + ('conv5_1', Convolution2D(512, 512, 3, 1, 1, **kwargs)), ('conv5_1_relu', relu), - ('conv5_2', lambda: Convolution2D(512, 512, 3, 1, 1, **kwargs)), + ('conv5_2', Convolution2D(512, 512, 3, 1, 1, **kwargs)), ('conv5_2_relu', relu), - ('conv5_3', lambda: Convolution2D(512, 512, 3, 1, 1, **kwargs)), + ('conv5_3', Convolution2D(512, 512, 3, 1, 1, **kwargs)), ('conv5_3_relu', relu), ('pool5', _max_pooling_2d), - ('fc6', lambda: Linear(512 * 7 * 7, 4096, **kwargs)), + ('fc6', Linear(512 * 7 * 7, 4096, **fc_kwargs)), ('fc6_relu', relu), ('fc6_dropout', dropout), - ('fc7', lambda: Linear(4096, 4096, **kwargs)), + ('fc7', Linear(4096, 4096, **fc_kwargs)), ('fc7_relu', relu), ('fc7_dropout', dropout), - ('fc8', lambda: Linear(4096, n_class, **kwargs)), + ('fc8', Linear(4096, n_class, **fc_kwargs)), ('prob', softmax) - ]) - layers = _choose_necessary_layers(layers, layer_names) - # Instantiate uninitialized links. - link_names = ['conv1_1', 'conv1_2', 'conv2_1', 'conv2_2', - 'conv3_1', 'conv3_2', 'conv3_3', 'conv4_1', - 'conv4_2', 'conv4_3', 'conv5_1', 'conv5_2', - 'conv5_3', 'fc6', 'fc7', 'fc8'] - for name in list(layers.keys()): - if name in link_names: - layers[name] = layers[name]() + ]) super(VGG16, self).__init__(layers, layer_names) @@ -183,15 +184,3 @@ def __init__(self, pretrained_model=None, n_class=None, mean=None, def _max_pooling_2d(x): return max_pooling_2d(x, ksize=2) - - -def _choose_necessary_layers(layers, layer_names): - if isinstance(layer_names, str): - layer_names = [layer_names] - last_index = max([list(layers.keys()).index(name) for - name in layer_names]) - - # Equivalent to `functions = functions[:last_index + 1]`. - layers = collections.OrderedDict( - islice(layers.items(), None, last_index + 1)) - return layers From 4c8b3e6bfdcf07538452b3270674b86522a92e3c Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 28 Jun 2017 12:13:11 +0900 Subject: [PATCH 060/139] delete redundant layerts from extraction_chain --- chainercv/links/model/extraction_chain.py | 12 +++++++- .../model_tests/test_extraction_chain.py | 28 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/chainercv/links/model/extraction_chain.py b/chainercv/links/model/extraction_chain.py index 15268d958e..d2f6bd2b70 100644 --- a/chainercv/links/model/extraction_chain.py +++ b/chainercv/links/model/extraction_chain.py @@ -1,5 +1,7 @@ -import chainer import collections +from itertools import islice + +import chainer class ExtractionChain(chainer.Chain): @@ -14,6 +16,7 @@ def __init__(self, layers, layer_names=None): if layer_names is None: layer_names = self._layers.keys()[-1] + if (not isinstance(layer_names, str) and all([isinstance(name, str) for name in layer_names])): return_tuple = True @@ -23,6 +26,13 @@ def __init__(self, layers, layer_names=None): self._return_tuple = return_tuple self._layer_names = layer_names + # Delete unnecessary layers from self._layers based on layer_names. + # Equivalent to layers = layers[:last_index + 1] + last_index = max([list(self._layers.keys()).index(name) for + name in self._layer_names]) + self._layers = collections.OrderedDict( + islice(self._layers.items(), None, last_index + 1)) + with self.init_scope(): for name, function in self._layers.items(): if isinstance(function, chainer.Link): diff --git a/tests/links_tests/model_tests/test_extraction_chain.py b/tests/links_tests/model_tests/test_extraction_chain.py index 3dfd900614..5b6212c9e9 100644 --- a/tests/links_tests/model_tests/test_extraction_chain.py +++ b/tests/links_tests/model_tests/test_extraction_chain.py @@ -131,4 +131,32 @@ def test_copy_gpu(self): self.check_copy() +class TestExtractionChainRedundantLayers(unittest.TestCase): + + def setUp(self): + self.l1 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) + self.f1 = DummyFunc() + self.f2 = DummyFunc() + self.l2 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) + + self.link = ExtractionChain( + collections.OrderedDict( + [('l1', self.l1), + ('f1', self.f1), + ('f2', self.f2), + ('l2', self.l2)]), + layer_names=['l1', 'f1']) + + def check_redundant_layers(self): + self.assertNotIn('f2', self.link._layer_names) + self.assertNotIn('l2', self.link._layer_names) + + def test_redundant_layers_cpu(self): + self.check_redundant_layers() + + @attr.gpu + def test_redundant_layers_gpu(self): + self.check_redundant_layers() + + testing.run_module(__name__, __file__) From e51762f554edf5fb70bba998e6dc6084283a22f2 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 28 Jun 2017 12:15:00 +0900 Subject: [PATCH 061/139] extraction_chain --> sequential_extractor --- chainercv/links/__init__.py | 2 +- ...action_chain.py => sequential_extractor.py} | 6 +++--- chainercv/links/model/vgg/vgg16.py | 4 ++-- ...n_chain.py => test_sequential_extractor.py} | 18 +++++++++--------- 4 files changed, 15 insertions(+), 15 deletions(-) rename chainercv/links/model/{extraction_chain.py => sequential_extractor.py} (93%) rename tests/links_tests/model_tests/{test_extraction_chain.py => test_sequential_extractor.py} (90%) diff --git a/chainercv/links/__init__.py b/chainercv/links/__init__.py index 1a1173bfcc..06831a9fd3 100644 --- a/chainercv/links/__init__.py +++ b/chainercv/links/__init__.py @@ -2,7 +2,7 @@ from chainercv.links.model.faster_rcnn.faster_rcnn_vgg import FasterRCNNVGG16 # NOQA from chainercv.links.model.segnet.segnet_basic import SegNetBasic # NOQA -from chainercv.links.model.extraction_chain import ExtractionChain # NOQA +from chainercv.links.model.sequential_extractor import SequentialExtractor # NOQA from chainercv.links.model.ssd import SSD300 # NOQA from chainercv.links.model.ssd import SSD512 # NOQA from chainercv.links.model.vgg import VGG16 # NOQA diff --git a/chainercv/links/model/extraction_chain.py b/chainercv/links/model/sequential_extractor.py similarity index 93% rename from chainercv/links/model/extraction_chain.py rename to chainercv/links/model/sequential_extractor.py index d2f6bd2b70..29966fcddc 100644 --- a/chainercv/links/model/extraction_chain.py +++ b/chainercv/links/model/sequential_extractor.py @@ -4,10 +4,10 @@ import chainer -class ExtractionChain(chainer.Chain): +class SequentialExtractor(chainer.Chain): def __init__(self, layers, layer_names=None): - super(ExtractionChain, self).__init__() + super(SequentialExtractor, self).__init__() if not isinstance(layers, collections.OrderedDict): layers = collections.OrderedDict( @@ -54,7 +54,7 @@ def __call__(self, x): return features def copy(self): - ret = super(ExtractionChain, self).copy() + ret = super(SequentialExtractor, self).copy() layers = [] for name, function in self._layers.items(): if name in self._children: diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 28e7a2fada..5c3d77b985 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -15,7 +15,7 @@ from chainercv.utils import download_model -from chainercv.links.model.extraction_chain import ExtractionChain +from chainercv.links.model.sequential_extractor import SequentialExtractor # RGB order @@ -23,7 +23,7 @@ [123.68, 116.779, 103.939], dtype=np.float32)[:, np.newaxis, np.newaxis] -class VGG16(ExtractionChain): +class VGG16(SequentialExtractor): """VGG16 Network for classification and feature extraction. diff --git a/tests/links_tests/model_tests/test_extraction_chain.py b/tests/links_tests/model_tests/test_sequential_extractor.py similarity index 90% rename from tests/links_tests/model_tests/test_extraction_chain.py rename to tests/links_tests/model_tests/test_sequential_extractor.py index 5b6212c9e9..f1449ace28 100644 --- a/tests/links_tests/model_tests/test_extraction_chain.py +++ b/tests/links_tests/model_tests/test_sequential_extractor.py @@ -10,7 +10,7 @@ from chainer.function import Function -from chainercv.links import ExtractionChain +from chainercv.links import SequentialExtractor from chainercv.utils.testing import ConstantStubLink @@ -20,7 +20,7 @@ def forward(self, inputs): return inputs[0] * 2, -class TestExtractionChainOrderedDictFunctions(unittest.TestCase): +class TestSequentialExtractorOrderedDictFunctions(unittest.TestCase): def setUp(self): self.l1 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) @@ -28,7 +28,7 @@ def setUp(self): self.f2 = DummyFunc() self.l2 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) - self.link = ExtractionChain( + self.link = SequentialExtractor( collections.OrderedDict( [('l1', self.l1), ('f1', self.f1), @@ -68,7 +68,7 @@ def test_call_output_gpu(self): self.check_call_output() -class TestExtractionChainListFunctions(unittest.TestCase): +class TestSequentialExtractorListFunctions(unittest.TestCase): def setUp(self): self.l1 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) @@ -76,7 +76,7 @@ def setUp(self): self.f2 = DummyFunc() self.l2 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) - self.link = ExtractionChain( + self.link = SequentialExtractor( [self.l1, self.f1, self.f2, self.l2]) self.x = np.random.uniform(size=(1, 3, 24, 24)) @@ -101,7 +101,7 @@ def test_call_output_gpu(self): self.check_call_output() -class TestExtractionChainCopy(unittest.TestCase): +class TestSequentialExtractorCopy(unittest.TestCase): def setUp(self): self.l1 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) @@ -109,7 +109,7 @@ def setUp(self): self.f2 = DummyFunc() self.l2 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) - self.link = ExtractionChain( + self.link = SequentialExtractor( collections.OrderedDict( [('l1', self.l1), ('f1', self.f1), @@ -131,7 +131,7 @@ def test_copy_gpu(self): self.check_copy() -class TestExtractionChainRedundantLayers(unittest.TestCase): +class TestSequentialExtractorRedundantLayers(unittest.TestCase): def setUp(self): self.l1 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) @@ -139,7 +139,7 @@ def setUp(self): self.f2 = DummyFunc() self.l2 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) - self.link = ExtractionChain( + self.link = SequentialExtractor( collections.OrderedDict( [('l1', self.l1), ('f1', self.f1), From 10815f733d73a8c2683a5c00960ea5bff574122f Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 28 Jun 2017 12:20:40 +0900 Subject: [PATCH 062/139] fix comment --- chainercv/links/model/sequential_extractor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainercv/links/model/sequential_extractor.py b/chainercv/links/model/sequential_extractor.py index 29966fcddc..a2e94a2cec 100644 --- a/chainercv/links/model/sequential_extractor.py +++ b/chainercv/links/model/sequential_extractor.py @@ -27,7 +27,7 @@ def __init__(self, layers, layer_names=None): self._layer_names = layer_names # Delete unnecessary layers from self._layers based on layer_names. - # Equivalent to layers = layers[:last_index + 1] + # Computation is equivalent to layers = layers[:last_index + 1]. last_index = max([list(self._layers.keys()).index(name) for name in self._layer_names]) self._layers = collections.OrderedDict( From 3e22012e00a6dbdd9c994b58195f037c957b113d Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 28 Jun 2017 12:25:46 +0900 Subject: [PATCH 063/139] [Sequential Extractor] Change default names for layers --- chainercv/links/model/sequential_extractor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chainercv/links/model/sequential_extractor.py b/chainercv/links/model/sequential_extractor.py index a2e94a2cec..614c3368ca 100644 --- a/chainercv/links/model/sequential_extractor.py +++ b/chainercv/links/model/sequential_extractor.py @@ -11,7 +11,8 @@ def __init__(self, layers, layer_names=None): if not isinstance(layers, collections.OrderedDict): layers = collections.OrderedDict( - [(str(i), function) for i, function in enumerate(layers)]) + [('{}_{}'.format(layer.__class__.__name__, i), layer) + for i, layer in enumerate(layers)]) self._layers = layers if layer_names is None: From c8fbda6fd73c1061c08e096baa2c1471ec44b45f Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 28 Jun 2017 12:27:40 +0900 Subject: [PATCH 064/139] [Sequential Extractor] function --> layer --- chainercv/links/model/sequential_extractor.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/chainercv/links/model/sequential_extractor.py b/chainercv/links/model/sequential_extractor.py index 614c3368ca..f433c2646c 100644 --- a/chainercv/links/model/sequential_extractor.py +++ b/chainercv/links/model/sequential_extractor.py @@ -35,15 +35,15 @@ def __init__(self, layers, layer_names=None): islice(self._layers.items(), None, last_index + 1)) with self.init_scope(): - for name, function in self._layers.items(): - if isinstance(function, chainer.Link): - setattr(self, name, function) + for name, layer in self._layers.items(): + if isinstance(layer, chainer.Link): + setattr(self, name, layer) def __call__(self, x): features = {} h = x - for name, function in self._layers.items(): - h = function(h) + for name, layer in self._layers.items(): + h = layer(h) if name in self._layer_names: features[name] = h @@ -57,9 +57,9 @@ def __call__(self, x): def copy(self): ret = super(SequentialExtractor, self).copy() layers = [] - for name, function in self._layers.items(): + for name, layer in self._layers.items(): if name in self._children: - function = ret[name] - layers.append((name, function)) + layer = ret[name] + layers.append((name, layer)) ret.layers = collections.OrderedDict(layers) return ret From 7ef4623878d9eba424cae7cefa34a9db395c1f2c Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 4 Jul 2017 15:00:38 +0900 Subject: [PATCH 065/139] sequential_extractor --> sequential_feature_extractor and add doc --- chainercv/links/__init__.py | 2 +- ...tor.py => sequential_feature_extractor.py} | 45 +++++++++++++++++-- chainercv/links/model/vgg/vgg16.py | 4 +- docs/source/reference/links.rst | 4 ++ ...y => test_sequential_feature_extractor.py} | 18 ++++---- 5 files changed, 58 insertions(+), 15 deletions(-) rename chainercv/links/model/{sequential_extractor.py => sequential_feature_extractor.py} (52%) rename tests/links_tests/model_tests/{test_sequential_extractor.py => test_sequential_feature_extractor.py} (89%) diff --git a/chainercv/links/__init__.py b/chainercv/links/__init__.py index 06831a9fd3..58f4ef99fb 100644 --- a/chainercv/links/__init__.py +++ b/chainercv/links/__init__.py @@ -2,7 +2,7 @@ from chainercv.links.model.faster_rcnn.faster_rcnn_vgg import FasterRCNNVGG16 # NOQA from chainercv.links.model.segnet.segnet_basic import SegNetBasic # NOQA -from chainercv.links.model.sequential_extractor import SequentialExtractor # NOQA +from chainercv.links.model.sequential_feature_extractor import SequentialFeatureExtractor # NOQA from chainercv.links.model.ssd import SSD300 # NOQA from chainercv.links.model.ssd import SSD512 # NOQA from chainercv.links.model.vgg import VGG16 # NOQA diff --git a/chainercv/links/model/sequential_extractor.py b/chainercv/links/model/sequential_feature_extractor.py similarity index 52% rename from chainercv/links/model/sequential_extractor.py rename to chainercv/links/model/sequential_feature_extractor.py index f433c2646c..223207227f 100644 --- a/chainercv/links/model/sequential_extractor.py +++ b/chainercv/links/model/sequential_feature_extractor.py @@ -4,10 +4,49 @@ import chainer -class SequentialExtractor(chainer.Chain): +class SequentialFeatureExtractor(chainer.Chain): + + """A feature extractor model with a single-stream forward pass. + + This class is a base class that can be used for an implementation of + a feature extractor model. + The link takes :obj:`layers` to specify the computation + conducted in :meth:`__call__`. + :obj:`layers` is a list or :obj:`collections.OrderedDict` of + callable objects called layers, which are going to be called sequentially + starting from the top to the end. + A :obj:`chainer.Link` object in the sequence will be added as + a child link of this object. + + :meth:`__call__` returns single or multiple features that are picked up + through a stream of computation. + These features can be specified by :obj:`layer_names`, which contains + the names of the layer whose output is collected. + When :obj:`layer_names` is a string, single value is returned. + When :obj:`layer_names` is an iterable of strings, a tuple of values + will be returned. These values are ordered in the same order of the + strings in :obj:`layer_names`. + + .. seealso:: + :obj:`chainercv.links.model.vgg.VGG16` + + The implementation is optimized for speed and memory. + A layer that is not needed to collect all features listed in + :obj:`layer_names` will not be added as a child link. + Also, this object only conducts the minimal amount of computation needed + to collect these features. + + Args: + layers (list or collections.OrderedDict of callables): + Callable objects called in the forward pass. + layer_names (string or iterable of strings): + Names of layers whose outputs will be collected in + the forward pass. + + """ def __init__(self, layers, layer_names=None): - super(SequentialExtractor, self).__init__() + super(SequentialFeatureExtractor, self).__init__() if not isinstance(layers, collections.OrderedDict): layers = collections.OrderedDict( @@ -55,7 +94,7 @@ def __call__(self, x): return features def copy(self): - ret = super(SequentialExtractor, self).copy() + ret = super(SequentialFeatureExtractor, self).copy() layers = [] for name, layer in self._layers.items(): if name in self._children: diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 5c3d77b985..7ef388505c 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -15,7 +15,7 @@ from chainercv.utils import download_model -from chainercv.links.model.sequential_extractor import SequentialExtractor +from chainercv.links.model.sequential_feature_extractor import SequentialFeatureExtractor # RGB order @@ -23,7 +23,7 @@ [123.68, 116.779, 103.939], dtype=np.float32)[:, np.newaxis, np.newaxis] -class VGG16(SequentialExtractor): +class VGG16(SequentialFeatureExtractor): """VGG16 Network for classification and feature extraction. diff --git a/docs/source/reference/links.rst b/docs/source/reference/links.rst index 763b6ffaa5..e5b041a2a3 100644 --- a/docs/source/reference/links.rst +++ b/docs/source/reference/links.rst @@ -15,6 +15,10 @@ For more details, please read :func:`VGG16Layers.predict`. links/vgg +.. autoclass:: chainercv.links.SequentialFeatureExtractor + :members: + + Detection --------- diff --git a/tests/links_tests/model_tests/test_sequential_extractor.py b/tests/links_tests/model_tests/test_sequential_feature_extractor.py similarity index 89% rename from tests/links_tests/model_tests/test_sequential_extractor.py rename to tests/links_tests/model_tests/test_sequential_feature_extractor.py index f1449ace28..65ccd434e3 100644 --- a/tests/links_tests/model_tests/test_sequential_extractor.py +++ b/tests/links_tests/model_tests/test_sequential_feature_extractor.py @@ -10,7 +10,7 @@ from chainer.function import Function -from chainercv.links import SequentialExtractor +from chainercv.links import SequentialFeatureExtractor from chainercv.utils.testing import ConstantStubLink @@ -20,7 +20,7 @@ def forward(self, inputs): return inputs[0] * 2, -class TestSequentialExtractorOrderedDictFunctions(unittest.TestCase): +class TestSequentialFeatureExtractorOrderedDictFunctions(unittest.TestCase): def setUp(self): self.l1 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) @@ -28,7 +28,7 @@ def setUp(self): self.f2 = DummyFunc() self.l2 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) - self.link = SequentialExtractor( + self.link = SequentialFeatureExtractor( collections.OrderedDict( [('l1', self.l1), ('f1', self.f1), @@ -68,7 +68,7 @@ def test_call_output_gpu(self): self.check_call_output() -class TestSequentialExtractorListFunctions(unittest.TestCase): +class TestSequentialFeatureExtractorListFunctions(unittest.TestCase): def setUp(self): self.l1 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) @@ -76,7 +76,7 @@ def setUp(self): self.f2 = DummyFunc() self.l2 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) - self.link = SequentialExtractor( + self.link = SequentialFeatureExtractor( [self.l1, self.f1, self.f2, self.l2]) self.x = np.random.uniform(size=(1, 3, 24, 24)) @@ -101,7 +101,7 @@ def test_call_output_gpu(self): self.check_call_output() -class TestSequentialExtractorCopy(unittest.TestCase): +class TestSequentialFeatureExtractorCopy(unittest.TestCase): def setUp(self): self.l1 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) @@ -109,7 +109,7 @@ def setUp(self): self.f2 = DummyFunc() self.l2 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) - self.link = SequentialExtractor( + self.link = SequentialFeatureExtractor( collections.OrderedDict( [('l1', self.l1), ('f1', self.f1), @@ -131,7 +131,7 @@ def test_copy_gpu(self): self.check_copy() -class TestSequentialExtractorRedundantLayers(unittest.TestCase): +class TestSequentialFeatureExtractorRedundantLayers(unittest.TestCase): def setUp(self): self.l1 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) @@ -139,7 +139,7 @@ def setUp(self): self.f2 = DummyFunc() self.l2 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) - self.link = SequentialExtractor( + self.link = SequentialFeatureExtractor( collections.OrderedDict( [('l1', self.l1), ('f1', self.f1), From 1c499384c904ba373dfe27738ad2432ee945abd9 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 4 Jul 2017 15:20:03 +0900 Subject: [PATCH 066/139] add doc to feature_extraction_predictor --- chainercv/links/__init__.py | 3 +- .../model/feature_extraction_predictor.py | 35 ++++++++++++++++--- docs/source/reference/links.rst | 6 ++-- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/chainercv/links/__init__.py b/chainercv/links/__init__.py index 58f4ef99fb..2bf1bc75d2 100644 --- a/chainercv/links/__init__.py +++ b/chainercv/links/__init__.py @@ -1,8 +1,9 @@ +from chainercv.links.model.feature_extraction_predictor import FeatureExtractionPredictor # NOQA from chainercv.links.model.pixelwise_softmax_classifier import PixelwiseSoftmaxClassifier # NOQA +from chainercv.links.model.sequential_feature_extractor import SequentialFeatureExtractor # NOQA from chainercv.links.model.faster_rcnn.faster_rcnn_vgg import FasterRCNNVGG16 # NOQA from chainercv.links.model.segnet.segnet_basic import SegNetBasic # NOQA -from chainercv.links.model.sequential_feature_extractor import SequentialFeatureExtractor # NOQA from chainercv.links.model.ssd import SSD300 # NOQA from chainercv.links.model.ssd import SSD512 # NOQA from chainercv.links.model.vgg import VGG16 # NOQA diff --git a/chainercv/links/model/feature_extraction_predictor.py b/chainercv/links/model/feature_extraction_predictor.py index 96e638dc63..377ff40c62 100644 --- a/chainercv/links/model/feature_extraction_predictor.py +++ b/chainercv/links/model/feature_extraction_predictor.py @@ -11,16 +11,37 @@ class FeatureExtractionPredictor(chainer.Chain): + """Wrapper around a feature extraction model to add predict method. + + This chain wraps aroudn a feature extraction model to predict features + from images. + The :meth:`predict` takes three steps to make predictions. + + 1. Preprocess images + 2. Forward the preprocessed images to the network + 3. Average features in the case when ten-crop is used. + + Example: + + >>> from chainercv.links import VGG16 + >>> from chainercv.links import FeatureExtractionPredictor + >>> base_model = VGG16(layer_names='prob') + >>> model = FeatureExtractionPredictor(base_model) + >>> prob = model.predict([img]) + + """ + def __init__(self, extractor, size=(224, 224), scale_size=256, do_ten_crop=False): + super(FeatureExtractionPredictor, self).__init__() self.scale_size = scale_size self.size = size self.do_ten_crop = do_ten_crop with self.init_scope(): self.extractor = extractor - + @property def mean(self): return self.extractor.mean @@ -28,6 +49,14 @@ def mean(self): def _prepare(self, img): """Transform an image to the input for VGG network. + This is a standard preprocessing scheme used by feature extraction + models. + First, the image is scaled so that the length of the smaller edge is + :math:`scale_size`. + Next, the image is center cropped or ten cropped to + :math:`(size, size)`. + Last, the image is mean subtracted by a mean image array :obj:`mean`. + Args: img (~numpy.ndarray): An image. This is in CHW and RGB format. The range of its value is :math:`[0, 255]`. @@ -70,8 +99,6 @@ def predict(self, imgs): When using patches from ten-crop, the features for each crop is averaged to compute one feature. - Ten-crop mode is only supported for calculation of features - :math:`fc6, fc7, fc8, prob`. Given :math:`N` input images, this outputs a batched array with batchsize :math:`N`. @@ -84,8 +111,6 @@ def predict(self, imgs): Returns: Variable or tuple of Variable: A batch of features or tuple of them. - The features to output are selected by :obj:`features` option - of :meth:`__init__`. """ imgs = [self._prepare(img) for img in imgs] diff --git a/docs/source/reference/links.rst b/docs/source/reference/links.rst index e5b041a2a3..2a60cacd90 100644 --- a/docs/source/reference/links.rst +++ b/docs/source/reference/links.rst @@ -6,9 +6,7 @@ Links Feature Extraction ------------------ - -Feature extraction links share a common method :meth:`predict` to extract features from images. -For more details, please read :func:`VGG16Layers.predict`. +Feature extraction models can be used to extract feature(s) given images. .. toctree:: @@ -18,6 +16,8 @@ For more details, please read :func:`VGG16Layers.predict`. .. autoclass:: chainercv.links.SequentialFeatureExtractor :members: +.. autoclass:: chainercv.links.FeatureExtractionPredictor + Detection --------- From 467c5005d2808686710ae7e7b5cf77fe811ff010 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 4 Jul 2017 15:54:22 +0900 Subject: [PATCH 067/139] test_feature_extraction_predictor --- .../model/feature_extraction_predictor.py | 5 +- .../test_feature_extraction_predictor.py | 93 +++++++++++++++++++ 2 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 tests/links_tests/model_tests/test_feature_extraction_predictor.py diff --git a/chainercv/links/model/feature_extraction_predictor.py b/chainercv/links/model/feature_extraction_predictor.py index 377ff40c62..397ca81b43 100644 --- a/chainercv/links/model/feature_extraction_predictor.py +++ b/chainercv/links/model/feature_extraction_predictor.py @@ -53,8 +53,7 @@ def _prepare(self, img): models. First, the image is scaled so that the length of the smaller edge is :math:`scale_size`. - Next, the image is center cropped or ten cropped to - :math:`(size, size)`. + Next, the image is center cropped or ten cropped to :math:`size`. Last, the image is mean subtracted by a mean image array :obj:`mean`. Args: @@ -109,7 +108,7 @@ def predict(self, imgs): and the range of their value is :math:`[0, 255]`. Returns: - Variable or tuple of Variable: + numpy.ndarray or tuple of numpy.ndarray: A batch of features or tuple of them. """ diff --git a/tests/links_tests/model_tests/test_feature_extraction_predictor.py b/tests/links_tests/model_tests/test_feature_extraction_predictor.py new file mode 100644 index 0000000000..726f853b2b --- /dev/null +++ b/tests/links_tests/model_tests/test_feature_extraction_predictor.py @@ -0,0 +1,93 @@ +import numpy as np +import unittest + +import chainer +from chainer import testing +from chainer.testing import attr + +from chainercv.links import FeatureExtractionPredictor + + +class DummyFeatureExtractor(chainer.Chain): + + mean = np.array([0, 0, 0]).reshape(3, 1, 1) + + def __init__(self, shape_0, shape_1): + super(DummyFeatureExtractor, self).__init__() + self.shape_0 = shape_0 + self.shape_1 = shape_1 + + def __call__(self, x): + shape = (x.shape[0],) + self.shape_0 + y0 = self.xp.random.rand(*shape).astype(np.float32) + + if self.shape_1 is None: + return chainer.Variable(y0) + shape = (x.shape[0],) + self.shape_1 + y1 = self.xp.random.rand(*shape).astype(np.float32) + return chainer.Variable(y0), chainer.Variable(y1) + + +@testing.parameterize( + {'shape_0': (5, 10, 10), 'shape_1': None, 'do_ten_crop': False}, + {'shape_0': (8,), 'shape_1': None, 'do_ten_crop': True}, + {'shape_0': (5, 10, 10), 'shape_1': (12,), 'do_ten_crop': False}, + {'shape_0': (8,), 'shape_1': (10,), 'do_ten_crop': True}, +) +class TestFeatureExtractionPredictorPredict(unittest.TestCase): + + def setUp(self): + self.link = FeatureExtractionPredictor( + DummyFeatureExtractor(self.shape_0, self.shape_1), + do_ten_crop=self.do_ten_crop) + self.x = np.random.uniform(size=(3, 3, 32, 32)).astype(np.float32) + + self.one_output = self.shape_1 is None + + def check(self, x): + out = self.link.predict(x) + if self.one_output: + self.assertEqual(out.shape, (self.x.shape[0],) + self.shape_0) + self.assertIsInstance(out, np.ndarray) + else: + out_0, out_1 = out + self.assertEqual(out_0.shape, (self.x.shape[0],) + self.shape_0) + self.assertEqual(out_1.shape, (self.x.shape[0],) + self.shape_1) + self.assertIsInstance(out_0, np.ndarray) + self.assertIsInstance(out_1, np.ndarray) + + def test_cpu(self): + self.check(self.x) + + @attr.gpu + def test_gpu(self): + self.link.to_gpu() + self.check(self.x) + + +@testing.parameterize( + {'do_ten_crop': False, 'size': (256, 192)}, + {'do_ten_crop': True, 'size': (256, 192)} +) +class TestFeatureExtractionPredictorPrepare(unittest.TestCase): + + n_channel = 3 + + def setUp(self): + self.link = FeatureExtractionPredictor( + DummyFeatureExtractor((1,), None), + size=self.size, + do_ten_crop=self.do_ten_crop) + if self.do_ten_crop: + self.expected_shape = (10, self.n_channel) + self.size + else: + self.expected_shape = (self.n_channel,) + self.size + + def test(self): + out = self.link._prepare( + np.random.uniform(size=(self.n_channel, 128, 256))) + + self.assertEqual(out.shape, self.expected_shape) + + +testing.run_module(__name__, __file__) From a1f43dd2e9808c1748a4d03e6e0b73462be972c5 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 4 Jul 2017 15:58:53 +0900 Subject: [PATCH 068/139] reflect name changes to examples/classification --- .../classification/convert_from_original/convert_vgg.py | 4 ++-- examples/classification/eval_imagenet.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/classification/convert_from_original/convert_vgg.py b/examples/classification/convert_from_original/convert_vgg.py index 2034e3f4f5..c8b4e2ce27 100644 --- a/examples/classification/convert_from_original/convert_vgg.py +++ b/examples/classification/convert_from_original/convert_vgg.py @@ -1,12 +1,12 @@ import chainer from chainer.links import VGG16Layers as VGG16Layers_chainer -from chainercv.links import VGG16Layers as VGG16Layers_cv +from chainercv.links import VGG16 as VGG16_cv def main(): chainer_model = VGG16Layers_chainer() - cv_model = VGG16Layers_cv(pretrained_model=None, n_class=1000) + cv_model = VGG16_cv(pretrained_model=None, n_class=1000) cv_model.conv1_1.copyparams(chainer_model.conv1_1) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index e82924b4d3..7b890194e4 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -9,7 +9,7 @@ from chainer import iterators from chainercv.datasets import DirectoryParsingClassificationDataset -from chainercv.links import VGG16Layers +from chainercv.links import VGG16 from chainercv.utils import apply_prediction_to_iterator @@ -50,9 +50,9 @@ def main(): if args.model == 'vgg16': if args.pretrained_model: - model = VGG16Layers(pretrained_model=args.pretrained_model) + model = VGG16(pretrained_model=args.pretrained_model) else: - model = VGG16Layers(pretrained_model='imagenet') + model = VGG16(pretrained_model='imagenet') if args.gpu >= 0: chainer.cuda.get_device(args.gpu).use() From 011b3209d417d85e7a82a7a2fe9f5e9ca9e8e20d Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 4 Jul 2017 16:22:10 +0900 Subject: [PATCH 069/139] update eval_imagenet and README --- examples/classification/README.md | 14 ++++---------- examples/classification/eval_imagenet.py | 2 ++ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/examples/classification/README.md b/examples/classification/README.md index d79f3d129f..663d926b83 100644 --- a/examples/classification/README.md +++ b/examples/classification/README.md @@ -2,11 +2,12 @@ ## Performance -| Model | Top 1 Error | Original Top 1 Error | +| Model | Top 1 Error (single crop) | Reference Top 1 Error (single crop) | |:-:|:-:|:-:| -| VGG16 | 27.06 % | 27.0 % [1] | +| VGG16 | 29.0 % | 28.5 % [1] | -The results can be reproduced by the following command +The results can be reproduced by the following command. +The score is reported using a weight converted from a weight trained by Caffe. ``` $ python eval_imagenet.py [--model vgg16] [--pretrained_model ] [--batchsize ] [--gpu ] @@ -35,13 +36,6 @@ The ImageNet Large Scale Visual Recognition Challenge (ILSVRC) dataset has 1000 wget -qO- https://raw.githubusercontent.com/soumith/imagenetloader.torch/master/valprep.sh | bash ``` -## Note on implementations - -#### VGG16 - -In the original paper, fully connected layers are used as convolutional layers, and the final output is the spatial average of the outputs of the convolutions. -Our implementation averages predictions from ten-cropped patches. - ## References diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index 7b890194e4..3dc0835d52 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -9,6 +9,7 @@ from chainer import iterators from chainercv.datasets import DirectoryParsingClassificationDataset +from chainercv.links import FeatureExtractionPredictor from chainercv.links import VGG16 from chainercv.utils import apply_prediction_to_iterator @@ -53,6 +54,7 @@ def main(): model = VGG16(pretrained_model=args.pretrained_model) else: model = VGG16(pretrained_model='imagenet') + model = FeatureExtractionPredictor(model) if args.gpu >= 0: chainer.cuda.get_device(args.gpu).use() From ae5fd2dfcba227aead073ef19af1b6650388d191 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 4 Jul 2017 16:29:28 +0900 Subject: [PATCH 070/139] fix flake8 --- chainercv/links/model/sequential_feature_extractor.py | 2 +- chainercv/links/model/vgg/vgg16.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/chainercv/links/model/sequential_feature_extractor.py b/chainercv/links/model/sequential_feature_extractor.py index 223207227f..39e9a5cf17 100644 --- a/chainercv/links/model/sequential_feature_extractor.py +++ b/chainercv/links/model/sequential_feature_extractor.py @@ -69,7 +69,7 @@ def __init__(self, layers, layer_names=None): # Delete unnecessary layers from self._layers based on layer_names. # Computation is equivalent to layers = layers[:last_index + 1]. last_index = max([list(self._layers.keys()).index(name) for - name in self._layer_names]) + name in self._layer_names]) self._layers = collections.OrderedDict( islice(self._layers.items(), None, last_index + 1)) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 7ef388505c..1639779299 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -15,7 +15,8 @@ from chainercv.utils import download_model -from chainercv.links.model.sequential_feature_extractor import SequentialFeatureExtractor +from chainercv.links.model.sequential_feature_extractor import \ + SequentialFeatureExtractor # RGB order From c8cdc36586f47208e50be9ae93c5a40746349a7e Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 5 Jul 2017 10:13:31 +0900 Subject: [PATCH 071/139] fix error for python3 --- chainercv/links/model/sequential_feature_extractor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainercv/links/model/sequential_feature_extractor.py b/chainercv/links/model/sequential_feature_extractor.py index 39e9a5cf17..496dec2e80 100644 --- a/chainercv/links/model/sequential_feature_extractor.py +++ b/chainercv/links/model/sequential_feature_extractor.py @@ -55,7 +55,7 @@ def __init__(self, layers, layer_names=None): self._layers = layers if layer_names is None: - layer_names = self._layers.keys()[-1] + layer_names = list(self._layers.keys())[-1] if (not isinstance(layer_names, str) and all([isinstance(name, str) for name in layer_names])): From 268e781d98398a06c67670d9e2bcfd95c25720b9 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 5 Jul 2017 10:28:27 +0900 Subject: [PATCH 072/139] improve doc --- .../model/sequential_feature_extractor.py | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/chainercv/links/model/sequential_feature_extractor.py b/chainercv/links/model/sequential_feature_extractor.py index 496dec2e80..3525bf272d 100644 --- a/chainercv/links/model/sequential_feature_extractor.py +++ b/chainercv/links/model/sequential_feature_extractor.py @@ -27,8 +27,21 @@ class SequentialFeatureExtractor(chainer.Chain): will be returned. These values are ordered in the same order of the strings in :obj:`layer_names`. - .. seealso:: - :obj:`chainercv.links.model.vgg.VGG16` + Examples: + + >>> import collections + >>> import chainer.functions as F + >>> import chainer.links as L + >>> layers = collections.OrderedDict([ + >>> ('l1', L.Linear(None, 1000)), + >>> ('l1_relu', F.relu), + >>> ('l2', L.Linear(None, 1000)), + >>> ('l2_relu', F.relu), + >>> ('l3', L.Linear(None, 10))]) + >>> model = SequentialFeatureExtractor(layers, ['l2_relu', 'l3']) + >>> # These are outputs of l2_relu and l3 layers. + >>> feat1, feat2 = model(imgs) + The implementation is optimized for speed and memory. A layer that is not needed to collect all features listed in @@ -79,6 +92,16 @@ def __init__(self, layers, layer_names=None): setattr(self, name, layer) def __call__(self, x): + """Forward sequential feature extraction model. + + Args: + x (chainer.Variable or array): Input to the network. + + Returns: + chainer.Variable or tuple of chainer.Variable: + The returned values are determined by :obj:`layer_names`. + + """ features = {} h = x for name, layer in self._layers.items(): From ebe9340798c9ab061db91a2e5f07dca430b87a49 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 5 Jul 2017 10:30:32 +0900 Subject: [PATCH 073/139] fix feature_extraction_predictor.predict --- chainercv/links/model/feature_extraction_predictor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/chainercv/links/model/feature_extraction_predictor.py b/chainercv/links/model/feature_extraction_predictor.py index 397ca81b43..2fc6b793b8 100644 --- a/chainercv/links/model/feature_extraction_predictor.py +++ b/chainercv/links/model/feature_extraction_predictor.py @@ -112,8 +112,9 @@ def predict(self, imgs): A batch of features or tuple of them. """ - imgs = [self._prepare(img) for img in imgs] - imgs = self.xp.asarray(imgs).reshape(-1, 3, 224, 224) + imgs = self.xp.asarray([self._prepare(img) for img in imgs]) + shape = (-1, imgs.shape[-3]) + self.size + imgs = imgs.reshape(shape) with chainer.function.no_backprop_mode(): imgs = chainer.Variable(imgs) From 8f65c028285a5470957e0f9c37cd6172f27b445d Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Wed, 5 Jul 2017 10:36:12 +0900 Subject: [PATCH 074/139] fix doc --- chainercv/links/model/feature_extraction_predictor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainercv/links/model/feature_extraction_predictor.py b/chainercv/links/model/feature_extraction_predictor.py index 2fc6b793b8..0c525f006a 100644 --- a/chainercv/links/model/feature_extraction_predictor.py +++ b/chainercv/links/model/feature_extraction_predictor.py @@ -109,7 +109,7 @@ def predict(self, imgs): Returns: numpy.ndarray or tuple of numpy.ndarray: - A batch of features or tuple of them. + A batch of features or a tuple of them. """ imgs = self.xp.asarray([self._prepare(img) for img in imgs]) From 80df6a926afeb021d463ba0c69f1d2e89cf543e0 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 6 Jul 2017 13:04:28 +0900 Subject: [PATCH 075/139] reorder arguments --- chainercv/links/model/vgg/vgg16.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 1639779299..e2b5b8cc87 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -57,6 +57,8 @@ class VGG16(SequentialFeatureExtractor): `_. Args: + layer_names (str or iterable of strings): The names of the feature to + output with :meth:`__call__` and :meth:`predict`. pretrained_model (str): The destination of the pre-trained chainer model serialized as a :obj:`.npz` file. If this is one of the strings described @@ -69,15 +71,8 @@ class VGG16(SequentialFeatureExtractor): mean (numpy.ndarray): A mean image. If :obj:`None` and a supported pretrained model is used, the mean image used to train the pretrained model will be used. - features (str or iterable of strings): The names of the feature to - output with :meth:`__call__` and :meth:`predict`. initialW (callable): Initializer for the weights. initial_bias (callable): Initializer for the biases. - mean (numpy.ndarray): A value to be subtracted from an image - in :meth:`_prepare`. - do_ten_crop (bool): If :obj:`True`, it averages results across - center, corners, and mirrors in :meth:`predict`. Otherwise, it uses - only the center. The default value is :obj:`False`. """ @@ -90,8 +85,9 @@ class VGG16(SequentialFeatureExtractor): } } - def __init__(self, pretrained_model=None, n_class=None, mean=None, - layer_names='prob', initialW=None, initial_bias=None): + def __init__(self, layer_names='prob', + pretrained_model=None, n_class=None, mean=None, + initialW=None, initial_bias=None): if n_class is None: if (pretrained_model not in self._models and any([name in ['fc8', 'prob'] for name in layer_names])): From 320174d310a94f6cefc22cb3f8864c1525f54d3d Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 6 Jul 2017 20:18:30 +0900 Subject: [PATCH 076/139] use initialize at runtime --- chainercv/links/model/vgg/vgg16.py | 44 +++++++------------ .../model_tests/vgg_tests/test_vgg16.py | 28 ------------ 2 files changed, 16 insertions(+), 56 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index e2b5b8cc87..c0d800f2ad 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -116,57 +116,45 @@ def __init__(self, layer_names='prob', initial_bias = constant.Zero() kwargs = {'initialW': initialW, 'initial_bias': initial_bias} - # Since fc layers take long time to instantiate, - # avoid doing so whenever possible. - fc_layer_names = ['fc6', 'fc6_relu', 'fc6_dropout', - 'fc7', 'fc7_relu', 'fc7_dropout', 'fc8', 'prob'] - if (any([name in fc_layer_names for name in layer_names]) - or layer_names in fc_layer_names): - fc_kwargs = {'initialW': constant.Zero(), - 'initial_bias': constant.Zero()} - else: - fc_kwargs = kwargs - - # The links are instantiated once it is decided to use them. layers = collections.OrderedDict([ - ('conv1_1', Convolution2D(3, 64, 3, 1, 1, **kwargs)), + ('conv1_1', Convolution2D(None, 64, 3, 1, 1, **kwargs)), ('conv1_1_relu', relu), - ('conv1_2', Convolution2D(64, 64, 3, 1, 1, **kwargs)), + ('conv1_2', Convolution2D(None, 64, 3, 1, 1, **kwargs)), ('conv1_2_relu', relu), ('pool1', _max_pooling_2d), - ('conv2_1', Convolution2D(64, 128, 3, 1, 1, **kwargs)), + ('conv2_1', Convolution2D(None, 128, 3, 1, 1, **kwargs)), ('conv2_1_relu', relu), - ('conv2_2', Convolution2D(128, 128, 3, 1, 1, **kwargs)), + ('conv2_2', Convolution2D(None, 128, 3, 1, 1, **kwargs)), ('conv2_2_relu', relu), ('pool2', _max_pooling_2d), - ('conv3_1', Convolution2D(128, 256, 3, 1, 1, **kwargs)), + ('conv3_1', Convolution2D(None, 256, 3, 1, 1, **kwargs)), ('conv3_1_relu', relu), - ('conv3_2', Convolution2D(256, 256, 3, 1, 1, **kwargs)), + ('conv3_2', Convolution2D(None, 256, 3, 1, 1, **kwargs)), ('conv3_2_relu', relu), - ('conv3_3', Convolution2D(256, 256, 3, 1, 1, **kwargs)), + ('conv3_3', Convolution2D(None, 256, 3, 1, 1, **kwargs)), ('conv3_3_relu', relu), ('pool3', _max_pooling_2d), - ('conv4_1', Convolution2D(256, 512, 3, 1, 1, **kwargs)), + ('conv4_1', Convolution2D(None, 512, 3, 1, 1, **kwargs)), ('conv4_1_relu', relu), - ('conv4_2', Convolution2D(512, 512, 3, 1, 1, **kwargs)), + ('conv4_2', Convolution2D(None, 512, 3, 1, 1, **kwargs)), ('conv4_2_relu', relu), - ('conv4_3', Convolution2D(512, 512, 3, 1, 1, **kwargs)), + ('conv4_3', Convolution2D(None, 512, 3, 1, 1, **kwargs)), ('conv4_3_relu', relu), ('pool4', _max_pooling_2d), - ('conv5_1', Convolution2D(512, 512, 3, 1, 1, **kwargs)), + ('conv5_1', Convolution2D(None, 512, 3, 1, 1, **kwargs)), ('conv5_1_relu', relu), - ('conv5_2', Convolution2D(512, 512, 3, 1, 1, **kwargs)), + ('conv5_2', Convolution2D(None, 512, 3, 1, 1, **kwargs)), ('conv5_2_relu', relu), - ('conv5_3', Convolution2D(512, 512, 3, 1, 1, **kwargs)), + ('conv5_3', Convolution2D(None, 512, 3, 1, 1, **kwargs)), ('conv5_3_relu', relu), ('pool5', _max_pooling_2d), - ('fc6', Linear(512 * 7 * 7, 4096, **fc_kwargs)), + ('fc6', Linear(None, 4096, **kwargs)), ('fc6_relu', relu), ('fc6_dropout', dropout), - ('fc7', Linear(4096, 4096, **fc_kwargs)), + ('fc7', Linear(None, 4096, **kwargs)), ('fc7_relu', relu), ('fc7_dropout', dropout), - ('fc8', Linear(4096, n_class, **fc_kwargs)), + ('fc8', Linear(None, n_class, **kwargs)), ('prob', softmax) ]) diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py index a47ddd9cf9..eca693c1f4 100644 --- a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -66,32 +66,4 @@ def test_copy_gpu(self): self.check_copy() -@testing.parameterize( - {'layer_names': 'pool4', - 'not_attribute': ['conv5_1', 'conv5_2', 'conv5_3', 'fc6', 'fc7', 'fc8'], - }, - {'layer_names': ['pool5', 'pool4'], - 'not_attribute': ['fc6', 'fc7', 'fc8'], - } -) -class TestVGG16FeatureOption(unittest.TestCase): - - def setUp(self): - self.link = VGG16( - pretrained_model=None, layer_names=self.layer_names, - initialW=Zero(), initial_bias=Zero()) - - def check_feature_option(self): - for name in self.not_attribute: - self.assertTrue(not hasattr(self.link, name)) - - def test_feature_option_cpu(self): - self.check_feature_option() - - @attr.gpu - def test_feature_option_gpu(self): - self.link.to_gpu() - self.check_feature_option() - - testing.run_module(__name__, __file__) From 55c50c97c14a7169317cbdc8f006f55cda9c1a11 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 6 Jul 2017 20:33:00 +0900 Subject: [PATCH 077/139] make layer_names dynamically changeable --- .../model/sequential_feature_extractor.py | 53 ++++++++++-------- .../test_sequential_feature_extractor.py | 55 +++++++------------ 2 files changed, 50 insertions(+), 58 deletions(-) diff --git a/chainercv/links/model/sequential_feature_extractor.py b/chainercv/links/model/sequential_feature_extractor.py index 3525bf272d..651834d03f 100644 --- a/chainercv/links/model/sequential_feature_extractor.py +++ b/chainercv/links/model/sequential_feature_extractor.py @@ -1,5 +1,4 @@ import collections -from itertools import islice import chainer @@ -38,16 +37,13 @@ class SequentialFeatureExtractor(chainer.Chain): >>> ('l2', L.Linear(None, 1000)), >>> ('l2_relu', F.relu), >>> ('l3', L.Linear(None, 10))]) - >>> model = SequentialFeatureExtractor(layers, ['l2_relu', 'l3']) - >>> # These are outputs of l2_relu and l3 layers. - >>> feat1, feat2 = model(imgs) - - - The implementation is optimized for speed and memory. - A layer that is not needed to collect all features listed in - :obj:`layer_names` will not be added as a child link. - Also, this object only conducts the minimal amount of computation needed - to collect these features. + >>> model = SequentialFeatureExtractor(layers, ['l2_relu', 'l1_relu']) + >>> # These are outputs of layer l2_relu and l1_relu. + >>> feat1, feat2 = model(x) + >>> # The layer_names can be dynamically changed. + >>> model.layer_names = 'l3' + >>> # This is an output of layer l1. + >>> feat3 = model(x) Args: layers (list or collections.OrderedDict of callables): @@ -67,6 +63,19 @@ def __init__(self, layers, layer_names=None): for i, layer in enumerate(layers)]) self._layers = layers + self.layer_names = layer_names + + with self.init_scope(): + for name, layer in self._layers.items(): + if isinstance(layer, chainer.Link): + setattr(self, name, layer) + + @property + def layer_names(self): + return self._layer_names + + @layer_names.setter + def layer_names(self, layer_names): if layer_names is None: layer_names = list(self._layers.keys())[-1] @@ -76,21 +85,12 @@ def __init__(self, layers, layer_names=None): else: return_tuple = False layer_names = [layer_names] + if any([name not in self._layers for name in layer_names]): + raise ValueError('Invalid layer name') + self._return_tuple = return_tuple self._layer_names = layer_names - # Delete unnecessary layers from self._layers based on layer_names. - # Computation is equivalent to layers = layers[:last_index + 1]. - last_index = max([list(self._layers.keys()).index(name) for - name in self._layer_names]) - self._layers = collections.OrderedDict( - islice(self._layers.items(), None, last_index + 1)) - - with self.init_scope(): - for name, layer in self._layers.items(): - if isinstance(layer, chainer.Link): - setattr(self, name, layer) - def __call__(self, x): """Forward sequential feature extraction model. @@ -102,9 +102,14 @@ def __call__(self, x): The returned values are determined by :obj:`layer_names`. """ + # The biggest index among indices of the layers that are included + # in self._layer_names. + last_index = max([list(self._layers.keys()).index(name) for + name in self._layer_names]) + features = {} h = x - for name, layer in self._layers.items(): + for name, layer in list(self._layers.items())[:last_index + 1]: h = layer(h) if name in self._layer_names: features[name] = h diff --git a/tests/links_tests/model_tests/test_sequential_feature_extractor.py b/tests/links_tests/model_tests/test_sequential_feature_extractor.py index 65ccd434e3..24412581cd 100644 --- a/tests/links_tests/model_tests/test_sequential_feature_extractor.py +++ b/tests/links_tests/model_tests/test_sequential_feature_extractor.py @@ -34,30 +34,26 @@ def setUp(self): ('f1', self.f1), ('f2', self.f2), ('l2', self.l2)]), - layer_names=['l1', 'f1', 'f2', 'l2']) + layer_names=['l1', 'f1', 'f2']) self.x = np.random.uniform(size=(1, 3, 24, 24)) def check_call_output(self): x = self.link.xp.asarray(self.x) out = self.link(x) - self.assertEqual(len(out), 4) + self.assertEqual(len(out), 3) self.assertIsInstance(out[0], chainer.Variable) self.assertIsInstance(out[1], chainer.Variable) self.assertIsInstance(out[2], chainer.Variable) - self.assertIsInstance(out[3], chainer.Variable) self.assertIsInstance(out[0].data, self.link.xp.ndarray) self.assertIsInstance(out[1].data, self.link.xp.ndarray) self.assertIsInstance(out[2].data, self.link.xp.ndarray) - self.assertIsInstance(out[3].data, self.link.xp.ndarray) out_data = [to_cpu(var.data) for var in out] np.testing.assert_equal(out_data[0], to_cpu(self.l1(x).data)) np.testing.assert_equal(out_data[1], to_cpu(self.f1(self.l1(x)).data)) np.testing.assert_equal( out_data[2], to_cpu(self.f2(self.f1(self.l1(x))).data)) - np.testing.assert_equal( - out_data[3], to_cpu(self.l2(self.f2(self.f1(self.l1(x)))).data)) def test_call_output_cpu(self): self.check_call_output() @@ -67,6 +63,25 @@ def test_call_output_gpu(self): self.link.to_gpu() self.check_call_output() + def check_call_dynamic_layer_names(self): + x = self.link.xp.asarray(self.x) + self.link.layer_names = ['l2'] + out, = self.link(x) + + self.assertIsInstance(out, chainer.Variable) + self.assertIsInstance(out.data, self.link.xp.ndarray) + + out_data = out.data + np.testing.assert_equal( + out_data, to_cpu(self.l2(self.f2(self.f1(self.l1(x)))).data)) + + def test_call_dynamic_layer_names_cpu(self): + self.check_call_dynamic_layer_names() + + @attr.gpu + def test_call_dynamic_layer_names_gpu(self): + self.check_call_dynamic_layer_names() + class TestSequentialFeatureExtractorListFunctions(unittest.TestCase): @@ -131,32 +146,4 @@ def test_copy_gpu(self): self.check_copy() -class TestSequentialFeatureExtractorRedundantLayers(unittest.TestCase): - - def setUp(self): - self.l1 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) - self.f1 = DummyFunc() - self.f2 = DummyFunc() - self.l2 = ConstantStubLink(np.random.uniform(size=(1, 3, 24, 24))) - - self.link = SequentialFeatureExtractor( - collections.OrderedDict( - [('l1', self.l1), - ('f1', self.f1), - ('f2', self.f2), - ('l2', self.l2)]), - layer_names=['l1', 'f1']) - - def check_redundant_layers(self): - self.assertNotIn('f2', self.link._layer_names) - self.assertNotIn('l2', self.link._layer_names) - - def test_redundant_layers_cpu(self): - self.check_redundant_layers() - - @attr.gpu - def test_redundant_layers_gpu(self): - self.check_redundant_layers() - - testing.run_module(__name__, __file__) From a232abcde4fceb0166efd3e0ff0bc731eb9ed89c Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Sat, 15 Jul 2017 19:25:50 +0900 Subject: [PATCH 078/139] use updated interface of SequentialFeatureExtractor --- chainercv/links/model/vgg/vgg16.py | 7 ++++++- .../model_tests/vgg_tests/test_vgg16.py | 19 ------------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index c0d800f2ad..1e49ebef02 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -158,7 +158,12 @@ def __init__(self, layer_names='prob', ('prob', softmax) ]) - super(VGG16, self).__init__(layers, layer_names) + super(VGG16, self).__init__() + with self.init_scope(): + for name, layer in layers.items(): + setattr(self, name, layer) + + self.layer_names = layer_names if pretrained_model in self._models: path = download_model(self._models[pretrained_model]['url']) diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py index eca693c1f4..be6af33be3 100644 --- a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -46,24 +46,5 @@ def test_call_gpu(self): self.check_call() -class TestVGG16Copy(unittest.TestCase): - - def setUp(self): - self.link = VGG16(pretrained_model=None, n_class=200, - layer_names='conv2_2', - initialW=Zero(), initial_bias=Zero()) - - def check_copy(self): - copied = self.link.copy() - self.assertIs(copied.conv1_1, copied.layers['conv1_1']) - - def test_copy_cpu(self): - self.check_copy() - - @attr.gpu - def test_copy_gpu(self): - self.link.to_gpu() - self.check_copy() - testing.run_module(__name__, __file__) From 348ae8533677aadcdbecb6cf153faea173609bd5 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Sat, 15 Jul 2017 19:34:43 +0900 Subject: [PATCH 079/139] fix doc --- chainercv/links/model/vgg/vgg16.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 1e49ebef02..f7da65d6fa 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -31,19 +31,24 @@ class VGG16(SequentialFeatureExtractor): This model is a feature extraction link. The network can choose to output features from set of all intermediate and final features produced by the original architecture. - The output features can be an array or tuple of arrays. - When :obj:`features` is an iterable of strings, outputs will be tuple. - When :obj:`features` is a string, output will be an array. + By setting :obj:`VGG16.layer_names`, the kind of features can be selected. Examples: - >>> model = VGG16(features='conv5_3') + >>> model = VGG16() + # By default, VGG16.__call__ returns a probability score. + >>> prob = model(imgs) + + >>> model.layer_names = 'conv5_3' # This is an activation of conv5_3 layer. - >>> feat = model(imgs) + >>> feat5_3 = model(imgs) - >>> model = VGG16(features=['conv5_3', 'fc6']) + >>> model.layer_names = ['conv5_3', 'fc6'] >>> # These are activations of conv5_3 and fc6 layers respectively. - >>> feat1, feat2 = model(imgs) + >>> feat5_3, feat6 = model(imgs) + + .. seealso:: + :class:`chainercv.links.model.SequentialFeatureExtractor` When :obj:`pretrained_model` is the path of a pre-trained chainer model serialized as a :obj:`.npz` file in the constructor, this chain model @@ -58,7 +63,7 @@ class VGG16(SequentialFeatureExtractor): Args: layer_names (str or iterable of strings): The names of the feature to - output with :meth:`__call__` and :meth:`predict`. + output with :meth:`__call__`. pretrained_model (str): The destination of the pre-trained chainer model serialized as a :obj:`.npz` file. If this is one of the strings described From 2940e0d114ed9396ea4fe2fca3744ec112083660 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 18 Jul 2017 09:53:10 +0900 Subject: [PATCH 080/139] it is not necessary to do a trick to save initialization time --- chainercv/links/model/vgg/vgg16.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index f7da65d6fa..563e8c5d95 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -106,19 +106,11 @@ def __init__(self, layer_names='prob', mean = self._models[pretrained_model]['mean'] self.mean = mean - if pretrained_model: - # As a sampling process is time-consuming, - # we employ a zero initializer for faster computation. - if initialW is None: - initialW = constant.Zero() - if initial_bias is None: - initial_bias = constant.Zero() - else: - # Employ default initializers used in the original paper. - if initialW is None: - initialW = normal.Normal(0.01) - if initial_bias is None: - initial_bias = constant.Zero() + # Employ default initializers used in the original paper. + if initialW is None: + initialW = normal.Normal(0.01) + if initial_bias is None: + initial_bias = constant.Zero() kwargs = {'initialW': initialW, 'initial_bias': initial_bias} layers = collections.OrderedDict([ From 3b24821c8ef382fd7ce8abc6aef140db13a8ef86 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 18 Jul 2017 10:00:40 +0900 Subject: [PATCH 081/139] use block --- chainercv/links/model/vgg/vgg16.py | 66 ++++++++++++++++++------------ 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 563e8c5d95..472e084d11 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -10,6 +10,7 @@ from chainer.functions import softmax from chainer.initializers import constant from chainer.initializers import normal +from chainer.links import BatchNormalization from chainer.links import Convolution2D from chainer.links import Linear @@ -24,6 +25,32 @@ [123.68, 116.779, 103.939], dtype=np.float32)[:, np.newaxis, np.newaxis] + +class Block(chainer.Chain): + + def __init__(self, in_channels, out_channels, ksize=None, stride=1, pad=0, + nobias=False, initialW=None, initial_bias=None, + activation=relu, use_bn=False, + decay=0.9, eps=2e-5, + use_gamma=True, use_beta=True, + initial_gamma=None, initial_beta=None): + self.use_bn = use_bn + self.activataion = activataion + with self.init_scope(): + self.conv = Convolution2D(in_channels, out_channels, ksize, stride, + pad, nobias, initialW, initial_bias) + if self.use_bn: + self.bn = BatchNormalization( + None, decay, eps, use_gamma, use_beta, + initial_gamma, initial_beta) + + def __call__(self, x): + h = self.conv(x) + if self.use_bn: + h = self.bn(h) + return self.activataion(h) + + class VGG16(SequentialFeatureExtractor): """VGG16 Network for classification and feature extraction. @@ -114,36 +141,23 @@ def __init__(self, layer_names='prob', kwargs = {'initialW': initialW, 'initial_bias': initial_bias} layers = collections.OrderedDict([ - ('conv1_1', Convolution2D(None, 64, 3, 1, 1, **kwargs)), - ('conv1_1_relu', relu), - ('conv1_2', Convolution2D(None, 64, 3, 1, 1, **kwargs)), - ('conv1_2_relu', relu), + ('conv1_1', Block(None, 64, 3, 1, 1, **kwargs)), + ('conv1_2', Block(None, 64, 3, 1, 1, **kwargs)), ('pool1', _max_pooling_2d), - ('conv2_1', Convolution2D(None, 128, 3, 1, 1, **kwargs)), - ('conv2_1_relu', relu), - ('conv2_2', Convolution2D(None, 128, 3, 1, 1, **kwargs)), - ('conv2_2_relu', relu), + ('conv2_1', Block(None, 128, 3, 1, 1, **kwargs)), + ('conv2_2', Block(None, 128, 3, 1, 1, **kwargs)), ('pool2', _max_pooling_2d), - ('conv3_1', Convolution2D(None, 256, 3, 1, 1, **kwargs)), - ('conv3_1_relu', relu), - ('conv3_2', Convolution2D(None, 256, 3, 1, 1, **kwargs)), - ('conv3_2_relu', relu), - ('conv3_3', Convolution2D(None, 256, 3, 1, 1, **kwargs)), - ('conv3_3_relu', relu), + ('conv3_1', Block(None, 256, 3, 1, 1, **kwargs)), + ('conv3_2', Block(None, 256, 3, 1, 1, **kwargs)), + ('conv3_3', Block(None, 256, 3, 1, 1, **kwargs)), ('pool3', _max_pooling_2d), - ('conv4_1', Convolution2D(None, 512, 3, 1, 1, **kwargs)), - ('conv4_1_relu', relu), - ('conv4_2', Convolution2D(None, 512, 3, 1, 1, **kwargs)), - ('conv4_2_relu', relu), - ('conv4_3', Convolution2D(None, 512, 3, 1, 1, **kwargs)), - ('conv4_3_relu', relu), + ('conv4_1', Block(None, 512, 3, 1, 1, **kwargs)), + ('conv4_2', Block(None, 512, 3, 1, 1, **kwargs)), + ('conv4_3', Block(None, 512, 3, 1, 1, **kwargs)), ('pool4', _max_pooling_2d), - ('conv5_1', Convolution2D(None, 512, 3, 1, 1, **kwargs)), - ('conv5_1_relu', relu), - ('conv5_2', Convolution2D(None, 512, 3, 1, 1, **kwargs)), - ('conv5_2_relu', relu), - ('conv5_3', Convolution2D(None, 512, 3, 1, 1, **kwargs)), - ('conv5_3_relu', relu), + ('conv5_1', Block(None, 512, 3, 1, 1, **kwargs)), + ('conv5_2', Block(None, 512, 3, 1, 1, **kwargs)), + ('conv5_3', Block(None, 512, 3, 1, 1, **kwargs)), ('pool5', _max_pooling_2d), ('fc6', Linear(None, 4096, **kwargs)), ('fc6_relu', relu), From 8b408c60ee953f67ea82aa0fbbc1eb9d248066f5 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 18 Jul 2017 10:11:50 +0900 Subject: [PATCH 082/139] simplify init_scope --- chainercv/links/model/vgg/vgg16.py | 73 +++++++++++++----------------- 1 file changed, 31 insertions(+), 42 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 472e084d11..131d643a28 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -1,6 +1,5 @@ from __future__ import division -import collections import numpy as np import chainer @@ -25,7 +24,6 @@ [123.68, 116.779, 103.939], dtype=np.float32)[:, np.newaxis, np.newaxis] - class Block(chainer.Chain): def __init__(self, in_channels, out_channels, ksize=None, stride=1, pad=0, @@ -35,7 +33,7 @@ def __init__(self, in_channels, out_channels, ksize=None, stride=1, pad=0, use_gamma=True, use_beta=True, initial_gamma=None, initial_beta=None): self.use_bn = use_bn - self.activataion = activataion + self.activaton = activation with self.init_scope(): self.conv = Convolution2D(in_channels, out_channels, ksize, stride, pad, nobias, initialW, initial_bias) @@ -117,16 +115,14 @@ class VGG16(SequentialFeatureExtractor): } } - def __init__(self, layer_names='prob', + def __init__(self, pretrained_model=None, n_class=None, mean=None, initialW=None, initial_bias=None): if n_class is None: - if (pretrained_model not in self._models and - any([name in ['fc8', 'prob'] for name in layer_names])): - raise ValueError( - 'The n_class needs to be supplied as an argument.') - elif pretrained_model: + if pretrained_model in self._models: n_class = self._models[pretrained_model]['n_class'] + else: + n_class = 1000 if mean is None: if pretrained_model in self._models: @@ -140,41 +136,34 @@ def __init__(self, layer_names='prob', initial_bias = constant.Zero() kwargs = {'initialW': initialW, 'initial_bias': initial_bias} - layers = collections.OrderedDict([ - ('conv1_1', Block(None, 64, 3, 1, 1, **kwargs)), - ('conv1_2', Block(None, 64, 3, 1, 1, **kwargs)), - ('pool1', _max_pooling_2d), - ('conv2_1', Block(None, 128, 3, 1, 1, **kwargs)), - ('conv2_2', Block(None, 128, 3, 1, 1, **kwargs)), - ('pool2', _max_pooling_2d), - ('conv3_1', Block(None, 256, 3, 1, 1, **kwargs)), - ('conv3_2', Block(None, 256, 3, 1, 1, **kwargs)), - ('conv3_3', Block(None, 256, 3, 1, 1, **kwargs)), - ('pool3', _max_pooling_2d), - ('conv4_1', Block(None, 512, 3, 1, 1, **kwargs)), - ('conv4_2', Block(None, 512, 3, 1, 1, **kwargs)), - ('conv4_3', Block(None, 512, 3, 1, 1, **kwargs)), - ('pool4', _max_pooling_2d), - ('conv5_1', Block(None, 512, 3, 1, 1, **kwargs)), - ('conv5_2', Block(None, 512, 3, 1, 1, **kwargs)), - ('conv5_3', Block(None, 512, 3, 1, 1, **kwargs)), - ('pool5', _max_pooling_2d), - ('fc6', Linear(None, 4096, **kwargs)), - ('fc6_relu', relu), - ('fc6_dropout', dropout), - ('fc7', Linear(None, 4096, **kwargs)), - ('fc7_relu', relu), - ('fc7_dropout', dropout), - ('fc8', Linear(None, n_class, **kwargs)), - ('prob', softmax) - ]) - super(VGG16, self).__init__() with self.init_scope(): - for name, layer in layers.items(): - setattr(self, name, layer) - - self.layer_names = layer_names + self.conv1_1 = Block(None, 64, 3, 1, 1, **kwargs) + self.conv1_2 = Block(None, 64, 3, 1, 1, **kwargs) + self.pool1 = _max_pooling_2d + self.conv2_1 = Block(None, 128, 3, 1, 1, **kwargs) + self.conv2_2 = Block(None, 128, 3, 1, 1, **kwargs) + self.pool2 = _max_pooling_2d + self.conv3_1 = Block(None, 256, 3, 1, 1, **kwargs) + self.conv3_2 = Block(None, 256, 3, 1, 1, **kwargs) + self.conv3_3 = Block(None, 256, 3, 1, 1, **kwargs) + self.pool3 = _max_pooling_2d + self.conv4_1 = Block(None, 512, 3, 1, 1, **kwargs) + self.conv4_2 = Block(None, 512, 3, 1, 1, **kwargs) + self.conv4_3 = Block(None, 512, 3, 1, 1, **kwargs) + self.pool4 = _max_pooling_2d + self.conv5_1 = Block(None, 512, 3, 1, 1, **kwargs) + self.conv5_2 = Block(None, 512, 3, 1, 1, **kwargs) + self.conv5_3 = Block(None, 512, 3, 1, 1, **kwargs) + self.pool5 = _max_pooling_2d + self.fc6 = Linear(None, 4096, **kwargs) + self.fc6_relu = relu + self.fc6_dropout = dropout + self.fc7 = Linear(None, 4096, **kwargs) + self.fc7_relu = relu + self.fc7_dropout = Linear(None, 4096, **kwargs) + self.fc8 = Linear(None, n_class, **kwargs) + self.prob = softmax if pretrained_model in self._models: path = download_model(self._models[pretrained_model]['url']) From 578b3aa0548b1af4a62321fa81fe11768a95f31c Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 18 Jul 2017 12:09:24 +0900 Subject: [PATCH 083/139] add convolution_2d_block --- chainercv/links/__init__.py | 2 + chainercv/links/connection/__init__.py | 1 + .../links/connection/convolution_2d_block.py | 50 +++++++++++++++++ chainercv/links/model/vgg/vgg16.py | 55 +++++-------------- .../test_convolution_2d_block.py | 50 +++++++++++++++++ 5 files changed, 118 insertions(+), 40 deletions(-) create mode 100644 chainercv/links/connection/__init__.py create mode 100644 chainercv/links/connection/convolution_2d_block.py create mode 100644 tests/links_tests/connection_tests/test_convolution_2d_block.py diff --git a/chainercv/links/__init__.py b/chainercv/links/__init__.py index 2bf1bc75d2..d56f355ca2 100644 --- a/chainercv/links/__init__.py +++ b/chainercv/links/__init__.py @@ -1,3 +1,5 @@ +from chainercv.links.connection.convolution_2d_block import Convolution2DBlock # NOQA + from chainercv.links.model.feature_extraction_predictor import FeatureExtractionPredictor # NOQA from chainercv.links.model.pixelwise_softmax_classifier import PixelwiseSoftmaxClassifier # NOQA from chainercv.links.model.sequential_feature_extractor import SequentialFeatureExtractor # NOQA diff --git a/chainercv/links/connection/__init__.py b/chainercv/links/connection/__init__.py new file mode 100644 index 0000000000..2de8e7ea00 --- /dev/null +++ b/chainercv/links/connection/__init__.py @@ -0,0 +1 @@ +from chainercv.links.connection.convolution_2d_block import Convolution2DBlock # NOQA diff --git a/chainercv/links/connection/convolution_2d_block.py b/chainercv/links/connection/convolution_2d_block.py new file mode 100644 index 0000000000..2b03f590fd --- /dev/null +++ b/chainercv/links/connection/convolution_2d_block.py @@ -0,0 +1,50 @@ +import numpy as np + +import chainer +from chainer.functions import relu +from chainer.links import BatchNormalization +from chainer.links import Convolution2D + + +class Convolution2DBlock(chainer.Chain): + """Convolution2D --> (Batch Normalization) --> Activation + + This is a chain that does two-dimensional convolution + and applies an activation. + Optionally, batch normalization can be executed in the middle. + + The parameters are a combination of the ones for + :class:`chainer.links.Convolution2D` and + :class:`chainer.links.BatchNormalization` except for + :obj:`activation` and :obj:`use_bn`. + + :obj:`activation` is a callable. The default value is + :func:`chainer.functions.relu`. + + :obj:`use_bn` is a bool that indicates whether to use + batch normalization or not. The default value is :obj:`False`. + + """ + + def __init__(self, in_channels, out_channels, ksize=None, stride=1, pad=0, + activation=relu, use_bn=False, + nobias=False, initialW=None, initial_bias=None, + decay=0.9, eps=2e-5, dtype=np.float32, + use_gamma=True, use_beta=True, + initial_gamma=None, initial_beta=None): + self.use_bn = use_bn + self.activation = activation + super(Convolution2DBlock, self).__init__() + with self.init_scope(): + self.conv = Convolution2D(in_channels, out_channels, ksize, stride, + pad, nobias, initialW, initial_bias) + if self.use_bn: + self.bn = BatchNormalization( + out_channels, decay, eps, dtype, use_gamma, use_beta, + initial_gamma, initial_beta) + + def __call__(self, x): + h = self.conv(x) + if self.use_bn: + h = self.bn(h) + return self.activation(h) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 131d643a28..b856398be1 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -9,12 +9,12 @@ from chainer.functions import softmax from chainer.initializers import constant from chainer.initializers import normal -from chainer.links import BatchNormalization -from chainer.links import Convolution2D + from chainer.links import Linear from chainercv.utils import download_model +from chainercv.links.connection.convolution_2d_block import Convolution2DBlock from chainercv.links.model.sequential_feature_extractor import \ SequentialFeatureExtractor @@ -24,31 +24,6 @@ [123.68, 116.779, 103.939], dtype=np.float32)[:, np.newaxis, np.newaxis] -class Block(chainer.Chain): - - def __init__(self, in_channels, out_channels, ksize=None, stride=1, pad=0, - nobias=False, initialW=None, initial_bias=None, - activation=relu, use_bn=False, - decay=0.9, eps=2e-5, - use_gamma=True, use_beta=True, - initial_gamma=None, initial_beta=None): - self.use_bn = use_bn - self.activaton = activation - with self.init_scope(): - self.conv = Convolution2D(in_channels, out_channels, ksize, stride, - pad, nobias, initialW, initial_bias) - if self.use_bn: - self.bn = BatchNormalization( - None, decay, eps, use_gamma, use_beta, - initial_gamma, initial_beta) - - def __call__(self, x): - h = self.conv(x) - if self.use_bn: - h = self.bn(h) - return self.activataion(h) - - class VGG16(SequentialFeatureExtractor): """VGG16 Network for classification and feature extraction. @@ -138,23 +113,23 @@ def __init__(self, super(VGG16, self).__init__() with self.init_scope(): - self.conv1_1 = Block(None, 64, 3, 1, 1, **kwargs) - self.conv1_2 = Block(None, 64, 3, 1, 1, **kwargs) + self.conv1_1 = Convolution2DBlock(None, 64, 3, 1, 1, **kwargs) + self.conv1_2 = Convolution2DBlock(None, 64, 3, 1, 1, **kwargs) self.pool1 = _max_pooling_2d - self.conv2_1 = Block(None, 128, 3, 1, 1, **kwargs) - self.conv2_2 = Block(None, 128, 3, 1, 1, **kwargs) + self.conv2_1 = Convolution2DBlock(None, 128, 3, 1, 1, **kwargs) + self.conv2_2 = Convolution2DBlock(None, 128, 3, 1, 1, **kwargs) self.pool2 = _max_pooling_2d - self.conv3_1 = Block(None, 256, 3, 1, 1, **kwargs) - self.conv3_2 = Block(None, 256, 3, 1, 1, **kwargs) - self.conv3_3 = Block(None, 256, 3, 1, 1, **kwargs) + self.conv3_1 = Convolution2DBlock(None, 256, 3, 1, 1, **kwargs) + self.conv3_2 = Convolution2DBlock(None, 256, 3, 1, 1, **kwargs) + self.conv3_3 = Convolution2DBlock(None, 256, 3, 1, 1, **kwargs) self.pool3 = _max_pooling_2d - self.conv4_1 = Block(None, 512, 3, 1, 1, **kwargs) - self.conv4_2 = Block(None, 512, 3, 1, 1, **kwargs) - self.conv4_3 = Block(None, 512, 3, 1, 1, **kwargs) + self.conv4_1 = Convolution2DBlock(None, 512, 3, 1, 1, **kwargs) + self.conv4_2 = Convolution2DBlock(None, 512, 3, 1, 1, **kwargs) + self.conv4_3 = Convolution2DBlock(None, 512, 3, 1, 1, **kwargs) self.pool4 = _max_pooling_2d - self.conv5_1 = Block(None, 512, 3, 1, 1, **kwargs) - self.conv5_2 = Block(None, 512, 3, 1, 1, **kwargs) - self.conv5_3 = Block(None, 512, 3, 1, 1, **kwargs) + self.conv5_1 = Convolution2DBlock(None, 512, 3, 1, 1, **kwargs) + self.conv5_2 = Convolution2DBlock(None, 512, 3, 1, 1, **kwargs) + self.conv5_3 = Convolution2DBlock(None, 512, 3, 1, 1, **kwargs) self.pool5 = _max_pooling_2d self.fc6 = Linear(None, 4096, **kwargs) self.fc6_relu = relu diff --git a/tests/links_tests/connection_tests/test_convolution_2d_block.py b/tests/links_tests/connection_tests/test_convolution_2d_block.py new file mode 100644 index 0000000000..19563c3931 --- /dev/null +++ b/tests/links_tests/connection_tests/test_convolution_2d_block.py @@ -0,0 +1,50 @@ +import unittest + +import numpy as np + +import chainer +from chainer import cuda +from chainer import testing +from chainer.testing import attr + +from chainercv.links import Convolution2DBlock + + +@testing.parameterize( + {'use_bn': True}, + {'use_bn': False} +) +class TestConvolution2DBlock(unittest.TestCase): + + in_channels = 3 + out_channels = 5 + ksize = 3 + stride = 1 + pad = 1 + + def setUp(self): + self.x = np.random.uniform( + -1, 1, (5, self.in_channels, 5, 5)).astype(np.float32) + self.gy = np.random.uniform( + -1, 1, (5, self.out_channels, 5, 5)).astype(np.float32) + self.l = Convolution2DBlock( + self.in_channels, self.out_channels, self.ksize, self.stride, + self.pad, use_bn=self.use_bn + ) + + def check_backward(self, x_data, y_grad): + x = chainer.Variable(x_data) + y = self.l(x) + y.grad = y_grad + y.backward() + + def test_backward_cpu(self): + self.check_backward(self.x, self.gy) + + @attr.gpu + def test_backward_gpu(self): + self.l.to_gpu() + self.check_backward(cuda.to_gpu(self.x), cuda.to_gpu(self.gy)) + + +testing.run_module(__name__, __file__) From 05b478c29d48ec9419cbcbc40daeb985472b5dfa Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 18 Jul 2017 12:20:06 +0900 Subject: [PATCH 084/139] change pretrained weights --- chainercv/links/model/vgg/vgg16.py | 2 +- .../convert_from_original/convert_vgg.py | 32 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index b856398be1..0a8abc664c 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -85,7 +85,7 @@ class VGG16(SequentialFeatureExtractor): 'imagenet': { 'n_class': 1000, 'url': 'https://github.com/yuyu2172/share-weights/releases/' - 'download/0.0.3/vgg16_imagenet_convert_2017_06_15.npz', + 'download/0.0.4/vgg16_imagenet_convert_2017_07_18.npz', 'mean': _imagenet_mean } } diff --git a/examples/classification/convert_from_original/convert_vgg.py b/examples/classification/convert_from_original/convert_vgg.py index c8b4e2ce27..abdca792c4 100644 --- a/examples/classification/convert_from_original/convert_vgg.py +++ b/examples/classification/convert_from_original/convert_vgg.py @@ -5,27 +5,27 @@ def main(): - chainer_model = VGG16Layers_chainer() + chainer_model = VGG16Layers_chainer(pretrained_model='auto') cv_model = VGG16_cv(pretrained_model=None, n_class=1000) - cv_model.conv1_1.copyparams(chainer_model.conv1_1) + cv_model.conv1_1.conv.copyparams(chainer_model.conv1_1) # The pretrained weights are trained to accept BGR images. # Convert weights so that they accept RGB images. - cv_model.conv1_1.W.data[:] = cv_model.conv1_1.W.data[:, ::-1] - - cv_model.conv1_2.copyparams(chainer_model.conv1_2) - cv_model.conv2_1.copyparams(chainer_model.conv2_1) - cv_model.conv2_2.copyparams(chainer_model.conv2_2) - cv_model.conv3_1.copyparams(chainer_model.conv3_1) - cv_model.conv3_2.copyparams(chainer_model.conv3_2) - cv_model.conv3_3.copyparams(chainer_model.conv3_3) - cv_model.conv4_1.copyparams(chainer_model.conv4_1) - cv_model.conv4_2.copyparams(chainer_model.conv4_2) - cv_model.conv4_3.copyparams(chainer_model.conv4_3) - cv_model.conv5_1.copyparams(chainer_model.conv5_1) - cv_model.conv5_2.copyparams(chainer_model.conv5_2) - cv_model.conv5_3.copyparams(chainer_model.conv5_3) + cv_model.conv1_1.conv.W.data[:] = cv_model.conv1_1.conv.W.data[:, ::-1] + + cv_model.conv1_2.conv.copyparams(chainer_model.conv1_2) + cv_model.conv2_1.conv.copyparams(chainer_model.conv2_1) + cv_model.conv2_2.conv.copyparams(chainer_model.conv2_2) + cv_model.conv3_1.conv.copyparams(chainer_model.conv3_1) + cv_model.conv3_2.conv.copyparams(chainer_model.conv3_2) + cv_model.conv3_3.conv.copyparams(chainer_model.conv3_3) + cv_model.conv4_1.conv.copyparams(chainer_model.conv4_1) + cv_model.conv4_2.conv.copyparams(chainer_model.conv4_2) + cv_model.conv4_3.conv.copyparams(chainer_model.conv4_3) + cv_model.conv5_1.conv.copyparams(chainer_model.conv5_1) + cv_model.conv5_2.conv.copyparams(chainer_model.conv5_2) + cv_model.conv5_3.conv.copyparams(chainer_model.conv5_3) cv_model.fc6.copyparams(chainer_model.fc6) cv_model.fc7.copyparams(chainer_model.fc7) cv_model.fc8.copyparams(chainer_model.fc8) From 826c183e37b1f24f250ecfdcf204222038cfe77a Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 18 Jul 2017 12:28:24 +0900 Subject: [PATCH 085/139] fix doc --- chainercv/links/model/vgg/vgg16.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 0a8abc664c..b3caa10c77 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -31,7 +31,7 @@ class VGG16(SequentialFeatureExtractor): This model is a feature extraction link. The network can choose to output features from set of all intermediate and final features produced by the original architecture. - By setting :obj:`VGG16.layer_names`, the kind of features can be selected. + By setting :obj:`VGG16.feature_names`, the kind of features can be selected. Examples: @@ -39,12 +39,12 @@ class VGG16(SequentialFeatureExtractor): # By default, VGG16.__call__ returns a probability score. >>> prob = model(imgs) - >>> model.layer_names = 'conv5_3' - # This is an activation of conv5_3 layer. + >>> model.feature_names = 'conv5_3' + # This is feature conv5_3. >>> feat5_3 = model(imgs) - >>> model.layer_names = ['conv5_3', 'fc6'] - >>> # These are activations of conv5_3 and fc6 layers respectively. + >>> model.feature_names = ['conv5_3', 'fc6'] + >>> # These are features conv5_3 and fc6. >>> feat5_3, feat6 = model(imgs) .. seealso:: @@ -62,7 +62,7 @@ class VGG16(SequentialFeatureExtractor): `_. Args: - layer_names (str or iterable of strings): The names of the feature to + feature_names (str or iterable of strings): The names of the feature to output with :meth:`__call__`. pretrained_model (str): The destination of the pre-trained chainer model serialized as a :obj:`.npz` file. From a5f0be746ca1d43bb102a07bbf7a9790cc6823e3 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 18 Jul 2017 12:35:17 +0900 Subject: [PATCH 086/139] fix test_vgg16 --- tests/links_tests/model_tests/vgg_tests/test_vgg16.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py index be6af33be3..8fb6718572 100644 --- a/tests/links_tests/model_tests/vgg_tests/test_vgg16.py +++ b/tests/links_tests/model_tests/vgg_tests/test_vgg16.py @@ -11,9 +11,9 @@ @testing.parameterize( - {'layer_names': 'prob', 'shapes': (1, 200), 'n_class': 200}, - {'layer_names': 'pool5', 'shapes': (1, 512, 7, 7), 'n_class': None}, - {'layer_names': ['conv5_3', 'conv4_2'], + {'feature_names': 'prob', 'shapes': (1, 200), 'n_class': 200}, + {'feature_names': 'pool5', 'shapes': (1, 512, 7, 7), 'n_class': None}, + {'feature_names': ['conv5_3', 'conv4_2'], 'shapes': ((1, 512, 14, 14), (1, 512, 28, 28)), 'n_class': None}, ) @attr.slow @@ -22,7 +22,8 @@ class TestVGG16Call(unittest.TestCase): def setUp(self): self.link = VGG16( pretrained_model=None, n_class=self.n_class, - layer_names=self.layer_names) + initialW=Zero()) + self.link.feature_names = self.feature_names def check_call(self): xp = self.link.xp @@ -46,5 +47,4 @@ def test_call_gpu(self): self.check_call() - testing.run_module(__name__, __file__) From 23a42d94685ae1b5b590664e5ad7b7583c5a3475 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 18 Jul 2017 13:00:44 +0900 Subject: [PATCH 087/139] fix doc --- chainercv/links/model/feature_extraction_predictor.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/chainercv/links/model/feature_extraction_predictor.py b/chainercv/links/model/feature_extraction_predictor.py index 0c525f006a..2c94637110 100644 --- a/chainercv/links/model/feature_extraction_predictor.py +++ b/chainercv/links/model/feature_extraction_predictor.py @@ -11,10 +11,8 @@ class FeatureExtractionPredictor(chainer.Chain): - """Wrapper around a feature extraction model to add predict method. + """Wrapper class that adds predict method to a feature extraction model. - This chain wraps aroudn a feature extraction model to predict features - from images. The :meth:`predict` takes three steps to make predictions. 1. Preprocess images @@ -25,7 +23,7 @@ class FeatureExtractionPredictor(chainer.Chain): >>> from chainercv.links import VGG16 >>> from chainercv.links import FeatureExtractionPredictor - >>> base_model = VGG16(layer_names='prob') + >>> base_model = VGG16() >>> model = FeatureExtractionPredictor(base_model) >>> prob = model.predict([img]) From 775ab31bcb4491498989bb50b798466e3111d56e Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 18 Jul 2017 13:20:04 +0900 Subject: [PATCH 088/139] fix a mistake in vgg16 --- chainercv/links/model/vgg/vgg16.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index b3caa10c77..3d362d50c3 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -136,7 +136,7 @@ def __init__(self, self.fc6_dropout = dropout self.fc7 = Linear(None, 4096, **kwargs) self.fc7_relu = relu - self.fc7_dropout = Linear(None, 4096, **kwargs) + self.fc7_dropout = dropout self.fc8 = Linear(None, n_class, **kwargs) self.prob = softmax From f553948b46feb3dbef0d3972cb4517a86eb5ffd6 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 20 Jul 2017 20:26:23 +0900 Subject: [PATCH 089/139] fix faster_rcnn_vgg --- chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py index 703f171d31..4f5e2dcdaf 100644 --- a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py +++ b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py @@ -102,7 +102,8 @@ def __init__(self, if vgg_initialW is None and pretrained_model: vgg_initialW = chainer.initializers.constant.Zero() - extractor = VGG16(feature_names='conv5_3', initialW=vgg_initialW) + extractor = VGG16(initialW=vgg_initialW) + extractor.feature_names = 'conv5_3' rpn = RegionProposalNetwork( 512, 512, ratios=ratios, From b9c3f5caa9ee7c830bf76f01e986c85dca437219 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 20 Jul 2017 22:01:04 +0900 Subject: [PATCH 090/139] fix Faster RCNN train to work --- examples/faster_rcnn/train.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/faster_rcnn/train.py b/examples/faster_rcnn/train.py index 84385f1564..946b204be8 100644 --- a/examples/faster_rcnn/train.py +++ b/examples/faster_rcnn/train.py @@ -71,7 +71,15 @@ def main(): chainer.cuda.get_device(args.gpu).use() optimizer = chainer.optimizers.MomentumSGD(lr=args.lr, momentum=0.9) optimizer.setup(model) - optimizer.add_hook(chainer.optimizer.WeightDecay(rate=0.0005)) + + update_link = [l for l in faster_rcnn.extractor.children() + if l.name not in ['fc6', 'fc7', 'fc8']] + update_link += (list(faster_rcnn.rpn.children()) + + list(faster_rcnn.head.children())) + for l in update_link: + for p in l.params(): + p.update_rule.add_hook( + chainer.optimizer.WeightDecay(rate=0.0005)) train_data = TransformDataset(train_data, Transform(faster_rcnn)) From a982c8e28f287c9d9f74c36c9e59cb25a6a647b8 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 20 Jul 2017 22:24:31 +0900 Subject: [PATCH 091/139] fix doc --- chainercv/links/model/vgg/vgg16.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 3d362d50c3..fe09a210e4 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -31,7 +31,10 @@ class VGG16(SequentialFeatureExtractor): This model is a feature extraction link. The network can choose to output features from set of all intermediate and final features produced by the original architecture. - By setting :obj:`VGG16.feature_names`, the kind of features can be selected. + The value of :obj:`VGG16.feature_names` selects the features to be + collected by :meth:`__call__`. + :obj:`self.all_feature_names` is the list of the names of features + that can be collected. Examples: @@ -62,8 +65,6 @@ class VGG16(SequentialFeatureExtractor): `_. Args: - feature_names (str or iterable of strings): The names of the feature to - output with :meth:`__call__`. pretrained_model (str): The destination of the pre-trained chainer model serialized as a :obj:`.npz` file. If this is one of the strings described From 48db171655056b38384691de9d5c8b89a7cddc1a Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 20 Jul 2017 22:24:47 +0900 Subject: [PATCH 092/139] use Zero initialization when pretrained model is used --- chainercv/links/model/vgg/vgg16.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index fe09a210e4..07cc358366 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -110,6 +110,11 @@ def __init__(self, initialW = normal.Normal(0.01) if initial_bias is None: initial_bias = constant.Zero() + + if pretrained_model: + # As a sampling process is time-consuming, + # we employ a zero initializer for faster computation. + initialW = constant.Zero() kwargs = {'initialW': initialW, 'initial_bias': initial_bias} super(VGG16, self).__init__() From 459e3c63a47a007266091fd53ca9a40df7b83721 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 20 Jul 2017 22:28:36 +0900 Subject: [PATCH 093/139] flake8 --- examples/faster_rcnn/train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/faster_rcnn/train.py b/examples/faster_rcnn/train.py index 946b204be8..1023c9a91e 100644 --- a/examples/faster_rcnn/train.py +++ b/examples/faster_rcnn/train.py @@ -73,7 +73,7 @@ def main(): optimizer.setup(model) update_link = [l for l in faster_rcnn.extractor.children() - if l.name not in ['fc6', 'fc7', 'fc8']] + if l.name not in ['fc6', 'fc7', 'fc8']] update_link += (list(faster_rcnn.rpn.children()) + list(faster_rcnn.head.children())) for l in update_link: From b8e890d71b9775be4ee5e485f2195baaf00287e9 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Thu, 20 Jul 2017 22:37:25 +0900 Subject: [PATCH 094/139] improve doc of feature_extraction_predictor --- chainercv/links/model/feature_extraction_predictor.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/chainercv/links/model/feature_extraction_predictor.py b/chainercv/links/model/feature_extraction_predictor.py index 2c94637110..87aae7975b 100644 --- a/chainercv/links/model/feature_extraction_predictor.py +++ b/chainercv/links/model/feature_extraction_predictor.py @@ -26,6 +26,9 @@ class FeatureExtractionPredictor(chainer.Chain): >>> base_model = VGG16() >>> model = FeatureExtractionPredictor(base_model) >>> prob = model.predict([img]) + # Predicting multiple features + >>> model.extractor.feature_names = ['conv5_3', 'fc7'] + >>> conv5_3, fc7 = model.predict([img]) """ @@ -45,7 +48,7 @@ def mean(self): return self.extractor.mean def _prepare(self, img): - """Transform an image to the input for VGG network. + """Prepare an image for feeding it to a model. This is a standard preprocessing scheme used by feature extraction models. @@ -92,10 +95,10 @@ def predict(self, imgs): When :obj:`self.do_ten_crop == True`, this extracts features from patches that are ten-cropped from images. - Otherwise, this extracts features from center-crop of the images. + Otherwise, this extracts features from a center crop of the images. - When using patches from ten-crop, the features for each crop - is averaged to compute one feature. + When using patches from ten crops, the output is the average + of ten features computed from the ten crops. Given :math:`N` input images, this outputs a batched array with batchsize :math:`N`. From 3820df986c377b5d283ccce54d11012ca522f09e Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 24 Jul 2017 12:33:55 +0900 Subject: [PATCH 095/139] use remove_unused --- chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py | 2 ++ examples/faster_rcnn/train.py | 10 +--------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py index 4f5e2dcdaf..e5482e7357 100644 --- a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py +++ b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py @@ -104,6 +104,8 @@ def __init__(self, extractor = VGG16(initialW=vgg_initialW) extractor.feature_names = 'conv5_3' + # Delete all layers after conv5_3. + extractor.remove_unused() rpn = RegionProposalNetwork( 512, 512, ratios=ratios, diff --git a/examples/faster_rcnn/train.py b/examples/faster_rcnn/train.py index 1023c9a91e..95af17876a 100644 --- a/examples/faster_rcnn/train.py +++ b/examples/faster_rcnn/train.py @@ -70,17 +70,9 @@ def main(): model.to_gpu(args.gpu) chainer.cuda.get_device(args.gpu).use() optimizer = chainer.optimizers.MomentumSGD(lr=args.lr, momentum=0.9) + optimizer.add_hook(chainer.optimizer.WeightDecay(rate=0.0005)) optimizer.setup(model) - update_link = [l for l in faster_rcnn.extractor.children() - if l.name not in ['fc6', 'fc7', 'fc8']] - update_link += (list(faster_rcnn.rpn.children()) + - list(faster_rcnn.head.children())) - for l in update_link: - for p in l.params(): - p.update_rule.add_hook( - chainer.optimizer.WeightDecay(rate=0.0005)) - train_data = TransformDataset(train_data, Transform(faster_rcnn)) train_iter = chainer.iterators.MultiprocessIterator( From 3323ce737d887c908a28b118fcf5978f0bb7241a Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 24 Jul 2017 12:33:55 +0900 Subject: [PATCH 096/139] use remove_unused --- chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py | 2 ++ examples/faster_rcnn/train.py | 10 +--------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py index 4f5e2dcdaf..e5482e7357 100644 --- a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py +++ b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py @@ -104,6 +104,8 @@ def __init__(self, extractor = VGG16(initialW=vgg_initialW) extractor.feature_names = 'conv5_3' + # Delete all layers after conv5_3. + extractor.remove_unused() rpn = RegionProposalNetwork( 512, 512, ratios=ratios, diff --git a/examples/faster_rcnn/train.py b/examples/faster_rcnn/train.py index 1023c9a91e..84385f1564 100644 --- a/examples/faster_rcnn/train.py +++ b/examples/faster_rcnn/train.py @@ -71,15 +71,7 @@ def main(): chainer.cuda.get_device(args.gpu).use() optimizer = chainer.optimizers.MomentumSGD(lr=args.lr, momentum=0.9) optimizer.setup(model) - - update_link = [l for l in faster_rcnn.extractor.children() - if l.name not in ['fc6', 'fc7', 'fc8']] - update_link += (list(faster_rcnn.rpn.children()) + - list(faster_rcnn.head.children())) - for l in update_link: - for p in l.params(): - p.update_rule.add_hook( - chainer.optimizer.WeightDecay(rate=0.0005)) + optimizer.add_hook(chainer.optimizer.WeightDecay(rate=0.0005)) train_data = TransformDataset(train_data, Transform(faster_rcnn)) From 9d2547340be556c1b93b37bf73fb3871ea0c3dc5 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 4 Aug 2017 16:01:12 +0900 Subject: [PATCH 097/139] change init style of convolution_2d_block --- .../links/connection/convolution_2d_block.py | 50 ++++++++++++++----- .../test_convolution_2d_block.py | 27 +++++++--- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/chainercv/links/connection/convolution_2d_block.py b/chainercv/links/connection/convolution_2d_block.py index 2b03f590fd..4c5643f065 100644 --- a/chainercv/links/connection/convolution_2d_block.py +++ b/chainercv/links/connection/convolution_2d_block.py @@ -1,5 +1,3 @@ -import numpy as np - import chainer from chainer.functions import relu from chainer.links import BatchNormalization @@ -24,24 +22,52 @@ class Convolution2DBlock(chainer.Chain): :obj:`use_bn` is a bool that indicates whether to use batch normalization or not. The default value is :obj:`False`. + Example: + + There are sevaral ways to make a Convolution2DBlock chain. + + 1. Give the first three arguments explicitly: + + >>> l = Convolution2DBlock(5, 10, 3) + + 2. Omit :obj:`in_channels` or fill it with :obj:`None`: + + In these ways, attributes are initialized at runtime based on + the channel size of the input. + + >>> l = Convolution2DBlock(None, 10, 3) + >>> l = Convolution2DBlock(10, 3) + + + Args: + in_channels (int or None): Number of channels of input arrays. + If :obj:`None`, parameter initialization will be deferred until the + first forward data pass at which time the size will be determined. + out_channels (int): Number of channels of output arrays. + ksize (int or pair of ints): Size of filters (a.k.a. kernels). + :obj:`ksize=k` and :obj:`ksize=(k, k)` are equivalent. + activation (callable): An activation function. The default value is + :func:`chainer.functions.relu`. + use_bn (bool): Indicates whether to use batch normalization or not. + The default value is :obj:`False`. + conv_kwargs (dict): Key-word arguments passed to initialize + :class:`chainer.links.Convolution2D`. + bn_kwargs (dict): Key-word arguments passed to initialize + :class:`chainer.links.BatchNormalization`. + """ - def __init__(self, in_channels, out_channels, ksize=None, stride=1, pad=0, + def __init__(self, in_channels, out_channels, ksize=None, activation=relu, use_bn=False, - nobias=False, initialW=None, initial_bias=None, - decay=0.9, eps=2e-5, dtype=np.float32, - use_gamma=True, use_beta=True, - initial_gamma=None, initial_beta=None): + conv_kwargs=dict(), bn_kwargs=dict()): self.use_bn = use_bn self.activation = activation super(Convolution2DBlock, self).__init__() with self.init_scope(): - self.conv = Convolution2D(in_channels, out_channels, ksize, stride, - pad, nobias, initialW, initial_bias) + self.conv = Convolution2D(in_channels, out_channels, ksize, + **conv_kwargs) if self.use_bn: - self.bn = BatchNormalization( - out_channels, decay, eps, dtype, use_gamma, use_beta, - initial_gamma, initial_beta) + self.bn = BatchNormalization(out_channels, **bn_kwargs) def __call__(self, x): h = self.conv(x) diff --git a/tests/links_tests/connection_tests/test_convolution_2d_block.py b/tests/links_tests/connection_tests/test_convolution_2d_block.py index 19563c3931..5b5d25db26 100644 --- a/tests/links_tests/connection_tests/test_convolution_2d_block.py +++ b/tests/links_tests/connection_tests/test_convolution_2d_block.py @@ -10,10 +10,10 @@ from chainercv.links import Convolution2DBlock -@testing.parameterize( - {'use_bn': True}, - {'use_bn': False} -) +@testing.parameterize(*testing.product({ + 'use_bn': [True, False], + 'args_style': ['explicit', 'None', 'omit'] +})) class TestConvolution2DBlock(unittest.TestCase): in_channels = 3 @@ -27,10 +27,21 @@ def setUp(self): -1, 1, (5, self.in_channels, 5, 5)).astype(np.float32) self.gy = np.random.uniform( -1, 1, (5, self.out_channels, 5, 5)).astype(np.float32) - self.l = Convolution2DBlock( - self.in_channels, self.out_channels, self.ksize, self.stride, - self.pad, use_bn=self.use_bn - ) + if self.args_style == 'explicit': + self.l = Convolution2DBlock( + self.in_channels, self.out_channels, self.ksize, + use_bn=self.use_bn, + conv_kwargs={'stride': self.stride, 'pad': self.pad}) + elif self.args_style == 'None': + self.l = Convolution2DBlock( + None, self.out_channels, self.ksize, + use_bn=self.use_bn, + conv_kwargs={'stride': self.stride, 'pad': self.pad}) + elif self.args_style == 'omit': + self.l = Convolution2DBlock( + self.out_channels, self.ksize, + use_bn=self.use_bn, + conv_kwargs={'stride': self.stride, 'pad': self.pad}) def check_backward(self, x_data, y_grad): x = chainer.Variable(x_data) From f615940307d96e9e7e3f87db5ae0edfdf6e3cae6 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 4 Aug 2017 16:13:21 +0900 Subject: [PATCH 098/139] use crop_size && make crop_size int --- .../links/model/feature_extraction_predictor.py | 12 ++++++------ .../model_tests/test_feature_extraction_predictor.py | 12 +++++++----- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/chainercv/links/model/feature_extraction_predictor.py b/chainercv/links/model/feature_extraction_predictor.py index 87aae7975b..41f04901a6 100644 --- a/chainercv/links/model/feature_extraction_predictor.py +++ b/chainercv/links/model/feature_extraction_predictor.py @@ -33,11 +33,11 @@ class FeatureExtractionPredictor(chainer.Chain): """ def __init__(self, extractor, - size=(224, 224), scale_size=256, + crop_size=224, scale_size=256, do_ten_crop=False): super(FeatureExtractionPredictor, self).__init__() self.scale_size = scale_size - self.size = size + self.crop_size = (crop_size, crop_size) self.do_ten_crop = do_ten_crop with self.init_scope(): @@ -54,7 +54,7 @@ def _prepare(self, img): models. First, the image is scaled so that the length of the smaller edge is :math:`scale_size`. - Next, the image is center cropped or ten cropped to :math:`size`. + Next, the image is center cropped or ten cropped to :math:`crop_size`. Last, the image is mean subtracted by a mean image array :obj:`mean`. Args: @@ -68,10 +68,10 @@ def _prepare(self, img): """ img = scale(img, size=self.scale_size) if self.do_ten_crop: - img = ten_crop(img, self.size) + img = ten_crop(img, self.crop_size) img -= self.mean[np.newaxis] else: - img = center_crop(img, self.size) + img = center_crop(img, self.crop_size) img -= self.mean return img @@ -114,7 +114,7 @@ def predict(self, imgs): """ imgs = self.xp.asarray([self._prepare(img) for img in imgs]) - shape = (-1, imgs.shape[-3]) + self.size + shape = (-1, imgs.shape[-3]) + self.crop_size imgs = imgs.reshape(shape) with chainer.function.no_backprop_mode(): diff --git a/tests/links_tests/model_tests/test_feature_extraction_predictor.py b/tests/links_tests/model_tests/test_feature_extraction_predictor.py index 726f853b2b..d11a07b6bd 100644 --- a/tests/links_tests/model_tests/test_feature_extraction_predictor.py +++ b/tests/links_tests/model_tests/test_feature_extraction_predictor.py @@ -66,8 +66,8 @@ def test_gpu(self): @testing.parameterize( - {'do_ten_crop': False, 'size': (256, 192)}, - {'do_ten_crop': True, 'size': (256, 192)} + {'do_ten_crop': False, 'crop_size': 192}, + {'do_ten_crop': True, 'crop_size': 192} ) class TestFeatureExtractionPredictorPrepare(unittest.TestCase): @@ -76,12 +76,14 @@ class TestFeatureExtractionPredictorPrepare(unittest.TestCase): def setUp(self): self.link = FeatureExtractionPredictor( DummyFeatureExtractor((1,), None), - size=self.size, + crop_size=self.crop_size, do_ten_crop=self.do_ten_crop) if self.do_ten_crop: - self.expected_shape = (10, self.n_channel) + self.size + self.expected_shape = ( + 10, self.n_channel, self.crop_size, self.crop_size) else: - self.expected_shape = (self.n_channel,) + self.size + self.expected_shape = ( + self.n_channel, self.crop_size, self.crop_size) def test(self): out = self.link._prepare( From 3bc3b099adebfeb15729ef29ae97f66a62242e64 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 4 Aug 2017 16:35:43 +0900 Subject: [PATCH 099/139] fix convolution_2d_block --- chainercv/links/connection/convolution_2d_block.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chainercv/links/connection/convolution_2d_block.py b/chainercv/links/connection/convolution_2d_block.py index 4c5643f065..1902b1426d 100644 --- a/chainercv/links/connection/convolution_2d_block.py +++ b/chainercv/links/connection/convolution_2d_block.py @@ -60,6 +60,9 @@ class Convolution2DBlock(chainer.Chain): def __init__(self, in_channels, out_channels, ksize=None, activation=relu, use_bn=False, conv_kwargs=dict(), bn_kwargs=dict()): + if ksize is None: + out_channels, ksize, in_channels = in_channels, out_channels, None + self.use_bn = use_bn self.activation = activation super(Convolution2DBlock, self).__init__() From 4233f8e53a324082d202de4fbfa62cb924a5f508 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 4 Aug 2017 16:47:42 +0900 Subject: [PATCH 100/139] stop using do_ten_crop --- .../model/feature_extraction_predictor.py | 66 +++++++++++-------- .../test_feature_extraction_predictor.py | 25 ++++--- 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/chainercv/links/model/feature_extraction_predictor.py b/chainercv/links/model/feature_extraction_predictor.py index 41f04901a6..2c05cf7e75 100644 --- a/chainercv/links/model/feature_extraction_predictor.py +++ b/chainercv/links/model/feature_extraction_predictor.py @@ -11,7 +11,7 @@ class FeatureExtractionPredictor(chainer.Chain): - """Wrapper class that adds predict method to a feature extraction model. + """Wrapper that adds a prediction method to a feature extraction model. The :meth:`predict` takes three steps to make predictions. @@ -30,15 +30,33 @@ class FeatureExtractionPredictor(chainer.Chain): >>> model.extractor.feature_names = ['conv5_3', 'fc7'] >>> conv5_3, fc7 = model.predict([img]) + When :obj:`self.crop == 'center'`, :meth:`predict` extracts features from + the center crop of the input images. + When :obj:`self.crop == '10'`, :meth:`predict` extracts features from + patches that are ten-cropped from the input images. + + When extracting more than one crops from an image, the output of + :meth:`predict` returns average of the features computed from the crops. + + Args: + extractor: A feature extraction model. This is a callable chain + that takes a batch of images and returns a variable or + tuple of variables + crop_size (int): The width and the height of a crop. + scale_size (int): Inside :meth:`_prepare`, an image is + resized so that its shorter edge has length equal + to :obj:`scale_size`. + crop ({'center', '10'}): Determines the style of cropping. + """ def __init__(self, extractor, crop_size=224, scale_size=256, - do_ten_crop=False): + crop='center'): super(FeatureExtractionPredictor, self).__init__() self.scale_size = scale_size self.crop_size = (crop_size, crop_size) - self.do_ten_crop = do_ten_crop + self.crop = crop with self.init_scope(): self.extractor = extractor @@ -63,20 +81,21 @@ def _prepare(self, img): Returns: ~numpy.ndarray: - A preprocessed image. + A preprocessed image. This is 4D array whose batch size is + the number of crops. """ img = scale(img, size=self.scale_size) - if self.do_ten_crop: - img = ten_crop(img, self.crop_size) - img -= self.mean[np.newaxis] - else: - img = center_crop(img, self.crop_size) - img -= self.mean + if self.crop == '10': + imgs = ten_crop(img, self.crop_size) + imgs -= self.mean[np.newaxis] + elif self.crop == 'center': + imgs = center_crop(img, self.crop_size)[np.newaxis] + imgs -= self.mean[np.newaxis] - return img + return imgs - def _average_ten_crop(self, y): + def _average_crops(self, y, n_crop): if y.ndim == 4: warnings.warn( 'Four dimensional features are averaged. ' @@ -84,22 +103,14 @@ def _average_ten_crop(self, y): 'their spatial information would be lost.') xp = chainer.cuda.get_array_module(y) - n = y.shape[0] // 10 - y_shape = y.shape[1:] - y = y.reshape((n, 10) + y_shape) - y = xp.sum(y, axis=1) / 10 + n = y.shape[0] // n_crop + y = y.reshape((n, n_crop) + y.shape[1:]) + y = xp.sum(y, axis=1) / n_crop return y def predict(self, imgs): """Predict features from images. - When :obj:`self.do_ten_crop == True`, this extracts features from - patches that are ten-cropped from images. - Otherwise, this extracts features from a center crop of the images. - - When using patches from ten crops, the output is the average - of ten features computed from the ten crops. - Given :math:`N` input images, this outputs a batched array with batchsize :math:`N`. @@ -114,6 +125,7 @@ def predict(self, imgs): """ imgs = self.xp.asarray([self._prepare(img) for img in imgs]) + n_crop = imgs.shape[-4] shape = (-1, imgs.shape[-3]) + self.crop_size imgs = imgs.reshape(shape) @@ -125,13 +137,13 @@ def predict(self, imgs): output = [] for activation in activations: activation = activation.data - if self.do_ten_crop: - activation = self._average_ten_crop(activation) + if n_crop > 1: + activation = self._average_crops(activation, n_crop) output.append(cuda.to_cpu(activation)) output = tuple(output) else: output = cuda.to_cpu(activations.data) - if self.do_ten_crop: - output = self._average_ten_crop(output) + if n_crop > 1: + output = self._average_crops(output, n_crop) return output diff --git a/tests/links_tests/model_tests/test_feature_extraction_predictor.py b/tests/links_tests/model_tests/test_feature_extraction_predictor.py index d11a07b6bd..f49eae0c66 100644 --- a/tests/links_tests/model_tests/test_feature_extraction_predictor.py +++ b/tests/links_tests/model_tests/test_feature_extraction_predictor.py @@ -29,17 +29,17 @@ def __call__(self, x): @testing.parameterize( - {'shape_0': (5, 10, 10), 'shape_1': None, 'do_ten_crop': False}, - {'shape_0': (8,), 'shape_1': None, 'do_ten_crop': True}, - {'shape_0': (5, 10, 10), 'shape_1': (12,), 'do_ten_crop': False}, - {'shape_0': (8,), 'shape_1': (10,), 'do_ten_crop': True}, + {'shape_0': (5, 10, 10), 'shape_1': None, 'crop': 'center'}, + {'shape_0': (8,), 'shape_1': None, 'crop': '10'}, + {'shape_0': (5, 10, 10), 'shape_1': (12,), 'crop': 'center'}, + {'shape_0': (8,), 'shape_1': (10,), 'crop': '10'}, ) class TestFeatureExtractionPredictorPredict(unittest.TestCase): def setUp(self): self.link = FeatureExtractionPredictor( DummyFeatureExtractor(self.shape_0, self.shape_1), - do_ten_crop=self.do_ten_crop) + crop=self.crop) self.x = np.random.uniform(size=(3, 3, 32, 32)).astype(np.float32) self.one_output = self.shape_1 is None @@ -66,8 +66,8 @@ def test_gpu(self): @testing.parameterize( - {'do_ten_crop': False, 'crop_size': 192}, - {'do_ten_crop': True, 'crop_size': 192} + {'crop': 'center', 'crop_size': 192}, + {'crop': '10', 'crop_size': 192} ) class TestFeatureExtractionPredictorPrepare(unittest.TestCase): @@ -76,14 +76,13 @@ class TestFeatureExtractionPredictorPrepare(unittest.TestCase): def setUp(self): self.link = FeatureExtractionPredictor( DummyFeatureExtractor((1,), None), - crop_size=self.crop_size, - do_ten_crop=self.do_ten_crop) - if self.do_ten_crop: + crop_size=self.crop_size, crop=self.crop) + if self.crop == 'center': self.expected_shape = ( - 10, self.n_channel, self.crop_size, self.crop_size) - else: + 1, self.n_channel, self.crop_size, self.crop_size) + elif self.crop == '10': self.expected_shape = ( - self.n_channel, self.crop_size, self.crop_size) + 10, self.n_channel, self.crop_size, self.crop_size) def test(self): out = self.link._prepare( From 6b8c0da7b5e5a868c162cb0c4f39e40961d768e3 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 4 Aug 2017 16:53:38 +0900 Subject: [PATCH 101/139] fix doc --- .../model/feature_extraction_predictor.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/chainercv/links/model/feature_extraction_predictor.py b/chainercv/links/model/feature_extraction_predictor.py index 2c05cf7e75..f443d58c52 100644 --- a/chainercv/links/model/feature_extraction_predictor.py +++ b/chainercv/links/model/feature_extraction_predictor.py @@ -13,11 +13,11 @@ class FeatureExtractionPredictor(chainer.Chain): """Wrapper that adds a prediction method to a feature extraction model. - The :meth:`predict` takes three steps to make predictions. + The :meth:`predict` takes three steps to make a prediction. - 1. Preprocess images + 1. Preprocess input images 2. Forward the preprocessed images to the network - 3. Average features in the case when ten-crop is used. + 3. Average features in the case when more than one crops are extracted. Example: @@ -40,11 +40,11 @@ class FeatureExtractionPredictor(chainer.Chain): Args: extractor: A feature extraction model. This is a callable chain - that takes a batch of images and returns a variable or + that takes a batch of images and returns a variable or a tuple of variables - crop_size (int): The width and the height of a crop. + crop_size (int): The width and the height of a cropped image. scale_size (int): Inside :meth:`_prepare`, an image is - resized so that its shorter edge has length equal + resized so that the length of the shorter edge is equal to :obj:`scale_size`. crop ({'center', '10'}): Determines the style of cropping. @@ -72,8 +72,9 @@ def _prepare(self, img): models. First, the image is scaled so that the length of the smaller edge is :math:`scale_size`. - Next, the image is center cropped or ten cropped to :math:`crop_size`. - Last, the image is mean subtracted by a mean image array :obj:`mean`. + Next, the image is cropped into patches with height and width equal to + :math:`crop_size`. + Last, the image is mean subtracted an array :obj:`mean`. Args: img (~numpy.ndarray): An image. This is in CHW and RGB format. @@ -111,7 +112,7 @@ def _average_crops(self, y, n_crop): def predict(self, imgs): """Predict features from images. - Given :math:`N` input images, this outputs a batched array with + Given :math:`N` input images, this method outputs a batched array with batchsize :math:`N`. Args: From b85a3cefb14516e0d5eb4d815dc49576d0b3dd69 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 4 Aug 2017 16:55:06 +0900 Subject: [PATCH 102/139] fix doc for VGG16 --- chainercv/links/model/vgg/vgg16.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 07cc358366..4a86cf75b7 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -76,7 +76,7 @@ class VGG16(SequentialFeatureExtractor): n_class (int): The dimension of the output of fc8. mean (numpy.ndarray): A mean image. If :obj:`None` and a supported pretrained model is used, - the mean image used to train the pretrained model will be used. + the mean value used to train the pretrained model will be used. initialW (callable): Initializer for the weights. initial_bias (callable): Initializer for the biases. From 20783d61fda964c9a31e5f7440adde795324805c Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 4 Aug 2017 17:00:07 +0900 Subject: [PATCH 103/139] fix variable names in eval_imagenet --- examples/classification/eval_imagenet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index 3dc0835d52..03e0fff377 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -65,11 +65,11 @@ def main(): model.predict, iterator, hook=ProgressHook(len(dataset))) del imgs - pred_labels, = pred_values - gt_labels, = gt_values + pred_scores, = pred_values + gt_scores, = gt_values accuracy = F.accuracy( - np.array(list(pred_labels)), np.array(list(gt_labels))).data + np.array(list(pred_scores)), np.array(list(gt_scores))).data print() print('Top 1 Error {}'.format(1. - accuracy)) From 20b6eb911f999a018c9532beaccff612dc294383 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 4 Aug 2017 17:12:22 +0900 Subject: [PATCH 104/139] fix vgg16 --- chainercv/links/model/vgg/vgg16.py | 49 +++++++++++++++++++----------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 4a86cf75b7..f343415999 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -115,35 +115,50 @@ def __init__(self, # As a sampling process is time-consuming, # we employ a zero initializer for faster computation. initialW = constant.Zero() - kwargs = {'initialW': initialW, 'initial_bias': initial_bias} + conv_kwargs = {'stride': 1, 'pad': 1, + 'initialW': initialW, 'initial_bias': initial_bias} + fc_kwargs = {'initialW': initialW, 'initial_bias': initial_bias} super(VGG16, self).__init__() with self.init_scope(): - self.conv1_1 = Convolution2DBlock(None, 64, 3, 1, 1, **kwargs) - self.conv1_2 = Convolution2DBlock(None, 64, 3, 1, 1, **kwargs) + self.conv1_1 = Convolution2DBlock(None, 64, 3, + conv_kwargs=conv_kwargs) + self.conv1_2 = Convolution2DBlock(None, 64, 3, + conv_kwargs=conv_kwargs) self.pool1 = _max_pooling_2d - self.conv2_1 = Convolution2DBlock(None, 128, 3, 1, 1, **kwargs) - self.conv2_2 = Convolution2DBlock(None, 128, 3, 1, 1, **kwargs) + self.conv2_1 = Convolution2DBlock(None, 128, 3, + conv_kwargs=conv_kwargs) + self.conv2_2 = Convolution2DBlock(None, 128, 3, + conv_kwargs=conv_kwargs) self.pool2 = _max_pooling_2d - self.conv3_1 = Convolution2DBlock(None, 256, 3, 1, 1, **kwargs) - self.conv3_2 = Convolution2DBlock(None, 256, 3, 1, 1, **kwargs) - self.conv3_3 = Convolution2DBlock(None, 256, 3, 1, 1, **kwargs) + self.conv3_1 = Convolution2DBlock(None, 256, 3, + conv_kwargs=conv_kwargs) + self.conv3_2 = Convolution2DBlock(None, 256, 3, + conv_kwargs=conv_kwargs) + self.conv3_3 = Convolution2DBlock(None, 256, 3, + conv_kwargs=conv_kwargs) self.pool3 = _max_pooling_2d - self.conv4_1 = Convolution2DBlock(None, 512, 3, 1, 1, **kwargs) - self.conv4_2 = Convolution2DBlock(None, 512, 3, 1, 1, **kwargs) - self.conv4_3 = Convolution2DBlock(None, 512, 3, 1, 1, **kwargs) + self.conv4_1 = Convolution2DBlock(None, 512, 3, + conv_kwargs=conv_kwargs) + self.conv4_2 = Convolution2DBlock(None, 512, 3, + conv_kwargs=conv_kwargs) + self.conv4_3 = Convolution2DBlock(None, 512, 3, + conv_kwargs=conv_kwargs) self.pool4 = _max_pooling_2d - self.conv5_1 = Convolution2DBlock(None, 512, 3, 1, 1, **kwargs) - self.conv5_2 = Convolution2DBlock(None, 512, 3, 1, 1, **kwargs) - self.conv5_3 = Convolution2DBlock(None, 512, 3, 1, 1, **kwargs) + self.conv5_1 = Convolution2DBlock(None, 512, 3, + conv_kwargs=conv_kwargs) + self.conv5_2 = Convolution2DBlock(None, 512, 3, + conv_kwargs=conv_kwargs) + self.conv5_3 = Convolution2DBlock(None, 512, 3, + conv_kwargs=conv_kwargs) self.pool5 = _max_pooling_2d - self.fc6 = Linear(None, 4096, **kwargs) + self.fc6 = Linear(None, 4096, **fc_kwargs) self.fc6_relu = relu self.fc6_dropout = dropout - self.fc7 = Linear(None, 4096, **kwargs) + self.fc7 = Linear(None, 4096, **fc_kwargs) self.fc7_relu = relu self.fc7_dropout = dropout - self.fc8 = Linear(None, n_class, **kwargs) + self.fc8 = Linear(None, n_class, **fc_kwargs) self.prob = softmax if pretrained_model in self._models: From a59194a67333587ac820ded6f2d52ff1e98c5535 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 4 Aug 2017 17:20:02 +0900 Subject: [PATCH 105/139] add convolution2DBlock to doc --- docs/source/reference/links.rst | 21 ++++++++++++++++----- docs/source/reference/links/connection.rst | 9 +++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 docs/source/reference/links/connection.rst diff --git a/docs/source/reference/links.rst b/docs/source/reference/links.rst index 2a60cacd90..70088dbc83 100644 --- a/docs/source/reference/links.rst +++ b/docs/source/reference/links.rst @@ -1,11 +1,13 @@ Links ===== -.. module:: chainercv.links.model.faster_rcnn + +Model +----- Feature Extraction ------------------- +~~~~~~~~~~~~~~~~~~ Feature extraction models can be used to extract feature(s) given images. .. toctree:: @@ -20,7 +22,7 @@ Feature extraction models can be used to extract feature(s) given images. Detection ---------- +~~~~~~~~~ Detection links share a common method :meth:`predict` to detect objects in images. For more details, please read :func:`FasterRCNN.predict`. @@ -32,7 +34,7 @@ For more details, please read :func:`FasterRCNN.predict`. Semantic Segmentation ---------------------- +~~~~~~~~~~~~~~~~~~~~~ .. module:: chainercv.links.model.segnet @@ -45,8 +47,17 @@ For more details, please read :func:`SegNetBasic.predict`. Classifiers ------------ +~~~~~~~~~~~ .. toctree:: links/classifier + + +Connection +---------- + +.. toctree:: + links/connection + + diff --git a/docs/source/reference/links/connection.rst b/docs/source/reference/links/connection.rst new file mode 100644 index 0000000000..874f88564b --- /dev/null +++ b/docs/source/reference/links/connection.rst @@ -0,0 +1,9 @@ +Connection +========== + +.. module:: chainercv.links.connection + + +Convolution2DBlock +------------------ +.. autoclass:: Convolution2DBlock From 9a64c9ba5c918078a4c01200d0d45ec3052d5f14 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 4 Aug 2017 18:40:56 +0900 Subject: [PATCH 106/139] delete unnecessary declaration of initial_bias --- chainercv/links/model/vgg/vgg16.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index f343415999..5afc653466 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -105,12 +105,9 @@ def __init__(self, mean = self._models[pretrained_model]['mean'] self.mean = mean - # Employ default initializers used in the original paper. if initialW is None: + # Employ default initializers used in the original paper. initialW = normal.Normal(0.01) - if initial_bias is None: - initial_bias = constant.Zero() - if pretrained_model: # As a sampling process is time-consuming, # we employ a zero initializer for faster computation. From 799b48b211d8705cdee09135f5d71767e80b7824 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 4 Aug 2017 21:13:26 +0900 Subject: [PATCH 107/139] specify n_class in eval_imagenet --- examples/classification/eval_imagenet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index 03e0fff377..b13ff12afb 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -51,9 +51,9 @@ def main(): if args.model == 'vgg16': if args.pretrained_model: - model = VGG16(pretrained_model=args.pretrained_model) + model = VGG16(pretrained_model=args.pretrained_model, n_class=1000) else: - model = VGG16(pretrained_model='imagenet') + model = VGG16(pretrained_model='imagenet', n_class=1000) model = FeatureExtractionPredictor(model) if args.gpu >= 0: From 5c83e38cf8d545cb75141a9eaf58e085a82d2a4b Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 4 Aug 2017 21:14:47 +0900 Subject: [PATCH 108/139] use directory_parsing_label_names --- examples/classification/eval_imagenet.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index b13ff12afb..c1c2763320 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -8,6 +8,7 @@ import chainer.functions as F from chainer import iterators +from chainercv.datasets import directory_parsing_label_names from chainercv.datasets import DirectoryParsingClassificationDataset from chainercv.links import FeatureExtractionPredictor from chainercv.links import VGG16 @@ -45,15 +46,18 @@ def main(): args = parser.parse_args() dataset = DirectoryParsingClassificationDataset(args.val) + label_names = directory_parsing_label_names(args.val) iterator = iterators.MultiprocessIterator( dataset, args.batchsize, repeat=False, shuffle=False, n_processes=6, shared_mem=300000000) if args.model == 'vgg16': if args.pretrained_model: - model = VGG16(pretrained_model=args.pretrained_model, n_class=1000) + model = VGG16(pretrained_model=args.pretrained_model, + n_class=len(label_names)) else: - model = VGG16(pretrained_model='imagenet', n_class=1000) + model = VGG16(pretrained_model='imagenet', + n_class=len(label_names)) model = FeatureExtractionPredictor(model) if args.gpu >= 0: From aafaecc0a9ab260190f1626e3aa5ec710afde66f Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 4 Aug 2017 21:18:47 +0900 Subject: [PATCH 109/139] expose crop option in eval_imagenet --- examples/classification/README.md | 2 +- examples/classification/eval_imagenet.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/classification/README.md b/examples/classification/README.md index 663d926b83..d92c2a3c0b 100644 --- a/examples/classification/README.md +++ b/examples/classification/README.md @@ -10,7 +10,7 @@ The results can be reproduced by the following command. The score is reported using a weight converted from a weight trained by Caffe. ``` -$ python eval_imagenet.py [--model vgg16] [--pretrained_model ] [--batchsize ] [--gpu ] +$ python eval_imagenet.py [--model vgg16] [--pretrained_model ] [--batchsize ] [--gpu ] [--crop center|10] ``` diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index c1c2763320..3fd05bfe9a 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -43,6 +43,7 @@ def main(): parser.add_argument('--pretrained_model') parser.add_argument('--gpu', type=int, default=-1) parser.add_argument('--batchsize', type=int, default=32) + parser.add_argument('--crop', type=str, default='center') args = parser.parse_args() dataset = DirectoryParsingClassificationDataset(args.val) @@ -53,12 +54,12 @@ def main(): if args.model == 'vgg16': if args.pretrained_model: - model = VGG16(pretrained_model=args.pretrained_model, - n_class=len(label_names)) + extractor = VGG16(pretrained_model=args.pretrained_model, + n_class=len(label_names)) else: - model = VGG16(pretrained_model='imagenet', - n_class=len(label_names)) - model = FeatureExtractionPredictor(model) + extractor = VGG16(pretrained_model='imagenet', + n_class=len(label_names)) + model = FeatureExtractionPredictor(extractor, crop=args.crop) if args.gpu >= 0: chainer.cuda.get_device(args.gpu).use() From fdd6f48035aec2a355a471549ad1c52f9276059c Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Fri, 4 Aug 2017 21:19:09 +0900 Subject: [PATCH 110/139] fix variable names --- examples/classification/eval_imagenet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index 3fd05bfe9a..150b4b6c0f 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -70,11 +70,11 @@ def main(): model.predict, iterator, hook=ProgressHook(len(dataset))) del imgs - pred_scores, = pred_values - gt_scores, = gt_values + pred_probs, = pred_values + gt_probs, = gt_values accuracy = F.accuracy( - np.array(list(pred_scores)), np.array(list(gt_scores))).data + np.array(list(pred_probs)), np.array(list(gt_probs))).data print() print('Top 1 Error {}'.format(1. - accuracy)) From 4bbdd5ad9b12803a86b0ce7fec24cd335a643946 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Sun, 6 Aug 2017 11:10:38 +0900 Subject: [PATCH 111/139] support tuple as an argument for shapes (FeatureExtractionPredictor) --- .../model/feature_extraction_predictor.py | 32 ++++++++++++------- .../test_feature_extraction_predictor.py | 25 +++++++++------ 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/chainercv/links/model/feature_extraction_predictor.py b/chainercv/links/model/feature_extraction_predictor.py index f443d58c52..dadaf1077e 100644 --- a/chainercv/links/model/feature_extraction_predictor.py +++ b/chainercv/links/model/feature_extraction_predictor.py @@ -5,6 +5,7 @@ from chainer import cuda from chainercv.transforms import center_crop +from chainercv.transforms import resize from chainercv.transforms import scale from chainercv.transforms import ten_crop @@ -42,10 +43,13 @@ class FeatureExtractionPredictor(chainer.Chain): extractor: A feature extraction model. This is a callable chain that takes a batch of images and returns a variable or a tuple of variables - crop_size (int): The width and the height of a cropped image. - scale_size (int): Inside :meth:`_prepare`, an image is - resized so that the length of the shorter edge is equal - to :obj:`scale_size`. + crop_size (int or tuple): The height and the width of a cropped image. + If this is an integer, an image is cropped to + :math:`(crop_size, crop_size)`. + scale_size (int or tuple): If :obj:`scale_size` is an integer, + an image is resized so that the length of the shorter edge is equal + to :obj:`scale_size`. If this is a tuple :obj:`(height, width)`, + an image is resized to :math:`(height, width)`. crop ({'center', '10'}): Determines the style of cropping. """ @@ -55,7 +59,9 @@ def __init__(self, extractor, crop='center'): super(FeatureExtractionPredictor, self).__init__() self.scale_size = scale_size - self.crop_size = (crop_size, crop_size) + if isinstance(crop_size, int): + crop_size = (crop_size, crop_size) + self.crop_size = crop_size self.crop = crop with self.init_scope(): @@ -70,10 +76,8 @@ def _prepare(self, img): This is a standard preprocessing scheme used by feature extraction models. - First, the image is scaled so that the length of the smaller edge is - :math:`scale_size`. - Next, the image is cropped into patches with height and width equal to - :math:`crop_size`. + First, the image is scaled or resized according to :math:`scale_size`. + Next, the image is cropped to :math:`crop_size`. Last, the image is mean subtracted an array :obj:`mean`. Args: @@ -86,13 +90,17 @@ def _prepare(self, img): the number of crops. """ - img = scale(img, size=self.scale_size) + if isinstance(self.scale_size, int): + img = scale(img, size=self.scale_size) + else: + img = resize(img, size=self.scale_size) + if self.crop == '10': imgs = ten_crop(img, self.crop_size) - imgs -= self.mean[np.newaxis] elif self.crop == 'center': imgs = center_crop(img, self.crop_size)[np.newaxis] - imgs -= self.mean[np.newaxis] + + imgs -= self.mean[np.newaxis] return imgs diff --git a/tests/links_tests/model_tests/test_feature_extraction_predictor.py b/tests/links_tests/model_tests/test_feature_extraction_predictor.py index f49eae0c66..60a9c86965 100644 --- a/tests/links_tests/model_tests/test_feature_extraction_predictor.py +++ b/tests/links_tests/model_tests/test_feature_extraction_predictor.py @@ -65,10 +65,11 @@ def test_gpu(self): self.check(self.x) -@testing.parameterize( - {'crop': 'center', 'crop_size': 192}, - {'crop': '10', 'crop_size': 192} -) +@testing.parameterize(*testing.product({ + 'crop': ['center', '10'], + 'crop_size': [192, (192, 256), (256, 192)], + 'scale_size': [256, (256, 256)] +})) class TestFeatureExtractionPredictorPrepare(unittest.TestCase): n_channel = 3 @@ -76,17 +77,21 @@ class TestFeatureExtractionPredictorPrepare(unittest.TestCase): def setUp(self): self.link = FeatureExtractionPredictor( DummyFeatureExtractor((1,), None), - crop_size=self.crop_size, crop=self.crop) + crop_size=self.crop_size, scale_size=self.scale_size, + crop=self.crop) + + if isinstance(self.crop_size, int): + hw = (self.crop_size, self.crop_size) + else: + hw = self.crop_size if self.crop == 'center': - self.expected_shape = ( - 1, self.n_channel, self.crop_size, self.crop_size) + self.expected_shape = (1, self.n_channel) + hw elif self.crop == '10': - self.expected_shape = ( - 10, self.n_channel, self.crop_size, self.crop_size) + self.expected_shape = (10, self.n_channel) + hw def test(self): out = self.link._prepare( - np.random.uniform(size=(self.n_channel, 128, 256))) + np.random.uniform(size=(self.n_channel, 286, 286))) self.assertEqual(out.shape, self.expected_shape) From 465f1df8d76b37dcc75545e3b38201e08f15df83 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Sun, 6 Aug 2017 11:11:54 +0900 Subject: [PATCH 112/139] use choices kwarg for eval_imagenet --- examples/classification/eval_imagenet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index 150b4b6c0f..523f7ec703 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -43,7 +43,7 @@ def main(): parser.add_argument('--pretrained_model') parser.add_argument('--gpu', type=int, default=-1) parser.add_argument('--batchsize', type=int, default=32) - parser.add_argument('--crop', type=str, default='center') + parser.add_argument('--crop', choices=('center', '10'), default='center') args = parser.parse_args() dataset = DirectoryParsingClassificationDataset(args.val) From 62edb90e97272b7226355f2f9482ec8ff23ecfbb Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Sun, 6 Aug 2017 16:53:57 +0900 Subject: [PATCH 113/139] update download link of FasterRCNNVGG16 --- chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py index e5482e7357..bdd575645f 100644 --- a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py +++ b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py @@ -73,7 +73,7 @@ class FasterRCNNVGG16(FasterRCNN): 'voc07': { 'n_fg_class': 20, 'url': 'https://github.com/yuyu2172/share-weights/releases/' - 'download/0.0.3/faster_rcnn_vgg16_voc07_2017_06_06.npz' + 'download/0.0.4/faster_rcnn_vgg16_voc07_trained_2017_08_06_trial_4.npz' } } feat_stride = 16 From 895ef8a55dfa8ad4042863bdb46db2ddd876cebc Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Sun, 6 Aug 2017 17:17:39 +0900 Subject: [PATCH 114/139] flake8 --- chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py index bdd575645f..e2767cb485 100644 --- a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py +++ b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py @@ -73,7 +73,8 @@ class FasterRCNNVGG16(FasterRCNN): 'voc07': { 'n_fg_class': 20, 'url': 'https://github.com/yuyu2172/share-weights/releases/' - 'download/0.0.4/faster_rcnn_vgg16_voc07_trained_2017_08_06_trial_4.npz' + 'download/0.0.4/' + 'faster_rcnn_vgg16_voc07_trained_2017_08_06_trial_4.npz' } } feat_stride = 16 From 141bc39943ff4a0fa3fe3f7fe029429d582abe89 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 7 Aug 2017 21:12:01 +0900 Subject: [PATCH 115/139] Convolution2DBlock -> Conv2DActiv --- chainercv/links/__init__.py | 2 +- chainercv/links/connection/__init__.py | 2 +- chainercv/links/connection/conv_2d_activ.py | 69 ++++++++++++++++ .../links/connection/convolution_2d_block.py | 79 ------------------- chainercv/links/model/vgg/vgg16.py | 51 +++++------- docs/source/reference/links/connection.rst | 6 +- ...tion_2d_block.py => test_conv_2d_activ.py} | 30 +++---- 7 files changed, 105 insertions(+), 134 deletions(-) create mode 100644 chainercv/links/connection/conv_2d_activ.py delete mode 100644 chainercv/links/connection/convolution_2d_block.py rename tests/links_tests/connection_tests/{test_convolution_2d_block.py => test_conv_2d_activ.py} (58%) diff --git a/chainercv/links/__init__.py b/chainercv/links/__init__.py index d56f355ca2..270e9c6359 100644 --- a/chainercv/links/__init__.py +++ b/chainercv/links/__init__.py @@ -1,4 +1,4 @@ -from chainercv.links.connection.convolution_2d_block import Convolution2DBlock # NOQA +from chainercv.links.connection.conv_2d_activ import Conv2DActiv # NOQA from chainercv.links.model.feature_extraction_predictor import FeatureExtractionPredictor # NOQA from chainercv.links.model.pixelwise_softmax_classifier import PixelwiseSoftmaxClassifier # NOQA diff --git a/chainercv/links/connection/__init__.py b/chainercv/links/connection/__init__.py index 2de8e7ea00..215e41b6a6 100644 --- a/chainercv/links/connection/__init__.py +++ b/chainercv/links/connection/__init__.py @@ -1 +1 @@ -from chainercv.links.connection.convolution_2d_block import Convolution2DBlock # NOQA +from chainercv.links.connection.conv_2d_activ import Conv2DActiv # NOQA diff --git a/chainercv/links/connection/conv_2d_activ.py b/chainercv/links/connection/conv_2d_activ.py new file mode 100644 index 0000000000..5a3a9afce5 --- /dev/null +++ b/chainercv/links/connection/conv_2d_activ.py @@ -0,0 +1,69 @@ +import chainer +from chainer.functions import relu +from chainer.links import Convolution2D + + +class Conv2DActiv(chainer.Chain): + """Convolution2D --> Activation + + This is a chain that does two-dimensional convolution + and applies an activation. + + Example: + + There are sevaral ways to make a Conv2DActiv chain. + + 1. Give the first three arguments explicitly: + + >>> l = Conv2DActiv(5, 10, 3) + + 2. Omit :obj:`in_channels` or fill it with :obj:`None`: + + In these ways, attributes are initialized at runtime based on + the channel size of the input. + + >>> l = Conv2DActiv(None, 10, 3) + >>> l = Conv2DActiv(10, 3) + + Args: + in_channels (int or None): Number of channels of input arrays. + If :obj:`None`, parameter initialization will be deferred until the + first forward data pass at which time the size will be determined. + out_channels (int): Number of channels of output arrays. + ksize (int or pair of ints): Size of filters (a.k.a. kernels). + :obj:`ksize=k` and :obj:`ksize=(k, k)` are equivalent. + stride (int or pair of ints): Stride of filter applications. + :obj:`stride=s` and :obj:`stride=(s, s)` are equivalent. + pad (int or pair of ints): Spatial padding width for input arrays. + :obj:`pad=p` and :obj:`pad=(p, p)` are equivalent. + nobias (bool): If :obj:`True`, + then this link does not use the bias term. + initialW (4-D array): Initial weight value. If :obj:`None`, the default + initializer is used. + May also be a callable that takes :obj:`numpy.ndarray` or + :obj:`cupy.ndarray` and edits its value. + initial_bias (1-D array): Initial bias value. If :obj:`None`, the bias + is set to 0. + May also be a callable that takes :obj:`numpy.ndarray` or + :obj:`cupy.ndarray` and edits its value. + activation (callable): An activation function. The default value is + :func:`chainer.functions.relu`. + + """ + + def __init__(self, in_channels, out_channels, ksize=None, + stride=1, pad=0, nobias=False, initialW=None, + initial_bias=None, activation=relu): + if ksize is None: + out_channels, ksize, in_channels = in_channels, out_channels, None + + self.activation = activation + super(Conv2DActiv, self).__init__() + with self.init_scope(): + self.conv = Convolution2D( + in_channels, out_channels, ksize, stride, pad, + nobias, initialW, initial_bias) + + def __call__(self, x): + h = self.conv(x) + return self.activation(h) diff --git a/chainercv/links/connection/convolution_2d_block.py b/chainercv/links/connection/convolution_2d_block.py deleted file mode 100644 index 1902b1426d..0000000000 --- a/chainercv/links/connection/convolution_2d_block.py +++ /dev/null @@ -1,79 +0,0 @@ -import chainer -from chainer.functions import relu -from chainer.links import BatchNormalization -from chainer.links import Convolution2D - - -class Convolution2DBlock(chainer.Chain): - """Convolution2D --> (Batch Normalization) --> Activation - - This is a chain that does two-dimensional convolution - and applies an activation. - Optionally, batch normalization can be executed in the middle. - - The parameters are a combination of the ones for - :class:`chainer.links.Convolution2D` and - :class:`chainer.links.BatchNormalization` except for - :obj:`activation` and :obj:`use_bn`. - - :obj:`activation` is a callable. The default value is - :func:`chainer.functions.relu`. - - :obj:`use_bn` is a bool that indicates whether to use - batch normalization or not. The default value is :obj:`False`. - - Example: - - There are sevaral ways to make a Convolution2DBlock chain. - - 1. Give the first three arguments explicitly: - - >>> l = Convolution2DBlock(5, 10, 3) - - 2. Omit :obj:`in_channels` or fill it with :obj:`None`: - - In these ways, attributes are initialized at runtime based on - the channel size of the input. - - >>> l = Convolution2DBlock(None, 10, 3) - >>> l = Convolution2DBlock(10, 3) - - - Args: - in_channels (int or None): Number of channels of input arrays. - If :obj:`None`, parameter initialization will be deferred until the - first forward data pass at which time the size will be determined. - out_channels (int): Number of channels of output arrays. - ksize (int or pair of ints): Size of filters (a.k.a. kernels). - :obj:`ksize=k` and :obj:`ksize=(k, k)` are equivalent. - activation (callable): An activation function. The default value is - :func:`chainer.functions.relu`. - use_bn (bool): Indicates whether to use batch normalization or not. - The default value is :obj:`False`. - conv_kwargs (dict): Key-word arguments passed to initialize - :class:`chainer.links.Convolution2D`. - bn_kwargs (dict): Key-word arguments passed to initialize - :class:`chainer.links.BatchNormalization`. - - """ - - def __init__(self, in_channels, out_channels, ksize=None, - activation=relu, use_bn=False, - conv_kwargs=dict(), bn_kwargs=dict()): - if ksize is None: - out_channels, ksize, in_channels = in_channels, out_channels, None - - self.use_bn = use_bn - self.activation = activation - super(Convolution2DBlock, self).__init__() - with self.init_scope(): - self.conv = Convolution2D(in_channels, out_channels, ksize, - **conv_kwargs) - if self.use_bn: - self.bn = BatchNormalization(out_channels, **bn_kwargs) - - def __call__(self, x): - h = self.conv(x) - if self.use_bn: - h = self.bn(h) - return self.activation(h) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 5afc653466..4fe8314690 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -14,7 +14,7 @@ from chainercv.utils import download_model -from chainercv.links.connection.convolution_2d_block import Convolution2DBlock +from chainercv.links.connection.conv_2d_activ import Conv2DActiv from chainercv.links.model.sequential_feature_extractor import \ SequentialFeatureExtractor @@ -112,50 +112,35 @@ def __init__(self, # As a sampling process is time-consuming, # we employ a zero initializer for faster computation. initialW = constant.Zero() - conv_kwargs = {'stride': 1, 'pad': 1, - 'initialW': initialW, 'initial_bias': initial_bias} - fc_kwargs = {'initialW': initialW, 'initial_bias': initial_bias} + kwargs = {'initialW': initialW, 'initial_bias': initial_bias} super(VGG16, self).__init__() with self.init_scope(): - self.conv1_1 = Convolution2DBlock(None, 64, 3, - conv_kwargs=conv_kwargs) - self.conv1_2 = Convolution2DBlock(None, 64, 3, - conv_kwargs=conv_kwargs) + self.conv1_1 = Conv2DActiv(None, 64, 3, 1, 1, **kwargs) + self.conv1_2 = Conv2DActiv(None, 64, 3, 1, 1, **kwargs) self.pool1 = _max_pooling_2d - self.conv2_1 = Convolution2DBlock(None, 128, 3, - conv_kwargs=conv_kwargs) - self.conv2_2 = Convolution2DBlock(None, 128, 3, - conv_kwargs=conv_kwargs) + self.conv2_1 = Conv2DActiv(None, 128, 3, 1, 1, **kwargs) + self.conv2_2 = Conv2DActiv(None, 128, 3, 1, 1, **kwargs) self.pool2 = _max_pooling_2d - self.conv3_1 = Convolution2DBlock(None, 256, 3, - conv_kwargs=conv_kwargs) - self.conv3_2 = Convolution2DBlock(None, 256, 3, - conv_kwargs=conv_kwargs) - self.conv3_3 = Convolution2DBlock(None, 256, 3, - conv_kwargs=conv_kwargs) + self.conv3_1 = Conv2DActiv(None, 256, 3, 1, 1, **kwargs) + self.conv3_2 = Conv2DActiv(None, 256, 3, 1, 1, **kwargs) + self.conv3_3 = Conv2DActiv(None, 256, 3, 1, 1, **kwargs) self.pool3 = _max_pooling_2d - self.conv4_1 = Convolution2DBlock(None, 512, 3, - conv_kwargs=conv_kwargs) - self.conv4_2 = Convolution2DBlock(None, 512, 3, - conv_kwargs=conv_kwargs) - self.conv4_3 = Convolution2DBlock(None, 512, 3, - conv_kwargs=conv_kwargs) + self.conv4_1 = Conv2DActiv(None, 512, 3, 1, 1, **kwargs) + self.conv4_2 = Conv2DActiv(None, 512, 3, 1, 1, **kwargs) + self.conv4_3 = Conv2DActiv(None, 512, 3, 1, 1, **kwargs) self.pool4 = _max_pooling_2d - self.conv5_1 = Convolution2DBlock(None, 512, 3, - conv_kwargs=conv_kwargs) - self.conv5_2 = Convolution2DBlock(None, 512, 3, - conv_kwargs=conv_kwargs) - self.conv5_3 = Convolution2DBlock(None, 512, 3, - conv_kwargs=conv_kwargs) + self.conv5_1 = Conv2DActiv(None, 512, 3, 1, 1, **kwargs) + self.conv5_2 = Conv2DActiv(None, 512, 3, 1, 1, **kwargs) + self.conv5_3 = Conv2DActiv(None, 512, 3, 1, 1, **kwargs) self.pool5 = _max_pooling_2d - self.fc6 = Linear(None, 4096, **fc_kwargs) + self.fc6 = Linear(None, 4096, **kwargs) self.fc6_relu = relu self.fc6_dropout = dropout - self.fc7 = Linear(None, 4096, **fc_kwargs) + self.fc7 = Linear(None, 4096, **kwargs) self.fc7_relu = relu self.fc7_dropout = dropout - self.fc8 = Linear(None, n_class, **fc_kwargs) + self.fc8 = Linear(None, n_class, **kwargs) self.prob = softmax if pretrained_model in self._models: diff --git a/docs/source/reference/links/connection.rst b/docs/source/reference/links/connection.rst index 874f88564b..f95e956e0d 100644 --- a/docs/source/reference/links/connection.rst +++ b/docs/source/reference/links/connection.rst @@ -4,6 +4,6 @@ Connection .. module:: chainercv.links.connection -Convolution2DBlock ------------------- -.. autoclass:: Convolution2DBlock +Conv2DActiv +----------- +.. autoclass:: Conv2DActiv diff --git a/tests/links_tests/connection_tests/test_convolution_2d_block.py b/tests/links_tests/connection_tests/test_conv_2d_activ.py similarity index 58% rename from tests/links_tests/connection_tests/test_convolution_2d_block.py rename to tests/links_tests/connection_tests/test_conv_2d_activ.py index 5b5d25db26..6a7eb0fc70 100644 --- a/tests/links_tests/connection_tests/test_convolution_2d_block.py +++ b/tests/links_tests/connection_tests/test_conv_2d_activ.py @@ -7,14 +7,15 @@ from chainer import testing from chainer.testing import attr -from chainercv.links import Convolution2DBlock +from chainercv.links import Conv2DActiv -@testing.parameterize(*testing.product({ - 'use_bn': [True, False], - 'args_style': ['explicit', 'None', 'omit'] -})) -class TestConvolution2DBlock(unittest.TestCase): +@testing.parameterize( + {'args_style': 'explicit'}, + {'args_style': 'None'}, + {'args_style': 'omit'} +) +class TestConv2DActiv(unittest.TestCase): in_channels = 3 out_channels = 5 @@ -28,20 +29,15 @@ def setUp(self): self.gy = np.random.uniform( -1, 1, (5, self.out_channels, 5, 5)).astype(np.float32) if self.args_style == 'explicit': - self.l = Convolution2DBlock( + self.l = Conv2DActiv( self.in_channels, self.out_channels, self.ksize, - use_bn=self.use_bn, - conv_kwargs={'stride': self.stride, 'pad': self.pad}) + self.stride, self.pad) elif self.args_style == 'None': - self.l = Convolution2DBlock( - None, self.out_channels, self.ksize, - use_bn=self.use_bn, - conv_kwargs={'stride': self.stride, 'pad': self.pad}) + self.l = Conv2DActiv( + None, self.out_channels, self.ksize, self.stride, self.pad) elif self.args_style == 'omit': - self.l = Convolution2DBlock( - self.out_channels, self.ksize, - use_bn=self.use_bn, - conv_kwargs={'stride': self.stride, 'pad': self.pad}) + self.l = Conv2DActiv( + self.out_channels, self.ksize, stride=self.stride, pad=self.pad) def check_backward(self, x_data, y_grad): x = chainer.Variable(x_data) From 6d4cf2946717be40df9b8e6aa309b7a24d34a03d Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 7 Aug 2017 21:14:26 +0900 Subject: [PATCH 116/139] use activ instead of activation --- chainercv/links/connection/conv_2d_activ.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chainercv/links/connection/conv_2d_activ.py b/chainercv/links/connection/conv_2d_activ.py index 5a3a9afce5..66b2375e16 100644 --- a/chainercv/links/connection/conv_2d_activ.py +++ b/chainercv/links/connection/conv_2d_activ.py @@ -46,18 +46,18 @@ class Conv2DActiv(chainer.Chain): is set to 0. May also be a callable that takes :obj:`numpy.ndarray` or :obj:`cupy.ndarray` and edits its value. - activation (callable): An activation function. The default value is + activ (callable): An activation function. The default value is :func:`chainer.functions.relu`. """ def __init__(self, in_channels, out_channels, ksize=None, stride=1, pad=0, nobias=False, initialW=None, - initial_bias=None, activation=relu): + initial_bias=None, activ=relu): if ksize is None: out_channels, ksize, in_channels = in_channels, out_channels, None - self.activation = activation + self.activ = activ super(Conv2DActiv, self).__init__() with self.init_scope(): self.conv = Convolution2D( @@ -66,4 +66,4 @@ def __init__(self, in_channels, out_channels, ksize=None, def __call__(self, x): h = self.conv(x) - return self.activation(h) + return self.activ(h) From b7df511d06eaf1e6ef12f0a9341007ae35114eec Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 7 Aug 2017 21:29:10 +0900 Subject: [PATCH 117/139] add a note in Conv2DActiv doc --- chainercv/links/connection/conv_2d_activ.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/chainercv/links/connection/conv_2d_activ.py b/chainercv/links/connection/conv_2d_activ.py index 66b2375e16..02adb77c74 100644 --- a/chainercv/links/connection/conv_2d_activ.py +++ b/chainercv/links/connection/conv_2d_activ.py @@ -9,6 +9,10 @@ class Conv2DActiv(chainer.Chain): This is a chain that does two-dimensional convolution and applies an activation. + The arguments are the same as that of + :class:`chainer.links.Convolution2D` + except for `obj:`activ`. + Example: There are sevaral ways to make a Conv2DActiv chain. From fd0dde57aadc08f6e2b68d5f93decbed6af72c36 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 7 Aug 2017 22:12:46 +0900 Subject: [PATCH 118/139] add test on forward and activ --- .../connection_tests/test_conv_2d_activ.py | 67 ++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/tests/links_tests/connection_tests/test_conv_2d_activ.py b/tests/links_tests/connection_tests/test_conv_2d_activ.py index 6a7eb0fc70..ad061a736f 100644 --- a/tests/links_tests/connection_tests/test_conv_2d_activ.py +++ b/tests/links_tests/connection_tests/test_conv_2d_activ.py @@ -10,12 +10,73 @@ from chainercv.links import Conv2DActiv +def _add_one(x): + return x + 1 + + @testing.parameterize( {'args_style': 'explicit'}, {'args_style': 'None'}, {'args_style': 'omit'} ) -class TestConv2DActiv(unittest.TestCase): +class TestConv2DActivForward(unittest.TestCase): + + in_channels = 1 + out_channels = 1 + ksize = 3 + stride = 1 + pad = 1 + + def setUp(self): + self.x = np.random.uniform( + -1, 1, (5, self.in_channels, 5, 5)).astype(np.float32) + + # Convolution is the identity function. + initialW = np.array([[0, 0, 0], [0, 1, 0], [0, 0, 0]], + dtype=np.float32).reshape(1, 1, 3, 3) + initial_bias = 0 + if self.args_style == 'explicit': + self.l = Conv2DActiv( + self.in_channels, self.out_channels, self.ksize, + self.stride, self.pad, + initialW=initialW, initial_bias=initial_bias, + activ=_add_one) + elif self.args_style == 'None': + self.l = Conv2DActiv( + None, self.out_channels, self.ksize, self.stride, self.pad, + initialW=initialW, initial_bias=initial_bias, + activ=_add_one) + elif self.args_style == 'omit': + self.l = Conv2DActiv( + self.out_channels, self.ksize, stride=self.stride, + pad=self.pad, initialW=initialW, initial_bias=initial_bias, + activ=_add_one) + + def check_forward(self, x_data): + x = chainer.Variable(x_data) + y = self.l(x) + + self.assertIsInstance(y, chainer.Variable) + self.assertIsInstance(y.data, self.l.xp.ndarray) + + np.testing.assert_almost_equal( + cuda.to_cpu(y.data), cuda.to_cpu(x_data) + 1) + + def test_forward_cpu(self): + self.check_forward(self.x) + + @attr.gpu + def test_forward_gpu(self): + self.l.to_gpu() + self.check_forward(cuda.to_gpu(self.x)) + + +@testing.parameterize( + {'args_style': 'explicit'}, + {'args_style': 'None'}, + {'args_style': 'omit'} +) +class TestConv2DActivBackward(unittest.TestCase): in_channels = 3 out_channels = 5 @@ -28,6 +89,7 @@ def setUp(self): -1, 1, (5, self.in_channels, 5, 5)).astype(np.float32) self.gy = np.random.uniform( -1, 1, (5, self.out_channels, 5, 5)).astype(np.float32) + if self.args_style == 'explicit': self.l = Conv2DActiv( self.in_channels, self.out_channels, self.ksize, @@ -37,7 +99,8 @@ def setUp(self): None, self.out_channels, self.ksize, self.stride, self.pad) elif self.args_style == 'omit': self.l = Conv2DActiv( - self.out_channels, self.ksize, stride=self.stride, pad=self.pad) + self.out_channels, self.ksize, stride=self.stride, + pad=self.pad) def check_backward(self, x_data, y_grad): x = chainer.Variable(x_data) From 5b2a25777fc391f1e624eb94297df2a3915f379f Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 8 Aug 2017 09:36:00 +0900 Subject: [PATCH 119/139] that of -> those of --- chainercv/links/connection/conv_2d_activ.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainercv/links/connection/conv_2d_activ.py b/chainercv/links/connection/conv_2d_activ.py index 02adb77c74..711006bd2e 100644 --- a/chainercv/links/connection/conv_2d_activ.py +++ b/chainercv/links/connection/conv_2d_activ.py @@ -9,7 +9,7 @@ class Conv2DActiv(chainer.Chain): This is a chain that does two-dimensional convolution and applies an activation. - The arguments are the same as that of + The arguments are the same as those of :class:`chainer.links.Convolution2D` except for `obj:`activ`. From 86029ef6e3f5e30125607d42828a67a231d1bd34 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 8 Aug 2017 09:37:50 +0900 Subject: [PATCH 120/139] fix doc --- chainercv/links/connection/conv_2d_activ.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainercv/links/connection/conv_2d_activ.py b/chainercv/links/connection/conv_2d_activ.py index 711006bd2e..0a97b6c47b 100644 --- a/chainercv/links/connection/conv_2d_activ.py +++ b/chainercv/links/connection/conv_2d_activ.py @@ -11,7 +11,7 @@ class Conv2DActiv(chainer.Chain): The arguments are the same as those of :class:`chainer.links.Convolution2D` - except for `obj:`activ`. + except for :obj:`activ`. Example: From 93d8e673caeab31307d1f200404f4d669c229701 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 8 Aug 2017 09:49:17 +0900 Subject: [PATCH 121/139] fix doc --- chainercv/links/connection/conv_2d_activ.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainercv/links/connection/conv_2d_activ.py b/chainercv/links/connection/conv_2d_activ.py index 0a97b6c47b..57d3d339f0 100644 --- a/chainercv/links/connection/conv_2d_activ.py +++ b/chainercv/links/connection/conv_2d_activ.py @@ -15,7 +15,7 @@ class Conv2DActiv(chainer.Chain): Example: - There are sevaral ways to make a Conv2DActiv chain. + There are sevaral ways to initialize a :class:`Conv2DActiv`. 1. Give the first three arguments explicitly: From 53bb7c692e92404e9cb3a568e5a2a37375cef617 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 8 Aug 2017 09:55:56 +0900 Subject: [PATCH 122/139] fix doc --- .../model/feature_extraction_predictor.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/chainercv/links/model/feature_extraction_predictor.py b/chainercv/links/model/feature_extraction_predictor.py index dadaf1077e..32ceb4ea13 100644 --- a/chainercv/links/model/feature_extraction_predictor.py +++ b/chainercv/links/model/feature_extraction_predictor.py @@ -37,19 +37,21 @@ class FeatureExtractionPredictor(chainer.Chain): patches that are ten-cropped from the input images. When extracting more than one crops from an image, the output of - :meth:`predict` returns average of the features computed from the crops. + :meth:`predict` returns the average of the features computed from the + crops. Args: extractor: A feature extraction model. This is a callable chain that takes a batch of images and returns a variable or a - tuple of variables - crop_size (int or tuple): The height and the width of a cropped image. - If this is an integer, an image is cropped to + tuple of variables. + crop_size (int or tuple): The height and the width of an image after + cropping in preprocessing. + If this is an integer, the image is cropped to :math:`(crop_size, crop_size)`. - scale_size (int or tuple): If :obj:`scale_size` is an integer, - an image is resized so that the length of the shorter edge is equal - to :obj:`scale_size`. If this is a tuple :obj:`(height, width)`, - an image is resized to :math:`(height, width)`. + scale_size (int or tuple): If :obj:`scale_size` is an integer, during + preprocessing, an image is resized so that the length of the shorter + edge is equal to :obj:`scale_size`. If this is a tuple + :obj:`(height, width)`, the image is resized to :math:`(height, width)`. crop ({'center', '10'}): Determines the style of cropping. """ @@ -78,7 +80,7 @@ def _prepare(self, img): models. First, the image is scaled or resized according to :math:`scale_size`. Next, the image is cropped to :math:`crop_size`. - Last, the image is mean subtracted an array :obj:`mean`. + Last, the image is mean subtracted by an array :obj:`mean`. Args: img (~numpy.ndarray): An image. This is in CHW and RGB format. From 3a13b35caf1777e8b20f1967a2aaae314113b0f7 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 8 Aug 2017 09:59:08 +0900 Subject: [PATCH 123/139] fix doc --- chainercv/links/model/vgg/vgg16.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 4fe8314690..3db2dc786d 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -28,11 +28,11 @@ class VGG16(SequentialFeatureExtractor): """VGG16 Network for classification and feature extraction. - This model is a feature extraction link. + This is a feature extraction model. The network can choose to output features from set of all - intermediate and final features produced by the original architecture. - The value of :obj:`VGG16.feature_names` selects the features to be - collected by :meth:`__call__`. + intermediate features. + The value of :obj:`VGG16.feature_names` selects the features that are going + to be collected by :meth:`__call__`. :obj:`self.all_feature_names` is the list of the names of features that can be collected. @@ -73,8 +73,8 @@ class VGG16(SequentialFeatureExtractor): where :obj:`$CHAINER_DATASET_ROOT` is set as :obj:`$HOME/.chainer/dataset` unless you specify another value by modifying the environment variable. - n_class (int): The dimension of the output of fc8. - mean (numpy.ndarray): A mean image. If :obj:`None` and + n_class (int): The number of classes. + mean (numpy.ndarray): A mean value. If :obj:`None` and a supported pretrained model is used, the mean value used to train the pretrained model will be used. initialW (callable): Initializer for the weights. From 8ea45df02cecc58ba46963ab8d777ba273743471 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Tue, 8 Aug 2017 14:04:50 +0900 Subject: [PATCH 124/139] flake8 --- chainercv/links/model/feature_extraction_predictor.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/chainercv/links/model/feature_extraction_predictor.py b/chainercv/links/model/feature_extraction_predictor.py index 32ceb4ea13..c6f146bb29 100644 --- a/chainercv/links/model/feature_extraction_predictor.py +++ b/chainercv/links/model/feature_extraction_predictor.py @@ -49,9 +49,10 @@ class FeatureExtractionPredictor(chainer.Chain): If this is an integer, the image is cropped to :math:`(crop_size, crop_size)`. scale_size (int or tuple): If :obj:`scale_size` is an integer, during - preprocessing, an image is resized so that the length of the shorter - edge is equal to :obj:`scale_size`. If this is a tuple - :obj:`(height, width)`, the image is resized to :math:`(height, width)`. + preprocessing, an image is resized so that the length of the + shorter edge is equal to :obj:`scale_size`. If this is a tuple + :obj:`(height, width)`, the image is resized to + :math:`(height, width)`. crop ({'center', '10'}): Determines the style of cropping. """ From d29172e35dc966b6a85a0cfa1d5416fba70fa132 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Sun, 20 Aug 2017 09:14:37 +0900 Subject: [PATCH 125/139] update code --- .../model/feature_extraction_predictor.py | 161 ------------------ docs/source/reference/links.rst | 2 +- docs/source/reference/links/vgg.rst | 1 - examples/classification/eval_imagenet.py | 6 +- .../test_feature_extraction_predictor.py | 99 ----------- 5 files changed, 4 insertions(+), 265 deletions(-) delete mode 100644 chainercv/links/model/feature_extraction_predictor.py delete mode 100644 tests/links_tests/model_tests/test_feature_extraction_predictor.py diff --git a/chainercv/links/model/feature_extraction_predictor.py b/chainercv/links/model/feature_extraction_predictor.py deleted file mode 100644 index c6f146bb29..0000000000 --- a/chainercv/links/model/feature_extraction_predictor.py +++ /dev/null @@ -1,161 +0,0 @@ -import numpy as np -import warnings - -import chainer -from chainer import cuda - -from chainercv.transforms import center_crop -from chainercv.transforms import resize -from chainercv.transforms import scale -from chainercv.transforms import ten_crop - - -class FeatureExtractionPredictor(chainer.Chain): - - """Wrapper that adds a prediction method to a feature extraction model. - - The :meth:`predict` takes three steps to make a prediction. - - 1. Preprocess input images - 2. Forward the preprocessed images to the network - 3. Average features in the case when more than one crops are extracted. - - Example: - - >>> from chainercv.links import VGG16 - >>> from chainercv.links import FeatureExtractionPredictor - >>> base_model = VGG16() - >>> model = FeatureExtractionPredictor(base_model) - >>> prob = model.predict([img]) - # Predicting multiple features - >>> model.extractor.feature_names = ['conv5_3', 'fc7'] - >>> conv5_3, fc7 = model.predict([img]) - - When :obj:`self.crop == 'center'`, :meth:`predict` extracts features from - the center crop of the input images. - When :obj:`self.crop == '10'`, :meth:`predict` extracts features from - patches that are ten-cropped from the input images. - - When extracting more than one crops from an image, the output of - :meth:`predict` returns the average of the features computed from the - crops. - - Args: - extractor: A feature extraction model. This is a callable chain - that takes a batch of images and returns a variable or a - tuple of variables. - crop_size (int or tuple): The height and the width of an image after - cropping in preprocessing. - If this is an integer, the image is cropped to - :math:`(crop_size, crop_size)`. - scale_size (int or tuple): If :obj:`scale_size` is an integer, during - preprocessing, an image is resized so that the length of the - shorter edge is equal to :obj:`scale_size`. If this is a tuple - :obj:`(height, width)`, the image is resized to - :math:`(height, width)`. - crop ({'center', '10'}): Determines the style of cropping. - - """ - - def __init__(self, extractor, - crop_size=224, scale_size=256, - crop='center'): - super(FeatureExtractionPredictor, self).__init__() - self.scale_size = scale_size - if isinstance(crop_size, int): - crop_size = (crop_size, crop_size) - self.crop_size = crop_size - self.crop = crop - - with self.init_scope(): - self.extractor = extractor - - @property - def mean(self): - return self.extractor.mean - - def _prepare(self, img): - """Prepare an image for feeding it to a model. - - This is a standard preprocessing scheme used by feature extraction - models. - First, the image is scaled or resized according to :math:`scale_size`. - Next, the image is cropped to :math:`crop_size`. - Last, the image is mean subtracted by an array :obj:`mean`. - - Args: - img (~numpy.ndarray): An image. This is in CHW and RGB format. - The range of its value is :math:`[0, 255]`. - - Returns: - ~numpy.ndarray: - A preprocessed image. This is 4D array whose batch size is - the number of crops. - - """ - if isinstance(self.scale_size, int): - img = scale(img, size=self.scale_size) - else: - img = resize(img, size=self.scale_size) - - if self.crop == '10': - imgs = ten_crop(img, self.crop_size) - elif self.crop == 'center': - imgs = center_crop(img, self.crop_size)[np.newaxis] - - imgs -= self.mean[np.newaxis] - - return imgs - - def _average_crops(self, y, n_crop): - if y.ndim == 4: - warnings.warn( - 'Four dimensional features are averaged. ' - 'If these are batch of 2D spatial features, ' - 'their spatial information would be lost.') - - xp = chainer.cuda.get_array_module(y) - n = y.shape[0] // n_crop - y = y.reshape((n, n_crop) + y.shape[1:]) - y = xp.sum(y, axis=1) / n_crop - return y - - def predict(self, imgs): - """Predict features from images. - - Given :math:`N` input images, this method outputs a batched array with - batchsize :math:`N`. - - Args: - imgs (iterable of numpy.ndarray): Array-images. - All images are in CHW and RGB format - and the range of their value is :math:`[0, 255]`. - - Returns: - numpy.ndarray or tuple of numpy.ndarray: - A batch of features or a tuple of them. - - """ - imgs = self.xp.asarray([self._prepare(img) for img in imgs]) - n_crop = imgs.shape[-4] - shape = (-1, imgs.shape[-3]) + self.crop_size - imgs = imgs.reshape(shape) - - with chainer.function.no_backprop_mode(): - imgs = chainer.Variable(imgs) - activations = self.extractor(imgs) - - if isinstance(activations, tuple): - output = [] - for activation in activations: - activation = activation.data - if n_crop > 1: - activation = self._average_crops(activation, n_crop) - output.append(cuda.to_cpu(activation)) - output = tuple(output) - else: - output = cuda.to_cpu(activations.data) - if n_crop > 1: - output = self._average_crops(output, n_crop) - - return output diff --git a/docs/source/reference/links.rst b/docs/source/reference/links.rst index 2fe9d405fd..d71bb44574 100644 --- a/docs/source/reference/links.rst +++ b/docs/source/reference/links.rst @@ -18,7 +18,7 @@ Feature extraction models can be used to extract feature(s) given images. .. autoclass:: chainercv.links.SequentialFeatureExtractor :members: -.. autoclass:: chainercv.links.FeatureExtractionPredictor +.. autoclass:: chainercv.links.FeaturePredictor Detection diff --git a/docs/source/reference/links/vgg.rst b/docs/source/reference/links/vgg.rst index 170aae5e6a..201aefca12 100644 --- a/docs/source/reference/links/vgg.rst +++ b/docs/source/reference/links/vgg.rst @@ -9,4 +9,3 @@ VGG16 .. autoclass:: VGG16 :members: - :special-members: __call__ diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index 523f7ec703..d852d73fd6 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -10,7 +10,7 @@ from chainercv.datasets import directory_parsing_label_names from chainercv.datasets import DirectoryParsingClassificationDataset -from chainercv.links import FeatureExtractionPredictor +from chainercv.links import FeaturePredictor from chainercv.links import VGG16 from chainercv.utils import apply_prediction_to_iterator @@ -50,7 +50,7 @@ def main(): label_names = directory_parsing_label_names(args.val) iterator = iterators.MultiprocessIterator( dataset, args.batchsize, repeat=False, shuffle=False, - n_processes=6, shared_mem=300000000) + n_processes=6, shared_mem=3 * 224 * 224 * 4) if args.model == 'vgg16': if args.pretrained_model: @@ -59,7 +59,7 @@ def main(): else: extractor = VGG16(pretrained_model='imagenet', n_class=len(label_names)) - model = FeatureExtractionPredictor(extractor, crop=args.crop) + model = FeaturePredictor(extractor, 224, crop=args.crop) if args.gpu >= 0: chainer.cuda.get_device(args.gpu).use() diff --git a/tests/links_tests/model_tests/test_feature_extraction_predictor.py b/tests/links_tests/model_tests/test_feature_extraction_predictor.py deleted file mode 100644 index 60a9c86965..0000000000 --- a/tests/links_tests/model_tests/test_feature_extraction_predictor.py +++ /dev/null @@ -1,99 +0,0 @@ -import numpy as np -import unittest - -import chainer -from chainer import testing -from chainer.testing import attr - -from chainercv.links import FeatureExtractionPredictor - - -class DummyFeatureExtractor(chainer.Chain): - - mean = np.array([0, 0, 0]).reshape(3, 1, 1) - - def __init__(self, shape_0, shape_1): - super(DummyFeatureExtractor, self).__init__() - self.shape_0 = shape_0 - self.shape_1 = shape_1 - - def __call__(self, x): - shape = (x.shape[0],) + self.shape_0 - y0 = self.xp.random.rand(*shape).astype(np.float32) - - if self.shape_1 is None: - return chainer.Variable(y0) - shape = (x.shape[0],) + self.shape_1 - y1 = self.xp.random.rand(*shape).astype(np.float32) - return chainer.Variable(y0), chainer.Variable(y1) - - -@testing.parameterize( - {'shape_0': (5, 10, 10), 'shape_1': None, 'crop': 'center'}, - {'shape_0': (8,), 'shape_1': None, 'crop': '10'}, - {'shape_0': (5, 10, 10), 'shape_1': (12,), 'crop': 'center'}, - {'shape_0': (8,), 'shape_1': (10,), 'crop': '10'}, -) -class TestFeatureExtractionPredictorPredict(unittest.TestCase): - - def setUp(self): - self.link = FeatureExtractionPredictor( - DummyFeatureExtractor(self.shape_0, self.shape_1), - crop=self.crop) - self.x = np.random.uniform(size=(3, 3, 32, 32)).astype(np.float32) - - self.one_output = self.shape_1 is None - - def check(self, x): - out = self.link.predict(x) - if self.one_output: - self.assertEqual(out.shape, (self.x.shape[0],) + self.shape_0) - self.assertIsInstance(out, np.ndarray) - else: - out_0, out_1 = out - self.assertEqual(out_0.shape, (self.x.shape[0],) + self.shape_0) - self.assertEqual(out_1.shape, (self.x.shape[0],) + self.shape_1) - self.assertIsInstance(out_0, np.ndarray) - self.assertIsInstance(out_1, np.ndarray) - - def test_cpu(self): - self.check(self.x) - - @attr.gpu - def test_gpu(self): - self.link.to_gpu() - self.check(self.x) - - -@testing.parameterize(*testing.product({ - 'crop': ['center', '10'], - 'crop_size': [192, (192, 256), (256, 192)], - 'scale_size': [256, (256, 256)] -})) -class TestFeatureExtractionPredictorPrepare(unittest.TestCase): - - n_channel = 3 - - def setUp(self): - self.link = FeatureExtractionPredictor( - DummyFeatureExtractor((1,), None), - crop_size=self.crop_size, scale_size=self.scale_size, - crop=self.crop) - - if isinstance(self.crop_size, int): - hw = (self.crop_size, self.crop_size) - else: - hw = self.crop_size - if self.crop == 'center': - self.expected_shape = (1, self.n_channel) + hw - elif self.crop == '10': - self.expected_shape = (10, self.n_channel) + hw - - def test(self): - out = self.link._prepare( - np.random.uniform(size=(self.n_channel, 286, 286))) - - self.assertEqual(out.shape, self.expected_shape) - - -testing.run_module(__name__, __file__) From a3a20b26fb2774e9e4802af80191d815060cfa5a Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Sun, 20 Aug 2017 09:23:27 +0900 Subject: [PATCH 126/139] update README --- examples/classification/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/classification/README.md b/examples/classification/README.md index d92c2a3c0b..1c67978c18 100644 --- a/examples/classification/README.md +++ b/examples/classification/README.md @@ -16,7 +16,7 @@ $ python eval_imagenet.py [--model vgg16] [--pretrained_mo ## How to prepare ImageNet Dataset -This instructions are copied from ImageNet training for Torch. +This instructions are based on the instruction found [here](https://github.com/facebook/fb.resnet.torch/blob/master/INSTALL.md#download-the-imagenet-dataset). The ImageNet Large Scale Visual Recognition Challenge (ILSVRC) dataset has 1000 categories and 1.2 million images. The images do not need to be preprocessed or packaged in any database, but the validation images need to be moved into appropriate subfolders. @@ -25,7 +25,7 @@ The ImageNet Large Scale Visual Recognition Challenge (ILSVRC) dataset has 1000 2. Extract the training data: ```bash mkdir train && mv ILSVRC2012_img_train.tar train/ && cd train - tar -xvf ILSVRC2012_img_train.tar && rm -f ILSVRC2012_img_train.tar + tar -xvf ILSVRC2012_img_train.tar && mv ILSVRC2012_img_train.tar .. find . -name "*.tar" | while read NAME ; do mkdir -p "${NAME%.tar}"; tar -xvf "${NAME}" -C "${NAME%.tar}"; rm -f "${NAME}"; done cd .. ``` @@ -34,6 +34,7 @@ The ImageNet Large Scale Visual Recognition Challenge (ILSVRC) dataset has 1000 ```bash mkdir val && mv ILSVRC2012_img_val.tar val/ && cd val && tar -xvf ILSVRC2012_img_val.tar wget -qO- https://raw.githubusercontent.com/soumith/imagenetloader.torch/master/valprep.sh | bash + mv ILSVRC2012_img_val.tar .. && cd .. ``` From 10d2e09104c8ed5a4042032c6537ce0f108a670d Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Sun, 20 Aug 2017 09:33:35 +0900 Subject: [PATCH 127/139] use default mean value when unsupecified --- chainercv/links/model/vgg/vgg16.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 3db2dc786d..75da524628 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -103,6 +103,8 @@ def __init__(self, if mean is None: if pretrained_model in self._models: mean = self._models[pretrained_model]['mean'] + else: + mean = _imagenet_mean self.mean = mean if initialW is None: From 3f0da8e5901ca065f20de8c2338ab6631a909a45 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Sun, 20 Aug 2017 09:43:38 +0900 Subject: [PATCH 128/139] fix eval_imagenet --- examples/classification/eval_imagenet.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index d852d73fd6..981226d30e 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -38,8 +38,7 @@ def main(): parser = argparse.ArgumentParser( description='Learning convnet from ILSVRC2012 dataset') parser.add_argument('val', help='Path to root of the validation dataset') - parser.add_argument( - '--model', choices=('vgg16')) + parser.add_argument('--model', choices=('vgg16',)) parser.add_argument('--pretrained_model') parser.add_argument('--gpu', type=int, default=-1) parser.add_argument('--batchsize', type=int, default=32) From 59f53e1d9d34e6b930391698141290c4c0f128d6 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Sun, 20 Aug 2017 09:49:26 +0900 Subject: [PATCH 129/139] fix eval_imagenet --- examples/classification/eval_imagenet.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/examples/classification/eval_imagenet.py b/examples/classification/eval_imagenet.py index 981226d30e..27483a809d 100644 --- a/examples/classification/eval_imagenet.py +++ b/examples/classification/eval_imagenet.py @@ -39,7 +39,7 @@ def main(): description='Learning convnet from ILSVRC2012 dataset') parser.add_argument('val', help='Path to root of the validation dataset') parser.add_argument('--model', choices=('vgg16',)) - parser.add_argument('--pretrained_model') + parser.add_argument('--pretrained_model', default='imagenet') parser.add_argument('--gpu', type=int, default=-1) parser.add_argument('--batchsize', type=int, default=32) parser.add_argument('--crop', choices=('center', '10'), default='center') @@ -49,16 +49,13 @@ def main(): label_names = directory_parsing_label_names(args.val) iterator = iterators.MultiprocessIterator( dataset, args.batchsize, repeat=False, shuffle=False, - n_processes=6, shared_mem=3 * 224 * 224 * 4) + n_processes=6, shared_mem=300000000) if args.model == 'vgg16': - if args.pretrained_model: - extractor = VGG16(pretrained_model=args.pretrained_model, - n_class=len(label_names)) - else: - extractor = VGG16(pretrained_model='imagenet', - n_class=len(label_names)) - model = FeaturePredictor(extractor, 224, crop=args.crop) + extractor = VGG16(pretrained_model=args.pretrained_model, + n_class=len(label_names)) + model = FeaturePredictor( + extractor, crop_size=224, scale_size=256, crop=args.crop) if args.gpu >= 0: chainer.cuda.get_device(args.gpu).use() From 4cf289c014eed4e41bb22c9326bca56ab36d09ad Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Sun, 20 Aug 2017 18:52:37 +0900 Subject: [PATCH 130/139] fix --- chainercv/links/model/vgg/vgg16.py | 26 +++++++++++++++++--------- docs/source/reference/links.rst | 2 +- examples/classification/README.md | 14 +++++++------- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/chainercv/links/model/vgg/vgg16.py b/chainercv/links/model/vgg/vgg16.py index 75da524628..bd43f102e5 100644 --- a/chainercv/links/model/vgg/vgg16.py +++ b/chainercv/links/model/vgg/vgg16.py @@ -26,10 +26,10 @@ class VGG16(SequentialFeatureExtractor): - """VGG16 Network for classification and feature extraction. + """VGG-16 Network for classification and feature extraction. This is a feature extraction model. - The network can choose to output features from set of all + The network can choose output features from set of all intermediate features. The value of :obj:`VGG16.feature_names` selects the features that are going to be collected by :meth:`__call__`. @@ -39,15 +39,15 @@ class VGG16(SequentialFeatureExtractor): Examples: >>> model = VGG16() - # By default, VGG16.__call__ returns a probability score. + # By default, __call__ returns a probability score (after Softmax). >>> prob = model(imgs) >>> model.feature_names = 'conv5_3' - # This is feature conv5_3. + # This is feature conv5_3 (after ReLU). >>> feat5_3 = model(imgs) >>> model.feature_names = ['conv5_3', 'fc6'] - >>> # These are features conv5_3 and fc6. + >>> # These are features conv5_3 (after ReLU) and fc6 (before ReLU). >>> feat5_3, feat6 = model(imgs) .. seealso:: @@ -73,10 +73,18 @@ class VGG16(SequentialFeatureExtractor): where :obj:`$CHAINER_DATASET_ROOT` is set as :obj:`$HOME/.chainer/dataset` unless you specify another value by modifying the environment variable. - n_class (int): The number of classes. - mean (numpy.ndarray): A mean value. If :obj:`None` and - a supported pretrained model is used, - the mean value used to train the pretrained model will be used. + n_class (int): The number of classes. If :obj:`None`, + the default values are used. + If a supported pretrained model is used, + the number of classes used to train the pretrained model + is used. Otherwise, the number of classes in ILSVRC 2012 dataset + is used. + mean (numpy.ndarray): A mean value. If :obj:`None`, + the default values are used. + If a supported pretrained model is used, + the mean value used to train the pretrained model is used. + Otherwise, the mean value calculated from ILSVRC 2012 dataset + is used. initialW (callable): Initializer for the weights. initial_bias (callable): Initializer for the biases. diff --git a/docs/source/reference/links.rst b/docs/source/reference/links.rst index d71bb44574..e1f59d820f 100644 --- a/docs/source/reference/links.rst +++ b/docs/source/reference/links.rst @@ -8,7 +8,7 @@ Model Feature Extraction ~~~~~~~~~~~~~~~~~~ -Feature extraction models can be used to extract feature(s) given images. +Feature extraction models extract feature(s) from given images. .. toctree:: diff --git a/examples/classification/README.md b/examples/classification/README.md index 1c67978c18..874e3c2ef8 100644 --- a/examples/classification/README.md +++ b/examples/classification/README.md @@ -24,17 +24,17 @@ The ImageNet Large Scale Visual Recognition Challenge (ILSVRC) dataset has 1000 2. Extract the training data: ```bash - mkdir train && mv ILSVRC2012_img_train.tar train/ && cd train - tar -xvf ILSVRC2012_img_train.tar && mv ILSVRC2012_img_train.tar .. - find . -name "*.tar" | while read NAME ; do mkdir -p "${NAME%.tar}"; tar -xvf "${NAME}" -C "${NAME%.tar}"; rm -f "${NAME}"; done - cd .. + $ mkdir train && mv ILSVRC2012_img_train.tar train/ && cd train + $ tar -xvf ILSVRC2012_img_train.tar && mv ILSVRC2012_img_train.tar .. + $ find . -name "*.tar" | while read NAME ; do mkdir -p "${NAME%.tar}"; tar -xvf "${NAME}" -C "${NAME%.tar}"; rm -f "${NAME}"; done + $ cd .. ``` 3. Extract the validation data and move images to subfolders: ```bash - mkdir val && mv ILSVRC2012_img_val.tar val/ && cd val && tar -xvf ILSVRC2012_img_val.tar - wget -qO- https://raw.githubusercontent.com/soumith/imagenetloader.torch/master/valprep.sh | bash - mv ILSVRC2012_img_val.tar .. && cd .. + $ mkdir val && mv ILSVRC2012_img_val.tar val/ && cd val && tar -xvf ILSVRC2012_img_val.tar + $ wget -qO- https://raw.githubusercontent.com/soumith/imagenetloader.torch/master/valprep.sh | bash + $ mv ILSVRC2012_img_val.tar .. && cd .. ``` From e45bc5a945bf68250c18cde71253fac3f7774afd Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Sun, 20 Aug 2017 19:24:56 +0900 Subject: [PATCH 131/139] change convert_vgg to use caffemodel --- .../convert_from_original/convert_vgg.py | 58 +++++++++++-------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/examples/classification/convert_from_original/convert_vgg.py b/examples/classification/convert_from_original/convert_vgg.py index abdca792c4..cb1bdf9888 100644 --- a/examples/classification/convert_from_original/convert_vgg.py +++ b/examples/classification/convert_from_original/convert_vgg.py @@ -1,36 +1,46 @@ +import argparse + import chainer -from chainer.links import VGG16Layers as VGG16Layers_chainer +import chainer.links.caffe.caffe_function as caffe + +from chainercv.links import VGG16 -from chainercv.links import VGG16 as VGG16_cv +""" +Please download a weight from here. +http://www.robots.ox.ac.uk/%7Evgg/software/very_deep/ +caffe/VGG_ILSVRC_16_layers.caffemodel +""" def main(): - chainer_model = VGG16Layers_chainer(pretrained_model='auto') - cv_model = VGG16_cv(pretrained_model=None, n_class=1000) + parser = argparse.ArgumentParser() + parser.add_argument('caffemodel') + parser.add_argument('output') + args = parser.parse_args() - cv_model.conv1_1.conv.copyparams(chainer_model.conv1_1) + caffemodel = caffe.CaffeFunction(args.caffemodel) + model = VGG16(pretrained_model=None, n_class=1000) + model.conv1_1.conv.copyparams(caffemodel.conv1_1) # The pretrained weights are trained to accept BGR images. # Convert weights so that they accept RGB images. - cv_model.conv1_1.conv.W.data[:] = cv_model.conv1_1.conv.W.data[:, ::-1] - - cv_model.conv1_2.conv.copyparams(chainer_model.conv1_2) - cv_model.conv2_1.conv.copyparams(chainer_model.conv2_1) - cv_model.conv2_2.conv.copyparams(chainer_model.conv2_2) - cv_model.conv3_1.conv.copyparams(chainer_model.conv3_1) - cv_model.conv3_2.conv.copyparams(chainer_model.conv3_2) - cv_model.conv3_3.conv.copyparams(chainer_model.conv3_3) - cv_model.conv4_1.conv.copyparams(chainer_model.conv4_1) - cv_model.conv4_2.conv.copyparams(chainer_model.conv4_2) - cv_model.conv4_3.conv.copyparams(chainer_model.conv4_3) - cv_model.conv5_1.conv.copyparams(chainer_model.conv5_1) - cv_model.conv5_2.conv.copyparams(chainer_model.conv5_2) - cv_model.conv5_3.conv.copyparams(chainer_model.conv5_3) - cv_model.fc6.copyparams(chainer_model.fc6) - cv_model.fc7.copyparams(chainer_model.fc7) - cv_model.fc8.copyparams(chainer_model.fc8) - - chainer.serializers.save_npz('vgg_from_caffe.npz', cv_model) + model.conv1_1.conv.W.data[:] = model.conv1_1.conv.W.data[:, ::-1] + model.conv1_2.conv.copyparams(caffemodel.conv1_2) + model.conv2_1.conv.copyparams(caffemodel.conv2_1) + model.conv2_2.conv.copyparams(caffemodel.conv2_2) + model.conv3_1.conv.copyparams(caffemodel.conv3_1) + model.conv3_2.conv.copyparams(caffemodel.conv3_2) + model.conv3_3.conv.copyparams(caffemodel.conv3_3) + model.conv4_1.conv.copyparams(caffemodel.conv4_1) + model.conv4_2.conv.copyparams(caffemodel.conv4_2) + model.conv4_3.conv.copyparams(caffemodel.conv4_3) + model.conv5_1.conv.copyparams(caffemodel.conv5_1) + model.conv5_2.conv.copyparams(caffemodel.conv5_2) + model.conv5_3.conv.copyparams(caffemodel.conv5_3) + model.fc6.copyparams(caffemodel.fc6) + model.fc7.copyparams(caffemodel.fc7) + model.fc8.copyparams(caffemodel.fc8) + chainer.serializers.save_npz(args.output, model) if __name__ == '__main__': From 49249ed5dbc0dba47cb65574d343a3766da3fbbe Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Sun, 20 Aug 2017 19:25:24 +0900 Subject: [PATCH 132/139] change name caffee2npz_vgg --- .../convert_from_original/{convert_vgg.py => caffee2npz_vgg.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/classification/convert_from_original/{convert_vgg.py => caffee2npz_vgg.py} (100%) diff --git a/examples/classification/convert_from_original/convert_vgg.py b/examples/classification/convert_from_original/caffee2npz_vgg.py similarity index 100% rename from examples/classification/convert_from_original/convert_vgg.py rename to examples/classification/convert_from_original/caffee2npz_vgg.py From 0dfddfe2765bcf3972a5a1c287f75db1f8176e12 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 21 Aug 2017 08:28:38 +0900 Subject: [PATCH 133/139] make vgg directory --- examples/vgg/README.md | 13 +++++++++++++ .../caffee2npz_vgg.py => vgg/caffee2npz_vgg_16.py} | 0 2 files changed, 13 insertions(+) create mode 100644 examples/vgg/README.md rename examples/{classification/convert_from_original/caffee2npz_vgg.py => vgg/caffee2npz_vgg_16.py} (100%) diff --git a/examples/vgg/README.md b/examples/vgg/README.md new file mode 100644 index 0000000000..0c8d010ed8 --- /dev/null +++ b/examples/vgg/README.md @@ -0,0 +1,13 @@ +# VGG + +For evaluation, please go to [`examples/classification`](https://github.com/chainer/chainercv/tree/master/examples). + +## Convert Caffe model +Convert `*.caffemodel` to `*.npz`. + +``` +$ python caff2npz_vgg_16.py .caffemodel .npz +``` + +The pretrained `.caffemodel` for VGG-16 can be downloaded from here. +http://www.robots.ox.ac.uk/%7Evgg/software/very_deep/caffe/VGG_ILSVRC_16_layers.caffemodel diff --git a/examples/classification/convert_from_original/caffee2npz_vgg.py b/examples/vgg/caffee2npz_vgg_16.py similarity index 100% rename from examples/classification/convert_from_original/caffee2npz_vgg.py rename to examples/vgg/caffee2npz_vgg_16.py From 09000110ebd887591d5fdaac6dfa58e1dc24ef22 Mon Sep 17 00:00:00 2001 From: Toru Ogawa Date: Mon, 21 Aug 2017 12:59:52 +0900 Subject: [PATCH 134/139] rename --- examples/vgg/{caffee2npz_vgg_16.py => caffee2npz.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/vgg/{caffee2npz_vgg_16.py => caffee2npz.py} (100%) diff --git a/examples/vgg/caffee2npz_vgg_16.py b/examples/vgg/caffee2npz.py similarity index 100% rename from examples/vgg/caffee2npz_vgg_16.py rename to examples/vgg/caffee2npz.py From 4de11f7d040b9a74c63c674421d6234fdd714b6f Mon Sep 17 00:00:00 2001 From: Toru Ogawa Date: Mon, 21 Aug 2017 13:06:09 +0900 Subject: [PATCH 135/139] update conversion --- examples/vgg/caffee2npz.py | 60 ++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/examples/vgg/caffee2npz.py b/examples/vgg/caffee2npz.py index cb1bdf9888..34ae81ee1c 100644 --- a/examples/vgg/caffee2npz.py +++ b/examples/vgg/caffee2npz.py @@ -1,45 +1,55 @@ import argparse +import re import chainer +from chainer import Link import chainer.links.caffe.caffe_function as caffe -from chainercv.links import VGG16 """ Please download a weight from here. -http://www.robots.ox.ac.uk/%7Evgg/software/very_deep/ -caffe/VGG_ILSVRC_16_layers.caffemodel +http://www.robots.ox.ac.uk/%7Evgg/software/very_deep/caffe/VGG_ILSVRC_16_layers.caffemodel """ +def rename(name): + m = re.match(r'conv(\d+)_(\d+)$', name) + if m: + i, j = map(int, m.groups()) + return 'conv{:d}_{:d}/conv'.format(i, j) + + return name + + +class VGGCaffeFunction(caffe.CaffeFunction): + + def __init__(self, model_path): + print('loading weights from {:s} ... '.format(model_path)) + super(VGGCaffeFunction, self).__init__(model_path) + + def __setattr__(self, name, value): + if self.within_init_scope and isinstance(value, Link): + new_name = rename(name) + + if new_name == 'conv1_1/conv': + # BGR -> RGB + value.W.data[:, ::-1] = value.W.data + print('{:s} -> {:s} (BGR -> RGB)'.format(name, new_name)) + else: + print('{:s} -> {:s}'.format(name, new_name)) + else: + new_name = name + + super(VGGCaffeFunction, self).__setattr__(new_name, value) + + def main(): parser = argparse.ArgumentParser() parser.add_argument('caffemodel') parser.add_argument('output') args = parser.parse_args() - caffemodel = caffe.CaffeFunction(args.caffemodel) - model = VGG16(pretrained_model=None, n_class=1000) - - model.conv1_1.conv.copyparams(caffemodel.conv1_1) - # The pretrained weights are trained to accept BGR images. - # Convert weights so that they accept RGB images. - model.conv1_1.conv.W.data[:] = model.conv1_1.conv.W.data[:, ::-1] - model.conv1_2.conv.copyparams(caffemodel.conv1_2) - model.conv2_1.conv.copyparams(caffemodel.conv2_1) - model.conv2_2.conv.copyparams(caffemodel.conv2_2) - model.conv3_1.conv.copyparams(caffemodel.conv3_1) - model.conv3_2.conv.copyparams(caffemodel.conv3_2) - model.conv3_3.conv.copyparams(caffemodel.conv3_3) - model.conv4_1.conv.copyparams(caffemodel.conv4_1) - model.conv4_2.conv.copyparams(caffemodel.conv4_2) - model.conv4_3.conv.copyparams(caffemodel.conv4_3) - model.conv5_1.conv.copyparams(caffemodel.conv5_1) - model.conv5_2.conv.copyparams(caffemodel.conv5_2) - model.conv5_3.conv.copyparams(caffemodel.conv5_3) - model.fc6.copyparams(caffemodel.fc6) - model.fc7.copyparams(caffemodel.fc7) - model.fc8.copyparams(caffemodel.fc8) + model = VGGCaffeFunction(args.caffemodel) chainer.serializers.save_npz(args.output, model) From 9d1e8ee8239350cba890af391bd723e4d6c8bf88 Mon Sep 17 00:00:00 2001 From: Toru Ogawa Date: Mon, 21 Aug 2017 13:09:11 +0900 Subject: [PATCH 136/139] fix typo --- examples/vgg/{caffee2npz.py => caffe2npz.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/vgg/{caffee2npz.py => caffe2npz.py} (100%) diff --git a/examples/vgg/caffee2npz.py b/examples/vgg/caffe2npz.py similarity index 100% rename from examples/vgg/caffee2npz.py rename to examples/vgg/caffe2npz.py From 743055faf72996fd608837bbd4b4367d286bf4fc Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 21 Aug 2017 14:20:54 +0900 Subject: [PATCH 137/139] fix download link for faster_rcnn_vgg --- chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py index e2767cb485..dd374e61bd 100644 --- a/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py +++ b/chainercv/links/model/faster_rcnn/faster_rcnn_vgg.py @@ -74,7 +74,7 @@ class FasterRCNNVGG16(FasterRCNN): 'n_fg_class': 20, 'url': 'https://github.com/yuyu2172/share-weights/releases/' 'download/0.0.4/' - 'faster_rcnn_vgg16_voc07_trained_2017_08_06_trial_4.npz' + 'faster_rcnn_vgg16_voc07_trained_2017_08_06.npz' } } feat_stride = 16 From 7bd02b0fb127e7c30e051ddf511841d6c791779d Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 21 Aug 2017 14:39:18 +0900 Subject: [PATCH 138/139] update README vgg --- examples/vgg/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/vgg/README.md b/examples/vgg/README.md index 0c8d010ed8..4839745083 100644 --- a/examples/vgg/README.md +++ b/examples/vgg/README.md @@ -6,7 +6,7 @@ For evaluation, please go to [`examples/classification`](https://github.com/chai Convert `*.caffemodel` to `*.npz`. ``` -$ python caff2npz_vgg_16.py .caffemodel .npz +$ python caff2npz.py .caffemodel .npz ``` The pretrained `.caffemodel` for VGG-16 can be downloaded from here. From 9ca4070df7f28bb371d092cf8d97177ecdb2f052 Mon Sep 17 00:00:00 2001 From: Yusuke Niitani Date: Mon, 21 Aug 2017 14:44:38 +0900 Subject: [PATCH 139/139] fix typo --- examples/vgg/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/vgg/README.md b/examples/vgg/README.md index 4839745083..fd07e4d451 100644 --- a/examples/vgg/README.md +++ b/examples/vgg/README.md @@ -6,7 +6,7 @@ For evaluation, please go to [`examples/classification`](https://github.com/chai Convert `*.caffemodel` to `*.npz`. ``` -$ python caff2npz.py .caffemodel .npz +$ python caffe2npz.py .caffemodel .npz ``` The pretrained `.caffemodel` for VGG-16 can be downloaded from here.