blob: 809e6cbc5a21fe8c954929e810eeb0206ba996cd [file] [log] [blame]
# 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 model is created following Caffe implementation of GoogleNet
https://github.com/BVLC/caffe/blob/master/models/bvlc_googlenet/
'''
from __future__ import print_function
from builtins import zip
from builtins import str
import os
import sys
import time
import numpy as np
import threading
import traceback
from argparse import ArgumentParser
from scipy.misc import imread, imresize
import numpy as np
from singa.layer import Layer, Conv2D, Activation, MaxPooling2D, AvgPooling2D,\
Split, Concat, LRN, Dropout, Flatten, Dense
from singa import layer
from singa import net as ffnet
from singa import device
from singa import tensor
from rafiki.agent import Agent, MsgType
def add_to_tuple(x):
'''return a tuple with the last two values incremented by 1'''
if len(x) == 3:
return (x[0], x[1] + 1, x[2] + 1)
else:
return (x[0], x[1], x[2] + 1, x[3] + 1)
class EndPadding(Layer):
'''Pad the end of the spatial axis with 1 row and 1 column of zeros.
This layer is inserted before the pooling layers outside the inception
block. We need such a layer because Caffe (ceil) and cuDNN (floor) have
different rounding strategies for the pooling layer.
http://joelouismarino.github.io/blog_posts/blog_googlenet_keras.html
'''
def __init__(self, name, input_sample_shape=None):
super(EndPadding, self).__init__(name)
if input_sample_shape is not None:
assert len(input_sample_shape) == 3, 'input must has 4 dims'
self.output_sample_shape = add_to_tuple(input_sample_shape)
def get_output_sample_shape(self):
return self.output_sample_shape
def setup(self, input_sample_shape):
assert len(input_sample_shape) == 3, 'input must has 4 dims'
self.output_sample_shape = add_to_tuple(input_sample_shape)
self.has_setup = True
def forward(self, flag, x):
'''pad zeros'''
tmp = tensor.to_numpy(x)
shape = add_to_tuple(x.shape)
ret = np.zeros(shape)
ret[:,:,:-1, :-1] = tmp
y = tensor.from_numpy(ret)
y.to_device(x.device)
return y
def backward(self, falg, dy):
'''remove paddings'''
tmp = tensor.to_numpy(dy)
dx = tensor.from_numpy(tmp[:,:,:-1,:-1])
dx.to_device(dy.device)
return dx, []
# b_specs = {'init': 'constant', 'value': 0, 'lr_mult': 2, 'decay_mult': 0}
def conv(net, src, name, num, kernel, stride=1, pad=0, suffix=''):
net.add(Conv2D('%s/%s' % (name, suffix), num, kernel, stride, pad=pad), src)
return net.add(Activation('%s/relue_%s' % (name, suffix)))
def pool(net, src, name, kernel, stride):
net.add(EndPadding('%s/pad' % name), src)
ret = net.add(MaxPooling2D('%s' % name, 3, 2, pad=0))
return ret
def inception(net, src, name, nb1x1, nb3x3r, nb3x3, nb5x5r, nb5x5, nbproj):
split = net.add(Split('%s/split' % name, 4), src)
c1x1 = conv(net, split, name, nb1x1, 1, suffix='1x1')
c3x3r = conv(net, split, name, nb3x3r, 1, suffix='3x3_reduce')
c3x3 = conv(net, c3x3r, name, nb3x3, 3, pad=1, suffix='3x3')
c5x5r = conv(net, split, name, nb5x5r, 1, suffix='5x5_reduce')
c5x5 = conv(net, c5x5r, name, nb5x5, 5, pad=2, suffix='5x5')
pool = net.add(MaxPooling2D('%s/pool' % name, 3, 1, pad=1), split)
cproj = conv(net, pool, name, nbproj, 1, suffix='pool_proj')
return net.add(Concat('%s/output' % name, 1), [c1x1, c3x3, c5x5, cproj])
def create_net(shape, weight_path='bvlc_googlenet.pickle'):
net = ffnet.FeedForwardNet()
net.add(Conv2D('conv1/7x7_s2', 64, 7, 2, pad=3, input_sample_shape=shape))
c1 = net.add(Activation('conv1/relu_7x7'))
pool1 = pool(net, c1, 'pool1/3x3_s2', 3, 2)
norm1 = net.add(LRN('pool1/norm1', 5, 0.0001, 0.75))
c3x3r = conv(net, norm1 , 'conv2', 64, 1, suffix='3x3_reduce')
c3x3 = conv(net, c3x3r, 'conv2', 192, 3, pad=1, suffix='3x3')
norm2 = net.add(LRN('conv2/norm2', 5, 0.0001, 0.75))
pool2 = pool(net, norm2, 'pool2/3x3_s2', 3, 2)
i3a=inception(net, pool2, 'inception_3a', 64, 96, 128, 16, 32, 32)
i3b=inception(net, i3a, 'inception_3b', 128, 128, 192, 32, 96, 64)
pool3=pool(net, i3b, 'pool3/3x3_s2', 3, 2)
i4a=inception(net, pool3, 'inception_4a', 192, 96, 208, 16, 48, 64)
i4b=inception(net, i4a, 'inception_4b', 160, 112, 224, 24, 64, 64)
i4c=inception(net, i4b, 'inception_4c', 128, 128, 256, 24, 64, 64)
i4d=inception(net, i4c, 'inception_4d', 112, 144, 288, 32, 64, 64)
i4e=inception(net, i4d, 'inception_4e', 256, 160, 320, 32, 128, 128)
pool4=pool(net, i4e,'pool4/3x3_s2', 3, 2)
i5a=inception(net, pool4, 'inception_5a', 256, 160, 320, 32, 128, 128)
i5b=inception(net, i5a, 'inception_5b', 384, 192, 384, 48, 128, 128)
pool5=net.add(AvgPooling2D('pool5/7x7_s1', 7, 1, pad=0))
drop5=net.add(Dropout('drop', 0.4))
flat=net.add(Flatten('flat'))
dense=net.add(Dense('loss3/classifier', 1000))
# prob=net.add(Softmax('softmax'))
net.load(weight_path, use_pickle=True)
print('total num of params %d' % (len(net.param_names())))
# SINGA and Caffe have different layout for the weight matrix of the dense
# layer
for key, val in zip(net.param_names(), net.param_values()):
# print key
if key == 'loss3/classifier_weight' or key == 'loss3/classifier/weight':
tmp = tensor.to_numpy(val)
tmp = tmp.reshape(tmp.shape[::-1])
val.copy_from_numpy(np.transpose(tmp))
return net
def serve(agent, use_cpu, parameter_file, topk=5):
if use_cpu:
print('running with cpu')
dev = device.get_default_device()
layer.engine = 'singacpp'
else:
print("runing with gpu")
dev = device.create_cuda_gpu()
agent = agent
print('Start intialization............')
net = create_net((3, 224, 224), parameter_file)
net.to_device(dev)
print('End intialization............')
labels = np.loadtxt('synset_words.txt', str, delimiter='\t ')
while True:
key, val = agent.pull()
if key is None:
time.sleep(0.1)
continue
msg_type = MsgType.parse(key)
if msg_type.is_request():
try:
response = ""
img = imread(val['image'], mode='RGB').astype(np.float32)
height,width = img.shape[:2]
img[:, :, 0] -= 123.68
img[:, :, 1] -= 116.779
img[:, :, 2] -= 103.939
img[:,:,[0,1,2]] = img[:,:,[2,1,0]]
img = img.transpose((2, 0, 1))
img = img[:, (height-224)//2:(height+224)//2,\
(width-224)//2:(width+224)//2]
images = np.expand_dims(img, axis=0)
x = tensor.from_numpy(images.astype(np.float32))
x.to_device(dev)
y = net.predict(x)
prob = np.average(tensor.to_numpy(y), 0)
# sort and reverse
idx = np.argsort(-prob)[0:topk]
for i in idx:
response += "%s:%s<br/>" % (labels[i], prob[i])
except:
traceback.print_exc()
response = "Sorry, system error during prediction."
agent.push(MsgType.kResponse, response)
elif MsgType.kCommandStop.equal(msg_type):
print('get stop command')
agent.push(MsgType.kStatus, "success")
break
else:
print('get unsupported message %s' % str(msg_type))
agent.push(MsgType.kStatus, "Unknown command")
break
# while loop
print("server stop")
def main():
try:
# Setup argument parser
parser = ArgumentParser(description="GoogleNet for image classification")
parser.add_argument("-p", "--port", default=9999, help="listen port")
parser.add_argument("-C", "--use_cpu", action="store_true")
parser.add_argument("--parameter_file", default="bvlc_googlenet.pickle",
help="relative path")
# Process arguments
args = parser.parse_args()
port = args.port
# start to train
agent = Agent(port)
serve(agent, args.use_cpu, args.parameter_file)
agent.stop()
except SystemExit:
return
except:
traceback.print_exc()
sys.stderr.write(" for help use --help \n\n")
return 2
if __name__ == '__main__':
main()