Skip to content
30 changes: 23 additions & 7 deletions convert.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
#!/usr/bin/env python

import os
import sys
import numpy as np
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')
Expand All @@ -17,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()
10 changes: 6 additions & 4 deletions examples/alexnet.py
Original file line number Diff line number Diff line change
@@ -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')
Expand Down
1 change: 1 addition & 0 deletions examples/caffenet.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from kaffe.tensorflow import Network


class CaffeNet(Network):
def setup(self):
(self.feed('data')
Expand Down
1 change: 1 addition & 0 deletions examples/googlenet.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from kaffe.tensorflow import Network


class GoogleNet(Network):
def setup(self):
(self.feed('data')
Expand Down
1 change: 1 addition & 0 deletions examples/vgg.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from kaffe.tensorflow import Network


class VGG16(Network):
def setup(self):
(self.feed('data')
Expand Down
20 changes: 11 additions & 9 deletions kaffe/base.py
Original file line number Diff line number Diff line change
@@ -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)
91 changes: 53 additions & 38 deletions kaffe/core.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import os
import sys
import numpy as np
from google.protobuf import text_format

from .layers import *
from .core import print_stderr

try:
import matplotlib
matplotlib.use('Agg')
import caffe
PYCAFFE_AVAILABLE = True
except ImportError:
Expand All @@ -28,16 +29,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
Expand All @@ -52,8 +54,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
Expand All @@ -68,15 +70,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):
Expand All @@ -100,6 +103,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.')
Expand Down Expand Up @@ -133,6 +137,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
Expand Down Expand Up @@ -166,10 +171,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
Expand All @@ -183,20 +188,28 @@ 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.
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

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):
def __init__(self, mapping):
Expand All @@ -206,7 +219,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))
Expand All @@ -215,7 +228,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

Expand All @@ -224,7 +237,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
Expand All @@ -242,6 +255,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
Expand All @@ -255,7 +269,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:
Expand All @@ -264,12 +278,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.
Expand All @@ -280,7 +294,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):
Expand All @@ -291,7 +305,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.')
Expand All @@ -302,10 +316,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
Expand All @@ -330,13 +344,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.
Expand All @@ -355,6 +369,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
Expand All @@ -368,15 +383,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
Expand Down
Loading