|
1 |
| -import numpy as np |
2 | 1 | from google.protobuf import text_format
|
3 | 2 |
|
4 |
| -from .layers import * |
5 |
| -from .base import print_stderr |
6 |
| -from .caffe import has_pycaffe, get_caffe_resolver |
| 3 | +from .caffe import get_caffe_resolver |
| 4 | +from .data import DataInjector |
| 5 | +from .errors import KaffeError, print_stderr |
| 6 | +from .layers import LayerAdapter, LayerType, NodeKind, NodeDispatch |
| 7 | +from .shapes import TensorShape |
7 | 8 |
|
8 | 9 | class Node(object):
|
9 | 10 |
|
@@ -41,12 +42,6 @@ def parameters(self):
|
41 | 42 | return self.layer.parameters
|
42 | 43 | return None
|
43 | 44 |
|
44 |
| - @property |
45 |
| - def data_shape(self): |
46 |
| - assert self.data |
47 |
| - # pylint: disable=unsubscriptable-object |
48 |
| - return self.data[IDX_WEIGHTS].shape |
49 |
| - |
50 | 45 | def __str__(self):
|
51 | 46 | return '[%s] %s' % (self.kind, self.name)
|
52 | 47 |
|
@@ -102,138 +97,25 @@ def visit(node):
|
102 | 97 | def compute_output_shapes(self):
|
103 | 98 | sorted_nodes = self.topologically_sorted()
|
104 | 99 | for node in sorted_nodes:
|
105 |
| - node.output_shape = NodeKind.compute_output_shape(node) |
| 100 | + node.output_shape = TensorShape(*NodeKind.compute_output_shape(node)) |
106 | 101 |
|
107 | 102 | def __contains__(self, key):
|
108 | 103 | return key in self.node_lut
|
109 | 104 |
|
110 | 105 | def __str__(self):
|
111 | 106 | hdr = '{:<20} {:<30} {:>20} {:>20}'.format('Type', 'Name', 'Param', 'Output')
|
112 | 107 | s = [hdr, '-' * 94]
|
| 108 | + fmt_tensor_shape = lambda ts: '({}, {}, {}, {})'.format(*ts) |
113 | 109 | for node in self.topologically_sorted():
|
114 |
| - data_shape = node.data[IDX_WEIGHTS].shape if node.data else '--' |
| 110 | + # If the node has learned parameters, display the first one's shape. |
| 111 | + # In case of convolutions, this corresponds to the weights. |
| 112 | + data_shape = node.data[0].shape if node.data else '--' |
115 | 113 | out_shape = node.output_shape or '--'
|
116 | 114 | s.append('{:<20} {:<30} {:>20} {:>20}'.format(node.kind, node.name, data_shape,
|
117 |
| - out_shape)) |
| 115 | + fmt_tensor_shape(out_shape))) |
118 | 116 | return '\n'.join(s)
|
119 | 117 |
|
120 | 118 |
|
121 |
| -class DataInjector(object): |
122 |
| - |
123 |
| - def __init__(self, def_path, data_path): |
124 |
| - self.def_path = def_path |
125 |
| - self.data_path = data_path |
126 |
| - self.did_use_pb = False |
127 |
| - self.params = None |
128 |
| - self.load() |
129 |
| - |
130 |
| - def load(self): |
131 |
| - if has_pycaffe(): |
132 |
| - self.load_using_caffe() |
133 |
| - else: |
134 |
| - self.load_using_pb() |
135 |
| - |
136 |
| - def load_using_caffe(self): |
137 |
| - caffe = get_caffe_resolver().caffe |
138 |
| - net = caffe.Net(self.def_path, self.data_path, caffe.TEST) |
139 |
| - data = lambda blob: blob.data |
140 |
| - self.params = [(k, map(data, v)) for k, v in net.params.items()] |
141 |
| - |
142 |
| - def load_using_pb(self): |
143 |
| - data = get_caffe_resolver().NetParameter() |
144 |
| - data.MergeFromString(open(self.data_path, 'rb').read()) |
145 |
| - pair = lambda layer: (layer.name, self.transform_data(layer)) |
146 |
| - layers = data.layers or data.layer |
147 |
| - self.params = [pair(layer) for layer in layers if layer.blobs] |
148 |
| - self.did_use_pb = True |
149 |
| - |
150 |
| - def transform_data(self, layer): |
151 |
| - transformed = [] |
152 |
| - for blob in layer.blobs: |
153 |
| - if len(blob.shape.dim): |
154 |
| - dims = blob.shape.dim |
155 |
| - c_o, c_i, h, w = map(int, [1] * (4 - len(dims)) + list(dims)) |
156 |
| - else: |
157 |
| - c_o = blob.num |
158 |
| - c_i = blob.channels |
159 |
| - h = blob.height |
160 |
| - w = blob.width |
161 |
| - data = np.array(blob.data, dtype=np.float32).reshape(c_o, c_i, h, w) |
162 |
| - transformed.append(data) |
163 |
| - return transformed |
164 |
| - |
165 |
| - def adjust_parameters(self, node, data): |
166 |
| - if not self.did_use_pb: |
167 |
| - return data |
168 |
| - # When using the protobuf-backend, each parameter initially has four dimensions. |
169 |
| - # In certain cases (like FC layers), we want to eliminate the singleton dimensions. |
170 |
| - # This implementation takes care of the common cases. However, it does leave the |
171 |
| - # potential for future issues. |
172 |
| - # The Caffe-backend does not suffer from this problem. |
173 |
| - data = list(data) |
174 |
| - squeeze_indices = [1] # Squeeze biases. |
175 |
| - if node.kind == NodeKind.InnerProduct: |
176 |
| - squeeze_indices.append(0) # Squeeze FC. |
177 |
| - for idx in squeeze_indices: |
178 |
| - data[idx] = np.squeeze(data[idx]) |
179 |
| - return data |
180 |
| - |
181 |
| - def inject(self, graph): |
182 |
| - for layer_name, data in self.params: |
183 |
| - if layer_name in graph: |
184 |
| - node = graph.get_node(layer_name) |
185 |
| - node.data = self.adjust_parameters(node, data) |
186 |
| - else: |
187 |
| - print_stderr('Ignoring parameters for non-existent layer: %s' % layer_name) |
188 |
| - |
189 |
| - |
190 |
| -class DataReshaper(object): |
191 |
| - |
192 |
| - def __init__(self, mapping): |
193 |
| - self.mapping = mapping |
194 |
| - |
195 |
| - def map(self, ndim): |
196 |
| - try: |
197 |
| - return self.mapping[ndim] |
198 |
| - except KeyError: |
199 |
| - raise KaffeError('Ordering not found for %d dimensional tensor.' % ndim) |
200 |
| - |
201 |
| - def transpose(self, data): |
202 |
| - return data.transpose(self.map(data.ndim)) |
203 |
| - |
204 |
| - def has_spatial_parent(self, node): |
205 |
| - try: |
206 |
| - parent = node.get_only_parent() |
207 |
| - s = parent.output_shape |
208 |
| - return s[IDX_H] > 1 or s[IDX_W] > 1 |
209 |
| - except KaffeError: |
210 |
| - return False |
211 |
| - |
212 |
| - def reshape(self, graph, replace=True): |
213 |
| - for node in graph.nodes: |
214 |
| - if node.data is None: |
215 |
| - continue |
216 |
| - data = node.data[IDX_WEIGHTS] |
217 |
| - if (node.kind == NodeKind.InnerProduct) and self.has_spatial_parent(node): |
218 |
| - # The FC layer connected to the spatial layer needs to be |
219 |
| - # re-wired to match the new spatial ordering. |
220 |
| - in_shape = node.get_only_parent().output_shape |
221 |
| - fc_shape = data.shape |
222 |
| - fc_order = self.map(2) |
223 |
| - data = data.reshape((fc_shape[IDX_C_OUT], in_shape[IDX_C], in_shape[IDX_H], |
224 |
| - in_shape[IDX_W])) |
225 |
| - data = self.transpose(data) |
226 |
| - node.reshaped_data = data.reshape(fc_shape[fc_order[0]], fc_shape[fc_order[1]]) |
227 |
| - else: |
228 |
| - node.reshaped_data = self.transpose(data) |
229 |
| - |
230 |
| - if replace: |
231 |
| - for node in graph.nodes: |
232 |
| - if node.data is not None: |
233 |
| - node.data[IDX_WEIGHTS] = node.reshaped_data |
234 |
| - del node.reshaped_data |
235 |
| - |
236 |
| - |
237 | 119 | class GraphBuilder(object):
|
238 | 120 | '''Constructs a model graph from a Caffe protocol buffer definition.'''
|
239 | 121 |
|
|
0 commit comments