Imported Upstream version 1.4.0
[platform/core/ml/nnfw.git] / compiler / nnc / utils / caffe_model_maker / GenerateCaffeModels.py
1 #!/usr/bin/python3
2 """
3 Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved
4
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9    http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16 """
17
18 import caffe
19 import numpy as np
20 import sys
21 import h5py
22 from itertools import chain
23 from caffe import layers as L
24 import random
25 import lmdb
26 from collections import Counter, OrderedDict
27
28 if (len(sys.argv) < 2):
29     dest_folder = ''
30     print('Using current directory as destination folder')
31 else:
32     dest_folder = sys.argv[1] + '/'
33
34
35 class PH:
36     """
37     PlaceHolder value
38     """
39
40     def __init__(self, type, param):
41         self.type = type
42         self.param = param
43
44
45 # Bookkeeping
46 LS = 224
47 # bynaryProto file for Infogain
48 H = np.eye(3, dtype='f4')
49 blob = caffe.io.array_to_blobproto(H.reshape((1, 1, 3, 3)))
50 with open(dest_folder + 'infogainH.binaryproto', 'wb+') as f:
51     f.write(blob.SerializeToString())
52
53 # List of hdf5 files
54 with open(dest_folder + "in", 'w+') as f:
55     f.write('in.hdf5')
56
57 #Window File
58 with open(dest_folder + "in_winds", 'w+') as f:
59     f.write("""# 1
60 in.jpg
61 3
62 224
63 224
64 2
65 1 0.1 50 50 60 70
66 1 0.9 30 30 50 50
67 # 2
68 in.jpg
69 3
70 224
71 224
72 2
73 1 0.1 50 50 70 70
74 1 0.9 30 30 50 50
75 """)
76
77 # HDF5 file for HDF5DataSet
78 h5f = h5py.File(dest_folder + "in.hdf5", "w")
79 h5f.create_dataset("data", data=np.random.rand(1, 3, LS, LS))
80 h5f.close()
81
82 # LMDB file
83 env = lmdb.open(dest_folder + 'test-lmdb')
84 with env.begin(write=True) as txn:
85     img_data = np.random.rand(3, LS, LS)
86     datum = caffe.io.array_to_datum(img_data, label=1)
87     txn.put('{:0>10d}'.format(1).encode('ascii'), datum.SerializeToString())
88 env.close()
89
90 # recurring parameters
91 losspara = {'ignore_label': True, 'normalization': 1, 'normalize': True}
92 softmaxpara = {'engine': 0, 'axis': 1}
93 gdfil = {'type': 'gaussian', 'std': 0.001}
94 cofil = {'type': 'constant', 'value': 0}
95 rp = {
96     'num_output': 1,
97     'weight_filler': gdfil,
98     'bias_filler': cofil,
99     'expose_hidden': True
100 }
101
102 filler_par = {
103     'type': 'constant',
104     'value': 0,
105     'min': 0,
106     'max': 1,
107     'mean': 0,
108     'std': 1,
109     'sparse': -1,  # -1 means no sparsification
110     'variance_norm': 0
111 }  # 0 = FAN_IN, 1 = FAN_OUT, 2 = AVERAGE
112
113 OPS = [
114     ('Parameter', {
115         'shape': {
116             'dim': [1]
117         },
118         "is_data": True
119     }),  # ok
120     (
121         'Data',
122         {
123             'source': 'test-lmdb',  # FIXME: unknown DB backend
124             'batch_size': 1,
125             'rand_skip': 0,
126             'backend': 1,  # 0 = LEVELDB, 1 = LMDB
127             'scale': 1.0,  # deprecated in favor of TransformationParameter
128             'mean_file': 'wtf.is_that',
129             'crop_size': 0,
130             'mirror': False,
131             'force_encoded_color': False,
132             'prefetch': 4,
133             "is_data": True
134         }),
135     (
136         'DummyData',
137         {
138             'data_filler': cofil,  # ok
139             #'num' : [1,1,1], # deprecated shape specification
140             #'channels' : [2,2,2],
141             #'height' : [3,3,3],
142             #'width' : [4,4,4]},
143             'shape': {
144                 'dim': [1, 3, LS, LS]
145             },
146             "is_data": True
147         }),
148     (
149         'ImageData',
150         {
151             'source': 'in_imgs',  # file with list of imgs
152             'top': 'op2',
153             'batch_size': 1,
154             'rand_skip': 0,
155             'shuffle': False,
156             'new_height': 0,
157             'new_width': 0,
158             'is_color': True,
159             'root_folder': '',
160             'scale': 1.0,  # deprecated in favor of TransformationParameter
161             'mirror': False,
162             "is_data": True
163         }),
164     (
165         'WindowData',
166         {
167             'source': 'in_winds',
168             'top': 'op2',
169             'batch_size': 1,
170             'mean_file': 'in.jpg',
171             'transform_param': {
172                 'scale': 0.8,
173                 'crop_size': 24,
174                 'mirror': False,
175                 #'fg_treshold' : 0.5,
176                 #'bg_treshold' : 0.5,
177                 #'fg_fraction' : 0.25,
178             },
179             'context_pad': 1,
180             'crop_mode': 'warp',
181             'cache_images': True,
182             'root_folder': './',
183             "is_data": True
184         }),
185     (
186         'HDF5Data',
187         {
188             'source': 'in',  # This is the name of the file WITH HDF5 FILENAMES 0_0
189             # Top should have the same name as the dataset in the hdf5 file
190             # FIXME Requires Caffegen to be built with Caffe that supports LMDB
191             'batch_size': 1,
192             'shuffle': False,
193             "is_data": True
194         }),
195     ('Input', {
196         'shape': {
197             'dim': [1, 2, 3, 4]
198         },
199         "is_data": True
200     }),  # ok
201     (
202         'MemoryData',
203         {
204             'batch_size': 1,  # ok
205             'channels': 2,
206             'height': 3,
207             'width': 4,
208             'top': "foo",
209             "is_data": True
210         }),
211
212     ## Regular OPS
213     (
214         "Convolution",
215         {
216             'num_output': 64,  # ok
217             'kernel_size': 9,
218             'stride': 1,
219             'pad': 0,
220             'weight_filler': gdfil,
221             'param': [{
222                 'lr_mult': 1
223             }, {
224                 'lr_mult': 0.1
225             }],
226             'bias_filler': cofil
227         }),
228
229     # Depthvise conv
230     (
231         "Convolution",
232         {
233             'num_output': 12,  # ok
234             'kernel_size': 9,
235             'stride': 1,
236             'dilation': 2,
237             'group': 3,
238             'pad': 0,
239             'weight_filler': gdfil,
240             'param': [{
241                 'lr_mult': 1
242             }, {
243                 'lr_mult': 0.1
244             }],
245             'bias_filler': cofil
246         }),
247     (
248         "Deconvolution",
249         {
250             'convolution_param':  # ok
251             {
252                 'num_output': 4,
253                 'kernel_size': 9,
254                 'stride': 1,
255                 'pad': 0,
256                 'weight_filler': gdfil,
257                 'bias_filler': cofil
258             }
259         }),
260     # Depthvise deconv
261     (
262         "Deconvolution",
263         {
264             'convolution_param':  # ok
265             {
266                 'num_output': 12,
267                 'kernel_size': 9,
268                 'stride': 1,
269                 'dilation': 2,
270                 'group': 3,
271                 'pad': 0,
272                 'weight_filler': gdfil,
273                 'bias_filler': cofil
274             }
275         }),
276     (
277         'BatchNorm',
278         {
279             'eps': 1e-5,  # ok
280             'moving_average_fraction': 0.999
281         }),
282     (
283         'LRN',
284         {
285             'alpha': 1.,  # ok
286             'beta': 0.75,
287             'norm_region': 1,
288             'local_size': 5,
289             'k': 1,
290             'engine': 0
291         }),
292     # local_size[default 5]: the number of channels to sum over
293     # alpha[default 1]: the scaling paramete
294     # beta[default5]: the exponent
295     # norm_region[default ACROSS_CHANNLS]: whether to sum over adjacent channels(ACROSS_CHANNLS) or nearby
296     # spatial locations(WITHIN_CHANNLS)
297     # `input / (1 + (\alpha/n) \sum_i x_i^2)^\beta`
298     (
299         "MVN",
300         {
301             'normalize_variance': True,  # ok
302             'across_channels': False,
303             'eps': 1e-9
304         }),
305     (
306         'Im2col',
307         {
308             'convolution_param':  # ok
309             {
310                 'num_output': 64,
311                 'kernel_size': 9,
312                 'stride': 1,
313                 'pad': 0,
314                 'weight_filler': gdfil,
315                 # 'param' : [{'lr_mult':1},{'lr_mult':0.1}],
316                 'bias_filler': cofil
317             }
318         }),
319     ('Dropout', {
320         'dropout_ratio': 0.5
321     }),  # ok
322     ('Split', {}),  # ok
323     ('Concat', {
324         'axis': 1
325     }),  # ok
326     (
327         'Tile',
328         {
329             'axis': 1,  # ok
330             'tiles': 2
331         }),
332     ('Slice', {
333         'axis': 1,
334         'top': 'op2',
335         'slice_point': 1
336     }),
337     (
338         'Reshape',
339         {
340             'shape': {
341                 'dim': [1, 0, -1]
342             },  # ok
343             'axis': 0,
344             'num_axes': -1
345         }),
346     # reshapes only [axis, axis + num_axes] if those aren't 0 and -1; axis can be negative
347     # 0 in shape means retaining dim size, -1 means auto size
348     (
349         'Flatten',
350         {
351             'axis': 1,  # ok
352             'end_axis': -1
353         }),
354     (
355         'Pooling',
356         {
357             'pool': 0,  # ok # pool: 0 = MAX, 1 = AVE, 2 = STOCHASTIC
358             'pad': 0,  # can be replaced with pad_w, pad_h
359             'kernel_size': 3,  # can be replaced with kernel_w, kernel_h
360             'stride': 1,  # can be replaced with stride_w, stride_h
361             'engine': 0,
362             'global_pooling': False
363         }),
364     # 'round_mode' : 0}), # 0 = CELS, 1 = FLOOR
365     (
366         'Reduction',
367         {
368             'operation': 1,  # ok # 1 = SUM, 2 = ASUM, 3 = SUMSQ, 4 = MEAN # ok
369             'axis': 0,
370             'coeff': 1.0
371         }),
372     (
373         'SPP',
374         {
375             'pyramid_height': 1,  # ok
376             'pool': 0,
377             'engine': 0
378         }),
379     (
380         'InnerProduct',
381         {
382             'num_output': 2,  # ok
383             'bias_term': True,
384             'weight_filler': filler_par,
385             'bias_filler': filler_par,
386             'axis': 1,
387             'transpose': False
388         }),
389     (
390         'Embed',
391         {
392             'num_output': 2,  # ok
393             'input_dim': 1,
394             'bias_term': True,
395             'weight_filler': filler_par,
396             'bias_filler': filler_par
397         }),
398     (
399         'ArgMax',
400         {
401             'out_max_val': False,  # ok # if True, outputs pairs (argmax, maxval) # ok
402             'top_k': 1,
403             'axis': -1
404         }),
405     (
406         'Softmax',
407         {
408             'engine': 0,  # ok
409             'axis': 1
410         }),
411     (
412         'ReLU',
413         {
414             'negative_slope': 0,  # ok
415             'engine': 0
416         }),
417     (
418         'PReLU',
419         {
420             'filler': filler_par,  # ok
421             'channel_shared': False
422         }),
423     ('ELU', {
424         'alpha': 1
425     }),  # ok
426     ('Sigmoid', {
427         'engine': 0
428     }),  # ok
429     ('BNLL', {}),  # ok
430     ('TanH', {
431         'engine': 0
432     }),  # ok
433     ('Threshold', {
434         'threshold': 0
435     }),  # ok
436     (
437         'Bias',
438         {
439             'axis': 0,  # ok
440             'num_axes': -1,
441             'filler': filler_par
442         }),
443     (
444         'Scale',
445         {
446             'axis': 0,  # ok
447             'num_axes': -1,
448             'filler': filler_par,
449             'bias_term': False,
450             'bias_filler': filler_par
451         }),
452     ('AbsVal', {}),  # ok
453     (
454         'Log',
455         {
456             'base': -1.0,  # ok
457             'scale': 1.0,
458             'shift': PH(float, (2.0, 10.0)),
459             'how_many': 10
460         }),  # y = ln(shift + scale * x) (log_base() for base > 0)
461     (
462         'Power',
463         {
464             'power': -1.0,  # ok
465             'scale': 1.0,
466             'shift': 0.0
467         }),  # y = (shift + scale * x) ^ power
468     (
469         'Exp',
470         {
471             'base': -1.0,  # ok
472             'scale': 1.0,
473             'shift': 0.0
474         }),
475
476     ## TWO INPUTS
477     (
478         'Crop',
479         {
480             'axis': 2,  # ok
481             'offset': [0],
482             "inputs": 2
483         }),  # if one offset - for all dims, more - specifies
484     (
485         "Eltwise",
486         {
487             'operation': 1,  # ok
488             'coeff': [3, 3],
489             'stable_prod_grad': True,
490             "inputs": 2
491         }),
492     ("EuclideanLoss", {
493         "inputs": 2
494     }),  # ok
495     ("HingeLoss", {
496         'norm': 1,
497         "inputs": 2
498     }),  # L1 = 1; L2 = 2; # ok
499     ("SigmoidCrossEntropyLoss", {
500         'loss_param': losspara,
501         "inputs": 2
502     }),  # ok
503
504     ## TWO Inputs, special shape
505     (
506         "Accuracy",
507         {
508             'top_k': 1,  # FIXME: different bottom shapes needed
509             'axis': 0,
510             'ignore_label': 0,
511             "inputs": 2,
512             "special_shape": [1, 3, 1, 1]
513         }),
514     (
515         "SoftmaxWithLoss",
516         {
517             'loss_param': losspara,  # FIXME: different bottom shapes needed
518             'softmax_param': softmaxpara,
519             "inputs": 2,
520             "special_shape": [1, 1, 1, 1]
521         }),
522     ("MultinomialLogisticLoss", {
523         'loss_param': losspara,
524         "inputs": 2,
525         "special_shape": [1, 1, 1, 1]
526     }),  # FIXME: different bottom shapes needed
527     ("Filter", {
528         "inputs": 2,
529         "special_shape": [1, 1, 1, 1]
530     }),  # FIXME: different bottom shapes needed
531     ('BatchReindex', {
532         "inputs": 2,
533         "special_shape": [2]
534     }),  # takes indices as second blob
535     ("InfogainLoss", {
536         'source': 'infogainH.binaryproto',
537         'axis': 1,
538         "inputs": 2,
539         "special_shape": [1, 1, 1, 1]
540     }),
541     (
542         'Python',
543         {
544             'python_param':  # Custom Loss layer
545             {
546                 'module': 'Pyloss',  # the module name -- usually the filename -- that needs to be in $PYTHONPATH
547                 'layer': 'EuclideanLossLayer',  # the layer name -- the class name in the module
548                 'share_in_parallel': False
549             },
550             # set loss weight so Caffe knows this is a loss layer.
551             # since PythonLayer inherits directly from Layer, this isn't automatically
552             # known to Caffe
553             'loss_weight': 1,
554             "inputs": 2,
555             "special_shape": [1, 3, 1, 1]
556         },
557     ),
558
559     ## NOTOP OPS
560     ('HDF5Output', {
561         'file_name': 'out.hdf5',
562         "inputs": 2,
563         "is_notop": True
564     }),  # ok
565     ('Silence', {
566         "inputs": 2,
567         "is_notop": True
568     }),  # ok, need to remove tops
569
570     ## THREE INPUTS
571     ("RNN", {
572         'recurrent_param': rp,
573         'top': "out2",
574         "inputs": 3
575     }),  # ok
576     ("Recurrent", {
577         'recurrent_param': rp,
578         'top': "out2",
579         "inputs": 3
580     }),  # ok
581
582     ## FOUR INPUTS
583     ("LSTM", {
584         'recurrent_param': rp,
585         'top': ["out2", "out3"],
586         "inputs": 4
587     }),  # ok
588
589     ## Handled explicitly (special case)
590     ("ContrastiveLoss", {
591         'margin': 1.0,
592         'legacy_version': False
593     }),
594 ]
595
596 #Helper functions
597
598
599 def traverse(obj, callback=None):
600     """
601      walks a nested dict/list recursively
602     :param obj:
603     :param callback:
604     :return:
605     """
606     if isinstance(obj, dict):
607         value = {k: traverse(v, callback) for k, v in obj.items()}
608     elif isinstance(obj, list):
609         value = [traverse(elem, callback) for elem in obj]
610     else:
611         value = obj
612
613     if callback is None:
614         return value
615     else:
616         return callback(value)
617
618
619 def mock(inp):
620     if not (isinstance(inp, PH)): return inp
621     if inp.type == int:
622         return random.randint(*inp.param)
623     if inp.type == float:
624         return random.uniform(*inp.param)
625
626
627 EXTRA_SHAPES = \
628     [(), # alredy defined
629      [1, 3],
630      [1, 3, 1],
631      [1, 3, 1]]
632
633
634 class Layer:
635     """
636     Represents a caffe layer
637     """
638
639     def __init__(self, name, params):
640         self.name = name
641         self.args = params
642         if self.args == None: self.args = dict()
643         self.num_inp = self.args.pop("inputs", 1)
644         self.num_out = self.args.pop("outputs", 1)
645         self.special_shape = self.args.pop("special_shape",
646                                            False)  # 2nd input has special shape
647         self.is_data = self.args.pop("is_data", False)
648         self.is_notop = self.args.pop("is_notop", False)
649
650     def make_net(self):
651         """
652         Creates a protobuf network
653         :return:
654         """
655         net = caffe.NetSpec()
656
657         if self.is_data:
658             net.data = getattr(L, self.name)(**self.args)
659
660         # Very special,
661         elif self.name == "ContrastiveLoss":
662             net.data = L.Input(shape={'dim': [1, 4]})
663             net.data1 = L.DummyData(data_filler=cofil, shape={'dim': [1, 4]})
664             net.data2 = L.DummyData(data_filler=cofil, shape={'dim': [1, 1]})
665
666             net.op = getattr(L, self.name)(net.data, net.data1, net.data2, **self.args)
667
668         # this covers most cases
669         else:
670             net.data = L.Input(shape={'dim': [1, 3, LS, LS]})
671             if self.num_inp == 2:
672                 net.data1 = L.DummyData(data_filler=cofil, shape={'dim': [1, 3, LS, LS]})
673             elif self.num_inp > 2:
674                 for i in range(1, self.num_inp):
675                     setattr(
676                         net, "data" + str(i),
677                         L.DummyData(data_filler=cofil, shape={'dim': EXTRA_SHAPES[i]}))
678             if self.special_shape:
679                 net.data = L.Input(shape={'dim': [1, 3, 1, 1]})
680                 net.data1 = L.DummyData(
681                     data_filler=cofil, shape={'dim': self.special_shape})
682
683             net.op = getattr(L, self.name)(
684                 net.data,
685                 *[getattr(net, "data" + str(i))
686                   for i in range(1, self.num_inp)], **self.args)
687
688         if self.is_notop:
689             net.op.fn.tops = OrderedDict()
690             net.op.fn.ntop = 0  # the messing about in question
691
692         return net
693
694
695 class LayerMaker:
696     """
697     Factory class for Layer
698     """
699
700     def __init__(self, params):
701         self.name, self.args = params
702         self.how_many = self.args.pop("how_many", 1)
703
704     def make(self):
705         return [Layer(self.name, traverse(self.args, mock)) for i in range(self.how_many)]
706
707
708 layer_gen = chain(*map(lambda para: LayerMaker(para).make(), OPS))
709
710 filename = dest_folder + '{}_{}.prototxt'
711
712 counter = Counter()
713 for layer in layer_gen:
714     n = layer.make_net()
715     counter[layer.name] += 1
716
717     with open(filename.format(layer.name, counter[layer.name] - 1), 'w+') as ptxt_file:
718         print(n.to_proto(), file=ptxt_file)
719
720     if layer.name == "Python":  # Special case for python layer
721         with open("Python_0.caffemodel", 'wb+') as caffemodelFile:
722             caffemodelFile.write(n.to_proto().SerializeToString())