From 0967b9d36f5496a680232a67bfd2e43cd9e70b04 Mon Sep 17 00:00:00 2001 From: ziky90 Date: Thu, 21 Apr 2016 13:48:47 +0200 Subject: [PATCH 01/14] cleaned code + increased readability following PEP8 --- convert.py | 3 +- examples/alexnet.py | 10 +-- examples/caffenet.py | 1 + examples/googlenet.py | 1 + examples/vgg.py | 1 + kaffe/base.py | 20 +++--- kaffe/core.py | 35 ++++++----- kaffe/layers.py | 105 +++++++++++++++++--------------- kaffe/shapes.py | 17 +++++- kaffe/tensorflow/network.py | 2 + kaffe/tensorflow/transformer.py | 44 +++++++------ test.py | 23 ++++--- 12 files changed, 149 insertions(+), 113 deletions(-) diff --git a/convert.py b/convert.py index d7e9f3a..3d858ab 100755 --- a/convert.py +++ b/convert.py @@ -1,12 +1,11 @@ #!/usr/bin/env python -import os -import sys import numpy as np import argparse from kaffe import KaffeError from kaffe.tensorflow import TensorFlowTransformer + def main(): parser = argparse.ArgumentParser() parser.add_argument('def_path', help='Model definition (.prototxt) path') diff --git a/examples/alexnet.py b/examples/alexnet.py index 6e65aed..9eb3f00 100644 --- a/examples/alexnet.py +++ b/examples/alexnet.py @@ -1,10 +1,12 @@ from kaffe.tensorflow import Network + class AlexNet(Network): - batch_size = 500 - scale_size = 227 - crop_size = 227 - isotropic = True + batch_size = 500 + scale_size = 227 + crop_size = 227 + isotropic = True + def setup(self): (self.feed('data') .conv(11, 11, 96, 4, 4, padding='VALID', name='conv1') diff --git a/examples/caffenet.py b/examples/caffenet.py index b8ed474..fa76085 100644 --- a/examples/caffenet.py +++ b/examples/caffenet.py @@ -1,5 +1,6 @@ from kaffe.tensorflow import Network + class CaffeNet(Network): def setup(self): (self.feed('data') diff --git a/examples/googlenet.py b/examples/googlenet.py index c0d985e..329936c 100644 --- a/examples/googlenet.py +++ b/examples/googlenet.py @@ -1,5 +1,6 @@ from kaffe.tensorflow import Network + class GoogleNet(Network): def setup(self): (self.feed('data') diff --git a/examples/vgg.py b/examples/vgg.py index 7eb59d9..3318cd8 100644 --- a/examples/vgg.py +++ b/examples/vgg.py @@ -1,5 +1,6 @@ from kaffe.tensorflow import Network + class VGG16(Network): def setup(self): (self.feed('data') diff --git a/kaffe/base.py b/kaffe/base.py index fd68a5a..e9e2a0b 100644 --- a/kaffe/base.py +++ b/kaffe/base.py @@ -1,19 +1,21 @@ import sys + class KaffeError(Exception): pass # Ordering of the blobs -IDX_WEIGHTS = 0 -IDX_BIAS = 1 +IDX_WEIGHTS = 0 +IDX_BIAS = 1 # The tensors are ordered (c_o, c_i, h, w) or (n, c, h, w) -IDX_N = 0 -IDX_C = 1 -IDX_C_OUT = 0 -IDX_C_IN = 1 -IDX_H = 2 -IDX_W = 3 +IDX_N = 0 +IDX_C = 1 +IDX_C_OUT = 0 +IDX_C_IN = 1 +IDX_H = 2 +IDX_W = 3 + def print_stderr(msg): - sys.stderr.write('%s\n'%msg) + sys.stderr.write('%s\n' % msg) diff --git a/kaffe/core.py b/kaffe/core.py index 7d01c64..a2b4a77 100644 --- a/kaffe/core.py +++ b/kaffe/core.py @@ -1,5 +1,4 @@ import os -import sys import numpy as np from google.protobuf import text_format @@ -28,16 +27,17 @@ print_stderr('Failed to import dist protobuf code. Using failsafe.') print_stderr('Custom layers might not work.') + class Node(object): def __init__(self, name, kind, layer=None): - self.name = name - self.kind = kind - self.layer = LayerAdapter(layer, kind) if layer else None - self.parents = [] - self.children = [] - self.data = None + self.name = name + self.kind = kind + self.layer = LayerAdapter(layer, kind) if layer else None + self.parents = [] + self.children = [] + self.data = None self.output_shape = None - self.metadata = {} + self.metadata = {} def add_parent(self, parent_node): assert parent_node not in self.parents @@ -100,6 +100,7 @@ def topologically_sorted(self): unsorted_nodes = list(self.nodes) temp_marked = set() perm_marked = set() + def visit(node): if node in temp_marked: raise KaffeError('Graph is not a DAG.') @@ -133,6 +134,7 @@ def __str__(self): node.name, data_shape, out_shape)) return '\n'.join(s) + class DataInjector(object): def __init__(self, def_path, data_path): self.def_path = def_path @@ -166,10 +168,10 @@ def transform_data(self, layer): dims = blob.shape.dim c_o, c_i, h, w = map(int, [1]*(4-len(dims))+list(dims)) else: - c_o = blob.num - c_i = blob.channels - h = blob.height - w = blob.width + c_o = blob.num + c_i = blob.channels + h = blob.height + w = blob.width data = np.array(blob.data, dtype=np.float32).reshape(c_o, c_i, h, w) transformed.append(data) return transformed @@ -183,9 +185,9 @@ def adjust_parameters(self, node, data): # potential for future issues. # The Caffe-backend does not suffer from this problem. data = list(data) - squeeze_indices = [1] # Squeeze biases. - if node.kind==NodeKind.InnerProduct: - squeeze_indices.append(0) # Squeeze FC. + squeeze_indices = [1] # Squeeze biases. + if node.kind == NodeKind.InnerProduct: + squeeze_indices.append(0) # Squeeze FC. for idx in squeeze_indices: data[idx] = np.squeeze(data[idx]) return data @@ -198,6 +200,7 @@ def inject(self, graph): else: print_stderr('Ignoring parameters for non-existent layer: %s'%layer_name) + class DataReshaper(object): def __init__(self, mapping): self.mapping = mapping @@ -215,7 +218,7 @@ def has_spatial_parent(self, node): try: parent = node.get_only_parent() s = parent.output_shape - return (s[IDX_H]>1 or s[IDX_W]>1) + return s[IDX_H] > 1 or s[IDX_W] > 1 except KaffeError: return False diff --git a/kaffe/layers.py b/kaffe/layers.py index b61e0d6..8319b68 100644 --- a/kaffe/layers.py +++ b/kaffe/layers.py @@ -3,62 +3,65 @@ from .shapes import * from collections import namedtuple -LAYER_DESCRIPTORS = { +LAYER_DESCRIPTORS = { # Caffe Types - 'AbsVal' : shape_identity, - 'Accuracy' : shape_scalar, - 'ArgMax' : shape_not_implemented, - 'BNLL' : shape_not_implemented, - 'Concat' : shape_concat, - 'ContrastiveLoss' : shape_scalar, - 'Convolution' : shape_convolution, - 'Deconvolution' : shape_not_implemented, - 'Data' : shape_data, - 'Dropout' : shape_identity, - 'DummyData' : shape_data, - 'EuclideanLoss' : shape_scalar, - 'Eltwise' : shape_identity, - 'Exp' : shape_identity, - 'Flatten' : shape_not_implemented, - 'HDF5Data' : shape_data, - 'HDF5Output' : shape_identity, - 'HingeLoss' : shape_scalar, - 'Im2col' : shape_not_implemented, - 'ImageData' : shape_data, - 'InfogainLoss' : shape_scalar, - 'InnerProduct' : shape_inner_product, - 'Input' : shape_data, - 'LRN' : shape_identity, - 'MemoryData' : shape_mem_data, - 'MultinomialLogisticLoss' : shape_scalar, - 'MVN' : shape_not_implemented, - 'Pooling' : shape_pool, - 'Power' : shape_identity, - 'ReLU' : shape_identity, - 'Sigmoid' : shape_identity, - 'SigmoidCrossEntropyLoss' : shape_scalar, - 'Silence' : shape_not_implemented, - 'Softmax' : shape_identity, - 'SoftmaxWithLoss' : shape_scalar, - 'Split' : shape_not_implemented, - 'Slice' : shape_not_implemented, - 'TanH' : shape_identity, - 'WindowData' : shape_not_implemented, - 'Threshold' : shape_identity, + 'AbsVal': shape_identity, + 'Accuracy': shape_scalar, + 'ArgMax': shape_not_implemented, + 'BNLL': shape_not_implemented, + 'Concat': shape_concat, + 'ContrastiveLoss': shape_scalar, + 'Convolution': shape_convolution, + 'Crop': shape_not_implemented, + 'Deconvolution': shape_not_implemented, + 'Data': shape_data, + 'Dropout': shape_identity, + 'DummyData': shape_data, + 'EuclideanLoss': shape_scalar, + 'Eltwise': shape_identity, + 'Exp': shape_identity, + 'Flatten': shape_not_implemented, + 'HDF5Data': shape_data, + 'HDF5Output': shape_identity, + 'HingeLoss': shape_scalar, + 'Im2col': shape_not_implemented, + 'ImageData': shape_data, + 'InfogainLoss': shape_scalar, + 'InnerProduct': shape_inner_product, + 'Input': shape_data, + 'LRN': shape_identity, + 'MemoryData': shape_mem_data, + 'MultinomialLogisticLoss': shape_scalar, + 'MVN': shape_not_implemented, + 'Pooling': shape_pool, + 'Power': shape_identity, + 'ReLU': shape_identity, + 'Sigmoid': shape_identity, + 'SigmoidCrossEntropyLoss': shape_scalar, + 'Silence': shape_not_implemented, + 'Softmax': shape_identity, + 'SoftmaxWithLoss': shape_scalar, + 'Split': shape_not_implemented, + 'Slice': shape_not_implemented, + 'TanH': shape_identity, + 'WindowData': shape_not_implemented, + 'Threshold': shape_identity, # Internal Types - 'Implicit' : shape_identity + 'Implicit': shape_identity } LAYER_TYPES = LAYER_DESCRIPTORS.keys() + def generate_layer_type_enum(): types = {t:t for t in LAYER_TYPES} return type('LayerType', (), types) LayerType = generate_layer_type_enum() + class NodeKind(LayerType): @staticmethod def map_raw_kind(kind): @@ -74,12 +77,15 @@ def compute_output_shape(node): except NotImplementedError: raise KaffeError('Output shape computation not implemented for type: %s'%node.kind) -class NodeDispatchError(KaffeError): pass + +class NodeDispatchError(KaffeError): + pass + class NodeDispatch(object): @staticmethod def get_handler_name(node_kind): - if len(node_kind)<=4: + if len(node_kind) <= 4: # A catch-all for things like ReLU and tanh return node_kind.lower() # Convert from CamelCase to under_scored @@ -94,6 +100,7 @@ def get_handler(self, node_kind, prefix): except AttributeError: raise NodeDispatchError('No handler found for node kind: %s (expected: %s)'%(node_kind, name)) + class LayerAdapter(object): def __init__(self, layer, kind): self.layer = layer @@ -139,8 +146,8 @@ def kernel_parameters(self): KernelParameters = namedtuple('KernelParameters', ['kernel_h', - 'kernel_w', - 'stride_h', - 'stride_w', - 'pad_h', - 'pad_w']) + 'kernel_w', + 'stride_h', + 'stride_w', + 'pad_h', + 'pad_w']) diff --git a/kaffe/shapes.py b/kaffe/shapes.py index c016afb..4e704e2 100644 --- a/kaffe/shapes.py +++ b/kaffe/shapes.py @@ -1,13 +1,16 @@ import math from .base import * + def make_shape(n, c, h, w): - return (n, c, h, w) + return n, c, h, w + def get_filter_output_shape(i_h, i_w, params, round_func): o_h = (i_h + 2*params.pad_h - params.kernel_h)/float(params.stride_h) + 1 o_w = (i_w + 2*params.pad_w - params.kernel_w)/float(params.stride_w) + 1 - return (int(round_func(o_h)), int(round_func(o_w))) + return int(round_func(o_h)), int(round_func(o_w)) + def get_strided_kernel_output_shape(node, round_func): assert node.layer is not None @@ -19,18 +22,22 @@ def get_strided_kernel_output_shape(node, round_func): params = node.layer.parameters has_c_o = hasattr(params, 'num_output') c = params.num_output if has_c_o else input_shape[IDX_C] - return make_shape(input_shape[IDX_N], c, o_h ,o_w) + return make_shape(input_shape[IDX_N], c, o_h, o_w) + def shape_not_implemented(node): raise NotImplementedError + def shape_identity(node): assert len(node.parents)>0 return node.parents[0].output_shape + def shape_scalar(node): return make_shape(1, 1, 1, 1) + def shape_data(node): if node.output_shape: # Old-style input specification @@ -57,6 +64,7 @@ def shape_mem_data(node): params.height, params.width) + def shape_concat(node): axis = node.layer.parameters.axis output_shape = None @@ -67,12 +75,15 @@ def shape_concat(node): output_shape[axis] += parent.output_shape[axis] return tuple(output_shape) + def shape_convolution(node): return get_strided_kernel_output_shape(node, math.floor) + def shape_pool(node): return get_strided_kernel_output_shape(node, math.ceil) + def shape_inner_product(node): input_shape = node.get_only_parent().output_shape return make_shape(input_shape[IDX_N], diff --git a/kaffe/tensorflow/network.py b/kaffe/tensorflow/network.py index cbfae9c..86bbb86 100644 --- a/kaffe/tensorflow/network.py +++ b/kaffe/tensorflow/network.py @@ -3,6 +3,7 @@ DEFAULT_PADDING = 'SAME' + def layer(op): def layer_decorated(self, *args, **kwargs): # Automatically set a name if not provided. @@ -24,6 +25,7 @@ def layer_decorated(self, *args, **kwargs): return self return layer_decorated + class Network(object): def __init__(self, inputs, trainable=True): self.inputs = [] diff --git a/kaffe/tensorflow/transformer.py b/kaffe/tensorflow/transformer.py index d55c2df..21e6592 100644 --- a/kaffe/tensorflow/transformer.py +++ b/kaffe/tensorflow/transformer.py @@ -4,6 +4,7 @@ from ..base import * from ..core import GraphBuilder, DataReshaper, NodeMapper + class TensorFlowNode(object): def __init__(self, op, *args, **kwargs): self.op = op @@ -11,38 +12,40 @@ def __init__(self, op, *args, **kwargs): self.kwargs = list(kwargs.items()) def format(self, arg): - return "'%s'"%arg if isinstance(arg, basestring) else str(arg) + return "'%s'" % arg if isinstance(arg, basestring) else str(arg) def pair(self, key, value): - return '%s=%s'%(key, self.format(value)) + return '%s=%s' % (key, self.format(value)) def emit(self): args = map(self.format, self.args) if self.kwargs: - args += [self.pair(k, v) for k,v in self.kwargs] + args += [self.pair(k, v) for k, v in self.kwargs] args.append(self.pair('name', self.node.name)) args = ', '.join(args) - return '%s(%s)'%(self.op, args) + return '%s(%s)' % (self.op, args) + def get_padding_type(kernel_params, input_shape, output_shape): - '''Translates Caffe's numeric padding to one of ('SAME', 'VALID'). + """Translates Caffe's numeric padding to one of ('SAME', 'VALID'). Caffe supports arbitrary padding values, while TensorFlow only supports 'SAME' and 'VALID' modes. So, not all Caffe paddings can be translated to TensorFlow. There are some subtleties to how the padding edge-cases are handled. These are described here: https://github.com/Yangqing/caffe2/blob/master/caffe2/proto/caffe2_legacy.proto - ''' + """ k_h, k_w, s_h, s_w, p_h, p_w = kernel_params s_o_h = np.ceil(input_shape[IDX_H]/float(s_h)) s_o_w = np.ceil(input_shape[IDX_W]/float(s_w)) - if (output_shape[IDX_H]==s_o_h) and (output_shape[IDX_W]==s_o_w): + if (output_shape[IDX_H] == s_o_h) and (output_shape[IDX_W] == s_o_w): return 'SAME' v_o_h = np.ceil((input_shape[IDX_H]-k_h+1.0)/float(s_h)) v_o_w = np.ceil((input_shape[IDX_W]-k_w+1.0)/float(s_w)) - if (output_shape[IDX_H]==v_o_h) and (output_shape[IDX_W]==v_o_w): + if (output_shape[IDX_H] == v_o_h) and (output_shape[IDX_W] == v_o_w): return 'VALID' return None + class TensorFlowMapper(NodeMapper): def get_kernel_params(self, node): @@ -50,13 +53,13 @@ def get_kernel_params(self, node): input_shape = node.get_only_parent().output_shape padding = get_padding_type(kernel_params, input_shape, node.output_shape) # Only emit the padding if it's not the default value. - padding = {'padding':padding} if padding!=network.DEFAULT_PADDING else {} - return (kernel_params, padding) + padding = {'padding': padding} if padding != network.DEFAULT_PADDING else {} + return kernel_params, padding def relu_adapted_node(self, node, *args, **kwargs): # Opt-out instead of opt-in as ReLU(op) is the common case. if not node.metadata.get('relu', False): - kwargs['relu']=False + kwargs['relu'] = False return TensorFlowNode(*args, **kwargs) def map_convolution(self, node): @@ -65,8 +68,8 @@ def map_convolution(self, node): group = node.parameters.group if group!=1: kwargs['group'] = group - assert kernel_params.kernel_h==h - assert kernel_params.kernel_w==w + assert kernel_params.kernel_h == h + assert kernel_params.kernel_w == w return self.relu_adapted_node(node, 'conv', kernel_params.kernel_h, @@ -81,9 +84,9 @@ def map_relu(self, node): def map_pooling(self, node): pool_type = node.parameters.pool - if pool_type==0: + if pool_type == 0: pool_op = 'max_pool' - elif pool_type==1: + elif pool_type == 1: pool_op = 'avg_pool' else: # Stochastic pooling, for instance. @@ -109,7 +112,7 @@ def map_lrn(self, node): params = node.parameters # The window size must be an odd value. For a window # size of (2*n+1), TensorFlow defines depth_radius = n. - assert (params.local_size%2==1) + assert (params.local_size % 2 == 1) # Caffe scales by (alpha/(2*n+1)), whereas TensorFlow # just scales by alpha (as does Krizhevsky's paper). # We'll account for that here. @@ -129,6 +132,7 @@ def map_dropout(self, node): def commit(self, chains): return chains + class TensorFlowEmitter(object): def __init__(self, tab=None): @@ -176,7 +180,7 @@ def emit(self, name, chains): for node in chain: b += self.emit_node(node) blocks.append(b[:-1]+')') - s = s + '\n\n'.join(blocks) + s += '\n\n'.join(blocks) return s @@ -201,10 +205,10 @@ def load(self, def_path, data_path, phase): def transform_data(self): # Cache the graph source before mutating it. self.transform_source() - mapping = {4 : (2, 3, 1, 0), # (c_o, c_i, h, w) -> (h, w, c_i, c_o) - 2 : (1, 0)} # (c_o, c_i) -> (c_i, c_o) + mapping = {4: (2, 3, 1, 0), # (c_o, c_i, h, w) -> (h, w, c_i, c_o) + 2: (1, 0)} # (c_o, c_i) -> (c_i, c_o) DataReshaper(mapping).reshape(self.graph) - return {node.name:node.data for node in self.graph.nodes if node.data} + return {node.name: node.data for node in self.graph.nodes if node.data} def transform_source(self): if self.source is None: diff --git a/test.py b/test.py index 6bef053..42b2483 100755 --- a/test.py +++ b/test.py @@ -7,6 +7,7 @@ import tensorflow as tf import examples + class ImageNet(object): def __init__(self, val_path, data_path, model): gt_lines = open(val_path).readlines() @@ -48,16 +49,17 @@ def batches(self, n): def __len__(self): return len(self.labels) + def test_imagenet(model, data_path, val_path, images_path, top_k=5): - test_data = tf.placeholder(tf.float32, shape=(model.batch_size, model.crop_size, model.crop_size, model.channels)) + test_data = tf.placeholder(tf.float32, shape=(model.batch_size, model.crop_size, model.crop_size, model.channels)) test_labels = tf.placeholder(tf.int32, shape=(model.batch_size,)) - net = model.net_class({'data':test_data}) - probs = net.get_output() - top_k_op = tf.nn.in_top_k(probs, test_labels, top_k) - imagenet = ImageNet(val_path, images_path, model) - correct = 0 - count = 0 - total = len(imagenet) + net = model.net_class({'data':test_data}) + probs = net.get_output() + top_k_op = tf.nn.in_top_k(probs, test_labels, top_k) + imagenet = ImageNet(val_path, images_path, model) + correct = 0 + count = 0 + total = len(imagenet) with tf.Session() as sesh: net.load(data_path, sesh) for idx, (images, labels) in enumerate(imagenet.batches(model.batch_size)): @@ -67,13 +69,14 @@ def test_imagenet(model, data_path, val_path, images_path, top_k=5): print('{:>6}/{:<6} {:>6.2f}%'.format(count, total, cur_accuracy)) print('Top %s Accuracy: %s'%(top_k, float(correct)/total)) + def main(): args = sys.argv[1:] if len(args) not in (3, 4): print('usage: %s net.params imagenet-val.txt imagenet-data-dir [model-index=0]'%os.path.basename(__file__)) exit(-1) - model_index = 0 if len(args)==3 else int(args[3]) - if model_index>=len(examples.MODELS): + model_index = 0 if len(args) == 3 else int(args[3]) + if model_index >= len(examples.MODELS): print('Invalid model index. Options are:') for idx, model in enumerate(examples.MODELS): print('%s: %s'%(idx, model)) From fdd9c58e647cc886331615c8176d4553d8376f57 Mon Sep 17 00:00:00 2001 From: ziky90 Date: Thu, 21 Apr 2016 13:54:21 +0200 Subject: [PATCH 02/14] more PEP8 stuff --- kaffe/core.py | 7 ++++--- kaffe/layers.py | 4 ++-- kaffe/shapes.py | 6 +++--- kaffe/tensorflow/network.py | 26 +++++++++++++------------- kaffe/tensorflow/transformer.py | 1 - 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/kaffe/core.py b/kaffe/core.py index a2b4a77..03fd30f 100644 --- a/kaffe/core.py +++ b/kaffe/core.py @@ -358,6 +358,7 @@ def build(self, fuse_relus=True): DataInjector(self.def_path, self.data_path).inject(graph) return graph + class NodeMapper(NodeDispatch): def __init__(self, graph): self.graph = graph @@ -371,15 +372,15 @@ def map(self): input_nodes = self.graph.get_input_nodes() nodes = [t for t in nodes if t not in input_nodes] # Remove implicit nodes. - nodes = [t for t in nodes if t.kind!=NodeKind.Implicit] + nodes = [t for t in nodes if t.kind != NodeKind.Implicit] # Decompose DAG into chains. chains = [] for node in nodes: attach_to_chain = None - if len(node.parents)==1: + if len(node.parents) == 1: parent = node.get_only_parent() for chain in chains: - if chain[-1]==parent: + if chain[-1] == parent: # Node is part of an existing chain. attach_to_chain = chain break diff --git a/kaffe/layers.py b/kaffe/layers.py index 8319b68..c8274c6 100644 --- a/kaffe/layers.py +++ b/kaffe/layers.py @@ -122,10 +122,10 @@ def get_kernel_value(scalar, repeated, idx, default=None): if repeated: if isinstance(repeated, numbers.Number): return repeated - if len(repeated)==1: + if len(repeated) == 1: # Same value applies to all spatial dimensions return int(repeated[0]) - assert idx0 + assert len(node.parents) > 0 return node.parents[0].output_shape -def shape_scalar(node): +def shape_scalar(): return make_shape(1, 1, 1, 1) diff --git a/kaffe/tensorflow/network.py b/kaffe/tensorflow/network.py index 86bbb86..7c0d75e 100644 --- a/kaffe/tensorflow/network.py +++ b/kaffe/tensorflow/network.py @@ -49,24 +49,24 @@ def load(self, data_path, session, ignore_missing=False): raise def feed(self, *args): - assert len(args)!=0 + assert len(args) != 0 self.inputs = [] - for layer in args: - if isinstance(layer, basestring): + for l in args: + if isinstance(l, basestring): try: - layer = self.layers[layer] + l = self.layers[l] except KeyError: print self.layers.keys() - raise KeyError('Unknown layer name fed: %s'%layer) - self.inputs.append(layer) + raise KeyError('Unknown layer name fed: %s' % l) + self.inputs.append(l) return self def get_output(self): return self.inputs[-1] def get_unique_name(self, prefix): - id = sum(t.startswith(prefix) for t,_ in self.layers.items())+1 - return '%s_%d'%(prefix, id) + id = sum(t.startswith(prefix) for t, _ in self.layers.items())+1 + return '%s_%d' % (prefix, id) def make_var(self, name, shape): return tf.get_variable(name, shape, trainable=self.trainable) @@ -78,18 +78,18 @@ def validate_padding(self, padding): def conv(self, input, k_h, k_w, c_o, s_h, s_w, name, relu=True, padding=DEFAULT_PADDING, group=1): self.validate_padding(padding) c_i = input.get_shape()[-1] - assert c_i%group==0 - assert c_o%group==0 + assert c_i % group == 0 + assert c_o % group == 0 convolve = lambda i, k: tf.nn.conv2d(i, k, [1, s_h, s_w, 1], padding=padding) with tf.variable_scope(name) as scope: kernel = self.make_var('weights', shape=[k_h, k_w, c_i/group, c_o]) biases = self.make_var('biases', [c_o]) - if group==1: + if group == 1: conv = convolve(input, kernel) else: input_groups = tf.split(3, group, input) kernel_groups = tf.split(3, group, kernel) - output_groups = [convolve(i, k) for i,k in zip(input_groups, kernel_groups)] + output_groups = [convolve(i, k) for i, k in zip(input_groups, kernel_groups)] conv = tf.concat(3, output_groups) if relu: bias = tf.reshape(tf.nn.bias_add(conv, biases), conv.get_shape().as_list()) @@ -135,7 +135,7 @@ def concat(self, inputs, axis, name): def fc(self, input, num_out, name, relu=True): with tf.variable_scope(name) as scope: input_shape = input.get_shape() - if input_shape.ndims==4: + if input_shape.ndims == 4: dim = 1 for d in input_shape[1:].as_list(): dim *= d diff --git a/kaffe/tensorflow/transformer.py b/kaffe/tensorflow/transformer.py index 21e6592..bf0c592 100644 --- a/kaffe/tensorflow/transformer.py +++ b/kaffe/tensorflow/transformer.py @@ -1,4 +1,3 @@ -import tensorflow as tf import numpy as np from . import network from ..base import * From 51fa828bb798a5332eb0ffa8d081876c6a67a48f Mon Sep 17 00:00:00 2001 From: ziky90 Date: Thu, 21 Apr 2016 13:58:39 +0200 Subject: [PATCH 03/14] code cleaning fix --- kaffe/layers.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/kaffe/layers.py b/kaffe/layers.py index c8274c6..6e23eb5 100644 --- a/kaffe/layers.py +++ b/kaffe/layers.py @@ -67,7 +67,6 @@ class NodeKind(LayerType): def map_raw_kind(kind): if kind in LAYER_TYPES: return kind - return None @staticmethod def compute_output_shape(node): @@ -75,7 +74,7 @@ def compute_output_shape(node): val = LAYER_DESCRIPTORS[node.kind](node) return val except NotImplementedError: - raise KaffeError('Output shape computation not implemented for type: %s'%node.kind) + raise KaffeError('Output shape computation not implemented for type: %s' % node.kind) class NodeDispatchError(KaffeError): @@ -98,7 +97,7 @@ def get_handler(self, node_kind, prefix): try: return getattr(self, name) except AttributeError: - raise NodeDispatchError('No handler found for node kind: %s (expected: %s)'%(node_kind, name)) + raise NodeDispatchError('No handler found for node kind: %s (expected: %s)' % (node_kind, name)) class LayerAdapter(object): @@ -113,7 +112,7 @@ def parameters(self): try: return getattr(self.layer, name) except AttributeError: - raise NodeDispatchError('Caffe parameters not found for layer kind: %s'%(self.kind)) + raise NodeDispatchError('Caffe parameters not found for layer kind: %s' % self.kind) @staticmethod def get_kernel_value(scalar, repeated, idx, default=None): From 2da9ddc1d7260bfd63bff3b1f8c292f190790864 Mon Sep 17 00:00:00 2001 From: ziky90 Date: Thu, 21 Apr 2016 14:26:50 +0200 Subject: [PATCH 04/14] reverted cleaning bugfixes --- kaffe/shapes.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/kaffe/shapes.py b/kaffe/shapes.py index c5e287a..aa7fd73 100644 --- a/kaffe/shapes.py +++ b/kaffe/shapes.py @@ -25,7 +25,12 @@ def get_strided_kernel_output_shape(node, round_func): return make_shape(input_shape[IDX_N], c, o_h, o_w) -def shape_not_implemented(): +def shape_not_implemented(node): + """ + In case that shape is not implemented + + :param node: node, it's necessary, because of the kaffe/layers.py + """ raise NotImplementedError @@ -34,7 +39,12 @@ def shape_identity(node): return node.parents[0].output_shape -def shape_scalar(): +def shape_scalar(node): + """ + Shape scalar + + :param node: node, it's necessary, because of the kaffe/layers.py + """ return make_shape(1, 1, 1, 1) From 502fe1eb1f793a780c0f402e05cdd9fbd0a5e774 Mon Sep 17 00:00:00 2001 From: ziky90 Date: Fri, 22 Apr 2016 10:49:49 +0200 Subject: [PATCH 05/14] added deconvolution layer shape computation + more code cleaning, replaced prints by logging --- convert.py | 29 ++++++++++++++++++++++------ kaffe/core.py | 25 +++++++++++++------------ kaffe/layers.py | 13 ++++++++++--- kaffe/shapes.py | 50 ++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 91 insertions(+), 26 deletions(-) diff --git a/convert.py b/convert.py index 3d858ab..ce797cb 100755 --- a/convert.py +++ b/convert.py @@ -1,12 +1,26 @@ #!/usr/bin/env python -import numpy as np +import sys +import os +import logging import argparse + +import numpy as np + from kaffe import KaffeError from kaffe.tensorflow import TensorFlowTransformer +logger = logging.getLogger(__name__) + + def main(): + logging.basicConfig( + format='%(asctime)s : %(levelname)s : %(module)s:%(funcName)s:%(lineno)d : %(message)s', + level=logging.DEBUG) + logger.info("running %s", " ".join(sys.argv)) + program = os.path.basename(sys.argv[0]) + parser = argparse.ArgumentParser() parser.add_argument('def_path', help='Model definition (.prototxt) path') parser.add_argument('data_path', help='Model data (.caffemodel) path') @@ -16,19 +30,22 @@ def main(): args = parser.parse_args() try: transformer = TensorFlowTransformer(args.def_path, args.data_path, phase=args.phase) - print('Converting data...') + logger.info('Converting data...') data = transformer.transform_data() - print('Saving data...') + logger.info('Saving data...') with open(args.data_output_path, 'wb') as data_out: np.save(data_out, data) if args.code_output_path is not None: - print('Saving source...') + logger.info('Saving source...') with open(args.code_output_path, 'wb') as src_out: src_out.write(transformer.transform_source()) - print('Done.') + logger.info('Done.') except KaffeError as err: - print('Error encountered: %s'%err) + logger.info('Error encountered: %s'%err) exit(-1) + logger.info("finished running %s", program) + + if __name__ == '__main__': main() diff --git a/kaffe/core.py b/kaffe/core.py index 03fd30f..9618313 100644 --- a/kaffe/core.py +++ b/kaffe/core.py @@ -227,7 +227,7 @@ def reshape(self, graph, replace=True): if node.data is None: continue data = node.data[IDX_WEIGHTS] - if (node.kind==NodeKind.InnerProduct) and self.has_spatial_parent(node): + if (node.kind == NodeKind.InnerProduct) and self.has_spatial_parent(node): # The FC layer connected to the spatial layer needs to be # re-wired to match the new spatial ordering. in_shape = node.get_only_parent().output_shape @@ -245,6 +245,7 @@ def reshape(self, graph, replace=True): node.data[IDX_WEIGHTS] = node.reshaped_data del node.reshaped_data + class GraphBuilder(object): def __init__(self, def_path, data_path=None, phase='test'): self.def_path = def_path @@ -258,7 +259,7 @@ def load(self): text_format.Merge(def_file.read(), self.params) def filter_layers(self, layers): - phase_map = {0:'train', 1:'test'} + phase_map = {0: 'train', 1: 'test'} filtered_layer_names = set() filtered_layers = [] for layer in layers: @@ -267,12 +268,12 @@ def filter_layers(self, layers): phase = phase_map[layer.include[0].phase] if len(layer.exclude): phase = phase_map[1-layer.include[0].phase] - exclude = (phase!=self.phase) + exclude = (phase != self.phase) # Dropout layers appear in a fair number of Caffe # test-time networks. These are just ignored. We'll # filter them out here. - if (not exclude) and (phase=='test'): - exclude = (layer.type==LayerType.Dropout) + if (not exclude) and (phase == 'test'): + exclude = (layer.type == LayerType.Dropout) if not exclude: filtered_layers.append(layer) # Guard against dupes. @@ -283,7 +284,7 @@ def filter_layers(self, layers): def make_node(self, layer): kind = NodeKind.map_raw_kind(layer.type) if kind is None: - raise KaffeError('Unknown layer type encountered: %s'%layer.type) + raise KaffeError('Unknown layer type encountered: %s' % layer.type) return Node(layer.name, kind, layer=layer) def make_input_nodes(self): @@ -294,7 +295,7 @@ def make_input_nodes(self): if len(nodes): input_dim = map(int, self.params.input_dim) if not input_dim: - if len(self.params.input_shape)>0: + if len(self.params.input_shape) > 0: input_dim = map(int, self.params.input_shape[0].dim) else: raise KaffeError('Dimensions for input not specified.') @@ -305,10 +306,10 @@ def make_input_nodes(self): def fuse_relus(self, nodes): fused_nodes = [] for node in nodes: - if node.kind!=NodeKind.ReLU: + if node.kind != NodeKind.ReLU: continue parent = node.get_only_parent() - if len(parent.children)!=1: + if len(parent.children) != 1: # We can only fuse this ReLU if its parent's # value isn't used by any other node. continue @@ -333,13 +334,13 @@ def build(self, fuse_relus=True): for layer in layers: node = graph.get_node(layer.name) for parent_name in layer.bottom: - assert parent_name!=layer.name + assert parent_name != layer.name parent_node = node_outputs.get(parent_name) - if (parent_node is None) or (parent_node==node): + if (parent_node is None) or (parent_node == node): parent_node = graph.get_node(parent_name) node.add_parent(parent_node) for child_name in layer.top: - if child_name==layer.name: + if child_name == layer.name: continue if child_name in graph: # This is an "in-place operation" that overwrites an existing node. diff --git a/kaffe/layers.py b/kaffe/layers.py index 6e23eb5..2ec618b 100644 --- a/kaffe/layers.py +++ b/kaffe/layers.py @@ -2,6 +2,7 @@ import numbers from .shapes import * from collections import namedtuple +import traceback LAYER_DESCRIPTORS = { @@ -14,7 +15,7 @@ 'ContrastiveLoss': shape_scalar, 'Convolution': shape_convolution, 'Crop': shape_not_implemented, - 'Deconvolution': shape_not_implemented, + 'Deconvolution': shape_deconvolution, 'Data': shape_data, 'Dropout': shape_identity, 'DummyData': shape_data, @@ -45,6 +46,7 @@ 'Split': shape_not_implemented, 'Slice': shape_not_implemented, 'TanH': shape_identity, + 'Unpooling': shape_not_implemented, 'WindowData': shape_not_implemented, 'Threshold': shape_identity, @@ -56,7 +58,7 @@ def generate_layer_type_enum(): - types = {t:t for t in LAYER_TYPES} + types = {t: t for t in LAYER_TYPES} return type('LayerType', (), types) LayerType = generate_layer_type_enum() @@ -74,6 +76,7 @@ def compute_output_shape(node): val = LAYER_DESCRIPTORS[node.kind](node) return val except NotImplementedError: + print traceback.print_exc() raise KaffeError('Output shape computation not implemented for type: %s' % node.kind) @@ -108,10 +111,14 @@ def __init__(self, layer, kind): @property def parameters(self): name = NodeDispatch.get_handler_name(self.kind) + # Hack, because deconvolution layers has convolution params + if name == "deconvolution": + name = "convolution" name = '_'.join((name, 'param')) try: return getattr(self.layer, name) except AttributeError: + print traceback.print_exc() raise NodeDispatchError('Caffe parameters not found for layer kind: %s' % self.kind) @staticmethod @@ -133,7 +140,7 @@ def get_kernel_value(scalar, repeated, idx, default=None): @property def kernel_parameters(self): - assert self.kind in (NodeKind.Convolution, NodeKind.Pooling) + assert self.kind in (NodeKind.Convolution, NodeKind.Pooling, NodeKind.Deconvolution) params = self.parameters k_h = self.get_kernel_value(params.kernel_h, params.kernel_size, 0) k_w = self.get_kernel_value(params.kernel_w, params.kernel_size, 1) diff --git a/kaffe/shapes.py b/kaffe/shapes.py index aa7fd73..6b66cb2 100644 --- a/kaffe/shapes.py +++ b/kaffe/shapes.py @@ -1,8 +1,14 @@ import math +import logging + from .base import * +logger = logging.getLogger(__name__) + + def make_shape(n, c, h, w): + logger.debug("Output shape of the layer is: %s", (n, c, h, w)) return n, c, h, w @@ -12,13 +18,36 @@ def get_filter_output_shape(i_h, i_w, params, round_func): return int(round_func(o_h)), int(round_func(o_w)) -def get_strided_kernel_output_shape(node, round_func): +def get_reverse_filter_output_shape(i_h, i_w, params, round_func): + """ + Compute output shape for the reverse filter + + :param i_h: height of the input + :param i_w: width of the input + :param params: namedtuple KernelParameters + :param round_func: function to round values + + :return: output_height, output_width + """ + o_h = ((i_h + 2 * params.pad_h) * params.stride_h) + params.kernel_h - params.stride_h + o_w = ((i_w + 2 * params.pad_w) * params.stride_w) + params.kernel_w - params.stride_w + return int(round_func(o_h)), int(round_func(o_w)) + + +def get_strided_kernel_output_shape(node, round_func, deconvolution=False): assert node.layer is not None + logger.info("computing shape for %s layer", node.kind) input_shape = node.get_only_parent().output_shape - o_h, o_w = get_filter_output_shape(input_shape[IDX_H], - input_shape[IDX_W], - node.layer.kernel_parameters, - round_func) + if deconvolution: + o_h, o_w = get_reverse_filter_output_shape(input_shape[IDX_H], + input_shape[IDX_W], + node.layer.kernel_parameters, + round_func) + else: + o_h, o_w = get_filter_output_shape(input_shape[IDX_H], + input_shape[IDX_W], + node.layer.kernel_parameters, + round_func) params = node.layer.parameters has_c_o = hasattr(params, 'num_output') c = params.num_output if has_c_o else input_shape[IDX_C] @@ -90,6 +119,17 @@ def shape_convolution(node): return get_strided_kernel_output_shape(node, math.floor) +def shape_deconvolution(node): + """ + Compute shape for the deconvolution layer. + + :param node: class Node representing deconvolution layer + + :return: output shape of the deconvolution layer + """ + return get_strided_kernel_output_shape(node, math.ceil, deconvolution=True) + + def shape_pool(node): return get_strided_kernel_output_shape(node, math.ceil) From 01098f090ceb85aaceed90c82ebba83685c93224 Mon Sep 17 00:00:00 2001 From: ziky90 Date: Mon, 25 Apr 2016 13:44:45 +0200 Subject: [PATCH 06/14] added crop layer --- convert.py | 2 +- kaffe/core.py | 22 +++++++++++++++------- kaffe/layers.py | 2 +- kaffe/shapes.py | 17 ++++++++++++++--- test.py | 6 +++--- 5 files changed, 34 insertions(+), 15 deletions(-) diff --git a/convert.py b/convert.py index ce797cb..5eb2fa6 100755 --- a/convert.py +++ b/convert.py @@ -41,7 +41,7 @@ def main(): src_out.write(transformer.transform_source()) logger.info('Done.') except KaffeError as err: - logger.info('Error encountered: %s'%err) + logger.info('Error encountered: %s' % err) exit(-1) logger.info("finished running %s", program) diff --git a/kaffe/core.py b/kaffe/core.py index 9618313..2a95e0d 100644 --- a/kaffe/core.py +++ b/kaffe/core.py @@ -52,8 +52,8 @@ def add_child(self, child_node): child_node.parents.append(self) def get_only_parent(self): - if len(self.parents)!=1: - raise KaffeError('Node (%s) expected to have 1 parent. Found %s.'%(self, len(self.parents))) + if len(self.parents) != 1: + raise KaffeError('Node (%s) expected to have 1 parent. Found %s.' % (self, len(self.parents))) return self.parents[0] @property @@ -68,15 +68,16 @@ def data_shape(self): return self.data[IDX_WEIGHTS].shape def __str__(self): - return '[%s] %s'%(self.kind, self.name) + return '[%s] %s' % (self.kind, self.name) def __repr__(self): - return '%s (0x%x)'%(self.name, id(self)) + return '%s (0x%x)' % (self.name, id(self)) + class Graph(object): def __init__(self, nodes=None, name=None): self.nodes = nodes or [] - self.node_lut = {node.name:node for node in self.nodes} + self.node_lut = {node.name: node for node in self.nodes} self.name = name def add_node(self, node): @@ -185,10 +186,16 @@ def adjust_parameters(self, node, data): # potential for future issues. # The Caffe-backend does not suffer from this problem. data = list(data) + logger.debug("data length:", len(data)) squeeze_indices = [1] # Squeeze biases. if node.kind == NodeKind.InnerProduct: squeeze_indices.append(0) # Squeeze FC. for idx in squeeze_indices: + logger.debug("index %s", idx) + logger.debug("data length %s", len(data)) + logger.debug("data shape %s", data[0].shape) + logger.debug("data idx shape %s", data[idx].shape) + # logger.debug(data[idx]) data[idx] = np.squeeze(data[idx]) return data @@ -196,9 +203,10 @@ def inject(self, graph): for layer_name, data in self.params: if layer_name in graph: node = graph.get_node(layer_name) + logger.debug(layer_name) node.data = self.adjust_parameters(node, data) else: - print_stderr('Ignoring parameters for non-existent layer: %s'%layer_name) + print_stderr('Ignoring parameters for non-existent layer: %s' % layer_name) class DataReshaper(object): @@ -209,7 +217,7 @@ def map(self, ndim): try: return self.mapping[ndim] except KeyError: - raise KaffeError('Ordering not found for %d dimensional tensor.'%ndim) + raise KaffeError('Ordering not found for %d dimensional tensor.' % ndim) def transpose(self, data): return data.transpose(self.map(data.ndim)) diff --git a/kaffe/layers.py b/kaffe/layers.py index 2ec618b..8d707e1 100644 --- a/kaffe/layers.py +++ b/kaffe/layers.py @@ -14,7 +14,7 @@ 'Concat': shape_concat, 'ContrastiveLoss': shape_scalar, 'Convolution': shape_convolution, - 'Crop': shape_not_implemented, + 'Crop': shape_crop, 'Deconvolution': shape_deconvolution, 'Data': shape_data, 'Dropout': shape_identity, diff --git a/kaffe/shapes.py b/kaffe/shapes.py index 6b66cb2..9b0f8df 100644 --- a/kaffe/shapes.py +++ b/kaffe/shapes.py @@ -123,13 +123,25 @@ def shape_deconvolution(node): """ Compute shape for the deconvolution layer. - :param node: class Node representing deconvolution layer + :param node: class Node representing the deconvolution layer :return: output shape of the deconvolution layer """ return get_strided_kernel_output_shape(node, math.ceil, deconvolution=True) +def shape_crop(node): + """ + Compute shape for the crop layer. + + :param node: class Node representing the crop layer + + :return: output shape of the crop layer + """ + n, c, h, w = node.parents[1].output_shape + return make_shape(n, c, h, w) + + def shape_pool(node): return get_strided_kernel_output_shape(node, math.ceil) @@ -138,5 +150,4 @@ def shape_inner_product(node): input_shape = node.get_only_parent().output_shape return make_shape(input_shape[IDX_N], node.layer.parameters.num_output, - 1, - 1) + 1, 1) diff --git a/test.py b/test.py index 42b2483..2422d33 100755 --- a/test.py +++ b/test.py @@ -22,7 +22,7 @@ def read_image(self, path): h, w, c = np.shape(img) scale_size = self.model.scale_size crop_size = self.model.crop_size - assert c==3 + assert c == 3 if self.model.isotropic: aspect = float(w)/h if w= len(examples.MODELS): print('Invalid model index. Options are:') for idx, model in enumerate(examples.MODELS): - print('%s: %s'%(idx, model)) + print('%s: %s' % (idx, model)) exit(-1) model = examples.MODELS[model_index] - print('Using model: %s'%(model)) + print('Using model: %s' % (model)) test_imagenet(model, *args[:3]) if __name__ == '__main__': From 0d9dc0619b6fb656bf3e3f3ed69b409dd2e8b7d2 Mon Sep 17 00:00:00 2001 From: ziky90 Date: Mon, 25 Apr 2016 14:59:07 +0200 Subject: [PATCH 07/14] added deconvolution and crop to the data transformation step --- kaffe/core.py | 2 ++ kaffe/tensorflow/transformer.py | 42 ++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/kaffe/core.py b/kaffe/core.py index 2a95e0d..5f93aa1 100644 --- a/kaffe/core.py +++ b/kaffe/core.py @@ -6,6 +6,8 @@ from .core import print_stderr try: + import matplotlib + matplotlib.use('Agg') import caffe PYCAFFE_AVAILABLE = True except ImportError: diff --git a/kaffe/tensorflow/transformer.py b/kaffe/tensorflow/transformer.py index bf0c592..aaaf3db 100644 --- a/kaffe/tensorflow/transformer.py +++ b/kaffe/tensorflow/transformer.py @@ -1,14 +1,23 @@ +import logging + import numpy as np + from . import network from ..base import * from ..core import GraphBuilder, DataReshaper, NodeMapper +logger = logging.getLogger(__name__) + + class TensorFlowNode(object): def __init__(self, op, *args, **kwargs): self.op = op + logger.debug("TF node operation: %s", op) self.args = args + logger.debug("TF node args: %s", args) self.kwargs = list(kwargs.items()) + logger.debug("TF node kwargs: %s", kwargs) def format(self, arg): return "'%s'" % arg if isinstance(arg, basestring) else str(arg) @@ -65,7 +74,7 @@ def map_convolution(self, node): (c_o, c_i, h, w) = node.data_shape (kernel_params, kwargs) = self.get_kernel_params(node) group = node.parameters.group - if group!=1: + if group != 1: kwargs['group'] = group assert kernel_params.kernel_h == h assert kernel_params.kernel_w == w @@ -78,6 +87,37 @@ def map_convolution(self, node): kernel_params.stride_w, **kwargs) + def map_deconvolution(self, node): + """ + Map the deconvolutional node to TensorFlowNode. + + :param node: Node object + """ + (c_o, c_i, h, w) = node.data_shape + (kernel_params, kwargs) = self.get_kernel_params(node) + group = node.parameters.group + if group != 1: + kwargs['group'] = group + assert kernel_params.kernel_h == h + assert kernel_params.kernel_w == w + return TensorFlowNode(node, + 'deconv', + kernel_params.kernel_h, + kernel_params.kernel_w, + c_o, + kernel_params.stride_h, + kernel_params.stride_w, + **kwargs) + + + def map_crop(self, node): + """ + Map the crop node to TensorFlowNode. + + :param node: Node object + """ + return TensorFlowNode('crop') + def map_relu(self, node): return TensorFlowNode('relu') From 26ef0ca7a0f3cc63b098fbba28219845d9aa4d05 Mon Sep 17 00:00:00 2001 From: ziky90 Date: Mon, 25 Apr 2016 16:39:51 +0200 Subject: [PATCH 08/14] fixes dealing with pading + forating --- kaffe/tensorflow/network.py | 6 +++--- kaffe/tensorflow/transformer.py | 15 ++++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/kaffe/tensorflow/network.py b/kaffe/tensorflow/network.py index 7c0d75e..4f665a7 100644 --- a/kaffe/tensorflow/network.py +++ b/kaffe/tensorflow/network.py @@ -9,9 +9,9 @@ def layer_decorated(self, *args, **kwargs): # Automatically set a name if not provided. name = kwargs.setdefault('name', self.get_unique_name(op.__name__)) # Figure out the layer inputs. - if len(self.inputs)==0: - raise RuntimeError('No input variables found for layer %s.'%name) - elif len(self.inputs)==1: + if len(self.inputs) == 0: + raise RuntimeError('No input variables found for layer %s.' % name) + elif len(self.inputs) == 1: layer_input = self.inputs[0] else: layer_input = list(self.inputs) diff --git a/kaffe/tensorflow/transformer.py b/kaffe/tensorflow/transformer.py index aaaf3db..94fb5ee 100644 --- a/kaffe/tensorflow/transformer.py +++ b/kaffe/tensorflow/transformer.py @@ -43,15 +43,17 @@ def get_padding_type(kernel_params, input_shape, output_shape): https://github.com/Yangqing/caffe2/blob/master/caffe2/proto/caffe2_legacy.proto """ k_h, k_w, s_h, s_w, p_h, p_w = kernel_params - s_o_h = np.ceil(input_shape[IDX_H]/float(s_h)) - s_o_w = np.ceil(input_shape[IDX_W]/float(s_w)) + s_o_h = np.ceil(input_shape[IDX_H] / float(s_h)) + s_o_w = np.ceil(input_shape[IDX_W] / float(s_w)) if (output_shape[IDX_H] == s_o_h) and (output_shape[IDX_W] == s_o_w): return 'SAME' - v_o_h = np.ceil((input_shape[IDX_H]-k_h+1.0)/float(s_h)) - v_o_w = np.ceil((input_shape[IDX_W]-k_w+1.0)/float(s_w)) + v_o_h = np.ceil((input_shape[IDX_H] - k_h + 1.0) / float(s_h)) + v_o_w = np.ceil((input_shape[IDX_W] - k_w + 1.0) / float(s_w)) if (output_shape[IDX_H] == v_o_h) and (output_shape[IDX_W] == v_o_w): return 'VALID' - return None + # Return network.DEFAULT_PADDING for case that padding is not compatible + # with TensorFlow + return network.DEFAULT_PADDING class TensorFlowMapper(NodeMapper): @@ -100,8 +102,7 @@ def map_deconvolution(self, node): kwargs['group'] = group assert kernel_params.kernel_h == h assert kernel_params.kernel_w == w - return TensorFlowNode(node, - 'deconv', + return TensorFlowNode('deconv', kernel_params.kernel_h, kernel_params.kernel_w, c_o, From cf14ccb7623e425fcc785f0e8af49b3233814b10 Mon Sep 17 00:00:00 2001 From: ziky90 Date: Tue, 26 Apr 2016 13:25:35 +0200 Subject: [PATCH 09/14] added deconvolution layer to network.py --- kaffe/tensorflow/network.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/kaffe/tensorflow/network.py b/kaffe/tensorflow/network.py index 4f665a7..0ef647f 100644 --- a/kaffe/tensorflow/network.py +++ b/kaffe/tensorflow/network.py @@ -74,6 +74,20 @@ def make_var(self, name, shape): def validate_padding(self, padding): assert padding in ('SAME', 'VALID') + @layer + def deconv(self, input, k_h, k_w, c_o, s_h, s_w, name, padding=DEFAULT_PADDING): + """ + Deconvolution network implementation in TensorFlow. + """ + self.validate_padding(padding) + c_i = input.get_shape()[-1] + convolve = lambda i, k: tf.nn.conv2d_transpose(i, k, [1, s_h, s_w, 1], padding=padding) + with tf.variable_scope(name) as scope: + kernel = self.make_var('weights', shape=[k_h, k_w, c_i / group, c_o]) + biases = self.make_var('biases', [c_o]) + conv = convolve(input, kernel) + return tf.reshape(tf.nn.bias_add(conv, biases), conv.get_shape().as_list(), name=scope.name) + @layer def conv(self, input, k_h, k_w, c_o, s_h, s_w, name, relu=True, padding=DEFAULT_PADDING, group=1): self.validate_padding(padding) From 947c8433a3d63d903f745f459e3c5a1f361c5f94 Mon Sep 17 00:00:00 2001 From: ziky90 Date: Tue, 26 Apr 2016 13:34:18 +0200 Subject: [PATCH 10/14] simpliified deconvolution step by removing group parameter --- kaffe/tensorflow/network.py | 2 +- kaffe/tensorflow/transformer.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/kaffe/tensorflow/network.py b/kaffe/tensorflow/network.py index 0ef647f..672b2a2 100644 --- a/kaffe/tensorflow/network.py +++ b/kaffe/tensorflow/network.py @@ -83,7 +83,7 @@ def deconv(self, input, k_h, k_w, c_o, s_h, s_w, name, padding=DEFAULT_PADDING): c_i = input.get_shape()[-1] convolve = lambda i, k: tf.nn.conv2d_transpose(i, k, [1, s_h, s_w, 1], padding=padding) with tf.variable_scope(name) as scope: - kernel = self.make_var('weights', shape=[k_h, k_w, c_i / group, c_o]) + kernel = self.make_var('weights', shape=[k_h, k_w, c_i, c_o]) biases = self.make_var('biases', [c_o]) conv = convolve(input, kernel) return tf.reshape(tf.nn.bias_add(conv, biases), conv.get_shape().as_list(), name=scope.name) diff --git a/kaffe/tensorflow/transformer.py b/kaffe/tensorflow/transformer.py index 94fb5ee..b840dc9 100644 --- a/kaffe/tensorflow/transformer.py +++ b/kaffe/tensorflow/transformer.py @@ -97,9 +97,6 @@ def map_deconvolution(self, node): """ (c_o, c_i, h, w) = node.data_shape (kernel_params, kwargs) = self.get_kernel_params(node) - group = node.parameters.group - if group != 1: - kwargs['group'] = group assert kernel_params.kernel_h == h assert kernel_params.kernel_w == w return TensorFlowNode('deconv', From c3fefb5b556bfa488ae2ccb3c9dbb00ca66c4664 Mon Sep 17 00:00:00 2001 From: ziky90 Date: Tue, 26 Apr 2016 15:53:37 +0200 Subject: [PATCH 11/14] fixed shape of the deconvolution layer computation --- kaffe/shapes.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/kaffe/shapes.py b/kaffe/shapes.py index 9b0f8df..bd867f8 100644 --- a/kaffe/shapes.py +++ b/kaffe/shapes.py @@ -18,20 +18,20 @@ def get_filter_output_shape(i_h, i_w, params, round_func): return int(round_func(o_h)), int(round_func(o_w)) -def get_reverse_filter_output_shape(i_h, i_w, params, round_func): +def get_reverse_filter_output_shape(i_h, i_w, params): """ - Compute output shape for the reverse filter + Compute output shape for the reverse filter. Computation is based on: + https://github.com/longjon/caffe/blob/25c2e3fdc7a27ec00893bd0335ac42e53ff2c7aa/src/caffe/layers/deconv_layer.cpp#L12 :param i_h: height of the input :param i_w: width of the input :param params: namedtuple KernelParameters - :param round_func: function to round values :return: output_height, output_width """ - o_h = ((i_h + 2 * params.pad_h) * params.stride_h) + params.kernel_h - params.stride_h - o_w = ((i_w + 2 * params.pad_w) * params.stride_w) + params.kernel_w - params.stride_w - return int(round_func(o_h)), int(round_func(o_w)) + o_h = ((i_h - 1) * params.stride_h) + params.kernel_h - 2 * params.pad_h + o_w = ((i_w - 1) * params.stride_w) + params.kernel_w - 2 * params.pad_w + return o_h, o_w def get_strided_kernel_output_shape(node, round_func, deconvolution=False): @@ -41,8 +41,7 @@ def get_strided_kernel_output_shape(node, round_func, deconvolution=False): if deconvolution: o_h, o_w = get_reverse_filter_output_shape(input_shape[IDX_H], input_shape[IDX_W], - node.layer.kernel_parameters, - round_func) + node.layer.kernel_parameters) else: o_h, o_w = get_filter_output_shape(input_shape[IDX_H], input_shape[IDX_W], From caad2af6da2f2f5f254f60ead441543bcb8c76ef Mon Sep 17 00:00:00 2001 From: ziky90 Date: Tue, 26 Apr 2016 16:10:36 +0200 Subject: [PATCH 12/14] fixed deconvolution layer conversion --- kaffe/tensorflow/network.py | 4 ++-- kaffe/tensorflow/transformer.py | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/kaffe/tensorflow/network.py b/kaffe/tensorflow/network.py index 672b2a2..025e30c 100644 --- a/kaffe/tensorflow/network.py +++ b/kaffe/tensorflow/network.py @@ -75,13 +75,13 @@ def validate_padding(self, padding): assert padding in ('SAME', 'VALID') @layer - def deconv(self, input, k_h, k_w, c_o, s_h, s_w, name, padding=DEFAULT_PADDING): + def deconv(self, input, k_h, k_w, c_o, s_h, s_w, o_h, o_w, name, padding=DEFAULT_PADDING): """ Deconvolution network implementation in TensorFlow. """ self.validate_padding(padding) c_i = input.get_shape()[-1] - convolve = lambda i, k: tf.nn.conv2d_transpose(i, k, [1, s_h, s_w, 1], padding=padding) + convolve = lambda i, k: tf.nn.conv2d_transpose(i, k, [1, o_h, o_w, c_o], [1, s_h, s_w, 1], padding=padding) with tf.variable_scope(name) as scope: kernel = self.make_var('weights', shape=[k_h, k_w, c_i, c_o]) biases = self.make_var('biases', [c_o]) diff --git a/kaffe/tensorflow/transformer.py b/kaffe/tensorflow/transformer.py index b840dc9..2f8db96 100644 --- a/kaffe/tensorflow/transformer.py +++ b/kaffe/tensorflow/transformer.py @@ -95,16 +95,20 @@ def map_deconvolution(self, node): :param node: Node object """ - (c_o, c_i, h, w) = node.data_shape + channels_output, channels_input, height, width = node.data_shape + batch_size, c_o, output_height, output_width = node.output_shape (kernel_params, kwargs) = self.get_kernel_params(node) - assert kernel_params.kernel_h == h - assert kernel_params.kernel_w == w + assert kernel_params.kernel_h == height + assert kernel_params.kernel_w == width + assert channels_output == c_o return TensorFlowNode('deconv', kernel_params.kernel_h, kernel_params.kernel_w, - c_o, + channels_output, kernel_params.stride_h, kernel_params.stride_w, + output_height, + output_width, **kwargs) From a4f2767ed18d4081d6158adf5cc8dc56ad835094 Mon Sep 17 00:00:00 2001 From: ziky90 Date: Wed, 27 Apr 2016 18:03:39 +0200 Subject: [PATCH 13/14] added possibility to add no bias term to the layer --- kaffe/layers.py | 9 ++++++--- kaffe/shapes.py | 4 ++-- kaffe/tensorflow/network.py | 6 +++--- kaffe/tensorflow/transformer.py | 13 ++++++++++--- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/kaffe/layers.py b/kaffe/layers.py index 8d707e1..4500baa 100644 --- a/kaffe/layers.py +++ b/kaffe/layers.py @@ -118,7 +118,6 @@ def parameters(self): try: return getattr(self.layer, name) except AttributeError: - print traceback.print_exc() raise NodeDispatchError('Caffe parameters not found for layer kind: %s' % self.kind) @staticmethod @@ -148,7 +147,10 @@ def kernel_parameters(self): s_w = self.get_kernel_value(params.stride_w, params.stride, 1, default=1) p_h = self.get_kernel_value(params.pad_h, params.pad, 0, default=0) p_w = self.get_kernel_value(params.pad_h, params.pad, 1, default=0) - return KernelParameters(k_h, k_w, s_h, s_w, p_h, p_w) + bias_term = self.get_kernel_value(params.bias_term, params.bias_term, 0, default=True) + if bias_term == "false": + bias_term = False + return KernelParameters(k_h, k_w, s_h, s_w, p_h, p_w, bias_term) KernelParameters = namedtuple('KernelParameters', ['kernel_h', @@ -156,4 +158,5 @@ def kernel_parameters(self): 'stride_h', 'stride_w', 'pad_h', - 'pad_w']) + 'pad_w', + 'bias_term']) diff --git a/kaffe/shapes.py b/kaffe/shapes.py index bd867f8..271ae9a 100644 --- a/kaffe/shapes.py +++ b/kaffe/shapes.py @@ -13,8 +13,8 @@ def make_shape(n, c, h, w): def get_filter_output_shape(i_h, i_w, params, round_func): - o_h = (i_h + 2*params.pad_h - params.kernel_h)/float(params.stride_h) + 1 - o_w = (i_w + 2*params.pad_w - params.kernel_w)/float(params.stride_w) + 1 + o_h = (i_h + 2 * params.pad_h - params.kernel_h)/float(params.stride_h) + 1 + o_w = (i_w + 2 * params.pad_w - params.kernel_w)/float(params.stride_w) + 1 return int(round_func(o_h)), int(round_func(o_w)) diff --git a/kaffe/tensorflow/network.py b/kaffe/tensorflow/network.py index 025e30c..24e8079 100644 --- a/kaffe/tensorflow/network.py +++ b/kaffe/tensorflow/network.py @@ -84,9 +84,9 @@ def deconv(self, input, k_h, k_w, c_o, s_h, s_w, o_h, o_w, name, padding=DEFAULT convolve = lambda i, k: tf.nn.conv2d_transpose(i, k, [1, o_h, o_w, c_o], [1, s_h, s_w, 1], padding=padding) with tf.variable_scope(name) as scope: kernel = self.make_var('weights', shape=[k_h, k_w, c_i, c_o]) - biases = self.make_var('biases', [c_o]) - conv = convolve(input, kernel) - return tf.reshape(tf.nn.bias_add(conv, biases), conv.get_shape().as_list(), name=scope.name) + # biases = self.make_var('biases', [c_o]) + return convolve(input, kernel) + # return tf.reshape(tf.nn.bias_add(conv, biases), conv.get_shape().as_list(), name=scope.name) @layer def conv(self, input, k_h, k_w, c_o, s_h, s_w, name, relu=True, padding=DEFAULT_PADDING, group=1): diff --git a/kaffe/tensorflow/transformer.py b/kaffe/tensorflow/transformer.py index 2f8db96..7dffedb 100644 --- a/kaffe/tensorflow/transformer.py +++ b/kaffe/tensorflow/transformer.py @@ -42,7 +42,7 @@ def get_padding_type(kernel_params, input_shape, output_shape): how the padding edge-cases are handled. These are described here: https://github.com/Yangqing/caffe2/blob/master/caffe2/proto/caffe2_legacy.proto """ - k_h, k_w, s_h, s_w, p_h, p_w = kernel_params + k_h, k_w, s_h, s_w, p_h, p_w, _ = kernel_params s_o_h = np.ceil(input_shape[IDX_H] / float(s_h)) s_o_w = np.ceil(input_shape[IDX_W] / float(s_w)) if (output_shape[IDX_H] == s_o_h) and (output_shape[IDX_W] == s_o_w): @@ -63,8 +63,12 @@ def get_kernel_params(self, node): input_shape = node.get_only_parent().output_shape padding = get_padding_type(kernel_params, input_shape, node.output_shape) # Only emit the padding if it's not the default value. - padding = {'padding': padding} if padding != network.DEFAULT_PADDING else {} - return kernel_params, padding + kwargs = {} + if padding != network.DEFAULT_PADDING: + kwargs['padding'] = padding + if not kernel_params[-1]: + kwargs['bias_term'] = False + return kernel_params, kwargs def relu_adapted_node(self, node, *args, **kwargs): # Opt-out instead of opt-in as ReLU(op) is the common case. @@ -98,6 +102,8 @@ def map_deconvolution(self, node): channels_output, channels_input, height, width = node.data_shape batch_size, c_o, output_height, output_width = node.output_shape (kernel_params, kwargs) = self.get_kernel_params(node) + # bias term info + node.layer.kernel_parameters. assert kernel_params.kernel_h == height assert kernel_params.kernel_w == width assert channels_output == c_o @@ -118,6 +124,7 @@ def map_crop(self, node): :param node: Node object """ + # TODO return TensorFlowNode('crop') def map_relu(self, node): From 57f60b8e793ae16f61ffb5a30d74fdf5526a0dca Mon Sep 17 00:00:00 2001 From: ziky90 Date: Thu, 28 Apr 2016 13:14:06 +0200 Subject: [PATCH 14/14] bugfix --- kaffe/tensorflow/transformer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/kaffe/tensorflow/transformer.py b/kaffe/tensorflow/transformer.py index 7dffedb..afdc3f0 100644 --- a/kaffe/tensorflow/transformer.py +++ b/kaffe/tensorflow/transformer.py @@ -102,8 +102,6 @@ def map_deconvolution(self, node): channels_output, channels_input, height, width = node.data_shape batch_size, c_o, output_height, output_width = node.output_shape (kernel_params, kwargs) = self.get_kernel_params(node) - # bias term info - node.layer.kernel_parameters. assert kernel_params.kernel_h == height assert kernel_params.kernel_w == width assert channels_output == c_o