From 53a1dc544075eb0e682181ab1cc57a1e1eeb70d4 Mon Sep 17 00:00:00 2001 From: lucasb-eyer Date: Thu, 16 Jul 2015 15:50:43 +0200 Subject: [PATCH 1/4] Add `mode` and alternative `border` parameters to CUDNN conv. - The `mode` now defaults to `cross`, which is what all other packages use and thus it allows loading their weights without needing to fiddle with mirroring/flipping them correctly. - Adds an alternative `border` parameter for these two reasons: 1. So that we have a flexible shortcut for 'same' 2. So that Theano can use some tricks for 'valid' and 'full' The old interface is still available and used since `border=None` by default. --- beacon8/layers/SpatialConvolutionCUDNN.py | 24 ++++++++++++++++++----- examples/MNIST/model.py | 4 ++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/beacon8/layers/SpatialConvolutionCUDNN.py b/beacon8/layers/SpatialConvolutionCUDNN.py index efef3f4..5d48b7f 100644 --- a/beacon8/layers/SpatialConvolutionCUDNN.py +++ b/beacon8/layers/SpatialConvolutionCUDNN.py @@ -8,7 +8,15 @@ class SpatialConvolutionCUDNN(Module): - def __init__(self, n_input_plane, n_output_plane, k_w, k_h, d_w=1, d_h=1, pad_w=0, pad_h=0, with_bias=True, initW=xavier(), initB=const(0)): + def __init__(self, n_input_plane, n_output_plane, k_w, k_h, d_w=1, d_h=1, pad_w=0, pad_h=0, mode='cross', with_bias=True, initW=xavier(), initB=const(0), border=None): + # mode='cross' is the default in Lasagne[1], Torch[2], matConvNet[3], Caffee[4]. + # + # 1: https://github.com/Lasagne/Lasagne/blob/63d44a0d/lasagne/layers/dnn.py#L299 + # 2: https://github.com/soumith/cudnn.torch/blob/840f0228/SpatialConvolution.lua#L83 + # 3: https://github.com/vlfeat/matconvnet/blob/b7dd9c96/matlab/src/bits/impl/nnconv_cudnn.cu#L133 + # 4: https://github.com/BVLC/caffe/blob/50ab52cb/include/caffe/util/cudnn.hpp#L104 + # + # `border` is an alternative way to specify `pad_w` and `pad_h` so that Theano strings can be used. Better documentation to follow soon. Module.__init__(self) self.n_input_plane = n_input_plane self.n_output_plane = n_output_plane @@ -16,10 +24,15 @@ def __init__(self, n_input_plane, n_output_plane, k_w, k_h, d_w=1, d_h=1, pad_w= self.k_h = k_h self.d_w = d_w self.d_h = d_h - self.pad_w = pad_w - self.pad_h = pad_h + self.mode = mode self.with_bias = with_bias + # 'same' is a (common) shortcut for "zero-padding so that outshape == inshape". + self.border = border or (pad_h, pad_w) + if self.border == 'same': + assert self.k_w % 2 == 1 and self.k_h % 2 == 1, "'same' convolution only supports odd filter sizes." + self.border = ((self.k_h-1)//2, (self.k_w-1)//2) + w_shape = (n_output_plane, n_input_plane, k_h, k_w) w_fan = (n_input_plane*k_w*k_h, n_output_plane*k_w*k_h) @@ -30,8 +43,9 @@ def __init__(self, n_input_plane, n_output_plane, k_w, k_h, d_w=1, d_h=1, pad_w= def symb_forward(self, symb_input): conv_output = _dnn.dnn_conv(img=symb_input, kerns=self.weight, - border_mode=(self.pad_h, self.pad_w), - subsample=(self.d_h, self.d_w)) + border_mode=self.border, + subsample=(self.d_h, self.d_w), + conv_mode=self.mode) if self.with_bias: return conv_output + self.bias.dimshuffle('x', 0, 'x', 'x') diff --git a/examples/MNIST/model.py b/examples/MNIST/model.py index 696457e..945fd27 100644 --- a/examples/MNIST/model.py +++ b/examples/MNIST/model.py @@ -20,12 +20,12 @@ def net(): def lenet(): model = bb8.Sequential() model.add(bb8.Reshape(-1, 1, 28, 28)) - model.add(bb8.SpatialConvolutionCUDNN(1, 32, 5, 5, 1, 1, 2, 2, with_bias=False)) + model.add(bb8.SpatialConvolutionCUDNN(1, 32, 5, 5, 1, 1, border='same', with_bias=False)) model.add(bb8.BatchNormalization(32)) model.add(bb8.ReLU()) model.add(bb8.SpatialMaxPoolingCUDNN(2, 2)) - model.add(bb8.SpatialConvolutionCUDNN(32, 64, 5, 5, 1, 1, 2, 2, with_bias=False)) + model.add(bb8.SpatialConvolutionCUDNN(32, 64, 5, 5, 1, 1, border='same', with_bias=False)) model.add(bb8.BatchNormalization(64)) model.add(bb8.ReLU()) model.add(bb8.SpatialMaxPoolingCUDNN(2, 2)) From 89a2fbe074589a2bfafae31feefe59ad9e64d06d Mon Sep 17 00:00:00 2001 From: lucasb-eyer Date: Thu, 16 Jul 2015 16:00:24 +0200 Subject: [PATCH 2/4] Make container slightly more flexible. This change is fully backwards compatible. --- beacon8/containers/Container.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/beacon8/containers/Container.py b/beacon8/containers/Container.py index 5081573..0247e4f 100644 --- a/beacon8/containers/Container.py +++ b/beacon8/containers/Container.py @@ -8,6 +8,7 @@ def __init__(self, *modules): self.modules = [] for module in modules: + assert isinstance(module, Module), "`Container`s can only contain objects of (sub)type `Module`." self.add(module) def evaluate(self): @@ -36,5 +37,5 @@ def get_stat_updates(self): stat_updates += module.get_stat_updates() return stat_updates - def add(self, module): - self.modules.append(module) + def add(self, *modules): + self.modules += modules From 2f340f4f9e001d6cb442af1a6f34916a36c2608d Mon Sep 17 00:00:00 2001 From: lucasb-eyer Date: Thu, 16 Jul 2015 16:02:40 +0200 Subject: [PATCH 3/4] Adds SpatialSoftMaxCUDNN from #13. (I need it for ILSVRC-pretrained VGG) --- beacon8/layers/SpatialSoftMaxCUDNN.py | 21 +++++++++++++++++++++ beacon8/layers/__init__.py | 1 + 2 files changed, 22 insertions(+) create mode 100644 beacon8/layers/SpatialSoftMaxCUDNN.py diff --git a/beacon8/layers/SpatialSoftMaxCUDNN.py b/beacon8/layers/SpatialSoftMaxCUDNN.py new file mode 100644 index 0000000..b1fee51 --- /dev/null +++ b/beacon8/layers/SpatialSoftMaxCUDNN.py @@ -0,0 +1,21 @@ +from .Module import Module + +import theano.sandbox.cuda.dnn as _dnn +import theano.sandbox.cuda.basic_ops as _cuops + + +def spatial_softmax(img, algo, mode): + img = _cuops.gpu_contiguous(img) + return _dnn.GpuDnnSoftmax(tensor_format='bc01', algo=algo, mode=mode)(img) + + +class SpatialSoftMaxCUDNN(Module): + def __init__(self, algo='accurate', mode='channel'): + # algo: 'fast' is straightforward softmax, 'accurate' is shifting inputs to avoid overflow. + # mode: 'instance' is a softmax per image (across C,W,H), 'channel' is a softmax per pixel per image (across C). + Module.__init__(self) + self.algo = algo + self.mode = mode + + def symb_forward(self, symb_input): + return spatial_softmax(symb_input, self.algo, self.mode) diff --git a/beacon8/layers/__init__.py b/beacon8/layers/__init__.py index 9aaab06..ba3023a 100644 --- a/beacon8/layers/__init__.py +++ b/beacon8/layers/__init__.py @@ -12,3 +12,4 @@ from .SpatialMaxPooling import * from .SpatialConvolutionCUDNN import * from .SpatialMaxPoolingCUDNN import * +from .SpatialSoftMaxCUDNN import * From 019652a124c58091701c403e54baa74d348d16f0 Mon Sep 17 00:00:00 2001 From: lucasb-eyer Date: Sun, 19 Jul 2015 12:17:58 +0200 Subject: [PATCH 4/4] Implemented model zoo with VGG16/19 as examples. This includes implementing a slightly fancier download functionality which caches the data in a `~/.cache/beacon8` folder. For the example code, this also necessitated writing `imread` and `imresize` functions which use either `cv2` or `PIL` depending on what's available. --- beacon8/__init__.py | 1 + beacon8/containers/Sequential.py | 5 +- beacon8/zoo/__init__.py | 3 + beacon8/zoo/download.py | 125 +++++++++++++++++++++++++++++++ beacon8/zoo/vgg.py | 61 +++++++++++++++ beacon8/zoo/vgg16.py | 46 ++++++++++++ beacon8/zoo/vgg19.py | 49 ++++++++++++ examples/Pretrained/run_vgg.py | 97 ++++++++++++++++++++++++ examples/utils.py | 56 ++++++++++++++ 9 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 beacon8/zoo/__init__.py create mode 100644 beacon8/zoo/download.py create mode 100644 beacon8/zoo/vgg.py create mode 100644 beacon8/zoo/vgg16.py create mode 100644 beacon8/zoo/vgg19.py create mode 100644 examples/Pretrained/run_vgg.py diff --git a/beacon8/__init__.py b/beacon8/__init__.py index 4e76af0..2ddcbd8 100644 --- a/beacon8/__init__.py +++ b/beacon8/__init__.py @@ -1,3 +1,4 @@ from .layers import * from .containers import * from .criteria import * +from . import zoo diff --git a/beacon8/containers/Sequential.py b/beacon8/containers/Sequential.py index 3a447a9..a642060 100644 --- a/beacon8/containers/Sequential.py +++ b/beacon8/containers/Sequential.py @@ -6,4 +6,7 @@ def symb_forward(self, symb_input): symb_output = symb_input for module in self.modules: symb_output = module.symb_forward(symb_output) - return symb_output \ No newline at end of file + return symb_output + + def subview(self, start=0, end=None): + return Sequential(*self.modules[start:end]) diff --git a/beacon8/zoo/__init__.py b/beacon8/zoo/__init__.py new file mode 100644 index 0000000..61df756 --- /dev/null +++ b/beacon8/zoo/__init__.py @@ -0,0 +1,3 @@ +from .download import download +from . import vgg16 +from . import vgg19 diff --git a/beacon8/zoo/download.py b/beacon8/zoo/download.py new file mode 100644 index 0000000..66354f1 --- /dev/null +++ b/beacon8/zoo/download.py @@ -0,0 +1,125 @@ +import sys as _sys +import os as _os +import email.utils as _eutils + +try: # Py3 + from urllib.request import urlopen as _urlopen + from urllib.error import URLError as _URLError + from socket import timeout as _timeout +except ImportError: # Py2 + from urllib2 import urlopen as _urlopen + from urllib2 import URLError as _URLError + FileExistsError = OSError + FileNotFoundError = IOError + + +def _httpfilename(response): + """ + Python2/3 compatibility function. + + Returns the filename stored in the `Content-Disposition` HTTP header of + given `response`, or `None` if that header is absent. + """ + try: # Py3 + return response.info().get_filename() + except AttributeError: # Py2 + import cgi + _, params = cgi.parse_header(response.headers.get('Content-Disposition', '')) + return params.get('filename', None) + + +def _getheader(response, header, default=None): + """ + Python2/3 compatibility function. + + Returns a HTTP `header` from given HTTP `response`, or `default` if that + header is absent. + """ + try: # Py3 + return response.getheader(header, default) + except AttributeError: # Py2 + return response.info().getheader(header, default) + + +def download(url, into='~/.cache/beacon8', saveas=None, desc=None, quiet=False): + """ + Downloads the content of `url` into a file in the directory `into`. + + - `url`: The URL to download content from. + - `into`: The folder to save the downloaded content to. + - `saveas`: Optionally a different filename than that from the URL. + - `desc`: Text used for progress-description. + - `quiet`: Suppresses any console-output of this function if `True`. + """ + + # Make sure the target folder exists. + into = _os.path.expanduser(into) + try: + _os.makedirs(into) + except FileExistsError: + pass + + try: + response = _urlopen(url, timeout=5) + except (_URLError, _timeout): + # No internet connection is available, so just trust the file if it's already there. + saveas = saveas or _os.path.basename(url) + target = _os.path.join(into, saveas) + + if not _os.path.isfile(target): + raise FileNotFoundError(target) + if not quiet: + print("No internet connection; using untrusted cached file at {}".format(target)) + return target + + # We do have an internet connection and were able to get to the URL. + saveas = saveas or _httpfilename(response) or _os.path.basename(url) + target = _os.path.join(into, saveas) + + leng = int(_getheader(response, 'Content-Length', 0)) + # assert leng == response.length, "Huh, looks like we didn't get all data. Maybe retry?" + + # In case the file's already there, we may avoid re-downloading it. + if _os.path.isfile(target): + # First, check if we got ETag which is a widely-supported checksum-ish HTTP header. + try: + with open(target + '.etag', 'r') as f: + etag = f.read() + if _getheader(response, 'ETag') == etag: + return target + except FileNotFoundError: + pass + + # Alternatively, check whether the file has the same size. + if _os.path.getsize(target) == leng: + # If there's no last-modified header, just trust it blindly. + servertime = _eutils.parsedate_tz(_getheader(response, 'Last-Modified')) + if servertime is None: + return target + else: + # But if there is, we may also check that. + if _os.path.getmtime(target) >= _eutils.mktime_tz(servertime): + return target + + # TODO: Use progressbar from example utils. + if not quiet: + desc = desc or '{} to {}'.format(url, target) + _sys.stdout.write('Downloading {}: {}k/{}k (:.2%)'.format(desc, 0, leng//1024, 0)) + _sys.stdout.flush() + + with open(target, 'wb+') as f: + while f.tell() < leng: + f.write(response.read(1024*8)) + if not quiet: + _sys.stdout.write('\rDownloading {}: {}k/{}k ({:.2%})'.format(desc, f.tell()//1024, leng//1024, float(f.tell())/leng)) + _sys.stdout.flush() + if not quiet: + print("") + + # Finally, if present, save the ETag for later checking. + etag = _getheader(response, 'ETag') + if etag is not None: + with open(target + '.etag', 'w+') as f: + f.write(etag) + + return target diff --git a/beacon8/zoo/vgg.py b/beacon8/zoo/vgg.py new file mode 100644 index 0000000..68ca4cc --- /dev/null +++ b/beacon8/zoo/vgg.py @@ -0,0 +1,61 @@ +import beacon8 as bb8 + +import scipy.io +import numpy as np + + +def model_head(fully_conv=True): + if fully_conv: + return [ + bb8.SpatialConvolutionCUDNN( 512, 4096, 7, 7, border='valid'), bb8.ReLU(), + bb8.Dropout(0.5), + bb8.SpatialConvolutionCUDNN(4096, 4096, 1, 1, border='valid'), bb8.ReLU(), + bb8.Dropout(0.5), + bb8.SpatialConvolutionCUDNN(4096, 1000, 1, 1, border='valid'), bb8.ReLU(), + bb8.SpatialSoftMaxCUDNN(), + ] + else: + return [ + bb8.Reshape(-1, 512*7*7), + bb8.Linear(512*7*7, 4096), bb8.ReLU(), + bb8.Dropout(0.5), + bb8.Linear(4096, 4096), bb8.ReLU(), + bb8.Dropout(0.5), + bb8.Linear(4096, 1000), + bb8.SoftMax() + ] + + +def params(large=True, fully_conv=True, fname=None): + # Thanks a lot to @317070 (Jonas Degrave) for this! + if large: + fname = fname or bb8.zoo.download('http://www.vlfeat.org/matconvnet/models/imagenet-vgg-verydeep-19.mat', saveas='vgg19-imagenet.mat', desc='vgg19-imagenet.mat') + layers = [0,2,5,7,10,12,14,16,19,21,23,25,28,30,32,34,37,39,41] + else: + fname = fname or bb8.zoo.download('http://www.vlfeat.org/matconvnet/models/imagenet-vgg-verydeep-16.mat', saveas='vgg16-imagenet.mat', desc='vgg16-imagenet.mat') + layers = [0,2,5,7,10,12,14,17,19,21,24,26,28,31,33,35] + + params = [] + + mat = scipy.io.loadmat(fname) + for l in layers: + W = mat['layers'][0,l][0,0][0][0,0] + W = W.transpose(3,2,0,1) + b = mat['layers'][0,l][0,0][0][0,1] + b = b.squeeze() + params += [W, b] + + # For the "classic" case of fully-connected layers as GEMM, we need to + # reshape the parameters into the matrices they are. + if not fully_conv: + params[-6] = params[-6].reshape(4096, -1).T + params[-4] = params[-4].squeeze().T + params[-2] = params[-2].squeeze().T + + # The mean is actually a single scalar per color channel. + mean = mat['normalization'][0,0][0] # This is H,W,C + mean = np.mean(mean, axis=(0,1)) + + classes = np.array([cls[0] for cls in mat['classes'][0,0][0][0,:]]) + + return params, mean, classes diff --git a/beacon8/zoo/vgg16.py b/beacon8/zoo/vgg16.py new file mode 100644 index 0000000..0ab63f7 --- /dev/null +++ b/beacon8/zoo/vgg16.py @@ -0,0 +1,46 @@ +import beacon8 as bb8 +from . import vgg as _vgg + + +def model(fully_conv=True): + conv3 = lambda nin, nout: bb8.SpatialConvolutionCUDNN(nin, nout, 3, 3, border='same') + + return bb8.Sequential( + conv3( 3, 64), bb8.ReLU(), + conv3( 64, 64), bb8.ReLU(), + bb8.SpatialMaxPoolingCUDNN(2, 2), + conv3( 64,128), bb8.ReLU(), + conv3(128,128), bb8.ReLU(), + bb8.SpatialMaxPoolingCUDNN(2, 2), + conv3(128,256), bb8.ReLU(), + conv3(256,256), bb8.ReLU(), + conv3(256,256), bb8.ReLU(), + bb8.SpatialMaxPoolingCUDNN(2, 2), + conv3(256,512), bb8.ReLU(), + conv3(512,512), bb8.ReLU(), + conv3(512,512), bb8.ReLU(), + bb8.SpatialMaxPoolingCUDNN(2, 2), + conv3(512,512), bb8.ReLU(), + conv3(512,512), bb8.ReLU(), + conv3(512,512), bb8.ReLU(), + bb8.SpatialMaxPoolingCUDNN(2, 2), + *_vgg.model_head(fully_conv) + ) + + +def params(fully_conv=True, fname=None): + return _vgg.params(large=False, fully_conv=fully_conv, fname=fname) + + +def pretrained(fully_conv=True, fname=None): + # Create the model + vgg = model(fully_conv) + + # Load the parameters and a few more settings from the downloaded pretrained file. + values, mean, classes = params(fully_conv, fname) + + # Load the pretrained parameter values into the network. + for p, v in zip(vgg.parameters()[0], values): + p.set_value(v) + + return vgg, mean, classes diff --git a/beacon8/zoo/vgg19.py b/beacon8/zoo/vgg19.py new file mode 100644 index 0000000..e40a6da --- /dev/null +++ b/beacon8/zoo/vgg19.py @@ -0,0 +1,49 @@ +import beacon8 as bb8 +from . import vgg as _vgg + + +def model(fully_conv=True): + conv3 = lambda nin, nout: bb8.SpatialConvolutionCUDNN(nin, nout, 3, 3, border='same') + + return bb8.Sequential( + conv3( 3, 64), bb8.ReLU(), + conv3( 64, 64), bb8.ReLU(), + bb8.SpatialMaxPoolingCUDNN(2, 2), + conv3( 64,128), bb8.ReLU(), + conv3(128,128), bb8.ReLU(), + bb8.SpatialMaxPoolingCUDNN(2, 2), + conv3(128,256), bb8.ReLU(), + conv3(256,256), bb8.ReLU(), + conv3(256,256), bb8.ReLU(), + conv3(256,256), bb8.ReLU(), + bb8.SpatialMaxPoolingCUDNN(2, 2), + conv3(256,512), bb8.ReLU(), + conv3(512,512), bb8.ReLU(), + conv3(512,512), bb8.ReLU(), + conv3(512,512), bb8.ReLU(), + bb8.SpatialMaxPoolingCUDNN(2, 2), + conv3(512,512), bb8.ReLU(), + conv3(512,512), bb8.ReLU(), + conv3(512,512), bb8.ReLU(), + conv3(512,512), bb8.ReLU(), + bb8.SpatialMaxPoolingCUDNN(2, 2), + *_vgg.model_head(fully_conv) + ) + + +def params(fully_conv=True, fname=None): + return _vgg.params(large=True, fully_conv=fully_conv, fname=fname) + + +def pretrained(fully_conv=True, fname=None): + # Create the model + vgg = model(fully_conv) + + # Load the parameters and a few more settings from the downloaded pretrained file. + values, mean, classes = params(fully_conv, fname) + + # Load the pretrained parameter values into the network. + for p, v in zip(vgg.parameters()[0], values): + p.set_value(v) + + return vgg, mean, classes diff --git a/examples/Pretrained/run_vgg.py b/examples/Pretrained/run_vgg.py new file mode 100644 index 0000000..1744e31 --- /dev/null +++ b/examples/Pretrained/run_vgg.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python + +import sys, os, json, argparse, collections +import numpy as np +import beacon8 as bb8 + +from examples.utils import imread, imresize + + +def resizecropto(img, size=224): + # First, resize the smallest side to `size`. + img = imresize(img, h=img.shape[0]*size//min(img.shape[:2]), + w=img.shape[1]*size//min(img.shape[:2])) + + # Then, crop-out the central part of the largest side. + return img[(img.shape[0]-size)//2:img.shape[0]-(img.shape[0]-size)//2, + (img.shape[1]-size)//2:img.shape[1]-(img.shape[1]-size)//2, + :] + + +def here(fname): + return os.path.join(os.path.dirname(__file__), fname) + + +def printnow(fmt, *a, **kw): + sys.stdout.write(fmt.format(*a, **kw)) + sys.stdout.flush() + + +if __name__ == "__main__": + if __package__ is None: # PEP366 + __package__ = "beacon8.examples.Pretrained" + + parser = argparse.ArgumentParser(description="Runs the image through a model pre-trained on ImageNet.") + parser.add_argument('-m', '--model', choices=['vgg16', 'vgg19'], default='vgg19') + parser.add_argument('-r', '--raw', action='store_true', help="Do not subtract the trainset's channel-mean") + parser.add_argument('image', nargs='?') + args = parser.parse_args() + + if args.model == 'vgg16': + vgg = bb8.zoo.vgg16 + elif args.model == 'vgg19': + vgg = bb8.zoo.vgg19 + + printnow("Loading models...") + modelFC, mean, classes = vgg.pretrained(fully_conv=True) + model, mean, classes = vgg.pretrained(fully_conv=False) + printnow("Done\n") + + # Switch to prediction mode for deterministic output. + # NOTE: it can be interesting[1] to evaluate in training mode! + # 1: http://mlg.eng.cam.ac.uk/yarin/blog_3d801aa532c1ce.html + modelFC.evaluate() + model.evaluate() + + # Potentially download example image from gist. + args.image = args.image or bb8.zoo.download('https://gist.github.com/lucasb-eyer/237d03b9fcdb0fe03bce/raw/ILSVRC2012_val_00000151.JPEG') + + printnow("Loading and preparing image...") + img = imread(args.image) + + # Subtracting the per-channel mean (stored in `mean`) as only pre-processing. + if not args.raw: + img -= mean + + # For the regular network, we need to resize and crop the image. + img224 = resizecropto(img, 224) + + # Get them from disk-space HWC to nnet-space CHW. + img = img.transpose((2,0,1)) + img224 = img224.transpose((2,0,1)) + + # Put them into a lonely minibatch. + minibatch = np.array([img]) + minibatch224 = np.array([img224]) + printnow("Done\n") + + # And send them through the networks. + printnow("Regular model:\n") + preds = np.array(model.forward(minibatch224)) + top1 = np.argmax(preds[0]) + printnow(" - shape of predictions: {}\n", preds.shape) + printnow(" - top-1 prediction: {}\n", top1) + + printnow("Fully-conv model:\n") + predsFC = np.array(modelFC.forward(minibatch)) + top1FC = np.argmax(predsFC[0], axis=0) + printnow(" - shape of predictions: {}\n", predsFC.shape) + printnow(" - top-1 of average: {}\n", np.argmax(np.mean(predsFC, axis=(2,3))[0])) + printnow(" - top-1 map:\n{}\n", top1FC) + + # Now, we want to print the relevant words that occured as predictions. + ilsvrc_words = json.load(open(bb8.zoo.download('https://gist.github.com/lucasb-eyer/237d03b9fcdb0fe03bce/raw/ILSVRC2012_words.json'))) + ilsvrc_ids = json.load(open(bb8.zoo.download('https://gist.github.com/lucasb-eyer/237d03b9fcdb0fe03bce/raw/ILSVRC2012_ids.json'))) + + for i, _ in collections.Counter([top1] + list(top1FC.flat)).most_common(): + printnow('{} (wnid: {}, ilsvrcid: {}): {}\n', i, classes[i], ilsvrc_ids[classes[i]], ilsvrc_words[classes[i]]) diff --git a/examples/utils.py b/examples/utils.py index adf1388..2440c88 100644 --- a/examples/utils.py +++ b/examples/utils.py @@ -1,4 +1,5 @@ import sys as _sys +import numpy as _np # Progressbar @@ -34,3 +35,58 @@ def finish(self): def make_progressbar(prefix, data_size): return SimpleProgressBar(data_size, prefix + ", processed {i} of {tot} ({pct:.2%})") + + +# Reading and resizing images +############################# + + +try: + import cv2 as _cv2 + + def imread(fname, dtype=_np.float32): + im = _cv2.imread(fname, flags=_cv2.IMREAD_UNCHANGED) + if im is None: + raise IOError("Couldn't open image file {}".format(fname)) + return im + + def imresize(im, h, w): + if im.shape[:2] == (h, w): + return im + + # Use AREA interpolation as soon as one dimension gets smaller to avoid moiree. + inter = _cv2.INTER_AREA if im.shape[0] < h or im.shape[1] < w else _cv2.INTER_LINEAR + return _cv2.resize(im, (w,h), interpolation=inter) + +except ImportError: + + try: + # This is what scipy's imread does lazily. + from PIL import Image as _Image + + def imread(fname, dtype=_np.float32): + # This does what CV_LOAD_IMAGE_ANYDEPTH does by default. + return _np.array(_Image.open(fname), dtype=dtype) + + def imresize(im, h, w): + if im.shape[:2] == (h, w): + return im + + # This has problems re-reading a numpy array if it's not 8-bit anymore. + assert im.max() > 1, "PIL has problems resizing images after they've been changed to e.g. [0-1] range. Either install OpenCV or resize right after reading the image." + img = _Image.fromarray(im.astype(_np.uint8)) + return _np.array(img.resize((w,h), _Image.BILINEAR), dtype=im.dtype) + + except ImportError: + + def imread(fname, dtype=None): + raise ImportError( + "Neither OpenCV nor the Python Imaging Library (PIL) is " + "installed. Please install either for loading images." + ) + + def imresize(im, h, w): + raise ImportError( + "Neither OpenCV nor the Python Imaging Library (PIL) is " + "installed. Please install either for resizing images." + )