You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@singa.apache.org by wa...@apache.org on 2018/05/19 04:25:07 UTC
[2/3] incubator-singa git commit: SINGA-363 Add DenseNet for Imagenet
classification
SINGA-363 Add DenseNet for Imagenet classification
Project: http://git-wip-us.apache.org/repos/asf/incubator-singa/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-singa/commit/8a1d98a3
Tree: http://git-wip-us.apache.org/repos/asf/incubator-singa/tree/8a1d98a3
Diff: http://git-wip-us.apache.org/repos/asf/incubator-singa/diff/8a1d98a3
Branch: refs/heads/master
Commit: 8a1d98a39385137b6250bc36c5a558c9d731aa45
Parents: dd58f49
Author: Wentong-DST <li...@gmail.com>
Authored: Fri May 18 13:18:57 2018 +0800
Committer: Wentong-DST <li...@gmail.com>
Committed: Fri May 18 13:18:57 2018 +0800
----------------------------------------------------------------------
examples/imagenet/densenet/README.md | 50 +++++++++
examples/imagenet/densenet/convert.py | 90 +++++++++++++++
examples/imagenet/densenet/model.py | 170 +++++++++++++++++++++++++++++
examples/imagenet/densenet/serve.py | 144 ++++++++++++++++++++++++
4 files changed, 454 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/8a1d98a3/examples/imagenet/densenet/README.md
----------------------------------------------------------------------
diff --git a/examples/imagenet/densenet/README.md b/examples/imagenet/densenet/README.md
new file mode 100644
index 0000000..390672f
--- /dev/null
+++ b/examples/imagenet/densenet/README.md
@@ -0,0 +1,50 @@
+---
+name: DenseNet models on ImageNet
+SINGA version: 1.1.1
+SINGA commit:
+license: https://github.com/pytorch/vision/blob/master/torchvision/models/densenet.py
+---
+
+# Image Classification using DenseNet
+
+
+In this example, we convert DenseNet on [PyTorch](https://github.com/pytorch/vision/blob/master/torchvision/models/densenet.py)
+to SINGA for image classification.
+
+## Instructions
+
+* Download one parameter checkpoint file (see below) and the synset word file of ImageNet into this folder, e.g.,
+
+ $ wget https://s3-ap-southeast-1.amazonaws.com/dlfile/densenet/densenet-121.tar.gz
+ $ wget https://s3-ap-southeast-1.amazonaws.com/dlfile/resnet/synset_words.txt
+ $ tar xvf densenet-121.tar.gz
+
+* Usage
+
+ $ python serve.py -h
+
+* Example
+
+ # use cpu
+ $ python serve.py --use_cpu --parameter_file densenet-121.pickle --depth 121 &
+ # use gpu
+ $ python serve.py --parameter_file densenet-121.pickle --depth 121 &
+
+ The parameter files for the following model and depth configuration pairs are provided:
+ [121](https://s3-ap-southeast-1.amazonaws.com/dlfile/densenet/densenet-121.tar.gz), [169](https://s3-ap-southeast-1.amazonaws.com/dlfile/densenet/densenet-169.tar.gz), [201](https://s3-ap-southeast-1.amazonaws.com/dlfile/densenet/densenet-201.tar.gz), [161](https://s3-ap-southeast-1.amazonaws.com/dlfile/densenet/densenet-161.tar.gz)
+
+* Submit images for classification
+
+ $ curl -i -F image=@image1.jpg http://localhost:9999/api
+ $ curl -i -F image=@image2.jpg http://localhost:9999/api
+ $ curl -i -F image=@image3.jpg http://localhost:9999/api
+
+image1.jpg, image2.jpg and image3.jpg should be downloaded before executing the above commands.
+
+## Details
+
+The parameter files were converted from the pytorch via the convert.py program.
+
+Usage:
+
+ $ python convert.py -h
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/8a1d98a3/examples/imagenet/densenet/convert.py
----------------------------------------------------------------------
diff --git a/examples/imagenet/densenet/convert.py b/examples/imagenet/densenet/convert.py
new file mode 100644
index 0000000..0e0522d
--- /dev/null
+++ b/examples/imagenet/densenet/convert.py
@@ -0,0 +1,90 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+'''Extract the net parameters from the pytorch file and store them
+as python dict using cPickle. Must install pytorch.
+'''
+
+import torch.utils.model_zoo as model_zoo
+
+import numpy as np
+from argparse import ArgumentParser
+
+import model
+
+try:
+ import cPickle as pickle
+except ModuleNotFoundError:
+ import pickle
+
+URL_PREFIX = 'https://download.pytorch.org/models/'
+model_urls = {
+ 'densenet121': URL_PREFIX + 'densenet121-a639ec97.pth',
+ 'densenet169': URL_PREFIX + 'densenet169-b2777c0a.pth',
+ 'densenet201': URL_PREFIX + 'densenet201-c1103571.pth',
+ 'densenet161': URL_PREFIX + 'densenet161-8d451a50.pth',
+}
+
+
+def rename(pname):
+ p1 = pname.find('/')
+ p2 = pname.rfind('/')
+ assert p1 != -1 and p2 != -1, 'param name = %s is not correct' % pname
+ if 'gamma' in pname:
+ suffix = 'weight'
+ elif 'beta' in pname:
+ suffix = 'bias'
+ elif 'mean' in pname:
+ suffix = 'running_mean'
+ elif 'var' in pname:
+ suffix = 'running_var'
+ else:
+ suffix = pname[p2 + 1:]
+ return pname[p1+1:p2] + '.' + suffix
+
+
+if __name__ == '__main__':
+ parser = ArgumentParser(description='Convert params from torch to python'
+ 'dict. ')
+ parser.add_argument("depth", type=int, choices=[121, 169, 201, 161])
+ parser.add_argument("outfile")
+ parser.add_argument('nb_classes', default=1000, type=int)
+
+ args = parser.parse_args()
+
+ net = model.create_net(args.depth, args.nb_classes)
+ url = 'densenet%d' % args.depth
+ torch_dict = model_zoo.load_url(model_urls[url])
+ params = {'SINGA_VERSION': 1101}
+
+ # resolve dict keys name mismatch problem
+ print(len(net.param_names()), len(torch_dict.keys()))
+ for pname, pval, torch_name in\
+ zip(net.param_names(), net.param_values(), torch_dict.keys()):
+ #torch_name = rename(pname)
+ ary = torch_dict[torch_name].numpy()
+ ary = np.array(ary, dtype=np.float32)
+ if len(ary.shape) == 4:
+ params[pname] = np.reshape(ary, (ary.shape[0], -1))
+ else:
+ params[pname] = np.transpose(ary)
+ #pdb.set_trace()
+ assert pval.shape == params[pname].shape, 'shape mismatch for {0}, \
+ expected {1} in torch model, got {2} in singa model'.\
+ format(pname, params[pname].shape, pval.shape)
+
+ with open(args.outfile, 'wb') as fd:
+ pickle.dump(params, fd)
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/8a1d98a3/examples/imagenet/densenet/model.py
----------------------------------------------------------------------
diff --git a/examples/imagenet/densenet/model.py b/examples/imagenet/densenet/model.py
new file mode 100644
index 0000000..6ffaf00
--- /dev/null
+++ b/examples/imagenet/densenet/model.py
@@ -0,0 +1,170 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# =============================================================================
+''' This models are created following
+https://arxiv.org/pdf/1608.06993.pdf and
+https://github.com/pytorch/vision/blob/master/torchvision/models/densenet.py
+'''
+from singa import initializer
+from singa import layer
+from singa import net as ffnet
+from singa import loss
+from singa import metric
+from singa.layer import Conv2D, Activation, MaxPooling2D,\
+ AvgPooling2D, Split, Concat, Flatten, BatchNormalization
+
+import math
+import sys
+
+ffnet.verbose = True
+
+conv_bias = False
+
+
+def add_dense_connected_layers(name, net, growth_rate):
+ net.add(BatchNormalization('%s/bn1' % name))
+ net.add(Activation('%s/relu1' % name))
+ net.add(Conv2D('%s/conv1' % name, 4 * growth_rate, 1, 1,
+ pad=0, use_bias=conv_bias))
+ net.add(BatchNormalization('%s/bn2' % name))
+ net.add(Activation('%s/relu2' % name))
+ return net.add(Conv2D('%s/conv2' % name, growth_rate, 3, 1,
+ pad=1, use_bias=conv_bias))
+
+
+def add_layer(name, net, growth_rate):
+ split = net.add(Split('%s/split' % name, 2))
+ dense = add_dense_connected_layers(name, net, growth_rate)
+ net.add(Concat('%s/concat' % name, 1), [split, dense])
+
+
+def add_transition(name, net, n_channels, last=False):
+ net.add(BatchNormalization('%s/norm' % name))
+ lyr = net.add(Activation('%s/relu' % name))
+ if last:
+ net.add(AvgPooling2D('%s/pool' % name,
+ lyr.get_output_sample_shape()[1:3], pad=0))
+ net.add(Flatten('flat'))
+ else:
+ net.add(Conv2D('%s/conv' % name, n_channels, 1, 1,
+ pad=0, use_bias=conv_bias))
+ net.add(AvgPooling2D('%s/pool' % name, 2, 2, pad=0))
+
+
+def add_block(name, net, n_channels, N, growth_rate):
+ for i in range(N):
+ add_layer('%s/%d' % (name, i), net, growth_rate)
+ n_channels += growth_rate
+ return n_channels
+
+
+def densenet_base(depth, growth_rate=32, reduction=0.5):
+ '''
+ rewrite according to pytorch models
+ special case of densenet 161
+ '''
+ if depth == 121:
+ stages = [6, 12, 24, 16]
+ elif depth == 169:
+ stages = [6, 12, 32, 32]
+ elif depth == 201:
+ stages = [6, 12, 48, 32]
+ elif depth == 161:
+ stages = [6, 12, 36, 24]
+ else:
+ print('unknown depth: %d' % depth)
+ sys.exit(-1)
+
+ net = ffnet.FeedForwardNet()
+ growth_rate = 48 if depth == 161 else 32
+ n_channels = 2 * growth_rate
+
+ net.add(Conv2D('input/conv', n_channels, 7, 2, pad=3,
+ use_bias=conv_bias, input_sample_shape=(3, 224, 224)))
+ net.add(BatchNormalization('input/bn'))
+ net.add(Activation('input/relu'))
+ net.add(MaxPooling2D('input/pool', 3, 2, pad=1))
+
+ # Dense-Block 1 and transition (56x56)
+ n_channels = add_block('block1', net, n_channels, stages[0], growth_rate)
+ add_transition('trans1', net, int(math.floor(n_channels*reduction)))
+ n_channels = math.floor(n_channels*reduction)
+
+ # Dense-Block 2 and transition (28x28)
+ n_channels = add_block('block2', net, n_channels, stages[1], growth_rate)
+ add_transition('trans2', net, int(math.floor(n_channels*reduction)))
+ n_channels = math.floor(n_channels*reduction)
+
+ # Dense-Block 3 and transition (14x14)
+ n_channels = add_block('block3', net, n_channels, stages[2], growth_rate)
+ add_transition('trans3', net, int(math.floor(n_channels*reduction)))
+ n_channels = math.floor(n_channels*reduction)
+
+ # Dense-Block 4 and transition (7x7)
+ n_channels = add_block('block4', net, n_channels, stages[3], growth_rate)
+ add_transition('trans4', net, n_channels, True)
+
+ return net
+
+
+def init_params(net, weight_path=None, is_train=False):
+ '''Init parameters randomly or from checkpoint file.
+
+ Args:
+ net, a constructed neural net
+ weight_path, checkpoint file path
+ is_train, if false, then a checkpoint file must be presented
+ '''
+ assert is_train is True or weight_path is not None, \
+ 'must provide a checkpoint file for serving'
+
+ if weight_path is None:
+ for pname, pval in zip(net.param_names(), net.param_values()):
+ if 'conv' in pname and len(pval.shape) > 1:
+ initializer.gaussian(pval, 0, pval.shape[1])
+ elif 'dense' in pname:
+ if len(pval.shape) > 1:
+ initializer.gaussian(pval, 0, pval.shape[0])
+ else:
+ pval.set_value(0)
+ # init params from batch norm layer
+ elif 'mean' in pname or 'beta' in pname:
+ pval.set_value(0)
+ elif 'var' in pname:
+ pval.set_value(1)
+ elif 'gamma' in pname:
+ initializer.uniform(pval, 0, 1)
+ else:
+ net.load(weight_path, use_pickle=True)
+
+
+def create_net(depth, nb_classes, dense=0, use_cpu=True):
+ if use_cpu:
+ layer.engine = 'singacpp'
+
+ net = densenet_base(depth)
+
+ # this part was not included in the pytorch model
+ if dense > 0:
+ net.add(layer.Dense('hidden-dense', dense))
+ net.add(layer.Activation('act-dense'))
+ net.add(layer.Dropout('dropout'))
+
+ net.add(layer.Dense('sigmoid', nb_classes))
+ return net
+
+if __name__ == '__main__':
+ create_net(121, 1000)
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-singa/blob/8a1d98a3/examples/imagenet/densenet/serve.py
----------------------------------------------------------------------
diff --git a/examples/imagenet/densenet/serve.py b/examples/imagenet/densenet/serve.py
new file mode 100644
index 0000000..2e401b9
--- /dev/null
+++ b/examples/imagenet/densenet/serve.py
@@ -0,0 +1,144 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import sys
+import time
+import numpy as np
+import traceback
+from argparse import ArgumentParser
+from scipy.misc import imread
+
+from singa import device
+from singa import tensor
+from singa import image_tool
+
+from rafiki.agent import Agent, MsgType
+import model
+
+tool = image_tool.ImageTool()
+num_augmentation = 1
+crop_size = 224
+mean = np.array([0.485, 0.456, 0.406])
+std = np.array([0.229, 0.224, 0.225])
+
+
+def allowed_file(filename):
+ return '.' in filename and filename.rsplit('.', 1)[1] in \
+ ["PNG", "png", "jpg", "JPG", "JPEG", "jpeg"]
+
+
+def serve(net, label_map, dev, agent, topk=5):
+ '''Serve to predict image labels.
+ It prints the topk food names for each image.
+ Args:
+ label_map: a list of food names, corresponding to the index in meta_file
+ '''
+
+ images = tensor.Tensor((num_augmentation, 3, crop_size, crop_size), dev)
+ while True:
+ msg, val = agent.pull()
+ if msg is None:
+ time.sleep(0.1)
+ continue
+ msg = MsgType.parse(msg)
+ if msg.is_request():
+ try:
+ # process images
+ img = imread(val['image'], mode='RGB').astype(np.float32) / 255
+ height,width = img.shape[:2]
+ img -= mean
+ img /= std
+ img = img.transpose((2, 0, 1))
+ img = img[:, (height-224)//2:(height+224)//2,
+ (width-224)//2:(width+224)//2]
+ images.copy_from_numpy(img)
+ print("input: ", images.l1())
+ # do prediction
+ y = net.predict(images)
+ prob = np.average(tensor.to_numpy(y), 0)
+ idx = np.argsort(-prob)
+ # prepare results
+ response = ""
+ for i in range(topk):
+ response += "%s:%f <br/>" % (label_map[idx[i]],
+ prob[idx[i]])
+ except:
+ traceback.print_exc()
+ response = "sorry, system error during prediction."
+ agent.push(MsgType.kResponse, response)
+ elif msg.is_command():
+ if MsgType.kCommandStop.equal(msg):
+ print('get stop command')
+ agent.push(MsgType.kStatus, "success")
+ break
+ else:
+ print('get unsupported command %s' % str(msg))
+ agent.push(MsgType.kStatus, "Unknown command")
+ else:
+ print('get unsupported message %s' % str(msg))
+ agent.push(MsgType.kStatus, "unsupported msg; going to shutdown")
+ break
+ print("server stop")
+
+
+def main():
+ try:
+ # Setup argument parser
+ parser = ArgumentParser(description='DenseNet inference')
+
+ parser.add_argument("--port", default=9999, help="listen port")
+ parser.add_argument("--use_cpu", action="store_true",
+ help="If set, load models onto CPU devices")
+ parser.add_argument("--parameter_file", default="densenet-121.pickle")
+ parser.add_argument("--depth", type=int, choices=[121, 169, 201, 161],
+ default=121)
+
+ parser.add_argument('--nb_classes', default=1000, type=int)
+
+ # Process arguments
+ args = parser.parse_args()
+ port = args.port
+
+ # start to train
+ agent = Agent(port)
+
+ net = model.create_net(args.depth, args.nb_classes, 0, args.use_cpu)
+ if args.use_cpu:
+ print('Using CPU')
+ dev = device.get_default_device()
+ else:
+ print('Using GPU')
+ dev = device.create_cuda_gpu()
+ net.to_device(dev)
+ print('start to load parameter_file')
+ model.init_params(net, args.parameter_file)
+ print('Finish loading models')
+
+ labels = np.loadtxt('synset_words.txt', str, delimiter='\t ')
+ serve(net, labels, dev, agent)
+ # wait the agent finish handling http request
+ agent.stop()
+
+ except SystemExit:
+ return
+ except:
+ traceback.print_exc()
+ sys.stderr.write(" for help use --help \n\n")
+ return 2
+
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file