TinyMS SSD300 Tutorial¶
In this tutorial, using TinyMS API to train/serve an SSD300 model will be demonstrated.
Prerequisite¶
Ubuntu:
18.04
Python:
3.7.x
Flask:
1.1.2
MindSpore:
CPU-1.1.1
TinyMS:
0.1.0
numpy:
1.17.5
Pillow:
8.1.0
pip:
21.0.1
requests:
2.18.4
Introduction¶
TinyMS is a high-level API which is designed for amateur of deep learning. It minimizes the number of actions of users required to construct, train, evaluate and serve a model. TinyMS also provides tutorials and documentations for developers.
This tutorial consists of six parts, constructing the model
, downloading dataset
, training
, define servable json
, starting server
and making predictions
in which the server will be run in a sub process.
[1]:
import os
import json
import time
import tinyms as ts
import xml.etree.ElementTree as et
from PIL import Image
from tinyms import context, layers, primitives as P, Tensor
from tinyms.serving import start_server, predict, list_servables, shutdown, server_started
from tinyms.data import VOCDataset, download_dataset
from tinyms.vision import voc_transform, coco_eval, ImageViewer
from tinyms.model import Model, ssd300_mobilenetv2, ssd300_infer
from tinyms.losses import net_with_loss
from tinyms.optimizers import Momentum
from tinyms.callbacks import ModelCheckpoint, CheckpointConfig, LossMonitor, TimeMonitor
from tinyms.utils.train.lr_generator import mobilenetv2_lr as ssd300_lr
from tinyms.initializers import initializer, TruncatedNormal
[WARNING] ME(14174:140126738016064,MainProcess):2021-03-19-15:33:42.136.028 [mindspore/ops/operations/array_ops.py:2302] WARN_DEPRECATED: The usage of Pack is deprecated. Please use Stack.
WARNING: 'ControlDepend' is deprecated from version 1.1 and will be removed in a future version, use 'Depend' instead.
1. Construct the model¶
[2]:
# build network
net = ssd300_mobilenetv2(class_num=21)
2. Download dataset¶
The VOC dataset will be downloaded if voc
folder didn’t exist at the root. If voc
folder already exists, this step will not be performed.
[3]:
# download the dataset
voc_path = '/root/voc'
if not os.path.exists(voc_path):
download_dataset('voc', '/root')
print('************Download complete*************')
else:
print('************Dataset already exists.**************')
************** Downloading the VOC2007 dataset **************
[████████████████████████████████████████████████████████████████████████████████████████████████████] 100.00%
============== /root/voc/VOCtrainval_06-Nov-2007.tar is ready ==============
************Download complete*************
3. Train the model & evaluation¶
The dataset for both training and evaluation will be defined here, and the parameters for training also set in this block. A trained ckpt file will be saved to /etc/tinyms/serving/ssd300
folder for later use, meanwhile the evaluation will be performed and the Accuracy
can be checked
Notice: Since training SSD300 on CPU is time consuming, we recommend skip training and using provided ckpt files to run.
[ ]:
class TrainingWrapper(layers.Layer):
"""
Encapsulation class of SSD300 network training.
Append an optimizer to the training network after that the construct
function can be called to create the backward graph.
Args:
network (Layer): The training network. Note that loss function should have been added.
optimizer (Optimizer): Optimizer for updating the weights.
sens (float): The adjust parameter. Default: 1.0.
"""
def __init__(self, network, optimizer, sens=1.0):
super(TrainingWrapper, self).__init__(auto_prefix=False)
self.network = network
self.network.set_grad()
self.weights = ts.ParameterTuple(network.trainable_params())
self.optimizer = optimizer
self.grad = P.GradOperation(get_by_list=True, sens_param=True)
self.sens = sens
self.hyper_map = P.HyperMap()
def construct(self, *args):
weights = self.weights
loss = self.network(*args)
sens = P.Fill()(P.DType()(loss), P.Shape()(loss), self.sens)
grads = self.grad(self.network, weights)(*args, sens)
return P.depend(loss, self.optimizer(grads))
def create_voc_label(voc_dir, voc_cls, usage='val'):
"""Get image path and annotation from VOC."""
if not os.path.isdir(voc_dir):
raise ValueError(f'Cannot find {voc_dir} dataset path.')
anno_dir = voc_dir
if os.path.isdir(os.path.join(voc_dir, 'Annotations')):
anno_dir = os.path.join(voc_dir, 'Annotations')
cls_map = {name: i for i, name in enumerate(voc_cls)}
# Fetch the specific xml files path
xml_files = []
with open(os.path.join(voc_dir, 'ImageSets', 'Main', usage+'.txt'), 'r') as f:
for line in f:
xml_files.append(line.strip('\n')+'.xml')
json_dict = {"images": [], "type": "instances", "annotations": [],
"categories": []}
bnd_id = 1
for xml_file in xml_files:
img_id = xml_files.index(xml_file)
tree = et.parse(os.path.join(anno_dir, xml_file))
root_node = tree.getroot()
file_name = root_node.find('filename').text
for obj in root_node.iter('object'):
cls_name = obj.find('name').text
if cls_name not in cls_map:
print(f'Label "{cls_name}" not in "{cls_map}"')
continue
bnd_box = obj.find('bndbox')
x_min = int(float(bnd_box.find('xmin').text)) - 1
y_min = int(float(bnd_box.find('ymin').text)) - 1
x_max = int(float(bnd_box.find('xmax').text)) - 1
y_max = int(float(bnd_box.find('ymax').text)) - 1
o_width = abs(x_max - x_min)
o_height = abs(y_max - y_min)
ann = {'area': o_width * o_height, 'iscrowd': 0,
'image_id': img_id,
'bbox': [x_min, y_min, o_width, o_height],
'category_id': cls_map[cls_name], 'id': bnd_id,
'ignore': 0,
'segmentation': []}
json_dict['annotations'].append(ann)
bnd_id = bnd_id + 1
size = root_node.find("size")
width = int(size.find('width').text)
height = int(size.find('height').text)
image = {'file_name': file_name, 'height': height, 'width': width,
'id': img_id}
json_dict['images'].append(image)
for cls_name, cid in cls_map.items():
cat = {'supercategory': 'none', 'id': cid, 'name': cls_name}
json_dict['categories'].append(cat)
anno_file = os.path.join(anno_dir, 'annotation.json')
with open(anno_file, 'w') as f:
json.dump(json_dict, f)
return anno_file
# check ckpt folder exists or not
ckpt_folder = '/etc/tinyms/serving/ssd300'
ckpt_path = '/etc/tinyms/serving/ssd300/ssd300.ckpt'
if not os.path.exists(ckpt_folder):
!mkdir -p /etc/tinyms/serving/ssd300
else:
print('ssd300 ckpt folder already exists')
# set parameters
epoch_size = 800 # default is 800
batch_size = 32
voc_path = '/root/voc/VOCdevkit/VOC2007'
# set environment parameters
context.set_context(mode=context.GRAPH_MODE, device_target="CPU")
dataset_sink_mode = False
# set dataset parameters
train_dataset = VOCDataset(voc_path, task='Detection', usage='trainval', num_parallel_workers=4, shuffle=True, decode=True)
train_dataset = voc_transform.apply_ds(train_dataset, repeat_size=1, batch_size=batch_size, num_parallel_workers=4, is_training=True)
eval_dataset = VOCDataset(voc_path, task='Detection', usage='val', num_parallel_workers=4, shuffle=True, decode=True)
eval_dataset = voc_transform.apply_ds(eval_dataset, repeat_size=1, batch_size=batch_size, num_parallel_workers=4, is_training=False)
dataset_size = train_dataset.get_dataset_size()
total = eval_dataset.get_dataset_size()
# define the loss function
net = net_with_loss(net)
params = net.trainable_params()
for p in params:
if 'beta' not in p.name and 'gamma' not in p.name and 'bias' not in p.name:
p.set_data(initializer(TruncatedNormal(0.02), p.data.shape, p.data.dtype))
# define the optimizer
pre_trained_epoch_size = 0
save_checkpoint_epochs = 10
lr = 0.01
lr = ssd300_lr(global_step=pre_trained_epoch_size * dataset_size,
lr_init=0.001, lr_end=0.001 * lr, lr_max=lr,
warmup_epochs=2, total_epochs=epoch_size,
steps_per_epoch=dataset_size)
loss_scale = 1.0
opt = Momentum(filter(lambda x: x.requires_grad, net.get_parameters()), lr,0.9, 1.5e-4, loss_scale)
model = Model(TrainingWrapper(net, opt, loss_scale))
model.compile()
ckpoint_cb = ModelCheckpoint(prefix="ssd300", config=CheckpointConfig(
save_checkpoint_steps=save_checkpoint_epochs * dataset_size,
keep_checkpoint_max=10))
print('************************Start training*************************')
model.train(epoch_size, train_dataset, callbacks=[ckpoint_cb, LossMonitor(), TimeMonitor(data_size=dataset_size)],
dataset_sink_mode=dataset_sink_mode)
model.save_checkpoint(ckpt_path)
print('************************Finished training*************************')
eval_net = ssd300_infer(class_num=21)
model = Model(eval_net)
model.load_checkpoint(ckpt_path)
# perform the model predict operation
print("\n========================================\n")
print("total images num: ", total)
print("Processing, please wait a moment...")
start = time.time()
pred_data = []
id_iter = 0
for data in eval_dataset.create_dict_iterator(output_numpy=True):
image_np = data['image']
image_shape = data['image_shape']
output = model.predict(Tensor(image_np))
for batch_idx in range(image_np.shape[0]):
pred_data.append({"boxes": output[0].asnumpy()[batch_idx],
"box_scores": output[1].asnumpy()[batch_idx],
"img_id": id_iter,
"image_shape": image_shape[batch_idx]})
id_iter += 1
cost_time = int((time.time() - start) * 1000)
print(f' 100% [{total}/{total}] cost {cost_time} ms')
# calculate mAP for the predict data
voc_cls = ['background',
'aeroplane', 'bicycle', 'bird', 'boat', 'bottle',
'bus', 'car', 'cat', 'chair', 'cow',
'diningtable', 'dog', 'horse', 'motorbike', 'person',
'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor']
anno_file = create_voc_label(voc_path, voc_cls)
mAP = coco_eval(pred_data, anno_file)
print("\n========================================\n")
print(f"mAP: {mAP}")
Notice: If skipped training process, download the pretrained ckpt file and continue to serving
Click HERE to download the ckpt file and save this file to /etc/tinyms/serving/ssd300/ssd300.ckpt
.
Or run the following code to download and store the ckpt file:
[4]:
ssd300_ckpt_folder = '/etc/tinyms/serving/ssd300'
ssd300_ckpt_path = '/etc/tinyms/serving/ssd300/ssd300.ckpt'
if not os.path.exists(ssd300_ckpt_folder):
!mkdir -p /etc/tinyms/serving/ssd300
!wget -P /etc/tinyms/serving/ssd300 https://ascend-tutorials.obs.cn-north-4.myhuaweicloud.com/ckpt_files/voc/ssd300.ckpt
else:
print('ssd300 ckpt folder already exists')
if not os.path.exists(ssd300_ckpt_path):
!wget -P /etc/tinyms/serving/ssd300 https://ascend-tutorials.obs.cn-north-4.myhuaweicloud.com/ckpt_files/voc/ssd300.ckpt
else:
print('ssd300 ckpt file already exists')
ssd300 ckpt folder already exists
--2021-03-19 15:38:53-- https://ascend-tutorials.obs.cn-north-4.myhuaweicloud.com/ckpt_files/voc/ssd300.ckpt
Resolving ascend-tutorials.obs.cn-north-4.myhuaweicloud.com (ascend-tutorials.obs.cn-north-4.myhuaweicloud.com)... 49.4.112.113, 49.4.112.90, 121.36.121.44, ...
Connecting to ascend-tutorials.obs.cn-north-4.myhuaweicloud.com (ascend-tutorials.obs.cn-north-4.myhuaweicloud.com)|49.4.112.113|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 28056511 (27M) [binary/octet-stream]
Saving to: ‘/etc/tinyms/serving/ssd300/ssd300.ckpt’
ssd300.ckpt 100%[===================>] 26.76M 20.7MB/s in 1.3s
2021-03-19 15:38:55 (20.7 MB/s) - ‘/etc/tinyms/serving/ssd300/ssd300.ckpt’ saved [28056511/28056511]
4. Define servable.json¶
Run this code to define the servable json file for later use:
[5]:
servable_json = [{'name': 'ssd300',
'description': 'This servable hosts an ssd300 model predicting bounding boxes',
'model': {
"name": "ssd300",
"format": "ckpt",
"class_num": 21}}]
os.chdir("/etc/tinyms/serving")
json_data = json.dumps(servable_json, indent=4)
with open('servable.json', 'w') as json_file:
json_file.write(json_data)
5. Start server¶
5.1 Introduction¶
TinyMS Serving is a C/S(client/server) structure. There is a server and client. TinyMS using Flask which is a micro web framework written in python as the C/S communication tool. In order to serve a model, user must start server first. If successfully started, the server will be run in a subprocess and listening to POST requests from 127.0.0.1 port 5000 sent by client and handle the requests using MindSpore backend which will construct the model, run the prediction and send the result back to the client.
5.2 start server¶
Run the following code block to start the server:
[6]:
start_server()
Server starts at host 127.0.0.1, port 5000
6. Make predictions¶
6.1 Upload the pic¶
A picture is required to be the input. In this tutorial, the SSD300 ckpt requires a picture containing objects of
['background', 'aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor']
Click HERE to download the picture which is used in this tutorial. Upload the pic, if using terminal, either scp
or wget
will do, if running in Jupyter, click Upload
button at the top right and select the picture. Save the picture at the root folder, rename to ssd300_test.jpeg
(or any name you want).
Or run the following code to download the picture used in this tutorial:
[7]:
# download the test pic
if not os.path.exists('/root/ssd300_test.jpeg'):
!wget -P /root/ https://ascend-tutorials.obs.cn-north-4.myhuaweicloud.com/tinyms-test-pics/ssd300_test/ssd300_test.jpeg
else:
print('ssd300_test.jpeg already exists')
--2021-03-19 15:38:59-- https://ascend-tutorials.obs.cn-north-4.myhuaweicloud.com/tinyms-test-pics/ssd300_test/ssd300_test.jpeg
Resolving ascend-tutorials.obs.cn-north-4.myhuaweicloud.com (ascend-tutorials.obs.cn-north-4.myhuaweicloud.com)... 49.4.112.113, 49.4.112.90, 121.36.121.44, ...
Connecting to ascend-tutorials.obs.cn-north-4.myhuaweicloud.com (ascend-tutorials.obs.cn-north-4.myhuaweicloud.com)|49.4.112.113|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 70412 (69K) [image/jpeg]
Saving to: ‘/root/ssd300_test.jpeg’
ssd300_test.jpeg 100%[===================>] 68.76K 338KB/s in 0.2s
2021-03-19 15:39:00 (338 KB/s) - ‘/root/ssd300_test.jpeg’ saved [70412/70412]
6.2 List servables¶
Now, use list_servables
function to check what model is servable right now.
[8]:
list_servables()
[8]:
[{'description': 'This servable hosts an ssd300 model predicting bounding boxes',
'model': {'class_num': 21, 'format': 'ckpt', 'name': 'ssd300'},
'name': 'ssd300'}]
If the output description shows it is an ssd300
model, run the following code to predict the bounding boxes
6.3 Sending request and get the result¶
Run predict
function and get the result, right now only TOP1CLASS
strategy is supported for SSD300. Call ImageViewer.draw
to draw the bounding boxes
[9]:
# set image path and output strategy(only TOP1_CLASS)
image_path = "/root/ssd300_test.jpeg"
strategy = "TOP1_CLASS"
labels = ['background',
'aeroplane', 'bicycle', 'bird', 'boat', 'bottle',
'bus', 'car', 'cat', 'chair', 'cow',
'diningtable', 'dog', 'horse', 'motorbike', 'person',
'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor']
# predict(image_path, servable_name, dataset_name, strategy)
# ImageViewer(img, title)
# ImageViewer.draw(predict_result, labels)
if server_started() is True:
res = predict(image_path, 'ssd300', 'voc', strategy)
img_viewer = ImageViewer(Image.open(image_path))
img_viewer.draw(res, labels)
else:
print("Server not started")
Check output¶
If the input picture is shown with bounding boxes labeled with object classes and score, that means the prediction is successfully performed.
Shutdown server¶
[10]:
shutdown()
[10]:
'Server shutting down...'