Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions beacon8/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .layers import *
from .containers import *
from .criteria import *
from . import zoo
5 changes: 3 additions & 2 deletions beacon8/containers/Container.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
5 changes: 4 additions & 1 deletion beacon8/containers/Sequential.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
return symb_output

def subview(self, start=0, end=None):
return Sequential(*self.modules[start:end])
24 changes: 19 additions & 5 deletions beacon8/layers/SpatialConvolutionCUDNN.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,31 @@


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
self.k_w = k_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)

Expand All @@ -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')
Expand Down
21 changes: 21 additions & 0 deletions beacon8/layers/SpatialSoftMaxCUDNN.py
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions beacon8/layers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
from .SpatialMaxPooling import *
from .SpatialConvolutionCUDNN import *
from .SpatialMaxPoolingCUDNN import *
from .SpatialSoftMaxCUDNN import *
3 changes: 3 additions & 0 deletions beacon8/zoo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .download import download
from . import vgg16
from . import vgg19
125 changes: 125 additions & 0 deletions beacon8/zoo/download.py
Original file line number Diff line number Diff line change
@@ -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
61 changes: 61 additions & 0 deletions beacon8/zoo/vgg.py
Original file line number Diff line number Diff line change
@@ -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
46 changes: 46 additions & 0 deletions beacon8/zoo/vgg16.py
Original file line number Diff line number Diff line change
@@ -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
49 changes: 49 additions & 0 deletions beacon8/zoo/vgg19.py
Original file line number Diff line number Diff line change
@@ -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
Loading