From d0dd3292ecf50b09760d4a42d2ff18cf9582bceb Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 27 Mar 2020 14:38:27 -0400 Subject: [PATCH] move to tf1.15 --- trainer/.dockerignore | 8 ++ trainer/Dockerfile | 88 ++++++++++++++ trainer/README.md | 25 ++++ trainer/buildandrun.sh | 7 ++ trainer/runner.sh | 21 ++++ trainer/src/convert/convert.py | 48 +++++--- trainer/src/convert/convert_ssd_helper.py | 111 +++++++++--------- trainer/src/convert/convert_to_core_ml.py | 92 +++++++++++---- trainer/src/convert/convert_to_tfjs.py | 57 ++++++--- trainer/src/convert/convert_to_tflite.py | 11 +- .../src/data/prepare_data_classification.py | 2 - trainer/src/requirements.txt | 8 +- trainer/src/start.sh | 18 ++- 13 files changed, 368 insertions(+), 128 deletions(-) create mode 100644 trainer/.dockerignore create mode 100644 trainer/Dockerfile create mode 100644 trainer/README.md create mode 100755 trainer/buildandrun.sh create mode 100644 trainer/runner.sh mode change 100755 => 100644 trainer/src/start.sh diff --git a/trainer/.dockerignore b/trainer/.dockerignore new file mode 100644 index 0000000..7a8bd90 --- /dev/null +++ b/trainer/.dockerignore @@ -0,0 +1,8 @@ +# Ignore everything. +** + +# Allow `/src` and `Dockerfile`. +!/Dockerfile +!/src/** + +!/runner.sh \ No newline at end of file diff --git a/trainer/Dockerfile b/trainer/Dockerfile new file mode 100644 index 0000000..a2ab1b3 --- /dev/null +++ b/trainer/Dockerfile @@ -0,0 +1,88 @@ +FROM tensorflow/tensorflow:1.15.2-py3 + +RUN apt-get update \ + && apt-get -y install automake autotools-dev fuse g++ git libcurl4-openssl-dev libfuse-dev libssl-dev libxml2-dev make pkg-config \ + && apt-get -y install s3fs \ + && apt-get -y install python-scipy + +RUN pip install \ + absl-py==0.7.1 \ + alabaster==0.7.12 \ + appdirs==1.4.3 \ + astor==0.7.1 \ + Babel==2.8.0 \ + certifi==2019.9.11 \ + chardet==3.0.4 \ + cloudpickle==1.2.2 \ + decorator==4.4.1 \ + docutils==0.16 \ + gast==0.2.2 \ + google-pasta==0.1.6 \ + # graphsurgeon==0.4.1 \ + grpcio==1.16.1 \ + h5py==2.9.0 \ + # hpo==0.3.1 \ + idna==2.9 \ + imagesize==1.2.0 \ + Jinja2==2.11.1 \ + joblib==0.14.1 \ + Keras==2.2.5 \ + Keras-Applications==1.0.8 \ + Keras-Preprocessing==1.1.0 \ + Markdown==3.1.1 \ + MarkupSafe==1.1.1 \ + # mkl-fft==1.0.15 \ + # mkl-random==1.1.0 \ + # mkl-service==2.3.0 \ + mock==4.0.1 \ + nose==1.3.7 \ + numpy==1.16.5 \ + numpydoc==0.9.2 \ + olefile==0.46 \ + packaging==20.1 \ + pandas==1.0.1 \ + Pillow==6.2.1 \ + ply==3.11 \ + protobuf==3.8.0 \ + psutil==5.5.0 \ + Pygments==2.5.2 \ + Pyomo==5.2 \ + pyparsing==2.4.6 \ + python-dateutil==2.8.1 \ + pytz==2019.3 \ + PyUtilib==5.7.3 \ + PyYAML==5.1.2 \ + # rbfopt==0.2 \ + requests==2.23.0 \ + scikit-learn==0.22.2.post1 \ + # scipy==1.3.1 \ + six==1.12.0 \ + sklearn==0.0 \ + snowballstemmer==2.0.0 \ + Sphinx==2.4.3 \ + sphinxcontrib-applehelp==1.0.2 \ + sphinxcontrib-devhelp==1.0.2 \ + sphinxcontrib-htmlhelp==1.0.3 \ + sphinxcontrib-jsmath==1.0.1 \ + sphinxcontrib-qthelp==1.0.3 \ + sphinxcontrib-serializinghtml==1.1.4 \ + # tensorboard==1.15.0 \ + # tensorflow==1.15.0 \ + # tensorflow-estimator==1.15.1 \ + # tensorflow-probability==0.8.0 \ + # tensorflow-serving-api==1.15.0 \ + # tensorrt==6.0.1.5 \ + termcolor==1.1.0 \ + # tflms==2.0.2 \ + # uff==0.6.5 \ + urllib3==1.25.8 \ + Werkzeug==0.15.4 \ + wrapt==1.11.2 + +ADD src / +ADD runner.sh . + +RUN chmod +x start.sh +RUN chmod +x runner.sh + +ENTRYPOINT ["./runner.sh"] \ No newline at end of file diff --git a/trainer/README.md b/trainer/README.md new file mode 100644 index 0000000..29842dc --- /dev/null +++ b/trainer/README.md @@ -0,0 +1,25 @@ +Build the docker image: +``` +docker build -t trainer . +``` + +Run with number of steps: +``` +docker build -t trainer . && docker run -a stdin -a stdout -a stderr -i -t --privileged trainer 10 +``` + +``` +zip -r trainer.zip src +cacli train thumbs-up-down-v2 --steps 500 --script trainer.zip --frameworkv 1.15 +``` + +coremltools==3.3 +tfcoreml==1.1 +tensorflowjs==1.4.0 +``` +python -m convert.convert --tfjs --coreml --tflite \ + --tfjs-path=../model_web \ + --mlmodel-path=../model_ios \ + --tflite-path=../model_android \ + --saved-model-path=../model +``` \ No newline at end of file diff --git a/trainer/buildandrun.sh b/trainer/buildandrun.sh new file mode 100755 index 0000000..dd0e8e6 --- /dev/null +++ b/trainer/buildandrun.sh @@ -0,0 +1,7 @@ +source .env + +BUCKET=$1 +STEPS=$2 + +docker build -t trainer . +docker run -a stdin -a stdout -a stderr -i -t --privileged trainer $S3_ID $S3_KEY $BUCKET $STEPS \ No newline at end of file diff --git a/trainer/runner.sh b/trainer/runner.sh new file mode 100644 index 0000000..0b0227c --- /dev/null +++ b/trainer/runner.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +mkdir data_dir +mkdir result_dir + +S3_ID=$1 +S3_KEY=$2 +BUCKET=$3 +STEPS=$4 + +echo $S3_ID:$S3_KEY > .passwd-s3fs +chmod 600 .passwd-s3fs +s3fs $BUCKET /data_dir -o passwd_file=.passwd-s3fs -o url=https://s3.us.cloud-object-storage.appdomain.cloud/ -o use_path_request_style +s3fs $BUCKET /result_dir -o passwd_file=.passwd-s3fs -o url=https://s3.us.cloud-object-storage.appdomain.cloud/ -o use_path_request_style + +export DATA_DIR=/data_dir +export RESULT_DIR=/result_dir/training-local-$(cat /dev/urandom | tr -cd '\-_a-zA-Z0-9' | head -c 9) + +mkdir $RESULT_DIR + +./start.sh $STEPS \ No newline at end of file diff --git a/trainer/src/convert/convert.py b/trainer/src/convert/convert.py index 81cef49..fcc252d 100644 --- a/trainer/src/convert/convert.py +++ b/trainer/src/convert/convert.py @@ -9,6 +9,7 @@ from convert.types import ModelType import tensorflow as tf +tf.enable_eager_execution() parser = argparse.ArgumentParser() # export types @@ -16,12 +17,10 @@ parser.add_argument('--tflite', action='store_true') parser.add_argument('--tfjs', action='store_true') -# model params -parser.add_argument('--input-name', type=str) -parser.add_argument('--output-names', type=str, nargs='+') +parser.add_argument('--model-type', type=str) # import paths -parser.add_argument('--exported-graph-path', type=str, default='exported_graph') +parser.add_argument('--saved-model-path', type=str) # export paths parser.add_argument('--mlmodel-path', type=str, default='model_ios') @@ -30,16 +29,8 @@ args = parser.parse_args() def infer_model_structure(): - if args.input_name and args.output_names: - return { - 'input_name': args.input_name, - 'output_names': args.output_names, - 'type': ModelType.NONE - } - with tf.Session(graph=tf.Graph()) as sess: - saved_model_path = os.path.join(args.exported_graph_path, 'saved_model') - tf.saved_model.loader.load(sess, ['serve'], saved_model_path) + tf.saved_model.loader.load(sess, ['serve'], args.saved_model_path) graph = tf.get_default_graph() ops = [op.name for op in graph.get_operations()] op1 = 'Postprocessor/ExpandDims_1' @@ -63,28 +54,53 @@ def infer_model_structure(): } model_structure = infer_model_structure() +print(args.model_type) try: if args.coreml: + print(' ' * 80) + print('_' * 80) + print('Converting to Core ML') from convert.convert_to_core_ml import convert_to_core_ml - convert_to_core_ml(args.exported_graph_path, model_structure, args.mlmodel_path) + convert_to_core_ml(args.saved_model_path, model_structure, args.mlmodel_path) + print('Successfully converted to Core ML') + print('_' * 80) + print(' ' * 80) except Exception as e: print(e) print("Unable to convert to Core ML") + print('_' * 80) + print(' ' * 80) try: if args.tflite: + print(' ' * 80) + print('_' * 80) + print('Converting to TensorFlow Lite') from convert.convert_to_tflite import convert_to_tflite - convert_to_tflite(args.exported_graph_path, model_structure, args.tflite_path) + convert_to_tflite(args.saved_model_path, model_structure, args.tflite_path) + print('Successfully converted to TensorFlow Lite') + print('_' * 80) + print(' ' * 80) except Exception as e: print(e) print("Unable to convert to TensorFlow Lite") + print('_' * 80) + print(' ' * 80) try: if args.tfjs: + print(' ' * 80) + print('_' * 80) + print('Converting to TensorFlow.js') from convert.convert_to_tfjs import convert_to_tfjs output_names = model_structure['output_names'] - convert_to_tfjs(args.exported_graph_path, output_names, args.tfjs_path) + convert_to_tfjs(args.saved_model_path, output_names, args.tfjs_path) + print('Successfully converted to TensorFlow.js') + print('_' * 80) + print(' ' * 80) except Exception as e: print(e) print("Unable to convert to TensorFlow.js") + print('_' * 80) + print(' ' * 80) diff --git a/trainer/src/convert/convert_ssd_helper.py b/trainer/src/convert/convert_ssd_helper.py index e58e54b..c924587 100644 --- a/trainer/src/convert/convert_ssd_helper.py +++ b/trainer/src/convert/convert_ssd_helper.py @@ -23,7 +23,7 @@ def optimize_graph(input_path, output_path, input_nodes, output_nodes): graph = tf.Graph() with tf.Session(graph=graph) as sess: - tf.saved_model.loader.load(sess, [tf.saved_model.tag_constants.SERVING], input_path) + tf.saved_model.loader.load(sess, [tf.saved_model.SERVING], input_path) gdef = strip_unused_lib.strip_unused( input_graph_def=graph.as_graph_def(), @@ -35,10 +35,9 @@ def optimize_graph(input_path, output_path, input_nodes, output_nodes): return graph -def convert_ssd_tflite(exported_graph_path, model_structure, output_path): +def convert_ssd_tflite(saved_model_path, model_structure, output_path): num_anchors = 1917 - saved_model_path = os.path.join(exported_graph_path, 'saved_model') tflite_model_path = os.path.join(output_path, 'model.tflite') # Strip the model down to something usable by Core ML. @@ -46,9 +45,10 @@ def convert_ssd_tflite(exported_graph_path, model_structure, output_path): # applies the sigmoid to the class scores. frozen_model_path = '.tmp/tmp_frozen_graph.pb' input_node = 'Preprocessor/sub' - bbox_output_node = 'concat' - class_output_node = 'Postprocessor/convert_scores' - graph = optimize_graph(saved_model_path, frozen_model_path, [input_node], [bbox_output_node, class_output_node]) + bbox_output_node = 'Squeeze' + class_output_node = 'Postprocessor/convert_scores' + graph = optimize_graph(saved_model_path, frozen_model_path, [ + input_node], [bbox_output_node, class_output_node]) # Convert to tflite model. input_arrays = [input_node] @@ -70,13 +70,12 @@ def convert_ssd_tflite(exported_graph_path, model_structure, output_path): json.dump(anchors.tolist(), f) -def convert_ssd(exported_graph_path, model_structure, output_path): +def convert_ssd(saved_model_path, model_structure, output_path): num_anchors = 1917 - saved_model_path = os.path.join(exported_graph_path, 'saved_model') coreml_model_path = os.path.join(output_path, 'Model.mlmodel') - json_labels = os.path.join(exported_graph_path, 'labels.json') + json_labels = os.path.join(saved_model_path, 'labels.json') with open(json_labels) as f: labels = json.load(f) @@ -85,43 +84,51 @@ def convert_ssd(exported_graph_path, model_structure, output_path): # applies the sigmoid to the class scores. frozen_model_path = '.tmp/tmp_frozen_graph.pb' input_node = 'Preprocessor/sub' - bbox_output_node = 'concat' - class_output_node = 'Postprocessor/convert_scores' - graph = optimize_graph(saved_model_path, frozen_model_path, [input_node], [bbox_output_node, class_output_node]) - - # conversion tensors have a `:0` at the end of the name - input_tensor = input_node + ':0' - bbox_output_tensor = bbox_output_node + ':0' - class_output_tensor = class_output_node + ':0' + bbox_output_node = 'Squeeze' + class_output_node = 'Postprocessor/convert_scores' + graph = optimize_graph(saved_model_path, frozen_model_path, [ + input_node], [bbox_output_node, class_output_node]) # Convert to Core ML model. ssd_model = tfcoreml.convert( tf_model_path=frozen_model_path, mlmodel_path=coreml_model_path, - input_name_shape_dict={ input_tensor: [1, 300, 300, 3] }, - image_input_names=input_tensor, - output_feature_names=[bbox_output_tensor, class_output_tensor], + input_name_shape_dict={'Preprocessor/sub': [1, 300, 300, 3]}, + image_input_names=['Preprocessor/sub'], + output_feature_names=['Squeeze', 'Postprocessor/convert_scores'], is_bgr=False, red_bias=-1.0, green_bias=-1.0, blue_bias=-1.0, - image_scale=2./255) + image_scale=2./255, + minimum_ios_deployment_target='13') spec = ssd_model.get_spec() + input_mlmodel = 'Preprocessor/sub' + class_output_mlmodel = 'Postprocessor/convert_scores' + bbox_output_mlmodel = 'Squeeze' + # Rename the inputs and outputs to something more readable. spec.description.input[0].name = 'image' spec.description.input[0].shortDescription = 'Input image' - spec.description.output[0].name = 'scores' - spec.description.output[0].shortDescription = 'Predicted class scores for each bounding box' - spec.description.output[1].name = 'boxes' - spec.description.output[1].shortDescription = 'Predicted coordinates for each bounding box' + spec.neuralNetwork.preprocessing[0].featureName = 'image' + + for i in range(len(spec.description.output)): + if spec.description.output[i].name == bbox_output_mlmodel: + spec.description.output[i].name = 'boxes' + spec.description.output[i].shortDescription = 'Predicted coordinates for each bounding box' + spec.description.output[i].type.multiArrayType.shape[:] = [ + 4, num_anchors, 1] - input_mlmodel = input_tensor.replace(':', '__').replace('/', '__') - class_output_mlmodel = class_output_tensor.replace(':', '__').replace('/', '__') - bbox_output_mlmodel = bbox_output_tensor.replace(':', '__').replace('/', '__') + if spec.description.output[i].name == class_output_mlmodel: + spec.description.output[i].name = 'scores' + spec.description.output[i].shortDescription = 'Predicted class scores for each bounding box' + spec.description.output[i].type.multiArrayType.shape[:] = [ + len(labels) + 1, num_anchors, 1] for i in range(len(spec.neuralNetwork.layers)): + # Assumes everything only has 1 input or output... if spec.neuralNetwork.layers[i].input[0] == input_mlmodel: spec.neuralNetwork.layers[i].input[0] = 'image' if spec.neuralNetwork.layers[i].output[0] == class_output_mlmodel: @@ -129,24 +136,19 @@ def convert_ssd(exported_graph_path, model_structure, output_path): if spec.neuralNetwork.layers[i].output[0] == bbox_output_mlmodel: spec.neuralNetwork.layers[i].output[0] = 'boxes' - spec.neuralNetwork.preprocessing[0].featureName = 'image' - - # For some reason the output shape of the `scores` output is not filled in. - spec.description.output[0].type.multiArrayType.shape.append(len(labels) + 1) - spec.description.output[0].type.multiArrayType.shape.append(num_anchors) + def _convert_multiarray_to_float32(feature): + from coremltools.proto import FeatureTypes_pb2 as ft + if feature.type.HasField('multiArrayType'): + feature.type.multiArrayType.dataType = ft.ArrayFeatureType.DOUBLE - # And the `boxes` output shape is (4, 1917, 1) so get rid of that last one. - del spec.description.output[1].type.multiArrayType.shape[-1] + for input_ in spec.description.input: + _convert_multiarray_to_float32(input_) + for output_ in spec.description.output: + _convert_multiarray_to_float32(output_) # Convert weights to 16-bit floats to make the model smaller. spec = coremltools.utils.convert_neural_network_spec_weights_to_fp16(spec) - # Create a new MLModel from the modified spec and save it. - ssd_model = coremltools.models.MLModel(spec) - - decoder_model = build_decoder(graph, len(labels), num_anchors) - nms_model = build_nms(decoder_model, labels) - input_features = [ ('image', datatypes.Array(3, 300, 300)), ('iouThreshold', datatypes.Double()), @@ -157,30 +159,32 @@ def convert_ssd(exported_graph_path, model_structure, output_path): pipeline = Pipeline(input_features, output_features) - # We added a dimension of size 1 to the back of the inputs of the decoder - # model, so we should also add this to the output of the SSD model or else - # the inputs and outputs do not match and the pipeline is not valid. - ssd_output = ssd_model._spec.description.output - ssd_output[0].type.multiArrayType.shape[:] = [len(labels) + 1, num_anchors, 1] - ssd_output[1].type.multiArrayType.shape[:] = [4, num_anchors, 1] + # Create a new MLModel from the modified spec and save it. + ssd_model = coremltools.models.MLModel(spec) + decoder_model = build_decoder(graph, len(labels), num_anchors) + nms_model = build_nms(decoder_model, labels) pipeline.add_model(ssd_model) pipeline.add_model(decoder_model) pipeline.add_model(nms_model) # The `image` input should really be an image, not a multi-array. - pipeline.spec.description.input[0].ParseFromString(ssd_model._spec.description.input[0].SerializeToString()) + pipeline.spec.description.input[0].ParseFromString( + ssd_model._spec.description.input[0].SerializeToString()) # Copy the declarations of the `confidence` and `coordinates` outputs. # The Pipeline makes these strings by default. - pipeline.spec.description.output[0].ParseFromString(nms_model._spec.description.output[0].SerializeToString()) - pipeline.spec.description.output[1].ParseFromString(nms_model._spec.description.output[1].SerializeToString()) + pipeline.spec.description.output[0].ParseFromString( + nms_model._spec.description.output[0].SerializeToString()) + pipeline.spec.description.output[1].ParseFromString( + nms_model._spec.description.output[1].SerializeToString()) # Add descriptions to the inputs and outputs. pipeline.spec.description.input[1].shortDescription = '(optional) IOU Threshold override' pipeline.spec.description.input[2].shortDescription = '(optional) Confidence Threshold override' pipeline.spec.description.output[0].shortDescription = u'Boxes \xd7 Class confidence' - pipeline.spec.description.output[1].shortDescription = u'Boxes \xd7 [x, y, width, height] (relative to image size)' + pipeline.spec.description.output[ + 1].shortDescription = u'Boxes \xd7 [x, y, width, height] (relative to image size)' # Add metadata to the model. pipeline.spec.description.metadata.versionString = 'ssd_mobilenet' @@ -194,9 +198,10 @@ def convert_ssd(exported_graph_path, model_structure, output_path): 'confidence_threshold': str(0.5), 'classes': ','.join(labels) } - pipeline.spec.description.metadata.userDefined.update(user_defined_metadata) + pipeline.spec.description.metadata.userDefined.update( + user_defined_metadata) - pipeline.spec.specificationVersion = 3 + pipeline.spec.specificationVersion = 4 final_model = coremltools.models.MLModel(pipeline.spec) final_model.save(coreml_model_path) diff --git a/trainer/src/convert/convert_to_core_ml.py b/trainer/src/convert/convert_to_core_ml.py index 82fc6c9..d6375d4 100644 --- a/trainer/src/convert/convert_to_core_ml.py +++ b/trainer/src/convert/convert_to_core_ml.py @@ -11,9 +11,9 @@ from tensorflow.python.framework import dtypes from tensorflow.python.platform import gfile -def convert_to_core_ml(exported_graph_path, model_structure, output_path): +def convert_to_core_ml(saved_model_path, model_structure, output_path): import tfcoreml - + if not os.path.exists('.tmp'): os.makedirs('.tmp') @@ -22,38 +22,78 @@ def convert_to_core_ml(exported_graph_path, model_structure, output_path): os.makedirs(output_path) if model_structure['type'] == ModelType.LOCALIZATION: - convert_ssd(exported_graph_path, model_structure, output_path) + convert_ssd(saved_model_path, model_structure, output_path) else: frozen_model_file = '.tmp/tmp_frozen_graph.pb' + with tf.Session(graph=tf.Graph()) as sess: - saved_model_path = os.path.join(exported_graph_path, 'saved_model/') - tf.saved_model.loader.load(sess, [tf.saved_model.tag_constants.SERVING], saved_model_path) + print('loading model...') + tf.saved_model.loader.load(sess, [tf.saved_model.SERVING], saved_model_path) + + print('stripping unused ops...') + gdef = strip_unused_lib.strip_unused( + input_graph_def=tf.get_default_graph().as_graph_def(), + input_node_names=[model_structure['input_name']], + output_node_names=model_structure['output_names'], + placeholder_type_enum=dtypes.float32.as_datatype_enum) + + gdef = tf.graph_util.convert_variables_to_constants(sess, gdef, model_structure['output_names']) + with gfile.GFile(frozen_model_file, 'wb') as f: - output_graph_def = tf.graph_util.convert_variables_to_constants( - sess, - tf.get_default_graph().as_graph_def(), - model_structure['output_names']) - f.write(output_graph_def.SerializeToString()) + print('writing frozen model...') + f.write(gdef.SerializeToString()) + + + # with tf.Session(graph=tf.Graph()) as sess: + # tf.saved_model.loader.load(sess, [tf.saved_model.SERVING], saved_model_path) + # with gfile.GFile(frozen_model_file, 'wb') as f: + # output_graph_def = tf.graph_util.convert_variables_to_constants( + # sess, + # tf.get_default_graph().as_graph_def(), + # model_structure['output_names']) + # f.write(output_graph_def.SerializeToString()) - output_feature_names = ['{}:0'.format(name) for name in model_structure['output_names']] + # output_feature_names = ['{}:0'.format(name) for name in model_structure['output_names']] + print('loading labels...') class_labels = None - json_labels = os.path.join(exported_graph_path, 'labels.json') - text_labels = os.path.join(exported_graph_path, 'labels.txt') + text_labels = os.path.join(saved_model_path, 'labels.txt') if os.path.isfile(text_labels): with open(text_labels, 'r') as f: class_labels = f.read() class_labels = list(filter(bool, [s.strip() for s in class_labels.splitlines()])) - elif os.path.isfile(json_labels): - with open(json_labels) as f: - class_labels = json.load(f) - - tfcoreml.convert(tf_model_path=frozen_model_file, - mlmodel_path=os.path.join(output_path, 'Model.mlmodel'), - output_feature_names=output_feature_names, - class_labels=class_labels, - red_bias=-1, - green_bias=-1, - blue_bias=-1, - image_scale=1.0/128.0, - image_input_names='{}:0'.format(model_structure['input_name'])) \ No newline at end of file + + + print(model_structure['input_name']) + print(model_structure['output_names']) + print(class_labels) + + print('converting...') + # , "input/BottleneckInputPlaceholder": [-1, 1024] + tfcoreml.convert( + tf_model_path=frozen_model_file, + mlmodel_path=os.path.join(output_path, 'Model.mlmodel'), + input_name_shape_dict={model_structure['input_name']: [1, 224, 224, 3] , "input/BottleneckInputPlaceholder": [-1, 1024]}, + image_input_names=[model_structure['input_name']], + output_feature_names=model_structure['output_names'], + # predicted_probabilities_output='final_result', + # predicted_feature_name='classLabel', + class_labels=os.path.join(saved_model_path, 'labels.txt'), + is_bgr=False, + red_bias=-1.0, + green_bias=-1.0, + blue_bias=-1.0, + image_scale=2./255, + minimum_ios_deployment_target='13') + + # tfcoreml.convert( + # tf_model_path=frozen_model_file, + # mlmodel_path=os.path.join(output_path, 'Model.mlmodel'), + # image_input_names=[model_structure['input_name']], + # output_feature_names=model_structure['output_names'], + # class_labels=class_labels, + # red_bias=-1, + # green_bias=-1, + # blue_bias=-1, + # image_scale=1.0/128.0, + # minimum_ios_deployment_target='13') \ No newline at end of file diff --git a/trainer/src/convert/convert_to_tfjs.py b/trainer/src/convert/convert_to_tfjs.py index 28411f5..33cf2cf 100644 --- a/trainer/src/convert/convert_to_tfjs.py +++ b/trainer/src/convert/convert_to_tfjs.py @@ -2,27 +2,50 @@ import json import shutil -def convert_to_tfjs(exported_graph_path, output_names, output_path): - from tensorflowjs.converters import convert_tf_saved_model +import tensorflow as tf +from tensorflow.python.tools import strip_unused_lib +from tensorflow.python.framework import dtypes +from tensorflow.python.platform import gfile + +def convert_to_tfjs(saved_model_path, output_names, output_path): + from tensorflowjs.converters import tf_saved_model_conversion_v2 + from convert.convert_ssd_helper import optimize_graph - saved_model_path = os.path.join(exported_graph_path, 'saved_model') output_names_str = ','.join(output_names) - try: - convert_tf_saved_model( - saved_model_path, - output_names_str, - output_path, - saved_model_tags='serve', - quantization_dtype=None, - skip_op_check=False, - strip_debug_ops=True) - except Exception as err: - print('Error: {}'.format(err)) + + frozen_model_file = '.tmp/tfjs_tmp_frozen_graph.pb' + + with tf.Session(graph=tf.Graph()) as sess: + print('loading model...') + tf.saved_model.loader.load(sess, [tf.saved_model.SERVING], saved_model_path) + + print('stripping unused ops...') + gdef = strip_unused_lib.strip_unused( + input_graph_def=tf.get_default_graph().as_graph_def(), + input_node_names=[], + output_node_names=output_names, + placeholder_type_enum=dtypes.float32.as_datatype_enum) + + gdef = tf.graph_util.convert_variables_to_constants(sess, gdef, output_names) + + with gfile.GFile(frozen_model_file, 'wb') as f: + print('writing frozen model...') + f.write(gdef.SerializeToString()) + # optimize_graph(saved_model_path, frozen_model_path, [], output_names) + + tf_saved_model_conversion_v2.convert_tf_frozen_model( + frozen_model_file, + output_names_str, + output_path, + quantization_dtype=None, + skip_op_check=False, + strip_debug_ops=True) + # Move the labels to the model directory. - json_labels = os.path.join(exported_graph_path, 'labels.json') - text_labels = os.path.join(exported_graph_path, 'labels.txt') + json_labels = os.path.join(saved_model_path, 'labels.json') + text_labels = os.path.join(saved_model_path, 'labels.txt') if not os.path.isfile(json_labels): with open(text_labels, 'r') as f: labels = f.read() @@ -30,4 +53,4 @@ def convert_to_tfjs(exported_graph_path, output_names, output_path): with open(os.path.join(output_path, 'labels.json'), 'w') as f: json.dump(labels, f) else: - shutil.copy2(json_labels, output_path) \ No newline at end of file + shutil.copy2(json_labels, output_path) diff --git a/trainer/src/convert/convert_to_tflite.py b/trainer/src/convert/convert_to_tflite.py index 2d0581b..9cece81 100644 --- a/trainer/src/convert/convert_to_tflite.py +++ b/trainer/src/convert/convert_to_tflite.py @@ -10,16 +10,16 @@ from convert.TFLiteConverter import convert -def convert_to_tflite(exported_graph_path, model_structure, output_path): +def convert_to_tflite(saved_model_path, model_structure, output_path): if model_structure['type'] == ModelType.LOCALIZATION: if os.path.exists(output_path) and os.path.isdir(output_path): shutil.rmtree(output_path) os.makedirs(output_path) - convert_ssd_tflite(exported_graph_path, model_structure, output_path) + convert_ssd_tflite(saved_model_path, model_structure, output_path) # Move the labels to the model directory. - json_labels = os.path.join(exported_graph_path, 'labels.json') + json_labels = os.path.join(saved_model_path, 'labels.json') shutil.copy2(json_labels, output_path) else: if os.path.exists(output_path) and os.path.isdir(output_path): @@ -29,7 +29,6 @@ def convert_to_tflite(exported_graph_path, model_structure, output_path): input_arrays = [model_structure['input_name']] output_arrays = model_structure['output_names'] - saved_model_path = os.path.join(exported_graph_path, 'saved_model') converter = convert.from_saved_model( saved_model_path, input_arrays=input_arrays, @@ -39,8 +38,8 @@ def convert_to_tflite(exported_graph_path, model_structure, output_path): f.write(tflite_model) # Move the labels to the model directory. - json_labels = os.path.join(exported_graph_path, 'labels.json') - text_labels = os.path.join(exported_graph_path, 'labels.txt') + json_labels = os.path.join(saved_model_path, 'labels.json') + text_labels = os.path.join(saved_model_path, 'labels.txt') if not os.path.isfile(json_labels): with open(text_labels, 'r') as f: labels = f.read() diff --git a/trainer/src/data/prepare_data_classification.py b/trainer/src/data/prepare_data_classification.py index 46ba2bd..bbb5d5b 100644 --- a/trainer/src/data/prepare_data_classification.py +++ b/trainer/src/data/prepare_data_classification.py @@ -24,8 +24,6 @@ def create_dir(base, dirName): data_dir = create_dir('', 'data') - create_dir(write_bucket, 'model') - annotations_file = os.path.join(read_bucket, '_annotations.json') with open(annotations_file) as f: diff --git a/trainer/src/requirements.txt b/trainer/src/requirements.txt index a355090..665a2ca 100644 --- a/trainer/src/requirements.txt +++ b/trainer/src/requirements.txt @@ -1,7 +1,9 @@ pycocotools==2.0.0 -coremltools==2.0 -tfcoreml==0.3.0 -tensorflowjs==0.8.0 +coremltools==3.3 +tfcoreml==1.1 +tensorflowjs==1.4.0 tensorflow-hub==0.3.0 h5py==2.8.0 numpy==1.17.5 +sympy==1.5.1 +mpmath==1.1.0 \ No newline at end of file diff --git a/trainer/src/start.sh b/trainer/src/start.sh old mode 100755 new mode 100644 index e82c858..075bad7 --- a/trainer/src/start.sh +++ b/trainer/src/start.sh @@ -2,6 +2,7 @@ trap 'echo CACLI-TRAINING-FAILED; exit 1' ERR +pip install --user Cython contextlib2 matplotlib pip install --user --no-deps -r requirements.txt SCRIPT="python - << EOF @@ -16,6 +17,7 @@ EOF" TYPE=$(eval "$SCRIPT") PIPELINE_CONFIG_PATH=pipeline.config +TMP_OUTPUT_DIRECTORY=${RESULT_DIR}/_model OUTPUT_DIRECTORY=${RESULT_DIR}/model OUTPUT_LABEL_PATH=${OUTPUT_DIRECTORY}/labels.json CHECKPOINT_PATH=${RESULT_DIR}/checkpoints @@ -40,7 +42,9 @@ TRAINED_CHECKPOINT_PREFIX=$(python -m get_latest_checkpoint \ python -m object_detection.export_inference_graph \ --pipeline_config_path=$PIPELINE_CONFIG_PATH \ --trained_checkpoint_prefix=$TRAINED_CHECKPOINT_PREFIX \ - --output_directory=$OUTPUT_DIRECTORY + --output_directory=$TMP_OUTPUT_DIRECTORY + +cp -r $TMP_OUTPUT_DIRECTORY/saved_model $OUTPUT_DIRECTORY python -m export_labels \ --label_map_path=$LABEL_MAP_PATH \ @@ -52,18 +56,22 @@ echo '/////////////////////////////' python -m data.prepare_data_classification python -m classification.retrain \ --image_dir=data \ - --saved_model_dir=$OUTPUT_DIRECTORY/saved_model \ + --saved_model_dir=$OUTPUT_DIRECTORY \ --tfhub_module=https://tfhub.dev/google/imagenet/mobilenet_v1_100_224/feature_vector/1 \ --validation_percentage=20 \ --how_many_training_steps=$1 \ - --output_labels=$OUTPUT_DIRECTORY/labels.txt + --output_labels=labels.txt +mv labels.txt $OUTPUT_DIRECTORY fi echo 'CACLI-TRAINING-SUCCESS' -trap 'echo CACLI-CONVERSION-FAILED; exit 1' ERR + +# Don't crash for failed conversion. +trap 'echo CACLI-CONVERSION-FAILED; exit 0' ERR python -m convert.convert --tfjs --coreml --tflite \ + --$TYPE \ --tfjs-path=${RESULT_DIR}/model_web \ --mlmodel-path=${RESULT_DIR}/model_ios \ --tflite-path=${RESULT_DIR}/model_android \ - --exported-graph-path=$OUTPUT_DIRECTORY \ No newline at end of file + --saved-model-path=$OUTPUT_DIRECTORY \ No newline at end of file