From 9443ae6e21cc4611162426b2ab7c644feadf3dc5 Mon Sep 17 00:00:00 2001 From: Pedro Larroy Date: Tue, 9 Apr 2019 17:37:33 -0700 Subject: [PATCH 01/16] Fix warning on macro expansion using defined. (#14598) warning: macro expansion producing 'defined' has undefined behavior [-Wexpansion-to-defined] --- src/operator/nn/dropout-inl.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/operator/nn/dropout-inl.h b/src/operator/nn/dropout-inl.h index 82bdda69dcd0..01611dfce191 100644 --- a/src/operator/nn/dropout-inl.h +++ b/src/operator/nn/dropout-inl.h @@ -39,7 +39,10 @@ #include "../random/sampler.h" #include "../tensor/elemwise_binary_broadcast_op.h" -#define MXNET_USE_MKL_DROPOUT defined(USE_MKL) && defined(_OPENMP) && !defined(__CUDACC__) +#if defined(USE_MKL) && defined(_OPENMP) && !defined(__CUDACC__) +#define MXNET_USE_MKL_DROPOUT 1 +#endif + #if MXNET_USE_MKL_DROPOUT #include From c6516cc133106f44e247f9ed45165226e3682b62 Mon Sep 17 00:00:00 2001 From: Robert Stone Date: Tue, 9 Apr 2019 18:59:36 -0700 Subject: [PATCH 02/16] [MXNET-1359] Adds a multiclass-MCC metric derived from Pearson (#14461) * Adds a multiclass-MCC metric derived from Pearson * trigger ci --- python/mxnet/metric.py | 132 ++++++++++++++++++++++++++- tests/python/unittest/test_metric.py | 82 +++++++++++++++++ 2 files changed, 213 insertions(+), 1 deletion(-) diff --git a/python/mxnet/metric.py b/python/mxnet/metric.py index ecb8e1c3bc22..4bfd1b36bf89 100644 --- a/python/mxnet/metric.py +++ b/python/mxnet/metric.py @@ -860,7 +860,7 @@ class MCC(EvalMetric): .. note:: - This version of MCC only supports binary classification. + This version of MCC only supports binary classification. See PCC. Parameters ---------- @@ -1476,6 +1476,136 @@ def update(self, labels, preds): self.global_num_inst += 1 +@register +class PCC(EvalMetric): + """PCC is a multiclass equivalent for the Matthews correlation coefficient derived + from a discrete solution to the Pearson correlation coefficient. + + .. math:: + \\text{PCC} = \\frac {\\sum _{k}\\sum _{l}\\sum _{m}C_{kk}C_{lm}-C_{kl}C_{mk}} + {{\\sqrt {\\sum _{k}(\\sum _{l}C_{kl})(\\sum _{k'|k'\\neq k}\\sum _{l'}C_{k'l'})}} + {\\sqrt {\\sum _{k}(\\sum _{l}C_{lk})(\\sum _{k'|k'\\neq k}\\sum _{l'}C_{l'k'})}}} + + defined in terms of a K x K confusion matrix C. + + When there are more than two labels the PCC will no longer range between -1 and +1. + Instead the minimum value will be between -1 and 0 depending on the true distribution. + The maximum value is always +1. + + Parameters + ---------- + name : str + Name of this metric instance for display. + output_names : list of str, or None + Name of predictions that should be used when updating with update_dict. + By default include all predictions. + label_names : list of str, or None + Name of labels that should be used when updating with update_dict. + By default include all labels. + + Examples + -------- + >>> # In this example the network almost always predicts positive + >>> false_positives = 1000 + >>> false_negatives = 1 + >>> true_positives = 10000 + >>> true_negatives = 1 + >>> predicts = [mx.nd.array( + [[.3, .7]]*false_positives + + [[.7, .3]]*true_negatives + + [[.7, .3]]*false_negatives + + [[.3, .7]]*true_positives + )] + >>> labels = [mx.nd.array( + [0]*(false_positives + true_negatives) + + [1]*(false_negatives + true_positives) + )] + >>> f1 = mx.metric.F1() + >>> f1.update(preds = predicts, labels = labels) + >>> pcc = mx.metric.PCC() + >>> pcc.update(preds = predicts, labels = labels) + >>> print f1.get() + ('f1', 0.95233560306652054) + >>> print pcc.get() + ('pcc', 0.01917751877733392) + """ + def __init__(self, name='pcc', + output_names=None, label_names=None, + has_global_stats=True): + self.k = 2 + super(PCC, self).__init__( + name=name, output_names=output_names, label_names=label_names, + has_global_stats=has_global_stats) + + def _grow(self, inc): + self.lcm = numpy.pad( + self.lcm, ((0, inc), (0, inc)), 'constant', constant_values=(0)) + self.gcm = numpy.pad( + self.gcm, ((0, inc), (0, inc)), 'constant', constant_values=(0)) + self.k += inc + + def _calc_mcc(self, cmat): + n = cmat.sum() + x = cmat.sum(axis=1) + y = cmat.sum(axis=0) + cov_xx = numpy.sum(x * (n - x)) + cov_yy = numpy.sum(y * (n - y)) + if cov_xx == 0 or cov_yy == 0: + return float('nan') + i = cmat.diagonal() + cov_xy = numpy.sum(i * n - x * y) + return cov_xy / (cov_xx * cov_yy) ** 0.5 + + def update(self, labels, preds): + """Updates the internal evaluation result. + + Parameters + ---------- + labels : list of `NDArray` + The labels of the data. + + preds : list of `NDArray` + Predicted values. + """ + labels, preds = check_label_shapes(labels, preds, True) + + # update the confusion matrix + for label, pred in zip(labels, preds): + label = label.astype('int32', copy=False).asnumpy() + pred = pred.asnumpy().argmax(axis=1) + n = max(pred.max(), label.max()) + if n >= self.k: + self._grow(n + 1 - self.k) + bcm = numpy.zeros((self.k, self.k)) + for i, j in zip(pred, label): + bcm[i, j] += 1 + self.lcm += bcm + self.gcm += bcm + + self.num_inst += 1 + self.global_num_inst += 1 + + @property + def sum_metric(self): + return self._calc_mcc(self.lcm) * self.num_inst + + @property + def global_sum_metric(self): + return self._calc_mcc(self.gcm) * self.global_num_inst + + def reset(self): + """Resets the internal evaluation result to initial state.""" + self.global_num_inst = 0. + self.gcm = numpy.zeros((self.k, self.k)) + self.reset_local() + + def reset_local(self): + """Resets the local portion of the internal evaluation results + to initial state.""" + self.num_inst = 0. + self.lcm = numpy.zeros((self.k, self.k)) + + @register class Loss(EvalMetric): """Dummy metric for directly printing loss. diff --git a/tests/python/unittest/test_metric.py b/tests/python/unittest/test_metric.py index 2821c4bbae3c..d8dca753bda4 100644 --- a/tests/python/unittest/test_metric.py +++ b/tests/python/unittest/test_metric.py @@ -34,6 +34,7 @@ def test_metrics(): check_metric('mcc') check_metric('perplexity', -1) check_metric('pearsonr') + check_metric('pcc') check_metric('nll_loss') check_metric('loss') composite = mx.metric.create(['acc', 'f1']) @@ -89,6 +90,7 @@ def test_global_metric(): _check_global_metric('mcc', shape=(10,2), average='micro') _check_global_metric('perplexity', -1) _check_global_metric('pearsonr', use_same_shape=True) + _check_global_metric('pcc', shape=(10,2)) _check_global_metric('nll_loss') _check_global_metric('loss') _check_global_metric('ce') @@ -253,6 +255,86 @@ def test_pearsonr(): _, pearsonr = metric.get() assert pearsonr == pearsonr_expected +def cm_batch(cm): + # generate a batch yielding a given confusion matrix + n = len(cm) + ident = np.identity(n) + labels = [] + preds = [] + for i in range(n): + for j in range(n): + labels += [ i ] * cm[i][j] + preds += [ ident[j] ] * cm[i][j] + return ([ mx.nd.array(labels, dtype='int32') ], [ mx.nd.array(preds) ]) + +def test_pcc(): + labels, preds = cm_batch([ + [ 7, 3 ], + [ 2, 5 ], + ]) + met_pcc = mx.metric.create('pcc') + met_pcc.update(labels, preds) + _, pcc = met_pcc.get() + + # pcc should agree with mcc for binary classification + met_mcc = mx.metric.create('mcc') + met_mcc.update(labels, preds) + _, mcc = met_mcc.get() + np.testing.assert_almost_equal(pcc, mcc) + + # pcc should agree with Pearson for binary classification + met_pear = mx.metric.create('pearsonr') + met_pear.update(labels, [p.argmax(axis=1) for p in preds]) + _, pear = met_pear.get() + np.testing.assert_almost_equal(pcc, pear) + + # check multiclass case against reference implementation + CM = [ + [ 23, 13, 3 ], + [ 7, 19, 11 ], + [ 2, 5, 17 ], + ] + K = 3 + ref = sum( + CM[k][k] * CM[l][m] - CM[k][l] * CM[m][k] + for k in range(K) + for l in range(K) + for m in range(K) + ) / (sum( + sum(CM[k][l] for l in range(K)) * sum( + sum(CM[f][g] for g in range(K)) + for f in range(K) + if f != k + ) + for k in range(K) + ) * sum( + sum(CM[l][k] for l in range(K)) * sum( + sum(CM[f][g] for f in range(K)) + for g in range(K) + if g != k + ) + for k in range(K) + )) ** 0.5 + labels, preds = cm_batch(CM) + met_pcc.reset() + met_pcc.update(labels, preds) + _, pcc = met_pcc.get() + np.testing.assert_almost_equal(pcc, ref) + + # things that should not change metric score: + # * order + # * batch size + # * update frequency + labels = [ [ i ] for i in labels[0] ] + labels.reverse() + preds = [ [ i.reshape((1, -1)) ] for i in preds[0] ] + preds.reverse() + + met_pcc.reset() + for l, p in zip(labels, preds): + met_pcc.update(l, p) + assert pcc == met_pcc.get()[1] + def test_single_array_input(): pred = mx.nd.array([[1,2,3,4]]) label = pred + 0.1 From d596b59ba6412fb3236b909ee43099324cc92803 Mon Sep 17 00:00:00 2001 From: Xinyu Chen Date: Tue, 9 Apr 2019 21:40:55 -0700 Subject: [PATCH 03/16] Support SSD f32/int8 evaluation on COCO dataset (#14646) * support ssd coco * improve doc --- example/quantization/README.md | 1 + example/ssd/README.md | 56 ++++++++++++++++++++-- example/ssd/dataset/mscoco.py | 10 +++- example/ssd/dataset/names/mscoco.names | 12 ++--- example/ssd/symbol/legacy_vgg16_ssd_300.py | 3 +- example/ssd/symbol/legacy_vgg16_ssd_512.py | 3 +- example/ssd/symbol/symbol_builder.py | 3 +- example/ssd/train.py | 5 +- example/ssd/train/metric.py | 11 +++++ example/ssd/train/train_net.py | 8 +++- 10 files changed, 90 insertions(+), 22 deletions(-) diff --git a/example/quantization/README.md b/example/quantization/README.md index b77537d4fba7..fc9a26755b4e 100644 --- a/example/quantization/README.md +++ b/example/quantization/README.md @@ -35,6 +35,7 @@ The following models have been tested on Linux systems. |[ResNet152-V2](#8)|[MXNet ModelZoo](http://data.mxnet.io/models/imagenet/resnet/152-layers/)|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec)|76.76%/93.03%|76.48%/92.96%| |[Inception-BN](#9)|[MXNet ModelZoo](http://data.mxnet.io/models/imagenet/inception-bn/)|[Validation Dataset](http://data.mxnet.io/data/val_256_q90.rec)|72.09%/90.60%|72.00%/90.53%| | [SSD-VGG16](#10) | [example/ssd](https://github.com/apache/incubator-mxnet/tree/master/example/ssd) | VOC2007/2012 | 0.8366 mAP | 0.8364 mAP | +| [SSD-VGG16](#10) | [example/ssd](https://github.com/apache/incubator-mxnet/tree/master/example/ssd) | COCO2014 | 0.2552 mAP | 0.253 mAP |

ResNet50-V1

diff --git a/example/ssd/README.md b/example/ssd/README.md index 6d4caa481bd7..92a125f1892d 100644 --- a/example/ssd/README.md +++ b/example/ssd/README.md @@ -42,6 +42,7 @@ remarkable traits of MXNet. Due to the permission issue, this example is maintained in this [repository](https://github.com/zhreshold/mxnet-ssd) separately. You can use the link regarding specific per example [issues](https://github.com/zhreshold/mxnet-ssd/issues). ### What's new +* Support training and inference on COCO dataset. Int8 inference achieves 0.253 mAP on CPU with MKL-DNN backend, which is a comparable accuracy to FP32 (0.2552 mAP). * Support uint8 inference on CPU with MKL-DNN backend. Uint8 inference achieves 0.8364 mAP, which is a comparable accuracy to FP32 (0.8366 mAP). * Added live camera capture and detection display (run with --camera flag). Example: `./demo.py --camera --cpu --frame-resize 0.5` @@ -119,9 +120,9 @@ You can use `./demo.py --camera` to use a video capture device with opencv such will open a window that will display the camera output together with the detections. You can play with the detection threshold to get more or less detections. -### Train the model +### Train the model on VOC * Note that we recommend to use gluon-cv to train the model, please refer to [gluon-cv ssd](https://gluon-cv.mxnet.io/build/examples_detection/train_ssd_voc.html). -This example only covers training on Pascal VOC dataset. Other datasets should +This example only covers training on Pascal VOC or MS COCO dataset. Other datasets should be easily supported by adding subclass derived from class `Imdb` in `dataset/imdb.py`. See example of `dataset/pascal_voc.py` for details. * Download the converted pretrained `vgg16_reduced` model [here](https://github.com/zhreshold/mxnet-ssd/releases/download/v0.2-beta/vgg16_reduced.zip), unzip `.param` and `.json` files @@ -166,16 +167,53 @@ Check `python train.py --help` for more training options. For example, if you ha python train.py --gpus 0,1,2,3 --batch-size 32 ``` +### Train the model on COCO +* Download the COCO2014 dataset, skip this step if you already have one. +``` +cd /path/to/where_you_store_datasets/ +wget http://images.cocodataset.org/zips/train2014.zip +wget http://images.cocodataset.org/zips/val2014.zip +wget http://images.cocodataset.org/annotations/annotations_trainval2014.zip +# Extract the data. +unzip train2014.zip +unzip val2014.zip +unzip annotations_trainval2014.zip +``` +* We are going to use `train2014,valminusminival2014` set in COCO2014 for training and `minival2014` for evaluation as a common strategy. +* Then link `COCO2014` folder to `data/coco` by default: +``` +ln -s /path/to/COCO2014 /path/to/incubator-mxnet/example/ssd/data/coco +``` +Use hard link instead of copy could save us a bit disk space. +* Create packed binary file for faster training: +``` +# cd /path/to/incubator-mxnet/example/ssd +bash tools/prepare_coco.sh +# or if you are using windows +python tools/prepare_dataset.py --dataset coco --set train2014,valminusminival2014 --target ./data/train.lst --root ./data/coco +python tools/prepare_dataset.py --dataset coco --set minival2014 --target ./data/val.lst --root ./data/coco --no-shuffle +``` +* Start training: +``` +# cd /path/to/incubator-mxnet/example/ssd +python train.py --label-width=560 --num-class=80 --class-names=./dataset/names/coco_label --pretrained="" --num-example=117265 --batch-size=64 +``` + ### Evalute trained model Make sure you have val.rec as validation dataset. It's the same one as used in training. Use: ``` # cd /path/to/incubator-mxnet/example/ssd python evaluate.py --gpus 0,1 --batch-size 128 --epoch 0 + +# Evaluate on COCO dataset +python evaluate.py --gpus 0,1 --batch-size 128 --epoch 0 --num-class=80 --class-names=./dataset/names/mscoco.names ``` ### Quantize model -Follow the [Train instructions](https://github.com/apache/incubator-mxnet/tree/master/example/ssd#train-the-model) to train a FP32 `SSD-VGG16_reduced_300x300` model based on Pascal VOC dataset. You can also download our [SSD-VGG16 pre-trained model](http://apache-mxnet.s3-accelerate.dualstack.amazonaws.com/gluon/models/ssd_vgg16_reduced_300-dd479559.zip) and [packed binary data](http://apache-mxnet.s3-accelerate.dualstack.amazonaws.com/gluon/dataset/ssd-val-fc19a535.zip). Create `model` and `data` directories if they're not exist, extract the zip files, then rename the uncompressed files as follows (eg, rename `ssd-val-fc19a535.idx` to `val.idx`, `ssd-val-fc19a535.lst` to `val.lst`, `ssd-val-fc19a535.rec` to `val.rec`, `ssd_vgg16_reduced_300-dd479559.params` to `ssd_vgg16_reduced_300-0000.params`, `ssd_vgg16_reduced_300-symbol-dd479559.json` to `ssd_vgg16_reduced_300-symbol.json`.) +To quantize a model on VOC dataset, follow the [Train instructions](https://github.com/apache/incubator-mxnet/tree/master/example/ssd#train-the-model-on-VOC) to train a FP32 `SSD-VGG16_reduced_300x300` model based on Pascal VOC dataset. You can also download our [SSD-VGG16 pre-trained model](http://apache-mxnet.s3-accelerate.dualstack.amazonaws.com/gluon/models/ssd_vgg16_reduced_300-dd479559.zip) and [packed binary data](http://apache-mxnet.s3-accelerate.dualstack.amazonaws.com/gluon/dataset/ssd-val-fc19a535.zip). Create `model` and `data` directories if they're not exist, extract the zip files, then rename the uncompressed files as follows (eg, rename `ssd-val-fc19a535.idx` to `val.idx`, `ssd-val-fc19a535.lst` to `val.lst`, `ssd-val-fc19a535.rec` to `val.rec`, `ssd_vgg16_reduced_300-dd479559.params` to `ssd_vgg16_reduced_300-0000.params`, `ssd_vgg16_reduced_300-symbol-dd479559.json` to `ssd_vgg16_reduced_300-symbol.json`.) + +To quantize a model on COCO dataset, follow the [Train instructions](https://github.com/apache/incubator-mxnet/tree/master/example/ssd#train-the-model-on-COCO) to train a FP32 `SSD-VGG16_reduced_300x300` model based on COCO dataset. You can also download our [SSD-VGG16 pre-trained model](http://apache-mxnet.s3-accelerate.dualstack.amazonaws.com/gluon/models/ssd_vgg16_reduced_300-7fedd4ad.zip) and [packed binary data](http://apache-mxnet.s3-accelerate.dualstack.amazonaws.com/gluon/dataset/ssd_coco-val-e91096e8.zip). Create `model` and `data` directories if they're not exist, extract the zip files, then rename the uncompressed files as follows (eg, rename `ssd_coco-val-e91096e8.idx` to `val.idx`, `ssd_coco-val-e91096e8.lst` to `val.lst`, `ssd_coco-val-e91096e8.rec` to `val.rec`, `ssd_vgg16_reduced_300-7fedd4ad.params` to `ssd_vgg16_reduced_300-0000.params`, `ssd_vgg16_reduced_300-symbol-7fedd4ad.json` to `ssd_vgg16_reduced_300-symbol.json`.) ``` data/ @@ -199,12 +237,20 @@ After quantization, INT8 models will be saved in `model/` dictionary. Use the f # USE MKLDNN AS SUBGRAPH BACKEND export MXNET_SUBGRAPH_BACKEND=MKLDNN -# Launch FP32 Inference +# Launch FP32 Inference on VOC dataset python evaluate.py --cpu --num-batch 10 --batch-size 224 --deploy --prefix=./model/ssd_ -# Launch INT8 Inference +# Launch INT8 Inference on VOC dataset python evaluate.py --cpu --num-batch 10 --batch-size 224 --deploy --prefix=./model/cqssd_ +# Launch FP32 Inference on COCO dataset + +python evaluate.py --cpu --num-batch 10 --batch-size 224 --deploy --prefix=./model/ssd_ --num-class=80 --class-names=./dataset/names/mscoco.names + +# Launch INT8 Inference on COCO dataset + +python evaluate.py --cpu --num-batch 10 --batch-size 224 --deploy --prefix=./model/cqssd_ --num-class=80 --class-names=./dataset/names/mscoco.names + # Launch dummy data Inference python benchmark_score.py --deploy --prefix=./model/ssd_ python benchmark_score.py --deploy --prefix=./model/cqssd_ diff --git a/example/ssd/dataset/mscoco.py b/example/ssd/dataset/mscoco.py index 469a15ae2720..dbe6e6909f4d 100644 --- a/example/ssd/dataset/mscoco.py +++ b/example/ssd/dataset/mscoco.py @@ -97,6 +97,12 @@ def _load_all(self, anno_file, shuffle): labels = [] coco = COCO(anno_file) img_ids = coco.getImgIds() + # deal with class names + cats = [cat['name'] for cat in coco.loadCats(coco.getCatIds())] + class_to_coco_ind = dict(zip(cats, coco.getCatIds())) + class_to_ind = dict(zip(self.classes, range(len(self.classes)))) + coco_ind_to_class_ind = dict([(class_to_coco_ind[cls], class_to_ind[cls]) + for cls in self.classes[0:]]) for img_id in img_ids: # filename image_info = coco.loadImgs(img_id)[0] @@ -109,7 +115,7 @@ def _load_all(self, anno_file, shuffle): annos = coco.loadAnns(anno_ids) label = [] for anno in annos: - cat_id = int(anno["category_id"]) + cat_id = coco_ind_to_class_ind[anno['category_id']] bbox = anno["bbox"] assert len(bbox) == 4 xmin = float(bbox[0]) / width @@ -123,7 +129,7 @@ def _load_all(self, anno_file, shuffle): if shuffle: import random - indices = range(len(image_set_index)) + indices = list(range(len(image_set_index))) random.shuffle(indices) image_set_index = [image_set_index[i] for i in indices] labels = [labels[i] for i in indices] diff --git a/example/ssd/dataset/names/mscoco.names b/example/ssd/dataset/names/mscoco.names index ca76c80b5b2c..941cb4e13922 100644 --- a/example/ssd/dataset/names/mscoco.names +++ b/example/ssd/dataset/names/mscoco.names @@ -1,8 +1,8 @@ person bicycle car -motorbike -aeroplane +motorcycle +airplane bus train truck @@ -55,12 +55,12 @@ pizza donut cake chair -sofa -pottedplant +couch +potted plant bed -diningtable +dining table toilet -tvmonitor +tv laptop mouse remote diff --git a/example/ssd/symbol/legacy_vgg16_ssd_300.py b/example/ssd/symbol/legacy_vgg16_ssd_300.py index 29fc30be65d4..0acac6e4294b 100644 --- a/example/ssd/symbol/legacy_vgg16_ssd_300.py +++ b/example/ssd/symbol/legacy_vgg16_ssd_300.py @@ -200,8 +200,7 @@ def get_symbol(num_classes=20, nms_thresh=0.5, force_suppress=False, loc_preds = net.get_internals()["multibox_loc_pred_output"] anchor_boxes = net.get_internals()["multibox_anchors_output"] - cls_prob = mx.symbol.SoftmaxActivation(data=cls_preds, mode='channel', \ - name='cls_prob') + cls_prob = mx.symbol.softmax(data=cls_preds, axis=1, name='cls_prob') out = mx.symbol.contrib.MultiBoxDetection(*[cls_prob, loc_preds, anchor_boxes], \ name="detection", nms_threshold=nms_thresh, force_suppress=force_suppress, variances=(0.1, 0.1, 0.2, 0.2), nms_topk=nms_topk) diff --git a/example/ssd/symbol/legacy_vgg16_ssd_512.py b/example/ssd/symbol/legacy_vgg16_ssd_512.py index c5c3095dfd77..74d6b37fc11e 100644 --- a/example/ssd/symbol/legacy_vgg16_ssd_512.py +++ b/example/ssd/symbol/legacy_vgg16_ssd_512.py @@ -203,8 +203,7 @@ def get_symbol(num_classes=20, nms_thresh=0.5, force_suppress=False, nms_topk=40 loc_preds = net.get_internals()["multibox_loc_pred_output"] anchor_boxes = net.get_internals()["multibox_anchors_output"] - cls_prob = mx.symbol.SoftmaxActivation(data=cls_preds, mode='channel', \ - name='cls_prob') + cls_prob = mx.symbol.softmax(data=cls_preds, axis=1, name='cls_prob') out = mx.symbol.contrib.MultiBoxDetection(*[cls_prob, loc_preds, anchor_boxes], \ name="detection", nms_threshold=nms_thresh, force_suppress=force_suppress, variances=(0.1, 0.1, 0.2, 0.2), nms_topk=nms_topk) diff --git a/example/ssd/symbol/symbol_builder.py b/example/ssd/symbol/symbol_builder.py index 041f83eb44da..135c42e8be15 100644 --- a/example/ssd/symbol/symbol_builder.py +++ b/example/ssd/symbol/symbol_builder.py @@ -175,8 +175,7 @@ def get_symbol(network, num_classes, from_layers, num_filters, sizes, ratios, num_classes, sizes=sizes, ratios=ratios, normalization=normalizations, \ num_channels=num_filters, clip=False, interm_layer=0, steps=steps) - cls_prob = mx.symbol.SoftmaxActivation(data=cls_preds, mode='channel', \ - name='cls_prob') + cls_prob = mx.symbol.softmax(data=cls_preds, axis=1, name='cls_prob') out = mx.symbol.contrib.MultiBoxDetection(*[cls_prob, loc_preds, anchor_boxes], \ name="detection", nms_threshold=nms_thresh, force_suppress=force_suppress, variances=(0.1, 0.1, 0.2, 0.2), nms_topk=nms_topk) diff --git a/example/ssd/train.py b/example/ssd/train.py index 09c618a96429..5965aeec6c7a 100755 --- a/example/ssd/train.py +++ b/example/ssd/train.py @@ -103,6 +103,8 @@ def parse_args(): help='use difficult ground-truths in evaluation') parser.add_argument('--no-voc07', dest='use_voc07_metric', action='store_false', help='dont use PASCAL VOC 07 11-point metric') + parser.add_argument('--kv-store', type=str, default='local', + help='key-value store type') args = parser.parse_args() return args @@ -150,4 +152,5 @@ def parse_class_names(args): force_nms=args.force_nms, ovp_thresh=args.overlap_thresh, use_difficult=args.use_difficult, - voc07_metric=args.use_voc07_metric) + voc07_metric=args.use_voc07_metric, + kv_store=args.kv_store) diff --git a/example/ssd/train/metric.py b/example/ssd/train/metric.py index 731f8fcc19f4..eeb9796bf4a8 100644 --- a/example/ssd/train/metric.py +++ b/example/ssd/train/metric.py @@ -39,6 +39,17 @@ def reset(self): self.num_inst = [0] * self.num self.sum_metric = [0.0] * self.num + def reset_local(self): + """ + override reset behavior + """ + if getattr(self, 'num', None) is None: + self.num_inst = 0 + self.sum_metric = 0.0 + else: + self.num_inst = [0] * self.num + self.sum_metric = [0.0] * self.num + def update(self, labels, preds): """ Implementation of updating metrics diff --git a/example/ssd/train/train_net.py b/example/ssd/train/train_net.py index 304a43b3d949..b37e3d5abcec 100644 --- a/example/ssd/train/train_net.py +++ b/example/ssd/train/train_net.py @@ -97,7 +97,7 @@ def train_net(net, train_path, num_classes, batch_size, use_difficult=False, class_names=None, voc07_metric=False, nms_topk=400, force_suppress=False, train_list="", val_path="", val_list="", iter_monitor=0, - monitor_pattern=".*", log_file=None): + monitor_pattern=".*", log_file=None, kv_store=None): """ Wrapper for training phase. @@ -258,6 +258,9 @@ def train_net(net, train_path, num_classes, batch_size, else: valid_metric = MApMetric(ovp_thresh, use_difficult, class_names, pred_idx=3) + # create kvstore when there are gpus + kv = mx.kvstore.create(kv_store) if kv_store else None + mod.fit(train_iter, val_iter, eval_metric=MultiBoxMetric(), @@ -272,4 +275,5 @@ def train_net(net, train_path, num_classes, batch_size, arg_params=args, aux_params=auxs, allow_missing=True, - monitor=monitor) + monitor=monitor, + kvstore=kv) From fde4963160cf01068c3ff3d028dccd2b1f2bb73b Mon Sep 17 00:00:00 2001 From: Jake Lee Date: Wed, 10 Apr 2019 20:01:10 +0800 Subject: [PATCH 04/16] [WIP][Dependency Update] Upgrade the libtiff to 4.0.10 (#14623) * upgrade the libtiff to 4.0.10 * retrigger CI * retrigger CI * retrigger CI * Retrigger CI --- tools/dependencies/libtiff.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/dependencies/libtiff.sh b/tools/dependencies/libtiff.sh index a1140d52a94c..f57099bde845 100755 --- a/tools/dependencies/libtiff.sh +++ b/tools/dependencies/libtiff.sh @@ -19,16 +19,16 @@ # This script builds the static library of libtiff that can be used as dependency of mxnet/opencv. set -ex -TIFF_VERSION="4-0-9" +TIFF_VERSION="4.0.10" if [[ ! -f $DEPS_PATH/lib/libtiff.a ]]; then # download and build libtiff >&2 echo "Building libtiff..." download \ - https://gitlab.com/libtiff/libtiff/-/archive/Release-v${TIFF_VERSION}/libtiff-Release-v${TIFF_VERSION}.zip \ + https://download.osgeo.org/libtiff/tiff-${TIFF_VERSION}.zip \ ${DEPS_PATH}/libtiff.zip unzip -q $DEPS_PATH/libtiff.zip -d $DEPS_PATH pushd . - cd $DEPS_PATH/libtiff-Release-v$TIFF_VERSION + cd $DEPS_PATH/tiff-$TIFF_VERSION ./configure --quiet --disable-shared --disable-jpeg --disable-zlib --disable-jbig --disable-lzma --prefix=$DEPS_PATH $MAKE $MAKE install From 8f0878c9b424aa9a1f575d22ce9b20e4e4254b93 Mon Sep 17 00:00:00 2001 From: Zach Kimberg Date: Wed, 10 Apr 2019 14:02:42 -0700 Subject: [PATCH 05/16] [MXNET-1287] Miscellaneous Scala warning fixes (#14658) * Remove printing during tests * Fix macro annotation warnings * Remove failing links from spark * Return printed as debug logs --- .../test/scala/org/apache/mxnet/ImageSuite.scala | 10 +++++----- .../scala/org/apache/mxnet/KVStoreSuite.scala | 3 --- .../org/apache/mxnet/ModelParallelSuite.scala | 5 ----- .../scala/org/apache/mxnet/OperatorSuite.scala | 6 ------ .../test/scala/org/apache/mxnet/SymbolSuite.scala | 14 -------------- .../scala/org/apache/mxnet/GeneratorBase.scala | 2 +- .../scala/org/apache/mxnet/NDArrayMacro.scala | 15 ++++++++------- .../main/scala/org/apache/mxnet/SymbolMacro.scala | 15 ++++++++------- .../apache/mxnet/javaapi/JavaNDArrayMacro.scala | 8 +++----- .../scala/org/apache/mxnet/spark/MXNDArray.scala | 2 +- .../scala/org/apache/mxnet/spark/MXNetModel.scala | 2 +- 11 files changed, 27 insertions(+), 55 deletions(-) diff --git a/scala-package/core/src/test/scala/org/apache/mxnet/ImageSuite.scala b/scala-package/core/src/test/scala/org/apache/mxnet/ImageSuite.scala index d4cf35af186f..dca4ce02ef89 100644 --- a/scala-package/core/src/test/scala/org/apache/mxnet/ImageSuite.scala +++ b/scala-package/core/src/test/scala/org/apache/mxnet/ImageSuite.scala @@ -57,7 +57,7 @@ class ImageSuite extends FunSuite with BeforeAndAfterAll { test("Test load image") { val nd = Image.imRead(imLocation) - logger.info(s"OpenCV load image with shape: ${nd.shape}") + logger.debug(s"OpenCV load image with shape: ${nd.shape}") require(nd.shape == Shape(576, 1024, 3), "image shape not Match!") } @@ -65,14 +65,14 @@ class ImageSuite extends FunSuite with BeforeAndAfterAll { val url = new URL("https://s3.amazonaws.com/model-server/inputs/Pug-Cookie.jpg") val inputStream = url.openStream val nd = Image.imDecode(inputStream) - logger.info(s"OpenCV load image with shape: ${nd.shape}") + logger.debug(s"OpenCV load image with shape: ${nd.shape}") require(nd.shape == Shape(576, 1024, 3), "image shape not Match!") } test("Test resize image") { val nd = Image.imRead(imLocation) val resizeIm = Image.imResize(nd, 224, 224) - logger.info(s"OpenCV resize image with shape: ${resizeIm.shape}") + logger.debug(s"OpenCV resize image with shape: ${resizeIm.shape}") require(resizeIm.shape == Shape(224, 224, 3), "image shape not Match!") } @@ -94,7 +94,7 @@ class ImageSuite extends FunSuite with BeforeAndAfterAll { val tempDirPath = System.getProperty("java.io.tmpdir") val img = Image.toImage(resizeIm) ImageIO.write(img, "png", new File(tempDirPath + "/inputImages/out.png")) - logger.info(s"converted image stored in ${tempDirPath + "/inputImages/out.png"}") + logger.debug(s"converted image stored in ${tempDirPath + "/inputImages/out.png"}") } test("Test draw Bounding box") { @@ -107,7 +107,7 @@ class ImageSuite extends FunSuite with BeforeAndAfterAll { Image.drawBoundingBox(buf, box, Some(names), fontSizeMult = Some(1.4f)) val tempDirPath = System.getProperty("java.io.tmpdir") ImageIO.write(buf, "png", new File(tempDirPath + "/inputImages/out2.png")) - logger.info(s"converted image stored in ${tempDirPath + "/inputImages/out2.png"}") + logger.debug(s"converted image stored in ${tempDirPath + "/inputImages/out2.png"}") for (coord <- box) { val topLeft = buf.getRGB(coord("xmin"), coord("ymin")) val downLeft = buf.getRGB(coord("xmin"), coord("ymax")) diff --git a/scala-package/core/src/test/scala/org/apache/mxnet/KVStoreSuite.scala b/scala-package/core/src/test/scala/org/apache/mxnet/KVStoreSuite.scala index 2e1b36c5b162..3eb61414bac1 100644 --- a/scala-package/core/src/test/scala/org/apache/mxnet/KVStoreSuite.scala +++ b/scala-package/core/src/test/scala/org/apache/mxnet/KVStoreSuite.scala @@ -67,9 +67,6 @@ class KVStoreSuite extends FunSuite with BeforeAndAfterAll { val kv = KVStore.create() val updater = new MXKVStoreUpdater { override def update(key: Int, input: NDArray, stored: NDArray): Unit = { - // scalastyle:off println - println(s"update on key $key") - // scalastyle:on println stored += input * 2 } override def dispose(): Unit = {} diff --git a/scala-package/core/src/test/scala/org/apache/mxnet/ModelParallelSuite.scala b/scala-package/core/src/test/scala/org/apache/mxnet/ModelParallelSuite.scala index 8ed1dc4c2332..2962e3b4781c 100644 --- a/scala-package/core/src/test/scala/org/apache/mxnet/ModelParallelSuite.scala +++ b/scala-package/core/src/test/scala/org/apache/mxnet/ModelParallelSuite.scala @@ -58,11 +58,6 @@ class ModelParallelSuite extends FunSuite with BeforeAndAfterAll { val arrGrad2 = arrGrad.map(_.copyTo(ctx1)) val exec2 = net.bind(ctx1, args = arr2, argsGrad = arrGrad2) - // Show the execution plan that involves copynode - // scalastyle:off println - print(exec1.debugStr) - // scalastyle:on println - exec1.forward() exec2.forward() assert(reldiff(exec1.outputs(0).copyTo(ctx1), diff --git a/scala-package/core/src/test/scala/org/apache/mxnet/OperatorSuite.scala b/scala-package/core/src/test/scala/org/apache/mxnet/OperatorSuite.scala index dd5f96f980a6..deb149985ce8 100644 --- a/scala-package/core/src/test/scala/org/apache/mxnet/OperatorSuite.scala +++ b/scala-package/core/src/test/scala/org/apache/mxnet/OperatorSuite.scala @@ -614,9 +614,6 @@ class OperatorSuite extends FunSuite with BeforeAndAfterAll val embed = Symbol.Embedding(name = "embed")()( Map("data" -> data, "input_dim" -> inDim, "output_dim" -> outDim)) // TODO - // scalastyle:off println - println(s"Embeded symbol: ${embed.toJson}") - // scalastyle:on println } // check ops handle duplicate input correctly. @@ -983,9 +980,6 @@ class OperatorSuite extends FunSuite with BeforeAndAfterAll test("batch norm") { val data = Symbol.Variable("data") val test = Symbol.BatchNorm(name = "bn")()(Map("data" -> data, "fix_gamma" -> "False")) - // scalastyle:off println - println(s"BatchNorm: ${test.toJson}") - // scalastyle:on println // TODO: check numeric gradient } diff --git a/scala-package/core/src/test/scala/org/apache/mxnet/SymbolSuite.scala b/scala-package/core/src/test/scala/org/apache/mxnet/SymbolSuite.scala index d134c83ff7e7..415be5122c95 100644 --- a/scala-package/core/src/test/scala/org/apache/mxnet/SymbolSuite.scala +++ b/scala-package/core/src/test/scala/org/apache/mxnet/SymbolSuite.scala @@ -32,14 +32,8 @@ class SymbolSuite extends FunSuite with BeforeAndAfterAll { var net2 = Symbol.FullyConnected(name = "fc3")()(Map("num_hidden" -> 10)) net2 = Symbol.Activation()()(Map("data" -> net2, "act_type" -> "relu")) net2 = Symbol.FullyConnected(name = "fc4")()(Map("data" -> net2, "num_hidden" -> 20)) - // scalastyle:off println - println(s"net2 debug info:\n${net2.debugStr}") - // scalastyle:on println val composed = net2(name = "composed", Map("fc3_data" -> net1)) - // scalastyle:off println - println(s"composed debug info:\n${composed.debugStr}") - // scalastyle:on println val multiOut = Symbol.Group(composed, net1) assert(multiOut.listOutputs().length === 2) } @@ -77,10 +71,6 @@ class SymbolSuite extends FunSuite with BeforeAndAfterAll { val lam = Symbol.Variable("lam") val rnd = Symbol.random.poisson(lam = Some(lam), shape = Some(Shape(2, 2))) val rnd2 = Symbol.random.poisson(lam = Some(1f), shape = Some(Shape(2, 2))) - // scalastyle:off println - println(s"Symbol.random.poisson debug info: ${rnd.debugStr}") - println(s"Symbol.random.poisson debug info: ${rnd2.debugStr}") - // scalastyle:on println } test("Symbol random module is generated properly - special case of 'normal'") { @@ -88,9 +78,5 @@ class SymbolSuite extends FunSuite with BeforeAndAfterAll { val scale = Symbol.Variable("scale") val rnd = Symbol.random.normal(mu = Some(loc), sigma = Some(scale), shape = Some(Shape(2, 2))) val rnd2 = Symbol.random.normal(mu = Some(1f), sigma = Some(2f), shape = Some(Shape(2, 2))) - // scalastyle:off println - println(s"Symbol.random.sample_normal debug info: ${rnd.debugStr}") - println(s"Symbol.random.random_normal debug info: ${rnd2.debugStr}") - // scalastyle:on println } } diff --git a/scala-package/macros/src/main/scala/org/apache/mxnet/GeneratorBase.scala b/scala-package/macros/src/main/scala/org/apache/mxnet/GeneratorBase.scala index 498c4e943669..b2033f529c65 100644 --- a/scala-package/macros/src/main/scala/org/apache/mxnet/GeneratorBase.scala +++ b/scala-package/macros/src/main/scala/org/apache/mxnet/GeneratorBase.scala @@ -116,7 +116,7 @@ private[mxnet] abstract class GeneratorBase { */ protected def structGeneration(c: blackbox.Context) (funcDef: List[c.universe.DefDef], annottees: c.Expr[Any]*) - : c.Expr[Any] = { + : c.Expr[Nothing] = { import c.universe._ val inputs = annottees.map(_.tree).toList // pattern match on the inputs diff --git a/scala-package/macros/src/main/scala/org/apache/mxnet/NDArrayMacro.scala b/scala-package/macros/src/main/scala/org/apache/mxnet/NDArrayMacro.scala index c18694b59bf6..f5b8bce11cf5 100644 --- a/scala-package/macros/src/main/scala/org/apache/mxnet/NDArrayMacro.scala +++ b/scala-package/macros/src/main/scala/org/apache/mxnet/NDArrayMacro.scala @@ -22,15 +22,16 @@ import scala.language.experimental.macros import scala.reflect.macros.blackbox private[mxnet] class AddNDArrayFunctions(isContrib: Boolean) extends StaticAnnotation { - private[mxnet] def macroTransform(annottees: Any*) = macro NDArrayMacro.addDefs + private[mxnet] def macroTransform(annottees: Any*): Any = macro NDArrayMacro.addDefs } private[mxnet] class AddNDArrayAPIs(isContrib: Boolean) extends StaticAnnotation { - private[mxnet] def macroTransform(annottees: Any*) = macro TypedNDArrayAPIMacro.typeSafeAPIDefs + private[mxnet] def macroTransform(annottees: Any*): Any = + macro TypedNDArrayAPIMacro.typeSafeAPIDefs } private[mxnet] class AddNDArrayRandomAPIs(isContrib: Boolean) extends StaticAnnotation { - private[mxnet] def macroTransform(annottees: Any*) = + private[mxnet] def macroTransform(annottees: Any*): Any = macro TypedNDArrayRandomAPIMacro.typeSafeAPIDefs } @@ -39,7 +40,7 @@ private[mxnet] class AddNDArrayRandomAPIs(isContrib: Boolean) extends StaticAnno */ private[mxnet] object NDArrayMacro extends GeneratorBase { - def addDefs(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { + def addDefs(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Nothing] = { import c.universe._ val isContrib: Boolean = c.prefix.tree match { case q"new AddNDArrayFunctions($b)" => c.eval[Boolean](c.Expr(b)) @@ -49,7 +50,7 @@ private[mxnet] object NDArrayMacro extends GeneratorBase { } private def impl(c: blackbox.Context) - (isContrib: Boolean, annottees: c.Expr[Any]*): c.Expr[Any] = { + (isContrib: Boolean, annottees: c.Expr[Any]*): c.Expr[Nothing] = { import c.universe._ val functions = functionsToGenerate(isSymbol = false, isContrib) @@ -82,7 +83,7 @@ private[mxnet] object NDArrayMacro extends GeneratorBase { */ private[mxnet] object TypedNDArrayAPIMacro extends GeneratorBase { - def typeSafeAPIDefs(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { + def typeSafeAPIDefs(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Nothing] = { import c.universe._ val isContrib: Boolean = c.prefix.tree match { case q"new AddNDArrayAPIs($b)" => c.eval[Boolean](c.Expr(b)) @@ -148,7 +149,7 @@ private[mxnet] object TypedNDArrayAPIMacro extends GeneratorBase { private[mxnet] object TypedNDArrayRandomAPIMacro extends GeneratorBase with RandomHelpers { - def typeSafeAPIDefs(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { + def typeSafeAPIDefs(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Nothing] = { // Note: no contrib managed in this module val functionDefs = typeSafeRandomFunctionsToGenerate(isSymbol = false) diff --git a/scala-package/macros/src/main/scala/org/apache/mxnet/SymbolMacro.scala b/scala-package/macros/src/main/scala/org/apache/mxnet/SymbolMacro.scala index 7ec80b9c066c..06b567c3d2d4 100644 --- a/scala-package/macros/src/main/scala/org/apache/mxnet/SymbolMacro.scala +++ b/scala-package/macros/src/main/scala/org/apache/mxnet/SymbolMacro.scala @@ -23,15 +23,16 @@ import scala.language.experimental.macros import scala.reflect.macros.blackbox private[mxnet] class AddSymbolFunctions(isContrib: Boolean) extends StaticAnnotation { - private[mxnet] def macroTransform(annottees: Any*) = macro SymbolMacro.addDefs + private[mxnet] def macroTransform(annottees: Any*): Any = macro SymbolMacro.addDefs } private[mxnet] class AddSymbolAPIs(isContrib: Boolean) extends StaticAnnotation { - private[mxnet] def macroTransform(annottees: Any*) = macro TypedSymbolAPIMacro.typeSafeAPIDefs + private[mxnet] def macroTransform(annottees: Any*): Any = + macro TypedSymbolAPIMacro.typeSafeAPIDefs } private[mxnet] class AddSymbolRandomAPIs(isContrib: Boolean) extends StaticAnnotation { - private[mxnet] def macroTransform(annottees: Any*) = + private[mxnet] def macroTransform(annottees: Any*): Any = macro TypedSymbolRandomAPIMacro.typeSafeAPIDefs } @@ -40,7 +41,7 @@ private[mxnet] class AddSymbolRandomAPIs(isContrib: Boolean) extends StaticAnnot */ private[mxnet] object SymbolMacro extends GeneratorBase { - def addDefs(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { + def addDefs(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Nothing] = { import c.universe._ val isContrib: Boolean = c.prefix.tree match { case q"new AddSymbolFunctions($b)" => c.eval[Boolean](c.Expr(b)) @@ -50,7 +51,7 @@ private[mxnet] object SymbolMacro extends GeneratorBase { } private def impl(c: blackbox.Context) - (isContrib: Boolean, annottees: c.Expr[Any]*): c.Expr[Any] = { + (isContrib: Boolean, annottees: c.Expr[Any]*): c.Expr[Nothing] = { import c.universe._ val functions = functionsToGenerate(isSymbol = false, isContrib) @@ -76,7 +77,7 @@ private[mxnet] object SymbolMacro extends GeneratorBase { */ private[mxnet] object TypedSymbolAPIMacro extends GeneratorBase { - def typeSafeAPIDefs(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { + def typeSafeAPIDefs(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Nothing] = { import c.universe._ val isContrib: Boolean = c.prefix.tree match { case q"new AddSymbolAPIs($b)" => c.eval[Boolean](c.Expr(b)) @@ -140,7 +141,7 @@ private[mxnet] object TypedSymbolAPIMacro extends GeneratorBase { private[mxnet] object TypedSymbolRandomAPIMacro extends GeneratorBase with RandomHelpers { - def typeSafeAPIDefs(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { + def typeSafeAPIDefs(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Nothing] = { val functionDefs = typeSafeRandomFunctionsToGenerate(isSymbol = true) .map(f => buildTypedFunction(c)(f)) diff --git a/scala-package/macros/src/main/scala/org/apache/mxnet/javaapi/JavaNDArrayMacro.scala b/scala-package/macros/src/main/scala/org/apache/mxnet/javaapi/JavaNDArrayMacro.scala index fa3565b4fb8e..9bf0818c14a4 100644 --- a/scala-package/macros/src/main/scala/org/apache/mxnet/javaapi/JavaNDArrayMacro.scala +++ b/scala-package/macros/src/main/scala/org/apache/mxnet/javaapi/JavaNDArrayMacro.scala @@ -25,18 +25,16 @@ import scala.language.experimental.macros import scala.reflect.macros.blackbox private[mxnet] class AddJNDArrayAPIs(isContrib: Boolean) extends StaticAnnotation { - private[mxnet] def macroTransform(annottees: Any*) = macro JavaNDArrayMacro.typeSafeAPIDefs + private[mxnet] def macroTransform(annottees: Any*): Any = macro JavaNDArrayMacro.typeSafeAPIDefs } private[mxnet] object JavaNDArrayMacro extends GeneratorBase { - // scalastyle:off havetype - def typeSafeAPIDefs(c: blackbox.Context)(annottees: c.Expr[Any]*) = { + def typeSafeAPIDefs(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Nothing] = { typeSafeAPIImpl(c)(annottees: _*) } - // scalastyle:off havetype - private def typeSafeAPIImpl(c: blackbox.Context)(annottees: c.Expr[Any]*) : c.Expr[Any] = { + private def typeSafeAPIImpl(c: blackbox.Context)(annottees: c.Expr[Any]*) : c.Expr[Nothing] = { import c.universe._ val isContrib: Boolean = c.prefix.tree match { diff --git a/scala-package/spark/src/main/scala/org/apache/mxnet/spark/MXNDArray.scala b/scala-package/spark/src/main/scala/org/apache/mxnet/spark/MXNDArray.scala index a18c47d78741..0d4c18c2b8e9 100644 --- a/scala-package/spark/src/main/scala/org/apache/mxnet/spark/MXNDArray.scala +++ b/scala-package/spark/src/main/scala/org/apache/mxnet/spark/MXNDArray.scala @@ -20,7 +20,7 @@ package org.apache.mxnet.spark import org.apache.mxnet.NDArray /** - * A wrapper for serialize & deserialize
[[org.apache.mxnet.NDArray]]
in spark job + * A wrapper for serialize & deserialize ``org.apache.mxnet.NDArray`` in spark job * @author Yizhi Liu */ class MXNDArray(@transient private var ndArray: NDArray) extends Serializable { diff --git a/scala-package/spark/src/main/scala/org/apache/mxnet/spark/MXNetModel.scala b/scala-package/spark/src/main/scala/org/apache/mxnet/spark/MXNetModel.scala index 2c4c8fe42780..234e9a597cf5 100644 --- a/scala-package/spark/src/main/scala/org/apache/mxnet/spark/MXNetModel.scala +++ b/scala-package/spark/src/main/scala/org/apache/mxnet/spark/MXNetModel.scala @@ -23,7 +23,7 @@ import org.apache.spark.SparkContext import org.apache.spark.mllib.linalg.Vector /** - * Wrapper for
[[org.apache.mxnet.Model]]
which used in Spark application + * Wrapper for ``org.apache.mxnet.Model`` which used in Spark application * @author Yizhi Liu */ class MXNetModel private[mxnet]( From 2c5d7f768bdd1599c35f1a3cd1266efd051a9986 Mon Sep 17 00:00:00 2001 From: Tao Lv Date: Thu, 11 Apr 2019 06:41:32 +0800 Subject: [PATCH 06/16] Optimize transpose operator with MKL-DNN (#14545) * add mkldnn transpose * general transpose * support mkldnn format * fix lint * address comments * add unit test * add comments * retrigger CI --- src/operator/nn/mkldnn/mkldnn_base-inl.h | 2 + src/operator/nn/mkldnn/mkldnn_ops-inl.h | 6 + src/operator/nn/mkldnn/mkldnn_transpose.cc | 161 +++++++++++++++++++++ src/operator/tensor/matrix_op-inl.h | 15 ++ src/operator/tensor/matrix_op.cc | 34 +++++ tests/python/mkl/test_mkldnn.py | 15 ++ 6 files changed, 233 insertions(+) create mode 100644 src/operator/nn/mkldnn/mkldnn_transpose.cc diff --git a/src/operator/nn/mkldnn/mkldnn_base-inl.h b/src/operator/nn/mkldnn/mkldnn_base-inl.h index 0a89c0f31981..a460e33fa548 100644 --- a/src/operator/nn/mkldnn/mkldnn_base-inl.h +++ b/src/operator/nn/mkldnn/mkldnn_base-inl.h @@ -175,12 +175,14 @@ struct ConvolutionParam; struct DeconvolutionParam; struct SoftmaxParam; struct SoftmaxOutputParam; +struct TransposeParam; bool SupportMKLDNNAct(const ActivationParam& param); bool SupportMKLDNNAct(const ActivationParam& param, const NDArray &input); bool SupportMKLDNNConv(const ConvolutionParam& params, const NDArray &input); bool SupportMKLDNNDeconv(const DeconvolutionParam& params, const NDArray &input); bool SupportMKLDNNSoftmax(const SoftmaxParam& param); bool SupportMKLDNNSoftmaxOutput(const SoftmaxOutputParam ¶m); +bool SupportMKLDNNTranspose(const TransposeParam& param, const NDArray &data); } // namespace op static int GetTypeSize(int dtype) { diff --git a/src/operator/nn/mkldnn/mkldnn_ops-inl.h b/src/operator/nn/mkldnn/mkldnn_ops-inl.h index 39f26325b2a5..f3f61b457507 100644 --- a/src/operator/nn/mkldnn/mkldnn_ops-inl.h +++ b/src/operator/nn/mkldnn/mkldnn_ops-inl.h @@ -113,6 +113,12 @@ void MKLDNNActivationBackward(const nnvm::NodeAttrs& attrs, const OpContext &ctx void MKLDNNSum(const mkldnn::memory &arr1, const mkldnn::memory &arr2, const mkldnn::memory &out); +void MKLDNNTransposeForward(const nnvm::NodeAttrs& attrs, + const OpContext &ctx, + const NDArray &data, + const OpReqType &req, + const NDArray &output); + } // namespace op } // namespace mxnet #endif // MXNET_USE_MKLDNN == 1 diff --git a/src/operator/nn/mkldnn/mkldnn_transpose.cc b/src/operator/nn/mkldnn/mkldnn_transpose.cc new file mode 100644 index 000000000000..0986d0616f75 --- /dev/null +++ b/src/operator/nn/mkldnn/mkldnn_transpose.cc @@ -0,0 +1,161 @@ +/* + * 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. + */ + +/*! + * \file mkldnn_transpose.cc + * \brief Implement transpose operator via MKL-DNN reorder primitive + * \author Tao Lv +*/ + +#if MXNET_USE_MKLDNN == 1 + +#include +#include "../../tensor/matrix_op-inl.h" + +namespace mxnet { +namespace op { + +bool SupportMKLDNNTranspose(const TransposeParam& param, + const NDArray &data) { + auto data_ndim = data.shape().ndim(); + + if (data_ndim > 4 || data.dtype() != mshadow::kFloat32) + return false; + + return true; +} + +typedef ParamOpSign MKLDNNTransposeSignature; + +class MKLDNNTransposeForward { + std::shared_ptr data_; + std::shared_ptr out_; + std::shared_ptr dst_pd_; + std::shared_ptr transpose_; + + public: + MKLDNNTransposeForward(const TransposeParam& param, + const NDArray &data) { + auto shape = data.shape(); + auto data_ndim = shape.ndim(); + auto axes_ndim = param.axes.ndim(); + auto axes = mxnet::TShape(data_ndim); + if (axes_ndim == 0) { + for (size_t i = 0; i < data_ndim; i++) { + axes[i] = data_ndim - i - 1; + } + } else { + axes = param.axes; + } + + auto engine = CpuEngine::Get()->get_engine(); + auto in_mem = data.GetMKLDNNData(); + auto src_pd = in_mem->get_primitive_desc(); + data_ = std::make_shared(src_pd, nullptr); + + // destination + // Not all formats are well defined with a certain name in MKL-DNN. + // For example, transpose(NCHW, (0, 2, 1, 3)) -> NHCW, which is not explicitly defined in + // MKL-DNN. To support general transposing, we need create destination format from scratch. + mkldnn_memory_desc_t dst_fmt; + dst_fmt.primitive_kind = mkldnn_memory; + dst_fmt.ndims = data_ndim; + dst_fmt.data_type = mkldnn_f32; + dst_fmt.format = mkldnn_blocked; + + for (size_t i = 0; i < data_ndim; i++) + dst_fmt.dims[i] = shape[i]; + + unsigned int total_stride = 1; + for (int i = data_ndim - 1; i >= 0; i--) { + dst_fmt.layout_desc.blocking.padding_dims[i] = shape[i]; + dst_fmt.layout_desc.blocking.block_dims[i] = 1; + dst_fmt.layout_desc.blocking.offset_padding_to_data[i]= 0; + // strides[0]: stride between the first elements of adjacent blocks. + dst_fmt.layout_desc.blocking.strides[0][axes[i]] = total_stride; + // strides[1]: strides between elements in the same block. + dst_fmt.layout_desc.blocking.strides[1][axes[i]] = 1; + + total_stride *= shape[axes[i]]; + } + + dst_fmt.layout_desc.blocking.offset_padding = 0; + dst_pd_ = std::make_shared(dst_fmt, engine); + out_ = std::make_shared(*dst_pd_, nullptr); + + transpose_ = std::make_shared(*data_, *out_); + } + + void SetNewMem(const NDArray &data, const NDArray &output) { + if (data.IsMKLDNNData()) { + this->data_->set_data_handle(data.GetMKLDNNData()->get_data_handle()); + } else { + MSHADOW_TYPE_SWITCH(data.dtype(), DTYPE, { + this->data_->set_data_handle(data.data().dptr()); + }); + } + + CHECK(!output.IsMKLDNNData()); + MSHADOW_TYPE_SWITCH(output.dtype(), DTYPE, { + this->out_->set_data_handle(output.data().dptr()); + }); + } + + const mkldnn::reorder &GetFwd() const { + return *transpose_; + } +}; + +static MKLDNNTransposeForward &GetTransposeForward(const TransposeParam& param, + const NDArray &data) { +#if DMLC_CXX11_THREAD_LOCAL + static thread_local std::unordered_map fwds; +#else + static MX_THREAD_LOCAL std::unordered_map fwds; +#endif + MKLDNNTransposeSignature key(param); + key.AddSign(data); + + auto it = fwds.find(key); + if (it == fwds.end()) { + MKLDNNTransposeForward fwd(param, data); + it = AddToCache(&fwds, key, fwd); + } + return it->second; +} + +void MKLDNNTransposeForward(const nnvm::NodeAttrs& attrs, + const OpContext &ctx, + const NDArray &data, + const OpReqType &req, + const NDArray &output) { + const TransposeParam& param = nnvm::get(attrs.parsed); + + auto stream = MKLDNNStream::Get(); + auto fwd = GetTransposeForward(param, data); + + fwd.SetNewMem(data, output); + stream->RegisterPrim(fwd.GetFwd()); + stream->Submit(); +} +} // namespace op +} // namespace mxnet +#endif diff --git a/src/operator/tensor/matrix_op-inl.h b/src/operator/tensor/matrix_op-inl.h index 5eecda622729..fa108158b5c9 100644 --- a/src/operator/tensor/matrix_op-inl.h +++ b/src/operator/tensor/matrix_op-inl.h @@ -238,6 +238,10 @@ struct TransposeParam : public dmlc::Parameter { DMLC_DECLARE_FIELD(axes).set_default(mxnet::TShape()) .describe("Target axis order. By default the axes will be inverted."); } + + bool operator==(const TransposeParam &other) const { + return this->axes == other.axes; + } }; template @@ -2841,4 +2845,15 @@ inline uint32_t SplitNumOutputs(const NodeAttrs& attrs) { } // namespace op } // namespace mxnet +namespace std { +template<> +struct hash { + size_t operator()(const mxnet::op::TransposeParam& val) { + size_t ret = 0; + ret = dmlc::HashCombine(ret, val.axes); + return ret; + } +}; +} // namespace std + #endif // MXNET_OPERATOR_TENSOR_MATRIX_OP_INL_H_ diff --git a/src/operator/tensor/matrix_op.cc b/src/operator/tensor/matrix_op.cc index 3bca330f98b0..1431fef13594 100644 --- a/src/operator/tensor/matrix_op.cc +++ b/src/operator/tensor/matrix_op.cc @@ -339,6 +339,35 @@ Example:: }) .add_argument("data", "NDArray-or-Symbol", "Input array."); +#if MXNET_USE_MKLDNN == 1 +static void TransposeComputeExCPU(const nnvm::NodeAttrs& attrs, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + const TransposeParam& param = nnvm::get(attrs.parsed); + CHECK_EQ(req[0], kWriteTo) << "Transpose does not support inplace"; + CHECK_EQ(inputs.size(), 1U); + CHECK_EQ(outputs.size(), 1U); + + if (SupportMKLDNNTranspose(param, inputs[0])) { + MKLDNNTransposeForward(attrs, ctx, inputs[0], req[0], outputs[0]); + return; + } + FallBackCompute(Transpose, attrs, ctx, inputs, req, outputs); +} + +inline static bool TransposeStorageType(const nnvm::NodeAttrs& attrs, + const int dev_mask, + DispatchMode* dispatch_mode, + std::vector* in_attrs, + std::vector* out_attrs) { + CHECK_EQ(in_attrs->size(), 1U); + CHECK_EQ(out_attrs->size(), 1U); + return MKLDNNStorageType(attrs, dev_mask, true, dispatch_mode, in_attrs, out_attrs); +} +#endif + NNVM_REGISTER_OP(transpose) .describe(R"code(Permutes the dimensions of an array. @@ -393,6 +422,11 @@ Examples:: } }) .set_attr("FCompute", Transpose) +#if MXNET_USE_MKLDNN == 1 +.set_attr("TIsMKLDNN", true) +.set_attr("FComputeEx", TransposeComputeExCPU) +.set_attr("FInferStorageType", TransposeStorageType) +#endif .add_argument("data", "NDArray-or-Symbol", "Source input") .add_arguments(TransposeParam::__FIELDS__()); diff --git a/tests/python/mkl/test_mkldnn.py b/tests/python/mkl/test_mkldnn.py index 01ba03cab7cd..0610b606c201 100644 --- a/tests/python/mkl/test_mkldnn.py +++ b/tests/python/mkl/test_mkldnn.py @@ -473,6 +473,21 @@ def backward(self, req, out_grad, in_data, out_data, in_grad, aux): exec1 = custom.bind(mx.cpu(), args={'data': mx.nd.ones([10,3,96,96]), 'conv_weight': mx.nd.ones([8,3,5,5])}) exec1.forward()[0].wait_to_read() +@with_seed() +def test_conv_transpose(): + axes = [(0,2,1,3), (0,2,3,1), (1,2,3,0), (3,2,1,0)] + a = np.random.rand(10, 16, 50, 50) + b = np.random.rand(32, 16, 3, 3) + x = mx.nd.array(a) + w = mx.nd.array(b) + y = mx.nd.Convolution(data=x, weight=w, kernel=(3, 3), num_group=1, num_filter=32, no_bias=True) + for axis in axes: + t = mx.nd.transpose(y, axis) + t.wait_to_read() + s = y.asnumpy() + n = np.transpose(s, axis) + np.allclose(t.asnumpy(), n) + if __name__ == '__main__': install.test_mkldnn_install() From 52e2e8ed630886b5e8edce20b960fa7d62620287 Mon Sep 17 00:00:00 2001 From: Zach Kimberg Date: Thu, 11 Apr 2019 10:07:52 -0700 Subject: [PATCH 07/16] Fix scalastyle (#14669) --- .../scala/org/apache/mxnet/FeedForward.scala | 15 +- .../apache/mxnet/module/BucketingModule.scala | 54 +++---- .../org/apache/mxnet/module/Module.scala | 153 +++++++++--------- .../mxnet/module/SequentialModule.scala | 135 ++++++++-------- .../apache/mxnetexamples/rnn/BucketIo.scala | 8 +- .../apache/mxnet/spark/utils/Network.scala | 22 ++- .../mxnet/spark/SharedSparkContext.scala | 12 +- 7 files changed, 194 insertions(+), 205 deletions(-) diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/FeedForward.scala b/scala-package/core/src/main/scala/org/apache/mxnet/FeedForward.scala index 2ed9d8cfbb84..2b1765531824 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/FeedForward.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/FeedForward.scala @@ -180,6 +180,7 @@ class FeedForward private( // Initialize the predictor module for running prediction. private def initPredictor(inputShapes: Map[String, Shape]): Unit = { + var shouldInit = true if (this.predExec != null) { val (argShapes, _, _) = symbol.inferShape(inputShapes) require(argShapes != null, "Shape inference failed." + @@ -187,14 +188,16 @@ class FeedForward private( s"and aux states ${symbol.listAuxiliaryStates()}") val predShapes = this.predExec.argArrays.map(_.shape) if (argShapes.sameElements(predShapes)) { - return + shouldInit = false } } - // for now only use the first device - val predExec = symbol.simpleBind(ctx(0), gradReq = "null", shapeDict = inputShapes) - predExec.copyParamsFrom(_argParams, _auxParams) - ExecutorManager.checkArguments(symbol) - this.predExec = predExec + if(shouldInit) { + // for now only use the first device + val predExec = symbol.simpleBind(ctx(0), gradReq = "null", shapeDict = inputShapes) + predExec.copyParamsFrom(_argParams, _auxParams) + ExecutorManager.checkArguments(symbol) + this.predExec = predExec + } } // Initialize the iterator given input. diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/module/BucketingModule.scala b/scala-package/core/src/main/scala/org/apache/mxnet/module/BucketingModule.scala index 1ac798e1b617..41a6f69394d2 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/module/BucketingModule.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/module/BucketingModule.scala @@ -173,14 +173,13 @@ class BucketingModule(symGen: AnyRef => (Symbol, IndexedSeq[String], IndexedSeq[ allowMissing: Boolean = false, forceInit: Boolean = false, allowExtra: Boolean = false): Unit = { - if (paramsInitialized && !forceInit) { - return + if (!paramsInitialized || forceInit) { + require(binded, "call bind before initializing the parameters") + this._currModule.initParams(initializer, argParams, auxParams, + allowMissing, forceInit, allowExtra) + this.paramsDirty = false + this.paramsInitialized = true } - require(binded, "call bind before initializing the parameters") - this._currModule.initParams(initializer, argParams, auxParams, - allowMissing, forceInit, allowExtra) - this.paramsDirty = false - this.paramsInitialized = true } /** @@ -218,28 +217,27 @@ class BucketingModule(symGen: AnyRef => (Symbol, IndexedSeq[String], IndexedSeq[ if (this.binded) { logger.warn("Already bound, ignoring bind()") - return - } + } else { + require(sharedModule.isEmpty, + "sharedModule for BucketingModule is not supported") - require(sharedModule.isEmpty, - "sharedModule for BucketingModule is not supported") - - this.forTraining = forTraining - this.inputsNeedGrad = inputsNeedGrad - this.binded = true - - val (sym, dNames, lNames) = this.symGen(this.defaultBucketKey) - val module = new Module(sym, dNames, lNames, this.contexts, - this.workLoadList, this.fixedParamNames) - module.bind(dataShapes, labelShapes, forTraining, inputsNeedGrad, - forceRebind = false, sharedModule = None, gradReq) - this._currModule = module - this._currBucketKey = this.defaultBucketKey - this._buckets(this.defaultBucketKey) = module - - // copy back saved params, if already initialized - if (this.paramsInitialized) { - this.setParams(argParams, auxParams) + this.forTraining = forTraining + this.inputsNeedGrad = inputsNeedGrad + this.binded = true + + val (sym, dNames, lNames) = this.symGen(this.defaultBucketKey) + val module = new Module(sym, dNames, lNames, this.contexts, + this.workLoadList, this.fixedParamNames) + module.bind(dataShapes, labelShapes, forTraining, inputsNeedGrad, + forceRebind = false, sharedModule = None, gradReq) + this._currModule = module + this._currBucketKey = this.defaultBucketKey + this._buckets(this.defaultBucketKey) = module + + // copy back saved params, if already initialized + if (this.paramsInitialized) { + this.setParams(argParams, auxParams) + } } } diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/module/Module.scala b/scala-package/core/src/main/scala/org/apache/mxnet/module/Module.scala index 97df3dcb307d..3255d9346b80 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/module/Module.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/module/Module.scala @@ -121,36 +121,35 @@ class Module(symbolVar: Symbol, allowMissing: Boolean = false, forceInit: Boolean = false, allowExtra: Boolean = false): Unit = { - if (paramsInitialized && !forceInit) { - return - } - require(binded, "call bind before initializing the parameters") + if (!paramsInitialized || forceInit) { + require(binded, "call bind before initializing the parameters") - if (this.argParams == null) { - val paramArrays = - execGroup.paramArrays.map(nds => NDArray.zeros(nds(0).shape, dtype = nds(0).dtype)) - this.argParams = this.paramNames.zip(paramArrays).toMap - } + if (this.argParams == null) { + val paramArrays = + execGroup.paramArrays.map(nds => NDArray.zeros(nds(0).shape, dtype = nds(0).dtype)) + this.argParams = this.paramNames.zip(paramArrays).toMap + } - if (this.auxParams == null) { - val auxArrays = - execGroup.auxArrays.map(nds => NDArray.zeros(nds(0).shape, dtype = nds(0).dtype)) - this.auxParams = this.auxNames.zip(auxArrays).toMap - } + if (this.auxParams == null) { + val auxArrays = + execGroup.auxArrays.map(nds => NDArray.zeros(nds(0).shape, dtype = nds(0).dtype)) + this.auxParams = this.auxNames.zip(auxArrays).toMap + } - this.argParams.foreach { case (name, arr) => - impl(name, arr, allowMissing, Option(initializer), argParams) - } + this.argParams.foreach { case (name, arr) => + impl(name, arr, allowMissing, Option(initializer), argParams) + } - this.auxParams.foreach { case (name, arr) => - impl(name, arr, allowMissing, Option(initializer), auxParams) - } + this.auxParams.foreach { case (name, arr) => + impl(name, arr, allowMissing, Option(initializer), auxParams) + } - this.paramsInitialized = true - this.paramsDirty = false + this.paramsInitialized = true + this.paramsDirty = false - // copy the initialized parameters to devices - this.execGroup.setParams(this.argParams, this.auxParams, allowExtra = allowExtra) + // copy the initialized parameters to devices + this.execGroup.setParams(this.argParams, this.auxParams, allowExtra = allowExtra) + } } // Internal helper for parameter initialization @@ -246,64 +245,64 @@ class Module(symbolVar: Symbol, if (binded) { logger.warn("Already binded, ignoring bind()") - return - } + } else { + this.forTraining = forTraining + this.inputsNeedGrad = inputsNeedGrad + this.binded = true - this.forTraining = forTraining - this.inputsNeedGrad = inputsNeedGrad - this.binded = true + if (!forTraining) { + require(!inputsNeedGrad, "Invalid inputsNeedGrad (cannot be true if not forTraining)") + } else { + // this is not True, as some module might not contains a loss function + // that consumes the labels + // require(labelShapes != None) + } - if (!forTraining) { - require(!inputsNeedGrad, "Invalid inputsNeedGrad (cannot be true if not forTraining)") - } else { - // this is not True, as some module might not contains a loss function - // that consumes the labels - // require(labelShapes != None) - } + this.dataShapesVar = dataShapes + this.labelShapesVar = labelShapes - this.dataShapesVar = dataShapes - this.labelShapesVar = labelShapes - - val sharedGroup = - sharedModule.map(sharedModuleInst => { - require(sharedModuleInst.binded && sharedModuleInst.paramsInitialized, - s"bind() and initParams() must be called first on shared module.") - sharedModuleInst.execGroup - }) - - val inputTypes = this.dataShapesVar.map(dataDesc => (dataDesc.name, dataDesc.dtype)).toMap ++ - labelShapes.map(shapes => shapes.map(dataDesc => (dataDesc.name, dataDesc.dtype)).toMap) - .getOrElse(Map.empty[String, DType]) - - execGroup = new Builder(symbol, contexts, paramNames) - .setWorkLoadList(workLoads) - .setDataShapes(dataShapes) - .setLabelShapes(labelShapes.orNull) - .setForTraining(forTraining) - .setInputsNeedGrad(inputsNeedGrad) - .setSharedGroup(sharedGroup.orNull) - .setFixedParamNames(fixedParamNames.orNull) - .setGradReq(gradReq) - .setInputTypes(inputTypes) - .build() - - if (sharedModule.isDefined) { - paramsInitialized = true - argParams = sharedModule.get.argParams - auxParams = sharedModule.get.auxParams - } else if (paramsInitialized) { - // if the parameters are already initialized, we are re-binding - // so automatically copy the already initialized params - execGroup.setParams(argParams, auxParams) - } + val sharedGroup = + sharedModule.map(sharedModuleInst => { + require(sharedModuleInst.binded && sharedModuleInst.paramsInitialized, + s"bind() and initParams() must be called first on shared module.") + sharedModuleInst.execGroup + }) - sharedModule.foreach { - case sharedModuleInst: Module => - if (sharedModuleInst.optimizerInitialized) { - borrowOptimizer(sharedModuleInst) - } - case _ => + val inputTypes = this.dataShapesVar.map(dataDesc => (dataDesc.name, dataDesc.dtype)).toMap ++ + labelShapes.map(shapes => shapes.map(dataDesc => (dataDesc.name, dataDesc.dtype)).toMap) + .getOrElse(Map.empty[String, DType]) + + execGroup = new Builder(symbol, contexts, paramNames) + .setWorkLoadList(workLoads) + .setDataShapes(dataShapes) + .setLabelShapes(labelShapes.orNull) + .setForTraining(forTraining) + .setInputsNeedGrad(inputsNeedGrad) + .setSharedGroup(sharedGroup.orNull) + .setFixedParamNames(fixedParamNames.orNull) + .setGradReq(gradReq) + .setInputTypes(inputTypes) + .build() + + if (sharedModule.isDefined) { + paramsInitialized = true + argParams = sharedModule.get.argParams + auxParams = sharedModule.get.auxParams + } else if (paramsInitialized) { + // if the parameters are already initialized, we are re-binding + // so automatically copy the already initialized params + execGroup.setParams(argParams, auxParams) + } + + sharedModule.foreach { + case sharedModuleInst: Module => + if (sharedModuleInst.optimizerInitialized) { + borrowOptimizer(sharedModuleInst) + } + case _ => + } } + } /** diff --git a/scala-package/core/src/main/scala/org/apache/mxnet/module/SequentialModule.scala b/scala-package/core/src/main/scala/org/apache/mxnet/module/SequentialModule.scala index 2e506c08e548..3c3eeb97f201 100644 --- a/scala-package/core/src/main/scala/org/apache/mxnet/module/SequentialModule.scala +++ b/scala-package/core/src/main/scala/org/apache/mxnet/module/SequentialModule.scala @@ -154,38 +154,37 @@ class SequentialModule extends BaseModule { allowMissing: Boolean = false, forceInit: Boolean = false, allowExtra: Boolean = false): Unit = { - if (this.paramsInitialized && !forceInit) { - return - } - require(this.binded, "call bind before initializing the parameters") + if (!this.paramsInitialized || forceInit) { + require(this.binded, "call bind before initializing the parameters") - for (module <- this.modules) { - module.initParams(initializer = initializer, argParams = argParams, - auxParams = auxParams, allowMissing = allowMissing, - forceInit = forceInit, allowExtra = allowExtra) - } + for (module <- this.modules) { + module.initParams(initializer = initializer, argParams = argParams, + auxParams = auxParams, allowMissing = allowMissing, + forceInit = forceInit, allowExtra = allowExtra) + } - // Internal function to help checking duplicated names, - // make sure we do not have duplicated parameter names. - def checkName(knownNames: scala.collection.mutable.Map[String, Int], - newNames: Array[String], modules: ArrayBuffer[BaseModule], i: Int): Unit = { - for (name <- newNames) { - require(!knownNames.contains(name), s"Duplicated parameter names: " + - s"name $name in layer $i (${modules(i).getClass.getName}) is already " + - s"used in layer ${knownNames("name")}" + - s"(${modules(knownNames("name")).getClass.getName})") - knownNames(name) = i + // Internal function to help checking duplicated names, + // make sure we do not have duplicated parameter names. + def checkName(knownNames: scala.collection.mutable.Map[String, Int], + newNames: Array[String], modules: ArrayBuffer[BaseModule], i: Int): Unit = { + for (name <- newNames) { + require(!knownNames.contains(name), s"Duplicated parameter names: " + + s"name $name in layer $i (${modules(i).getClass.getName}) is already " + + s"used in layer ${knownNames("name")}" + + s"(${modules(knownNames("name")).getClass.getName})") + knownNames(name) = i + } } - } - val argNames = scala.collection.mutable.Map[String, Int]() - val auxNames = scala.collection.mutable.Map[String, Int]() - for ((module, iLayer) <- this.modules.zipWithIndex) { - val (argParams, auxParams) = module.getParams - checkName(argNames, argParams.keys.toArray, this.modules, iLayer) - checkName(auxNames, auxParams.keys.toArray, this.modules, iLayer) + val argNames = scala.collection.mutable.Map[String, Int]() + val auxNames = scala.collection.mutable.Map[String, Int]() + for ((module, iLayer) <- this.modules.zipWithIndex) { + val (argParams, auxParams) = module.getParams + checkName(argNames, argParams.keys.toArray, this.modules, iLayer) + checkName(auxNames, auxParams.keys.toArray, this.modules, iLayer) + } + this.paramsInitialized = true } - this.paramsInitialized = true } /** @@ -216,54 +215,54 @@ class SequentialModule extends BaseModule { gradReq: String = "write"): Unit = { if (this.binded && !forceRebind) { logger.warn(s"Already binded, ignoring bind()") - return - } - - if (inputsNeedGrad) { - require(forTraining, "inputsNeedGrad can be set only for training") - } - - require(sharedModule == None, "Shared module is not supported") - require(this.modules.length > 0, "Attempting to bind an empty SequentialModule") - - this.forTraining = forTraining - this.inputsNeedGrad = inputsNeedGrad - this.binded = true - - // the same label shapes are used for all chained modules - this.labelShapesVar = labelShapes + } else { + if (inputsNeedGrad) { + require(forTraining, "inputsNeedGrad can be set only for training") + } - var myDataShapes = dataShapes - var myLabelShapes = labelShapes - var anybodyEverNeedsLabel = false - for ((module, iLayer) <- this.modules.zipWithIndex) { - val meta = this.metas(iLayer) - if (meta.contains(META_TAKE_LABELS) && meta(META_TAKE_LABELS)) { - myLabelShapes = labelShapes - anybodyEverNeedsLabel = true - } else myLabelShapes = None - - val myInputsNeedGrad = if (inputsNeedGrad || (forTraining && iLayer > 0)) true else false - if (meta.contains(META_AUTO_WIRING) && meta(META_AUTO_WIRING)) { - val dataNames = module.dataNames - require(dataNames.length == myDataShapes.length, - s"dataNmes $dataNames and dataShapes $myDataShapes do not match") - myDataShapes = dataNames.zip(myDataShapes).map { case (newName, dataDes) => - DataDesc(newName, dataDes.shape) + require(sharedModule == None, "Shared module is not supported") + require(this.modules.length > 0, "Attempting to bind an empty SequentialModule") + + this.forTraining = forTraining + this.inputsNeedGrad = inputsNeedGrad + this.binded = true + + // the same label shapes are used for all chained modules + this.labelShapesVar = labelShapes + + var myDataShapes = dataShapes + var myLabelShapes = labelShapes + var anybodyEverNeedsLabel = false + for ((module, iLayer) <- this.modules.zipWithIndex) { + val meta = this.metas(iLayer) + if (meta.contains(META_TAKE_LABELS) && meta(META_TAKE_LABELS)) { + myLabelShapes = labelShapes + anybodyEverNeedsLabel = true + } else myLabelShapes = None + + val myInputsNeedGrad = if (inputsNeedGrad || (forTraining && iLayer > 0)) true else false + if (meta.contains(META_AUTO_WIRING) && meta(META_AUTO_WIRING)) { + val dataNames = module.dataNames + require(dataNames.length == myDataShapes.length, + s"dataNmes $dataNames and dataShapes $myDataShapes do not match") + myDataShapes = dataNames.zip(myDataShapes).map { case (newName, dataDes) => + DataDesc(newName, dataDes.shape) + } } - } - module.bind(myDataShapes, myLabelShapes, forTraining, myInputsNeedGrad, + module.bind(myDataShapes, myLabelShapes, forTraining, myInputsNeedGrad, forceRebind, sharedModule = None, gradReq) - // the output of the previous module is the data of the next module - myDataShapes = module.outputShapes.map{case (name, shape) => DataDesc(name, shape)} - } + // the output of the previous module is the data of the next module + myDataShapes = module.outputShapes.map{case (name, shape) => DataDesc(name, shape)} + } - if (!anybodyEverNeedsLabel) { - // then I do not need label either - this.labelShapesVar = None + if (!anybodyEverNeedsLabel) { + // then I do not need label either + this.labelShapesVar = None + } } + } /** diff --git a/scala-package/examples/src/main/scala/org/apache/mxnetexamples/rnn/BucketIo.scala b/scala-package/examples/src/main/scala/org/apache/mxnetexamples/rnn/BucketIo.scala index 6d414bb0328a..350e28cf8634 100644 --- a/scala-package/examples/src/main/scala/org/apache/mxnetexamples/rnn/BucketIo.scala +++ b/scala-package/examples/src/main/scala/org/apache/mxnetexamples/rnn/BucketIo.scala @@ -202,10 +202,10 @@ object BucketIo { labelBuf.set(labels.flatten) iBucket += 1 - val batchProvideData = { val tmp = ListMap("data" -> dataBuf.shape) - tmp ++ initStates.map(x => x._1 -> Shape(x._2._1, x._2._2)) - } - val batchProvideLabel = ListMap("softmax_label" -> labelBuf.shape) + val batchProvideData = IndexedSeq(DataDesc("data", dataBuf.shape, dataBuf.dtype)) ++ + initStates.map { + case (name, shape) => DataDesc(name, Shape(shape._1, shape._2), DType.Float32)} + val batchProvideLabel = IndexedSeq(DataDesc("softmax_label", labelBuf.shape, labelBuf.dtype)) val initStateArrays = initStates.map(x => NDArray.zeros(x._2._1, x._2._2)) new DataBatch(IndexedSeq(dataBuf.copy()) ++ initStateArrays, IndexedSeq(labelBuf.copy()), diff --git a/scala-package/spark/src/main/scala/org/apache/mxnet/spark/utils/Network.scala b/scala-package/spark/src/main/scala/org/apache/mxnet/spark/utils/Network.scala index c61229af0035..836901f69f8f 100644 --- a/scala-package/spark/src/main/scala/org/apache/mxnet/spark/utils/Network.scala +++ b/scala-package/spark/src/main/scala/org/apache/mxnet/spark/utils/Network.scala @@ -20,6 +20,7 @@ package org.apache.mxnet.spark.utils import java.io.IOException import java.net.{ServerSocket, NetworkInterface} import java.util.regex.Pattern +import scala.collection.JavaConverters._ /** * Helper functions to decide ip address / port @@ -33,19 +34,16 @@ object Network { "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$") def ipAddress: String = { - val interfaces = NetworkInterface.getNetworkInterfaces - while (interfaces.hasMoreElements) { - val interface = interfaces.nextElement - val addresses = interface.getInetAddresses - while (addresses.hasMoreElements) { - val address = addresses.nextElement - val ip = address.getHostAddress - if (!ip.startsWith("127.") && IPADDRESS_PATTERN.matcher(ip).matches()) { - return ip + val interfaces = NetworkInterface.getNetworkInterfaces.asScala + val interface = interfaces.toStream.flatMap( + _.getInetAddresses.asScala.toStream.flatMap( + address => { + val ip = address.getHostAddress + Option(ip).filter(ip => !ip.startsWith("127.") && IPADDRESS_PATTERN.matcher(ip).matches()) } - } - } - "127.0.0.1" + ) + ).headOption + interface.getOrElse("127.0.0.1") } def availablePort: Int = { diff --git a/scala-package/spark/src/test/scala/org/apache/mxnet/spark/SharedSparkContext.scala b/scala-package/spark/src/test/scala/org/apache/mxnet/spark/SharedSparkContext.scala index 6d36ca51db90..293cfa13cfce 100644 --- a/scala-package/spark/src/test/scala/org/apache/mxnet/spark/SharedSparkContext.scala +++ b/scala-package/spark/src/test/scala/org/apache/mxnet/spark/SharedSparkContext.scala @@ -92,20 +92,12 @@ trait SharedSparkContext extends FunSuite with BeforeAndAfterEach with BeforeAnd private def getJarFilePath(root: String): String = { val jarFiles = findJars(s"$root/target/") - if (jarFiles != null && jarFiles.nonEmpty) { - jarFiles.head.getAbsolutePath - } else { - null - } + Option(jarFiles).flatMap(_.headOption).map(_.getAbsolutePath).orNull } private def getSparkJar: String = { val jarFiles = findJars(s"$composeWorkingDirPath/target/") - if (jarFiles != null && jarFiles.nonEmpty) { - jarFiles.head.getAbsolutePath - } else { - null - } + Option(jarFiles).flatMap(_.headOption).map(_.getAbsolutePath).orNull } private def getNativeJars(root: String): String = From e701e7100c31b63e8f3b93008872606e921ec6ad Mon Sep 17 00:00:00 2001 From: Zach Kimberg Date: Thu, 11 Apr 2019 10:09:07 -0700 Subject: [PATCH 08/16] Avoid secondary deployment of package to local (#14647) --- scala-package/deploy/pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scala-package/deploy/pom.xml b/scala-package/deploy/pom.xml index 74b57077773c..b4dd273719bb 100644 --- a/scala-package/deploy/pom.xml +++ b/scala-package/deploy/pom.xml @@ -150,6 +150,9 @@ + + true + From 26b14bca0ace33b0f2767a71f7dfed52cef9c631 Mon Sep 17 00:00:00 2001 From: Zach Kimberg Date: Thu, 11 Apr 2019 13:21:31 -0700 Subject: [PATCH 09/16] [MXNET-1287] Up scala comp (#14667) * Upgrade scala compiler * Fix scala version mismatch * Fix no source root warnings * Misc compiler fixes --- .../assembly/src/main/assembly/assembly.xml | 6 +-- .../assembly/src/main/assembly/javadoc.xml | 2 +- .../assembly/src/main/assembly/source.xml | 2 +- scala-package/core/pom.xml | 5 +- .../deploy/src/main/deploy/deploy.xml | 2 +- scala-package/examples/pom.xml | 4 -- scala-package/infer/pom.xml | 4 -- scala-package/macros/pom.xml | 4 -- scala-package/pom.xml | 46 +++++++++++-------- scala-package/spark/pom.xml | 5 ++ 10 files changed, 38 insertions(+), 42 deletions(-) diff --git a/scala-package/assembly/src/main/assembly/assembly.xml b/scala-package/assembly/src/main/assembly/assembly.xml index 3a8e11b436d1..5a931360645c 100644 --- a/scala-package/assembly/src/main/assembly/assembly.xml +++ b/scala-package/assembly/src/main/assembly/assembly.xml @@ -33,8 +33,8 @@ org.slf4j:slf4j-api args4j:args4j - / - true + . + false true runtime @@ -71,7 +71,7 @@ cub/LICENSE.TXT mkldnn/external/mklml_mac_2019.0.1.20180928/license.txt - / + . diff --git a/scala-package/assembly/src/main/assembly/javadoc.xml b/scala-package/assembly/src/main/assembly/javadoc.xml index 176fa432190c..c6df96a3f5a5 100644 --- a/scala-package/assembly/src/main/assembly/javadoc.xml +++ b/scala-package/assembly/src/main/assembly/javadoc.xml @@ -25,7 +25,7 @@ ${rootdir}/core/target/site/scaladocs - / + . diff --git a/scala-package/assembly/src/main/assembly/source.xml b/scala-package/assembly/src/main/assembly/source.xml index c06786130192..1f004e811cfc 100644 --- a/scala-package/assembly/src/main/assembly/source.xml +++ b/scala-package/assembly/src/main/assembly/source.xml @@ -29,7 +29,7 @@ **\/*.scala - / + . diff --git a/scala-package/core/pom.xml b/scala-package/core/pom.xml index 5a28a11a0208..b888b5549e7a 100644 --- a/scala-package/core/pom.xml +++ b/scala-package/core/pom.xml @@ -100,10 +100,6 @@ - - org.apache.maven.plugins - maven-compiler-plugin - org.apache.maven.plugins maven-clean-plugin @@ -141,6 +137,7 @@ + org.apache.mxnet diff --git a/scala-package/deploy/src/main/deploy/deploy.xml b/scala-package/deploy/src/main/deploy/deploy.xml index 7265e7de8809..f5b624885cbb 100644 --- a/scala-package/deploy/src/main/deploy/deploy.xml +++ b/scala-package/deploy/src/main/deploy/deploy.xml @@ -38,7 +38,7 @@ org.scala-lang.modules scala-parser-combinators_2.11 - 1.0.4 + 1.0.5 org.scala-lang diff --git a/scala-package/examples/pom.xml b/scala-package/examples/pom.xml index d60782ffd06b..257529199176 100644 --- a/scala-package/examples/pom.xml +++ b/scala-package/examples/pom.xml @@ -82,10 +82,6 @@ org.apache.maven.plugins maven-jar-plugin - - org.apache.maven.plugins - maven-compiler-plugin - net.alchim31.maven scala-maven-plugin diff --git a/scala-package/infer/pom.xml b/scala-package/infer/pom.xml index ed90d8073675..81e93932e83f 100644 --- a/scala-package/infer/pom.xml +++ b/scala-package/infer/pom.xml @@ -46,10 +46,6 @@ - - org.apache.maven.plugins - maven-compiler-plugin - org.scalatest scalatest-maven-plugin diff --git a/scala-package/macros/pom.xml b/scala-package/macros/pom.xml index 91a13d0fc521..cb84824bb4e2 100644 --- a/scala-package/macros/pom.xml +++ b/scala-package/macros/pom.xml @@ -51,10 +51,6 @@ - - org.apache.maven.plugins - maven-compiler-plugin - org.scalatest scalatest-maven-plugin diff --git a/scala-package/pom.xml b/scala-package/pom.xml index 62def16627b9..44b784a56f8e 100644 --- a/scala-package/pom.xml +++ b/scala-package/pom.xml @@ -53,8 +53,11 @@ + 1.7 2.11.8 + 2.11 + g++ $ ${project.basedir}/.. @@ -252,9 +255,11 @@ maven-compiler-plugin 3.3 - 1.7 - 1.7 + ${java.version} + ${java.version} UTF-8 + true + true @@ -339,11 +344,12 @@ - org.scala-tools - maven-scala-plugin - 2.15.2 + net.alchim31.maven + scala-maven-plugin + 3.4.4 - incremental + ${java.version} + ${java.version} org.scalamacros @@ -354,25 +360,19 @@ + compile + add-source compile testCompile + doc-jar - - - - net.alchim31.maven - scala-maven-plugin - 3.3.2 - - - - package - attach-javadocs + presite + pre-site - doc-jar + add-source @@ -388,6 +388,7 @@ + commons-codec @@ -419,7 +420,7 @@ org.scalatest scalatest_2.11 - 3.0.4 + 3.0.2 test @@ -454,7 +455,12 @@ org.scala-lang.modules scala-parser-combinators_2.11 - 1.0.4 + 1.0.5 + + + org.scala-lang.modules + scala-xml_2.11 + 1.0.6 org.scala-lang diff --git a/scala-package/spark/pom.xml b/scala-package/spark/pom.xml index 2acb70b43303..b61a427f83ef 100644 --- a/scala-package/spark/pom.xml +++ b/scala-package/spark/pom.xml @@ -50,5 +50,10 @@ args4j 2.33 + + org.json4s + json4s-core_2.11 + 3.5.1 + From 596ef3a9a2dc1c59c388ef5259a9eb8a6b6a1beb Mon Sep 17 00:00:00 2001 From: Hao Jin Date: Thu, 11 Apr 2019 14:53:54 -0700 Subject: [PATCH 10/16] Add nd.power and sym.pow (#14606) * add nd.power and sym.pow * deprecate sym.pow, get rid of nd.pow --- python/mxnet/symbol/symbol.py | 41 +++++++++++++++++++++++++- tests/python/unittest/test_operator.py | 15 ++++++---- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/python/mxnet/symbol/symbol.py b/python/mxnet/symbol/symbol.py index 0c0a0a1e3c88..91d4ca16df07 100644 --- a/python/mxnet/symbol/symbol.py +++ b/python/mxnet/symbol/symbol.py @@ -47,7 +47,7 @@ from ._internal import SymbolBase, _set_symbol_class __all__ = ["Symbol", "var", "Variable", "Group", "load", "load_json", - "pow", "maximum", "minimum", "hypot", "eye", "zeros", "ones", "full", "arange", + "pow", "power", "maximum", "minimum", "hypot", "eye", "zeros", "ones", "full", "arange", "histogram", "split_v2"] @@ -2740,6 +2740,8 @@ def pow(base, exp): Both inputs can be Symbol or scalar number. Broadcasting is not supported. Use `broadcast_pow` instead. + `sym.pow` is being deprecated, please use `sym.power` instead. + Parameters --------- base : Symbol or scalar @@ -2780,6 +2782,43 @@ def pow(base, exp): raise TypeError('types (%s, %s) not supported' % (str(type(base)), str(type(exp)))) +def power(base, exp): + """Returns element-wise result of base element raised to powers from exp element. + + Both inputs can be Symbol or scalar number. + Broadcasting is not supported. Use `broadcast_pow` instead. + + Parameters + --------- + base : Symbol or scalar + The base symbol + exp : Symbol or scalar + The exponent symbol + + Returns + ------- + Symbol or scalar + The bases in x raised to the exponents in y. + + Examples + -------- + >>> mx.sym.power(2, 3) + 8 + >>> x = mx.sym.Variable('x') + >>> y = mx.sym.Variable('y') + >>> z = mx.sym.power(x, 2) + >>> z.eval(x=mx.nd.array([1,2]))[0].asnumpy() + array([ 1., 4.], dtype=float32) + >>> z = mx.sym.power(3, y) + >>> z.eval(y=mx.nd.array([2,3]))[0].asnumpy() + array([ 9., 27.], dtype=float32) + >>> z = mx.sym.power(x, y) + >>> z.eval(x=mx.nd.array([3,4]), y=mx.nd.array([2,3]))[0].asnumpy() + array([ 9., 64.], dtype=float32) + """ + return pow(base, exp) + + # pylint: disable=no-member # pylint: disable=redefined-builtin def maximum(left, right): diff --git a/tests/python/unittest/test_operator.py b/tests/python/unittest/test_operator.py index 17618e414343..ccb351f434da 100644 --- a/tests/python/unittest/test_operator.py +++ b/tests/python/unittest/test_operator.py @@ -698,11 +698,11 @@ def test_symbol_pow(): def test_pow_fn(): shape = (3, 4) exp = mx.symbol.Variable("exp") - y = mx.sym.pow(2, exp) x = np.ones(shape)*3 - check_numeric_gradient(y, [x], numeric_eps=1E-3) - check_symbolic_forward(y, [x], [2**x]) - check_symbolic_backward(y, [x], [np.ones(shape)], [np.log(2) * 2**x]) + for y in [mx.sym.pow(2, exp), mx.sym.power(2, exp)]: + check_numeric_gradient(y, [x], numeric_eps=1E-3) + check_symbolic_forward(y, [x], [2**x]) + check_symbolic_backward(y, [x], [np.ones(shape)], [np.log(2) * 2**x]) @with_seed() @@ -6675,7 +6675,12 @@ def test_binary_math_operators(): lambda x, y: np.power(x, y), lambda x, y: np.power(x, y - 1.) * y, lambda x, y: np.power(x, y) * np.log(x), - 0.2, 5.0, -4.0, 4.0] + 0.2, 5.0, -4.0, 4.0], + 'power': [lambda x, y: mx.sym.power(x, y), + lambda x, y: np.power(x, y), + lambda x, y: np.power(x, y - 1.) * y, + lambda x, y: np.power(x, y) * np.log(x), + 0.2, 5.0, -4.0, 4.0] } # Loop over operators for name, op in binary_ops.items(): From 6a93bda8e9e92bc635f66e5a379566e52592b593 Mon Sep 17 00:00:00 2001 From: Zach Kimberg Date: Thu, 11 Apr 2019 15:57:49 -0700 Subject: [PATCH 11/16] Fix scaladoc scalastyle violations in Infer package (#14671) --- .../org/apache/mxnet/infer/Classifier.scala | 17 ++++ .../apache/mxnet/infer/ImageClassifier.scala | 36 ++++++--- .../org/apache/mxnet/infer/MXNetHandler.scala | 14 ++++ .../apache/mxnet/infer/ObjectDetector.scala | 19 +++++ .../org/apache/mxnet/infer/Predictor.scala | 38 +++++---- .../mxnet/infer/javaapi/ObjectDetector.scala | 80 +++++++++++++++---- .../mxnet/infer/javaapi/Predictor.scala | 52 ++++++------ 7 files changed, 187 insertions(+), 69 deletions(-) diff --git a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/Classifier.scala b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/Classifier.scala index bf6581588114..38fdc0028a7a 100644 --- a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/Classifier.scala +++ b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/Classifier.scala @@ -31,6 +31,7 @@ trait ClassifierBase { /** * Takes an array of floats and returns corresponding (Label, Score) tuples + * @tparam T The Scala equivalent of the DType used for the input array and return value * @param input Indexed sequence one-dimensional array of floats/doubles * @param topK (Optional) How many result (sorting based on the last axis) * elements to return. Default returns unsorted output. @@ -167,6 +168,12 @@ class Classifier(modelPathPrefix: String, result.toIndexedSeq } + /** + * Gives the path to the standard location of the synset.txt file + * @throws IllegalArgumentException Thrown when the file does not exist + * @param modelPathPrefix The path to the model directory + * @return The path to the synset.txt file + */ private[infer] def getSynsetFilePath(modelPathPrefix: String): String = { val dirPath = modelPathPrefix.substring(0, 1 + modelPathPrefix.lastIndexOf(File.separator)) val d = new File(dirPath) @@ -179,6 +186,11 @@ class Classifier(modelPathPrefix: String, s.getCanonicalPath } + /** + * Parses the labels from a synset file + * @param synsetFilePath The path to the synset file. Can be gotten from getSynsetFilePath + * @return A IndexedSeq of each element in the file + */ private[infer] def readSynsetFile(synsetFilePath: String): IndexedSeq[String] = { val f = io.Source.fromFile(synsetFilePath) try { @@ -188,6 +200,11 @@ class Classifier(modelPathPrefix: String, } } + /** + * Creates a predictor with the same modelPath, inputDescriptors, contexts, + * and epoch as the classifier + * @return The new Predictor + */ private[infer] def getPredictor(): PredictBase = { new Predictor(modelPathPrefix, inputDescriptors, contexts, epoch) } diff --git a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/ImageClassifier.scala b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/ImageClassifier.scala index 99c0432d79f2..fb5f39fb2096 100644 --- a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/ImageClassifier.scala +++ b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/ImageClassifier.scala @@ -66,6 +66,10 @@ class ImageClassifier(modelPathPrefix: String, protected[infer] val height = inputShape(inputLayout.indexOf('H')) protected[infer] val width = inputShape(inputLayout.indexOf('W')) + /** + * Get the names and shapes that would be returns by a classify call + * @return a list of (name, shape) tuples + */ def outputShapes: IndexedSeq[(String, Shape)] = predictor.outputShapes /** @@ -127,6 +131,19 @@ class ImageClassifier(modelPathPrefix: String, result } + /** + * Creates a Classifier + * + * @param modelPathPrefix Path prefix from where to load the model artifacts. + * These include the symbol, parameters, and synset.txt. + * Example: file://model-dir/resnet-152 (containing + * resnet-152-symbol.json, resnet-152-0000.params, and synset.txt). + * @param inputDescriptors Descriptors defining the input node names, shape, + * layout and type parameters + * @param contexts Device contexts on which you want to run inference; defaults to CPU + * @param epoch Model epoch to load; defaults to 0 + * @return A Classifier to perform inference with + */ private[infer] def getClassifier(modelPathPrefix: String, inputDescriptors: IndexedSeq[DataDesc], contexts: Array[Context] = Context.cpu(), @@ -156,19 +173,16 @@ object ImageClassifier { /** * Convert input BufferedImage to NDArray of input shape - * - *

* Note: Caller is responsible to dispose the NDArray * returned by this method after the use. - *

- * @param resizedImage BufferedImage to get pixels from * - * @param inputImageShape Input shape; for example for resnet it is (3,224,224). - Should be same as inputDescriptor shape. - * @param dType The DataType of the NDArray created from the image - * that should be returned. - * Currently it defaults to Dtype.Float32 - * @return NDArray pixels array with shape (3, 224, 224) in CHW format + * @param resizedImage BufferedImage to get pixels from + * @param inputImageShape Input shape; for example for resnet it is (3,224,224). + * Should be same as inputDescriptor shape. + * @param dType The DataType of the NDArray created from the image + * that should be returned. + * Currently it defaults to Dtype.Float32 + * @return NDArray pixels array with shape (3, 224, 224) in CHW format */ def bufferedImageToPixels(resizedImage: BufferedImage, inputImageShape: Shape, dType : DType = DType.Float32): NDArray = { @@ -235,4 +249,4 @@ object ImageClassifier { def loadInputBatch(inputImagePaths: List[String]): Traversable[BufferedImage] = { inputImagePaths.map(path => ImageIO.read(new File(path))) } -} \ No newline at end of file +} diff --git a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/MXNetHandler.scala b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/MXNetHandler.scala index d2bed3aa9d80..593bab66bf12 100644 --- a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/MXNetHandler.scala +++ b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/MXNetHandler.scala @@ -23,6 +23,12 @@ import org.slf4j.LoggerFactory private[infer] trait MXNetHandler { + /** + * Executes a function within a thread-safe executor + * @param f The function to execute + * @tparam T The return type of the function + * @return Returns the result of the function f + */ def execute[T](f: => T): T val executor: ExecutorService @@ -31,7 +37,11 @@ private[infer] trait MXNetHandler { private[infer] object MXNetHandlerType extends Enumeration { + /** + * The internal type of the MXNetHandlerType enumeration + */ type MXNetHandlerType = Value + val SingleThreadHandler = Value("MXNetSingleThreadHandler") val OneThreadPerModelHandler = Value("MXNetOneThreadPerModelHandler") } @@ -93,6 +103,10 @@ private[infer] object MXNetSingleThreadHandler extends MXNetThreadPoolHandler(1) private[infer] object MXNetHandler { + /** + * Creates a handler based on the handlerType + * @return A ThreadPool or Thread Handler + */ def apply(): MXNetHandler = { if (handlerType == MXNetHandlerType.OneThreadPerModelHandler) { new MXNetThreadPoolHandler(1) diff --git a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/ObjectDetector.scala b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/ObjectDetector.scala index 28a578cae79f..b78cfbccd987 100644 --- a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/ObjectDetector.scala +++ b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/ObjectDetector.scala @@ -111,6 +111,13 @@ class ObjectDetector(modelPathPrefix: String, batchResult.toIndexedSeq } + /** + * Formats detection results by sorting in descending order of accuracy (topK only) + * and combining with synset labels + * @param predictResultND The results from the objectDetect call + * @param topK The number of top results to return or None for all + * @return The top predicted results as (className, [Accuracy, Xmin, Ymin, Xmax, Ymax]) + */ private[infer] def sortAndReformat(predictResultND: NDArray, topK: Option[Int]) : IndexedSeq[(String, Array[Float])] = { // iterating over the all the predictions @@ -170,6 +177,18 @@ class ObjectDetector(modelPathPrefix: String, result } + /** + * Creates an image classifier from the object detector model + * @param modelPathPrefix Path prefix from where to load the model artifacts. + * These include the symbol, parameters, and synset.txt. + * Example: file://model-dir/resnet-152 (containing + * resnet-152-symbol.json, resnet-152-0000.params, and synset.txt). + * @param inputDescriptors Descriptors defining the input node names, shape, + * layout and type parameters + * @param contexts Device contexts on which you want to run inference; defaults to CPU + * @param epoch Model epoch to load; defaults to 0 + * @return The corresponding image classifier + */ private[infer] def getImageClassifier(modelPathPrefix: String, inputDescriptors: IndexedSeq[DataDesc], contexts: Array[Context] = Context.cpu(), diff --git a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/Predictor.scala b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/Predictor.scala index 66284c81bd2e..cb27c930903d 100644 --- a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/Predictor.scala +++ b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/Predictor.scala @@ -33,27 +33,27 @@ import org.slf4j.LoggerFactory private[infer] trait PredictBase { /** - * Converts indexed sequences of 1-D array to NDArrays. - *

- * This method will take input as IndexedSeq one dimensional arrays and creates the - * NDArray needed for inference. The array will be reshaped based on the input descriptors. - * @param input: An Indexed Sequence of a one-dimensional array of datatype - * Float or Double - An IndexedSequence is needed when the model has more than one input. - * @return Indexed sequence array of outputs - */ + * Converts indexed sequences of 1-D array to NDArrays. + * This method will take input as IndexedSeq one dimensional arrays and creates the + * NDArray needed for inference. The array will be reshaped based on the input descriptors. + * @tparam T The Scala equivalent of the DType used for the input array and return value + * @param input An Indexed Sequence of a one-dimensional array of datatype + * Float or Double + * An IndexedSequence is needed when the model has more than one input. + * @return Indexed sequence array of outputs + */ def predict[@specialized (Base.MX_PRIMITIVES) T](input: IndexedSeq[Array[T]]) : IndexedSeq[Array[T]] /** - * Predict using NDArray as input. - *

- * This method is useful when the input is a batch of data - * or when multiple operations on the input have to performed. - * Note: User is responsible for managing allocation/deallocation of NDArrays. - * @param input IndexedSequence NDArrays. - * @return Output of predictions as NDArrays. - */ + * Predict using NDArray as input. + *

+ * This method is useful when the input is a batch of data + * or when multiple operations on the input have to performed. + * Note: User is responsible for managing allocation/deallocation of NDArrays. + * @param input IndexedSequence NDArrays. + * @return Output of predictions as NDArrays. + */ def predictWithNDArray(input: IndexedSeq[NDArray]): IndexedSeq[NDArray] /** @@ -248,6 +248,10 @@ class Predictor(modelPathPrefix: String, resultND } + /** + * Creates the module backing the Predictor with the same path, epoch, contexts, and inputs + * @return The Module + */ private[infer] def loadModule(): Module = { val mod = mxNetHandler.execute(Module.loadCheckpoint(modelPathPrefix, epoch.get, contexts = contexts, dataNames = inputDescriptors.map(desc => desc.name))) diff --git a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/javaapi/ObjectDetector.scala b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/javaapi/ObjectDetector.scala index 05334e49a356..8131273eca94 100644 --- a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/javaapi/ObjectDetector.scala +++ b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/javaapi/ObjectDetector.scala @@ -31,19 +31,23 @@ import scala.language.implicitConversions * The ObjectDetector class helps to run ObjectDetection tasks where the goal * is to find bounding boxes and corresponding labels for objects in a image. * - * @param modelPathPrefix Path prefix from where to load the model artifacts. - * These include the symbol, parameters, and synset.txt. - * Example: file://model-dir/ssd_resnet50_512 (containing - * ssd_resnet50_512-symbol.json, ssd_resnet50_512-0000.params, - * and synset.txt) - * @param inputDescriptors Descriptors defining the input node names, shape, - * layout and type parameters - * @param contexts Device contexts on which you want to run inference. - * Defaults to CPU. - * @param epoch Model epoch to load; defaults to 0 + * @param objDetector A source Scala Object detector */ class ObjectDetector private[mxnet] (val objDetector: org.apache.mxnet.infer.ObjectDetector){ + /** + * + * @param modelPathPrefix Path prefix from where to load the model artifacts. + * These include the symbol, parameters, and synset.txt. + * Example: file://model-dir/ssd_resnet50_512 (containing + * ssd_resnet50_512-symbol.json, ssd_resnet50_512-0000.params, + * and synset.txt) + * @param inputDescriptors Descriptors defining the input node names, shape, + * layout and type parameters + * @param contexts Device contexts on which you want to run inference. + * Defaults to CPU. + * @param epoch Model epoch to load; defaults to 0 + */ def this(modelPathPrefix: String, inputDescriptors: java.lang.Iterable[DataDesc], contexts: java.lang.Iterable[Context], epoch: Int) = this { @@ -98,32 +102,78 @@ class ObjectDetector private[mxnet] (val objDetector: org.apache.mxnet.infer.Obj (ret map {a => (a map {e => new ObjectDetectorOutput(e._1, e._2)}).asJava}).asJava } + /** + * Helper to map an implicit conversion + * @param l The value to convert + * @tparam B The desired type + * @tparam A The input type + * @return The converted result + */ def convert[B, A <% B](l: IndexedSeq[A]): IndexedSeq[B] = l map { a => a: B } } object ObjectDetector { - implicit def fromObjectDetector(OD: org.apache.mxnet.infer.ObjectDetector): - ObjectDetector = new ObjectDetector(OD) - - implicit def toObjectDetector(jOD: ObjectDetector): - org.apache.mxnet.infer.ObjectDetector = jOD.objDetector + /** + * Loads an input images from file + * @param inputImagePath Path of single input image + * @return BufferedImage Buffered image + */ def loadImageFromFile(inputImagePath: String): BufferedImage = { org.apache.mxnet.infer.ImageClassifier.loadImageFromFile(inputImagePath) } + /** + * Reshape the input image to a new shape + * + * @param img Input image + * @param newWidth New width for rescaling + * @param newHeight New height for rescaling + * @return Rescaled BufferedImage + */ def reshapeImage(img : BufferedImage, newWidth: Int, newHeight: Int): BufferedImage = { org.apache.mxnet.infer.ImageClassifier.reshapeImage(img, newWidth, newHeight) } + /** + * Convert input BufferedImage to NDArray of input shape + * Note: Caller is responsible to dispose the NDArray + * returned by this method after the use. + * + * @param resizedImage BufferedImage to get pixels from + * @param inputImageShape Input shape; for example for resnet it is (3,224,224). + * Should be same as inputDescriptor shape. + * @return NDArray pixels array with shape (3, 224, 224) in CHW format + */ def bufferedImageToPixels(resizedImage: BufferedImage, inputImageShape: Shape): NDArray = { org.apache.mxnet.infer.ImageClassifier.bufferedImageToPixels(resizedImage, inputImageShape) } + /** + * Loads a batch of images from a folder + * @param inputImagePaths Path to a folder of images + * @return List of buffered images + */ def loadInputBatch(inputImagePaths: java.lang.Iterable[String]): java.util.List[BufferedImage] = { org.apache.mxnet.infer.ImageClassifier .loadInputBatch(inputImagePaths.asScala.toList).toList.asJava } + + /** + * Implicitly convert a Scala ObjectDetector to a Java ObjectDetector + * @param OD The Scala ObjectDetector + * @return The Java ObjectDetector + */ + implicit def fromObjectDetector(OD: org.apache.mxnet.infer.ObjectDetector): + ObjectDetector = new ObjectDetector(OD) + + /** + * Implicitly converts a Java ObjectDetector to a Scala ObjectDetector + * @param jOD The Java ObjectDetector + * @return The Scala ObjectDetector + */ + implicit def toObjectDetector(jOD: ObjectDetector): + org.apache.mxnet.infer.ObjectDetector = jOD.objDetector } diff --git a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/javaapi/Predictor.scala b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/javaapi/Predictor.scala index 6c0871fae51b..e1505a4da821 100644 --- a/scala-package/infer/src/main/scala/org/apache/mxnet/infer/javaapi/Predictor.scala +++ b/scala-package/infer/src/main/scala/org/apache/mxnet/infer/javaapi/Predictor.scala @@ -25,21 +25,25 @@ import scala.collection.JavaConverters._ /** * Implementation of prediction routines. * - * @param modelPathPrefix Path prefix from where to load the model artifacts. - * These include the symbol, parameters, and synset.txt - * Example: file://model-dir/resnet-152 (containing - * resnet-152-symbol.json, resnet-152-0000.params, and synset.txt). - * @param inputDescriptors Descriptors defining the input node names, shape, - * layout and type parameters - *

Note: If the input Descriptors is missing batchSize - * ('N' in layout), a batchSize of 1 is assumed for the model. - * @param contexts Device contexts on which you want to run inference; defaults to CPU - * @param epoch Model epoch to load; defaults to 0 - + * @param predictor The underlying Scala predictor */ // JavaDoc description of class to be updated in https://issues.apache.org/jira/browse/MXNET-1178 class Predictor private[mxnet] (val predictor: org.apache.mxnet.infer.Predictor){ + + /** + * + * @param modelPathPrefix Path prefix from where to load the model artifacts. + * These include the symbol, parameters, and synset.txt + * Example: file://model-dir/resnet-152 (containing + * resnet-152-symbol.json, resnet-152-0000.params, and synset.txt). + * @param inputDescriptors Descriptors defining the input node names, shape, + * layout and type parameters + *

Note: If the input Descriptors is missing batchSize + * ('N' in layout), a batchSize of 1 is assumed for the model. + * @param contexts Device contexts on which you want to run inference; defaults to CPU + * @param epoch Model epoch to load; defaults to 0 + */ def this(modelPathPrefix: String, inputDescriptors: java.lang.Iterable[DataDesc], contexts: java.lang.Iterable[Context], epoch: Int) = this { @@ -53,19 +57,17 @@ class Predictor private[mxnet] (val predictor: org.apache.mxnet.infer.Predictor) * Takes input as Array of one dimensional arrays and creates the NDArray needed for inference * The array will be reshaped based on the input descriptors. Example of calling in Java: * - *

-    * {@code
+    * {{{
     * float tmp[][] = new float[1][224];
     * for (int x = 0; x < 1; x++)
     *   for (int y = 0; y < 224; y++)
     *     tmp[x][y] = (int)(Math.random()*10);
     * predictor.predict(tmp);
-    * }
-    * 
+ * }}} * - * @param input: An Array of a one-dimensional array. - An extra Array is needed for when the model has more than one input. - * @return Indexed sequence array of outputs + * @param input An Array of a one-dimensional array. + * An extra Array is needed for when the model has more than one input. + * @return Indexed sequence array of outputs */ def predict(input: Array[Array[Float]]): Array[Array[Float]] = { @@ -76,18 +78,16 @@ class Predictor private[mxnet] (val predictor: org.apache.mxnet.infer.Predictor) * Takes input as Array of one dimensional arrays and creates the NDArray needed for inference * The array will be reshaped based on the input descriptors. Example of calling in Java: * - *
-    * {@code
+    * {{{
     * double tmp[][] = new double[1][224];
     * for (int x = 0; x < 1; x++)
     *   for (int y = 0; y < 224; y++)
     *     tmp[x][y] = (int)(Math.random()*10);
     * predictor.predict(tmp);
-    * }
-    * 
+ * }}} * - * @param input: An Array of a one-dimensional array. - An extra Array is needed for when the model has more than one input. + * @param input An Array of a one-dimensional array. + * An extra Array is needed for when the model has more than one input. * @return Indexed sequence array of outputs */ @@ -100,8 +100,8 @@ class Predictor private[mxnet] (val predictor: org.apache.mxnet.infer.Predictor) * Takes input as List of one dimensional iterables and creates the NDArray needed for inference * The array will be reshaped based on the input descriptors. * - * @param input: A List of a one-dimensional iterables of DType Float. - An extra List is needed for when the model has more than one input. + * @param input A List of a one-dimensional iterables of DType Float. + * An extra List is needed for when the model has more than one input. * @return Indexed sequence array of outputs */ def predict(input: java.util.List[java.util.List[java.lang.Float]]): From 02842368a89ffabf0eab3c90928feefc7aed463a Mon Sep 17 00:00:00 2001 From: Carin Meier Date: Thu, 11 Apr 2019 19:01:55 -0400 Subject: [PATCH 12/16] [Clojure] enhance draw bounding box (#14567) * add test for drawing bounding box * Uses the core image drawing bounding box functionality for the object detection example. Adjust the specs and names to make it easier to run with object detection and clojure draw bounding box example * feedback from @chouffe * refactor to be 3 top predictions instead of 5 to make the images less crowded --- .../examples/infer/objectdetector/project.clj | 1 - .../infer/objectdetector/src/infer/draw.clj | 44 -------------- .../src/infer/objectdetector_example.clj | 59 +++++++++++-------- .../infer/objectdetector_example_test.clj | 6 +- contrib/clojure-package/integration-tests.sh | 2 +- .../src/org/apache/clojure_mxnet/image.clj | 26 ++++---- .../org/apache/clojure_mxnet/image_test.clj | 16 ++++- 7 files changed, 66 insertions(+), 88 deletions(-) delete mode 100644 contrib/clojure-package/examples/infer/objectdetector/src/infer/draw.clj diff --git a/contrib/clojure-package/examples/infer/objectdetector/project.clj b/contrib/clojure-package/examples/infer/objectdetector/project.clj index cdd9a8991dc8..da01797f5a21 100644 --- a/contrib/clojure-package/examples/infer/objectdetector/project.clj +++ b/contrib/clojure-package/examples/infer/objectdetector/project.clj @@ -22,7 +22,6 @@ :aliases {"run-detector" ["run" "--" "-m" "models/resnet50_ssd/resnet50_ssd_model" "-i" "images/dog.jpg" "-d" "images/"]} :dependencies [[org.clojure/clojure "1.9.0"] [org.clojure/tools.cli "0.4.1"] - [origami "4.0.0-3"] [org.apache.mxnet.contrib.clojure/clojure-mxnet "1.5.0-SNAPSHOT"]] :main ^:skip-aot infer.objectdetector-example :profiles {:uberjar {:aot :all}}) diff --git a/contrib/clojure-package/examples/infer/objectdetector/src/infer/draw.clj b/contrib/clojure-package/examples/infer/objectdetector/src/infer/draw.clj deleted file mode 100644 index d29b34b5c22a..000000000000 --- a/contrib/clojure-package/examples/infer/objectdetector/src/infer/draw.clj +++ /dev/null @@ -1,44 +0,0 @@ -;; 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. -;; - -(ns infer.draw - (:require - [opencv4.colors.rgb :as rgb] - [opencv4.core :refer [FONT_HERSHEY_PLAIN imread imwrite new-point put-text! rectangle]])) - -(defn black-boxes! [img results] - (doseq [{confidence :confidence label :label top-left :top-left bottom-right :bottom-right} results] - (let [w (.width img) - h (.height img) - top-left-p (new-point (int (* w (first top-left))) (int (* h (second top-left)))) - bottom-right-p (new-point (int (* w (first bottom-right))) (int (* h (second bottom-right))))] - (if (< 15 confidence) - (do - (rectangle img top-left-p bottom-right-p rgb/white 1) - (put-text! img - (str label "[" confidence "% ]") - top-left-p - FONT_HERSHEY_PLAIN - 1.0 - rgb/white 1))))) - img) - -(defn draw-bounds [image results output-dir] - (let [out-file (str output-dir "/" (.getName (clojure.java.io/as-file image)))] - (-> image - (imread) - (black-boxes! results) - (imwrite out-file)))) \ No newline at end of file diff --git a/contrib/clojure-package/examples/infer/objectdetector/src/infer/objectdetector_example.clj b/contrib/clojure-package/examples/infer/objectdetector/src/infer/objectdetector_example.clj index 9331798b038c..65d822ff36aa 100644 --- a/contrib/clojure-package/examples/infer/objectdetector/src/infer/objectdetector_example.clj +++ b/contrib/clojure-package/examples/infer/objectdetector/src/infer/objectdetector_example.clj @@ -17,13 +17,15 @@ (ns infer.objectdetector-example (:require [org.apache.clojure-mxnet.context :as context] [org.apache.clojure-mxnet.dtype :as dtype] + [org.apache.clojure-mxnet.image :as image] [org.apache.clojure-mxnet.infer :as infer] [org.apache.clojure-mxnet.layout :as layout] [clojure.java.io :as io] - [infer.draw :as draw] - [clojure.string :refer [join]] + [clojure.string :as string] [clojure.tools.cli :refer [parse-opts]]) - (:gen-class)) + (:gen-class) + (:import (javax.imageio ImageIO) + (java.io File))) (defn check-valid-dir "Check that the input directory exists" @@ -54,27 +56,36 @@ :validate [check-valid-dir "Input directory not found"]] ["-h" "--help"]]) -(defn result->map [{:keys [class prob x-min y-min x-max y-max]}] - (hash-map - :label class - :confidence (int (* 100 prob)) - :top-left [x-min y-min] - :bottom-right [x-max y-max])) -(defn print-results [results] - (doseq [_r results] - (println (format "Class: %s Confidence=%s Coords=(%s, %s)" - (_r :label) - (_r :confidence) - (_r :top-left) - (_r :bottom-right))))) +(defn process-result! [output-dir image-path predictions] + (println "looking at image" image-path) + (println "predictions: " predictions) + (let [buf (ImageIO/read (new File image-path)) + width (.getWidth buf) + height (.getHeight buf) + names (mapv :class predictions) + coords (mapv (fn [prediction] + (-> prediction + (update :x-min #(* width %)) + (update :x-max #(* width %)) + (update :y-min #(* height %)) + (update :y-max #(* height %)))) + predictions) + new-img (-> (ImageIO/read (new File image-path)) + (image/draw-bounding-box! coords + {:stroke 2 + :names (mapv #(str (:class %) "-" (:prob %)) + predictions) + :transparency 0.5 + + :font-size-mult 1.0}))] + (->> (string/split image-path #"\/") + last + (io/file output-dir) + (ImageIO/write new-img "jpg")))) (defn process-results [images results output-dir] - (dotimes [i (count images)] - (let [image (nth images i) _results (map result->map (nth results i))] - (println "processing: " image) - (print-results _results) - (draw/draw-bounds image _results output-dir)))) + (doall (map (partial process-result! output-dir) images results))) (defn detect-single-image "Detect objects in a single image and print top-5 predictions" @@ -82,7 +93,7 @@ ([detector input-image output-dir] (.mkdir (io/file output-dir)) (let [image (infer/load-image-from-file input-image) - topk 5 + topk 3 res (infer/detect-objects detector image topk) ] (process-results @@ -109,7 +120,7 @@ (apply concat (for [image-files image-file-batches] (let [image-batch (infer/load-image-paths image-files) - topk 5 + topk 3 res (infer/detect-objects-batch detector image-batch topk) ] (process-results image-files @@ -143,5 +154,5 @@ (parse-opts args cli-options)] (cond (:help options) (println summary) - (some? errors) (println (join "\n" errors)) + (some? errors) (println (string/join "\n" errors)) :else (run-detector options)))) diff --git a/contrib/clojure-package/examples/infer/objectdetector/test/infer/objectdetector_example_test.clj b/contrib/clojure-package/examples/infer/objectdetector/test/infer/objectdetector_example_test.clj index 696d96b3ae3a..3d20c614918f 100644 --- a/contrib/clojure-package/examples/infer/objectdetector/test/infer/objectdetector_example_test.clj +++ b/contrib/clojure-package/examples/infer/objectdetector/test/infer/objectdetector_example_test.clj @@ -47,11 +47,11 @@ {:keys [class prob x-min x-max y-min y-max] :as pred} (first predictions)] (clojure.pprint/pprint predictions) (is (some? predictions)) - (is (= 5 (count predictions))) + (is (= 3 (count predictions))) (is (string? class)) (is (< 0.8 prob)) (is (every? #(< 0 % 1) [x-min x-max y-min y-max])) - (is (= #{"dog" "person" "bicycle" "car"} (set (mapv :class predictions)))))) + (is (= #{"dog" "bicycle" "car"} (set (mapv :class predictions)))))) (deftest test-batch-detection (let [detector (create-detector) @@ -60,7 +60,7 @@ predictions (first batch-predictions) {:keys [class prob x-min x-max y-min y-max] :as pred} (first predictions)] (is (some? batch-predictions)) - (is (= 5 (count predictions))) + (is (= 3 (count predictions))) (is (string? class)) (is (< 0.8 prob)) (println [x-min x-max y-min y-max]) diff --git a/contrib/clojure-package/integration-tests.sh b/contrib/clojure-package/integration-tests.sh index 5ae26e8dfcd4..3df9ba9787b5 100755 --- a/contrib/clojure-package/integration-tests.sh +++ b/contrib/clojure-package/integration-tests.sh @@ -26,7 +26,7 @@ lein install # then run through the examples EXAMPLES_HOME=${MXNET_HOME}/contrib/clojure-package/examples # use AWK pattern for blacklisting -TEST_CASES=`find ${EXAMPLES_HOME} -name test | awk '!/dontselect1|cnn-text-classification|gan|neural-style|infer|pre-trained-models/'` +TEST_CASES=`find ${EXAMPLES_HOME} -name test | awk '!/dontselect1|cnn-text-classification|gan|neural-style|pre-trained-models/'` for i in $TEST_CASES ; do cd ${i} && lein test done diff --git a/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj b/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj index e2e98c4e8f01..f81a35803171 100644 --- a/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj +++ b/contrib/clojure-package/src/org/apache/clojure_mxnet/image.clj @@ -202,11 +202,11 @@ (Image/toImage input)) (s/def ::buffered-image #(instance? BufferedImage %)) -(s/def ::xmin integer?) -(s/def ::xmax integer?) -(s/def ::ymin integer?) -(s/def ::ymax integer?) -(s/def ::coordinate (s/keys :req-un [::xmin ::xmax ::ymin ::ymax])) +(s/def ::x-min number?) +(s/def ::x-max number?) +(s/def ::y-min number?) +(s/def ::y-max number?) +(s/def ::coordinate (s/keys :req-un [::x-min ::x-max ::y-min ::y-max])) (s/def ::coordinates (s/coll-of ::coordinate)) (s/def ::names (s/nilable (s/coll-of string?))) (s/def ::stroke (s/and integer? pos?)) @@ -217,11 +217,11 @@ (defn- convert-coordinate "Convert bounding box coordinate to Scala correct types." - [{:keys [xmin xmax ymin ymax]}] - {:xmin (int xmin) - :xmax (int xmax) - :ymin (int ymin) - :ymax (int ymax)}) + [{:keys [x-min x-max y-min y-max]}] + {:xmin (int x-min) + :xmax (int x-max) + :ymin (int y-min) + :ymax (int y-max)}) (defn draw-bounding-box! "Draw bounding boxes on `buffered-image` and Mutate the input image. @@ -233,9 +233,9 @@ `transparency`: float in (0.0, 1.0) - Transparency of the bounding box returns: Modified `buffered-image` Ex: - (draw-bounding-box! img [{:xmin 0 :xmax 100 :ymin 0 :ymax 100}]) - (draw-bounding-box! [{:xmin 190 :xmax 850 :ymin 50 :ymax 450} - {:xmin 200 :xmax 350 :ymin 440 :ymax 530}] + (draw-bounding-box! img [{:x-min 0 :x-max 100 :y-min 0 :y-max 100}]) + (draw-bounding-box! [{:x-min 190 :x-max 850 :y-min 50 :y-max 450} + {:x-min 200 :x-max 350 :y-min 440 :y-max 530}] {:stroke 2 :names [\"pug\" \"cookie\"] :transparency 0.8 diff --git a/contrib/clojure-package/test/org/apache/clojure_mxnet/image_test.clj b/contrib/clojure-package/test/org/apache/clojure_mxnet/image_test.clj index 38ab11c86012..23b88d07e896 100644 --- a/contrib/clojure-package/test/org/apache/clojure_mxnet/image_test.clj +++ b/contrib/clojure-package/test/org/apache/clojure_mxnet/image_test.clj @@ -20,7 +20,8 @@ [org.apache.clojure-mxnet.ndarray :as ndarray] [clojure.java.io :as io] [clojure.test :refer :all]) - (:import (javax.imageio ImageIO))) + (:import (javax.imageio ImageIO) + (java.io File))) (def tmp-dir (System/getProperty "java.io.tmpdir")) (def image-path (.getAbsolutePath (io/file tmp-dir "Pug-Cookie.jpg"))) @@ -76,4 +77,15 @@ (let [img-arr (image/read-image image-path) resized-arr (image/resize-image img-arr 224 224) new-img (image/to-image resized-arr)] - (is (= true (ImageIO/write new-img "png" (io/file tmp-dir "out.png")))))) + (is (ImageIO/write new-img "png" (io/file tmp-dir "out.png"))))) + +(deftest test-draw-bounding-box! + (let [orig-img (ImageIO/read (new File image-path)) + new-img (-> orig-img + (image/draw-bounding-box! [{:x-min 190 :x-max 850 :y-min 50 :y-max 450} + {:x-min 200 :x-max 350 :y-min 440 :y-max 530}] + {:stroke 2 + :names ["pug" "cookie"] + :transparency 0.8 + :font-size-mult 2.0}))] + (is (ImageIO/write new-img "png" (io/file tmp-dir "out.png"))))) From 800590e5116d75f5f0aa0f554c501627f43e8e39 Mon Sep 17 00:00:00 2001 From: Yuxi Hu Date: Thu, 11 Apr 2019 22:04:49 -0700 Subject: [PATCH 13/16] Add MXEnginePushAsync and MXEnginePushSync C APIs (#14615) * add PushAsyncPtr and PushSyncPtr APIs in engine * avoid using shared_ptr for param in new APIs * avoid using std::vector in parameters * change to C API * address comments and add tests * fix perl build * use int instead of size_t --- include/mxnet/c_api.h | 61 +++++++++++++++- src/c_api/c_api.cc | 88 ++++++++++++++++++++++++ tests/cpp/engine/threaded_engine_test.cc | 78 +++++++++++++++++++++ 3 files changed, 225 insertions(+), 2 deletions(-) diff --git a/include/mxnet/c_api.h b/include/mxnet/c_api.h index 9a24b7516128..2f9d74dc5ba0 100644 --- a/include/mxnet/c_api.h +++ b/include/mxnet/c_api.h @@ -95,10 +95,22 @@ typedef void *CudaKernelHandle; typedef void *ProfileHandle; /*! \brief handle to DLManagedTensor*/ typedef void *DLManagedTensorHandle; - +/*! \brief handle to Context */ +typedef const void *ContextHandle; +/*! \brief handle to Engine FnProperty */ +typedef const void *EngineFnPropertyHandle; +/*! \brief handle to Engine VarHandle */ +typedef void *EngineVarHandle; + +/*! \brief Engine asynchronous operation */ +typedef void (*EngineAsyncFunc)(void*, void*, void*); +/*! \brief Engine synchronous operation */ +typedef void (*EngineSyncFunc)(void*, void*); +/*! \brief Callback to free the param for EngineAsyncFunc/EngineSyncFunc */ +typedef void (*EngineFuncParamDeleter)(void*); typedef void (*ExecutorMonitorCallback)(const char*, NDArrayHandle, - void *); + void*); struct NativeOpInfo { void (*forward)(int, float**, int*, unsigned**, int*, void*); @@ -2541,6 +2553,51 @@ MXNET_DLL int MXNDArrayGetSharedMemHandle(NDArrayHandle handle, int* shared_pid, MXNET_DLL int MXNDArrayCreateFromSharedMem(int shared_pid, int shared_id, const mx_uint *shape, mx_uint ndim, int dtype, NDArrayHandle *out); +/*! + * \brief Push an asynchronous operation to the engine. + * \param async_func Execution function whici takes a parameter on_complete + * that must be called when the execution ompletes. + * \param func_param The parameter set on calling async_func, can be NULL. + * \param deleter The callback to free func_param, can be NULL. + * \param ctx_handle Execution context. + * \param const_vars_handle The variables that current operation will use + * but not mutate. + * \param num_const_vars The number of const_vars. + * \param mutable_vars_handle The variables that current operation will mutate. + * \param num_mutable_vars The number of mutable_vars. + * \param prop_handle Property of the function. + * \param priority Priority of the action, as hint to the engine. + * \param opr_name The operation name. + * \param wait Whether this is a WaitForVar operation. + */ +MXNET_DLL int MXEnginePushAsync(EngineAsyncFunc async_func, void* func_param, + EngineFuncParamDeleter deleter, ContextHandle ctx_handle, + EngineVarHandle const_vars_handle, int num_const_vars, + EngineVarHandle mutable_vars_handle, int num_mutable_vars, + EngineFnPropertyHandle prop_handle = NULL, int priority = 0, + const char* opr_name = NULL, bool wait = false); + +/*! + * \brief Push a synchronous operation to the engine. + * \param sync_func Execution function that executes the operation. + * \param func_param The parameter set on calling sync_func, can be NULL. + * \param deleter The callback to free func_param, can be NULL. + * \param ctx_handle Execution context. + * \param const_vars_handle The variables that current operation will use + * but not mutate. + * \param num_const_vars The number of const_vars. + * \param mutable_vars_handle The variables that current operation will mutate. + * \param num_mutable_vars The number of mutable_vars. + * \param prop_handle Property of the function. + * \param priority Priority of the action, as hint to the engine. + * \param opr_name The operation name. + */ +MXNET_DLL int MXEnginePushSync(EngineSyncFunc sync_func, void* func_param, + EngineFuncParamDeleter deleter, ContextHandle ctx_handle, + EngineVarHandle const_vars_handle, int num_const_vars, + EngineVarHandle mutable_vars_handle, int num_mutable_vars, + EngineFnPropertyHandle prop_handle = NULL, int priority = 0, + const char* opr_name = NULL); #ifdef __cplusplus } diff --git a/src/c_api/c_api.cc b/src/c_api/c_api.cc index 70ba84b5f94b..45197aafe019 100644 --- a/src/c_api/c_api.cc +++ b/src/c_api/c_api.cc @@ -1401,3 +1401,91 @@ int MXNDArrayCreateFromSharedMem(int shared_pid, int shared_id, const mx_uint *s *out = new NDArray(shared_pid, shared_id, mxnet::TShape(shape, shape + ndim), dtype); API_END(); } + +typedef Engine::VarHandle VarHandle; +typedef Engine::CallbackOnComplete CallbackOnComplete; + +void AssertValidNumberVars(int num_const_vars, int num_mutable_vars) { + CHECK_GE(num_const_vars, 0) << "Non-negative number of const vars expected."; + CHECK_GE(num_mutable_vars, 0) << "Non-negative number of mutable vars expected."; +} + +int MXEnginePushAsync(EngineAsyncFunc async_func, void* func_param, + EngineFuncParamDeleter deleter, ContextHandle ctx_handle, + EngineVarHandle const_vars_handle, int num_const_vars, + EngineVarHandle mutable_vars_handle, int num_mutable_vars, + EngineFnPropertyHandle prop_handle, int priority, + const char* opr_name, bool wait) { + API_BEGIN(); + + auto exec_ctx = *static_cast(ctx_handle); + auto const_vars = static_cast(const_vars_handle); + auto mutable_vars = static_cast(mutable_vars_handle); + auto prop = FnProperty::kNormal; + if (prop_handle) { + prop = *static_cast(prop_handle); + } + + Engine::AsyncFn exec_fn; + if (deleter == nullptr) { + exec_fn = [async_func, func_param](RunContext rctx, + CallbackOnComplete on_complete) { + async_func(&rctx, &on_complete, func_param); + }; + } else { + // Wrap func_param in a shared_ptr with deleter such that deleter + // will be called when the lambda goes out of scope. + std::shared_ptr shared_func_param(func_param, deleter); + exec_fn = [async_func, shared_func_param](RunContext rctx, + CallbackOnComplete on_complete) { + async_func(&rctx, &on_complete, shared_func_param.get()); + }; + } + + AssertValidNumberVars(num_const_vars, num_mutable_vars); + std::vector const_var_vec(const_vars, const_vars + num_const_vars); + std::vector mutable_var_vec(mutable_vars, mutable_vars + num_mutable_vars); + Engine::Get()->PushAsync(exec_fn, exec_ctx, const_var_vec, mutable_var_vec, + prop, priority, opr_name, wait); + + API_END(); +} + +int MXEnginePushSync(EngineSyncFunc sync_func, void* func_param, + EngineFuncParamDeleter deleter, ContextHandle ctx_handle, + EngineVarHandle const_vars_handle, int num_const_vars, + EngineVarHandle mutable_vars_handle, int num_mutable_vars, + EngineFnPropertyHandle prop_handle, int priority, + const char* opr_name) { + API_BEGIN(); + + auto exec_ctx = *static_cast(ctx_handle); + auto const_vars = static_cast(const_vars_handle); + auto mutable_vars = static_cast(mutable_vars_handle); + auto prop = FnProperty::kNormal; + if (prop_handle) { + prop = *static_cast(prop_handle); + } + + Engine::SyncFn exec_fn; + if (deleter == nullptr) { + exec_fn = [sync_func, func_param](RunContext rctx) { + sync_func(&rctx, func_param); + }; + } else { + // Wrap func_param in a shared_ptr with deleter such that deleter + // will be called when the lambda goes out of scope. + std::shared_ptr shared_func_param(func_param, deleter); + exec_fn = [sync_func, shared_func_param](RunContext rctx) { + sync_func(&rctx, shared_func_param.get()); + }; + } + + AssertValidNumberVars(num_const_vars, num_mutable_vars); + std::vector const_var_vec(const_vars, const_vars + num_const_vars); + std::vector mutable_var_vec(mutable_vars, mutable_vars + num_mutable_vars); + Engine::Get()->PushSync(exec_fn, exec_ctx, const_var_vec, mutable_var_vec, + prop, priority, opr_name); + + API_END(); +} diff --git a/tests/cpp/engine/threaded_engine_test.cc b/tests/cpp/engine/threaded_engine_test.cc index 6d669c19bcaa..405f3b30a176 100644 --- a/tests/cpp/engine/threaded_engine_test.cc +++ b/tests/cpp/engine/threaded_engine_test.cc @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -176,6 +177,83 @@ TEST(Engine, RandSumExpr) { void Foo(mxnet::RunContext, int i) { printf("The fox says %d\n", i); } +void FooAsyncFunc(void*, void* cb_ptr, void* param) { + if (param == nullptr) { + LOG(INFO) << "The fox asynchronously says receiving nothing."; + } else { + auto num = static_cast(param); + EXPECT_EQ(*num, 100); + LOG(INFO) << "The fox asynchronously says receiving " << *num; + } + auto cb = *static_cast(cb_ptr); + cb(); +} + +void FooSyncFunc(void*, void* param) { + if (param == nullptr) { + LOG(INFO) << "The fox synchronously says receiving nothing."; + } else { + auto num = static_cast(param); + EXPECT_EQ(*num, 101); + LOG(INFO) << "The fox synchronously says receiving " << *num; + } +} + +void FooFuncDeleter(void* param) { + if (param != nullptr) { + auto num = static_cast(param); + LOG(INFO) << "The fox says deleting " << *num; + delete num; + } +} + +TEST(Engine, PushFunc) { + auto var = mxnet::Engine::Get()->NewVariable(); + auto ctx = mxnet::Context{}; + + // Test #1 + LOG(INFO) << "===== Test #1: PushAsync param and deleter ====="; + int* a = new int(100); + int res = MXEnginePushAsync(FooAsyncFunc, a, FooFuncDeleter, &ctx, &var, 1, nullptr, 0); + EXPECT_EQ(res, 0); + + // Test #2 + LOG(INFO) << "===== Test #2: PushAsync NULL param and NULL deleter ====="; + res = MXEnginePushAsync(FooAsyncFunc, nullptr, nullptr, &ctx, nullptr, 0, &var, 0); + EXPECT_EQ(res, 0); + + // Test #3 + LOG(INFO) << "===== Test #3: PushAsync invalid number of const vars ====="; + res = MXEnginePushAsync(FooAsyncFunc, nullptr, nullptr, &ctx, &var, -1, nullptr, 0); + EXPECT_EQ(res, -1); + + // Test #4 + LOG(INFO) << "===== Test #4: PushAsync invalid number of mutable vars ====="; + res = MXEnginePushAsync(FooAsyncFunc, nullptr, nullptr, &ctx, nullptr, 0, &var, -1); + EXPECT_EQ(res, -1); + + // Test #5 + LOG(INFO) << "===== Test #5: PushSync param and deleter ====="; + int* b = new int(101); + res = MXEnginePushSync(FooSyncFunc, b, FooFuncDeleter, &ctx, &var, 1, nullptr, 0); + EXPECT_EQ(res, 0); + + // Test #6 + LOG(INFO) << "===== Test #6: PushSync NULL param and NULL deleter ====="; + res = MXEnginePushSync(FooSyncFunc, nullptr, nullptr, &ctx, nullptr, 0, &var, 1); + EXPECT_EQ(res, 0); + + // Test #7 + LOG(INFO) << "===== Test #7: PushSync invalid number of const vars ====="; + res = MXEnginePushSync(FooSyncFunc, nullptr, nullptr, &ctx, &var, -1, nullptr, 0); + EXPECT_EQ(res, -1); + + // Test #8 + LOG(INFO) << "===== Test #8: PushSync invalid number of mutable vars ====="; + res = MXEnginePushSync(FooSyncFunc, nullptr, nullptr, &ctx, nullptr, 0, &var, -1); + EXPECT_EQ(res, -1); +} + TEST(Engine, basics) { auto&& engine = mxnet::Engine::Get(); auto&& var = engine->NewVariable(); From c7888047b108358a9c523306ef5574f3245c929f Mon Sep 17 00:00:00 2001 From: perdasilva Date: Fri, 12 Apr 2019 19:31:24 +0200 Subject: [PATCH 14/16] Updates tolerances for test_layer_bidirectional (#14682) --- tests/python/unittest/test_gluon_rnn.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/python/unittest/test_gluon_rnn.py b/tests/python/unittest/test_gluon_rnn.py index b410362c8fd1..9d7892010839 100644 --- a/tests/python/unittest/test_gluon_rnn.py +++ b/tests/python/unittest/test_gluon_rnn.py @@ -22,8 +22,7 @@ from numpy.testing import assert_allclose import unittest from mxnet.test_utils import almost_equal, assert_almost_equal -from common import assert_raises_cudnn_not_satisfied - +from common import assert_raises_cudnn_not_satisfied, with_seed def test_rnn(): cell = gluon.rnn.RNNCell(100, prefix='rnn_') @@ -244,6 +243,7 @@ def test_bidirectional(): @assert_raises_cudnn_not_satisfied(min_version='5.1.10') +@with_seed() def test_layer_bidirectional(): class RefBiLSTM(gluon.Block): def __init__(self, size, **kwargs): @@ -279,7 +279,7 @@ def forward(self, inpt): ref_net_params[k.replace('l0', 'l0l0').replace('r0', 'r0l0')].set_data(weights[k]) data = mx.random.uniform(shape=(11, 10, in_size)) - assert_allclose(net(data).asnumpy(), ref_net(data).asnumpy()) + assert_allclose(net(data).asnumpy(), ref_net(data).asnumpy(), rtol=1e-04, atol=1e-02) From 30d479feae11b04d423f2ec055d124fb6d2d5730 Mon Sep 17 00:00:00 2001 From: Kellen Sunderland Date: Fri, 12 Apr 2019 13:17:07 -0700 Subject: [PATCH 15/16] Tweak the copy for the cudnn autotuning warning. (#14680) --- src/operator/nn/cudnn/cudnn_algoreg-inl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/operator/nn/cudnn/cudnn_algoreg-inl.h b/src/operator/nn/cudnn/cudnn_algoreg-inl.h index cef9d6f86940..3f2d24c5bf7e 100644 --- a/src/operator/nn/cudnn/cudnn_algoreg-inl.h +++ b/src/operator/nn/cudnn/cudnn_algoreg-inl.h @@ -96,7 +96,7 @@ class CuDNNAlgoReg { if (param.cudnn_tune.value() && reg_.size() % 50 == 0) { LOG(INFO) << "Running performance tests to find the best convolution " "algorithm, " - "this can take a while... (setting env variable " + "this can take a while... (set the environment variable " "MXNET_CUDNN_AUTOTUNE_DEFAULT to 0 to disable)"; if (reg_.size() >= 1000) { // Many people are very concerned about this warning, so change the warning once. From 5fc5c271ab8f16c027f4e563306afc2d0f8d336c Mon Sep 17 00:00:00 2001 From: Anirudh Subramanian Date: Fri, 12 Apr 2019 15:03:54 -0700 Subject: [PATCH 16/16] Fix profiler check (#14677) * Relax constexpr restriction * Image classifcation mkldnn * Check mem profiler greater than 0 * Revert "Relax constexpr restriction" This reverts commit 5016170ff124987aa32636e3528fb8f07a034490. * Revert "Image classifcation mkldnn" This reverts commit 30bfab21e9233d42f12a03cb3226b85cd41657bc. * Add test for profiler * Simplify test --- src/profiler/profiler.h | 6 ++++++ src/profiler/storage_profiler.h | 6 +++++- tests/python/unittest/test_exc_handling.py | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/profiler/profiler.h b/src/profiler/profiler.h index adea941bda13..f1fac9ae8ddd 100644 --- a/src/profiler/profiler.h +++ b/src/profiler/profiler.h @@ -608,6 +608,12 @@ struct ProfileCounter : public ProfileObject { return IncrementValue(static_cast(v)); } } + + inline bool operator >=(int64_t v) { + CHECK_GE(v, 0); + return value_ >= static_cast(v); + } + /*! \brief operator: object = v */ inline ProfileCounter& operator = (uint64_t v) { SetValue(v); diff --git a/src/profiler/storage_profiler.h b/src/profiler/storage_profiler.h index bcbe7e7e3ffd..5ab5983267eb 100644 --- a/src/profiler/storage_profiler.h +++ b/src/profiler/storage_profiler.h @@ -66,7 +66,11 @@ class DeviceStorageProfiler { Init(); // In case of bug which tries to free first const size_t idx = prof->DeviceIndex(handle.ctx.dev_type, handle.ctx.dev_id); CHECK_LT(idx, mem_counters_.size()) << "Invalid device index: " << idx; - *mem_counters_[idx] -= handle.size; + if (*mem_counters_[idx] >= handle.size) { + *mem_counters_[idx] -= handle.size; + } else { + *mem_counters_[idx] = 0; + } } } } diff --git a/tests/python/unittest/test_exc_handling.py b/tests/python/unittest/test_exc_handling.py index 60799f821b8e..5627ac50d26e 100644 --- a/tests/python/unittest/test_exc_handling.py +++ b/tests/python/unittest/test_exc_handling.py @@ -165,6 +165,22 @@ def test_multiple_waitalls(): assert caught, "No exception thrown" mx.nd.waitall() +@with_seed() +def test_exc_profiler(): + def run_training_iteration(data): + output = net(data) + + net = gluon.nn.HybridSequential() + with net.name_scope(): + net.add(gluon.nn.Dense(10)) + + ctx = default_context() + net.collect_params().initialize(mx.init.Xavier(), ctx=ctx) + data = mx.nd.ones((3, 4)) + mx.profiler.set_state("run") + run_training_iteration(data) + mx.nd.waitall() + mx.profiler.set_state("stop") if __name__ == '__main__':